From bd443420777e87f109f0ded7a9e07af50ab5ebd9 Mon Sep 17 00:00:00 2001 From: Akshay Rai Date: Wed, 3 Sep 2014 03:42:59 -0700 Subject: [PATCH 001/272] HADOOP-6636 : Dr. Elephant heuristic to detect tasks nearing their queue limit. Currently disabled. It will be enabled through pluggable heuristics later. --- .../drelephant/analysis/HeuristicResult.java | 4 + .../heuristics/JobQueueLimitHeuristic.java | 96 +++++++++++++++++++ app/controllers/Application.java | 5 +- app/views/helpJobQueueLimit.scala.html | 55 +++++++++++ .../JobQueueLimitHeuristicTest.java | 68 +++++++++++++ 5 files changed, 226 insertions(+), 2 deletions(-) create mode 100644 app/com/linkedin/drelephant/analysis/heuristics/JobQueueLimitHeuristic.java create mode 100644 app/views/helpJobQueueLimit.scala.html create mode 100644 test/com/linkedin/drelephant/analysis/heuristics/JobQueueLimitHeuristicTest.java diff --git a/app/com/linkedin/drelephant/analysis/HeuristicResult.java b/app/com/linkedin/drelephant/analysis/HeuristicResult.java index c716f665d..3a18ade3e 100644 --- a/app/com/linkedin/drelephant/analysis/HeuristicResult.java +++ b/app/com/linkedin/drelephant/analysis/HeuristicResult.java @@ -63,4 +63,8 @@ public void addDetail(String... parts) { detailsColumns = parts.length; } } + + public void setSeverity(Severity severity){ + this.severity = severity; + } } diff --git a/app/com/linkedin/drelephant/analysis/heuristics/JobQueueLimitHeuristic.java b/app/com/linkedin/drelephant/analysis/heuristics/JobQueueLimitHeuristic.java new file mode 100644 index 000000000..533ea22ba --- /dev/null +++ b/app/com/linkedin/drelephant/analysis/heuristics/JobQueueLimitHeuristic.java @@ -0,0 +1,96 @@ +package com.linkedin.drelephant.analysis.heuristics; + +import java.util.Properties; +import java.util.concurrent.TimeUnit; + +import com.linkedin.drelephant.analysis.Heuristic; +import com.linkedin.drelephant.analysis.HeuristicResult; +import com.linkedin.drelephant.analysis.Severity; +import com.linkedin.drelephant.hadoop.HadoopJobData; +import com.linkedin.drelephant.hadoop.HadoopTaskData; + + +public class JobQueueLimitHeuristic implements Heuristic { + public static final String heuristicName = "Job Queue Timeout Limit"; + + @Override + public String getHeuristicName() { + return heuristicName; + } + + @Override + public HeuristicResult apply(HadoopJobData data) { + HeuristicResult result = new HeuristicResult(heuristicName, Severity.NONE); + Properties jobConf = data.getJobConf(); + long queueTimeoutLimit = TimeUnit.MINUTES.toMillis(15); + + // Fetch the Queue to which the job is submitted. + String queueName = jobConf.getProperty("mapred.job.queue.name"); + if (queueName == null) { + throw new IllegalStateException("Queue Name not found."); + } + + // Compute severity if job is submitted to default queue else set severity to NONE. + HadoopTaskData[] mapTasks = data.getMapperData(); + HadoopTaskData[] redTasks = data.getReducerData(); + Severity[] mapTasksSeverity = new Severity[mapTasks.length]; + Severity[] redTasksSeverity = new Severity[redTasks.length]; + if (queueName.equals("default")) { + result.addDetail("Queue: ", queueName); + result.addDetail("Number of Map tasks", Integer.toString(mapTasks.length)); + result.addDetail("Number of Reduce tasks", Integer.toString(redTasks.length)); + + // Calculate Severity of Mappers + mapTasksSeverity = getTasksSeverity(mapTasks, queueTimeoutLimit); + result.addDetail("Number of Map tasks that are in severe state (14 to 14.5 min)", + Long.toString(getSeverityFrequency(Severity.SEVERE, mapTasksSeverity))); + result.addDetail("Number of Map tasks that are in critical state (over 14.5 min)", + Long.toString(getSeverityFrequency(Severity.CRITICAL, mapTasksSeverity))); + + // Calculate Severity of Reducers + redTasksSeverity = getTasksSeverity(redTasks, queueTimeoutLimit); + result.addDetail("Number of Reduce tasks that are in severe state (14 to 14.5 min)", + Long.toString(getSeverityFrequency(Severity.SEVERE, redTasksSeverity))); + result.addDetail("Number of Reduce tasks that are in critical state (over 14.5 min)", + Long.toString(getSeverityFrequency(Severity.CRITICAL, redTasksSeverity))); + + // Calculate Job severity + result.setSeverity(Severity.max(Severity.max(mapTasksSeverity), Severity.max(redTasksSeverity))); + + } else { + result.addDetail("This Heuristic is not applicable to " + queueName + " queue"); + result.setSeverity(Severity.NONE); + } + return result; + } + + private Severity[] getTasksSeverity(HadoopTaskData[] tasks, long queueTimeout) { + Severity[] tasksSeverity = new Severity[tasks.length]; + int i = 0; + for (HadoopTaskData task : tasks) { + tasksSeverity[i] = getQueueLimitSeverity(task.getRunTime(), queueTimeout); + i++; + } + return tasksSeverity; + } + + private long getSeverityFrequency(Severity severity, Severity[] tasksSeverity) { + long count = 0; + for (Severity taskSeverity : tasksSeverity) { + if (taskSeverity.equals(severity)) { + count++; + } + } + return count; + } + + private Severity getQueueLimitSeverity(long taskTime, long queueTimeout) { + long timeUnit = TimeUnit.SECONDS.toMillis(30); // 30s + if (queueTimeout == 0) { + return Severity.NONE; + } + return Severity.getSeverityAscending(taskTime, queueTimeout - 4 * timeUnit, queueTimeout - 3 * timeUnit, + queueTimeout - 2 * timeUnit, queueTimeout - timeUnit); + } + +} diff --git a/app/controllers/Application.java b/app/controllers/Application.java index 86be3d406..8e7e03fec 100644 --- a/app/controllers/Application.java +++ b/app/controllers/Application.java @@ -9,10 +9,8 @@ import java.util.List; import java.util.Map; - import model.JobResult; import views.html.*; - import play.api.templates.Html; import play.data.DynamicForm; import play.data.Form; @@ -23,6 +21,7 @@ import com.avaje.ebean.ExpressionList; import com.linkedin.drelephant.ElephantAnalyser; import com.linkedin.drelephant.analysis.Severity; +import com.linkedin.drelephant.analysis.heuristics.JobQueueLimitHeuristic; import com.linkedin.drelephant.analysis.heuristics.MapperDataSkewHeuristic; import com.linkedin.drelephant.analysis.heuristics.MapperInputSizeHeuristic; import com.linkedin.drelephant.analysis.heuristics.MapperSpeedHeuristic; @@ -138,6 +137,8 @@ public static Result help() { page = helpReducerTime.render(); } else if (topic.equals(ShuffleSortHeuristic.heuristicName)) { page = helpShuffleSort.render(); + } else if (topic.equals(JobQueueLimitHeuristic.heuristicName)) { + page = helpJobQueueLimit.render(); } else if (topic.equals(ElephantAnalyser.NO_DATA)) { page = helpNoData.render(); } diff --git a/app/views/helpJobQueueLimit.scala.html b/app/views/helpJobQueueLimit.scala.html new file mode 100644 index 000000000..ad426b461 --- /dev/null +++ b/app/views/helpJobQueueLimit.scala.html @@ -0,0 +1,55 @@ +

The task execution limit on the Default queue is 15 minutes (900 seconds). You really, really want to be clear of +that limit or your job may fail as data grows or if you get assigned some slow nodes. So while running a job on the +default queue make sure each task runs between 5 to 15 minutes.

+

This analysis shows an indication of how long your tasks have taken to run on the Default Queue.

+
Example
+

+

+ +

Job Queue Limit

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Severity: Critical
Queue: default
Number of Map tasks50
Number of Reduce tasks2
Number of Map tasks that are in severe state (14 to 14.5 min)3
Number of Map tasks that are in critical state (over 14.5 min)0
Number of Reduce tasks that are in severe state (14 to 14.5 min)5
Number of Reduce tasks that are in critical state (over 14.5 min)1
+
+
+

+

Suggestions

+

+ If a Job has task times approaching 15 minutes then you must tune down your task runtimes(usually by increasing the + number of tasks) or by moving it to the marathon queue. The marathon queue is built for long running jobs so that + faster jobs can finish in a reasonable time without having to worry about resources being available. +

\ No newline at end of file diff --git a/test/com/linkedin/drelephant/analysis/heuristics/JobQueueLimitHeuristicTest.java b/test/com/linkedin/drelephant/analysis/heuristics/JobQueueLimitHeuristicTest.java new file mode 100644 index 000000000..d65dc052a --- /dev/null +++ b/test/com/linkedin/drelephant/analysis/heuristics/JobQueueLimitHeuristicTest.java @@ -0,0 +1,68 @@ +package com.linkedin.drelephant.analysis.heuristics; + +import java.io.IOException; +import java.util.Properties; + +import org.junit.Test; + +import junit.framework.TestCase; + +import com.linkedin.drelephant.analysis.Constants; +import com.linkedin.drelephant.analysis.Heuristic; +import com.linkedin.drelephant.analysis.HeuristicResult; +import com.linkedin.drelephant.analysis.Severity; +import com.linkedin.drelephant.hadoop.HadoopCounterHolder; +import com.linkedin.drelephant.hadoop.HadoopJobData; +import com.linkedin.drelephant.hadoop.HadoopTaskData; + + +public class JobQueueLimitHeuristicTest extends TestCase { + + Heuristic heuristic = new JobQueueLimitHeuristic(); + private static final int numTasks = Constants.SHUFFLE_SORT_MAX_SAMPLE_SIZE; + + @Test + public void testRuntimeCritical() throws IOException { + assertEquals(Severity.CRITICAL, analyzeJob((long) (14.5 * 60 * 1000), "default")); + } + + public void testRuntimeSevere() throws IOException { + assertEquals(Severity.SEVERE, analyzeJob(14 * 60 * 1000, "default")); + } + + public void testRuntimeModerate() throws IOException { + assertEquals(Severity.MODERATE, analyzeJob((long) (13.5 * 60 * 1000), "default")); + } + + public void testRuntimeLow() throws IOException { + assertEquals(Severity.LOW, analyzeJob(13 * 60 * 1000, "default")); + } + + public void testRuntimeNone() throws IOException { + assertEquals(Severity.NONE, analyzeJob(12 * 60 * 1000, "default")); + } + + public void testNonDefaultRuntimeNone() throws IOException { + assertEquals(Severity.NONE, analyzeJob(15 * 60 * 1000, "non-default")); + } + + private Severity analyzeJob(long runtime, String queueName) throws IOException { + HadoopCounterHolder dummyCounter = new HadoopCounterHolder(null); + HadoopTaskData[] mappers = new HadoopTaskData[2 * numTasks / 3]; + HadoopTaskData[] reducers = new HadoopTaskData[numTasks / 3]; + Properties jobConf = new Properties(); + jobConf.put("mapred.job.queue.name", queueName); + int i = 0; + for (; i < 2 * numTasks / 3; i++) { + mappers[i] = new HadoopTaskData(dummyCounter, new long[] { 0, runtime, 0, 0 }); + } + for (i = 0; i < numTasks / 3; i++) { + reducers[i] = new HadoopTaskData(dummyCounter, new long[] { 0, runtime, 0, 0 }); + } + HadoopJobData data = + new HadoopJobData().setCounters(dummyCounter).setReducerData(reducers).setMapperData(mappers) + .setJobConf(jobConf); + HeuristicResult result = heuristic.apply(data); + return result.getSeverity(); + } +} From 3d9223cee9f389252f4d9732ac439ad3cd95a39d Mon Sep 17 00:00:00 2001 From: Mark Wagner Date: Tue, 9 Sep 2014 14:10:37 -0700 Subject: [PATCH 002/272] HADOOP-7028: Update version to 0.5-SNAPSHOT --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 1bf6c1ebe..59b0d0167 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ import play.Project._ name := "dr-elephant" -version := "0.4" +version := "0.5-SNAPSHOT" javacOptions in Compile ++= Seq("-source", "1.6", "-target", "1.6") From 87defee68fb56c43c87734eb047a2d6a8dc15bd1 Mon Sep 17 00:00:00 2001 From: fli Date: Fri, 12 Sep 2014 14:50:59 -0700 Subject: [PATCH 003/272] HADOOP-6642: Enforce code style conventions for Dr. Elephant --- app/Global.java | 10 +- app/com/linkedin/drelephant/DrElephant.java | 10 +- .../linkedin/drelephant/ElephantAnalyser.java | 114 +++---- .../linkedin/drelephant/ElephantFetcher.java | 6 +- .../drelephant/ElephantFetcherClassic.java | 62 ++-- .../drelephant/ElephantFetcherYarn.java | 128 ++++---- .../linkedin/drelephant/ElephantRunner.java | 33 +- .../linkedin/drelephant/InfoExtractor.java | 6 +- .../drelephant/analysis/Constants.java | 27 +- .../drelephant/analysis/Heuristic.java | 5 +- .../drelephant/analysis/HeuristicResult.java | 105 +++---- .../drelephant/analysis/Severity.java | 157 +++++----- .../heuristics/GenericDataSkewHeuristic.java | 99 +++--- .../heuristics/JobQueueLimitHeuristic.java | 6 +- .../heuristics/MapperDataSkewHeuristic.java | 19 +- .../heuristics/MapperInputSizeHeuristic.java | 103 +++---- .../heuristics/MapperSpeedHeuristic.java | 111 ++++--- .../heuristics/ReducerDataSkewHeuristic.java | 17 +- .../heuristics/ReducerTimeHeuristic.java | 105 +++---- .../heuristics/ShuffleSortHeuristic.java | 98 +++--- .../hadoop/HadoopCounterHolder.java | 140 +++++---- .../drelephant/hadoop/HadoopJobData.java | 54 ++-- .../drelephant/hadoop/HadoopSecurity.java | 30 +- .../drelephant/hadoop/HadoopTaskData.java | 104 +++---- .../linkedin/drelephant/math/Statistics.java | 289 +++++++++--------- .../drelephant/notifications/EmailThread.java | 151 ++++----- app/com/linkedin/drelephant/util/Utils.java | 118 +++---- app/controllers/Application.java | 53 ++-- app/model/JobHeuristicResult.java | 48 +-- app/model/JobResult.java | 11 +- app/model/JobType.java | 27 +- app/model/StringResult.java | 3 +- app/views/help.scala.html | 2 +- app/views/search.scala.html | 2 +- .../JobQueueLimitHeuristicTest.java | 14 +- .../MapperDataSkewHeuristicTest.java | 24 +- .../MapperInputSizeHeuristicTest.java | 28 +- .../heuristics/MapperSpeedHeuristicTest.java | 30 +- .../ReducerDataSkewHeuristicTest.java | 26 +- .../heuristics/ReducerTimeHeuristicTest.java | 28 +- .../heuristics/ShuffleSortHeuristicTest.java | 39 +-- 41 files changed, 1225 insertions(+), 1217 deletions(-) diff --git a/app/Global.java b/app/Global.java index 02b04175d..ed2ca80fd 100644 --- a/app/Global.java +++ b/app/Global.java @@ -13,7 +13,7 @@ public class Global extends GlobalSettings { - DrElephant drElephant; + DrElephant _drElephant; public void onStart(Application app) { Logger.info("Application has started"); @@ -21,8 +21,8 @@ public void onStart(Application app) { fixJavaKerberos(); try { - drElephant = new DrElephant(); - drElephant.start(); + _drElephant = new DrElephant(); + _drElephant.start(); } catch (IOException e) { Logger.error("Application start failed...", e); } @@ -30,8 +30,8 @@ public void onStart(Application app) { public void onStop(Application app) { Logger.info("Application shutdown..."); - if (drElephant != null) { - drElephant.kill(); + if (_drElephant != null) { + _drElephant.kill(); } } diff --git a/app/com/linkedin/drelephant/DrElephant.java b/app/com/linkedin/drelephant/DrElephant.java index c471029cf..df34b9f7b 100644 --- a/app/com/linkedin/drelephant/DrElephant.java +++ b/app/com/linkedin/drelephant/DrElephant.java @@ -4,20 +4,20 @@ public class DrElephant extends Thread { - private ElephantRunner elephant; + private ElephantRunner _elephant; public DrElephant() throws IOException { - elephant = new ElephantRunner(); + _elephant = new ElephantRunner(); } @Override public void run() { - elephant.run(); + _elephant.run(); } public void kill() { - if (elephant != null) { - elephant.kill(); + if (_elephant != null) { + _elephant.kill(); } } } diff --git a/app/com/linkedin/drelephant/ElephantAnalyser.java b/app/com/linkedin/drelephant/ElephantAnalyser.java index 6ad090dfa..28d2c6034 100644 --- a/app/com/linkedin/drelephant/ElephantAnalyser.java +++ b/app/com/linkedin/drelephant/ElephantAnalyser.java @@ -9,75 +9,79 @@ import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.analysis.heuristics.*; +import com.linkedin.drelephant.analysis.heuristics.MapperDataSkewHeuristic; +import com.linkedin.drelephant.analysis.heuristics.MapperInputSizeHeuristic; +import com.linkedin.drelephant.analysis.heuristics.MapperSpeedHeuristic; +import com.linkedin.drelephant.analysis.heuristics.ReducerDataSkewHeuristic; +import com.linkedin.drelephant.analysis.heuristics.ReducerTimeHeuristic; +import com.linkedin.drelephant.analysis.heuristics.ShuffleSortHeuristic; import com.linkedin.drelephant.hadoop.HadoopJobData; import model.JobType; -public class ElephantAnalyser { - public static final String NO_DATA = "No Data Received"; - private static final ElephantAnalyser instance = new ElephantAnalyser(); - private HeuristicResult nodata; - private List heuristics = new ArrayList(); - public List heuristicNames = new ArrayList(); +public class ElephantAnalyser { + public static final String NO_DATA = "No Data Received"; + private static final ElephantAnalyser ANALYZER = new ElephantAnalyser(); - public ElephantAnalyser() { - nodata = new HeuristicResult(NO_DATA, Severity.LOW); - addHeuristic(new MapperDataSkewHeuristic()); - addHeuristic(new ReducerDataSkewHeuristic()); - addHeuristic(new MapperInputSizeHeuristic()); - addHeuristic(new MapperSpeedHeuristic()); - addHeuristic(new ReducerTimeHeuristic()); - addHeuristic(new ShuffleSortHeuristic()); - } + private HeuristicResult _nodata; + private List _heuristics = new ArrayList(); + public List _heuristicNames = new ArrayList(); - public void addHeuristic(Heuristic heuristic) { - heuristics.add(heuristic); - heuristicNames.add(heuristic.getHeuristicName()); - } + public ElephantAnalyser() { + _nodata = new HeuristicResult(NO_DATA, Severity.LOW); + addHeuristic(new MapperDataSkewHeuristic()); + addHeuristic(new ReducerDataSkewHeuristic()); + addHeuristic(new MapperInputSizeHeuristic()); + addHeuristic(new MapperSpeedHeuristic()); + addHeuristic(new ReducerTimeHeuristic()); + addHeuristic(new ShuffleSortHeuristic()); + } - public HeuristicResult[] analyse(HadoopJobData data) { - if (data.getMapperData().length == 0 && data.getReducerData().length == 0) { - return new HeuristicResult[]{nodata}; - } + public void addHeuristic(Heuristic heuristic) { + _heuristics.add(heuristic); + _heuristicNames.add(heuristic.getHeuristicName()); + } - List results = new ArrayList(); - for (Heuristic heuristic : heuristics) { - results.add(heuristic.apply(data)); - } - return results.toArray(new HeuristicResult[results.size()]); + public HeuristicResult[] analyse(HadoopJobData data) { + if (data.getMapperData().length == 0 && data.getReducerData().length == 0) { + return new HeuristicResult[] { _nodata }; } - public JobType getJobType(HadoopJobData data) { - String pigVersion = data.getJobConf().getProperty("pig.version"); - if (pigVersion != null && !pigVersion.isEmpty()) { - return JobType.PIG; - } - String hiveMapredMode = data.getJobConf().getProperty("hive.mapred.mode"); - if (hiveMapredMode != null && !hiveMapredMode.isEmpty()) { - return JobType.HIVE; - } - - return JobType.HADOOPJAVA; + List results = new ArrayList(); + for (Heuristic heuristic : _heuristics) { + results.add(heuristic.apply(data)); } + return results.toArray(new HeuristicResult[results.size()]); + } - - public Map getMetaUrls(HadoopJobData data) { - Map result = new HashMap(); - final String prefix = "meta.url."; - Properties jobConf = data.getJobConf(); - for (Map.Entry entry : jobConf.entrySet()) { - if (entry.getKey().toString().startsWith(prefix)) { - String key = entry.getKey().toString(); - String value = jobConf.getProperty(key); - result.put(key.substring(prefix.length()), value); - } - } - return result; + public JobType getJobType(HadoopJobData data) { + String pigVersion = data.getJobConf().getProperty("pig.version"); + if (pigVersion != null && !pigVersion.isEmpty()) { + return JobType.PIG; } + String hiveMapredMode = data.getJobConf().getProperty("hive.mapred.mode"); + if (hiveMapredMode != null && !hiveMapredMode.isEmpty()) { + return JobType.HIVE; + } + return JobType.HADOOPJAVA; + } - public static ElephantAnalyser instance() { - return instance; + public Map getMetaUrls(HadoopJobData data) { + Map result = new HashMap(); + final String prefix = "meta.url."; + Properties jobConf = data.getJobConf(); + for (Map.Entry entry : jobConf.entrySet()) { + if (entry.getKey().toString().startsWith(prefix)) { + String key = entry.getKey().toString(); + String value = jobConf.getProperty(key); + result.put(key.substring(prefix.length()), value); + } } + return result; + } + + public static ElephantAnalyser instance() { + return ANALYZER; + } } diff --git a/app/com/linkedin/drelephant/ElephantFetcher.java b/app/com/linkedin/drelephant/ElephantFetcher.java index 24f5afe62..d4781cae8 100644 --- a/app/com/linkedin/drelephant/ElephantFetcher.java +++ b/app/com/linkedin/drelephant/ElephantFetcher.java @@ -8,11 +8,9 @@ public interface ElephantFetcher { - public List fetchJobList() throws IOException, AuthenticationException; - public void fetchJobData(HadoopJobData job_data) throws IOException, AuthenticationException; - - public void finishJob(HadoopJobData job_data, boolean success); + public void fetchJobData(HadoopJobData jobData) throws IOException, AuthenticationException; + public void finishJob(HadoopJobData jobData, boolean success); } diff --git a/app/com/linkedin/drelephant/ElephantFetcherClassic.java b/app/com/linkedin/drelephant/ElephantFetcherClassic.java index de5bd3783..c981237ac 100644 --- a/app/com/linkedin/drelephant/ElephantFetcherClassic.java +++ b/app/com/linkedin/drelephant/ElephantFetcherClassic.java @@ -11,7 +11,13 @@ import org.apache.commons.io.IOUtils; import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.mapred.*; +import org.apache.hadoop.mapred.Counters; +import org.apache.hadoop.mapred.JobClient; +import org.apache.hadoop.mapred.JobConf; +import org.apache.hadoop.mapred.JobID; +import org.apache.hadoop.mapred.JobStatus; +import org.apache.hadoop.mapred.RunningJob; +import org.apache.hadoop.mapred.TaskReport; import org.apache.hadoop.security.authentication.client.AuthenticatedURL; import org.apache.hadoop.security.authentication.client.AuthenticationException; import org.apache.log4j.Logger; @@ -36,11 +42,11 @@ public class ElephantFetcherClassic implements ElephantFetcher { private static final Logger logger = Logger.getLogger(ElephantFetcher.class); - private static SimpleDateFormat dateFormat = new SimpleDateFormat("d-MMM-yyyy HH:mm:ss"); + private static final SimpleDateFormat DATEFORMAT = new SimpleDateFormat("d-MMM-yyyy HH:mm:ss"); - private JobClient jobClient; - private Set previousJobs = new HashSet(); - private boolean firstRun = true; + private JobClient _jobClient; + private Set _previousJobs = new HashSet(); + private boolean _firstRun = true; public ElephantFetcherClassic(Configuration hadoopConf) throws IOException { init(hadoopConf); @@ -48,19 +54,19 @@ public ElephantFetcherClassic(Configuration hadoopConf) throws IOException { private void init(Configuration hadoopConf) throws IOException { logger.info("Connecting to the jobtracker"); - jobClient = new JobClient(new JobConf(hadoopConf)); + _jobClient = new JobClient(new JobConf(hadoopConf)); } public List fetchJobList() throws IOException { JobStatus[] result = null; - result = jobClient.getAllJobs(); + result = _jobClient.getAllJobs(); if (result == null) { throw new IOException("Error fetching joblist from jobtracker."); } Set successJobs = filterSuccessfulJobs(result); - successJobs = filterPreviousJobs(successJobs, previousJobs); + successJobs = filterPreviousJobs(successJobs, _previousJobs); List jobList = new ArrayList(); for (String jobId : successJobs) { @@ -71,16 +77,16 @@ public List fetchJobList() throws IOException { public void finishJob(HadoopJobData jobData, boolean success) { if (success) { - previousJobs.add(jobData.getJobId()); + _previousJobs.add(jobData.getJobId()); } } public void fetchJobData(HadoopJobData jobData) throws IOException, AuthenticationException { - JobID job_id = JobID.forName(jobData.getJobId()); + JobID jobId = JobID.forName(jobData.getJobId()); - RunningJob job = getJob(job_id); + RunningJob job = getJob(jobId); if (job == null) { - throw new IOException("Unable to fetch job data from Jobtracker, job id = " + job_id); + throw new IOException("Unable to fetch job data from Jobtracker, job id = " + jobId); } JobStatus status = job.getJobStatus(); @@ -91,8 +97,8 @@ public void fetchJobData(HadoopJobData jobData) throws IOException, Authenticati HadoopCounterHolder counterHolder = fetchCounter(job.getCounters()); - TaskReport[] mapperTasks = getMapTaskReports(job_id); - TaskReport[] reducerTasks = getReduceTaskReports(job_id); + TaskReport[] mapperTasks = getMapTaskReports(jobId); + TaskReport[] reducerTasks = getReduceTaskReports(jobId); String jobTrackingUrl = job.getTrackingURL(); int sampleSize = Constants.SHUFFLE_SORT_MAX_SAMPLE_SIZE; @@ -115,16 +121,16 @@ public void fetchJobData(HadoopJobData jobData) throws IOException, Authenticati } - private RunningJob getJob(JobID job_id) throws IOException { - return jobClient.getJob(job_id); + private RunningJob getJob(JobID jobId) throws IOException { + return _jobClient.getJob(jobId); } - private TaskReport[] getMapTaskReports(JobID job_id) throws IOException { - return jobClient.getMapTaskReports(job_id); + private TaskReport[] getMapTaskReports(JobID jobId) throws IOException { + return _jobClient.getMapTaskReports(jobId); } - private TaskReport[] getReduceTaskReports(JobID job_id) throws IOException { - return jobClient.getReduceTaskReports(job_id); + private TaskReport[] getReduceTaskReports(JobID jobId) throws IOException { + return _jobClient.getReduceTaskReports(jobId); } private Properties getJobConf(RunningJob job) throws IOException, AuthenticationException { @@ -171,7 +177,7 @@ private Set filterSuccessfulJobs(JobStatus[] jobs) { private Set filterPreviousJobs(Set jobs, Set previousJobs) { logger.info("Cleaning up previous runs."); // On first run, check against DB - if (firstRun) { + if (_firstRun) { Set newJobs = new HashSet(); for (String jobId : jobs) { JobResult prevResult = JobResult.find.byId(jobId); @@ -184,7 +190,7 @@ private Set filterPreviousJobs(Set jobs, Set previousJob } } jobs = newJobs; - firstRun = false; + _firstRun = false; } else { // Remove untracked jobs previousJobs.retainAll(jobs); @@ -264,10 +270,10 @@ private long[] tryExtractDetailFromRow(Element row, boolean isReducer) throws Pa if (finishTime.contains("(")) { finishTime = finishTime.substring(0, finishTime.indexOf("(") - 1); } - long start = dateFormat.parse(startTime).getTime(); - long shuffle = dateFormat.parse(shuffleTime).getTime(); - long sort = dateFormat.parse(sortTime).getTime(); - long finish = dateFormat.parse(finishTime).getTime(); + long start = DATEFORMAT.parse(startTime).getTime(); + long shuffle = DATEFORMAT.parse(shuffleTime).getTime(); + long sort = DATEFORMAT.parse(sortTime).getTime(); + long finish = DATEFORMAT.parse(finishTime).getTime(); long shuffleDuration = (shuffle - start); long sortDuration = (sort - shuffle); @@ -279,8 +285,8 @@ private long[] tryExtractDetailFromRow(Element row, boolean isReducer) throws Pa if (finishTime.contains("(")) { finishTime = finishTime.substring(0, finishTime.indexOf("(") - 1); } - long start = dateFormat.parse(startTime).getTime(); - long finish = dateFormat.parse(finishTime).getTime(); + long start = DATEFORMAT.parse(startTime).getTime(); + long finish = DATEFORMAT.parse(finishTime).getTime(); return new long[] { start, finish, 0, 0 }; } } diff --git a/app/com/linkedin/drelephant/ElephantFetcherYarn.java b/app/com/linkedin/drelephant/ElephantFetcherYarn.java index 3814d1fb7..98c623217 100644 --- a/app/com/linkedin/drelephant/ElephantFetcherYarn.java +++ b/app/com/linkedin/drelephant/ElephantFetcherYarn.java @@ -33,12 +33,12 @@ public class ElephantFetcherYarn implements ElephantFetcher { private static final Logger logger = Logger.getLogger(ElephantFetcher.class); - private RetryFactory retryFactory; - private URLFactory urlFactory; - private JSONFactory jsonFactory; - private boolean firstTime = true; - private long lastTime = 0; - private long currentTime = 0; + private RetryFactory _retryFactory; + private URLFactory _urlFactory; + private JSONFactory _jsonFactory; + private boolean _firstRun = true; + private long _lastTime = 0; + private long _currentTime = 0; public ElephantFetcherYarn(Configuration hadoopConf) throws IOException { init(hadoopConf); @@ -47,9 +47,9 @@ public ElephantFetcherYarn(Configuration hadoopConf) throws IOException { private void init(Configuration hadoopConf) throws IOException { logger.info("Connecting to the job history server..."); String jhistoryAddr = hadoopConf.get("mapreduce.jobhistory.webapp.address"); - urlFactory = new URLFactory(jhistoryAddr); - jsonFactory = new JSONFactory(); - retryFactory = new RetryFactory(); + _urlFactory = new URLFactory(jhistoryAddr); + _jsonFactory = new JSONFactory(); + _retryFactory = new RetryFactory(); logger.info("Connection success."); } @@ -63,18 +63,18 @@ public List fetchJobList() throws IOException, AuthenticationExce List jobList; - currentTime = System.currentTimeMillis(); - URL joblistURL = urlFactory.fetchJobListURL(lastTime, currentTime); + _currentTime = System.currentTimeMillis(); + URL joblistURL = _urlFactory.fetchJobListURL(_lastTime, _currentTime); - jobList = jsonFactory.getJobData(joblistURL, firstTime); - if (firstTime) { - firstTime = false; + jobList = _jsonFactory.getJobData(joblistURL, _firstRun); + if (_firstRun) { + _firstRun = false; } else { // If not first time, also fetch jobs that need to retry - jobList.addAll(retryFactory.getJobs()); + jobList.addAll(_retryFactory.getJobs()); } - lastTime = currentTime; + _lastTime = _currentTime; return jobList; } @@ -95,7 +95,7 @@ public void finishJob(HadoopJobData jobData, boolean success) { if (!success) { clearJobData(jobData); // Add to retry list - retryFactory.addJob(jobData); + _retryFactory.addJob(jobData); } } @@ -104,18 +104,18 @@ public void fetchJobData(HadoopJobData jobData) throws IOException, Authenticati String jobId = jobData.getJobId(); // Fetch job counter - URL jobCounterURL = urlFactory.getJobCounterURL(jobId); - HadoopCounterHolder jobCounter = jsonFactory.getJobCounter(jobCounterURL); + URL jobCounterURL = _urlFactory.getJobCounterURL(jobId); + HadoopCounterHolder jobCounter = _jsonFactory.getJobCounter(jobCounterURL); // Fetch job config - URL jobConfigURL = urlFactory.getJobConfigURL(jobId); - Properties jobConf = jsonFactory.getProperties(jobConfigURL); + URL jobConfigURL = _urlFactory.getJobConfigURL(jobId); + Properties jobConf = _jsonFactory.getProperties(jobConfigURL); // Fetch task data - URL taskListURL = urlFactory.getTaskListURL(jobId); + URL taskListURL = _urlFactory.getTaskListURL(jobId); List mapperList = new ArrayList(); List reducerList = new ArrayList(); - jsonFactory.getTaskDataAll(taskListURL, jobId, mapperList, reducerList); + _jsonFactory.getTaskDataAll(taskListURL, jobId, mapperList, reducerList); HadoopTaskData[] mapperData = mapperList.toArray(new HadoopTaskData[mapperList.size()]); HadoopTaskData[] reducerData = reducerList.toArray(new HadoopTaskData[reducerList.size()]); @@ -124,26 +124,26 @@ public void fetchJobData(HadoopJobData jobData) throws IOException, Authenticati } private String getJobDetailURL(String jobId) { - return urlFactory.getJobDetailURLString(jobId); + return _urlFactory.getJobDetailURLString(jobId); } private URL getTaskCounterURL(String jobId, String taskId) throws MalformedURLException { - return urlFactory.getTaskCounterURL(jobId, taskId); + return _urlFactory.getTaskCounterURL(jobId, taskId); } private URL getTaskAttemptURL(String jobId, String taskId, String attemptId) throws MalformedURLException { - return urlFactory.getTaskAttemptURL(jobId, taskId, attemptId); + return _urlFactory.getTaskAttemptURL(jobId, taskId, attemptId); } private class URLFactory { - private String root; - private String restRoot; + private String _root; + private String _restRoot; public URLFactory(String hserverAddr) throws IOException { - root = "http://" + hserverAddr; - restRoot = "http://" + hserverAddr + "/ws/v1/history/mapreduce/jobs"; - verifyURL(restRoot); + _root = "http://" + hserverAddr; + _restRoot = "http://" + hserverAddr + "/ws/v1/history/mapreduce/jobs"; + verifyURL(_restRoot); } private void verifyURL(String url) throws IOException { @@ -154,52 +154,52 @@ private void verifyURL(String url) throws IOException { } private String getJobDetailURLString(String jobId) { - return root + "/jobhistory/job/" + jobId; + return _root + "/jobhistory/job/" + jobId; } private URL fetchJobListURL(long startTime, long endTime) throws MalformedURLException { - return new URL(restRoot + "?finishedTimeBegin=" + startTime + "&finishedTimeEnd=" + endTime + "&state=SUCCEEDED"); + return new URL(_restRoot + "?finishedTimeBegin=" + startTime + "&finishedTimeEnd=" + endTime + "&state=SUCCEEDED"); } private URL getJobConfigURL(String jobId) throws MalformedURLException { - return new URL(restRoot + "/" + jobId + "/conf"); + return new URL(_restRoot + "/" + jobId + "/conf"); } private URL getJobCounterURL(String jobId) throws MalformedURLException { - return new URL(restRoot + "/" + jobId + "/counters"); + return new URL(_restRoot + "/" + jobId + "/counters"); } private URL getTaskListURL(String jobId) throws MalformedURLException { - return new URL(restRoot + "/" + jobId + "/tasks"); + return new URL(_restRoot + "/" + jobId + "/tasks"); } private URL getTaskCounterURL(String jobId, String taskId) throws MalformedURLException { - return new URL(restRoot + "/" + jobId + "/tasks/" + taskId + "/counters"); + return new URL(_restRoot + "/" + jobId + "/tasks/" + taskId + "/counters"); } private URL getTaskAttemptURL(String jobId, String taskId, String attemptId) throws MalformedURLException { - return new URL(restRoot + "/" + jobId + "/tasks/" + taskId + "/attempts/" + attemptId); + return new URL(_restRoot + "/" + jobId + "/tasks/" + taskId + "/attempts/" + attemptId); } } private class JSONFactory { - private ObjectMapper mapper = new ObjectMapper(); - private AuthenticatedURL.Token token = new AuthenticatedURL.Token(); - private AuthenticatedURL authenticatedURL = new AuthenticatedURL(); - private Set counterSet = new HashSet();; + private ObjectMapper _objMapper = new ObjectMapper(); + private AuthenticatedURL.Token _authToken = new AuthenticatedURL.Token(); + private AuthenticatedURL _authURL = new AuthenticatedURL(); + private Set _counterSet = new HashSet();; public JSONFactory() { // Store the set of counters we want to fetch for (CounterName counter : CounterName.values()) { - counterSet.add(counter.getName()); + _counterSet.add(counter.getName()); } } private List getJobData(URL url, boolean checkDB) throws IOException, AuthenticationException { List jobList = new ArrayList(); - HttpURLConnection conn = authenticatedURL.openConnection(url, token); - JsonNode rootNode = mapper.readTree(conn.getInputStream()); + HttpURLConnection conn = _authURL.openConnection(url, _authToken); + JsonNode rootNode = _objMapper.readTree(conn.getInputStream()); JsonNode jobs = rootNode.path("jobs").path("job"); for (JsonNode job : jobs) { @@ -223,8 +223,8 @@ private List getJobData(URL url, boolean checkDB) throws IOExcept private Properties getProperties(URL url) throws IOException, AuthenticationException { Properties jobConf = new Properties(); - HttpURLConnection conn = authenticatedURL.openConnection(url, token); - JsonNode rootNode = mapper.readTree(conn.getInputStream()); + HttpURLConnection conn = _authURL.openConnection(url, _authToken); + JsonNode rootNode = _objMapper.readTree(conn.getInputStream()); JsonNode configs = rootNode.path("conf").path("property"); for (JsonNode conf : configs) { @@ -238,14 +238,14 @@ private Properties getProperties(URL url) throws IOException, AuthenticationExce private HadoopCounterHolder getJobCounter(URL url) throws IOException, AuthenticationException { Map counterMap = new EnumMap(CounterName.class); - HttpURLConnection conn = authenticatedURL.openConnection(url, token); - JsonNode rootNode = mapper.readTree(conn.getInputStream()); + HttpURLConnection conn = _authURL.openConnection(url, _authToken); + JsonNode rootNode = _objMapper.readTree(conn.getInputStream()); JsonNode groups = rootNode.path("jobCounters").path("counterGroup"); for (JsonNode group : groups) { for (JsonNode counter : group.path("counter")) { String name = counter.get("name").getValueAsText(); - if (counterSet.contains(name)) { + if (_counterSet.contains(name)) { // This is a counter we want to fetch long val = counter.get("totalCounterValue").getLongValue(); counterMap.put(CounterName.valueOf(name), val); @@ -264,14 +264,14 @@ private HadoopCounterHolder getJobCounter(URL url) throws IOException, Authentic private HadoopCounterHolder getTaskCounter(URL url) throws IOException, AuthenticationException { Map counterMap = new EnumMap(CounterName.class); - HttpURLConnection conn = authenticatedURL.openConnection(url, token); - JsonNode rootNode = mapper.readTree(conn.getInputStream()); + HttpURLConnection conn = _authURL.openConnection(url, _authToken); + JsonNode rootNode = _objMapper.readTree(conn.getInputStream()); JsonNode groups = rootNode.path("jobTaskCounters").path("taskCounterGroup"); for (JsonNode group : groups) { for (JsonNode counter : group.path("counter")) { String name = counter.get("name").getValueAsText(); - if (counterSet.contains(name)) { + if (_counterSet.contains(name)) { long val = counter.get("value").getLongValue(); counterMap.put(CounterName.valueOf(name), val); } @@ -287,8 +287,8 @@ private HadoopCounterHolder getTaskCounter(URL url) throws IOException, Authenti } private long[] getTaskExecTime(URL url) throws IOException, AuthenticationException { - HttpURLConnection conn = authenticatedURL.openConnection(url, token); - JsonNode rootNode = mapper.readTree(conn.getInputStream()); + HttpURLConnection conn = _authURL.openConnection(url, _authToken); + JsonNode rootNode = _objMapper.readTree(conn.getInputStream()); JsonNode taskAttempt = rootNode.path("taskAttempt"); long startTime = taskAttempt.get("startTime").getLongValue(); @@ -310,8 +310,8 @@ private long[] getTaskExecTime(URL url) throws IOException, AuthenticationExcept private void getTaskDataAll(URL url, String jobId, List mapperList, List reducerList) throws IOException, AuthenticationException { - HttpURLConnection conn = authenticatedURL.openConnection(url, token); - JsonNode rootNode = mapper.readTree(conn.getInputStream()); + HttpURLConnection conn = _authURL.openConnection(url, _authToken); + JsonNode rootNode = _objMapper.readTree(conn.getInputStream()); JsonNode tasks = rootNode.path("tasks").path("task"); for (JsonNode task : tasks) { @@ -337,27 +337,27 @@ private void getTaskDataAll(URL url, String jobId, List mapperLi private class RetryFactory { private static final int DEFAULT_RETRY = 3; - private Map retryMap = new HashMap(); + private Map _retryMap = new HashMap(); private void addJob(HadoopJobData job) { - if (retryMap.containsKey(job)) { + if (_retryMap.containsKey(job)) { // This is old retry job - int retryLeft = retryMap.get(job); + int retryLeft = _retryMap.get(job); if (retryLeft == 1) { // Drop job on max retries logger.error("Drop job. Reason: reach max retry for job id=" + job.getJobId()); - retryMap.remove(job); + _retryMap.remove(job); } else { - retryMap.put(job, retryLeft - 1); + _retryMap.put(job, retryLeft - 1); } } else { // This is new retry job - retryMap.put(job, DEFAULT_RETRY); + _retryMap.put(job, DEFAULT_RETRY); } } private List getJobs() { - return Lists.newArrayList(retryMap.keySet()); + return Lists.newArrayList(_retryMap.keySet()); } } diff --git a/app/com/linkedin/drelephant/ElephantRunner.java b/app/com/linkedin/drelephant/ElephantRunner.java index 66d540758..9667deb82 100644 --- a/app/com/linkedin/drelephant/ElephantRunner.java +++ b/app/com/linkedin/drelephant/ElephantRunner.java @@ -24,21 +24,21 @@ public class ElephantRunner implements Runnable { private static final long WAIT_INTERVAL = 10 * 1000; private static final Logger logger = Logger.getLogger(ElephantRunner.class); - private AtomicBoolean running = new AtomicBoolean(true); - private EmailThread emailer = new EmailThread(); - private HadoopSecurity hadoopSecurity; - private InfoExtractor urlRetriever = new InfoExtractor(); + private AtomicBoolean _running = new AtomicBoolean(true); + private EmailThread _emailer = new EmailThread(); + private HadoopSecurity _hadoopSecurity; + private InfoExtractor _urlRetriever = new InfoExtractor(); @Override public void run() { logger.info("Dr.elephant has started"); try { - hadoopSecurity = new HadoopSecurity(); - hadoopSecurity.doAs(new PrivilegedAction() { + _hadoopSecurity = new HadoopSecurity(); + _hadoopSecurity.doAs(new PrivilegedAction() { @Override public Void run() { Constants.load(); - emailer.start(); + _emailer.start(); long lastRun; ElephantFetcher fetcher = null; @@ -54,14 +54,15 @@ public Void run() { } else if (framework.equals("classic")) { fetcher = new ElephantFetcherClassic(hadoopConf); } else { - logger.error("mapreduce.framework.name must be either 'classic' or 'yarn'. Current value: "+framework); + logger.error("mapreduce.framework.name must be either 'classic' or 'yarn'. Current: " + framework); return null; } } else { if (hadoopConf.get("mapred.job.tracker.http.address") != null) { fetcher = new ElephantFetcherClassic(hadoopConf); } else { - logger.error("Either mapreduce.framework.name or mapred.job.tracker.http.address must be set. Plseae check your configuration."); + logger.error("Either mapreduce.framework.name or mapred.job.tracker.http.address must be set."); + logger.error("Plseae check your configuration."); return null; } } @@ -71,13 +72,13 @@ public Void run() { return null; } - while (running.get()) { + while (_running.get()) { lastRun = System.currentTimeMillis(); logger.info("Fetching job list....."); try { - hadoopSecurity.checkLogin(); + _hadoopSecurity.checkLogin(); } catch (IOException e) { logger.info("Error with hadoop kerberos login", e); continue; @@ -109,7 +110,7 @@ public Void run() { // Wait for long enough long nextRun = lastRun + WAIT_INTERVAL; long waitTime = nextRun - System.currentTimeMillis(); - while (running.get() && waitTime > 0) { + while (_running.get() && waitTime > 0) { try { Thread.sleep(waitTime); } catch (InterruptedException e) { @@ -166,15 +167,15 @@ private void analyzeJob(HadoopJobData jobData) { } result.severity = worstSeverity; - urlRetriever.retrieveURLs(result, jobData); + _urlRetriever.retrieveURLs(result, jobData); result.save(); - emailer.enqueue(result); + _emailer.enqueue(result); } public void kill() { - running.set(false); - emailer.kill(); + _running.set(false); + _emailer.kill(); } } diff --git a/app/com/linkedin/drelephant/InfoExtractor.java b/app/com/linkedin/drelephant/InfoExtractor.java index 9cab589ce..a7f02f5f2 100644 --- a/app/com/linkedin/drelephant/InfoExtractor.java +++ b/app/com/linkedin/drelephant/InfoExtractor.java @@ -8,6 +8,7 @@ import com.linkedin.drelephant.hadoop.HadoopJobData; + /** * InfoExtractor is responsible for retrieving information and context about a * job from the job's configuration which will be leveraged by the UI @@ -17,8 +18,7 @@ public class InfoExtractor { private static final String AZK_URL_PREFIX = "azkaban.link"; private static final String AZK_WORKFLOW_URL = "azkaban.link.workflow.url"; private static final String AZK_JOB_URL = "azkaban.link.job.url"; - private static final String AZK_JOB_EXECUTION_URL = - "azkaban.link.jobexec.url"; + private static final String AZK_JOB_EXECUTION_URL = "azkaban.link.jobexec.url"; private static final String AZK_EXECUTION_URL = "azkaban.link.execution.url"; private static final String AZK_ATTEMPT_URL = "azkaban.link.attempt.url"; private static final String AZK_URN_KEY = "azk.urn"; @@ -36,7 +36,7 @@ void retrieveURLs(JobResult result, HadoopJobData jobData) { String truncate(String value, String jobId) { if (value != null && value.length() > JobResult.URL_LEN_LIMIT) { - logger.info("Truncate long URL in job result for job : "+jobId+ ". Original Url : "+value); + logger.info("Truncate long URL in job result for job: " + jobId + ". Original Url: " + value); value = value.substring(0, JobResult.URL_LEN_LIMIT); } return value; diff --git a/app/com/linkedin/drelephant/analysis/Constants.java b/app/com/linkedin/drelephant/analysis/Constants.java index 9e481cceb..13fecc4d4 100644 --- a/app/com/linkedin/drelephant/analysis/Constants.java +++ b/app/com/linkedin/drelephant/analysis/Constants.java @@ -7,19 +7,22 @@ import java.io.IOException; -public class Constants { - private static final Logger logger = Logger.getLogger(Constants.class); - public static long HDFS_BLOCK_SIZE = 64 * 1024 * 1024; - public static final long DISK_READ_SPEED = 100 * 1024 * 1024; - public static final int SHUFFLE_SORT_MAX_SAMPLE_SIZE = 50; +public final class Constants { + private static final Logger logger = Logger.getLogger(Constants.class); + public static long HDFS_BLOCK_SIZE = 64 * 1024 * 1024; + public static final long DISK_READ_SPEED = 100 * 1024 * 1024; + public static final int SHUFFLE_SORT_MAX_SAMPLE_SIZE = 50; - public static void load() { - try { - HDFS_BLOCK_SIZE = FileSystem.get(new Configuration()).getDefaultBlockSize(new Path("/")); - } catch (IOException e) { - logger.error("Error getting FS Block Size!", e); - } + private Constants() { + } - logger.info("HDFS BLock size: " + HDFS_BLOCK_SIZE); + public static void load() { + try { + HDFS_BLOCK_SIZE = FileSystem.get(new Configuration()).getDefaultBlockSize(new Path("/")); + } catch (IOException e) { + logger.error("Error getting FS Block Size!", e); } + + logger.info("HDFS BLock size: " + HDFS_BLOCK_SIZE); + } } diff --git a/app/com/linkedin/drelephant/analysis/Heuristic.java b/app/com/linkedin/drelephant/analysis/Heuristic.java index 073e90171..fd7f89dd3 100644 --- a/app/com/linkedin/drelephant/analysis/Heuristic.java +++ b/app/com/linkedin/drelephant/analysis/Heuristic.java @@ -2,8 +2,9 @@ import com.linkedin.drelephant.hadoop.HadoopJobData; + public interface Heuristic { - public HeuristicResult apply(HadoopJobData data); + public HeuristicResult apply(HadoopJobData data); - public String getHeuristicName(); + public String getHeuristicName(); } diff --git a/app/com/linkedin/drelephant/analysis/HeuristicResult.java b/app/com/linkedin/drelephant/analysis/HeuristicResult.java index 3a18ade3e..20729f32d 100644 --- a/app/com/linkedin/drelephant/analysis/HeuristicResult.java +++ b/app/com/linkedin/drelephant/analysis/HeuristicResult.java @@ -5,66 +5,67 @@ import java.util.ArrayList; import java.util.List; + public class HeuristicResult { - private String analysis; - private Severity severity; - private List details; - private int detailsColumns = 0; + private String _analysis; + private Severity _severity; + private List _details; + private int _detailsColumns = 0; - public HeuristicResult(String analysis, Severity severity) { - this.analysis = analysis; - this.severity = severity; - this.details = new ArrayList(); - } + public HeuristicResult(String analysis, Severity severity) { + this._analysis = analysis; + this._severity = severity; + this._details = new ArrayList(); + } - public String getAnalysis() { - return analysis; - } + public String getAnalysis() { + return _analysis; + } - public Severity getSeverity() { - return severity; - } + public Severity getSeverity() { + return _severity; + } - /** - * Gets a list of lines of comma-separated strings - * - * @return - */ - public List getDetails() { - return details; - } + /** + * Gets a list of lines of comma-separated strings + * + * @return + */ + public List getDetails() { + return _details; + } - /** - * Create a string that contains lines of comma-separated strings - * - * @return - */ - public String getDetailsCSV() { - return Utils.combineCsvLines(details.toArray(new String[details.size()])); - } + /** + * Create a string that contains lines of comma-separated strings + * + * @return + */ + public String getDetailsCSV() { + return Utils.combineCsvLines(_details.toArray(new String[_details.size()])); + } - /** - * Gets the number of columns in the csv formatted details store - * - * @return - */ - public int getDetailsColumns() { - return detailsColumns; - } + /** + * Gets the number of columns in the csv formatted details store + * + * @return + */ + public int getDetailsColumns() { + return _detailsColumns; + } - /** - * Add a new line to the csv formatted details store - * - * @param parts strings to join into a single line - */ - public void addDetail(String... parts) { - details.add(Utils.createCsvLine(parts)); - if (parts.length > detailsColumns) { - detailsColumns = parts.length; - } + /** + * Add a new line to the csv formatted details store + * + * @param parts strings to join into a single line + */ + public void addDetail(String... parts) { + _details.add(Utils.createCsvLine(parts)); + if (parts.length > _detailsColumns) { + _detailsColumns = parts.length; } + } - public void setSeverity(Severity severity){ - this.severity = severity; - } + public void setSeverity(Severity severity) { + this._severity = severity; + } } diff --git a/app/com/linkedin/drelephant/analysis/Severity.java b/app/com/linkedin/drelephant/analysis/Severity.java index ba0a1635e..5e46296b1 100644 --- a/app/com/linkedin/drelephant/analysis/Severity.java +++ b/app/com/linkedin/drelephant/analysis/Severity.java @@ -2,104 +2,105 @@ import com.avaje.ebean.annotation.EnumValue; + public enum Severity { - @EnumValue("4") - CRITICAL(4, "Critical", "danger"), + @EnumValue("4") + CRITICAL(4, "Critical", "danger"), - @EnumValue("3") - SEVERE(3, "Severe", "severe"), + @EnumValue("3") + SEVERE(3, "Severe", "severe"), - @EnumValue("2") - MODERATE(2, "Moderate", "warning"), + @EnumValue("2") + MODERATE(2, "Moderate", "warning"), - @EnumValue("1") - LOW(1, "Low", "success"), + @EnumValue("1") + LOW(1, "Low", "success"), - @EnumValue("0") - NONE(0, "None", "success"); + @EnumValue("0") + NONE(0, "None", "success"); - private int value; - private String text; - private String bootstrapColor; + private int _value; + private String _text; + private String _bootstrapColor; - Severity(int value, String text, String bootstrapColor) { - this.value = value; - this.text = text; - this.bootstrapColor = bootstrapColor; - } + Severity(int value, String text, String bootstrapColor) { + this._value = value; + this._text = text; + this._bootstrapColor = bootstrapColor; + } - public int getValue() { - return value; - } + public int getValue() { + return _value; + } - public String getText() { - return text; - } + public String getText() { + return _text; + } - public String getBootstrapColor() { - return bootstrapColor; - } + public String getBootstrapColor() { + return _bootstrapColor; + } - public static Severity byValue(int value) { - for (Severity severity : values()) { - if (severity.value == value) { - return severity; - } - } - return NONE; + public static Severity byValue(int value) { + for (Severity severity : values()) { + if (severity._value == value) { + return severity; + } } + return NONE; + } - public static Severity max(Severity a, Severity b) { - if (a.value > b.value) { - return a; - } - return b; + public static Severity max(Severity a, Severity b) { + if (a._value > b._value) { + return a; } + return b; + } - public static Severity max(Severity... severities) { - Severity currentSeverity = NONE; - for (Severity severity : severities) { - currentSeverity = max(currentSeverity, severity); - } - return currentSeverity; + public static Severity max(Severity... severities) { + Severity currentSeverity = NONE; + for (Severity severity : severities) { + currentSeverity = max(currentSeverity, severity); } + return currentSeverity; + } - public static Severity min(Severity a, Severity b) { - if (a.value < b.value) { - return a; - } - return b; + public static Severity min(Severity a, Severity b) { + if (a._value < b._value) { + return a; } + return b; + } - public static Severity getSeverityAscending(long value, long low, long moderate, long severe, long critical) { - if (value >= critical) { - return CRITICAL; - } - if (value >= severe) { - return SEVERE; - } - if (value >= moderate) { - return MODERATE; - } - if (value >= low) { - return LOW; - } - return NONE; + public static Severity getSeverityAscending(long value, long low, long moderate, long severe, long critical) { + if (value >= critical) { + return CRITICAL; + } + if (value >= severe) { + return SEVERE; } + if (value >= moderate) { + return MODERATE; + } + if (value >= low) { + return LOW; + } + return NONE; + } - public static Severity getSeverityDescending(long value, long low, long moderate, long severe, long critical) { - if (value <= critical) { - return CRITICAL; - } - if (value <= severe) { - return SEVERE; - } - if (value <= moderate) { - return MODERATE; - } - if (value <= low) { - return LOW; - } - return NONE; + public static Severity getSeverityDescending(long value, long low, long moderate, long severe, long critical) { + if (value <= critical) { + return CRITICAL; + } + if (value <= severe) { + return SEVERE; + } + if (value <= moderate) { + return MODERATE; + } + if (value <= low) { + return LOW; } + return NONE; + } } diff --git a/app/com/linkedin/drelephant/analysis/heuristics/GenericDataSkewHeuristic.java b/app/com/linkedin/drelephant/analysis/heuristics/GenericDataSkewHeuristic.java index ad9f63c51..867d9ecac 100644 --- a/app/com/linkedin/drelephant/analysis/heuristics/GenericDataSkewHeuristic.java +++ b/app/com/linkedin/drelephant/analysis/heuristics/GenericDataSkewHeuristic.java @@ -10,73 +10,70 @@ import com.linkedin.drelephant.math.Statistics; import org.apache.commons.io.FileUtils; -public abstract class GenericDataSkewHeuristic implements Heuristic { - private HadoopCounterHolder.CounterName counterName; - private String heuristicName; - @Override - public String getHeuristicName() { - return heuristicName; - } +public abstract class GenericDataSkewHeuristic implements Heuristic { + private HadoopCounterHolder.CounterName _counterName; + private String _heuristicName; - protected GenericDataSkewHeuristic(HadoopCounterHolder.CounterName counterName, String heuristicName) { - this.counterName = counterName; - this.heuristicName = heuristicName; - } + @Override + public String getHeuristicName() { + return _heuristicName; + } - protected abstract HadoopTaskData[] getTasks(HadoopJobData data); + protected GenericDataSkewHeuristic(HadoopCounterHolder.CounterName counterName, String heuristicName) { + this._counterName = counterName; + this._heuristicName = heuristicName; + } - @Override - public HeuristicResult apply(HadoopJobData data) { - HadoopTaskData[] tasks = getTasks(data); + protected abstract HadoopTaskData[] getTasks(HadoopJobData data); - //Gather data - long[] inputBytes = new long[tasks.length]; + @Override + public HeuristicResult apply(HadoopJobData data) { + HadoopTaskData[] tasks = getTasks(data); - for (int i = 0; i < tasks.length; i++) { - inputBytes[i] = tasks[i].getCounters().get(counterName); - } + //Gather data + long[] inputBytes = new long[tasks.length]; - //Analyze data - long[][] groups = Statistics.findTwoGroups(inputBytes); + for (int i = 0; i < tasks.length; i++) { + inputBytes[i] = tasks[i].getCounters().get(_counterName); + } - long avg1 = Statistics.average(groups[0]); - long avg2 = Statistics.average(groups[1]); + //Analyze data + long[][] groups = Statistics.findTwoGroups(inputBytes); - long min = Math.min(avg1, avg2); - long diff = Math.abs(avg2 - avg1); + long avg1 = Statistics.average(groups[0]); + long avg2 = Statistics.average(groups[1]); - Severity severity = getDeviationSeverity(min, diff); + long min = Math.min(avg1, avg2); + long diff = Math.abs(avg2 - avg1); - //This reduces severity if the largest file sizes are insignificant - severity = Severity.min(severity, getFilesSeverity(avg2)); + Severity severity = getDeviationSeverity(min, diff); - //This reduces severity if number of tasks is insignificant - severity = Severity.min(severity, Statistics.getNumTasksSeverity(groups[0].length)); + //This reduces severity if the largest file sizes are insignificant + severity = Severity.min(severity, getFilesSeverity(avg2)); - HeuristicResult result = new HeuristicResult(heuristicName, severity); + //This reduces severity if number of tasks is insignificant + severity = Severity.min(severity, Statistics.getNumTasksSeverity(groups[0].length)); - result.addDetail("Number of tasks", Integer.toString(tasks.length)); - result.addDetail("Group A", groups[0].length + " tasks @ " + FileUtils.byteCountToDisplaySize(avg1) + " avg"); - result.addDetail("Group B", groups[1].length + " tasks @ " + FileUtils.byteCountToDisplaySize(avg2) + " avg"); + HeuristicResult result = new HeuristicResult(_heuristicName, severity); - return result; - } + result.addDetail("Number of tasks", Integer.toString(tasks.length)); + result.addDetail("Group A", groups[0].length + " tasks @ " + FileUtils.byteCountToDisplaySize(avg1) + " avg"); + result.addDetail("Group B", groups[1].length + " tasks @ " + FileUtils.byteCountToDisplaySize(avg2) + " avg"); - public static Severity getDeviationSeverity(long averageMin, long averageDiff) { - if (averageMin <= 0) { - averageMin = 1; - } - long value = averageDiff / averageMin; - return Severity.getSeverityAscending(value, - 2, 4, 8, 16); - } + return result; + } - public static Severity getFilesSeverity(long value) { - return Severity.getSeverityAscending(value, - Constants.HDFS_BLOCK_SIZE / 8, - Constants.HDFS_BLOCK_SIZE / 4, - Constants.HDFS_BLOCK_SIZE / 2, - Constants.HDFS_BLOCK_SIZE); + public static Severity getDeviationSeverity(long averageMin, long averageDiff) { + if (averageMin <= 0) { + averageMin = 1; } + long value = averageDiff / averageMin; + return Severity.getSeverityAscending(value, 2, 4, 8, 16); + } + + public static Severity getFilesSeverity(long value) { + return Severity.getSeverityAscending(value, Constants.HDFS_BLOCK_SIZE / 8, Constants.HDFS_BLOCK_SIZE / 4, + Constants.HDFS_BLOCK_SIZE / 2, Constants.HDFS_BLOCK_SIZE); + } } diff --git a/app/com/linkedin/drelephant/analysis/heuristics/JobQueueLimitHeuristic.java b/app/com/linkedin/drelephant/analysis/heuristics/JobQueueLimitHeuristic.java index 533ea22ba..a01a6cfcc 100644 --- a/app/com/linkedin/drelephant/analysis/heuristics/JobQueueLimitHeuristic.java +++ b/app/com/linkedin/drelephant/analysis/heuristics/JobQueueLimitHeuristic.java @@ -11,16 +11,16 @@ public class JobQueueLimitHeuristic implements Heuristic { - public static final String heuristicName = "Job Queue Timeout Limit"; + public static final String HEURISTIC_NAME = "Job Queue Timeout Limit"; @Override public String getHeuristicName() { - return heuristicName; + return HEURISTIC_NAME; } @Override public HeuristicResult apply(HadoopJobData data) { - HeuristicResult result = new HeuristicResult(heuristicName, Severity.NONE); + HeuristicResult result = new HeuristicResult(HEURISTIC_NAME, Severity.NONE); Properties jobConf = data.getJobConf(); long queueTimeoutLimit = TimeUnit.MINUTES.toMillis(15); diff --git a/app/com/linkedin/drelephant/analysis/heuristics/MapperDataSkewHeuristic.java b/app/com/linkedin/drelephant/analysis/heuristics/MapperDataSkewHeuristic.java index 43d806f5d..2bed7ef77 100644 --- a/app/com/linkedin/drelephant/analysis/heuristics/MapperDataSkewHeuristic.java +++ b/app/com/linkedin/drelephant/analysis/heuristics/MapperDataSkewHeuristic.java @@ -4,15 +4,16 @@ import com.linkedin.drelephant.hadoop.HadoopJobData; import com.linkedin.drelephant.hadoop.HadoopTaskData; + public class MapperDataSkewHeuristic extends GenericDataSkewHeuristic { - public static final String heuristicName = "Mapper Data Skew"; + public static final String HEURISTIC_NAME = "Mapper Data Skew"; - public MapperDataSkewHeuristic() { - super(HadoopCounterHolder.CounterName.HDFS_BYTES_READ, heuristicName); - } + public MapperDataSkewHeuristic() { + super(HadoopCounterHolder.CounterName.HDFS_BYTES_READ, HEURISTIC_NAME); + } - @Override - protected HadoopTaskData[] getTasks(HadoopJobData data) { - return data.getMapperData(); - } -} \ No newline at end of file + @Override + protected HadoopTaskData[] getTasks(HadoopJobData data) { + return data.getMapperData(); + } +} diff --git a/app/com/linkedin/drelephant/analysis/heuristics/MapperInputSizeHeuristic.java b/app/com/linkedin/drelephant/analysis/heuristics/MapperInputSizeHeuristic.java index 3bc906747..3c49f9dab 100644 --- a/app/com/linkedin/drelephant/analysis/heuristics/MapperInputSizeHeuristic.java +++ b/app/com/linkedin/drelephant/analysis/heuristics/MapperInputSizeHeuristic.java @@ -10,75 +10,68 @@ import com.linkedin.drelephant.math.Statistics; import org.apache.commons.io.FileUtils; + public class MapperInputSizeHeuristic implements Heuristic { - public static final String heuristicName = "Mapper Input Size"; + public static final String HEURISTIC_NAME = "Mapper Input Size"; - @Override - public String getHeuristicName() { - return heuristicName; - } + @Override + public String getHeuristicName() { + return HEURISTIC_NAME; + } - @Override - public HeuristicResult apply(HadoopJobData data) { - HadoopTaskData[] tasks = data.getMapperData(); + @Override + public HeuristicResult apply(HadoopJobData data) { + HadoopTaskData[] tasks = data.getMapperData(); - //Gather data - long[] inputBytes = new long[tasks.length]; + //Gather data + long[] inputBytes = new long[tasks.length]; - for (int i = 0; i < tasks.length; i++) { - inputBytes[i] = tasks[i].getCounters().get(HadoopCounterHolder.CounterName.HDFS_BYTES_READ); - } + for (int i = 0; i < tasks.length; i++) { + inputBytes[i] = tasks[i].getCounters().get(HadoopCounterHolder.CounterName.HDFS_BYTES_READ); + } - //Analyze data - long average = Statistics.average(inputBytes); + //Analyze data + long average = Statistics.average(inputBytes); - Severity smallFilesSeverity = smallFilesSeverity(average, tasks.length); - Severity largeFilesSeverity = largeFilesSeverity(average, tasks.length); - Severity severity = Severity.max(smallFilesSeverity, largeFilesSeverity); + Severity smallFilesSeverity = smallFilesSeverity(average, tasks.length); + Severity largeFilesSeverity = largeFilesSeverity(average, tasks.length); + Severity severity = Severity.max(smallFilesSeverity, largeFilesSeverity); - HeuristicResult result = new HeuristicResult(heuristicName, severity); + HeuristicResult result = new HeuristicResult(HEURISTIC_NAME, severity); - result.addDetail("Number of tasks", Integer.toString(tasks.length)); - result.addDetail("Average task input", FileUtils.byteCountToDisplaySize(average)); + result.addDetail("Number of tasks", Integer.toString(tasks.length)); + result.addDetail("Average task input", FileUtils.byteCountToDisplaySize(average)); - return result; - } + return result; + } - private Severity smallFilesSeverity(long value, long numTasks) { - Severity severity = getSmallFilesSeverity(value); - Severity taskSeverity = getNumTasksSeverity(numTasks); - return Severity.min(severity, taskSeverity); - } + private Severity smallFilesSeverity(long value, long numTasks) { + Severity severity = getSmallFilesSeverity(value); + Severity taskSeverity = getNumTasksSeverity(numTasks); + return Severity.min(severity, taskSeverity); + } - private Severity largeFilesSeverity(long value, long numTasks) { - Severity severity = getLargeFilesSeverity(value); - Severity taskSeverity = getNumTasksSeverityReverse(numTasks); - return Severity.min(severity, taskSeverity); - } + private Severity largeFilesSeverity(long value, long numTasks) { + Severity severity = getLargeFilesSeverity(value); + Severity taskSeverity = getNumTasksSeverityReverse(numTasks); + return Severity.min(severity, taskSeverity); + } - public static Severity getSmallFilesSeverity(long value) { - return Severity.getSeverityDescending(value, - Constants.HDFS_BLOCK_SIZE / 2, - Constants.HDFS_BLOCK_SIZE / 4, - Constants.HDFS_BLOCK_SIZE / 8, - Constants.HDFS_BLOCK_SIZE / 32); - } + public static Severity getSmallFilesSeverity(long value) { + return Severity.getSeverityDescending(value, Constants.HDFS_BLOCK_SIZE / 2, Constants.HDFS_BLOCK_SIZE / 4, + Constants.HDFS_BLOCK_SIZE / 8, Constants.HDFS_BLOCK_SIZE / 32); + } - public static Severity getLargeFilesSeverity(long value) { - return Severity.getSeverityAscending(value, - Constants.HDFS_BLOCK_SIZE * 2, - Constants.HDFS_BLOCK_SIZE * 3, - Constants.HDFS_BLOCK_SIZE * 4, - Constants.HDFS_BLOCK_SIZE * 5); - } + public static Severity getLargeFilesSeverity(long value) { + return Severity.getSeverityAscending(value, Constants.HDFS_BLOCK_SIZE * 2, Constants.HDFS_BLOCK_SIZE * 3, + Constants.HDFS_BLOCK_SIZE * 4, Constants.HDFS_BLOCK_SIZE * 5); + } - public static Severity getNumTasksSeverity(long numTasks) { - return Severity.getSeverityAscending(numTasks, - 10, 50, 200, 500); - } + public static Severity getNumTasksSeverity(long numTasks) { + return Severity.getSeverityAscending(numTasks, 10, 50, 200, 500); + } - public static Severity getNumTasksSeverityReverse(long numTasks) { - return Severity.getSeverityDescending(numTasks, - 1000, 500, 200, 100); - } + public static Severity getNumTasksSeverityReverse(long numTasks) { + return Severity.getSeverityDescending(numTasks, 1000, 500, 200, 100); + } } diff --git a/app/com/linkedin/drelephant/analysis/heuristics/MapperSpeedHeuristic.java b/app/com/linkedin/drelephant/analysis/heuristics/MapperSpeedHeuristic.java index 8046ef5ce..1f1c19f17 100644 --- a/app/com/linkedin/drelephant/analysis/heuristics/MapperSpeedHeuristic.java +++ b/app/com/linkedin/drelephant/analysis/heuristics/MapperSpeedHeuristic.java @@ -14,72 +14,67 @@ import org.apache.commons.io.FileUtils; -public class MapperSpeedHeuristic implements Heuristic { - public static final String heuristicName = "Mapper Speed"; - - @Override - public String getHeuristicName() { - return heuristicName; - } - @Override - public HeuristicResult apply(HadoopJobData data) { - - HadoopTaskData[] tasks = data.getMapperData(); - - List input_byte_sizes = new ArrayList(); - List speeds = new ArrayList(); - List runtimes = new ArrayList(); - - for(HadoopTaskData task : tasks) { - if(task.timed()) { - long input_bytes = task.getCounters().get(HadoopCounterHolder.CounterName.HDFS_BYTES_READ); - long runtime = task.getEndTime() - task.getStartTime(); - //Apply 1 minute buffer - runtime -= 60 * 1000; - if (runtime < 1000) { - runtime = 1000; - } - input_byte_sizes.add(input_bytes); - runtimes.add(runtime); - //Speed is bytes per second - speeds.add((1000 * input_bytes) / (runtime)); - } +public class MapperSpeedHeuristic implements Heuristic { + public static final String HEURISTIC_NAME = "Mapper Speed"; + + @Override + public String getHeuristicName() { + return HEURISTIC_NAME; + } + + @Override + public HeuristicResult apply(HadoopJobData data) { + + HadoopTaskData[] tasks = data.getMapperData(); + + List inputByteSizes = new ArrayList(); + List speeds = new ArrayList(); + List runtimes = new ArrayList(); + + for (HadoopTaskData task : tasks) { + if (task.timed()) { + long inputBytes = task.getCounters().get(HadoopCounterHolder.CounterName.HDFS_BYTES_READ); + long runtime = task.getEndTime() - task.getStartTime(); + //Apply 1 minute buffer + runtime -= 60 * 1000; + if (runtime < 1000) { + runtime = 1000; } + inputByteSizes.add(inputBytes); + runtimes.add(runtime); + //Speed is bytes per second + speeds.add((1000 * inputBytes) / (runtime)); + } + } - //Analyze data - long averageSpeed = Statistics.average(speeds); - long averageSize = Statistics.average(input_byte_sizes); - long averageRuntime = Statistics.average(runtimes); + //Analyze data + long averageSpeed = Statistics.average(speeds); + long averageSize = Statistics.average(inputByteSizes); + long averageRuntime = Statistics.average(runtimes); - Severity severity = getDiskSpeedSeverity(averageSpeed); + Severity severity = getDiskSpeedSeverity(averageSpeed); - //This reduces severity if task runtime is insignificant - severity = Severity.min(severity, getRuntimeSeverity(averageRuntime)); + //This reduces severity if task runtime is insignificant + severity = Severity.min(severity, getRuntimeSeverity(averageRuntime)); - HeuristicResult result = new HeuristicResult(heuristicName, severity); + HeuristicResult result = new HeuristicResult(HEURISTIC_NAME, severity); - result.addDetail("Number of tasks", Integer.toString(tasks.length)); - result.addDetail("Average task input size", FileUtils.byteCountToDisplaySize(averageSize)); - result.addDetail("Average task speed", FileUtils.byteCountToDisplaySize(averageSpeed) + "/s"); - result.addDetail("Average task runtime", Statistics.readableTimespan(averageRuntime)); + result.addDetail("Number of tasks", Integer.toString(tasks.length)); + result.addDetail("Average task input size", FileUtils.byteCountToDisplaySize(averageSize)); + result.addDetail("Average task speed", FileUtils.byteCountToDisplaySize(averageSpeed) + "/s"); + result.addDetail("Average task runtime", Statistics.readableTimespan(averageRuntime)); - return result; - } + return result; + } - public static Severity getDiskSpeedSeverity(long speed) { - return Severity.getSeverityDescending(speed, - Constants.DISK_READ_SPEED / 2, - Constants.DISK_READ_SPEED / 4, - Constants.DISK_READ_SPEED / 8, - Constants.DISK_READ_SPEED / 32); - } + public static Severity getDiskSpeedSeverity(long speed) { + return Severity.getSeverityDescending(speed, Constants.DISK_READ_SPEED / 2, Constants.DISK_READ_SPEED / 4, + Constants.DISK_READ_SPEED / 8, Constants.DISK_READ_SPEED / 32); + } - public static Severity getRuntimeSeverity(long runtime) { - return Severity.getSeverityAscending(runtime, - 5 * Statistics.MINUTE, - 20 * Statistics.MINUTE, - 40 * Statistics.MINUTE, - 1 * Statistics.HOUR); - } + public static Severity getRuntimeSeverity(long runtime) { + return Severity.getSeverityAscending(runtime, 5 * Statistics.MINUTE, 20 * Statistics.MINUTE, + 40 * Statistics.MINUTE, 1 * Statistics.HOUR); + } } diff --git a/app/com/linkedin/drelephant/analysis/heuristics/ReducerDataSkewHeuristic.java b/app/com/linkedin/drelephant/analysis/heuristics/ReducerDataSkewHeuristic.java index 72c6ec499..218033a68 100644 --- a/app/com/linkedin/drelephant/analysis/heuristics/ReducerDataSkewHeuristic.java +++ b/app/com/linkedin/drelephant/analysis/heuristics/ReducerDataSkewHeuristic.java @@ -4,15 +4,16 @@ import com.linkedin.drelephant.hadoop.HadoopJobData; import com.linkedin.drelephant.hadoop.HadoopTaskData; + public class ReducerDataSkewHeuristic extends GenericDataSkewHeuristic { - public static final String heuristicName = "Reducer Data Skew"; + public static final String HEURISTIC_NAME = "Reducer Data Skew"; - public ReducerDataSkewHeuristic() { - super(HadoopCounterHolder.CounterName.REDUCE_SHUFFLE_BYTES, heuristicName); - } + public ReducerDataSkewHeuristic() { + super(HadoopCounterHolder.CounterName.REDUCE_SHUFFLE_BYTES, HEURISTIC_NAME); + } - @Override - protected HadoopTaskData[] getTasks(HadoopJobData data) { - return data.getReducerData(); - } + @Override + protected HadoopTaskData[] getTasks(HadoopJobData data) { + return data.getReducerData(); + } } diff --git a/app/com/linkedin/drelephant/analysis/heuristics/ReducerTimeHeuristic.java b/app/com/linkedin/drelephant/analysis/heuristics/ReducerTimeHeuristic.java index 4982000f9..e0674c016 100644 --- a/app/com/linkedin/drelephant/analysis/heuristics/ReducerTimeHeuristic.java +++ b/app/com/linkedin/drelephant/analysis/heuristics/ReducerTimeHeuristic.java @@ -10,76 +10,69 @@ import com.linkedin.drelephant.hadoop.HadoopTaskData; import com.linkedin.drelephant.math.Statistics; + public class ReducerTimeHeuristic implements Heuristic { - public static final String heuristicName = "Reducer Time"; + public static final String HEURISTIC_NAME = "Reducer Time"; - @Override - public String getHeuristicName() { - return heuristicName; - } + @Override + public String getHeuristicName() { + return HEURISTIC_NAME; + } - @Override - public HeuristicResult apply(HadoopJobData data) { - HadoopTaskData[] tasks = data.getReducerData(); + @Override + public HeuristicResult apply(HadoopJobData data) { + HadoopTaskData[] tasks = data.getReducerData(); - List runTimes = new ArrayList(); + List runTimes = new ArrayList(); - for(HadoopTaskData task : tasks) { - if(task.timed()) { - runTimes.add(task.getRunTime()); - } - } + for (HadoopTaskData task : tasks) { + if (task.timed()) { + runTimes.add(task.getRunTime()); + } + } - //Analyze data - long averageRuntime = Statistics.average(runTimes); + //Analyze data + long averageRuntime = Statistics.average(runTimes); - Severity shortTimeSeverity = shortTimeSeverity(averageRuntime, tasks.length); - Severity longTimeSeverity = longTimeSeverity(averageRuntime, tasks.length); - Severity severity = Severity.max(shortTimeSeverity, longTimeSeverity); + Severity shortTimeSeverity = shortTimeSeverity(averageRuntime, tasks.length); + Severity longTimeSeverity = longTimeSeverity(averageRuntime, tasks.length); + Severity severity = Severity.max(shortTimeSeverity, longTimeSeverity); - HeuristicResult result = new HeuristicResult(heuristicName, severity); + HeuristicResult result = new HeuristicResult(HEURISTIC_NAME, severity); - result.addDetail("Number of tasks", Integer.toString(tasks.length)); - result.addDetail("Average task time", Statistics.readableTimespan(averageRuntime)); + result.addDetail("Number of tasks", Integer.toString(tasks.length)); + result.addDetail("Average task time", Statistics.readableTimespan(averageRuntime)); - return result; - } + return result; + } - private Severity shortTimeSeverity(long runtime, long numTasks) { - Severity timeSeverity = getShortRuntimeSeverity(runtime); - Severity taskSeverity = getNumTasksSeverity(numTasks); - return Severity.min(timeSeverity, taskSeverity); - } + private Severity shortTimeSeverity(long runtime, long numTasks) { + Severity timeSeverity = getShortRuntimeSeverity(runtime); + Severity taskSeverity = getNumTasksSeverity(numTasks); + return Severity.min(timeSeverity, taskSeverity); + } - private Severity longTimeSeverity(long runtime, long numTasks) { - Severity timeSeverity = getLongRuntimeSeverity(runtime); - Severity taskSeverity = getNumTasksSeverityReverse(numTasks); - return Severity.min(timeSeverity, taskSeverity); - } + private Severity longTimeSeverity(long runtime, long numTasks) { + Severity timeSeverity = getLongRuntimeSeverity(runtime); + Severity taskSeverity = getNumTasksSeverityReverse(numTasks); + return Severity.min(timeSeverity, taskSeverity); + } - public static Severity getShortRuntimeSeverity(long runtime) { - return Severity.getSeverityDescending(runtime, - 10 * Statistics.MINUTE, - 5 * Statistics.MINUTE, - 2 * Statistics.MINUTE, - 1 * Statistics.MINUTE); - } + public static Severity getShortRuntimeSeverity(long runtime) { + return Severity.getSeverityDescending(runtime, 10 * Statistics.MINUTE, 5 * Statistics.MINUTE, + 2 * Statistics.MINUTE, 1 * Statistics.MINUTE); + } - public static Severity getLongRuntimeSeverity(long runtime) { - return Severity.getSeverityAscending(runtime, - 15 * Statistics.MINUTE, - 30 * Statistics.MINUTE, - 1 * Statistics.HOUR, - 2 * Statistics.HOUR); - } + public static Severity getLongRuntimeSeverity(long runtime) { + return Severity.getSeverityAscending(runtime, 15 * Statistics.MINUTE, 30 * Statistics.MINUTE, 1 * Statistics.HOUR, + 2 * Statistics.HOUR); + } - public static Severity getNumTasksSeverity(long numTasks) { - return Severity.getSeverityAscending(numTasks, - 10, 50, 200, 500); - } + public static Severity getNumTasksSeverity(long numTasks) { + return Severity.getSeverityAscending(numTasks, 10, 50, 200, 500); + } - public static Severity getNumTasksSeverityReverse(long numTasks) { - return Severity.getSeverityDescending(numTasks, - 100, 50, 20, 10); - } + public static Severity getNumTasksSeverityReverse(long numTasks) { + return Severity.getSeverityDescending(numTasks, 100, 50, 20, 10); + } } diff --git a/app/com/linkedin/drelephant/analysis/heuristics/ShuffleSortHeuristic.java b/app/com/linkedin/drelephant/analysis/heuristics/ShuffleSortHeuristic.java index e00964ccd..8e2c48900 100644 --- a/app/com/linkedin/drelephant/analysis/heuristics/ShuffleSortHeuristic.java +++ b/app/com/linkedin/drelephant/analysis/heuristics/ShuffleSortHeuristic.java @@ -3,7 +3,6 @@ import java.util.ArrayList; import java.util.List; -import com.linkedin.drelephant.analysis.Constants; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; @@ -11,67 +10,64 @@ import com.linkedin.drelephant.hadoop.HadoopTaskData; import com.linkedin.drelephant.math.Statistics; + public class ShuffleSortHeuristic implements Heuristic { - public static final String heuristicName = "Shuffle & Sort"; + public static final String HEURISTIC_NAME = "Shuffle & Sort"; - @Override - public String getHeuristicName() { - return heuristicName; - } + @Override + public String getHeuristicName() { + return HEURISTIC_NAME; + } - @Override - public HeuristicResult apply(HadoopJobData data) { + @Override + public HeuristicResult apply(HadoopJobData data) { - HadoopTaskData[] tasks = data.getReducerData(); + HadoopTaskData[] tasks = data.getReducerData(); - List execTime = new ArrayList(); - List shuffleTime = new ArrayList(); - List sortTime = new ArrayList(); + List execTime = new ArrayList(); + List shuffleTime = new ArrayList(); + List sortTime = new ArrayList(); - for(HadoopTaskData task : tasks) { - if(task.timed()) { - execTime.add(task.getExecutionTime()); - shuffleTime.add(task.getShuffleTime()); - sortTime.add(task.getSortTime()); - } - } + for (HadoopTaskData task : tasks) { + if (task.timed()) { + execTime.add(task.getExecutionTime()); + shuffleTime.add(task.getShuffleTime()); + sortTime.add(task.getSortTime()); + } + } - //Analyze data - long avgExecTime = Statistics.average(execTime); - long avgShuffleTime = Statistics.average(shuffleTime); - long avgSortTime = Statistics.average(sortTime); + //Analyze data + long avgExecTime = Statistics.average(execTime); + long avgShuffleTime = Statistics.average(shuffleTime); + long avgSortTime = Statistics.average(sortTime); + Severity shuffleSeverity = getShuffleSortSeverity(avgShuffleTime, avgExecTime); + Severity sortSeverity = getShuffleSortSeverity(avgSortTime, avgExecTime); + Severity severity = Severity.max(shuffleSeverity, sortSeverity); - Severity shuffleSeverity = getShuffleSortSeverity(avgShuffleTime, avgExecTime); - Severity sortSeverity = getShuffleSortSeverity(avgSortTime, avgExecTime); - Severity severity = Severity.max(shuffleSeverity, sortSeverity); + HeuristicResult result = new HeuristicResult(HEURISTIC_NAME, severity); - HeuristicResult result = new HeuristicResult(heuristicName, severity); + result.addDetail("Number of tasks", Integer.toString(data.getReducerData().length)); + result.addDetail("Average code runtime", Statistics.readableTimespan(avgExecTime)); + String shuffleFactor = Statistics.describeFactor(avgShuffleTime, avgExecTime, "x"); + result.addDetail("Average shuffle time", Statistics.readableTimespan(avgShuffleTime) + " " + shuffleFactor); + String sortFactor = Statistics.describeFactor(avgSortTime, avgExecTime, "x"); + result.addDetail("Average sort time", Statistics.readableTimespan(avgSortTime) + " " + sortFactor); - result.addDetail("Number of tasks", Integer.toString(data.getReducerData().length)); - result.addDetail("Average code runtime", Statistics.readableTimespan(avgExecTime)); - String shuffleFactor = Statistics.describeFactor(avgShuffleTime, avgExecTime, "x"); - result.addDetail("Average shuffle time", Statistics.readableTimespan(avgShuffleTime) + " " + shuffleFactor); - String sortFactor = Statistics.describeFactor(avgSortTime, avgExecTime, "x"); - result.addDetail("Average sort time", Statistics.readableTimespan(avgSortTime) + " " + sortFactor); + return result; + } - return result; - } + public static Severity getShuffleSortSeverity(long runtime, long codetime) { + Severity runtimeSeverity = + Severity.getSeverityAscending(runtime, 1 * Statistics.MINUTE, 5 * Statistics.MINUTE, 10 * Statistics.MINUTE, + 30 * Statistics.MINUTE); - public static Severity getShuffleSortSeverity(long runtime, long codetime) { - Severity runtimeSeverity = Severity.getSeverityAscending(runtime, - 1 * Statistics.MINUTE, - 5 * Statistics.MINUTE, - 10 * Statistics.MINUTE, - 30 * Statistics.MINUTE); - - if (codetime <= 0) { - return runtimeSeverity; - } - long value = runtime * 2 / codetime; - Severity runtimeRatioSeverity = Severity.getSeverityAscending(value, - 1, 2, 4, 8); - - return Severity.min(runtimeSeverity, runtimeRatioSeverity); + if (codetime <= 0) { + return runtimeSeverity; } -} \ No newline at end of file + long value = runtime * 2 / codetime; + Severity runtimeRatioSeverity = Severity.getSeverityAscending(value, 1, 2, 4, 8); + + return Severity.min(runtimeSeverity, runtimeRatioSeverity); + } +} diff --git a/app/com/linkedin/drelephant/hadoop/HadoopCounterHolder.java b/app/com/linkedin/drelephant/hadoop/HadoopCounterHolder.java index e0b698bf0..ad3427497 100644 --- a/app/com/linkedin/drelephant/hadoop/HadoopCounterHolder.java +++ b/app/com/linkedin/drelephant/hadoop/HadoopCounterHolder.java @@ -2,89 +2,87 @@ import java.util.Map; + public class HadoopCounterHolder { - private Map counters; + private Map _counters; - public HadoopCounterHolder(Map counterMap) { - counters = counterMap; - } + public HadoopCounterHolder(Map counterMap) { + _counters = counterMap; + } - public long get(CounterName counterName) { - Long value = counters.get(counterName); - if (value == null) { - return 0; - } - return value; + public long get(CounterName counterName) { + Long value = _counters.get(counterName); + if (value == null) { + return 0; } + return value; + } - public void set(CounterName counterName, long value) { - counters.put(counterName, value); - } + public void set(CounterName counterName, long value) { + _counters.put(counterName, value); + } - public static enum GroupName { - FileInput("org.apache.hadoop.mapred.FileInputFormat$Counter"), - FileSystemCounters("FileSystemCounters"), - MapReduce("org.apache.hadoop.mapred.Task$Counter"), - FileOutput("org.apache.hadoop.mapred.FileOutputFormat$Counter"); + public static enum GroupName { + FileInput("org.apache.hadoop.mapred.FileInputFormat$Counter"), + FileSystemCounters("FileSystemCounters"), + MapReduce("org.apache.hadoop.mapred.Task$Counter"), + FileOutput("org.apache.hadoop.mapred.FileOutputFormat$Counter"); - String name; + String _name; - GroupName(String name) { - this.name = name; - } - - public String getName() { - return name; - } + GroupName(String name) { + this._name = name; } - public static enum CounterName { - BYTES_READ(GroupName.FileInput, "BYTES_READ"), - BYTES_WRITTEN(GroupName.FileOutput, "BYTES_WRITTEN"), - - FILE_BYTES_READ(GroupName.FileSystemCounters, "FILE_BYTES_READ"), - FILE_BYTES_WRITTEN(GroupName.FileSystemCounters, "FILE_BYTES_WRITTEN"), - HDFS_BYTES_READ(GroupName.FileSystemCounters, "HDFS_BYTES_READ"), - HDFS_BYTES_WRITTEN(GroupName.FileSystemCounters, "HDFS_BYTES_WRITTEN"), - - - MAP_INPUT_RECORDS(GroupName.MapReduce, "MAP_INPUT_RECORDS"), - MAP_OUTPUT_RECORDS(GroupName.MapReduce, "MAP_OUTPUT_RECORDS"), - MAP_OUTPUT_BYTES(GroupName.MapReduce, "MAP_OUTPUT_BYTES"), - MAP_OUTPUT_MATERIALIZED_BYTES(GroupName.MapReduce, "MAP_OUTPUT_MATERIALIZED_BYTES"), - SPLIT_RAW_BYTES(GroupName.MapReduce, "SPLIT_RAW_BYTES"), - - REDUCE_INPUT_GROUPS(GroupName.MapReduce, "REDUCE_INPUT_GROUPS"), - REDUCE_SHUFFLE_BYTES(GroupName.MapReduce, "REDUCE_SHUFFLE_BYTES"), - REDUCE_OUTPUT_RECORDS(GroupName.MapReduce, "REDUCE_OUTPUT_RECORDS"), - REDUCE_INPUT_RECORDS(GroupName.MapReduce, "REDUCE_INPUT_RECORDS"), - - COMBINE_INPUT_RECORDS(GroupName.MapReduce, "COMBINE_INPUT_RECORDS"), - COMBINE_OUTPUT_RECORDS(GroupName.MapReduce, "COMBINE_OUTPUT_RECORDS"), - SPILLED_RECORDS(GroupName.MapReduce, "SPILLED_RECORDS"), - - CPU_MILLISECONDS(GroupName.MapReduce, "CPU_MILLISECONDS"), - COMMITTED_HEAP_BYTES(GroupName.MapReduce, "COMMITTED_HEAP_BYTES"), - PHYSICAL_MEMORY_BYTES(GroupName.MapReduce, "PHYSICAL_MEMORY_BYTES"), - VIRTUAL_MEMORY_BYTES(GroupName.MapReduce, "VIRTUAL_MEMORY_BYTES"); - - GroupName group; - String name; - - CounterName(GroupName group, String name) { - this.group = group; - this.name = name; - } + public String getName() { + return _name; + } + } + + public static enum CounterName { + BYTES_READ(GroupName.FileInput, "BYTES_READ"), + BYTES_WRITTEN(GroupName.FileOutput, "BYTES_WRITTEN"), + + FILE_BYTES_READ(GroupName.FileSystemCounters, "FILE_BYTES_READ"), + FILE_BYTES_WRITTEN(GroupName.FileSystemCounters, "FILE_BYTES_WRITTEN"), + HDFS_BYTES_READ(GroupName.FileSystemCounters, "HDFS_BYTES_READ"), + HDFS_BYTES_WRITTEN(GroupName.FileSystemCounters, "HDFS_BYTES_WRITTEN"), + + MAP_INPUT_RECORDS(GroupName.MapReduce, "MAP_INPUT_RECORDS"), + MAP_OUTPUT_RECORDS(GroupName.MapReduce, "MAP_OUTPUT_RECORDS"), + MAP_OUTPUT_BYTES(GroupName.MapReduce, "MAP_OUTPUT_BYTES"), + MAP_OUTPUT_MATERIALIZED_BYTES(GroupName.MapReduce, "MAP_OUTPUT_MATERIALIZED_BYTES"), + SPLIT_RAW_BYTES(GroupName.MapReduce, "SPLIT_RAW_BYTES"), + + REDUCE_INPUT_GROUPS(GroupName.MapReduce, "REDUCE_INPUT_GROUPS"), + REDUCE_SHUFFLE_BYTES(GroupName.MapReduce, "REDUCE_SHUFFLE_BYTES"), + REDUCE_OUTPUT_RECORDS(GroupName.MapReduce, "REDUCE_OUTPUT_RECORDS"), + REDUCE_INPUT_RECORDS(GroupName.MapReduce, "REDUCE_INPUT_RECORDS"), + + COMBINE_INPUT_RECORDS(GroupName.MapReduce, "COMBINE_INPUT_RECORDS"), + COMBINE_OUTPUT_RECORDS(GroupName.MapReduce, "COMBINE_OUTPUT_RECORDS"), + SPILLED_RECORDS(GroupName.MapReduce, "SPILLED_RECORDS"), + + CPU_MILLISECONDS(GroupName.MapReduce, "CPU_MILLISECONDS"), + COMMITTED_HEAP_BYTES(GroupName.MapReduce, "COMMITTED_HEAP_BYTES"), + PHYSICAL_MEMORY_BYTES(GroupName.MapReduce, "PHYSICAL_MEMORY_BYTES"), + VIRTUAL_MEMORY_BYTES(GroupName.MapReduce, "VIRTUAL_MEMORY_BYTES"); + + GroupName _group; + String _name; + + CounterName(GroupName group, String name) { + this._group = group; + this._name = name; + } - public GroupName getGroup() { - return group; - } + public GroupName getGroup() { + return _group; + } - public String getName() { - return name; - } + public String getName() { + return _name; } + } } - - diff --git a/app/com/linkedin/drelephant/hadoop/HadoopJobData.java b/app/com/linkedin/drelephant/hadoop/HadoopJobData.java index b7a70c5d6..ecf695321 100644 --- a/app/com/linkedin/drelephant/hadoop/HadoopJobData.java +++ b/app/com/linkedin/drelephant/hadoop/HadoopJobData.java @@ -4,94 +4,94 @@ public class HadoopJobData { - private String jobId=""; - private String username = ""; - private String url = ""; - private String jobName = ""; - private long startTime = 0; - private HadoopCounterHolder counterHolder; - private HadoopTaskData[] mapperData; - private HadoopTaskData[] reducerData; - private Properties jobConf; + private String _jobId = ""; + private String _username = ""; + private String _url = ""; + private String _jobName = ""; + private long _startTime = 0; + private HadoopCounterHolder _counterHolder; + private HadoopTaskData[] _mapperData; + private HadoopTaskData[] _reducerData; + private Properties _jobConf; public HadoopJobData setJobId(String jobId) { - this.jobId = jobId; + this._jobId = jobId; return this; } public HadoopJobData setJobName(String jobName) { - this.jobName = jobName; + this._jobName = jobName; return this; } public HadoopJobData setUsername(String username) { - this.username = username; + this._username = username; return this; } public HadoopJobData setStartTime(long startTime) { - this.startTime = startTime; + this._startTime = startTime; return this; } public HadoopJobData setUrl(String url) { - this.url = url; + this._url = url; return this; } public HadoopJobData setCounters(HadoopCounterHolder counterHolder) { - this.counterHolder = counterHolder; + this._counterHolder = counterHolder; return this; } public HadoopJobData setMapperData(HadoopTaskData[] mappers) { - this.mapperData = mappers; + this._mapperData = mappers; return this; } public HadoopJobData setReducerData(HadoopTaskData[] reducers) { - this.reducerData = reducers; + this._reducerData = reducers; return this; } public HadoopJobData setJobConf(Properties jobConf) { - this.jobConf = jobConf; + this._jobConf = jobConf; return this; } public HadoopCounterHolder getCounters() { - return counterHolder; + return _counterHolder; } public HadoopTaskData[] getMapperData() { - return mapperData; + return _mapperData; } public HadoopTaskData[] getReducerData() { - return reducerData; + return _reducerData; } public Properties getJobConf() { - return jobConf; + return _jobConf; } public String getUsername() { - return username; + return _username; } public long getStartTime() { - return startTime; + return _startTime; } public String getUrl() { - return url; + return _url; } public String getJobName() { - return jobName; + return _jobName; } public String getJobId() { - return jobId; + return _jobId; } } diff --git a/app/com/linkedin/drelephant/hadoop/HadoopSecurity.java b/app/com/linkedin/drelephant/hadoop/HadoopSecurity.java index aeb110b45..457e8b3b3 100644 --- a/app/com/linkedin/drelephant/hadoop/HadoopSecurity.java +++ b/app/com/linkedin/drelephant/hadoop/HadoopSecurity.java @@ -12,38 +12,38 @@ public class HadoopSecurity { private static final Logger logger = Logger.getLogger(HadoopSecurity.class); - private UserGroupInformation loginUser = null; + private UserGroupInformation _loginUser = null; - private String keytabLocation; - private String keytabUser; - private boolean securityEnabled = false; + private String _keytabLocation; + private String _keytabUser; + private boolean _securityEnabled = false; public HadoopSecurity() throws IOException { Configuration conf = new Configuration(); UserGroupInformation.setConfiguration(conf); - securityEnabled = UserGroupInformation.isSecurityEnabled(); - if (securityEnabled) { - keytabLocation = Play.application().configuration().getString("keytab.location"); - keytabUser = Play.application().configuration().getString("keytab.user"); + _securityEnabled = UserGroupInformation.isSecurityEnabled(); + if (_securityEnabled) { + _keytabLocation = Play.application().configuration().getString("keytab.location"); + _keytabUser = Play.application().configuration().getString("keytab.user"); checkLogin(); } } public UserGroupInformation getUGI() throws IOException { checkLogin(); - return loginUser; + return _loginUser; } public void checkLogin() throws IOException { - if (loginUser == null) { + if (_loginUser == null) { logger.info("No login user. Creating login user"); - logger.info("Logging with " + keytabUser + " and " + keytabLocation); - UserGroupInformation.loginUserFromKeytab(keytabUser, keytabLocation); - loginUser = UserGroupInformation.getLoginUser(); - logger.info("Logged in with user " + loginUser); + logger.info("Logging with " + _keytabUser + " and " + _keytabLocation); + UserGroupInformation.loginUserFromKeytab(_keytabUser, _keytabLocation); + _loginUser = UserGroupInformation.getLoginUser(); + logger.info("Logged in with user " + _loginUser); } else { - loginUser.checkTGTAndReloginFromKeytab(); + _loginUser.checkTGTAndReloginFromKeytab(); } } diff --git a/app/com/linkedin/drelephant/hadoop/HadoopTaskData.java b/app/com/linkedin/drelephant/hadoop/HadoopTaskData.java index 7c599ac36..c53750f98 100644 --- a/app/com/linkedin/drelephant/hadoop/HadoopTaskData.java +++ b/app/com/linkedin/drelephant/hadoop/HadoopTaskData.java @@ -1,55 +1,55 @@ package com.linkedin.drelephant.hadoop; -public class HadoopTaskData{ - private HadoopCounterHolder counterHolder; - private long startTime = 0; - private long endTime = 0; - private long shuffleTime = 0; - private long sortTime = 0; - private boolean timed = false; - - public HadoopTaskData(HadoopCounterHolder counterHolder, long[] time) { - this.counterHolder = counterHolder; - this.startTime = time[0]; - this.endTime = time[1]; - this.shuffleTime = time[2]; - this.sortTime = time[3]; - this.timed = true; - } - - public HadoopTaskData(HadoopCounterHolder counterHolder) { - this.counterHolder = counterHolder; - } - - public HadoopCounterHolder getCounters() { - return counterHolder; - } - - public long getStartTime() { - return startTime; - } - - public long getEndTime() { - return endTime; - } - - public long getRunTime() { - return endTime - startTime; - } - - public long getExecutionTime() { - return endTime - startTime - shuffleTime - sortTime; - } - - public long getShuffleTime() { - return shuffleTime; - } - - public long getSortTime() { - return sortTime; - } - - public boolean timed() { - return timed; - } +public class HadoopTaskData { + private HadoopCounterHolder _counterHolder; + private long _startTime = 0; + private long _endTime = 0; + private long _shuffleTime = 0; + private long _sortTime = 0; + private boolean _timed = false; + + public HadoopTaskData(HadoopCounterHolder counterHolder, long[] time) { + this._counterHolder = counterHolder; + this._startTime = time[0]; + this._endTime = time[1]; + this._shuffleTime = time[2]; + this._sortTime = time[3]; + this._timed = true; + } + + public HadoopTaskData(HadoopCounterHolder counterHolder) { + this._counterHolder = counterHolder; + } + + public HadoopCounterHolder getCounters() { + return _counterHolder; + } + + public long getStartTime() { + return _startTime; + } + + public long getEndTime() { + return _endTime; + } + + public long getRunTime() { + return _endTime - _startTime; + } + + public long getExecutionTime() { + return _endTime - _startTime - _shuffleTime - _sortTime; + } + + public long getShuffleTime() { + return _shuffleTime; + } + + public long getSortTime() { + return _sortTime; + } + + public boolean timed() { + return _timed; + } } diff --git a/app/com/linkedin/drelephant/math/Statistics.java b/app/com/linkedin/drelephant/math/Statistics.java index 1a7cdad2c..e49dda498 100644 --- a/app/com/linkedin/drelephant/math/Statistics.java +++ b/app/com/linkedin/drelephant/math/Statistics.java @@ -9,172 +9,175 @@ import java.util.List; import java.util.Random; -public class Statistics { - - public static final long SECOND = 1000L; - public static final long MINUTE = 60L * SECOND; - public static final long HOUR = 60L * MINUTE; - - /** - * Check if the array has deviating elements. - *

- * Deviating elements are found by comparing each individual value against the average. - * - * @param values the array of values to check - * @param buffer the amount to ignore as a buffer for smaller valued lists - * @param factor the amount of allowed deviation is calculated from average * factor - * @return the index of the deviating value, or -1 if - */ - public static int[] deviates(long[] values, long buffer, double factor) { - if (values == null || values.length == 0) { - return new int[0]; - } - - long avg = average(values); - - //Find deviated elements - - long minimumDiff = Math.max(buffer, (long) (avg * factor)); - List deviatedElements = new ArrayList(); - - for (int i = 0; i < values.length; i++) { - long diff = values[i] - avg; - if (diff > minimumDiff) { - deviatedElements.add(i); - } - } - - int[] result = new int[deviatedElements.size()]; - for (int i = 0; i < result.length; i++) { - result[i] = deviatedElements.get(i); - } - - return result; - } - public static long[][] findTwoGroups(long[] values) { - return findTwoGroupsRecursive(values, average(values), 2); +public final class Statistics { + + public static final long SECOND = 1000L; + public static final long MINUTE = 60L * SECOND; + public static final long HOUR = 60L * MINUTE; + + private Statistics() { + } + + /** + * Check if the array has deviating elements. + *

+ * Deviating elements are found by comparing each individual value against the average. + * + * @param values the array of values to check + * @param buffer the amount to ignore as a buffer for smaller valued lists + * @param factor the amount of allowed deviation is calculated from average * factor + * @return the index of the deviating value, or -1 if + */ + public static int[] deviates(long[] values, long buffer, double factor) { + if (values == null || values.length == 0) { + return new int[0]; } - public static long[][] findTwoGroupsRecursive(long[] values, long middle, int levels) { - if (levels > 0) { - long[][] result = two_means(values, middle); - long newMiddle = average(result[1]) - average(result[0]); - return findTwoGroupsRecursive(values, newMiddle, levels - 1); - } - return two_means(values, middle); - } + long avg = average(values); - private static long[][] two_means(long[] values, long middle) { - List smaller = new ArrayList(); - List larger = new ArrayList(); - for (int i = 0; i < values.length; i++) { - if (values[i] < middle) { - smaller.add(values[i]); - } else { - larger.add(values[i]); - } - } - - long[][] result = new long[2][]; - result[0] = toArray(smaller); - result[1] = toArray(larger); - - return result; - } + //Find deviated elements - private static long[] toArray(List input) { - long[] result = new long[input.size()]; - for (int i = 0; i < result.length; i++) { - result[i] = input.get(i); - } - return result; + long minimumDiff = Math.max(buffer, (long) (avg * factor)); + List deviatedElements = new ArrayList(); + + for (int i = 0; i < values.length; i++) { + long diff = values[i] - avg; + if (diff > minimumDiff) { + deviatedElements.add(i); + } } - public static long average(long[] values) { - //Find average - double sum = 0d; - for (long value : values) { - sum += value; - } - return (long) (sum / (double) values.length); + int[] result = new int[deviatedElements.size()]; + for (int i = 0; i < result.length; i++) { + result[i] = deviatedElements.get(i); } - public static long average(List values) { - //Find average - double sum = 0d; - for (long value : values) { - sum += value; + return result; + } + + public static long[][] findTwoGroups(long[] values) { + return findTwoGroupsRecursive(values, average(values), 2); + } + + public static long[][] findTwoGroupsRecursive(long[] values, long middle, int levels) { + if (levels > 0) { + long[][] result = twoMeans(values, middle); + long newMiddle = average(result[1]) - average(result[0]); + return findTwoGroupsRecursive(values, newMiddle, levels - 1); + } + return twoMeans(values, middle); + } + + private static long[][] twoMeans(long[] values, long middle) { + List smaller = new ArrayList(); + List larger = new ArrayList(); + for (int i = 0; i < values.length; i++) { + if (values[i] < middle) { + smaller.add(values[i]); + } else { + larger.add(values[i]); } - return (long) (sum / (double) values.size()); } - public static String describeFactor(long value, long compare, String suffix) { - double factor = (double) value / (double) compare; - if (Double.isNaN(factor)) { - return ""; - } - return "(" + String.format("%.2f", factor) + suffix + ")"; + long[][] result = new long[2][]; + result[0] = toArray(smaller); + result[1] = toArray(larger); + + return result; + } + + private static long[] toArray(List input) { + long[] result = new long[input.size()]; + for (int i = 0; i < result.length; i++) { + result[i] = input.get(i); + } + return result; + } + + public static long average(long[] values) { + //Find average + double sum = 0d; + for (long value : values) { + sum += value; } + return (long) (sum / (double) values.length); + } + + public static long average(List values) { + //Find average + double sum = 0d; + for (long value : values) { + sum += value; + } + return (long) (sum / (double) values.size()); + } - public static String readableTimespan(long milliseconds) { - long seconds = milliseconds / 1000; - long minutes = seconds / 60; - long hours = minutes / 60; - minutes %= 60; - seconds %= 60; - StringBuilder sb = new StringBuilder(); - if (hours > 0) { - sb.append(hours).append("hr "); - } - if (minutes > 0) { - sb.append(minutes).append("min "); - } - if (seconds > 0) { - sb.append(seconds).append("sec "); - } - return sb.toString().trim(); + public static String describeFactor(long value, long compare, String suffix) { + double factor = (double) value / (double) compare; + if (Double.isNaN(factor)) { + return ""; + } + return "(" + String.format("%.2f", factor) + suffix + ")"; + } + + public static String readableTimespan(long milliseconds) { + long seconds = milliseconds / 1000; + long minutes = seconds / 60; + long hours = minutes / 60; + minutes %= 60; + seconds %= 60; + StringBuilder sb = new StringBuilder(); + if (hours > 0) { + sb.append(hours).append("hr "); } + if (minutes > 0) { + sb.append(minutes).append("min "); + } + if (seconds > 0) { + sb.append(seconds).append("sec "); + } + return sb.toString().trim(); + } - public static Severity getNumTasksSeverity(long numTasks) { - return Severity.getSeverityAscending(numTasks, - 10, 50, 100, 200); + public static Severity getNumTasksSeverity(long numTasks) { + return Severity.getSeverityAscending(numTasks, 10, 50, 100, 200); + } + + public static T[] createSample(Class clazz, T[] objects, int size) { + //Skip this process if number of items already smaller than sample size + if (objects.length <= size) { + return objects; } - public static T[] createSample(Class clazz, T[] objects, int size) { - //Skip this process if number of items already smaller than sample size - if (objects.length <= size) { - return objects; - } + @SuppressWarnings("unchecked") + T[] result = (T[]) Array.newInstance(clazz, size); - @SuppressWarnings("unchecked") - T[] result = (T[]) Array.newInstance(clazz, size); + //Shuffle a clone copy + T[] clone = objects.clone(); + Collections.shuffle(Arrays.asList(clone)); - //Shuffle a clone copy - T[] clone = objects.clone(); - Collections.shuffle(Arrays.asList(clone)); + //Take the first n items + System.arraycopy(clone, 0, result, 0, size); - //Take the first n items - System.arraycopy(clone, 0, result, 0, size); + return result; + } - return result; + // Create a random sample within the original array + public static void shuffleArraySample(T[] array, int sampleSize) { + if (array.length <= sampleSize) { + return; } - // Create a random sample within the original array - public static void shuffleArraySample(T[] array, int sampleSize) { - if(array.length <= sampleSize) { - return; - } - - T temp; - int index; - Random random = new Random(); - - for (int i = 0; i resultQueue; - private AtomicBoolean running = new AtomicBoolean(true); + private LinkedBlockingQueue _resultQueue; + private AtomicBoolean _running = new AtomicBoolean(true); - private String smtpHost; - private int smtpPort; - private String smtpFrom; - private DefaultAuthenticator authenticator = null; + private String _smtpHost; + private int _smtpPort; + private String _smtpFrom; + private DefaultAuthenticator _authenticator = null; - public EmailThread() { - setName("Email Thread"); - resultQueue = new LinkedBlockingQueue(); - smtpHost = Play.application().configuration().getString("smtp.host"); - smtpPort = Play.application().configuration().getInt("smtp.port"); - String smtpUser = Play.application().configuration().getString("smtp.user"); - String smtpPassword = Play.application().configuration().getString("smtp.password"); - if (smtpUser != null && !smtpUser.isEmpty()) { - authenticator = new DefaultAuthenticator(smtpUser, smtpPassword); - } - smtpFrom = Play.application().configuration().getString("smtp.from"); + public EmailThread() { + setName("Email Thread"); + _resultQueue = new LinkedBlockingQueue(); + _smtpHost = Play.application().configuration().getString("smtp.host"); + _smtpPort = Play.application().configuration().getInt("smtp.port"); + String smtpUser = Play.application().configuration().getString("smtp.user"); + String smtpPassword = Play.application().configuration().getString("smtp.password"); + if (smtpUser != null && !smtpUser.isEmpty()) { + _authenticator = new DefaultAuthenticator(smtpUser, smtpPassword); } + _smtpFrom = Play.application().configuration().getString("smtp.from"); + } - @Override - public void run() { - while (running.get()) { - JobResult result = null; - while (result == null && running.get()) { - try { - result = resultQueue.take(); - } catch (InterruptedException e) { - //Ignored - } - } - if (!running.get()) { - return; - } - Severity worstSeverity = result.severity; - if (worstSeverity == Severity.CRITICAL) { - //Send email right away - sendCriticalEmail(result); - } else if (worstSeverity == Severity.SEVERE) { - //Keep track of a digest and send in intervals - } + @Override + public void run() { + while (_running.get()) { + JobResult result = null; + while (result == null && _running.get()) { + try { + result = _resultQueue.take(); + } catch (InterruptedException e) { + //Ignored } + } + if (!_running.get()) { + return; + } + Severity worstSeverity = result.severity; + if (worstSeverity == Severity.CRITICAL) { + //Send email right away + sendCriticalEmail(result); + } else if (worstSeverity == Severity.SEVERE) { + //Keep track of a digest and send in intervals + } } + } - public void kill() { - running.set(false); - this.interrupt(); - } + public void kill() { + _running.set(false); + this.interrupt(); + } - public void enqueue(JobResult result) { - try { - resultQueue.put(result); - } catch (InterruptedException e) { - e.printStackTrace(); - } + public void enqueue(JobResult result) { + try { + _resultQueue.put(result); + } catch (InterruptedException e) { + e.printStackTrace(); } + } - private void sendCriticalEmail(JobResult result) { - try { - //Generate content - String html = emailcritical.render(result).body(); + private void sendCriticalEmail(JobResult result) { + try { + //Generate content + String html = emailcritical.render(result).body(); - //Send email - HtmlEmail email = new HtmlEmail(); - email.setHostName(smtpHost); - email.setSmtpPort(smtpPort); - if (authenticator != null) { - email.setAuthenticator(authenticator); - } - email.setSSLOnConnect(true); - email.setFrom(smtpFrom); - email.addTo(result.username + "@linkedin.com"); - email.setSubject("Dr. Elephant - Hadoop Job Status Notification"); - email.setHtmlMsg(html); - email.setDebug(true); - /////////////////// - // - // WARNING: This next line will send out the emails. - // Do NOT uncomment before proper testing and mental meditation. - // - /////////////////// - //email.send(); - } catch (EmailException e) { - e.printStackTrace(); - } + //Send email + HtmlEmail email = new HtmlEmail(); + email.setHostName(_smtpHost); + email.setSmtpPort(_smtpPort); + if (_authenticator != null) { + email.setAuthenticator(_authenticator); + } + email.setSSLOnConnect(true); + email.setFrom(_smtpFrom); + email.addTo(result.username + "@linkedin.com"); + email.setSubject("Dr. Elephant - Hadoop Job Status Notification"); + email.setHtmlMsg(html); + email.setDebug(true); + /////////////////// + // + // WARNING: This next line will send out the emails. + // Do NOT uncomment before proper testing and mental meditation. + // + /////////////////// + //email.send(); + } catch (EmailException e) { + e.printStackTrace(); } + } } diff --git a/app/com/linkedin/drelephant/util/Utils.java b/app/com/linkedin/drelephant/util/Utils.java index 1a37516eb..1b24f0cde 100644 --- a/app/com/linkedin/drelephant/util/Utils.java +++ b/app/com/linkedin/drelephant/util/Utils.java @@ -3,70 +3,74 @@ import java.util.ArrayList; import java.util.List; -public class Utils { - public static String combineCsvLines(String[] lines) { - StringBuilder sb = new StringBuilder(); - for (String line : lines) { - sb.append(line).append("\n"); - } - return sb.toString().trim(); +public final class Utils { + + private Utils() { + } + + public static String combineCsvLines(String[] lines) { + StringBuilder sb = new StringBuilder(); + for (String line : lines) { + sb.append(line).append("\n"); } + return sb.toString().trim(); + } - public static String createCsvLine(String... parts) { - StringBuilder sb = new StringBuilder(); - String quotes = "\""; - String comma = ","; - for (int i = 0; i < parts.length; i++) { - sb.append(quotes).append(parts[i].replaceAll(quotes, quotes + quotes)).append(quotes); - if (i != parts.length - 1) { - sb.append(comma); - } - } - return sb.toString(); + public static String createCsvLine(String... parts) { + StringBuilder sb = new StringBuilder(); + String quotes = "\""; + String comma = ","; + for (int i = 0; i < parts.length; i++) { + sb.append(quotes).append(parts[i].replaceAll(quotes, quotes + quotes)).append(quotes); + if (i != parts.length - 1) { + sb.append(comma); + } } + return sb.toString(); + } - public static String[][] parseCsvLines(String data) { - if (data.isEmpty()) { - return new String[0][]; - } - String[] lines = data.split("\n"); - String[][] result = new String[lines.length][]; - for (int i = 0; i < lines.length; i++) { - result[i] = parseCsvLine(lines[i]); - } - return result; + public static String[][] parseCsvLines(String data) { + if (data.isEmpty()) { + return new String[0][]; } + String[] lines = data.split("\n"); + String[][] result = new String[lines.length][]; + for (int i = 0; i < lines.length; i++) { + result[i] = parseCsvLine(lines[i]); + } + return result; + } - public static String[] parseCsvLine(String line) { - List store = new ArrayList(); - StringBuilder curVal = new StringBuilder(); - boolean inquotes = false; - for (int i = 0; i < line.length(); i++) { - char ch = line.charAt(i); - if (inquotes) { - if (ch == '\"') { - inquotes = false; - } else { - curVal.append(ch); - } - } else { - if (ch == '\"') { - inquotes = true; - if (curVal.length() > 0) { - //if this is the second quote in a value, add a quote - //this is for the double quote in the middle of a value - curVal.append('\"'); - } - } else if (ch == ',') { - store.add(curVal.toString()); - curVal = new StringBuilder(); - } else { - curVal.append(ch); - } - } + public static String[] parseCsvLine(String line) { + List store = new ArrayList(); + StringBuilder curVal = new StringBuilder(); + boolean inquotes = false; + for (int i = 0; i < line.length(); i++) { + char ch = line.charAt(i); + if (inquotes) { + if (ch == '\"') { + inquotes = false; + } else { + curVal.append(ch); + } + } else { + if (ch == '\"') { + inquotes = true; + if (curVal.length() > 0) { + //if this is the second quote in a value, add a quote + //this is for the double quote in the middle of a value + curVal.append('\"'); + } + } else if (ch == ',') { + store.add(curVal.toString()); + curVal = new StringBuilder(); + } else { + curVal.append(ch); } - store.add(curVal.toString()); - return store.toArray(new String[store.size()]); + } } + store.add(curVal.toString()); + return store.toArray(new String[store.size()]); + } } diff --git a/app/controllers/Application.java b/app/controllers/Application.java index 8e7e03fec..9f7dc4564 100644 --- a/app/controllers/Application.java +++ b/app/controllers/Application.java @@ -29,13 +29,14 @@ import com.linkedin.drelephant.analysis.heuristics.ReducerTimeHeuristic; import com.linkedin.drelephant.analysis.heuristics.ShuffleSortHeuristic; + public class Application extends Controller { private static final long DAY = 24 * 60 * 60 * 1000; private static final long FETCH_DELAY = 60 * 1000; - private static long lastFetch = 0; - private static int numJobsAnalyzed = 0; - private static int numJobsCritical = 0; - private static int numJobsSevere = 0; + private static long _lastFetch = 0; + private static int _numJobsAnalyzed = 0; + private static int _numJobsCritical = 0; + private static int _numJobsSevere = 0; public static Result search() { DynamicForm form = Form.form().bindFromRequest(request()); @@ -88,32 +89,28 @@ public static Result search() { e.printStackTrace(); } } - List results = - query.order().desc("analysisTime").setMaxRows(50) - .fetch("heuristicResults").findList(); + List results = query.order().desc("analysisTime").setMaxRows(50).fetch("heuristicResults").findList(); return ok(search.render(multijob.render("Results", results))); } } public static Result dashboard(int page) { long now = System.currentTimeMillis(); - if (now - lastFetch > FETCH_DELAY) { - numJobsAnalyzed = - JobResult.find.where().gt("analysisTime", now - DAY).findRowCount(); - numJobsCritical = - JobResult.find.where().gt("analysisTime", now - DAY) - .eq("severity", Severity.CRITICAL.getValue()).findRowCount(); - numJobsSevere = - JobResult.find.where().gt("analysisTime", now - DAY) - .eq("severity", Severity.SEVERE.getValue()).findRowCount(); - lastFetch = now; + if (now - _lastFetch > FETCH_DELAY) { + _numJobsAnalyzed = JobResult.find.where().gt("analysisTime", now - DAY).findRowCount(); + _numJobsCritical = + JobResult.find.where().gt("analysisTime", now - DAY).eq("severity", Severity.CRITICAL.getValue()) + .findRowCount(); + _numJobsSevere = + JobResult.find.where().gt("analysisTime", now - DAY).eq("severity", Severity.SEVERE.getValue()) + .findRowCount(); + _lastFetch = now; } List results = - JobResult.find.where().gt("analysisTime", now - DAY).order() - .desc("analysisTime").setMaxRows(50).fetch("heuristicResults") - .findList(); + JobResult.find.where().gt("analysisTime", now - DAY).order().desc("analysisTime").setMaxRows(50) + .fetch("heuristicResults").findList(); - return ok(index.render(numJobsAnalyzed, numJobsSevere, numJobsCritical, + return ok(index.render(_numJobsAnalyzed, _numJobsSevere, _numJobsCritical, multijob.render("Latest analysis", results))); } @@ -125,19 +122,19 @@ public static Result help() { String title = "Help"; if (topic != null && !topic.isEmpty()) { - if (topic.equals(MapperDataSkewHeuristic.heuristicName)) { + if (topic.equals(MapperDataSkewHeuristic.HEURISTIC_NAME)) { page = helpMapperDataSkew.render(); - } else if (topic.equals(ReducerDataSkewHeuristic.heuristicName)) { + } else if (topic.equals(ReducerDataSkewHeuristic.HEURISTIC_NAME)) { page = helpReducerDataSkew.render(); - } else if (topic.equals(MapperInputSizeHeuristic.heuristicName)) { + } else if (topic.equals(MapperInputSizeHeuristic.HEURISTIC_NAME)) { page = helpMapperInputSize.render(); - } else if (topic.equals(MapperSpeedHeuristic.heuristicName)) { + } else if (topic.equals(MapperSpeedHeuristic.HEURISTIC_NAME)) { page = helpMapperSpeed.render(); - } else if (topic.equals(ReducerTimeHeuristic.heuristicName)) { + } else if (topic.equals(ReducerTimeHeuristic.HEURISTIC_NAME)) { page = helpReducerTime.render(); - } else if (topic.equals(ShuffleSortHeuristic.heuristicName)) { + } else if (topic.equals(ShuffleSortHeuristic.HEURISTIC_NAME)) { page = helpShuffleSort.render(); - } else if (topic.equals(JobQueueLimitHeuristic.heuristicName)) { + } else if (topic.equals(JobQueueLimitHeuristic.HEURISTIC_NAME)) { page = helpJobQueueLimit.render(); } else if (topic.equals(ElephantAnalyser.NO_DATA)) { page = helpNoData.render(); diff --git a/app/model/JobHeuristicResult.java b/app/model/JobHeuristicResult.java index c7bd3c8ec..637fa03e5 100644 --- a/app/model/JobHeuristicResult.java +++ b/app/model/JobHeuristicResult.java @@ -1,5 +1,12 @@ package model; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Lob; +import javax.persistence.ManyToOne; + import com.fasterxml.jackson.annotation.JsonBackReference; import com.fasterxml.jackson.annotation.JsonIgnore; import com.linkedin.drelephant.analysis.Severity; @@ -7,36 +14,35 @@ import play.db.ebean.Model; -import javax.persistence.*; @Entity public class JobHeuristicResult extends Model { - private static final long serialVersionUID = 123L; + private static final long serialVersionUID = 123L; - @JsonIgnore - @Id - public int id; + @JsonIgnore + @Id + public int id; - @JsonBackReference - @ManyToOne(cascade = CascadeType.ALL) - public JobResult job; + @JsonBackReference + @ManyToOne(cascade = CascadeType.ALL) + public JobResult job; - @Column - public Severity severity; + @Column + public Severity severity; - @Column - public String analysisName; + @Column + public String analysisName; - @JsonIgnore - @Lob - public String data; + @JsonIgnore + @Lob + public String data; - @JsonIgnore - @Column - public int dataColumns; + @JsonIgnore + @Column + public int dataColumns; - public String[][] getDataArray() { - return Utils.parseCsvLines(data); - } + public String[][] getDataArray() { + return Utils.parseCsvLines(data); + } } diff --git a/app/model/JobResult.java b/app/model/JobResult.java index 6b1a65959..b76739545 100644 --- a/app/model/JobResult.java +++ b/app/model/JobResult.java @@ -4,10 +4,16 @@ import com.linkedin.drelephant.analysis.Severity; import play.db.ebean.Model; -import javax.persistence.*; import java.util.List; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.OneToMany; + + @Entity public class JobResult extends Model { @@ -58,6 +64,5 @@ public class JobResult extends Model { @OneToMany(cascade = CascadeType.ALL, mappedBy = "job") public List heuristicResults; - public static Finder find = new Finder( - String.class, JobResult.class); + public static Finder find = new Finder(String.class, JobResult.class); } diff --git a/app/model/JobType.java b/app/model/JobType.java index cc17a7d38..4ea0552ae 100644 --- a/app/model/JobType.java +++ b/app/model/JobType.java @@ -2,23 +2,24 @@ import com.avaje.ebean.annotation.EnumValue; + public enum JobType { - @EnumValue("Hadoop") - HADOOPJAVA("HadoopJava"), + @EnumValue("Hadoop") + HADOOPJAVA("HadoopJava"), - @EnumValue("Pig") - PIG("Pig"), + @EnumValue("Pig") + PIG("Pig"), - @EnumValue("Hive") - HIVE("Hive"); + @EnumValue("Hive") + HIVE("Hive"); - private String text; + private String _text; - private JobType(String text) { - this.text = text; - } + private JobType(String text) { + this._text = text; + } - public String getText() { - return text; - } + public String getText() { + return _text; + } } diff --git a/app/model/StringResult.java b/app/model/StringResult.java index 150122572..90aa14186 100644 --- a/app/model/StringResult.java +++ b/app/model/StringResult.java @@ -4,6 +4,7 @@ import com.avaje.ebean.annotation.Sql; + @Entity @Sql public class StringResult { @@ -16,4 +17,4 @@ public void setString(String val) { public String getString() { return string; } -} \ No newline at end of file +} diff --git a/app/views/help.scala.html b/app/views/help.scala.html index 51dc13440..468dcea9e 100644 --- a/app/views/help.scala.html +++ b/app/views/help.scala.html @@ -3,7 +3,7 @@ @main("Dr. Elephant", "help") { @tags.column(3) { @tags.panel(){ Topics } { - @for(heuristic <- com.linkedin.drelephant.ElephantAnalyser.instance().heuristicNames) { + @for(heuristic <- com.linkedin.drelephant.ElephantAnalyser.instance()._heuristicNames) { @heuristic
} @com.linkedin.drelephant.ElephantAnalyser.NO_DATA
diff --git a/app/views/search.scala.html b/app/views/search.scala.html index 1738d8273..7e8e696f7 100644 --- a/app/views/search.scala.html +++ b/app/views/search.scala.html @@ -35,7 +35,7 @@

diff --git a/test/com/linkedin/drelephant/analysis/heuristics/JobQueueLimitHeuristicTest.java b/test/com/linkedin/drelephant/analysis/heuristics/JobQueueLimitHeuristicTest.java index d65dc052a..5f78256d3 100644 --- a/test/com/linkedin/drelephant/analysis/heuristics/JobQueueLimitHeuristicTest.java +++ b/test/com/linkedin/drelephant/analysis/heuristics/JobQueueLimitHeuristicTest.java @@ -18,8 +18,8 @@ public class JobQueueLimitHeuristicTest extends TestCase { - Heuristic heuristic = new JobQueueLimitHeuristic(); - private static final int numTasks = Constants.SHUFFLE_SORT_MAX_SAMPLE_SIZE; + Heuristic _heuristic = new JobQueueLimitHeuristic(); + private static final int NUM_TASKS = Constants.SHUFFLE_SORT_MAX_SAMPLE_SIZE; @Test public void testRuntimeCritical() throws IOException { @@ -48,21 +48,21 @@ public void testNonDefaultRuntimeNone() throws IOException { private Severity analyzeJob(long runtime, String queueName) throws IOException { HadoopCounterHolder dummyCounter = new HadoopCounterHolder(null); - HadoopTaskData[] mappers = new HadoopTaskData[2 * numTasks / 3]; - HadoopTaskData[] reducers = new HadoopTaskData[numTasks / 3]; + HadoopTaskData[] mappers = new HadoopTaskData[2 * NUM_TASKS / 3]; + HadoopTaskData[] reducers = new HadoopTaskData[NUM_TASKS / 3]; Properties jobConf = new Properties(); jobConf.put("mapred.job.queue.name", queueName); int i = 0; - for (; i < 2 * numTasks / 3; i++) { + for (; i < 2 * NUM_TASKS / 3; i++) { mappers[i] = new HadoopTaskData(dummyCounter, new long[] { 0, runtime, 0, 0 }); } - for (i = 0; i < numTasks / 3; i++) { + for (i = 0; i < NUM_TASKS / 3; i++) { reducers[i] = new HadoopTaskData(dummyCounter, new long[] { 0, runtime, 0, 0 }); } HadoopJobData data = new HadoopJobData().setCounters(dummyCounter).setReducerData(reducers).setMapperData(mappers) .setJobConf(jobConf); - HeuristicResult result = heuristic.apply(data); + HeuristicResult result = _heuristic.apply(data); return result.getSeverity(); } } diff --git a/test/com/linkedin/drelephant/analysis/heuristics/MapperDataSkewHeuristicTest.java b/test/com/linkedin/drelephant/analysis/heuristics/MapperDataSkewHeuristicTest.java index b6016647a..821c261ad 100644 --- a/test/com/linkedin/drelephant/analysis/heuristics/MapperDataSkewHeuristicTest.java +++ b/test/com/linkedin/drelephant/analysis/heuristics/MapperDataSkewHeuristicTest.java @@ -17,35 +17,35 @@ public class MapperDataSkewHeuristicTest extends TestCase { - private static final long unitSize = Constants.HDFS_BLOCK_SIZE / 64; - Heuristic heuristic = new MapperDataSkewHeuristic(); + private static final long UNITSIZE = Constants.HDFS_BLOCK_SIZE / 64; + Heuristic _heuristic = new MapperDataSkewHeuristic(); public void testCritical() throws IOException { - assertEquals(Severity.CRITICAL, analyzeJob(200, 200, 1 * unitSize, 100 * unitSize)); + assertEquals(Severity.CRITICAL, analyzeJob(200, 200, 1 * UNITSIZE, 100 * UNITSIZE)); } public void testSevere() throws IOException { - assertEquals(Severity.SEVERE, analyzeJob(200, 200, 10 * unitSize, 100 * unitSize)); + assertEquals(Severity.SEVERE, analyzeJob(200, 200, 10 * UNITSIZE, 100 * UNITSIZE)); } public void testModerate() throws IOException { - assertEquals(Severity.MODERATE, analyzeJob(200, 200, 20 * unitSize, 100 * unitSize)); + assertEquals(Severity.MODERATE, analyzeJob(200, 200, 20 * UNITSIZE, 100 * UNITSIZE)); } public void testLow() throws IOException { - assertEquals(Severity.LOW, analyzeJob(200, 200, 30 * unitSize, 100 * unitSize)); + assertEquals(Severity.LOW, analyzeJob(200, 200, 30 * UNITSIZE, 100 * UNITSIZE)); } public void testNone() throws IOException { - assertEquals(Severity.NONE, analyzeJob(200, 200, 50 * unitSize, 100 * unitSize)); + assertEquals(Severity.NONE, analyzeJob(200, 200, 50 * UNITSIZE, 100 * UNITSIZE)); } public void testSmallFiles() throws IOException { - assertEquals(Severity.NONE, analyzeJob(200, 200, 1 * unitSize, 5 * unitSize)); + assertEquals(Severity.NONE, analyzeJob(200, 200, 1 * UNITSIZE, 5 * UNITSIZE)); } public void testSmallTasks() throws IOException { - assertEquals(Severity.NONE, analyzeJob(5, 5, 10 * unitSize, 100 * unitSize)); + assertEquals(Severity.NONE, analyzeJob(5, 5, 10 * UNITSIZE, 100 * UNITSIZE)); } private Severity analyzeJob(int numSmallTasks, int numLargeTasks, long smallInputSize, long largeInputSize) @@ -53,10 +53,10 @@ private Severity analyzeJob(int numSmallTasks, int numLargeTasks, long smallInpu HadoopCounterHolder jobCounter = new HadoopCounterHolder(null); HadoopTaskData[] mappers = new HadoopTaskData[numSmallTasks + numLargeTasks]; - HadoopCounterHolder smallCounter = new HadoopCounterHolder(new HashMap()); + HadoopCounterHolder smallCounter = new HadoopCounterHolder(new HashMap()); smallCounter.set(HadoopCounterHolder.CounterName.HDFS_BYTES_READ, smallInputSize); - HadoopCounterHolder largeCounter = new HadoopCounterHolder(new HashMap()); + HadoopCounterHolder largeCounter = new HadoopCounterHolder(new HashMap()); largeCounter.set(HadoopCounterHolder.CounterName.HDFS_BYTES_READ, largeInputSize); int i = 0; @@ -68,7 +68,7 @@ private Severity analyzeJob(int numSmallTasks, int numLargeTasks, long smallInpu } HadoopJobData data = new HadoopJobData().setCounters(jobCounter).setMapperData(mappers); - HeuristicResult result = heuristic.apply(data); + HeuristicResult result = _heuristic.apply(data); return result.getSeverity(); } diff --git a/test/com/linkedin/drelephant/analysis/heuristics/MapperInputSizeHeuristicTest.java b/test/com/linkedin/drelephant/analysis/heuristics/MapperInputSizeHeuristicTest.java index a0d61f215..f32c34ee2 100644 --- a/test/com/linkedin/drelephant/analysis/heuristics/MapperInputSizeHeuristicTest.java +++ b/test/com/linkedin/drelephant/analysis/heuristics/MapperInputSizeHeuristicTest.java @@ -17,54 +17,54 @@ public class MapperInputSizeHeuristicTest extends TestCase { - private static final long unitSize = Constants.HDFS_BLOCK_SIZE; - Heuristic heuristic = new MapperInputSizeHeuristic(); + private static final long UNITSIZE = Constants.HDFS_BLOCK_SIZE; + Heuristic _heuristic = new MapperInputSizeHeuristic(); public void testLargeFileCritical() throws IOException { - assertEquals(Severity.CRITICAL, analyzeJob(100, 5 * unitSize)); + assertEquals(Severity.CRITICAL, analyzeJob(100, 5 * UNITSIZE)); } public void testLargeFileSevere() throws IOException { - assertEquals(Severity.SEVERE, analyzeJob(200, 5 * unitSize)); + assertEquals(Severity.SEVERE, analyzeJob(200, 5 * UNITSIZE)); } public void testLargeFileModerate() throws IOException { - assertEquals(Severity.MODERATE, analyzeJob(500, 5 * unitSize)); + assertEquals(Severity.MODERATE, analyzeJob(500, 5 * UNITSIZE)); } public void testLargeFileLow() throws IOException { - assertEquals(Severity.LOW, analyzeJob(1000, 5 * unitSize)); + assertEquals(Severity.LOW, analyzeJob(1000, 5 * UNITSIZE)); } public void testLargeFileNone() throws IOException { - assertEquals(Severity.NONE, analyzeJob(2000, 5 * unitSize)); + assertEquals(Severity.NONE, analyzeJob(2000, 5 * UNITSIZE)); } public void testSmallFileCritical() throws IOException { - assertEquals(Severity.CRITICAL, analyzeJob(500, unitSize / 32)); + assertEquals(Severity.CRITICAL, analyzeJob(500, UNITSIZE / 32)); } public void testSmallFileSevere() throws IOException { - assertEquals(Severity.SEVERE, analyzeJob(200, unitSize / 32)); + assertEquals(Severity.SEVERE, analyzeJob(200, UNITSIZE / 32)); } public void testSmallFileModerate() throws IOException { - assertEquals(Severity.MODERATE, analyzeJob(50, unitSize / 32)); + assertEquals(Severity.MODERATE, analyzeJob(50, UNITSIZE / 32)); } public void testSmallFileLow() throws IOException { - assertEquals(Severity.LOW, analyzeJob(10, unitSize / 32)); + assertEquals(Severity.LOW, analyzeJob(10, UNITSIZE / 32)); } public void testSmallFileNone() throws IOException { - assertEquals(Severity.NONE, analyzeJob(5, unitSize / 32)); + assertEquals(Severity.NONE, analyzeJob(5, UNITSIZE / 32)); } private Severity analyzeJob(int numTasks, long inputSize) throws IOException { HadoopCounterHolder jobCounter = new HadoopCounterHolder(null); HadoopTaskData[] mappers = new HadoopTaskData[numTasks]; - HadoopCounterHolder taskCounter = new HadoopCounterHolder(new HashMap()); + HadoopCounterHolder taskCounter = new HadoopCounterHolder(new HashMap()); taskCounter.set(HadoopCounterHolder.CounterName.HDFS_BYTES_READ, inputSize); int i = 0; @@ -73,7 +73,7 @@ private Severity analyzeJob(int numTasks, long inputSize) throws IOException { } HadoopJobData data = new HadoopJobData().setCounters(jobCounter).setMapperData(mappers); - HeuristicResult result = heuristic.apply(data); + HeuristicResult result = _heuristic.apply(data); return result.getSeverity(); } } diff --git a/test/com/linkedin/drelephant/analysis/heuristics/MapperSpeedHeuristicTest.java b/test/com/linkedin/drelephant/analysis/heuristics/MapperSpeedHeuristicTest.java index 880b5dd26..462ce4036 100644 --- a/test/com/linkedin/drelephant/analysis/heuristics/MapperSpeedHeuristicTest.java +++ b/test/com/linkedin/drelephant/analysis/heuristics/MapperSpeedHeuristicTest.java @@ -17,49 +17,49 @@ public class MapperSpeedHeuristicTest extends TestCase { - Heuristic heuristic = new MapperSpeedHeuristic(); - private static final long unitSize = Constants.HDFS_BLOCK_SIZE / 64; - private static final long minute = Statistics.MINUTE; - private static final int numTasks = Constants.SHUFFLE_SORT_MAX_SAMPLE_SIZE; + Heuristic _heuristic = new MapperSpeedHeuristic(); + private static final long UNITSIZE = Constants.HDFS_BLOCK_SIZE / 64; + private static final long MINUTE = Statistics.MINUTE; + private static final int NUMTASKS = Constants.SHUFFLE_SORT_MAX_SAMPLE_SIZE; public void testCritical() throws IOException { - assertEquals(Severity.CRITICAL, analyzeJob(120 * minute, 10000 * unitSize)); + assertEquals(Severity.CRITICAL, analyzeJob(120 * MINUTE, 10000 * UNITSIZE)); } public void testSevere() throws IOException { - assertEquals(Severity.SEVERE, analyzeJob(120 * minute, 50000 * unitSize)); + assertEquals(Severity.SEVERE, analyzeJob(120 * MINUTE, 50000 * UNITSIZE)); } public void testModerate() throws IOException { - assertEquals(Severity.MODERATE, analyzeJob(120 * minute, 100000 * unitSize)); + assertEquals(Severity.MODERATE, analyzeJob(120 * MINUTE, 100000 * UNITSIZE)); } public void testLow() throws IOException { - assertEquals(Severity.LOW, analyzeJob(120 * minute, 200000 * unitSize)); + assertEquals(Severity.LOW, analyzeJob(120 * MINUTE, 200000 * UNITSIZE)); } public void testNone() throws IOException { - assertEquals(Severity.NONE, analyzeJob(120 * minute, 500000 * unitSize)); + assertEquals(Severity.NONE, analyzeJob(120 * MINUTE, 500000 * UNITSIZE)); } public void testShortTask() throws IOException { - assertEquals(Severity.NONE, analyzeJob(2 * minute, 10 * unitSize)); + assertEquals(Severity.NONE, analyzeJob(2 * MINUTE, 10 * UNITSIZE)); } private Severity analyzeJob(long runtime, long readBytes) throws IOException { HadoopCounterHolder jobCounter = new HadoopCounterHolder(null); - HadoopTaskData[] mappers = new HadoopTaskData[numTasks]; + HadoopTaskData[] mappers = new HadoopTaskData[NUMTASKS]; - HadoopCounterHolder counter = new HadoopCounterHolder(new HashMap()); + HadoopCounterHolder counter = new HadoopCounterHolder(new HashMap()); counter.set(HadoopCounterHolder.CounterName.HDFS_BYTES_READ, readBytes); int i = 0; - for (; i < numTasks; i++) { - mappers[i] = new HadoopTaskData(counter, new long[]{0,runtime,0,0}); + for (; i < NUMTASKS; i++) { + mappers[i] = new HadoopTaskData(counter, new long[] { 0, runtime, 0, 0 }); } HadoopJobData data = new HadoopJobData().setCounters(jobCounter).setMapperData(mappers); - HeuristicResult result = heuristic.apply(data); + HeuristicResult result = _heuristic.apply(data); return result.getSeverity(); } } diff --git a/test/com/linkedin/drelephant/analysis/heuristics/ReducerDataSkewHeuristicTest.java b/test/com/linkedin/drelephant/analysis/heuristics/ReducerDataSkewHeuristicTest.java index 625d9da87..4339da4c4 100644 --- a/test/com/linkedin/drelephant/analysis/heuristics/ReducerDataSkewHeuristicTest.java +++ b/test/com/linkedin/drelephant/analysis/heuristics/ReducerDataSkewHeuristicTest.java @@ -16,35 +16,35 @@ public class ReducerDataSkewHeuristicTest extends TestCase { - private static final long unitSize = Constants.HDFS_BLOCK_SIZE / 64; - Heuristic heuristic = new ReducerDataSkewHeuristic(); + private static final long UNITSIZE = Constants.HDFS_BLOCK_SIZE / 64; + Heuristic _heuristic = new ReducerDataSkewHeuristic(); public void testCritical() throws IOException { - assertEquals(Severity.CRITICAL, analyzeJob(200, 200, 1 * unitSize, 100 * unitSize)); + assertEquals(Severity.CRITICAL, analyzeJob(200, 200, 1 * UNITSIZE, 100 * UNITSIZE)); } public void testSevere() throws IOException { - assertEquals(Severity.SEVERE, analyzeJob(200, 200, 10 * unitSize, 100 * unitSize)); + assertEquals(Severity.SEVERE, analyzeJob(200, 200, 10 * UNITSIZE, 100 * UNITSIZE)); } public void testModerate() throws IOException { - assertEquals(Severity.MODERATE, analyzeJob(200, 200, 20 * unitSize, 100 * unitSize)); + assertEquals(Severity.MODERATE, analyzeJob(200, 200, 20 * UNITSIZE, 100 * UNITSIZE)); } public void testLow() throws IOException { - assertEquals(Severity.LOW, analyzeJob(200, 200, 30 * unitSize, 100 * unitSize)); + assertEquals(Severity.LOW, analyzeJob(200, 200, 30 * UNITSIZE, 100 * UNITSIZE)); } public void testNone() throws IOException { - assertEquals(Severity.NONE, analyzeJob(200, 200, 50 * unitSize, 100 * unitSize)); + assertEquals(Severity.NONE, analyzeJob(200, 200, 50 * UNITSIZE, 100 * UNITSIZE)); } public void testSmallFiles() throws IOException { - assertEquals(Severity.NONE, analyzeJob(200, 200, 1 * unitSize, 5 * unitSize)); + assertEquals(Severity.NONE, analyzeJob(200, 200, 1 * UNITSIZE, 5 * UNITSIZE)); } public void testSmallTasks() throws IOException { - assertEquals(Severity.NONE, analyzeJob(5, 5, 10 * unitSize, 100 * unitSize)); + assertEquals(Severity.NONE, analyzeJob(5, 5, 10 * UNITSIZE, 100 * UNITSIZE)); } private Severity analyzeJob(int numSmallTasks, int numLargeTasks, long smallInputSize, long largeInputSize) @@ -52,10 +52,10 @@ private Severity analyzeJob(int numSmallTasks, int numLargeTasks, long smallInpu HadoopCounterHolder jobCounter = new HadoopCounterHolder(null); HadoopTaskData[] reducers = new HadoopTaskData[numSmallTasks + numLargeTasks]; - HadoopCounterHolder smallCounter = new HadoopCounterHolder(new HashMap()); + HadoopCounterHolder smallCounter = new HadoopCounterHolder(new HashMap()); smallCounter.set(HadoopCounterHolder.CounterName.REDUCE_SHUFFLE_BYTES, smallInputSize); - HadoopCounterHolder largeCounter = new HadoopCounterHolder(new HashMap()); + HadoopCounterHolder largeCounter = new HadoopCounterHolder(new HashMap()); largeCounter.set(HadoopCounterHolder.CounterName.REDUCE_SHUFFLE_BYTES, largeInputSize); int i = 0; @@ -66,8 +66,8 @@ private Severity analyzeJob(int numSmallTasks, int numLargeTasks, long smallInpu reducers[i] = new HadoopTaskData(largeCounter, new long[4]); } - HadoopJobData data = new HadoopJobData().setCounters(jobCounter).setReducerData(reducers); - HeuristicResult result = heuristic.apply(data); + HadoopJobData data = new HadoopJobData().setCounters(jobCounter).setReducerData(reducers); + HeuristicResult result = _heuristic.apply(data); return result.getSeverity(); } } diff --git a/test/com/linkedin/drelephant/analysis/heuristics/ReducerTimeHeuristicTest.java b/test/com/linkedin/drelephant/analysis/heuristics/ReducerTimeHeuristicTest.java index 916e21334..93645f85d 100644 --- a/test/com/linkedin/drelephant/analysis/heuristics/ReducerTimeHeuristicTest.java +++ b/test/com/linkedin/drelephant/analysis/heuristics/ReducerTimeHeuristicTest.java @@ -14,47 +14,47 @@ public class ReducerTimeHeuristicTest extends TestCase { - Heuristic heuristic = new ReducerTimeHeuristic(); - private static final long minute = Statistics.MINUTE;; + Heuristic _heuristic = new ReducerTimeHeuristic(); + private static final long MINUTE = Statistics.MINUTE;; public void testShortRunetimeCritical() throws IOException { - assertEquals(Severity.CRITICAL, analyzeJob(1 * minute, 500)); + assertEquals(Severity.CRITICAL, analyzeJob(1 * MINUTE, 500)); } public void testShortRunetimeSevere() throws IOException { - assertEquals(Severity.SEVERE, analyzeJob(1 * minute, 200)); + assertEquals(Severity.SEVERE, analyzeJob(1 * MINUTE, 200)); } public void testShortRunetimeModerate() throws IOException { - assertEquals(Severity.MODERATE, analyzeJob(1 * minute, 50)); + assertEquals(Severity.MODERATE, analyzeJob(1 * MINUTE, 50)); } public void testShortRunetimeLow() throws IOException { - assertEquals(Severity.LOW, analyzeJob(1 * minute, 10)); + assertEquals(Severity.LOW, analyzeJob(1 * MINUTE, 10)); } public void testShortRunetimeNone() throws IOException { - assertEquals(Severity.NONE, analyzeJob(1 * minute, 2)); + assertEquals(Severity.NONE, analyzeJob(1 * MINUTE, 2)); } public void testLongRunetimeCritical() throws IOException { - assertEquals(Severity.CRITICAL, analyzeJob(120 * minute, 10)); + assertEquals(Severity.CRITICAL, analyzeJob(120 * MINUTE, 10)); } public void testLongRunetimeSevere() throws IOException { - assertEquals(Severity.SEVERE, analyzeJob(120 * minute, 20)); + assertEquals(Severity.SEVERE, analyzeJob(120 * MINUTE, 20)); } public void testLongRunetimeModerate() throws IOException { - assertEquals(Severity.MODERATE, analyzeJob(120 * minute, 50)); + assertEquals(Severity.MODERATE, analyzeJob(120 * MINUTE, 50)); } public void testLongRunetimeLow() throws IOException { - assertEquals(Severity.LOW, analyzeJob(120 * minute, 100)); + assertEquals(Severity.LOW, analyzeJob(120 * MINUTE, 100)); } public void testLongRunetimeNone() throws IOException { - assertEquals(Severity.NONE, analyzeJob(120 * minute, 200)); + assertEquals(Severity.NONE, analyzeJob(120 * MINUTE, 200)); } private Severity analyzeJob(long runtime, int numTasks) throws IOException { @@ -63,11 +63,11 @@ private Severity analyzeJob(long runtime, int numTasks) throws IOException { int i = 0; for (; i < numTasks; i++) { - reducers[i] = new HadoopTaskData(dummyCounter, new long[]{0,runtime,0,0}); + reducers[i] = new HadoopTaskData(dummyCounter, new long[] { 0, runtime, 0, 0 }); } HadoopJobData data = new HadoopJobData().setCounters(dummyCounter).setReducerData(reducers); - HeuristicResult result = heuristic.apply(data); + HeuristicResult result = _heuristic.apply(data); return result.getSeverity(); } } diff --git a/test/com/linkedin/drelephant/analysis/heuristics/ShuffleSortHeuristicTest.java b/test/com/linkedin/drelephant/analysis/heuristics/ShuffleSortHeuristicTest.java index 4c9e880a9..136d0298d 100644 --- a/test/com/linkedin/drelephant/analysis/heuristics/ShuffleSortHeuristicTest.java +++ b/test/com/linkedin/drelephant/analysis/heuristics/ShuffleSortHeuristicTest.java @@ -15,68 +15,69 @@ public class ShuffleSortHeuristicTest extends TestCase { - Heuristic heuristic = new ShuffleSortHeuristic(); - private static final int numTasks = Constants.SHUFFLE_SORT_MAX_SAMPLE_SIZE; - private static final long minute = Statistics.MINUTE;; + Heuristic _heuristic = new ShuffleSortHeuristic(); + private static final int NUMTASKS = Constants.SHUFFLE_SORT_MAX_SAMPLE_SIZE; + private static final long MINUTE = Statistics.MINUTE;; public void testLongShuffleCritical() throws IOException { - assertEquals(Severity.CRITICAL, analyzeJob(30 * minute, 0, 5 * minute)); + assertEquals(Severity.CRITICAL, analyzeJob(30 * MINUTE, 0, 5 * MINUTE)); } public void testLongShuffleSevere() throws IOException { - assertEquals(Severity.SEVERE, analyzeJob(30 * minute, 0, 10 * minute)); + assertEquals(Severity.SEVERE, analyzeJob(30 * MINUTE, 0, 10 * MINUTE)); } public void testLongShuffleModerate() throws IOException { - assertEquals(Severity.MODERATE, analyzeJob(30 * minute, 0, 20 * minute)); + assertEquals(Severity.MODERATE, analyzeJob(30 * MINUTE, 0, 20 * MINUTE)); } public void testLongShuffleLow() throws IOException { - assertEquals(Severity.LOW, analyzeJob(30 * minute, 0, 40 * minute)); + assertEquals(Severity.LOW, analyzeJob(30 * MINUTE, 0, 40 * MINUTE)); } public void testLongShuffleNone() throws IOException { - assertEquals(Severity.NONE, analyzeJob(30 * minute, 0, 80 * minute)); + assertEquals(Severity.NONE, analyzeJob(30 * MINUTE, 0, 80 * MINUTE)); } public void testLongSortCritical() throws IOException { - assertEquals(Severity.CRITICAL, analyzeJob(0, 30 * minute, 5 * minute)); + assertEquals(Severity.CRITICAL, analyzeJob(0, 30 * MINUTE, 5 * MINUTE)); } public void testLongSortSevere() throws IOException { - assertEquals(Severity.SEVERE, analyzeJob(0, 30 * minute, 10 * minute)); + assertEquals(Severity.SEVERE, analyzeJob(0, 30 * MINUTE, 10 * MINUTE)); } public void testLongSortModerate() throws IOException { - assertEquals(Severity.MODERATE, analyzeJob(0, 30 * minute, 20 * minute)); + assertEquals(Severity.MODERATE, analyzeJob(0, 30 * MINUTE, 20 * MINUTE)); } public void testLongSortLow() throws IOException { - assertEquals(Severity.LOW, analyzeJob(0, 30 * minute, 40 * minute)); + assertEquals(Severity.LOW, analyzeJob(0, 30 * MINUTE, 40 * MINUTE)); } public void testLongSortNone() throws IOException { - assertEquals(Severity.NONE, analyzeJob(0, 30 * minute, 80 * minute)); + assertEquals(Severity.NONE, analyzeJob(0, 30 * MINUTE, 80 * MINUTE)); } public void testShortShuffle() throws IOException { - assertEquals(Severity.NONE, analyzeJob(minute / 2, 0, minute / 2)); + assertEquals(Severity.NONE, analyzeJob(MINUTE / 2, 0, MINUTE / 2)); } public void testShortSort() throws IOException { - assertEquals(Severity.NONE, analyzeJob(0, minute / 2, minute / 2)); + assertEquals(Severity.NONE, analyzeJob(0, MINUTE / 2, MINUTE / 2)); } private Severity analyzeJob(long shuffleTime, long sortTime, long reduceTime) throws IOException { HadoopCounterHolder dummyCounter = new HadoopCounterHolder(null); - HadoopTaskData[] reducers = new HadoopTaskData[numTasks]; + HadoopTaskData[] reducers = new HadoopTaskData[NUMTASKS]; int i = 0; - for (; i < numTasks; i++) { - reducers[i] = new HadoopTaskData(dummyCounter, new long[]{ 0, shuffleTime + sortTime + reduceTime, shuffleTime, sortTime}); + for (; i < NUMTASKS; i++) { + reducers[i] = + new HadoopTaskData(dummyCounter, new long[] { 0, shuffleTime + sortTime + reduceTime, shuffleTime, sortTime }); } HadoopJobData data = new HadoopJobData().setCounters(dummyCounter).setReducerData(reducers); - HeuristicResult result = heuristic.apply(data); + HeuristicResult result = _heuristic.apply(data); return result.getSeverity(); } From c2ffd308691f21e8144d267143702d61614d24b3 Mon Sep 17 00:00:00 2001 From: nntnag17 Date: Thu, 18 Sep 2014 01:50:16 -0700 Subject: [PATCH 004/272] HADOOP-6635: Mapper Spill Heuristic added --- .../linkedin/drelephant/ElephantAnalyser.java | 2 + .../heuristics/MapperSpillHeuristic.java | 75 +++++++++++++++++++ app/controllers/Application.java | 3 + app/views/helpMapperSpill.scala.html | 40 ++++++++++ .../heuristics/MapperSpillHeuristicTest.java | 58 ++++++++++++++ 5 files changed, 178 insertions(+) create mode 100644 app/com/linkedin/drelephant/analysis/heuristics/MapperSpillHeuristic.java create mode 100644 app/views/helpMapperSpill.scala.html create mode 100644 test/com/linkedin/drelephant/analysis/heuristics/MapperSpillHeuristicTest.java diff --git a/app/com/linkedin/drelephant/ElephantAnalyser.java b/app/com/linkedin/drelephant/ElephantAnalyser.java index 28d2c6034..e664d352d 100644 --- a/app/com/linkedin/drelephant/ElephantAnalyser.java +++ b/app/com/linkedin/drelephant/ElephantAnalyser.java @@ -12,6 +12,7 @@ import com.linkedin.drelephant.analysis.heuristics.MapperDataSkewHeuristic; import com.linkedin.drelephant.analysis.heuristics.MapperInputSizeHeuristic; import com.linkedin.drelephant.analysis.heuristics.MapperSpeedHeuristic; +import com.linkedin.drelephant.analysis.heuristics.MapperSpillHeuristic; import com.linkedin.drelephant.analysis.heuristics.ReducerDataSkewHeuristic; import com.linkedin.drelephant.analysis.heuristics.ReducerTimeHeuristic; import com.linkedin.drelephant.analysis.heuristics.ShuffleSortHeuristic; @@ -36,6 +37,7 @@ public ElephantAnalyser() { addHeuristic(new MapperSpeedHeuristic()); addHeuristic(new ReducerTimeHeuristic()); addHeuristic(new ShuffleSortHeuristic()); + addHeuristic(new MapperSpillHeuristic()); } public void addHeuristic(Heuristic heuristic) { diff --git a/app/com/linkedin/drelephant/analysis/heuristics/MapperSpillHeuristic.java b/app/com/linkedin/drelephant/analysis/heuristics/MapperSpillHeuristic.java new file mode 100644 index 000000000..841824e78 --- /dev/null +++ b/app/com/linkedin/drelephant/analysis/heuristics/MapperSpillHeuristic.java @@ -0,0 +1,75 @@ +package com.linkedin.drelephant.analysis.heuristics; + +import com.linkedin.drelephant.analysis.Heuristic; +import com.linkedin.drelephant.analysis.HeuristicResult; +import com.linkedin.drelephant.analysis.Severity; +import com.linkedin.drelephant.hadoop.HadoopCounterHolder; +import com.linkedin.drelephant.hadoop.HadoopJobData; +import com.linkedin.drelephant.hadoop.HadoopTaskData; + + +public class MapperSpillHeuristic implements Heuristic { + public static final String HEURISTIC_NAME = "Mapper Spill"; + private static final long THRESHOLD_SPILL_FACTOR = 10000; + + @Override + public HeuristicResult apply(HadoopJobData data) { + HadoopTaskData[] tasks = data.getMapperData(); + + long totalSpills = 0; + long totalOutputRecords = 0; + double ratioSpills = 0.0; + + for (HadoopTaskData task : tasks) { + totalSpills += task.getCounters().get(HadoopCounterHolder.CounterName.SPILLED_RECORDS); + totalOutputRecords += task.getCounters().get(HadoopCounterHolder.CounterName.MAP_OUTPUT_RECORDS); + } + + //If both totalSpills and totalOutputRecords are zero then set ratioSpills to zero. + if (totalSpills == 0) { + ratioSpills = 0; + } else { + ratioSpills = (double) totalSpills / (double) totalOutputRecords; + } + + Severity severity = getSpillSeverity(ratioSpills); + + HeuristicResult result = new HeuristicResult(HEURISTIC_NAME, severity); + + result.addDetail("Number of spilled records ", Long.toString(totalSpills)); + result.addDetail("Number of Mapper output records ", Long.toString(totalOutputRecords)); + result.addDetail("Ratio of spilled records to mapper output records", Double.toString(ratioSpills)); + + return result; + + } + + @Override + public String getHeuristicName() { + return HEURISTIC_NAME; + } + + /** + * + * @param ratioSpills + * @return Severity + * + * ratioSpills: Severity + * + * <1.25: None + * 1.25-1.5: Low + * 1.5-1.75: Moderate + * 1.75-2: Severe + * >=2: Critical + * + */ + + public static Severity getSpillSeverity(double ratioSpills) { + long normalizedSpillRatio = 0; + //Normalize the ratio to integer. + normalizedSpillRatio = (long) (ratioSpills * THRESHOLD_SPILL_FACTOR); + return Severity.getSeverityAscending(normalizedSpillRatio, (long) (1.25 * THRESHOLD_SPILL_FACTOR), + (long) (1.5 * THRESHOLD_SPILL_FACTOR), (long) (1.75 * THRESHOLD_SPILL_FACTOR), + (long) (2 * THRESHOLD_SPILL_FACTOR)); + } +} diff --git a/app/controllers/Application.java b/app/controllers/Application.java index 9f7dc4564..1c4b0f0c0 100644 --- a/app/controllers/Application.java +++ b/app/controllers/Application.java @@ -25,6 +25,7 @@ import com.linkedin.drelephant.analysis.heuristics.MapperDataSkewHeuristic; import com.linkedin.drelephant.analysis.heuristics.MapperInputSizeHeuristic; import com.linkedin.drelephant.analysis.heuristics.MapperSpeedHeuristic; +import com.linkedin.drelephant.analysis.heuristics.MapperSpillHeuristic; import com.linkedin.drelephant.analysis.heuristics.ReducerDataSkewHeuristic; import com.linkedin.drelephant.analysis.heuristics.ReducerTimeHeuristic; import com.linkedin.drelephant.analysis.heuristics.ShuffleSortHeuristic; @@ -136,6 +137,8 @@ public static Result help() { page = helpShuffleSort.render(); } else if (topic.equals(JobQueueLimitHeuristic.HEURISTIC_NAME)) { page = helpJobQueueLimit.render(); + } else if (topic.equals(MapperSpillHeuristic.HEURISTIC_NAME)){ + page = helpMapperSpill.render(); } else if (topic.equals(ElephantAnalyser.NO_DATA)) { page = helpNoData.render(); } diff --git a/app/views/helpMapperSpill.scala.html b/app/views/helpMapperSpill.scala.html new file mode 100644 index 000000000..d9687a7fc --- /dev/null +++ b/app/views/helpMapperSpill.scala.html @@ -0,0 +1,40 @@ +

+ This analysis shows the amount of spill that occurs on mapper side in your code
+

+

+ +

+
Example
+

+

+

+

Suggestions

+

+

    +
  1. Use combiner to lower the map output size.
  2. +
  3. Increase the size of map side sort buffer (io.sort.mb)
  4. +
  5. Increase the size of index buffer (io.sort.record.percent) +
  6. Increase the spill percentage (io.sort.spill.percent), default value is 0.8.
  7. +
+

\ No newline at end of file diff --git a/test/com/linkedin/drelephant/analysis/heuristics/MapperSpillHeuristicTest.java b/test/com/linkedin/drelephant/analysis/heuristics/MapperSpillHeuristicTest.java new file mode 100644 index 000000000..5773090ad --- /dev/null +++ b/test/com/linkedin/drelephant/analysis/heuristics/MapperSpillHeuristicTest.java @@ -0,0 +1,58 @@ +package com.linkedin.drelephant.analysis.heuristics; + +import java.io.IOException; +import java.util.HashMap; + +import com.linkedin.drelephant.analysis.Constants; +import com.linkedin.drelephant.analysis.Heuristic; +import com.linkedin.drelephant.analysis.HeuristicResult; +import com.linkedin.drelephant.analysis.Severity; +import com.linkedin.drelephant.hadoop.HadoopCounterHolder; +import com.linkedin.drelephant.hadoop.HadoopCounterHolder.CounterName; +import com.linkedin.drelephant.hadoop.HadoopJobData; +import com.linkedin.drelephant.hadoop.HadoopTaskData; +import junit.framework.TestCase; + + +public class MapperSpillHeuristicTest extends TestCase { + + Heuristic heuristic = new MapperSpillHeuristic(); + private static final int numTasks = Constants.SHUFFLE_SORT_MAX_SAMPLE_SIZE; + + public void testCritical() throws IOException { + assertEquals(Severity.CRITICAL, analyzeJob(2200, 1000)); + } + + public void testSevere() throws IOException { + assertEquals(Severity.SEVERE, analyzeJob(1755, 1000)); + } + + public void testModerate() throws IOException { + assertEquals(Severity.MODERATE, analyzeJob(1555, 1000)); + } + + public void testLow() throws IOException { + assertEquals(Severity.LOW, analyzeJob(1352, 1000)); + } + + public void testNone() throws IOException { + assertEquals(Severity.NONE, analyzeJob(1000, 1000)); + } + + private Severity analyzeJob(long spilledRecords, long mapRecords) throws IOException { + HadoopCounterHolder jobCounter = new HadoopCounterHolder(null); + HadoopTaskData[] mappers = new HadoopTaskData[numTasks]; + + HadoopCounterHolder counter = new HadoopCounterHolder(new HashMap()); + counter.set(HadoopCounterHolder.CounterName.SPILLED_RECORDS, spilledRecords); + counter.set(HadoopCounterHolder.CounterName.MAP_OUTPUT_RECORDS, mapRecords); + + for (int i=0; i < numTasks; i++) { + mappers[i] = new HadoopTaskData(counter, new long[] { 0, 5, 5, 5 }); + } + + HadoopJobData data = new HadoopJobData().setCounters(jobCounter).setMapperData(mappers); + HeuristicResult result = heuristic.apply(data); + return result.getSeverity(); + } +} From bcdc4e0f8421426125578bb4be734d31a8eb16b3 Mon Sep 17 00:00:00 2001 From: Akshay Rai Date: Thu, 18 Sep 2014 23:28:54 -0700 Subject: [PATCH 005/272] HADOOP-5675: Dr. Elephant Search trims leading and trailing whitespaces --- app/controllers/Application.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/controllers/Application.java b/app/controllers/Application.java index 1c4b0f0c0..537125f9c 100644 --- a/app/controllers/Application.java +++ b/app/controllers/Application.java @@ -42,7 +42,9 @@ public class Application extends Controller { public static Result search() { DynamicForm form = Form.form().bindFromRequest(request()); String jobId = form.get("jobid"); + jobId = jobId != null ? jobId.trim() : null; String username = form.get("user"); + username = username != null ? username.trim() : null; String severity = form.get("severity"); String jobtype = form.get("jobtype"); String analysis = form.get("analysis"); From c12afefa9efe095a20c6ae90c2c2c91512f562f3 Mon Sep 17 00:00:00 2001 From: nntnag17 Date: Fri, 26 Sep 2014 02:22:09 -0700 Subject: [PATCH 006/272] HADOOP-6565: Pluggable heuristics feature added --- README.md | 10 +++ .../linkedin/drelephant/ElephantAnalyser.java | 80 +++++++++-------- .../drelephant/util/HeuristicConf.java | 81 ++++++++++++++++++ .../drelephant/util/HeuristicConfData.java | 37 ++++++++ app/controllers/Application.java | 79 ++++++++++------- app/views/help.scala.html | 2 +- app/views/search.scala.html | 2 +- conf/HeuristicConf.xml | 85 +++++++++++++++++++ 8 files changed, 308 insertions(+), 68 deletions(-) create mode 100644 app/com/linkedin/drelephant/util/HeuristicConf.java create mode 100644 app/com/linkedin/drelephant/util/HeuristicConfData.java create mode 100644 conf/HeuristicConf.xml diff --git a/README.md b/README.md index 172604f50..f9707e153 100644 --- a/README.md +++ b/README.md @@ -59,3 +59,13 @@ When the schema in the model package changes, play will need to be ran to automa * ./kill.sh * unzip dr-elephant-0.1-SNAPSHOT.zip * ./run.sh + +### Adding new heuristics + +* Create a new heuristic and test it. +* Create a new view for the heuristic for example helpMapperSpill.scala.html +* Add the details of the heuristic in the HeuristicConf.xml file. The HeuristicConf.xml file requires the following details for each heuristic: + * heuristicname: Name of the heuristic. (e.g. Mapper Spill) + * classname: This should be the fully qualified name of the class (e.g. com.linkedin.drelephant.analysis.heuristics.MapperSpillHeuristic) + * viewname: This should be the fully qualified name of the view. ( e.g. views.html.helpMapperSpill ) +*Run Doctor Elephant, it should now include the new heuristics. \ No newline at end of file diff --git a/app/com/linkedin/drelephant/ElephantAnalyser.java b/app/com/linkedin/drelephant/ElephantAnalyser.java index e664d352d..a93d3639b 100644 --- a/app/com/linkedin/drelephant/ElephantAnalyser.java +++ b/app/com/linkedin/drelephant/ElephantAnalyser.java @@ -1,24 +1,18 @@ package com.linkedin.drelephant; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import java.util.Properties; +import model.JobType; + +import org.apache.hadoop.conf.Configuration; +import play.api.Play; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.analysis.heuristics.MapperDataSkewHeuristic; -import com.linkedin.drelephant.analysis.heuristics.MapperInputSizeHeuristic; -import com.linkedin.drelephant.analysis.heuristics.MapperSpeedHeuristic; -import com.linkedin.drelephant.analysis.heuristics.MapperSpillHeuristic; -import com.linkedin.drelephant.analysis.heuristics.ReducerDataSkewHeuristic; -import com.linkedin.drelephant.analysis.heuristics.ReducerTimeHeuristic; -import com.linkedin.drelephant.analysis.heuristics.ShuffleSortHeuristic; import com.linkedin.drelephant.hadoop.HadoopJobData; - -import model.JobType; +import com.linkedin.drelephant.util.HeuristicConf; +import com.linkedin.drelephant.util.HeuristicConfData; public class ElephantAnalyser { @@ -27,17 +21,14 @@ public class ElephantAnalyser { private HeuristicResult _nodata; private List _heuristics = new ArrayList(); - public List _heuristicNames = new ArrayList(); + private List _heuristicNames = new ArrayList(); - public ElephantAnalyser() { + private ElephantAnalyser() { _nodata = new HeuristicResult(NO_DATA, Severity.LOW); - addHeuristic(new MapperDataSkewHeuristic()); - addHeuristic(new ReducerDataSkewHeuristic()); - addHeuristic(new MapperInputSizeHeuristic()); - addHeuristic(new MapperSpeedHeuristic()); - addHeuristic(new ReducerTimeHeuristic()); - addHeuristic(new ShuffleSortHeuristic()); - addHeuristic(new MapperSpillHeuristic()); + List heuristics = getHeuristics(); + for (Heuristic heuristic : heuristics) { + addHeuristic(heuristic); + } } public void addHeuristic(Heuristic heuristic) { @@ -69,21 +60,42 @@ public JobType getJobType(HadoopJobData data) { return JobType.HADOOPJAVA; } - public Map getMetaUrls(HadoopJobData data) { - Map result = new HashMap(); - final String prefix = "meta.url."; - Properties jobConf = data.getJobConf(); - for (Map.Entry entry : jobConf.entrySet()) { - if (entry.getKey().toString().startsWith(prefix)) { - String key = entry.getKey().toString(); - String value = jobConf.getProperty(key); - result.put(key.substring(prefix.length()), value); + public static ElephantAnalyser instance() { + return ANALYZER; + } + + public List getHeuristicNames() { + return this._heuristicNames; + } + + private List getHeuristics() { + List heuristics = new ArrayList(); + HeuristicConf conf = HeuristicConf.instance(); + List heuristicsConfData = conf.getHeuristicsConfData(); + Configuration hadoopConf = new Configuration(); + String framework = hadoopConf.get("mapreduce.framework.name"); + + for (HeuristicConfData data : heuristicsConfData) { + if (data.getHadoopVersions().contains(framework)) { + try { + Class heuristicClass = Play.current().classloader().loadClass(data.getClassName()); + heuristics.add((Heuristic) heuristicClass.newInstance()); + } catch (ClassNotFoundException e) { + throw new RuntimeException("Could not find class " + data.getClassName() + + ". Make sure your class is in the classpath", e); + } catch (InstantiationException e) { + throw new RuntimeException( + "Could not instantiate class " + + data.getClassName() + + ". Check that your class is not an abstract class, an interface, an array class, a primitive type, or void. Also make sure the class has a nullary constructor.", + e); + } catch (IllegalAccessException e) { + throw new RuntimeException("Error in accessing the class " + data.getClassName() + ". Make sure " + + data.getClassName() + " is public", e); + } } } - return result; - } + return heuristics; - public static ElephantAnalyser instance() { - return ANALYZER; } } diff --git a/app/com/linkedin/drelephant/util/HeuristicConf.java b/app/com/linkedin/drelephant/util/HeuristicConf.java new file mode 100644 index 000000000..6ca034ae9 --- /dev/null +++ b/app/com/linkedin/drelephant/util/HeuristicConf.java @@ -0,0 +1,81 @@ +package com.linkedin.drelephant.util; + +import java.io.FileInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + + +public class HeuristicConf { + + private static final String FILE_PATH = "conf/HeuristicConf.xml"; + private List heuristicsConfData; + private static HeuristicConf heuristicConfInstance = new HeuristicConf(); + + private HeuristicConf() { + parseHeuristicConf(); + } + + public static HeuristicConf instance() { + return heuristicConfInstance; + } + + public List getHeuristicsConfData() { + return heuristicsConfData; + } + + private void parseHeuristicConf() { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + heuristicsConfData = new ArrayList(); + DocumentBuilder builder; + Document document = null; + try { + FileInputStream fstream = new FileInputStream(FILE_PATH); + builder = factory.newDocumentBuilder(); + document = builder.parse(fstream); + } catch (ParserConfigurationException e) { + throw new RuntimeException("Could not parse configuration file " + FILE_PATH + + ". Check if the parser is property configured", e); + } catch (SAXException e) { + throw new RuntimeException("PARSE ERROR: Could not parse configuration file " + FILE_PATH + + ". Check if the file is properly formed", e); + } catch (IOException e) { + throw new RuntimeException("I/O ERROR: Not able to read " + FILE_PATH + + " file. Check if the file exists and it has proper permissions.", e); + } + + NodeList nodes = document.getDocumentElement().getChildNodes(); + for (int i = 0; i < nodes.getLength(); i++) { + Node node = nodes.item(i); + HeuristicConfData heuristicConfData = null; + if (node.getNodeType() == Node.ELEMENT_NODE) { + Element heuristic = (Element) node; + String className = heuristic.getElementsByTagName("classname").item(0).getTextContent(); + String heuristicName = heuristic.getElementsByTagName("heuristicname").item(0).getTextContent(); + String viewName = heuristic.getElementsByTagName("viewname").item(0).getTextContent(); + Node versionNode = heuristic.getElementsByTagName("hadoopversions").item(0); + if (versionNode.getNodeType() == Node.ELEMENT_NODE) { + ArrayList hadoopVersions = new ArrayList(); + Element versions = (Element) versionNode; + NodeList versionList = versions.getElementsByTagName("version"); + for (int j = 0; j < versionList.getLength(); j++) { + hadoopVersions.add(versionList.item(j).getTextContent()); + } + heuristicConfData = new HeuristicConfData(heuristicName, className, viewName, hadoopVersions); + } + heuristicsConfData.add(heuristicConfData); + } + } + } + +} diff --git a/app/com/linkedin/drelephant/util/HeuristicConfData.java b/app/com/linkedin/drelephant/util/HeuristicConfData.java new file mode 100644 index 000000000..99016c33e --- /dev/null +++ b/app/com/linkedin/drelephant/util/HeuristicConfData.java @@ -0,0 +1,37 @@ +package com.linkedin.drelephant.util; + +import java.util.ArrayList; +import java.util.List; + + + +public class HeuristicConfData { + private String heuristicName; + private String className; + private String viewName; + private List hadoopVersions; + + public HeuristicConfData(String heuristicName,String className, String viewName, List hadoopVersions) + { + this.heuristicName = heuristicName; + this.className = className; + this.viewName = viewName; + this.hadoopVersions = hadoopVersions; + } + public String getHeuristicName() { + return heuristicName; + } + + public String getClassName() { + return className; + } + + public String getViewName() { + return viewName; + } + + public List getHadoopVersions() { + return hadoopVersions; + } + +} diff --git a/app/controllers/Application.java b/app/controllers/Application.java index 537125f9c..88f8daefa 100644 --- a/app/controllers/Application.java +++ b/app/controllers/Application.java @@ -1,5 +1,7 @@ package controllers; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -11,6 +13,7 @@ import model.JobResult; import views.html.*; +import play.api.Play; import play.api.templates.Html; import play.data.DynamicForm; import play.data.Form; @@ -19,16 +22,9 @@ import play.mvc.Result; import com.avaje.ebean.ExpressionList; -import com.linkedin.drelephant.ElephantAnalyser; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.analysis.heuristics.JobQueueLimitHeuristic; -import com.linkedin.drelephant.analysis.heuristics.MapperDataSkewHeuristic; -import com.linkedin.drelephant.analysis.heuristics.MapperInputSizeHeuristic; -import com.linkedin.drelephant.analysis.heuristics.MapperSpeedHeuristic; -import com.linkedin.drelephant.analysis.heuristics.MapperSpillHeuristic; -import com.linkedin.drelephant.analysis.heuristics.ReducerDataSkewHeuristic; -import com.linkedin.drelephant.analysis.heuristics.ReducerTimeHeuristic; -import com.linkedin.drelephant.analysis.heuristics.ShuffleSortHeuristic; +import com.linkedin.drelephant.util.HeuristicConf; +import com.linkedin.drelephant.util.HeuristicConfData; public class Application extends Controller { @@ -38,13 +34,18 @@ public class Application extends Controller { private static int _numJobsAnalyzed = 0; private static int _numJobsCritical = 0; private static int _numJobsSevere = 0; + private static Map pageMap = new HashMap(); + + static { + fillPageMap(); + } public static Result search() { DynamicForm form = Form.form().bindFromRequest(request()); String jobId = form.get("jobid"); jobId = jobId != null ? jobId.trim() : null; String username = form.get("user"); - username = username != null ? username.trim() : null; + username = username != null ? username.trim() : null; String severity = form.get("severity"); String jobtype = form.get("jobtype"); String analysis = form.get("analysis"); @@ -120,38 +121,52 @@ public static Result dashboard(int page) { public static Result help() { DynamicForm form = Form.form().bindFromRequest(request()); String topic = form.get("topic"); - Html page = null; String title = "Help"; - if (topic != null && !topic.isEmpty()) { - if (topic.equals(MapperDataSkewHeuristic.HEURISTIC_NAME)) { - page = helpMapperDataSkew.render(); - } else if (topic.equals(ReducerDataSkewHeuristic.HEURISTIC_NAME)) { - page = helpReducerDataSkew.render(); - } else if (topic.equals(MapperInputSizeHeuristic.HEURISTIC_NAME)) { - page = helpMapperInputSize.render(); - } else if (topic.equals(MapperSpeedHeuristic.HEURISTIC_NAME)) { - page = helpMapperSpeed.render(); - } else if (topic.equals(ReducerTimeHeuristic.HEURISTIC_NAME)) { - page = helpReducerTime.render(); - } else if (topic.equals(ShuffleSortHeuristic.HEURISTIC_NAME)) { - page = helpShuffleSort.render(); - } else if (topic.equals(JobQueueLimitHeuristic.HEURISTIC_NAME)) { - page = helpJobQueueLimit.render(); - } else if (topic.equals(MapperSpillHeuristic.HEURISTIC_NAME)){ - page = helpMapperSpill.render(); - } else if (topic.equals(ElephantAnalyser.NO_DATA)) { - page = helpNoData.render(); - } + page = pageMap.get(topic); if (page != null) { title = topic; } } - return ok(help.render(title, page)); } + //create a map to cache pages. + private static void fillPageMap() { + + Html page = null; + HeuristicConf conf = HeuristicConf.instance(); + List heuristicsConfData = conf.getHeuristicsConfData(); + for (HeuristicConfData data : heuristicsConfData) { + try { + Class viewClass = Play.current().classloader().loadClass(data.getViewName()); + Class heuristicClass = Play.current().classloader().loadClass(data.getClassName()); + String heuristicName = (String) heuristicClass.getDeclaredField("HEURISTIC_NAME").get(null); + Method render = viewClass.getDeclaredMethod("render"); + page = (Html) render.invoke(null); + pageMap.put(heuristicName, page); + } catch (ClassNotFoundException e) { + throw new RuntimeException("Could not find class " + data.getViewName() + + ". Make sure your class is in the classpath", e); + } catch (SecurityException e) { + throw new RuntimeException(e); + } catch (NoSuchMethodException e) { + throw new RuntimeException("The method render() doesn't exist in the class " + data.getViewName(), e); + } catch (IllegalArgumentException e) { + throw new RuntimeException("The arguments passed to render() are illegal or inappropriate"); + } catch (IllegalAccessException e) { + throw new RuntimeException("Error in accessing the class " + data.getViewName() + ". Make sure " + + data.getViewName() + " is public"); + } catch (InvocationTargetException e) { + throw new RuntimeException("The render method threw an exception. Check the implementation of the method"); + } catch (NoSuchFieldException e) { + throw new RuntimeException("No field with the name HEURISTIC_NAME was found in class " + data.getClassName()); + } + + } + } + /** * A listing of all MR jobs from historic executions of the same job */ diff --git a/app/views/help.scala.html b/app/views/help.scala.html index 468dcea9e..28f02bfa2 100644 --- a/app/views/help.scala.html +++ b/app/views/help.scala.html @@ -3,7 +3,7 @@ @main("Dr. Elephant", "help") { @tags.column(3) { @tags.panel(){ Topics } { - @for(heuristic <- com.linkedin.drelephant.ElephantAnalyser.instance()._heuristicNames) { + @for(heuristic <- com.linkedin.drelephant.ElephantAnalyser.instance().getHeuristicNames()) { @heuristic
} @com.linkedin.drelephant.ElephantAnalyser.NO_DATA
diff --git a/app/views/search.scala.html b/app/views/search.scala.html index 7e8e696f7..d96de3cf2 100644 --- a/app/views/search.scala.html +++ b/app/views/search.scala.html @@ -35,7 +35,7 @@
diff --git a/conf/HeuristicConf.xml b/conf/HeuristicConf.xml new file mode 100644 index 000000000..d3cfe9230 --- /dev/null +++ b/conf/HeuristicConf.xml @@ -0,0 +1,85 @@ + + + + + + Job Queue Limit + com.linkedin.drelephant.analysis.heuristics.JobQueueLimitHeuristic + views.html.helpJobQueueLimit + + classic + + + + + Mapper Data Skew + com.linkedin.drelephant.analysis.heuristics.MapperDataSkewHeuristic + views.html.helpMapperDataSkew + + classic + yarn + + + + + Mapper Input Size + com.linkedin.drelephant.analysis.heuristics.MapperInputSizeHeuristic + views.html.helpMapperInputSize + + classic + yarn + + + + + Mapper Speed + com.linkedin.drelephant.analysis.heuristics.MapperSpeedHeuristic + views.html.helpMapperSpeed + + classic + yarn + + + + + + Reducer Data Skew + com.linkedin.drelephant.analysis.heuristics.ReducerDataSkewHeuristic + views.html.helpReducerDataSkew + + classic + yarn + + + + + Reducer Time + com.linkedin.drelephant.analysis.heuristics.ReducerTimeHeuristic + views.html.helpReducerTime + + classic + yarn + + + + + Shuffle & Sort + com.linkedin.drelephant.analysis.heuristics.ShuffleSortHeuristic + views.html.helpShuffleSort + + classic + yarn + + + + + Mapper Spill + com.linkedin.drelephant.analysis.heuristics.MapperSpillHeuristic + views.html.helpMapperSpill + + classic + yarn + + + + \ No newline at end of file From 16cf892b73fda17d04b780d5c037072039d193b9 Mon Sep 17 00:00:00 2001 From: Akshay Rai Date: Sun, 5 Oct 2014 23:00:25 -0700 Subject: [PATCH 007/272] HADOOP-7151: Dr. Elephant - Updating 'Mapper Speed' and 'Mapper Input Size' help pages --- app/views/helpMapperInputSize.scala.html | 14 +++++++------- app/views/helpMapperSpeed.scala.html | 9 +++++++-- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/app/views/helpMapperInputSize.scala.html b/app/views/helpMapperInputSize.scala.html index dad7880fa..1828fa102 100644 --- a/app/views/helpMapperInputSize.scala.html +++ b/app/views/helpMapperInputSize.scala.html @@ -36,9 +36,9 @@

Suggestions

Set the number of mappers smaller by specifying a number or combining small files using Pig or Hive.

- For Hadoop/Java jobs: Try to use mapred.max.split.size in the job conf to split the input files appropriately.
- For Apache-Pig jobs: Lower pig.maxCombinedSplitSize and set mapred.max.split.size to something smaller.
- For Apache-Hive jobs: Try to use mapred.max.split.size in the job conf to split the input files appropriately.
+ For Hadoop/Java jobs: Try increasing mapred.max.split.size in the job conf so that each mapper will now process larger data.
+ For Apache-Pig jobs : Try setting pig.maxCombinedSplitSize and mapred.max.split.size to something larger.
+ For Apache-Hive jobs: Try increasing mapred.max.split.size in the job conf so that each mapper will now process larger data.

Large files/Unsplittable files

@@ -73,7 +73,7 @@

Suggestions

Set the number of mappers larger by giving a specific number.

- For Hadoop/Java jobs: Use "jobConf.setNumMapTasks(NUMBER_OF_MAPPERS);"
- For Apache-Pig jobs: Ask Hadoop-Dev
- For Apache-Hive jobs: Ask Hadoop-Dev
-

\ No newline at end of file + The maximum map split size is controlled by the "mapred.max.split.size" parameter. By decreasing this value + below dfs.block.size, you can reduce the maximum input size for each mapper, thereby increase the number of mappers + in your job.
+

diff --git a/app/views/helpMapperSpeed.scala.html b/app/views/helpMapperSpeed.scala.html index 1e618c356..0c4f31211 100644 --- a/app/views/helpMapperSpeed.scala.html +++ b/app/views/helpMapperSpeed.scala.html @@ -37,5 +37,10 @@

Mapper Speed

Suggestions

If your mappers are CPU bound (Average task speed ~KB/s), then your mappers are performing significant CPU work, - and you should consider optimizing your mapper code or check for inefficiencies in code. -

\ No newline at end of file + and you should consider optimizing your mapper code or check for inefficiencies in code. Alternatively, in rare + cases, it may help to reduce the maximum size of input that each mapper can process.
+
+ The maximum map split size is controlled by the "mapred.max.split.size" parameter. By decreasing this value + below dfs.block.size, you can reduce the maximum input size for each mapper, thereby increase the number of mappers + in your job.
+

From 2bdbbd97d05dd4ac900523f1eca9e627a5242d99 Mon Sep 17 00:00:00 2001 From: fli Date: Wed, 17 Sep 2014 16:39:01 -0700 Subject: [PATCH 008/272] HADOOP-7029: Missing jobs in Dr. Elephant --- .../linkedin/drelephant/ElephantAnalyser.java | 7 +- .../linkedin/drelephant/ElephantFetcher.java | 26 + .../drelephant/ElephantFetcherClassic.java | 531 ++++++++++++++---- .../drelephant/ElephantFetcherYarn.java | 136 +++-- .../linkedin/drelephant/ElephantRunner.java | 81 ++- .../hadoop/HadoopCounterHolder.java | 93 +-- .../drelephant/hadoop/HadoopJobData.java | 10 + 7 files changed, 665 insertions(+), 219 deletions(-) diff --git a/app/com/linkedin/drelephant/ElephantAnalyser.java b/app/com/linkedin/drelephant/ElephantAnalyser.java index a93d3639b..84516acad 100644 --- a/app/com/linkedin/drelephant/ElephantAnalyser.java +++ b/app/com/linkedin/drelephant/ElephantAnalyser.java @@ -6,6 +6,7 @@ import org.apache.hadoop.conf.Configuration; import play.api.Play; +import org.apache.log4j.Logger; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; @@ -17,11 +18,12 @@ public class ElephantAnalyser { public static final String NO_DATA = "No Data Received"; - private static final ElephantAnalyser ANALYZER = new ElephantAnalyser(); + private List _heuristicNames = new ArrayList(); + private static final Logger logger = Logger.getLogger(ElephantAnalyser.class); + private static final ElephantAnalyser ANALYZER = new ElephantAnalyser(); private HeuristicResult _nodata; private List _heuristics = new ArrayList(); - private List _heuristicNames = new ArrayList(); private ElephantAnalyser() { _nodata = new HeuristicResult(NO_DATA, Severity.LOW); @@ -38,6 +40,7 @@ public void addHeuristic(Heuristic heuristic) { public HeuristicResult[] analyse(HadoopJobData data) { if (data.getMapperData().length == 0 && data.getReducerData().length == 0) { + logger.info("No Data Received for job: " + data.getJobId()); return new HeuristicResult[] { _nodata }; } diff --git a/app/com/linkedin/drelephant/ElephantFetcher.java b/app/com/linkedin/drelephant/ElephantFetcher.java index d4781cae8..5cd519036 100644 --- a/app/com/linkedin/drelephant/ElephantFetcher.java +++ b/app/com/linkedin/drelephant/ElephantFetcher.java @@ -7,10 +7,36 @@ import java.util.List; +/** + * The interface to define common methods for each fetcher + * We have different fetcher implementation for hadoop 1.x and hadoop 2.x + */ public interface ElephantFetcher { + + /** + * This method is called in Runner's main or each executor thread, before calling other fetcher methods + * in order to initialize thread local variables for the thread. + * @param a thread identifier of Runner's main/executor thread + */ + public void init(int threadId) throws IOException; + + /** + * This method is called periodically in main thread to fetch a list of jobs. + * @return A list of Hadoop jobs (only job id, no data) + */ public List fetchJobList() throws IOException, AuthenticationException; + /** + * This method is called in executor thread to fetch job data before calling analyzer method for the job + * @param The job to fetch data for + */ public void fetchJobData(HadoopJobData jobData) throws IOException, AuthenticationException; + /** + * This method is called in executor thread to report if this job is successfully fetched and analyzed + * Failed Job will be fetched again by fetchJobList() + * @param The processed job + * @param Whether the job is successfully fetched and analyzed + */ public void finishJob(HadoopJobData jobData, boolean success); } diff --git a/app/com/linkedin/drelephant/ElephantFetcherClassic.java b/app/com/linkedin/drelephant/ElephantFetcherClassic.java index c981237ac..0441e42e5 100644 --- a/app/com/linkedin/drelephant/ElephantFetcherClassic.java +++ b/app/com/linkedin/drelephant/ElephantFetcherClassic.java @@ -12,11 +12,13 @@ import org.apache.commons.io.IOUtils; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.mapred.Counters; +import org.apache.hadoop.mapred.Counters.Counter; import org.apache.hadoop.mapred.JobClient; import org.apache.hadoop.mapred.JobConf; import org.apache.hadoop.mapred.JobID; import org.apache.hadoop.mapred.JobStatus; import org.apache.hadoop.mapred.RunningJob; +import org.apache.hadoop.mapred.TIPStatus; import org.apache.hadoop.mapred.TaskReport; import org.apache.hadoop.security.authentication.client.AuthenticatedURL; import org.apache.hadoop.security.authentication.client.AuthenticationException; @@ -37,56 +39,93 @@ import java.util.List; import java.util.Map; import java.util.Properties; +import java.util.Random; import java.util.Set; - +import java.util.concurrent.ConcurrentHashMap; public class ElephantFetcherClassic implements ElephantFetcher { private static final Logger logger = Logger.getLogger(ElephantFetcher.class); - private static final SimpleDateFormat DATEFORMAT = new SimpleDateFormat("d-MMM-yyyy HH:mm:ss"); - - private JobClient _jobClient; - private Set _previousJobs = new HashSet(); + private static final int DEFAULT_RETRY = 2; + private Configuration _conf; + private Set _previousJobs; + private Map _failedJobsInWait = new ConcurrentHashMap(); + private Map _failedJobsInProgress = new ConcurrentHashMap(); private boolean _firstRun = true; + private String _jobtrackerHttpRoot; public ElephantFetcherClassic(Configuration hadoopConf) throws IOException { - init(hadoopConf); + _conf = hadoopConf; + _jobtrackerHttpRoot = "http://" + hadoopConf.get("mapred.job.tracker.http.address") + "/"; } - private void init(Configuration hadoopConf) throws IOException { - logger.info("Connecting to the jobtracker"); - _jobClient = new JobClient(new JobConf(hadoopConf)); + public void init(int threadId) throws IOException { + ThreadContextMR1.init(_conf, threadId); } public List fetchJobList() throws IOException { JobStatus[] result = null; - result = _jobClient.getAllJobs(); + JobClient jobClient = ThreadContextMR1.getClient(); + result = jobClient.getAllJobs(); + if (result == null) { throw new IOException("Error fetching joblist from jobtracker."); } + // Get all completed jobs from jobtracker Set successJobs = filterSuccessfulJobs(result); - successJobs = filterPreviousJobs(successJobs, _previousJobs); + // Filter out newly completed jobs + successJobs = filterPreviousJobs(successJobs); + + // Add newly completed jobs to return list List jobList = new ArrayList(); for (String jobId : successJobs) { jobList.add(new HadoopJobData().setJobId(jobId)); } + // Add previously failed jobs to return list + List retryList = fetchAllFailedJobs(); + jobList.addAll(retryList); + logger.info("Fetcher gets total " + jobList.size() + " jobs (" + retryList.size() + " retry jobs)"); return jobList; } public void finishJob(HadoopJobData jobData, boolean success) { if (success) { - _previousJobs.add(jobData.getJobId()); + // Job succeeded, remove from retry list if this is a retry job + if(jobData.isRetryJob()) { + checkAndRemoveJobFromRetryList(jobData); + } + } else { + // Job failed, clear job data and add to retry list + if(!jobData.isRetryJob()) { + jobData.setRetry(true); + } + clearJobData(jobData); + addJobToRetryList(jobData); } } public void fetchJobData(HadoopJobData jobData) throws IOException, AuthenticationException { + JobID jobId = JobID.forName(jobData.getJobId()); + JobClient jobClient = ThreadContextMR1.getClient(); + TaskReport[] mapperTasks = null; + TaskReport[] reducerTasks = null; + RunningJob job = jobClient.getJob(jobId); + + if (job != null) { + mapperTasks = jobClient.getMapTaskReports(jobId); + reducerTasks = jobClient.getReduceTaskReports(jobId); + } - RunningJob job = getJob(jobId); - if (job == null) { - throw new IOException("Unable to fetch job data from Jobtracker, job id = " + jobId); + if (job == null || (mapperTasks.length == 0 && reducerTasks.length == 0)) { + boolean retired = checkRetiredAndFetchJobData(jobData); + if (retired) { + logger.info(jobData.getJobId() + " retired. Fetch data from web UI."); + ThreadContextMR1.updateAuthToken(); + return; + } } JobStatus status = job.getJobStatus(); @@ -94,60 +133,56 @@ public void fetchJobData(HadoopJobData jobData) throws IOException, Authenticati long startTime = status.getStartTime(); String jobUrl = job.getTrackingURL(); String jobName = job.getJobName(); + jobData.setUsername(username).setStartTime(startTime).setUrl(jobUrl).setJobName(jobName); + // Fetch job counter HadoopCounterHolder counterHolder = fetchCounter(job.getCounters()); - TaskReport[] mapperTasks = getMapTaskReports(jobId); - TaskReport[] reducerTasks = getReduceTaskReports(jobId); - String jobTrackingUrl = job.getTrackingURL(); int sampleSize = Constants.SHUFFLE_SORT_MAX_SAMPLE_SIZE; - HadoopTaskData[] mappers = new HadoopTaskData[mapperTasks.length]; + // Fetch mapper task data + List mapperList = new ArrayList(); Statistics.shuffleArraySample(mapperTasks, sampleSize); for (int i = 0; i < mapperTasks.length; i++) { - mappers[i] = fetchTaskData(jobTrackingUrl, mapperTasks[i], false, (i < sampleSize)); + HadoopTaskData mapper = fetchTaskData(jobUrl, mapperTasks[i], true, (i < sampleSize)); + if (mapper != null) { + mapperList.add(mapper); + } } + HadoopTaskData[] mappers = mapperList.toArray(new HadoopTaskData[mapperList.size()]); - HadoopTaskData[] reducers = new HadoopTaskData[reducerTasks.length]; + // Fetch reducer task data + List reducerList = new ArrayList(); Statistics.shuffleArraySample(reducerTasks, sampleSize); for (int i = 0; i < reducerTasks.length; i++) { - reducers[i] = fetchTaskData(jobTrackingUrl, reducerTasks[i], true, (i < sampleSize)); + HadoopTaskData reducer = fetchTaskData(jobUrl, reducerTasks[i], false, (i < sampleSize)); + if (reducer != null) { + reducerList.add(reducer); + } } + HadoopTaskData[] reducers = reducerList.toArray(new HadoopTaskData[reducerList.size()]); - Properties jobConf = getJobConf(job); - - jobData.setUsername(username).setStartTime(startTime).setUrl(jobUrl).setJobName(jobName).setCounters(counterHolder) - .setMapperData(mappers).setReducerData(reducers).setJobConf(jobConf); - - } - - private RunningJob getJob(JobID jobId) throws IOException { - return _jobClient.getJob(jobId); - } - - private TaskReport[] getMapTaskReports(JobID jobId) throws IOException { - return _jobClient.getMapTaskReports(jobId); + // Fetch job config + Properties jobConf = fetchJobConf(job); + jobData.setCounters(counterHolder).setMapperData(mappers).setReducerData(reducers).setJobConf(jobConf); + ThreadContextMR1.updateAuthToken(); } - private TaskReport[] getReduceTaskReports(JobID jobId) throws IOException { - return _jobClient.getReduceTaskReports(jobId); - } - - private Properties getJobConf(RunningJob job) throws IOException, AuthenticationException { - Properties properties = new Properties(); + private Properties fetchJobConf(RunningJob job) throws IOException, AuthenticationException { String jobconfUrl = getJobconfUrl(job); if (jobconfUrl == null) { - return properties; + return new Properties(); } + return fetchJobConfFromURL(jobconfUrl); + } + + private Properties fetchJobConfFromURL(String jobconfUrl) throws IOException, AuthenticationException { - URL url = new URL(jobconfUrl); - AuthenticatedURL.Token token = new AuthenticatedURL.Token(); - HttpURLConnection conn = new AuthenticatedURL().openConnection(url, token); - String data = IOUtils.toString(conn.getInputStream()); - Document doc = Jsoup.parse(data); + //Parse job config from config page + Properties properties = new Properties(); + Document doc = ThreadContextMR1.fetchHtmlDoc(jobconfUrl); Elements rows = doc.select("table").select("tr"); - for (int i = 1; i < rows.size(); i++) { - Element row = rows.get(i); + for (Element row : rows) { Elements cells = row.select("> td"); if (cells.size() == 2) { String key = cells.get(0).text().trim(); @@ -158,14 +193,9 @@ private Properties getJobConf(RunningJob job) throws IOException, Authentication return properties; } - private String getJobconfUrl(RunningJob job) { - String jobDetails = job.getTrackingURL(); - String root = jobDetails.substring(0, jobDetails.indexOf("jobdetails.jsp")); - return root + "jobconf.jsp?jobid=" + job.getID().toString(); - } - - private Set filterSuccessfulJobs(JobStatus[] jobs) { - Set successJobs = new HashSet(); + // Return a set of succeeded job ids + private HashSet filterSuccessfulJobs(JobStatus[] jobs) { + HashSet successJobs = new HashSet(); for (JobStatus job : jobs) { if (job.getRunState() == JobStatus.SUCCEEDED && job.isJobComplete()) { successJobs.add(job.getJobID().toString()); @@ -174,65 +204,72 @@ private Set filterSuccessfulJobs(JobStatus[] jobs) { return successJobs; } - private Set filterPreviousJobs(Set jobs, Set previousJobs) { - logger.info("Cleaning up previous runs."); - // On first run, check against DB + private Set filterPreviousJobs(Set jobs) { + + // On first run, check each job against DB if (_firstRun) { + logger.info("Checking database ...... "); Set newJobs = new HashSet(); for (String jobId : jobs) { JobResult prevResult = JobResult.find.byId(jobId); if (prevResult == null) { // Job not found, add to new jobs list newJobs.add(jobId); - } else { - // Job found, add to old jobs list - previousJobs.add(jobId); } } - jobs = newJobs; + _previousJobs = jobs; _firstRun = false; + return newJobs; } else { - // Remove untracked jobs - previousJobs.retainAll(jobs); - // Remove previously analysed jobs - jobs.removeAll(previousJobs); + @SuppressWarnings("unchecked") + Set tempPrevJobs = (HashSet) ((HashSet) jobs).clone(); + // Leave only the newly completed jobs + jobs.removeAll(_previousJobs); + _previousJobs = tempPrevJobs; + return jobs; } - return jobs; } - private HadoopTaskData fetchTaskData(String jobDetailUrl, TaskReport task, boolean isReducer, boolean sampled) + private HadoopTaskData fetchTaskData(String jobDetailUrl, TaskReport task, boolean isMapper, boolean sampled) throws IOException, AuthenticationException { + if (task.getCurrentStatus() != TIPStatus.COMPLETE) { + // this is a failed task. Discard it. + return null; + } + HadoopCounterHolder taskCounter = fetchCounter(task.getCounters()); + // This is not a sampled task, return task data with only counters if (!sampled) { return new HadoopTaskData(taskCounter); } - String taskDetailsUrl = getTaskDetailsPage(jobDetailUrl, task.getTaskID().toString()); - long[] time = fetchTaskDetails(taskDetailsUrl, isReducer); - + // This is a sampled task, fetch task time from task detail page + String taskDetailsUrl = getTaskDetailsPageUrl(jobDetailUrl, task.getTaskID().toString()); + long[] time = fetchTaskDetails(taskDetailsUrl, isMapper); return new HadoopTaskData(taskCounter, time); } - private String getTaskDetailsPage(String jobDetails, String taskId) { + private String getJobconfUrl(RunningJob job) { + String jobDetails = job.getTrackingURL(); + String root = jobDetails.substring(0, jobDetails.indexOf("jobdetails.jsp")); + return root + "jobconf.jsp?jobid=" + job.getID().toString(); + } + + private String getTaskDetailsPageUrl(String jobDetails, String taskId) { String root = jobDetails.substring(0, jobDetails.indexOf("jobdetails.jsp")); return root + "taskdetails.jsp?tipid=" + taskId.toString(); } - private long[] fetchTaskDetails(String taskDetailUrl, boolean isReducer) throws IOException, AuthenticationException { - - URL url = new URL(taskDetailUrl); - AuthenticatedURL.Token token = new AuthenticatedURL.Token(); - HttpURLConnection conn = new AuthenticatedURL().openConnection(url, token); - String data = IOUtils.toString(conn.getInputStream()); - Document doc = Jsoup.parse(data); + // Fetch task time from task detail page + private long[] fetchTaskDetails(String taskDetailUrl, boolean isMapper) throws IOException, AuthenticationException { + Document doc = ThreadContextMR1.fetchHtmlDoc(taskDetailUrl); Elements rows = doc.select("table").select("tr"); long[] time = null; - for (int i = 1; i < rows.size(); i++) { - Element row = rows.get(i); + for (Element row : rows) { try { - time = tryExtractDetailFromRow(row, isReducer); + time = tryExtractDetailFromRow(row, isMapper); if (time != null) { return time; } @@ -243,20 +280,21 @@ private long[] fetchTaskDetails(String taskDetailUrl, boolean isReducer) throws throw new IOException("No valid time data found from task detail page. TASK URL=" + taskDetailUrl); } - //Return shuffle sort time if successfully extracted data from row - private long[] tryExtractDetailFromRow(Element row, boolean isReducer) throws ParseException { + //Return shuffle sort time if successfully extracted data from table row () + private long[] tryExtractDetailFromRow(Element row, boolean isMapper) throws ParseException { Elements cells = row.select("> td"); // For rows() in reducer task page with other than 12 cols(),or 10 cols in mapper page, // they are not rows that contains time data - if ((isReducer && cells.size() != 12) || (!isReducer && cells.size() != 10)) { + if ((!isMapper && cells.size() != 12) || (isMapper && cells.size() != 10)) { return null; } boolean succeeded = cells.get(2).html().trim().equals("SUCCEEDED"); if (succeeded) { - if (isReducer) { - // Fetch time info from reducer task page + SimpleDateFormat dateFormat = ThreadContextMR1.getDateFormat(); + if (!isMapper) { + // reducer task. Get start finish shuffle sort time String startTime = cells.get(4).html().trim(); String shuffleTime = cells.get(5).html().trim(); String sortTime = cells.get(6).html().trim(); @@ -270,47 +308,326 @@ private long[] tryExtractDetailFromRow(Element row, boolean isReducer) throws Pa if (finishTime.contains("(")) { finishTime = finishTime.substring(0, finishTime.indexOf("(") - 1); } - long start = DATEFORMAT.parse(startTime).getTime(); - long shuffle = DATEFORMAT.parse(shuffleTime).getTime(); - long sort = DATEFORMAT.parse(sortTime).getTime(); - long finish = DATEFORMAT.parse(finishTime).getTime(); + long start = dateFormat.parse(startTime).getTime(); + long shuffle = dateFormat.parse(shuffleTime).getTime(); + long sort = dateFormat.parse(sortTime).getTime(); + long finish = dateFormat.parse(finishTime).getTime(); long shuffleDuration = (shuffle - start); long sortDuration = (sort - shuffle); return new long[] { start, finish, shuffleDuration, sortDuration }; } else { - // Fetch time info from mapper task page + // Mapper task. Get start finish time String startTime = cells.get(4).html().trim(); String finishTime = cells.get(5).html().trim(); if (finishTime.contains("(")) { finishTime = finishTime.substring(0, finishTime.indexOf("(") - 1); } - long start = DATEFORMAT.parse(startTime).getTime(); - long finish = DATEFORMAT.parse(finishTime).getTime(); + long start = dateFormat.parse(startTime).getTime(); + long finish = dateFormat.parse(finishTime).getTime(); return new long[] { start, finish, 0, 0 }; } } + // This is a failed task attempt. return null; } private HadoopCounterHolder fetchCounter(Counters counters) { Map counterMap = new EnumMap(CounterName.class); - for (CounterName counterName : CounterName.values()) { - counterMap.put(counterName, readCounter(counterName, counters)); + for (Counters.Group group : counters) { + for (Counter ctr : group) { + CounterName cn = CounterName.getCounterFromName(ctr.getName()); + if (cn != null) { + counterMap.put(cn, ctr.getValue()); + } + } } return new HadoopCounterHolder(counterMap); } - private long readCounter(CounterName counterName, Counters counters) { - String groupName = counterName.getGroup().getName(); - Counters.Group group = counters.getGroup(groupName); - if (group == null) { - return 0; + private void addJobToRetryList(HadoopJobData job) { + if (_failedJobsInProgress.containsKey(job)) { + // This is old retry job + int retryLeft = _failedJobsInProgress.get(job); + _failedJobsInProgress.remove(job); + if (retryLeft == 0) { + // Drop job on max retries + logger.error("Drop job. Reason: reach max retry for job id=" + job.getJobId()); + } else { + // Put this job back to failed job waiting list to be fetched again + _failedJobsInWait.put(job, retryLeft); + } + } else { + // This is new job to retry, add to wait list + _failedJobsInWait.put(job, DEFAULT_RETRY); + } + } + + private List fetchAllFailedJobs() { + List retryList = new ArrayList(); + for (HadoopJobData job : _failedJobsInWait.keySet()) { + int retry = _failedJobsInWait.get(job); + retryList.add(job); + _failedJobsInWait.remove(job); + _failedJobsInProgress.put(job, retry - 1); + } + return retryList; + } + + private void checkAndRemoveJobFromRetryList(HadoopJobData jobData) { + if (_failedJobsInProgress.containsKey(jobData)) { + _failedJobsInProgress.remove(jobData); + } + } + + private void clearJobData(HadoopJobData jobData) { + jobData.setCounters(null).setJobConf(null).setMapperData(null).setReducerData(null); + } + + // Return false if this is a real nodata received job ( 0 mapper & 0 reducer ) rather than a retired job + private boolean checkRetiredAndFetchJobData(HadoopJobData jobData) throws IOException, AuthenticationException { + String jobUrl = _jobtrackerHttpRoot + "jobdetails.jsp?jobid=" + jobData.getJobId(); + Document doc = ThreadContextMR1.fetchHtmlDoc(jobUrl); + Elements hrefs = doc.select("a"); + String mapperUrl = null; + String reducerUrl = null; + String confUrl = null; + + // Parse all hrefs in job main page to find job conf page and mapper/reducer main page + for (Element href : hrefs) { + String hrefStr = href.attr("href"); + if (hrefStr.contains("jobconf_history.jsp")) { + confUrl = _jobtrackerHttpRoot + hrefStr; + } + if (hrefStr.contains("taskType=MAP&status=SUCCESS")) { + mapperUrl = _jobtrackerHttpRoot + hrefStr; + } + if (hrefStr.contains("taskType=REDUCE&status=SUCCESS")) { + reducerUrl = _jobtrackerHttpRoot + hrefStr; + } + } + + if (mapperUrl == null || reducerUrl == null || confUrl == null) { + // This is an real no data received job + return false; } - Counters.Counter counter = group.getCounterForName(counterName.getName()); - if (counter == null) { - return 0; + + HadoopCounterHolder jobCounter = fetchJobCounterForRetiredJob(doc); + Properties jobConf = fetchJobConfFromURL(confUrl); + HadoopTaskData[] mapperData = fetchAllTaskDataForRetiredJob(mapperUrl, true); + HadoopTaskData[] reducerData = fetchAllTaskDataForRetiredJob(reducerUrl, false); + + long startTime = fetchStartTimeForRetiredJob(doc, jobUrl); + String username = jobConf.getProperty("user.name"); + String jobName = jobConf.getProperty("mapred.job.name"); + jobData.setUsername(username).setStartTime(startTime).setUrl(jobUrl).setJobName(jobName); + jobData.setCounters(jobCounter).setJobConf(jobConf).setMapperData(mapperData).setReducerData(reducerData); + return true; + } + + // Fetch job counter from job's main page + private HadoopCounterHolder fetchJobCounterForRetiredJob(Document doc) throws IOException, AuthenticationException { + Map counterMap = new EnumMap(CounterName.class); + Elements rows = doc.select("table").select("tr"); + for (Element row : rows) { + Elements cells = row.select("> td"); + if (cells.size() == 5) { + String countername = cells.get(1).text().trim(); + CounterName cn = CounterName.getCounterFromDisplayName(countername); + if (cn != null) { + counterMap.put(cn, Long.parseLong(cells.get(4).text().trim().replace(",", ""))); + } + } else if (cells.size() == 4) { + String countername = cells.get(0).text().trim(); + CounterName cn = CounterName.getCounterFromDisplayName(countername); + if (cn != null) { + counterMap.put(cn, Long.parseLong(cells.get(3).text().trim().replace(",", ""))); + } + } + } + return new HadoopCounterHolder(counterMap); + } + + // Fetch task counter from task's counter page + public HadoopCounterHolder fetchTaskCounterForRetiredJob(String taskCounterUrl) throws IOException, + AuthenticationException { + Map counterMap = new EnumMap(CounterName.class); + Document doc = ThreadContextMR1.fetchHtmlDoc(taskCounterUrl); + Elements rows = doc.select("table").select("tr"); + for (Element row : rows) { + Elements cells = row.select("> td"); + if (cells.size() == 3) { + String countername = cells.get(1).text().trim(); + CounterName cn = CounterName.getCounterFromDisplayName(countername); + if (cn != null) { + counterMap.put(cn, Long.parseLong(cells.get(2).text().trim().replace(",", ""))); + } + } + } + return new HadoopCounterHolder(counterMap); + } + + // We fetch the start time of job's Setup task as the job's start time, shown in job's main page + private long fetchStartTimeForRetiredJob(Document doc, String jobUrl) throws IOException { + Elements rows = doc.select("table").select("tr"); + for (Element row : rows) { + Elements cells = row.select("> td"); + if (cells.size() == 7 && cells.get(0).text().trim().equals("Setup")) { + SimpleDateFormat dateFormat = ThreadContextMR1.getDateFormat(); + try { + long time = dateFormat.parse(cells.get(5).text().trim()).getTime(); + return time; + } catch (ParseException e) { + throw new IOException("Error in fetching start time data from job page in URL : " + jobUrl); + } + } + } + throw new IOException("Unable to fetch start time data from job page in URL : " + jobUrl); + } + + private HadoopTaskData[] fetchAllTaskDataForRetiredJob(String taskIndexPage, boolean isMapper) throws IOException, + AuthenticationException { + List taskList = new ArrayList(); + Document doc = ThreadContextMR1.fetchHtmlDoc(taskIndexPage); + Elements hrefs = doc.select("a"); + for (Element href : hrefs) { + String hrefStr = href.attr("href"); + if (hrefStr.contains("taskdetailshistory.jsp?")) { + String taskUrl = _jobtrackerHttpRoot + hrefStr; + HadoopTaskData tdata = fetchTaskDataForRetiredJob(taskUrl, isMapper); + taskList.add(tdata); + } + } + return taskList.toArray(new HadoopTaskData[taskList.size()]); + } + + private HadoopTaskData fetchTaskDataForRetiredJob(String taskDetailUrl, boolean isMapper) throws IOException, + AuthenticationException { + Document doc = ThreadContextMR1.fetchHtmlDoc(taskDetailUrl); + Elements rows = doc.select("table").select("tr"); + HadoopTaskData taskData = null; + for (Element row : rows) { + try { + taskData = tryExtractTaskDataForRetiredJob(row, isMapper); + if (taskData != null) { + return taskData; + } + } catch (Exception e) { + throw new IOException("Error in fetch task data from task detail page. TASK URL=" + taskDetailUrl, e); + } + } + throw new IOException("No valid time data found from task detail page. TASK URL=" + taskDetailUrl); + } + + private HadoopTaskData tryExtractTaskDataForRetiredJob(Element row, boolean isMapper) throws IOException, + ParseException, AuthenticationException { + Elements cells = row.select("> td"); + + if (isMapper && cells.size() == 7) { + SimpleDateFormat dateFormat = ThreadContextMR1.getDateFormatForRetiredJob(); + String c = cells.get(6).text().trim(); + if (c.matches("[^0][0-9]+")) { + String startTime = cells.get(1).text().trim(); + String finishTime = cells.get(2).text().trim(); + if (finishTime.contains("(")) { + finishTime = finishTime.substring(0, finishTime.indexOf("(") - 1); + } + long start = dateFormat.parse(startTime).getTime(); + long finish = dateFormat.parse(finishTime).getTime(); + long[] time = new long[] { start, finish, 0, 0 }; + String counterUrl = _jobtrackerHttpRoot + cells.get(6).select("a").attr("href"); + HadoopCounterHolder ctrholder = fetchTaskCounterForRetiredJob(counterUrl); + return new HadoopTaskData(ctrholder, time); + } + } else if (!isMapper && cells.size() == 9) { + SimpleDateFormat dateFormat = ThreadContextMR1.getDateFormatForRetiredJob(); + String c = cells.get(8).text().trim(); + if (c.matches("[^0][0-9]+")) { + String startTime = cells.get(1).text().trim(); + String shuffleTime = cells.get(2).text().trim(); + String sortTime = cells.get(3).text().trim(); + String finishTime = cells.get(4).text().trim(); + if (shuffleTime.contains("(")) { + shuffleTime = shuffleTime.substring(0, shuffleTime.indexOf("(") - 1); + } + if (sortTime.contains("(")) { + sortTime = sortTime.substring(0, sortTime.indexOf("(") - 1); + } + if (finishTime.contains("(")) { + finishTime = finishTime.substring(0, finishTime.indexOf("(") - 1); + } + long start = dateFormat.parse(startTime).getTime(); + long shuffle = dateFormat.parse(shuffleTime).getTime(); + long sort = dateFormat.parse(sortTime).getTime(); + long finish = dateFormat.parse(finishTime).getTime(); + long shuffleDuration = (shuffle - start); + long sortDuration = (sort - shuffle); + long[] time = new long[] { start, finish, shuffleDuration, sortDuration }; + String counterUrl = _jobtrackerHttpRoot + cells.get(8).select("a").attr("href"); + HadoopCounterHolder ctrholder = fetchTaskCounterForRetiredJob(counterUrl); + return new HadoopTaskData(ctrholder, time); + } + } + return null; + } + +} + +final class ThreadContextMR1 { + private static final Logger logger = Logger.getLogger(ThreadContextMR1.class); + private static final ThreadLocal _LOCAL_THREAD_ID = new ThreadLocal(); + private static final ThreadLocal _LOCAL_JOB_CLIENT = new ThreadLocal(); + private static final ThreadLocal _LOCAL_AUTH_TOKEN = + new ThreadLocal(); + private static final ThreadLocal _LOCAL_AUTH_URL = new ThreadLocal(); + private static final ThreadLocal _LOCAL_DATE_FORMAT = new ThreadLocal(); + private static final ThreadLocal _LOCAL_DATE_FORMAT_FOR_RETIRED = + new ThreadLocal(); + private static final ThreadLocal _LOCAL_LAST_UPDATED = new ThreadLocal(); + private static final ThreadLocal _LOCAL_UPDATE_INTERVAL = new ThreadLocal(); + + private ThreadContextMR1() { + } + + public static void init(Configuration hadoopConf, int threadId) throws IOException { + _LOCAL_JOB_CLIENT.set(new JobClient(new JobConf(hadoopConf))); + _LOCAL_AUTH_TOKEN.set(new AuthenticatedURL.Token()); + _LOCAL_AUTH_URL.set(new AuthenticatedURL()); + _LOCAL_DATE_FORMAT.set(new SimpleDateFormat("d-MMM-yyyy HH:mm:ss")); + _LOCAL_DATE_FORMAT_FOR_RETIRED.set(new SimpleDateFormat("d/MM HH:mm:ss")); + _LOCAL_THREAD_ID.set(threadId); + _LOCAL_LAST_UPDATED.set(System.currentTimeMillis()); + // Random an interval for each executor to avoid update token at the same time + _LOCAL_UPDATE_INTERVAL.set(Statistics.MINUTE * 30 + new Random().nextLong() % (10 * Statistics.MINUTE)); + logger.info("Executor " + _LOCAL_THREAD_ID.get() + " update interval " + _LOCAL_UPDATE_INTERVAL.get() * 1.0 + / Statistics.MINUTE); + } + + public static JobClient getClient() { + return _LOCAL_JOB_CLIENT.get(); + } + + public static SimpleDateFormat getDateFormat() { + return _LOCAL_DATE_FORMAT.get(); + } + + public static SimpleDateFormat getDateFormatForRetiredJob() { + return _LOCAL_DATE_FORMAT_FOR_RETIRED.get(); + } + + public static Document fetchHtmlDoc(String url) throws IOException, AuthenticationException { + HttpURLConnection conn = _LOCAL_AUTH_URL.get().openConnection(new URL(url), _LOCAL_AUTH_TOKEN.get()); + Document doc = Jsoup.parse(IOUtils.toString(conn.getInputStream())); + return doc; + } + + public static void updateAuthToken() { + long curTime = System.currentTimeMillis(); + if (curTime - _LOCAL_LAST_UPDATED.get() > _LOCAL_UPDATE_INTERVAL.get()) { + logger.info("Executor " + _LOCAL_THREAD_ID.get() + " updates its AuthenticatedToken."); + _LOCAL_AUTH_TOKEN.set(new AuthenticatedURL.Token()); + _LOCAL_AUTH_URL.set(new AuthenticatedURL()); + _LOCAL_LAST_UPDATED.set(curTime); } - return counter.getValue(); } } diff --git a/app/com/linkedin/drelephant/ElephantFetcherYarn.java b/app/com/linkedin/drelephant/ElephantFetcherYarn.java index 98c623217..9199d92fb 100644 --- a/app/com/linkedin/drelephant/ElephantFetcherYarn.java +++ b/app/com/linkedin/drelephant/ElephantFetcherYarn.java @@ -22,12 +22,10 @@ import java.net.URLConnection; import java.util.ArrayList; import java.util.EnumMap; -import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Properties; -import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; public class ElephantFetcherYarn implements ElephantFetcher { @@ -41,10 +39,6 @@ public class ElephantFetcherYarn implements ElephantFetcher { private long _currentTime = 0; public ElephantFetcherYarn(Configuration hadoopConf) throws IOException { - init(hadoopConf); - } - - private void init(Configuration hadoopConf) throws IOException { logger.info("Connecting to the job history server..."); String jhistoryAddr = hadoopConf.get("mapreduce.jobhistory.webapp.address"); _urlFactory = new URLFactory(jhistoryAddr); @@ -53,6 +47,10 @@ private void init(Configuration hadoopConf) throws IOException { logger.info("Connection success."); } + public void init(int threadId) throws IOException { + ThreadContextMR2.init(threadId); + } + /* * Fetch job list to analyze * If first time, search time span from 0 to now, check database for each job @@ -62,6 +60,7 @@ private void init(Configuration hadoopConf) throws IOException { public List fetchJobList() throws IOException, AuthenticationException { List jobList; + int retrySize = 0; _currentTime = System.currentTimeMillis(); URL joblistURL = _urlFactory.fetchJobListURL(_lastTime, _currentTime); @@ -70,12 +69,16 @@ public List fetchJobList() throws IOException, AuthenticationExce if (_firstRun) { _firstRun = false; } else { + List retryList = _retryFactory.fetchAllRetryJobs(); // If not first time, also fetch jobs that need to retry - jobList.addAll(_retryFactory.getJobs()); + retrySize = retryList.size(); + jobList.addAll(retryList); } _lastTime = _currentTime; + logger.info("Fetcher gets total " + jobList.size() + " jobs (" + retrySize + " retry jobs)"); + return jobList; } @@ -93,9 +96,17 @@ private void clearJobData(HadoopJobData jobData) { // OnJobFinish Add to retry list upon failure public void finishJob(HadoopJobData jobData, boolean success) { if (!success) { + if(!jobData.isRetryJob()) { + jobData.setRetry(true); + } clearJobData(jobData); // Add to retry list - _retryFactory.addJob(jobData); + _retryFactory.addJobToRetryList(jobData); + } else { + if(jobData.isRetryJob()) { + // If it is retry job, remove it from retry map + _retryFactory.checkAndRemoveFromRetryList(jobData); + } } } @@ -183,23 +194,10 @@ private URL getTaskAttemptURL(String jobId, String taskId, String attemptId) thr } private class JSONFactory { - private ObjectMapper _objMapper = new ObjectMapper(); - private AuthenticatedURL.Token _authToken = new AuthenticatedURL.Token(); - private AuthenticatedURL _authURL = new AuthenticatedURL(); - private Set _counterSet = new HashSet();; - - public JSONFactory() { - // Store the set of counters we want to fetch - for (CounterName counter : CounterName.values()) { - _counterSet.add(counter.getName()); - } - } - private List getJobData(URL url, boolean checkDB) throws IOException, AuthenticationException { List jobList = new ArrayList(); - HttpURLConnection conn = _authURL.openConnection(url, _authToken); - JsonNode rootNode = _objMapper.readTree(conn.getInputStream()); + JsonNode rootNode = ThreadContextMR2.readJsonNode(url); JsonNode jobs = rootNode.path("jobs").path("job"); for (JsonNode job : jobs) { @@ -223,8 +221,7 @@ private List getJobData(URL url, boolean checkDB) throws IOExcept private Properties getProperties(URL url) throws IOException, AuthenticationException { Properties jobConf = new Properties(); - HttpURLConnection conn = _authURL.openConnection(url, _authToken); - JsonNode rootNode = _objMapper.readTree(conn.getInputStream()); + JsonNode rootNode = ThreadContextMR2.readJsonNode(url); JsonNode configs = rootNode.path("conf").path("property"); for (JsonNode conf : configs) { @@ -238,42 +235,33 @@ private Properties getProperties(URL url) throws IOException, AuthenticationExce private HadoopCounterHolder getJobCounter(URL url) throws IOException, AuthenticationException { Map counterMap = new EnumMap(CounterName.class); - HttpURLConnection conn = _authURL.openConnection(url, _authToken); - JsonNode rootNode = _objMapper.readTree(conn.getInputStream()); + JsonNode rootNode = ThreadContextMR2.readJsonNode(url); JsonNode groups = rootNode.path("jobCounters").path("counterGroup"); for (JsonNode group : groups) { for (JsonNode counter : group.path("counter")) { String name = counter.get("name").getValueAsText(); - if (_counterSet.contains(name)) { - // This is a counter we want to fetch - long val = counter.get("totalCounterValue").getLongValue(); - counterMap.put(CounterName.valueOf(name), val); + CounterName cn = CounterName.getCounterFromName(name); + if (cn != null) { + counterMap.put(cn, counter.get("totalCounterValue").getLongValue()); } } } - // For every missing counters in the job, set with default value 0 - for (CounterName name : CounterName.values()) { - if (!counterMap.containsKey(name)) { - counterMap.put(name, 0L); - } - } return new HadoopCounterHolder(counterMap); } private HadoopCounterHolder getTaskCounter(URL url) throws IOException, AuthenticationException { Map counterMap = new EnumMap(CounterName.class); - HttpURLConnection conn = _authURL.openConnection(url, _authToken); - JsonNode rootNode = _objMapper.readTree(conn.getInputStream()); + JsonNode rootNode = ThreadContextMR2.readJsonNode(url); JsonNode groups = rootNode.path("jobTaskCounters").path("taskCounterGroup"); for (JsonNode group : groups) { for (JsonNode counter : group.path("counter")) { String name = counter.get("name").getValueAsText(); - if (_counterSet.contains(name)) { - long val = counter.get("value").getLongValue(); - counterMap.put(CounterName.valueOf(name), val); + CounterName cn = CounterName.getCounterFromName(name); + if (cn != null) { + counterMap.put(cn, counter.get("value").getLongValue()); } } } @@ -287,8 +275,8 @@ private HadoopCounterHolder getTaskCounter(URL url) throws IOException, Authenti } private long[] getTaskExecTime(URL url) throws IOException, AuthenticationException { - HttpURLConnection conn = _authURL.openConnection(url, _authToken); - JsonNode rootNode = _objMapper.readTree(conn.getInputStream()); + + JsonNode rootNode = ThreadContextMR2.readJsonNode(url); JsonNode taskAttempt = rootNode.path("taskAttempt"); long startTime = taskAttempt.get("startTime").getLongValue(); @@ -310,8 +298,8 @@ private long[] getTaskExecTime(URL url) throws IOException, AuthenticationExcept private void getTaskDataAll(URL url, String jobId, List mapperList, List reducerList) throws IOException, AuthenticationException { - HttpURLConnection conn = _authURL.openConnection(url, _authToken); - JsonNode rootNode = _objMapper.readTree(conn.getInputStream()); + + JsonNode rootNode = ThreadContextMR2.readJsonNode(url); JsonNode tasks = rootNode.path("tasks").path("task"); for (JsonNode task : tasks) { @@ -336,29 +324,63 @@ private void getTaskDataAll(URL url, String jobId, List mapperLi } private class RetryFactory { - private static final int DEFAULT_RETRY = 3; - private Map _retryMap = new HashMap(); + private static final int DEFAULT_RETRY = 2; + private Map _retryMapInWait = new ConcurrentHashMap(); + private Map _retryMapInProgress = new ConcurrentHashMap(); - private void addJob(HadoopJobData job) { - if (_retryMap.containsKey(job)) { + private void addJobToRetryList(HadoopJobData job) { + if (_retryMapInProgress.containsKey(job)) { // This is old retry job - int retryLeft = _retryMap.get(job); - if (retryLeft == 1) { + int retryLeft = _retryMapInProgress.get(job); + _retryMapInProgress.remove(job); + if (retryLeft == 0) { // Drop job on max retries logger.error("Drop job. Reason: reach max retry for job id=" + job.getJobId()); - _retryMap.remove(job); } else { - _retryMap.put(job, retryLeft - 1); + _retryMapInWait.put(job, retryLeft); } } else { // This is new retry job - _retryMap.put(job, DEFAULT_RETRY); + _retryMapInWait.put(job, DEFAULT_RETRY); } } - private List getJobs() { - return Lists.newArrayList(_retryMap.keySet()); + private List fetchAllRetryJobs() { + List retryList = new ArrayList(); + for (HadoopJobData job : _retryMapInWait.keySet()) { + int retry = _retryMapInWait.get(job); + _retryMapInWait.remove(job); + retryList.add(job); + _retryMapInProgress.put(job, retry - 1); + } + return Lists.newArrayList(retryList); + } + + private void checkAndRemoveFromRetryList(HadoopJobData jobData) { + if (_retryMapInProgress.containsKey(jobData)) { + _retryMapInProgress.remove(jobData); + } } } +} + +final class ThreadContextMR2 { + private static final ThreadLocal _LOCAL_AUTH_TOKEN = + new ThreadLocal(); + private static final ThreadLocal _LOCAL_AUTH_URL = new ThreadLocal(); + private static final ThreadLocal _LOCAL_MAPPER = new ThreadLocal(); + + private ThreadContextMR2() { + } + static void init(int threadId) throws IOException { + _LOCAL_AUTH_TOKEN.set(new AuthenticatedURL.Token()); + _LOCAL_AUTH_URL.set(new AuthenticatedURL()); + _LOCAL_MAPPER.set(new ObjectMapper()); + } + + public static JsonNode readJsonNode(URL url) throws IOException, AuthenticationException { + HttpURLConnection conn = _LOCAL_AUTH_URL.get().openConnection(url, _LOCAL_AUTH_TOKEN.get()); + return _LOCAL_MAPPER.get().readTree(conn.getInputStream()); + } } diff --git a/app/com/linkedin/drelephant/ElephantRunner.java b/app/com/linkedin/drelephant/ElephantRunner.java index 9667deb82..7cc5492bb 100644 --- a/app/com/linkedin/drelephant/ElephantRunner.java +++ b/app/com/linkedin/drelephant/ElephantRunner.java @@ -4,6 +4,10 @@ import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicBoolean; import model.JobHeuristicResult; @@ -22,12 +26,15 @@ public class ElephantRunner implements Runnable { - private static final long WAIT_INTERVAL = 10 * 1000; + private static final long WAIT_INTERVAL = 60 * 1000; + private static final int EXECUTOR_NUM = 3; private static final Logger logger = Logger.getLogger(ElephantRunner.class); private AtomicBoolean _running = new AtomicBoolean(true); private EmailThread _emailer = new EmailThread(); private HadoopSecurity _hadoopSecurity; private InfoExtractor _urlRetriever = new InfoExtractor(); + private ExecutorService _service; + private BlockingQueue _jobQueue; @Override public void run() { @@ -67,11 +74,21 @@ public Void run() { } } + logger.info("Initializing fetcher in main thread 0"); + fetcher.init(0); + } catch (IOException e) { - logger.error("Error initializing dr elephant fetcher! ", e); + logger.error("Error initializing dr elephant fetcher in main thread", e); return null; } + _service = Executors.newFixedThreadPool(EXECUTOR_NUM); + _jobQueue = new LinkedBlockingQueue(); + + for (int i = 0; i < EXECUTOR_NUM; i++) { + _service.submit(new ExecutorThread(i + 1, _jobQueue, fetcher)); + } + while (_running.get()) { lastRun = System.currentTimeMillis(); @@ -92,20 +109,8 @@ public Void run() { continue; } - logger.info(successJobs.size() + " jobs to analyse."); - - // Analyse all ready jobs - for (HadoopJobData jobData : successJobs) { - try { - fetcher.fetchJobData(jobData); - analyzeJob(jobData); - fetcher.finishJob(jobData, true); - } catch (Exception e) { - logger.error("Error fetching job data. job id=" + jobData.getJobId(), e); - fetcher.finishJob(jobData, false); - } - } - logger.info("Finished all jobs. Waiting for refresh."); + _jobQueue.addAll(successJobs); + logger.info("Job queue size is " + _jobQueue.size()); // Wait for long enough long nextRun = lastRun + WAIT_INTERVAL; @@ -114,7 +119,7 @@ public Void run() { try { Thread.sleep(waitTime); } catch (InterruptedException e) { - logger.error("Thread interrupted", e); + logger.error("Runner thread interrupted.", e); } waitTime = nextRun - System.currentTimeMillis(); } @@ -127,10 +132,47 @@ public Void run() { } } - private void analyzeJob(HadoopJobData jobData) { + private class ExecutorThread implements Runnable { + private int _threadId; + private BlockingQueue _jobQueue; + private ElephantFetcher _fetcher; + + ExecutorThread(int threadNum, BlockingQueue jobQueue, ElephantFetcher fetcher) { + this._threadId = threadNum; + this._jobQueue = jobQueue; + this._fetcher = fetcher; + } + + @Override + public void run() { + try { + logger.info("Initializing fetcher in executor " + _threadId); + this._fetcher.init(_threadId); + } catch (IOException e) { + logger.error("Error initialize fetcher in executor " + _threadId, e); + } + while (!Thread.currentThread().isInterrupted()) { + HadoopJobData jobData = null; + try { + jobData = _jobQueue.take(); + _fetcher.fetchJobData(jobData); + analyzeJob(jobData, _threadId); + _fetcher.finishJob(jobData, true); + } catch (InterruptedException ex) { + logger.info("Executor Thread is interrupted and terminated."); + Thread.currentThread().interrupt(); + } catch (Exception e) { + logger.error("Error analyzing job id : " + jobData.getJobId(), e); + _fetcher.finishJob(jobData, false); + } + } + } + } + + private void analyzeJob(HadoopJobData jobData, int execThreadNum) { ElephantAnalyser analyser = ElephantAnalyser.instance(); - logger.info("Analyze job " + jobData.getJobId()); + logger.info("Analyze job " + jobData.getJobId() + " by executor " + execThreadNum); HeuristicResult[] analysisResults = analyser.analyse(jobData); JobType jobType = analyser.getJobType(jobData); @@ -177,5 +219,6 @@ private void analyzeJob(HadoopJobData jobData) { public void kill() { _running.set(false); _emailer.kill(); + _service.shutdownNow(); } } diff --git a/app/com/linkedin/drelephant/hadoop/HadoopCounterHolder.java b/app/com/linkedin/drelephant/hadoop/HadoopCounterHolder.java index ad3427497..968cafc00 100644 --- a/app/com/linkedin/drelephant/hadoop/HadoopCounterHolder.java +++ b/app/com/linkedin/drelephant/hadoop/HadoopCounterHolder.java @@ -1,5 +1,6 @@ package com.linkedin.drelephant.hadoop; +import java.util.HashMap; import java.util.Map; @@ -34,55 +35,79 @@ public static enum GroupName { GroupName(String name) { this._name = name; } - - public String getName() { - return _name; - } } public static enum CounterName { - BYTES_READ(GroupName.FileInput, "BYTES_READ"), - BYTES_WRITTEN(GroupName.FileOutput, "BYTES_WRITTEN"), - - FILE_BYTES_READ(GroupName.FileSystemCounters, "FILE_BYTES_READ"), - FILE_BYTES_WRITTEN(GroupName.FileSystemCounters, "FILE_BYTES_WRITTEN"), - HDFS_BYTES_READ(GroupName.FileSystemCounters, "HDFS_BYTES_READ"), - HDFS_BYTES_WRITTEN(GroupName.FileSystemCounters, "HDFS_BYTES_WRITTEN"), - - MAP_INPUT_RECORDS(GroupName.MapReduce, "MAP_INPUT_RECORDS"), - MAP_OUTPUT_RECORDS(GroupName.MapReduce, "MAP_OUTPUT_RECORDS"), - MAP_OUTPUT_BYTES(GroupName.MapReduce, "MAP_OUTPUT_BYTES"), - MAP_OUTPUT_MATERIALIZED_BYTES(GroupName.MapReduce, "MAP_OUTPUT_MATERIALIZED_BYTES"), - SPLIT_RAW_BYTES(GroupName.MapReduce, "SPLIT_RAW_BYTES"), - - REDUCE_INPUT_GROUPS(GroupName.MapReduce, "REDUCE_INPUT_GROUPS"), - REDUCE_SHUFFLE_BYTES(GroupName.MapReduce, "REDUCE_SHUFFLE_BYTES"), - REDUCE_OUTPUT_RECORDS(GroupName.MapReduce, "REDUCE_OUTPUT_RECORDS"), - REDUCE_INPUT_RECORDS(GroupName.MapReduce, "REDUCE_INPUT_RECORDS"), - - COMBINE_INPUT_RECORDS(GroupName.MapReduce, "COMBINE_INPUT_RECORDS"), - COMBINE_OUTPUT_RECORDS(GroupName.MapReduce, "COMBINE_OUTPUT_RECORDS"), - SPILLED_RECORDS(GroupName.MapReduce, "SPILLED_RECORDS"), - - CPU_MILLISECONDS(GroupName.MapReduce, "CPU_MILLISECONDS"), - COMMITTED_HEAP_BYTES(GroupName.MapReduce, "COMMITTED_HEAP_BYTES"), - PHYSICAL_MEMORY_BYTES(GroupName.MapReduce, "PHYSICAL_MEMORY_BYTES"), - VIRTUAL_MEMORY_BYTES(GroupName.MapReduce, "VIRTUAL_MEMORY_BYTES"); + BYTES_READ(GroupName.FileInput, "BYTES_READ", "Bytes Read"), + BYTES_WRITTEN(GroupName.FileOutput, "BYTES_WRITTEN", "Bytes Written"), + + FILE_BYTES_READ(GroupName.FileSystemCounters, "FILE_BYTES_READ", "FILE_BYTES_READ"), + FILE_BYTES_WRITTEN(GroupName.FileSystemCounters, "FILE_BYTES_WRITTEN", "FILE_BYTES_WRITTEN"), + HDFS_BYTES_READ(GroupName.FileSystemCounters, "HDFS_BYTES_READ", "HDFS_BYTES_READ"), + HDFS_BYTES_WRITTEN(GroupName.FileSystemCounters, "HDFS_BYTES_WRITTEN", "HDFS_BYTES_WRITTEN"), + + MAP_INPUT_RECORDS(GroupName.MapReduce, "MAP_INPUT_RECORDS", "Map input records"), + MAP_OUTPUT_RECORDS(GroupName.MapReduce, "MAP_OUTPUT_RECORDS", "Map output records"), + MAP_OUTPUT_BYTES(GroupName.MapReduce, "MAP_OUTPUT_BYTES", "Map output bytes"), + MAP_OUTPUT_MATERIALIZED_BYTES(GroupName.MapReduce, "MAP_OUTPUT_MATERIALIZED_BYTES", "Map output materialized bytes"), + SPLIT_RAW_BYTES(GroupName.MapReduce, "SPLIT_RAW_BYTES", "SPLIT_RAW_BYTES"), + + REDUCE_INPUT_GROUPS(GroupName.MapReduce, "REDUCE_INPUT_GROUPS", "Reduce input groups"), + REDUCE_SHUFFLE_BYTES(GroupName.MapReduce, "REDUCE_SHUFFLE_BYTES", "Reduce shuffle bytes"), + REDUCE_OUTPUT_RECORDS(GroupName.MapReduce, "REDUCE_OUTPUT_RECORDS", "Reduce output records"), + REDUCE_INPUT_RECORDS(GroupName.MapReduce, "REDUCE_INPUT_RECORDS", "Reduce input records"), + + COMBINE_INPUT_RECORDS(GroupName.MapReduce, "COMBINE_INPUT_RECORDS", "Combine input records"), + COMBINE_OUTPUT_RECORDS(GroupName.MapReduce, "COMBINE_OUTPUT_RECORDS", "Combine output records"), + SPILLED_RECORDS(GroupName.MapReduce, "SPILLED_RECORDS", "Spilled Records"), + + CPU_MILLISECONDS(GroupName.MapReduce, "CPU_MILLISECONDS", "CPU time spent (ms)"), + COMMITTED_HEAP_BYTES(GroupName.MapReduce, "COMMITTED_HEAP_BYTES", "Total committed heap usage (bytes)"), + PHYSICAL_MEMORY_BYTES(GroupName.MapReduce, "PHYSICAL_MEMORY_BYTES", "Physical memory (bytes) snapshot"), + VIRTUAL_MEMORY_BYTES(GroupName.MapReduce, "VIRTUAL_MEMORY_BYTES", "Virtual memory (bytes) snapshot"); GroupName _group; String _name; + String _displayName; - CounterName(GroupName group, String name) { + CounterName(GroupName group, String name, String displayName) { this._group = group; this._name = name; + this._displayName = displayName; + } + + static Map _counterDisplayNameMap; + static Map _counterNameMap; + static { + _counterDisplayNameMap = new HashMap(); + _counterNameMap = new HashMap(); + for (CounterName cn : CounterName.values()) { + _counterDisplayNameMap.put(cn._displayName, cn); + _counterNameMap.put(cn._name, cn); + } } - public GroupName getGroup() { - return _group; + public static CounterName getCounterFromName(String name) { + if (_counterNameMap.containsKey(name)) { + return _counterNameMap.get(name); + } + return null; + } + + public static CounterName getCounterFromDisplayName(String displayName) { + if (_counterDisplayNameMap.containsKey(displayName)) { + return _counterDisplayNameMap.get(displayName); + } + return null; } public String getName() { return _name; } + + public String getDisplayName() { + return _displayName; + } + } } diff --git a/app/com/linkedin/drelephant/hadoop/HadoopJobData.java b/app/com/linkedin/drelephant/hadoop/HadoopJobData.java index ecf695321..570964696 100644 --- a/app/com/linkedin/drelephant/hadoop/HadoopJobData.java +++ b/app/com/linkedin/drelephant/hadoop/HadoopJobData.java @@ -13,6 +13,12 @@ public class HadoopJobData { private HadoopTaskData[] _mapperData; private HadoopTaskData[] _reducerData; private Properties _jobConf; + private boolean _isRetry = false; + + public HadoopJobData setRetry(boolean isRetry) { + this._isRetry = isRetry; + return this; + } public HadoopJobData setJobId(String jobId) { this._jobId = jobId; @@ -91,6 +97,10 @@ public String getJobName() { return _jobName; } + public boolean isRetryJob() { + return _isRetry; + } + public String getJobId() { return _jobId; } From f8ab8cffd09031b3ad7e875c6f2dca52c33b5ef0 Mon Sep 17 00:00:00 2001 From: fli Date: Wed, 17 Sep 2014 16:39:01 -0700 Subject: [PATCH 009/272] HADOOP-7196: Dr.e get incorrect time data from jobtracker for a few jobs --- .../drelephant/ElephantFetcherClassic.java | 120 ++++++------------ .../drelephant/ElephantFetcherYarn.java | 5 +- .../heuristics/JobQueueLimitHeuristic.java | 14 +- .../heuristics/MapperSpeedHeuristic.java | 26 ++-- .../heuristics/ReducerTimeHeuristic.java | 32 ++--- .../heuristics/ShuffleSortHeuristic.java | 42 +++--- .../drelephant/hadoop/HadoopTaskData.java | 38 ++---- .../linkedin/drelephant/math/Statistics.java | 6 +- .../JobQueueLimitHeuristicTest.java | 6 +- .../MapperDataSkewHeuristicTest.java | 4 +- .../MapperInputSizeHeuristicTest.java | 2 +- .../heuristics/MapperSpeedHeuristicTest.java | 18 +-- .../ReducerDataSkewHeuristicTest.java | 4 +- .../heuristics/ReducerTimeHeuristicTest.java | 26 ++-- .../heuristics/ShuffleSortHeuristicTest.java | 32 ++--- 15 files changed, 163 insertions(+), 212 deletions(-) diff --git a/app/com/linkedin/drelephant/ElephantFetcherClassic.java b/app/com/linkedin/drelephant/ElephantFetcherClassic.java index 0441e42e5..deb40bb34 100644 --- a/app/com/linkedin/drelephant/ElephantFetcherClassic.java +++ b/app/com/linkedin/drelephant/ElephantFetcherClassic.java @@ -48,6 +48,7 @@ public class ElephantFetcherClassic implements ElephantFetcher { private static final int DEFAULT_RETRY = 2; private Configuration _conf; private Set _previousJobs; + private Map _failedJobsInWait = new ConcurrentHashMap(); private Map _failedJobsInProgress = new ConcurrentHashMap(); private boolean _firstRun = true; @@ -122,10 +123,12 @@ public void fetchJobData(HadoopJobData jobData) throws IOException, Authenticati if (job == null || (mapperTasks.length == 0 && reducerTasks.length == 0)) { boolean retired = checkRetiredAndFetchJobData(jobData); if (retired) { + // This is a retired job logger.info(jobData.getJobId() + " retired. Fetch data from web UI."); ThreadContextMR1.updateAuthToken(); return; } + // This is a no data job } JobStatus status = job.getJobStatus(); @@ -280,7 +283,8 @@ private long[] fetchTaskDetails(String taskDetailUrl, boolean isMapper) throws I throw new IOException("No valid time data found from task detail page. TASK URL=" + taskDetailUrl); } - //Return shuffle sort time if successfully extracted data from table row () + //This method return time array if successfully extracts data from table row () + //Return [totalMs,shuffleMs,sortMs] for reducers, [totalMs,0,0] for mappers private long[] tryExtractDetailFromRow(Element row, boolean isMapper) throws ParseException { Elements cells = row.select("> td"); @@ -292,40 +296,16 @@ private long[] tryExtractDetailFromRow(Element row, boolean isMapper) throws Par boolean succeeded = cells.get(2).html().trim().equals("SUCCEEDED"); if (succeeded) { - SimpleDateFormat dateFormat = ThreadContextMR1.getDateFormat(); if (!isMapper) { - // reducer task. Get start finish shuffle sort time - String startTime = cells.get(4).html().trim(); - String shuffleTime = cells.get(5).html().trim(); - String sortTime = cells.get(6).html().trim(); - String finishTime = cells.get(7).html().trim(); - if (shuffleTime.contains("(")) { - shuffleTime = shuffleTime.substring(0, shuffleTime.indexOf("(") - 1); - } - if (sortTime.contains("(")) { - sortTime = sortTime.substring(0, sortTime.indexOf("(") - 1); - } - if (finishTime.contains("(")) { - finishTime = finishTime.substring(0, finishTime.indexOf("(") - 1); - } - long start = dateFormat.parse(startTime).getTime(); - long shuffle = dateFormat.parse(shuffleTime).getTime(); - long sort = dateFormat.parse(sortTime).getTime(); - long finish = dateFormat.parse(finishTime).getTime(); - - long shuffleDuration = (shuffle - start); - long sortDuration = (sort - shuffle); - return new long[] { start, finish, shuffleDuration, sortDuration }; + // reducer task. Get total/shuffle/sort time from time span. Html text format: [abs time(time span)] + long shuffleTimeMs = parseTimeInMs(cells.get(5).html().trim()); + long sortTimeMs = parseTimeInMs(cells.get(6).html().trim()); + long totalTimeMs = parseTimeInMs(cells.get(7).html().trim()); + return new long[] { totalTimeMs, shuffleTimeMs, sortTimeMs }; } else { - // Mapper task. Get start finish time - String startTime = cells.get(4).html().trim(); - String finishTime = cells.get(5).html().trim(); - if (finishTime.contains("(")) { - finishTime = finishTime.substring(0, finishTime.indexOf("(") - 1); - } - long start = dateFormat.parse(startTime).getTime(); - long finish = dateFormat.parse(finishTime).getTime(); - return new long[] { start, finish, 0, 0 }; + // Mapper task. Get total time from time span + long totalTimeMs = parseTimeInMs(cells.get(5).html().trim()); + return new long[] { totalTimeMs, 0, 0 }; } } // This is a failed task attempt. @@ -473,7 +453,7 @@ private long fetchStartTimeForRetiredJob(Document doc, String jobUrl) throws IOE for (Element row : rows) { Elements cells = row.select("> td"); if (cells.size() == 7 && cells.get(0).text().trim().equals("Setup")) { - SimpleDateFormat dateFormat = ThreadContextMR1.getDateFormat(); + SimpleDateFormat dateFormat = new SimpleDateFormat("d-MMM-yyyy HH:mm:ss"); try { long time = dateFormat.parse(cells.get(5).text().trim()).getTime(); return time; @@ -522,47 +502,22 @@ private HadoopTaskData fetchTaskDataForRetiredJob(String taskDetailUrl, boolean private HadoopTaskData tryExtractTaskDataForRetiredJob(Element row, boolean isMapper) throws IOException, ParseException, AuthenticationException { Elements cells = row.select("> td"); - if (isMapper && cells.size() == 7) { - SimpleDateFormat dateFormat = ThreadContextMR1.getDateFormatForRetiredJob(); String c = cells.get(6).text().trim(); if (c.matches("[^0][0-9]+")) { - String startTime = cells.get(1).text().trim(); - String finishTime = cells.get(2).text().trim(); - if (finishTime.contains("(")) { - finishTime = finishTime.substring(0, finishTime.indexOf("(") - 1); - } - long start = dateFormat.parse(startTime).getTime(); - long finish = dateFormat.parse(finishTime).getTime(); - long[] time = new long[] { start, finish, 0, 0 }; + long totalTimeMs = parseTimeInMs(cells.get(2).html().trim()); + long[] time = new long[] { totalTimeMs, 0, 0 }; String counterUrl = _jobtrackerHttpRoot + cells.get(6).select("a").attr("href"); HadoopCounterHolder ctrholder = fetchTaskCounterForRetiredJob(counterUrl); return new HadoopTaskData(ctrholder, time); } } else if (!isMapper && cells.size() == 9) { - SimpleDateFormat dateFormat = ThreadContextMR1.getDateFormatForRetiredJob(); String c = cells.get(8).text().trim(); if (c.matches("[^0][0-9]+")) { - String startTime = cells.get(1).text().trim(); - String shuffleTime = cells.get(2).text().trim(); - String sortTime = cells.get(3).text().trim(); - String finishTime = cells.get(4).text().trim(); - if (shuffleTime.contains("(")) { - shuffleTime = shuffleTime.substring(0, shuffleTime.indexOf("(") - 1); - } - if (sortTime.contains("(")) { - sortTime = sortTime.substring(0, sortTime.indexOf("(") - 1); - } - if (finishTime.contains("(")) { - finishTime = finishTime.substring(0, finishTime.indexOf("(") - 1); - } - long start = dateFormat.parse(startTime).getTime(); - long shuffle = dateFormat.parse(shuffleTime).getTime(); - long sort = dateFormat.parse(sortTime).getTime(); - long finish = dateFormat.parse(finishTime).getTime(); - long shuffleDuration = (shuffle - start); - long sortDuration = (sort - shuffle); - long[] time = new long[] { start, finish, shuffleDuration, sortDuration }; + long shuffleTimeMs = parseTimeInMs(cells.get(2).html().trim()); + long sortTimeMs = parseTimeInMs(cells.get(3).html().trim()); + long totalTimeMs = parseTimeInMs(cells.get(4).html().trim()); + long[] time = new long[] { totalTimeMs, shuffleTimeMs, sortTimeMs }; String counterUrl = _jobtrackerHttpRoot + cells.get(8).select("a").attr("href"); HadoopCounterHolder ctrholder = fetchTaskCounterForRetiredJob(counterUrl); return new HadoopTaskData(ctrholder, time); @@ -571,6 +526,24 @@ private HadoopTaskData tryExtractTaskDataForRetiredJob(Element row, boolean isMa return null; } + // Return time in ms from input date "Xhrs,Xmins,Xsecs" + private long parseTimeInMs(String rawDateStr) { + int hour = 0; + int min = 0; + int sec = 0; + String dateStr = rawDateStr.substring(rawDateStr.indexOf('(') + 1, rawDateStr.indexOf(')')); + String[] parts = dateStr.split(","); + for (String part : parts) { + if (part.endsWith("hrs")) { + hour = Integer.parseInt(part.substring(0, part.indexOf('h')).trim()); + } else if (part.endsWith("mins")) { + min = Integer.parseInt(part.substring(0, part.indexOf('m')).trim()); + } else if (part.endsWith("sec")) { + sec = Integer.parseInt(part.substring(0, part.indexOf('s')).trim()); + } + } + return hour * Statistics.HOUR_IN_MS + min * Statistics.MINUTE_IN_MS + sec * Statistics.SECOND_IN_MS; + } } final class ThreadContextMR1 { @@ -580,9 +553,6 @@ final class ThreadContextMR1 { private static final ThreadLocal _LOCAL_AUTH_TOKEN = new ThreadLocal(); private static final ThreadLocal _LOCAL_AUTH_URL = new ThreadLocal(); - private static final ThreadLocal _LOCAL_DATE_FORMAT = new ThreadLocal(); - private static final ThreadLocal _LOCAL_DATE_FORMAT_FOR_RETIRED = - new ThreadLocal(); private static final ThreadLocal _LOCAL_LAST_UPDATED = new ThreadLocal(); private static final ThreadLocal _LOCAL_UPDATE_INTERVAL = new ThreadLocal(); @@ -593,28 +563,18 @@ public static void init(Configuration hadoopConf, int threadId) throws IOExcepti _LOCAL_JOB_CLIENT.set(new JobClient(new JobConf(hadoopConf))); _LOCAL_AUTH_TOKEN.set(new AuthenticatedURL.Token()); _LOCAL_AUTH_URL.set(new AuthenticatedURL()); - _LOCAL_DATE_FORMAT.set(new SimpleDateFormat("d-MMM-yyyy HH:mm:ss")); - _LOCAL_DATE_FORMAT_FOR_RETIRED.set(new SimpleDateFormat("d/MM HH:mm:ss")); _LOCAL_THREAD_ID.set(threadId); _LOCAL_LAST_UPDATED.set(System.currentTimeMillis()); // Random an interval for each executor to avoid update token at the same time - _LOCAL_UPDATE_INTERVAL.set(Statistics.MINUTE * 30 + new Random().nextLong() % (10 * Statistics.MINUTE)); + _LOCAL_UPDATE_INTERVAL.set(Statistics.MINUTE_IN_MS * 30 + new Random().nextLong() % (3 * Statistics.MINUTE_IN_MS)); logger.info("Executor " + _LOCAL_THREAD_ID.get() + " update interval " + _LOCAL_UPDATE_INTERVAL.get() * 1.0 - / Statistics.MINUTE); + / Statistics.MINUTE_IN_MS); } public static JobClient getClient() { return _LOCAL_JOB_CLIENT.get(); } - public static SimpleDateFormat getDateFormat() { - return _LOCAL_DATE_FORMAT.get(); - } - - public static SimpleDateFormat getDateFormatForRetiredJob() { - return _LOCAL_DATE_FORMAT_FOR_RETIRED.get(); - } - public static Document fetchHtmlDoc(String url) throws IOException, AuthenticationException { HttpURLConnection conn = _LOCAL_AUTH_URL.get().openConnection(new URL(url), _LOCAL_AUTH_TOKEN.get()); Document doc = Jsoup.parse(IOUtils.toString(conn.getInputStream())); diff --git a/app/com/linkedin/drelephant/ElephantFetcherYarn.java b/app/com/linkedin/drelephant/ElephantFetcherYarn.java index 9199d92fb..6ac662a14 100644 --- a/app/com/linkedin/drelephant/ElephantFetcherYarn.java +++ b/app/com/linkedin/drelephant/ElephantFetcherYarn.java @@ -286,11 +286,11 @@ private long[] getTaskExecTime(URL url) throws IOException, AuthenticationExcept long[] time; if (isMapper) { // No shuffle sore time in Mapper - time = new long[] { startTime, finishTime, 0, 0 }; + time = new long[] { finishTime-startTime, 0, 0 }; } else { long shuffleTime = taskAttempt.get("elapsedShuffleTime").getLongValue(); long sortTime = taskAttempt.get("elapsedMergeTime").getLongValue(); - time = new long[] { startTime, finishTime, shuffleTime, sortTime }; + time = new long[] { finishTime-startTime, shuffleTime, sortTime }; } return time; @@ -364,6 +364,7 @@ private void checkAndRemoveFromRetryList(HadoopJobData jobData) { } } + final class ThreadContextMR2 { private static final ThreadLocal _LOCAL_AUTH_TOKEN = new ThreadLocal(); diff --git a/app/com/linkedin/drelephant/analysis/heuristics/JobQueueLimitHeuristic.java b/app/com/linkedin/drelephant/analysis/heuristics/JobQueueLimitHeuristic.java index a01a6cfcc..db7a5f687 100644 --- a/app/com/linkedin/drelephant/analysis/heuristics/JobQueueLimitHeuristic.java +++ b/app/com/linkedin/drelephant/analysis/heuristics/JobQueueLimitHeuristic.java @@ -22,7 +22,7 @@ public String getHeuristicName() { public HeuristicResult apply(HadoopJobData data) { HeuristicResult result = new HeuristicResult(HEURISTIC_NAME, Severity.NONE); Properties jobConf = data.getJobConf(); - long queueTimeoutLimit = TimeUnit.MINUTES.toMillis(15); + long queueTimeoutLimitMs = TimeUnit.MINUTES.toMillis(15); // Fetch the Queue to which the job is submitted. String queueName = jobConf.getProperty("mapred.job.queue.name"); @@ -41,14 +41,14 @@ public HeuristicResult apply(HadoopJobData data) { result.addDetail("Number of Reduce tasks", Integer.toString(redTasks.length)); // Calculate Severity of Mappers - mapTasksSeverity = getTasksSeverity(mapTasks, queueTimeoutLimit); + mapTasksSeverity = getTasksSeverity(mapTasks, queueTimeoutLimitMs); result.addDetail("Number of Map tasks that are in severe state (14 to 14.5 min)", Long.toString(getSeverityFrequency(Severity.SEVERE, mapTasksSeverity))); result.addDetail("Number of Map tasks that are in critical state (over 14.5 min)", Long.toString(getSeverityFrequency(Severity.CRITICAL, mapTasksSeverity))); // Calculate Severity of Reducers - redTasksSeverity = getTasksSeverity(redTasks, queueTimeoutLimit); + redTasksSeverity = getTasksSeverity(redTasks, queueTimeoutLimitMs); result.addDetail("Number of Reduce tasks that are in severe state (14 to 14.5 min)", Long.toString(getSeverityFrequency(Severity.SEVERE, redTasksSeverity))); result.addDetail("Number of Reduce tasks that are in critical state (over 14.5 min)", @@ -68,7 +68,7 @@ private Severity[] getTasksSeverity(HadoopTaskData[] tasks, long queueTimeout) { Severity[] tasksSeverity = new Severity[tasks.length]; int i = 0; for (HadoopTaskData task : tasks) { - tasksSeverity[i] = getQueueLimitSeverity(task.getRunTime(), queueTimeout); + tasksSeverity[i] = getQueueLimitSeverity(task.getTotalRunTimeMs(), queueTimeout); i++; } return tasksSeverity; @@ -85,12 +85,12 @@ private long getSeverityFrequency(Severity severity, Severity[] tasksSeverity) { } private Severity getQueueLimitSeverity(long taskTime, long queueTimeout) { - long timeUnit = TimeUnit.SECONDS.toMillis(30); // 30s + long timeUnitMs = TimeUnit.SECONDS.toMillis(30); // 30s if (queueTimeout == 0) { return Severity.NONE; } - return Severity.getSeverityAscending(taskTime, queueTimeout - 4 * timeUnit, queueTimeout - 3 * timeUnit, - queueTimeout - 2 * timeUnit, queueTimeout - timeUnit); + return Severity.getSeverityAscending(taskTime, queueTimeout - 4 * timeUnitMs, queueTimeout - 3 * timeUnitMs, + queueTimeout - 2 * timeUnitMs, queueTimeout - timeUnitMs); } } diff --git a/app/com/linkedin/drelephant/analysis/heuristics/MapperSpeedHeuristic.java b/app/com/linkedin/drelephant/analysis/heuristics/MapperSpeedHeuristic.java index 1f1c19f17..33ef21989 100644 --- a/app/com/linkedin/drelephant/analysis/heuristics/MapperSpeedHeuristic.java +++ b/app/com/linkedin/drelephant/analysis/heuristics/MapperSpeedHeuristic.java @@ -30,40 +30,40 @@ public HeuristicResult apply(HadoopJobData data) { List inputByteSizes = new ArrayList(); List speeds = new ArrayList(); - List runtimes = new ArrayList(); + List runtimesMs = new ArrayList(); for (HadoopTaskData task : tasks) { if (task.timed()) { long inputBytes = task.getCounters().get(HadoopCounterHolder.CounterName.HDFS_BYTES_READ); - long runtime = task.getEndTime() - task.getStartTime(); + long runtimeMs = task.getTotalRunTimeMs(); //Apply 1 minute buffer - runtime -= 60 * 1000; - if (runtime < 1000) { - runtime = 1000; + runtimeMs -= 60 * 1000; + if (runtimeMs < 1000) { + runtimeMs = 1000; } inputByteSizes.add(inputBytes); - runtimes.add(runtime); + runtimesMs.add(runtimeMs); //Speed is bytes per second - speeds.add((1000 * inputBytes) / (runtime)); + speeds.add((1000 * inputBytes) / (runtimeMs)); } } //Analyze data long averageSpeed = Statistics.average(speeds); long averageSize = Statistics.average(inputByteSizes); - long averageRuntime = Statistics.average(runtimes); + long averageRuntimeMs = Statistics.average(runtimesMs); Severity severity = getDiskSpeedSeverity(averageSpeed); //This reduces severity if task runtime is insignificant - severity = Severity.min(severity, getRuntimeSeverity(averageRuntime)); + severity = Severity.min(severity, getRuntimeSeverity(averageRuntimeMs)); HeuristicResult result = new HeuristicResult(HEURISTIC_NAME, severity); result.addDetail("Number of tasks", Integer.toString(tasks.length)); result.addDetail("Average task input size", FileUtils.byteCountToDisplaySize(averageSize)); result.addDetail("Average task speed", FileUtils.byteCountToDisplaySize(averageSpeed) + "/s"); - result.addDetail("Average task runtime", Statistics.readableTimespan(averageRuntime)); + result.addDetail("Average task runtime", Statistics.readableTimespan(averageRuntimeMs)); return result; } @@ -73,8 +73,8 @@ public static Severity getDiskSpeedSeverity(long speed) { Constants.DISK_READ_SPEED / 8, Constants.DISK_READ_SPEED / 32); } - public static Severity getRuntimeSeverity(long runtime) { - return Severity.getSeverityAscending(runtime, 5 * Statistics.MINUTE, 20 * Statistics.MINUTE, - 40 * Statistics.MINUTE, 1 * Statistics.HOUR); + public static Severity getRuntimeSeverity(long runtimeMs) { + return Severity.getSeverityAscending(runtimeMs, 5 * Statistics.MINUTE_IN_MS, 20 * Statistics.MINUTE_IN_MS, + 40 * Statistics.MINUTE_IN_MS, 1 * Statistics.HOUR_IN_MS); } } diff --git a/app/com/linkedin/drelephant/analysis/heuristics/ReducerTimeHeuristic.java b/app/com/linkedin/drelephant/analysis/heuristics/ReducerTimeHeuristic.java index e0674c016..84b79f9f6 100644 --- a/app/com/linkedin/drelephant/analysis/heuristics/ReducerTimeHeuristic.java +++ b/app/com/linkedin/drelephant/analysis/heuristics/ReducerTimeHeuristic.java @@ -23,49 +23,49 @@ public String getHeuristicName() { public HeuristicResult apply(HadoopJobData data) { HadoopTaskData[] tasks = data.getReducerData(); - List runTimes = new ArrayList(); + List runTimesMs = new ArrayList(); for (HadoopTaskData task : tasks) { if (task.timed()) { - runTimes.add(task.getRunTime()); + runTimesMs.add(task.getTotalRunTimeMs()); } } //Analyze data - long averageRuntime = Statistics.average(runTimes); + long averageRuntimeMs = Statistics.average(runTimesMs); - Severity shortTimeSeverity = shortTimeSeverity(averageRuntime, tasks.length); - Severity longTimeSeverity = longTimeSeverity(averageRuntime, tasks.length); + Severity shortTimeSeverity = shortTimeSeverity(averageRuntimeMs, tasks.length); + Severity longTimeSeverity = longTimeSeverity(averageRuntimeMs, tasks.length); Severity severity = Severity.max(shortTimeSeverity, longTimeSeverity); HeuristicResult result = new HeuristicResult(HEURISTIC_NAME, severity); result.addDetail("Number of tasks", Integer.toString(tasks.length)); - result.addDetail("Average task time", Statistics.readableTimespan(averageRuntime)); + result.addDetail("Average task time", Statistics.readableTimespan(averageRuntimeMs)); return result; } - private Severity shortTimeSeverity(long runtime, long numTasks) { - Severity timeSeverity = getShortRuntimeSeverity(runtime); + private Severity shortTimeSeverity(long runtimeMs, long numTasks) { + Severity timeSeverity = getShortRuntimeSeverity(runtimeMs); Severity taskSeverity = getNumTasksSeverity(numTasks); return Severity.min(timeSeverity, taskSeverity); } - private Severity longTimeSeverity(long runtime, long numTasks) { - Severity timeSeverity = getLongRuntimeSeverity(runtime); + private Severity longTimeSeverity(long runtimeMs, long numTasks) { + Severity timeSeverity = getLongRuntimeSeverity(runtimeMs); Severity taskSeverity = getNumTasksSeverityReverse(numTasks); return Severity.min(timeSeverity, taskSeverity); } - public static Severity getShortRuntimeSeverity(long runtime) { - return Severity.getSeverityDescending(runtime, 10 * Statistics.MINUTE, 5 * Statistics.MINUTE, - 2 * Statistics.MINUTE, 1 * Statistics.MINUTE); + public static Severity getShortRuntimeSeverity(long runtimeMs) { + return Severity.getSeverityDescending(runtimeMs, 10 * Statistics.MINUTE_IN_MS, 5 * Statistics.MINUTE_IN_MS, + 2 * Statistics.MINUTE_IN_MS, 1 * Statistics.MINUTE_IN_MS); } - public static Severity getLongRuntimeSeverity(long runtime) { - return Severity.getSeverityAscending(runtime, 15 * Statistics.MINUTE, 30 * Statistics.MINUTE, 1 * Statistics.HOUR, - 2 * Statistics.HOUR); + public static Severity getLongRuntimeSeverity(long runtimeMs) { + return Severity.getSeverityAscending(runtimeMs, 15 * Statistics.MINUTE_IN_MS, 30 * Statistics.MINUTE_IN_MS, 1 * Statistics.HOUR_IN_MS, + 2 * Statistics.HOUR_IN_MS); } public static Severity getNumTasksSeverity(long numTasks) { diff --git a/app/com/linkedin/drelephant/analysis/heuristics/ShuffleSortHeuristic.java b/app/com/linkedin/drelephant/analysis/heuristics/ShuffleSortHeuristic.java index 8e2c48900..333931e40 100644 --- a/app/com/linkedin/drelephant/analysis/heuristics/ShuffleSortHeuristic.java +++ b/app/com/linkedin/drelephant/analysis/heuristics/ShuffleSortHeuristic.java @@ -24,48 +24,48 @@ public HeuristicResult apply(HadoopJobData data) { HadoopTaskData[] tasks = data.getReducerData(); - List execTime = new ArrayList(); - List shuffleTime = new ArrayList(); - List sortTime = new ArrayList(); + List execTimeMs = new ArrayList(); + List shuffleTimeMs = new ArrayList(); + List sortTimeMs = new ArrayList(); for (HadoopTaskData task : tasks) { if (task.timed()) { - execTime.add(task.getExecutionTime()); - shuffleTime.add(task.getShuffleTime()); - sortTime.add(task.getSortTime()); + execTimeMs.add(task.getCodeExecutionTimeMs()); + shuffleTimeMs.add(task.getShuffleTimeMs()); + sortTimeMs.add(task.getSortTimeMs()); } } //Analyze data - long avgExecTime = Statistics.average(execTime); - long avgShuffleTime = Statistics.average(shuffleTime); - long avgSortTime = Statistics.average(sortTime); + long avgExecTimeMs = Statistics.average(execTimeMs); + long avgShuffleTimeMs = Statistics.average(shuffleTimeMs); + long avgSortTimeMs = Statistics.average(sortTimeMs); - Severity shuffleSeverity = getShuffleSortSeverity(avgShuffleTime, avgExecTime); - Severity sortSeverity = getShuffleSortSeverity(avgSortTime, avgExecTime); + Severity shuffleSeverity = getShuffleSortSeverity(avgShuffleTimeMs, avgExecTimeMs); + Severity sortSeverity = getShuffleSortSeverity(avgSortTimeMs, avgExecTimeMs); Severity severity = Severity.max(shuffleSeverity, sortSeverity); HeuristicResult result = new HeuristicResult(HEURISTIC_NAME, severity); result.addDetail("Number of tasks", Integer.toString(data.getReducerData().length)); - result.addDetail("Average code runtime", Statistics.readableTimespan(avgExecTime)); - String shuffleFactor = Statistics.describeFactor(avgShuffleTime, avgExecTime, "x"); - result.addDetail("Average shuffle time", Statistics.readableTimespan(avgShuffleTime) + " " + shuffleFactor); - String sortFactor = Statistics.describeFactor(avgSortTime, avgExecTime, "x"); - result.addDetail("Average sort time", Statistics.readableTimespan(avgSortTime) + " " + sortFactor); + result.addDetail("Average code runtime", Statistics.readableTimespan(avgExecTimeMs)); + String shuffleFactor = Statistics.describeFactor(avgShuffleTimeMs, avgExecTimeMs, "x"); + result.addDetail("Average shuffle time", Statistics.readableTimespan(avgShuffleTimeMs) + " " + shuffleFactor); + String sortFactor = Statistics.describeFactor(avgSortTimeMs, avgExecTimeMs, "x"); + result.addDetail("Average sort time", Statistics.readableTimespan(avgSortTimeMs) + " " + sortFactor); return result; } - public static Severity getShuffleSortSeverity(long runtime, long codetime) { + public static Severity getShuffleSortSeverity(long runtimeMs, long codetimeMs) { Severity runtimeSeverity = - Severity.getSeverityAscending(runtime, 1 * Statistics.MINUTE, 5 * Statistics.MINUTE, 10 * Statistics.MINUTE, - 30 * Statistics.MINUTE); + Severity.getSeverityAscending(runtimeMs, 1 * Statistics.MINUTE_IN_MS, 5 * Statistics.MINUTE_IN_MS, 10 * Statistics.MINUTE_IN_MS, + 30 * Statistics.MINUTE_IN_MS); - if (codetime <= 0) { + if (codetimeMs <= 0) { return runtimeSeverity; } - long value = runtime * 2 / codetime; + long value = runtimeMs * 2 / codetimeMs; Severity runtimeRatioSeverity = Severity.getSeverityAscending(value, 1, 2, 4, 8); return Severity.min(runtimeSeverity, runtimeRatioSeverity); diff --git a/app/com/linkedin/drelephant/hadoop/HadoopTaskData.java b/app/com/linkedin/drelephant/hadoop/HadoopTaskData.java index c53750f98..8d7f8189e 100644 --- a/app/com/linkedin/drelephant/hadoop/HadoopTaskData.java +++ b/app/com/linkedin/drelephant/hadoop/HadoopTaskData.java @@ -2,18 +2,16 @@ public class HadoopTaskData { private HadoopCounterHolder _counterHolder; - private long _startTime = 0; - private long _endTime = 0; - private long _shuffleTime = 0; - private long _sortTime = 0; + private long _totalTimeMs = 0; + private long _shuffleTimeMs = 0; + private long _sortTimeMs = 0; private boolean _timed = false; public HadoopTaskData(HadoopCounterHolder counterHolder, long[] time) { this._counterHolder = counterHolder; - this._startTime = time[0]; - this._endTime = time[1]; - this._shuffleTime = time[2]; - this._sortTime = time[3]; + this._totalTimeMs = time[0]; + this._shuffleTimeMs = time[1]; + this._sortTimeMs = time[2]; this._timed = true; } @@ -25,28 +23,20 @@ public HadoopCounterHolder getCounters() { return _counterHolder; } - public long getStartTime() { - return _startTime; + public long getTotalRunTimeMs() { + return _totalTimeMs; } - public long getEndTime() { - return _endTime; + public long getCodeExecutionTimeMs() { + return _totalTimeMs - _shuffleTimeMs - _sortTimeMs; } - public long getRunTime() { - return _endTime - _startTime; + public long getShuffleTimeMs() { + return _shuffleTimeMs; } - public long getExecutionTime() { - return _endTime - _startTime - _shuffleTime - _sortTime; - } - - public long getShuffleTime() { - return _shuffleTime; - } - - public long getSortTime() { - return _sortTime; + public long getSortTimeMs() { + return _sortTimeMs; } public boolean timed() { diff --git a/app/com/linkedin/drelephant/math/Statistics.java b/app/com/linkedin/drelephant/math/Statistics.java index e49dda498..edcc62f44 100644 --- a/app/com/linkedin/drelephant/math/Statistics.java +++ b/app/com/linkedin/drelephant/math/Statistics.java @@ -12,9 +12,9 @@ public final class Statistics { - public static final long SECOND = 1000L; - public static final long MINUTE = 60L * SECOND; - public static final long HOUR = 60L * MINUTE; + public static final long SECOND_IN_MS = 1000L; + public static final long MINUTE_IN_MS = 60L * SECOND_IN_MS; + public static final long HOUR_IN_MS = 60L * MINUTE_IN_MS; private Statistics() { } diff --git a/test/com/linkedin/drelephant/analysis/heuristics/JobQueueLimitHeuristicTest.java b/test/com/linkedin/drelephant/analysis/heuristics/JobQueueLimitHeuristicTest.java index 5f78256d3..341bea59f 100644 --- a/test/com/linkedin/drelephant/analysis/heuristics/JobQueueLimitHeuristicTest.java +++ b/test/com/linkedin/drelephant/analysis/heuristics/JobQueueLimitHeuristicTest.java @@ -46,7 +46,7 @@ public void testNonDefaultRuntimeNone() throws IOException { assertEquals(Severity.NONE, analyzeJob(15 * 60 * 1000, "non-default")); } - private Severity analyzeJob(long runtime, String queueName) throws IOException { + private Severity analyzeJob(long runtimeMs, String queueName) throws IOException { HadoopCounterHolder dummyCounter = new HadoopCounterHolder(null); HadoopTaskData[] mappers = new HadoopTaskData[2 * NUM_TASKS / 3]; HadoopTaskData[] reducers = new HadoopTaskData[NUM_TASKS / 3]; @@ -54,10 +54,10 @@ private Severity analyzeJob(long runtime, String queueName) throws IOException { jobConf.put("mapred.job.queue.name", queueName); int i = 0; for (; i < 2 * NUM_TASKS / 3; i++) { - mappers[i] = new HadoopTaskData(dummyCounter, new long[] { 0, runtime, 0, 0 }); + mappers[i] = new HadoopTaskData(dummyCounter, new long[] { runtimeMs, 0, 0 }); } for (i = 0; i < NUM_TASKS / 3; i++) { - reducers[i] = new HadoopTaskData(dummyCounter, new long[] { 0, runtime, 0, 0 }); + reducers[i] = new HadoopTaskData(dummyCounter, new long[] { runtimeMs, 0, 0 }); } HadoopJobData data = new HadoopJobData().setCounters(dummyCounter).setReducerData(reducers).setMapperData(mappers) diff --git a/test/com/linkedin/drelephant/analysis/heuristics/MapperDataSkewHeuristicTest.java b/test/com/linkedin/drelephant/analysis/heuristics/MapperDataSkewHeuristicTest.java index 821c261ad..00c8936e1 100644 --- a/test/com/linkedin/drelephant/analysis/heuristics/MapperDataSkewHeuristicTest.java +++ b/test/com/linkedin/drelephant/analysis/heuristics/MapperDataSkewHeuristicTest.java @@ -61,10 +61,10 @@ private Severity analyzeJob(int numSmallTasks, int numLargeTasks, long smallInpu int i = 0; for (; i < numSmallTasks; i++) { - mappers[i] = new HadoopTaskData(smallCounter, new long[4]); + mappers[i] = new HadoopTaskData(smallCounter, new long[3]); } for (; i < numSmallTasks + numLargeTasks; i++) { - mappers[i] = new HadoopTaskData(largeCounter, new long[4]); + mappers[i] = new HadoopTaskData(largeCounter, new long[3]); } HadoopJobData data = new HadoopJobData().setCounters(jobCounter).setMapperData(mappers); diff --git a/test/com/linkedin/drelephant/analysis/heuristics/MapperInputSizeHeuristicTest.java b/test/com/linkedin/drelephant/analysis/heuristics/MapperInputSizeHeuristicTest.java index f32c34ee2..46a83e8a6 100644 --- a/test/com/linkedin/drelephant/analysis/heuristics/MapperInputSizeHeuristicTest.java +++ b/test/com/linkedin/drelephant/analysis/heuristics/MapperInputSizeHeuristicTest.java @@ -69,7 +69,7 @@ private Severity analyzeJob(int numTasks, long inputSize) throws IOException { int i = 0; for (; i < numTasks; i++) { - mappers[i] = new HadoopTaskData(taskCounter, new long[4]); + mappers[i] = new HadoopTaskData(taskCounter, new long[3]); } HadoopJobData data = new HadoopJobData().setCounters(jobCounter).setMapperData(mappers); diff --git a/test/com/linkedin/drelephant/analysis/heuristics/MapperSpeedHeuristicTest.java b/test/com/linkedin/drelephant/analysis/heuristics/MapperSpeedHeuristicTest.java index 462ce4036..1d71682ad 100644 --- a/test/com/linkedin/drelephant/analysis/heuristics/MapperSpeedHeuristicTest.java +++ b/test/com/linkedin/drelephant/analysis/heuristics/MapperSpeedHeuristicTest.java @@ -19,34 +19,34 @@ public class MapperSpeedHeuristicTest extends TestCase { Heuristic _heuristic = new MapperSpeedHeuristic(); private static final long UNITSIZE = Constants.HDFS_BLOCK_SIZE / 64; - private static final long MINUTE = Statistics.MINUTE; + private static final long MINUTE_IN_MS = Statistics.MINUTE_IN_MS; private static final int NUMTASKS = Constants.SHUFFLE_SORT_MAX_SAMPLE_SIZE; public void testCritical() throws IOException { - assertEquals(Severity.CRITICAL, analyzeJob(120 * MINUTE, 10000 * UNITSIZE)); + assertEquals(Severity.CRITICAL, analyzeJob(120 * MINUTE_IN_MS, 10000 * UNITSIZE)); } public void testSevere() throws IOException { - assertEquals(Severity.SEVERE, analyzeJob(120 * MINUTE, 50000 * UNITSIZE)); + assertEquals(Severity.SEVERE, analyzeJob(120 * MINUTE_IN_MS, 50000 * UNITSIZE)); } public void testModerate() throws IOException { - assertEquals(Severity.MODERATE, analyzeJob(120 * MINUTE, 100000 * UNITSIZE)); + assertEquals(Severity.MODERATE, analyzeJob(120 * MINUTE_IN_MS, 100000 * UNITSIZE)); } public void testLow() throws IOException { - assertEquals(Severity.LOW, analyzeJob(120 * MINUTE, 200000 * UNITSIZE)); + assertEquals(Severity.LOW, analyzeJob(120 * MINUTE_IN_MS, 200000 * UNITSIZE)); } public void testNone() throws IOException { - assertEquals(Severity.NONE, analyzeJob(120 * MINUTE, 500000 * UNITSIZE)); + assertEquals(Severity.NONE, analyzeJob(120 * MINUTE_IN_MS, 500000 * UNITSIZE)); } public void testShortTask() throws IOException { - assertEquals(Severity.NONE, analyzeJob(2 * MINUTE, 10 * UNITSIZE)); + assertEquals(Severity.NONE, analyzeJob(2 * MINUTE_IN_MS, 10 * UNITSIZE)); } - private Severity analyzeJob(long runtime, long readBytes) throws IOException { + private Severity analyzeJob(long runtimeMs, long readBytes) throws IOException { HadoopCounterHolder jobCounter = new HadoopCounterHolder(null); HadoopTaskData[] mappers = new HadoopTaskData[NUMTASKS]; @@ -55,7 +55,7 @@ private Severity analyzeJob(long runtime, long readBytes) throws IOException { int i = 0; for (; i < NUMTASKS; i++) { - mappers[i] = new HadoopTaskData(counter, new long[] { 0, runtime, 0, 0 }); + mappers[i] = new HadoopTaskData(counter, new long[] { runtimeMs, 0, 0 }); } HadoopJobData data = new HadoopJobData().setCounters(jobCounter).setMapperData(mappers); diff --git a/test/com/linkedin/drelephant/analysis/heuristics/ReducerDataSkewHeuristicTest.java b/test/com/linkedin/drelephant/analysis/heuristics/ReducerDataSkewHeuristicTest.java index 4339da4c4..5132efd1e 100644 --- a/test/com/linkedin/drelephant/analysis/heuristics/ReducerDataSkewHeuristicTest.java +++ b/test/com/linkedin/drelephant/analysis/heuristics/ReducerDataSkewHeuristicTest.java @@ -60,10 +60,10 @@ private Severity analyzeJob(int numSmallTasks, int numLargeTasks, long smallInpu int i = 0; for (; i < numSmallTasks; i++) { - reducers[i] = new HadoopTaskData(smallCounter, new long[4]); + reducers[i] = new HadoopTaskData(smallCounter, new long[3]); } for (; i < numSmallTasks + numLargeTasks; i++) { - reducers[i] = new HadoopTaskData(largeCounter, new long[4]); + reducers[i] = new HadoopTaskData(largeCounter, new long[3]); } HadoopJobData data = new HadoopJobData().setCounters(jobCounter).setReducerData(reducers); diff --git a/test/com/linkedin/drelephant/analysis/heuristics/ReducerTimeHeuristicTest.java b/test/com/linkedin/drelephant/analysis/heuristics/ReducerTimeHeuristicTest.java index 93645f85d..4d19569f2 100644 --- a/test/com/linkedin/drelephant/analysis/heuristics/ReducerTimeHeuristicTest.java +++ b/test/com/linkedin/drelephant/analysis/heuristics/ReducerTimeHeuristicTest.java @@ -15,55 +15,55 @@ public class ReducerTimeHeuristicTest extends TestCase { Heuristic _heuristic = new ReducerTimeHeuristic(); - private static final long MINUTE = Statistics.MINUTE;; + private static final long MINUTE_IN_MS = Statistics.MINUTE_IN_MS;; public void testShortRunetimeCritical() throws IOException { - assertEquals(Severity.CRITICAL, analyzeJob(1 * MINUTE, 500)); + assertEquals(Severity.CRITICAL, analyzeJob(1 * MINUTE_IN_MS, 500)); } public void testShortRunetimeSevere() throws IOException { - assertEquals(Severity.SEVERE, analyzeJob(1 * MINUTE, 200)); + assertEquals(Severity.SEVERE, analyzeJob(1 * MINUTE_IN_MS, 200)); } public void testShortRunetimeModerate() throws IOException { - assertEquals(Severity.MODERATE, analyzeJob(1 * MINUTE, 50)); + assertEquals(Severity.MODERATE, analyzeJob(1 * MINUTE_IN_MS, 50)); } public void testShortRunetimeLow() throws IOException { - assertEquals(Severity.LOW, analyzeJob(1 * MINUTE, 10)); + assertEquals(Severity.LOW, analyzeJob(1 * MINUTE_IN_MS, 10)); } public void testShortRunetimeNone() throws IOException { - assertEquals(Severity.NONE, analyzeJob(1 * MINUTE, 2)); + assertEquals(Severity.NONE, analyzeJob(1 * MINUTE_IN_MS, 2)); } public void testLongRunetimeCritical() throws IOException { - assertEquals(Severity.CRITICAL, analyzeJob(120 * MINUTE, 10)); + assertEquals(Severity.CRITICAL, analyzeJob(120 * MINUTE_IN_MS, 10)); } public void testLongRunetimeSevere() throws IOException { - assertEquals(Severity.SEVERE, analyzeJob(120 * MINUTE, 20)); + assertEquals(Severity.SEVERE, analyzeJob(120 * MINUTE_IN_MS, 20)); } public void testLongRunetimeModerate() throws IOException { - assertEquals(Severity.MODERATE, analyzeJob(120 * MINUTE, 50)); + assertEquals(Severity.MODERATE, analyzeJob(120 * MINUTE_IN_MS, 50)); } public void testLongRunetimeLow() throws IOException { - assertEquals(Severity.LOW, analyzeJob(120 * MINUTE, 100)); + assertEquals(Severity.LOW, analyzeJob(120 * MINUTE_IN_MS, 100)); } public void testLongRunetimeNone() throws IOException { - assertEquals(Severity.NONE, analyzeJob(120 * MINUTE, 200)); + assertEquals(Severity.NONE, analyzeJob(120 * MINUTE_IN_MS, 200)); } - private Severity analyzeJob(long runtime, int numTasks) throws IOException { + private Severity analyzeJob(long runtimeMs, int numTasks) throws IOException { HadoopCounterHolder dummyCounter = new HadoopCounterHolder(null); HadoopTaskData[] reducers = new HadoopTaskData[numTasks]; int i = 0; for (; i < numTasks; i++) { - reducers[i] = new HadoopTaskData(dummyCounter, new long[] { 0, runtime, 0, 0 }); + reducers[i] = new HadoopTaskData(dummyCounter, new long[] { runtimeMs, 0, 0 }); } HadoopJobData data = new HadoopJobData().setCounters(dummyCounter).setReducerData(reducers); diff --git a/test/com/linkedin/drelephant/analysis/heuristics/ShuffleSortHeuristicTest.java b/test/com/linkedin/drelephant/analysis/heuristics/ShuffleSortHeuristicTest.java index 136d0298d..6beb43e5d 100644 --- a/test/com/linkedin/drelephant/analysis/heuristics/ShuffleSortHeuristicTest.java +++ b/test/com/linkedin/drelephant/analysis/heuristics/ShuffleSortHeuristicTest.java @@ -17,64 +17,64 @@ public class ShuffleSortHeuristicTest extends TestCase { Heuristic _heuristic = new ShuffleSortHeuristic(); private static final int NUMTASKS = Constants.SHUFFLE_SORT_MAX_SAMPLE_SIZE; - private static final long MINUTE = Statistics.MINUTE;; + private static final long MINUTE_IN_MS = Statistics.MINUTE_IN_MS;; public void testLongShuffleCritical() throws IOException { - assertEquals(Severity.CRITICAL, analyzeJob(30 * MINUTE, 0, 5 * MINUTE)); + assertEquals(Severity.CRITICAL, analyzeJob(30 * MINUTE_IN_MS, 0, 5 * MINUTE_IN_MS)); } public void testLongShuffleSevere() throws IOException { - assertEquals(Severity.SEVERE, analyzeJob(30 * MINUTE, 0, 10 * MINUTE)); + assertEquals(Severity.SEVERE, analyzeJob(30 * MINUTE_IN_MS, 0, 10 * MINUTE_IN_MS)); } public void testLongShuffleModerate() throws IOException { - assertEquals(Severity.MODERATE, analyzeJob(30 * MINUTE, 0, 20 * MINUTE)); + assertEquals(Severity.MODERATE, analyzeJob(30 * MINUTE_IN_MS, 0, 20 * MINUTE_IN_MS)); } public void testLongShuffleLow() throws IOException { - assertEquals(Severity.LOW, analyzeJob(30 * MINUTE, 0, 40 * MINUTE)); + assertEquals(Severity.LOW, analyzeJob(30 * MINUTE_IN_MS, 0, 40 * MINUTE_IN_MS)); } public void testLongShuffleNone() throws IOException { - assertEquals(Severity.NONE, analyzeJob(30 * MINUTE, 0, 80 * MINUTE)); + assertEquals(Severity.NONE, analyzeJob(30 * MINUTE_IN_MS, 0, 80 * MINUTE_IN_MS)); } public void testLongSortCritical() throws IOException { - assertEquals(Severity.CRITICAL, analyzeJob(0, 30 * MINUTE, 5 * MINUTE)); + assertEquals(Severity.CRITICAL, analyzeJob(0, 30 * MINUTE_IN_MS, 5 * MINUTE_IN_MS)); } public void testLongSortSevere() throws IOException { - assertEquals(Severity.SEVERE, analyzeJob(0, 30 * MINUTE, 10 * MINUTE)); + assertEquals(Severity.SEVERE, analyzeJob(0, 30 * MINUTE_IN_MS, 10 * MINUTE_IN_MS)); } public void testLongSortModerate() throws IOException { - assertEquals(Severity.MODERATE, analyzeJob(0, 30 * MINUTE, 20 * MINUTE)); + assertEquals(Severity.MODERATE, analyzeJob(0, 30 * MINUTE_IN_MS, 20 * MINUTE_IN_MS)); } public void testLongSortLow() throws IOException { - assertEquals(Severity.LOW, analyzeJob(0, 30 * MINUTE, 40 * MINUTE)); + assertEquals(Severity.LOW, analyzeJob(0, 30 * MINUTE_IN_MS, 40 * MINUTE_IN_MS)); } public void testLongSortNone() throws IOException { - assertEquals(Severity.NONE, analyzeJob(0, 30 * MINUTE, 80 * MINUTE)); + assertEquals(Severity.NONE, analyzeJob(0, 30 * MINUTE_IN_MS, 80 * MINUTE_IN_MS)); } public void testShortShuffle() throws IOException { - assertEquals(Severity.NONE, analyzeJob(MINUTE / 2, 0, MINUTE / 2)); + assertEquals(Severity.NONE, analyzeJob(MINUTE_IN_MS / 2, 0, MINUTE_IN_MS / 2)); } public void testShortSort() throws IOException { - assertEquals(Severity.NONE, analyzeJob(0, MINUTE / 2, MINUTE / 2)); + assertEquals(Severity.NONE, analyzeJob(0, MINUTE_IN_MS / 2, MINUTE_IN_MS / 2)); } - private Severity analyzeJob(long shuffleTime, long sortTime, long reduceTime) throws IOException { + private Severity analyzeJob(long shuffleTimeMs, long sortTimeMs, long reduceTimeMs) throws IOException { HadoopCounterHolder dummyCounter = new HadoopCounterHolder(null); HadoopTaskData[] reducers = new HadoopTaskData[NUMTASKS]; int i = 0; for (; i < NUMTASKS; i++) { - reducers[i] = - new HadoopTaskData(dummyCounter, new long[] { 0, shuffleTime + sortTime + reduceTime, shuffleTime, sortTime }); + reducers[i] = new HadoopTaskData(dummyCounter, + new long[] { shuffleTimeMs + sortTimeMs + reduceTimeMs, shuffleTimeMs, sortTimeMs }); } HadoopJobData data = new HadoopJobData().setCounters(dummyCounter).setReducerData(reducers); HeuristicResult result = _heuristic.apply(data); From 873cd151e62c20a9fd65302eb51fcb4febe99292 Mon Sep 17 00:00:00 2001 From: fli Date: Thu, 2 Oct 2014 23:04:46 +0000 Subject: [PATCH 010/272] HADOOP-7374: Pluggable Heuristics is not working on Dr.E --- .../linkedin/drelephant/ElephantAnalyser.java | 60 +++++----- .../linkedin/drelephant/ElephantRunner.java | 8 +- .../drelephant/util/HeuristicConf.java | 113 +++++++++++++----- .../drelephant/util/HeuristicConfData.java | 36 ++---- app/controllers/Application.java | 71 ++++++----- conf/HeuristicConf.xml | 25 ++-- 6 files changed, 186 insertions(+), 127 deletions(-) diff --git a/app/com/linkedin/drelephant/ElephantAnalyser.java b/app/com/linkedin/drelephant/ElephantAnalyser.java index 84516acad..b87a7f39d 100644 --- a/app/com/linkedin/drelephant/ElephantAnalyser.java +++ b/app/com/linkedin/drelephant/ElephantAnalyser.java @@ -3,8 +3,6 @@ import java.util.ArrayList; import java.util.List; import model.JobType; - -import org.apache.hadoop.conf.Configuration; import play.api.Play; import org.apache.log4j.Logger; @@ -21,13 +19,27 @@ public class ElephantAnalyser { private List _heuristicNames = new ArrayList(); private static final Logger logger = Logger.getLogger(ElephantAnalyser.class); - private static final ElephantAnalyser ANALYZER = new ElephantAnalyser(); + private static ElephantAnalyser _analyzer = null; private HeuristicResult _nodata; private List _heuristics = new ArrayList(); + public static void init() { + if (_analyzer == null) { + _analyzer = new ElephantAnalyser(); + } + } + + public static ElephantAnalyser instance() { + if (_analyzer == null) { + init(); + } + return _analyzer; + } + private ElephantAnalyser() { + logger.info("Initialize Analyzer... Loading heuristics...."); _nodata = new HeuristicResult(NO_DATA, Severity.LOW); - List heuristics = getHeuristics(); + List heuristics = loadHeuristics(); for (Heuristic heuristic : heuristics) { addHeuristic(heuristic); } @@ -63,42 +75,32 @@ public JobType getJobType(HadoopJobData data) { return JobType.HADOOPJAVA; } - public static ElephantAnalyser instance() { - return ANALYZER; - } - public List getHeuristicNames() { return this._heuristicNames; } - private List getHeuristics() { + private List loadHeuristics() { + List heuristics = new ArrayList(); HeuristicConf conf = HeuristicConf.instance(); List heuristicsConfData = conf.getHeuristicsConfData(); - Configuration hadoopConf = new Configuration(); - String framework = hadoopConf.get("mapreduce.framework.name"); for (HeuristicConfData data : heuristicsConfData) { - if (data.getHadoopVersions().contains(framework)) { - try { - Class heuristicClass = Play.current().classloader().loadClass(data.getClassName()); - heuristics.add((Heuristic) heuristicClass.newInstance()); - } catch (ClassNotFoundException e) { - throw new RuntimeException("Could not find class " + data.getClassName() - + ". Make sure your class is in the classpath", e); - } catch (InstantiationException e) { - throw new RuntimeException( - "Could not instantiate class " - + data.getClassName() - + ". Check that your class is not an abstract class, an interface, an array class, a primitive type, or void. Also make sure the class has a nullary constructor.", - e); - } catch (IllegalAccessException e) { - throw new RuntimeException("Error in accessing the class " + data.getClassName() + ". Make sure " - + data.getClassName() + " is public", e); - } + try { + Class heuristicClass = Play.current().classloader().loadClass(data.getClassName()); + heuristics.add((Heuristic) heuristicClass.newInstance()); + logger.info("Load Heuristic : " + data.getClassName()); + } catch (ClassNotFoundException e) { + throw new RuntimeException("Could not find class " + data.getClassName(), e); + } catch (InstantiationException e) { + throw new RuntimeException("Could not instantiate class " + data.getClassName(), e); + } catch (IllegalAccessException e) { + throw new RuntimeException("Could not access constructor for class" + data.getClassName(), e); + } catch (RuntimeException e) { + //More descriptive on other runtime exception such as ClassCastException + throw new RuntimeException(data.getClassName() + " is not a valid Heuristic class.", e); } } return heuristics; - } } diff --git a/app/com/linkedin/drelephant/ElephantRunner.java b/app/com/linkedin/drelephant/ElephantRunner.java index 7cc5492bb..9068d431d 100644 --- a/app/com/linkedin/drelephant/ElephantRunner.java +++ b/app/com/linkedin/drelephant/ElephantRunner.java @@ -89,6 +89,13 @@ public Void run() { _service.submit(new ExecutorThread(i + 1, _jobQueue, fetcher)); } + try { + ElephantAnalyser.init(); + } catch (Exception e) { + logger.error("Error loading pluggable heuristics. ", e); + return null; + } + while (_running.get()) { lastRun = System.currentTimeMillis(); @@ -171,7 +178,6 @@ public void run() { private void analyzeJob(HadoopJobData jobData, int execThreadNum) { ElephantAnalyser analyser = ElephantAnalyser.instance(); - logger.info("Analyze job " + jobData.getJobId() + " by executor " + execThreadNum); HeuristicResult[] analysisResults = analyser.analyse(jobData); diff --git a/app/com/linkedin/drelephant/util/HeuristicConf.java b/app/com/linkedin/drelephant/util/HeuristicConf.java index 6ca034ae9..cc1bbd47a 100644 --- a/app/com/linkedin/drelephant/util/HeuristicConf.java +++ b/app/com/linkedin/drelephant/util/HeuristicConf.java @@ -1,7 +1,7 @@ package com.linkedin.drelephant.util; -import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; import java.util.List; @@ -9,73 +9,120 @@ import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; +import org.apache.hadoop.conf.Configuration; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; +import play.Logger; +import play.Play; + public class HeuristicConf { - private static final String FILE_PATH = "conf/HeuristicConf.xml"; - private List heuristicsConfData; - private static HeuristicConf heuristicConfInstance = new HeuristicConf(); + private static final String CONFIG_FILE_PATH = "HeuristicConf.xml"; + private List _heuristicsConfDataList; + private static HeuristicConf _heuristicConfInstance = new HeuristicConf(); private HeuristicConf() { parseHeuristicConf(); } public static HeuristicConf instance() { - return heuristicConfInstance; + return _heuristicConfInstance; } public List getHeuristicsConfData() { - return heuristicsConfData; + return _heuristicsConfDataList; } private void parseHeuristicConf() { - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - heuristicsConfData = new ArrayList(); - DocumentBuilder builder; + + Configuration hadoopConf = new Configuration(); + String hadoopVersion = hadoopConf.get("mapreduce.framework.name"); + if (hadoopVersion == null) { + if (hadoopConf.get("mapred.job.tracker.http.address") != null) { + hadoopVersion = "classic"; + } else { + throw new RuntimeException("Hadoop config error. No framework name provided."); + } + } + + Logger.info("Loading pluggable heuristics config file " + CONFIG_FILE_PATH); + _heuristicsConfDataList = new ArrayList(); + + InputStream instream = Play.application().resourceAsStream(CONFIG_FILE_PATH); + if (instream == null) { + throw new RuntimeException("File " + CONFIG_FILE_PATH + " does not exist."); + } + Document document = null; try { - FileInputStream fstream = new FileInputStream(FILE_PATH); - builder = factory.newDocumentBuilder(); - document = builder.parse(fstream); + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + document = builder.parse(instream); } catch (ParserConfigurationException e) { - throw new RuntimeException("Could not parse configuration file " + FILE_PATH - + ". Check if the parser is property configured", e); + throw new RuntimeException("XML Parser could not be created.", e); } catch (SAXException e) { - throw new RuntimeException("PARSE ERROR: Could not parse configuration file " + FILE_PATH - + ". Check if the file is properly formed", e); + throw new RuntimeException(CONFIG_FILE_PATH + " is not properly formed", e); } catch (IOException e) { - throw new RuntimeException("I/O ERROR: Not able to read " + FILE_PATH - + " file. Check if the file exists and it has proper permissions.", e); + throw new RuntimeException("Unable to read " + CONFIG_FILE_PATH, e); } NodeList nodes = document.getDocumentElement().getChildNodes(); for (int i = 0; i < nodes.getLength(); i++) { Node node = nodes.item(i); - HeuristicConfData heuristicConfData = null; + int n = 0; if (node.getNodeType() == Node.ELEMENT_NODE) { - Element heuristic = (Element) node; - String className = heuristic.getElementsByTagName("classname").item(0).getTextContent(); - String heuristicName = heuristic.getElementsByTagName("heuristicname").item(0).getTextContent(); - String viewName = heuristic.getElementsByTagName("viewname").item(0).getTextContent(); - Node versionNode = heuristic.getElementsByTagName("hadoopversions").item(0); - if (versionNode.getNodeType() == Node.ELEMENT_NODE) { - ArrayList hadoopVersions = new ArrayList(); - Element versions = (Element) versionNode; - NodeList versionList = versions.getElementsByTagName("version"); - for (int j = 0; j < versionList.getLength(); j++) { - hadoopVersions.add(versionList.item(j).getTextContent()); + n++; + Element heuristicNode = (Element) node; + + String className; + Node classNameNode = heuristicNode.getElementsByTagName("classname").item(0); + if (classNameNode == null) { + throw new RuntimeException("No tag 'classname' in heuristic " + n); + } + className = classNameNode.getTextContent(); + if (className.equals("")) { + throw new RuntimeException("Empty tag 'classname' in heuristic " + n); + } + + String heuristicName; + Node heuristicNameNode = heuristicNode.getElementsByTagName("heuristicname").item(0); + if (heuristicNameNode == null) { + throw new RuntimeException("No tag 'heuristicname' in heuristic " + n); + } + heuristicName = heuristicNameNode.getTextContent(); + if (heuristicName.equals("")) { + throw new RuntimeException("Empty tag 'heuristicname' in heuristic " + n); + } + + String viewName; + Node viewNameNode = heuristicNode.getElementsByTagName("viewname").item(0); + if (viewNameNode == null) { + throw new RuntimeException("No tag 'viewname' in heuristic " + n); + } + viewName = viewNameNode.getTextContent(); + if (viewName.equals("")) { + throw new RuntimeException("Empty tag 'viewname' in heuristic " + n); + } + + Node versionsNode = heuristicNode.getElementsByTagName("hadoopversions").item(0); + if (versionsNode == null || versionsNode.getNodeType() != Node.ELEMENT_NODE) { + throw new RuntimeException("No tag or invalid tag 'hadoopversions' in heuristic " + n); + } + NodeList versionList = ((Element) versionsNode).getElementsByTagName("version"); + for (int j = 0; j < versionList.getLength(); j++) { + String version = versionList.item(j).getTextContent(); + if (version.equals(hadoopVersion)) { + HeuristicConfData heuristicData = new HeuristicConfData(heuristicName, className, viewName); + _heuristicsConfDataList.add(heuristicData); + break; } - heuristicConfData = new HeuristicConfData(heuristicName, className, viewName, hadoopVersions); } - heuristicsConfData.add(heuristicConfData); } } } - } diff --git a/app/com/linkedin/drelephant/util/HeuristicConfData.java b/app/com/linkedin/drelephant/util/HeuristicConfData.java index 99016c33e..f732e4d64 100644 --- a/app/com/linkedin/drelephant/util/HeuristicConfData.java +++ b/app/com/linkedin/drelephant/util/HeuristicConfData.java @@ -1,37 +1,25 @@ package com.linkedin.drelephant.util; -import java.util.ArrayList; -import java.util.List; - - - public class HeuristicConfData { - private String heuristicName; - private String className; - private String viewName; - private List hadoopVersions; - - public HeuristicConfData(String heuristicName,String className, String viewName, List hadoopVersions) - { - this.heuristicName = heuristicName; - this.className = className; - this.viewName = viewName; - this.hadoopVersions = hadoopVersions; + private String _heuristicName; + private String _className; + private String _viewName; + + public HeuristicConfData(String heuristicName, String className, String viewName) { + this._heuristicName = heuristicName; + this._className = className; + this._viewName = viewName; } + public String getHeuristicName() { - return heuristicName; + return _heuristicName; } public String getClassName() { - return className; + return _className; } public String getViewName() { - return viewName; - } - - public List getHadoopVersions() { - return hadoopVersions; + return _viewName; } - } diff --git a/app/controllers/Application.java b/app/controllers/Application.java index 88f8daefa..d52c4df74 100644 --- a/app/controllers/Application.java +++ b/app/controllers/Application.java @@ -11,6 +11,8 @@ import java.util.List; import java.util.Map; +import org.apache.log4j.Logger; + import model.JobResult; import views.html.*; import play.api.Play; @@ -28,16 +30,23 @@ public class Application extends Controller { + private static final Logger logger = Logger.getLogger(Application.class); private static final long DAY = 24 * 60 * 60 * 1000; private static final long FETCH_DELAY = 60 * 1000; private static long _lastFetch = 0; private static int _numJobsAnalyzed = 0; private static int _numJobsCritical = 0; private static int _numJobsSevere = 0; - private static Map pageMap = new HashMap(); + private static Map _helpPages = new HashMap(); static { - fillPageMap(); + try { + logger.info("Loading pluggable heuristics help pages."); + fillHelpPages(); + } catch (Exception e) { + logger.error("Error loading pluggable heuristics help pages.", e); + throw new RuntimeException(e); + } } public static Result search() { @@ -124,7 +133,7 @@ public static Result help() { Html page = null; String title = "Help"; if (topic != null && !topic.isEmpty()) { - page = pageMap.get(topic); + page = _helpPages.get(topic); if (page != null) { title = topic; } @@ -133,37 +142,45 @@ public static Result help() { } //create a map to cache pages. - private static void fillPageMap() { - - Html page = null; + private static void fillHelpPages() { + logger.info("Loading help pages for pluggable heuristics"); HeuristicConf conf = HeuristicConf.instance(); - List heuristicsConfData = conf.getHeuristicsConfData(); - for (HeuristicConfData data : heuristicsConfData) { + List heuristicsConfList = conf.getHeuristicsConfData(); + for (HeuristicConfData heuristicConf : heuristicsConfList) { + Class viewClass = null; + String heuristicName = null; try { - Class viewClass = Play.current().classloader().loadClass(data.getViewName()); - Class heuristicClass = Play.current().classloader().loadClass(data.getClassName()); - String heuristicName = (String) heuristicClass.getDeclaredField("HEURISTIC_NAME").get(null); - Method render = viewClass.getDeclaredMethod("render"); - page = (Html) render.invoke(null); - pageMap.put(heuristicName, page); + String viewName = heuristicConf.getViewName(); + logger.info("Loading help page " + viewName); + viewClass = Play.current().classloader().loadClass(viewName); + Class heuristicClass = Play.current().classloader().loadClass(heuristicConf.getClassName()); + heuristicName = (String) heuristicClass.getDeclaredField("HEURISTIC_NAME").get(null); } catch (ClassNotFoundException e) { - throw new RuntimeException("Could not find class " + data.getViewName() - + ". Make sure your class is in the classpath", e); - } catch (SecurityException e) { - throw new RuntimeException(e); - } catch (NoSuchMethodException e) { - throw new RuntimeException("The method render() doesn't exist in the class " + data.getViewName(), e); - } catch (IllegalArgumentException e) { - throw new RuntimeException("The arguments passed to render() are illegal or inappropriate"); + throw new RuntimeException("Could not find class " + heuristicConf.getViewName(), e); } catch (IllegalAccessException e) { - throw new RuntimeException("Error in accessing the class " + data.getViewName() + ". Make sure " - + data.getViewName() + " is public"); - } catch (InvocationTargetException e) { - throw new RuntimeException("The render method threw an exception. Check the implementation of the method"); + throw new RuntimeException("field HEURISTIC_NAME in class " + heuristicConf.getClassName() + + " is not accessible."); } catch (NoSuchFieldException e) { - throw new RuntimeException("No field with the name HEURISTIC_NAME was found in class " + data.getClassName()); + throw new RuntimeException("No field HEURISTIC_NAME in class " + heuristicConf.getClassName()); + } catch (Exception e) { + // More descriptive on other Runtime Exceptions such as NullPointerException IllegalArgumentException + throw new RuntimeException("No valid field HEURISTIC_NAME in class" + heuristicConf.getClassName()); } + try { + Method render = viewClass.getDeclaredMethod("render"); + Html page = (Html) render.invoke(null); + _helpPages.put(heuristicName, page); + } catch (NoSuchMethodException e) { + throw new RuntimeException(viewClass.getName() + " is not a valid view.", e); + } catch (IllegalAccessException e) { + throw new RuntimeException(viewClass.getName() + " is not a valid view.", e); + } catch (InvocationTargetException e) { + throw new RuntimeException(viewClass.getName() + " is not a valid view.", e); + } catch (Exception e) { + // More descriptive on other Runtime Exceptions such as ClassCastException IllegalArgumentException + throw new RuntimeException(viewClass.getName() + " is not a valid view.", e); + } } } diff --git a/conf/HeuristicConf.xml b/conf/HeuristicConf.xml index d3cfe9230..530bfd764 100644 --- a/conf/HeuristicConf.xml +++ b/conf/HeuristicConf.xml @@ -2,15 +2,6 @@ - - Job Queue Limit - com.linkedin.drelephant.analysis.heuristics.JobQueueLimitHeuristic - views.html.helpJobQueueLimit - - classic - - - Mapper Data Skew com.linkedin.drelephant.analysis.heuristics.MapperDataSkewHeuristic @@ -41,6 +32,15 @@ + + Mapper Spill + com.linkedin.drelephant.analysis.heuristics.MapperSpillHeuristic + views.html.helpMapperSpill + + classic + yarn + + Reducer Data Skew @@ -73,12 +73,11 @@ - Mapper Spill - com.linkedin.drelephant.analysis.heuristics.MapperSpillHeuristic - views.html.helpMapperSpill + Job Queue Limit + com.linkedin.drelephant.analysis.heuristics.JobQueueLimitHeuristic + views.html.helpJobQueueLimit classic - yarn From 75187e4006c4ed3b5d168f2b13f685b7654723cf Mon Sep 17 00:00:00 2001 From: fli Date: Wed, 8 Oct 2014 02:08:28 +0100 Subject: [PATCH 011/272] HADOOP-7428: Deploy dr.elephant v0.5 on magic --- app/com/linkedin/drelephant/ElephantFetcherClassic.java | 1 + app/com/linkedin/drelephant/ElephantRunner.java | 4 ++-- .../analysis/heuristics/JobQueueLimitHeuristic.java | 2 +- build.sbt | 2 +- conf/HeuristicConf.xml | 4 +--- 5 files changed, 6 insertions(+), 7 deletions(-) diff --git a/app/com/linkedin/drelephant/ElephantFetcherClassic.java b/app/com/linkedin/drelephant/ElephantFetcherClassic.java index deb40bb34..5cda6ca34 100644 --- a/app/com/linkedin/drelephant/ElephantFetcherClassic.java +++ b/app/com/linkedin/drelephant/ElephantFetcherClassic.java @@ -356,6 +356,7 @@ private List fetchAllFailedJobs() { private void checkAndRemoveJobFromRetryList(HadoopJobData jobData) { if (_failedJobsInProgress.containsKey(jobData)) { + logger.info(jobData.getJobId()+" succeeded on retry."); _failedJobsInProgress.remove(jobData); } } diff --git a/app/com/linkedin/drelephant/ElephantRunner.java b/app/com/linkedin/drelephant/ElephantRunner.java index 9068d431d..7488d458b 100644 --- a/app/com/linkedin/drelephant/ElephantRunner.java +++ b/app/com/linkedin/drelephant/ElephantRunner.java @@ -169,7 +169,7 @@ public void run() { logger.info("Executor Thread is interrupted and terminated."); Thread.currentThread().interrupt(); } catch (Exception e) { - logger.error("Error analyzing job id : " + jobData.getJobId(), e); + logger.error("Error analyzing " + jobData.getJobId(), e); _fetcher.finishJob(jobData, false); } } @@ -178,7 +178,7 @@ public void run() { private void analyzeJob(HadoopJobData jobData, int execThreadNum) { ElephantAnalyser analyser = ElephantAnalyser.instance(); - logger.info("Analyze job " + jobData.getJobId() + " by executor " + execThreadNum); + logger.info("Analyze " + jobData.getJobId() + " by executor " + execThreadNum); HeuristicResult[] analysisResults = analyser.analyse(jobData); JobType jobType = analyser.getJobType(jobData); diff --git a/app/com/linkedin/drelephant/analysis/heuristics/JobQueueLimitHeuristic.java b/app/com/linkedin/drelephant/analysis/heuristics/JobQueueLimitHeuristic.java index db7a5f687..1bf15ac31 100644 --- a/app/com/linkedin/drelephant/analysis/heuristics/JobQueueLimitHeuristic.java +++ b/app/com/linkedin/drelephant/analysis/heuristics/JobQueueLimitHeuristic.java @@ -11,7 +11,7 @@ public class JobQueueLimitHeuristic implements Heuristic { - public static final String HEURISTIC_NAME = "Job Queue Timeout Limit"; + public static final String HEURISTIC_NAME = "Queue Time Limit"; @Override public String getHeuristicName() { diff --git a/build.sbt b/build.sbt index 59b0d0167..180c750f3 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ import play.Project._ name := "dr-elephant" -version := "0.5-SNAPSHOT" +version := "0.5" javacOptions in Compile ++= Seq("-source", "1.6", "-target", "1.6") diff --git a/conf/HeuristicConf.xml b/conf/HeuristicConf.xml index 530bfd764..ac5361ee9 100644 --- a/conf/HeuristicConf.xml +++ b/conf/HeuristicConf.xml @@ -37,8 +37,6 @@ com.linkedin.drelephant.analysis.heuristics.MapperSpillHeuristic views.html.helpMapperSpill - classic - yarn @@ -81,4 +79,4 @@ - \ No newline at end of file + From 8d071788b89177bcd97f9468a81240deb0cf96ed Mon Sep 17 00:00:00 2001 From: fli Date: Wed, 8 Oct 2014 23:08:45 +0100 Subject: [PATCH 012/272] HADOOP-7465: Update Dr.Elephant version number to 0.6-SNAPSHOT --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 180c750f3..42daa8bfd 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ import play.Project._ name := "dr-elephant" -version := "0.5" +version := "0.6-SNAPSHOT" javacOptions in Compile ++= Seq("-source", "1.6", "-target", "1.6") From 89875fd16b14abf9bbfd58522cf412f88ced2bc9 Mon Sep 17 00:00:00 2001 From: Anant Nag Date: Tue, 14 Oct 2014 00:20:12 -0700 Subject: [PATCH 013/272] HADOOP-5357: Logging for Dr. Elephant --- conf/log4j.properties | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 conf/log4j.properties diff --git a/conf/log4j.properties b/conf/log4j.properties new file mode 100644 index 000000000..00c4adb44 --- /dev/null +++ b/conf/log4j.properties @@ -0,0 +1,11 @@ +#Define the root logger with appender file +log4j.rootLogger = INFO, FA + +#File Appender +log4j.appender.FA=org.apache.log4j.DailyRollingFileAppender +log4j.appender.FA.File=./logs/dr_elephant.log +log4j.appender.FA.layout=org.apache.log4j.PatternLayout +log4j.appender.FA.layout.ConversionPattern=%d{MM-dd-yyyy HH:mm:ss} %-5p %c %x: %m%n +log4j.appender.FA.DatePattern='.'yyyy-MM-dd +log4j.appender.FA.ImmediateFlush=true +log4j.appender.FA.Append=false From f4183a2abd3acaaea42aea2498a39e6be5f12acd Mon Sep 17 00:00:00 2001 From: Akshay Rai Date: Thu, 9 Oct 2014 03:49:43 -0700 Subject: [PATCH 014/272] HADOOP-7474: Dr. Elephant successfully filters HadoopJava jobs --- app/controllers/Application.java | 3 ++- app/model/JobType.java | 36 +++++++++++++++++++++++++------- app/views/multijob.scala.html | 2 +- app/views/related.scala.html | 2 +- app/views/search.scala.html | 2 +- app/views/singlejob.scala.html | 2 +- 6 files changed, 34 insertions(+), 13 deletions(-) diff --git a/app/controllers/Application.java b/app/controllers/Application.java index d52c4df74..87fe9e11c 100644 --- a/app/controllers/Application.java +++ b/app/controllers/Application.java @@ -14,6 +14,7 @@ import org.apache.log4j.Logger; import model.JobResult; +import model.JobType; import views.html.*; import play.api.Play; import play.api.templates.Html; @@ -74,7 +75,7 @@ public static Result search() { query = query.ilike("username", username); } if (jobtype != null && !jobtype.isEmpty()) { - query = query.eq("job_type", jobtype); + query = query.eq("job_type", JobType.getDbName(jobtype)); } if (severity != null && !severity.isEmpty()) { query = query.ge("heuristicResults.severity", severity); diff --git a/app/model/JobType.java b/app/model/JobType.java index 4ea0552ae..1e91b594c 100644 --- a/app/model/JobType.java +++ b/app/model/JobType.java @@ -1,25 +1,45 @@ package model; +import java.util.HashMap; +import java.util.Map; + import com.avaje.ebean.annotation.EnumValue; public enum JobType { @EnumValue("Hadoop") - HADOOPJAVA("HadoopJava"), + HADOOPJAVA("HadoopJava", "Hadoop"), @EnumValue("Pig") - PIG("Pig"), + PIG("Pig", "Pig"), @EnumValue("Hive") - HIVE("Hive"); + HIVE("Hive", "Hive"); + + private String _name; + private String _dbName; - private String _text; + JobType(String name, String dbName) { + this._name = name; + this._dbName = dbName; + } + + private static Map _jobTypeNameMap; + static { + _jobTypeNameMap = new HashMap(); + for (JobType jobType : JobType.values()) { + _jobTypeNameMap.put(jobType._name, jobType._dbName); + } + } - private JobType(String text) { - this._text = text; + public String getName() { + return _name; } - public String getText() { - return _text; + public static String getDbName(String jobType) { + if (_jobTypeNameMap.containsKey(jobType)) { + return _jobTypeNameMap.get(jobType); + } + return null; } } diff --git a/app/views/multijob.scala.html b/app/views/multijob.scala.html index 585a1238c..06fa5d309 100644 --- a/app/views/multijob.scala.html +++ b/app/views/multijob.scala.html @@ -7,7 +7,7 @@

@title

@for(result <- results) { -

[@result.username][@result.jobType.getText()] @result.job_id

@result.jobName

+

[@result.username][@result.jobType.getName()] @result.job_id

@result.jobName

@for(heuristicResult <- result.heuristicResults) { @heuristicResult.analysisName diff --git a/app/views/related.scala.html b/app/views/related.scala.html index 8239dde78..805d1b2a9 100644 --- a/app/views/related.scala.html +++ b/app/views/related.scala.html @@ -12,7 +12,7 @@

Jobs related to @execurl

-

[@result.username][@result.jobType.getText()] @result.job_id

@result.jobName

+

[@result.username][@result.jobType.getName()] @result.job_id

@result.jobName

@for(heuristicResult <- result.heuristicResults) { @heuristicResult.analysisName diff --git a/app/views/search.scala.html b/app/views/search.scala.html index d96de3cf2..0aabf6f5c 100644 --- a/app/views/search.scala.html +++ b/app/views/search.scala.html @@ -18,7 +18,7 @@

diff --git a/app/views/singlejob.scala.html b/app/views/singlejob.scala.html index ff53814e5..ae2690d57 100644 --- a/app/views/singlejob.scala.html +++ b/app/views/singlejob.scala.html @@ -4,7 +4,7 @@ @if(result != null) {

-

[@result.username] [@result.jobType.getText()] @result.job_id

+

[@result.username] [@result.jobType.getName()] @result.job_id

@result.jobName
From 5be44eaf1ba53e072375275e7f50b66a825596ac Mon Sep 17 00:00:00 2001 From: Akshay Rai Date: Thu, 16 Oct 2014 06:10:40 -0700 Subject: [PATCH 015/272] HADOOP-7420: Filtering pig jobs using pig.script property --- app/com/linkedin/drelephant/ElephantAnalyser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/com/linkedin/drelephant/ElephantAnalyser.java b/app/com/linkedin/drelephant/ElephantAnalyser.java index b87a7f39d..5c7d1ae23 100644 --- a/app/com/linkedin/drelephant/ElephantAnalyser.java +++ b/app/com/linkedin/drelephant/ElephantAnalyser.java @@ -64,7 +64,7 @@ public HeuristicResult[] analyse(HadoopJobData data) { } public JobType getJobType(HadoopJobData data) { - String pigVersion = data.getJobConf().getProperty("pig.version"); + String pigVersion = data.getJobConf().getProperty("pig.script"); if (pigVersion != null && !pigVersion.isEmpty()) { return JobType.PIG; } From 03d0056278209c526fc9a1cf98148935c61fccb6 Mon Sep 17 00:00:00 2001 From: fli Date: Tue, 14 Oct 2014 12:05:48 -0700 Subject: [PATCH 016/272] HADOOP-7264:Dr.E give inconsistent result in MapperSpeedHeuristic --- .../heuristics/MapperInputSizeHeuristic.java | 2 +- .../heuristics/MapperSpeedHeuristic.java | 27 +++++++------------ .../linkedin/drelephant/math/Statistics.java | 13 +++++++++ 3 files changed, 24 insertions(+), 18 deletions(-) diff --git a/app/com/linkedin/drelephant/analysis/heuristics/MapperInputSizeHeuristic.java b/app/com/linkedin/drelephant/analysis/heuristics/MapperInputSizeHeuristic.java index 3c49f9dab..32774eded 100644 --- a/app/com/linkedin/drelephant/analysis/heuristics/MapperInputSizeHeuristic.java +++ b/app/com/linkedin/drelephant/analysis/heuristics/MapperInputSizeHeuristic.java @@ -40,7 +40,7 @@ public HeuristicResult apply(HadoopJobData data) { HeuristicResult result = new HeuristicResult(HEURISTIC_NAME, severity); result.addDetail("Number of tasks", Integer.toString(tasks.length)); - result.addDetail("Average task input", FileUtils.byteCountToDisplaySize(average)); + result.addDetail("Average task input size", FileUtils.byteCountToDisplaySize(average)); return result; } diff --git a/app/com/linkedin/drelephant/analysis/heuristics/MapperSpeedHeuristic.java b/app/com/linkedin/drelephant/analysis/heuristics/MapperSpeedHeuristic.java index 33ef21989..3c5ac9f18 100644 --- a/app/com/linkedin/drelephant/analysis/heuristics/MapperSpeedHeuristic.java +++ b/app/com/linkedin/drelephant/analysis/heuristics/MapperSpeedHeuristic.java @@ -14,7 +14,6 @@ import org.apache.commons.io.FileUtils; - public class MapperSpeedHeuristic implements Heuristic { public static final String HEURISTIC_NAME = "Mapper Speed"; @@ -36,11 +35,6 @@ public HeuristicResult apply(HadoopJobData data) { if (task.timed()) { long inputBytes = task.getCounters().get(HadoopCounterHolder.CounterName.HDFS_BYTES_READ); long runtimeMs = task.getTotalRunTimeMs(); - //Apply 1 minute buffer - runtimeMs -= 60 * 1000; - if (runtimeMs < 1000) { - runtimeMs = 1000; - } inputByteSizes.add(inputBytes); runtimesMs.add(runtimeMs); //Speed is bytes per second @@ -48,22 +42,21 @@ public HeuristicResult apply(HadoopJobData data) { } } - //Analyze data - long averageSpeed = Statistics.average(speeds); - long averageSize = Statistics.average(inputByteSizes); - long averageRuntimeMs = Statistics.average(runtimesMs); + long medianSpeed = Statistics.median(speeds); + long medianSize = Statistics.median(inputByteSizes); + long medianRuntimeMs = Statistics.median(runtimesMs); - Severity severity = getDiskSpeedSeverity(averageSpeed); + Severity severity = getDiskSpeedSeverity(medianSpeed); //This reduces severity if task runtime is insignificant - severity = Severity.min(severity, getRuntimeSeverity(averageRuntimeMs)); + severity = Severity.min(severity, getRuntimeSeverity(medianRuntimeMs)); HeuristicResult result = new HeuristicResult(HEURISTIC_NAME, severity); result.addDetail("Number of tasks", Integer.toString(tasks.length)); - result.addDetail("Average task input size", FileUtils.byteCountToDisplaySize(averageSize)); - result.addDetail("Average task speed", FileUtils.byteCountToDisplaySize(averageSpeed) + "/s"); - result.addDetail("Average task runtime", Statistics.readableTimespan(averageRuntimeMs)); + result.addDetail("Median task input size", FileUtils.byteCountToDisplaySize(medianSize)); + result.addDetail("Median task runtime", Statistics.readableTimespan(medianRuntimeMs)); + result.addDetail("Median task speed", FileUtils.byteCountToDisplaySize(medianSpeed) + "/s"); return result; } @@ -74,7 +67,7 @@ public static Severity getDiskSpeedSeverity(long speed) { } public static Severity getRuntimeSeverity(long runtimeMs) { - return Severity.getSeverityAscending(runtimeMs, 5 * Statistics.MINUTE_IN_MS, 20 * Statistics.MINUTE_IN_MS, - 40 * Statistics.MINUTE_IN_MS, 1 * Statistics.HOUR_IN_MS); + return Severity.getSeverityAscending(runtimeMs, 5 * Statistics.MINUTE_IN_MS, 10 * Statistics.MINUTE_IN_MS, + 15 * Statistics.MINUTE_IN_MS, 30 * Statistics.MINUTE_IN_MS); } } diff --git a/app/com/linkedin/drelephant/math/Statistics.java b/app/com/linkedin/drelephant/math/Statistics.java index edcc62f44..8082caf21 100644 --- a/app/com/linkedin/drelephant/math/Statistics.java +++ b/app/com/linkedin/drelephant/math/Statistics.java @@ -113,6 +113,19 @@ public static long average(List values) { return (long) (sum / (double) values.size()); } + public static long median(List values) { + if (values.size() == 0) { + throw new IllegalArgumentException("Median of an empty list is not defined."); + } + Collections.sort(values); + int middle = values.size() / 2; + if (values.size() % 2 == 0) { + return (values.get(middle - 1) + values.get(middle)) / 2; + } else { + return values.get(middle); + } + } + public static String describeFactor(long value, long compare, String suffix) { double factor = (double) value / (double) compare; if (Double.isNaN(factor)) { From b747f2f9c1381ec097444f2d66391496b3738aed Mon Sep 17 00:00:00 2001 From: fli Date: Tue, 21 Oct 2014 18:23:44 -0700 Subject: [PATCH 017/272] HADOOP-7603: Cluster deployment for dr.E --- compile.sh | 28 +++++++++-- conf/elephant.config.template | 7 +++ conf/log4j.properties | 2 +- start.sh | 88 +++++++++++++++++++++++++++++++++++ stop.sh | 28 +++++++++++ 5 files changed, 147 insertions(+), 6 deletions(-) create mode 100644 conf/elephant.config.template create mode 100755 start.sh create mode 100755 stop.sh diff --git a/compile.sh b/compile.sh index cd99e3c4b..a7b36b50e 100755 --- a/compile.sh +++ b/compile.sh @@ -1,5 +1,15 @@ #!/usr/bin/env bash +script_dir=`which $0` +script_dir=`dirname $script_dir` +script_dir=`cd $script_dir; pwd` + +project_root=$script_dir +start_script=$project_root/start.sh +stop_script=$project_root/stop.sh + +cd $project_root + rm -rf dist mkdir dist @@ -15,11 +25,15 @@ sed -i.bak $'/declare -r app_classpath/s/.$/:$HADOOP_HOME\/*:$HADOOP_HOME\/lib\/ chmod +x $DIST_NAME/bin/dr-elephant -zip -r $DIST_NAME-h1.zip $DIST_NAME -x *.zip tmp/\* * +cp $start_script $DIST_NAME/ + +cp $stop_script $DIST_NAME/ -mv $DIST_NAME-h1.zip ../../dist/ +zip -r $DIST_NAME-h1.zip $DIST_NAME -x $DIST_NAME/*.zip *.zip tmp/\* * -cd ../../ +mv $DIST_NAME-h1.zip $project_root/dist/ + +cd $project_root play -Dhadoop.version=2 clean compile test dist @@ -31,6 +45,10 @@ sed -i.bak $'/declare -r app_classpath/s/.$/:$HADOOP_HOME\/share\/hadoop\/common chmod +x $DIST_NAME/bin/dr-elephant -zip -r $DIST_NAME-h2.zip $DIST_NAME -x *.zip tmp/\* * +cp $start_script $DIST_NAME/ + +cp $stop_script $DIST_NAME/ + +zip -r $DIST_NAME-h2.zip $DIST_NAME -x $DIST_NAME/*.zip *.zip tmp/\* * -mv $DIST_NAME-h2.zip ../../dist/ +mv $DIST_NAME-h2.zip $project_root/dist/ diff --git a/conf/elephant.config.template b/conf/elephant.config.template new file mode 100644 index 000000000..8c48bcba3 --- /dev/null +++ b/conf/elephant.config.template @@ -0,0 +1,7 @@ +port=8080 +db_url=localhost +db_name=drelephant +db_user=root +db_password= +keytab_location="/export/apps/hadoop/keytabs/dr_elephant-service.keytab" +keytab_user="elephant/eat1-magicaz01.grid.linkedin.com" diff --git a/conf/log4j.properties b/conf/log4j.properties index 00c4adb44..6ccdde941 100644 --- a/conf/log4j.properties +++ b/conf/log4j.properties @@ -3,7 +3,7 @@ log4j.rootLogger = INFO, FA #File Appender log4j.appender.FA=org.apache.log4j.DailyRollingFileAppender -log4j.appender.FA.File=./logs/dr_elephant.log +log4j.appender.FA.File=logs/dr_elephant.log log4j.appender.FA.layout=org.apache.log4j.PatternLayout log4j.appender.FA.layout.ConversionPattern=%d{MM-dd-yyyy HH:mm:ss} %-5p %c %x: %m%n log4j.appender.FA.DatePattern='.'yyyy-MM-dd diff --git a/start.sh b/start.sh new file mode 100755 index 000000000..0750f3077 --- /dev/null +++ b/start.sh @@ -0,0 +1,88 @@ +#!/usr/bin/env bash + +function print_usage(){ + echo "usage: ./start.sh PATH_TO_CONFIG_FILE" +} + +function check_config(){ + if [ -z "${!1}" ]; + then + echo "error: ${1} must be present in the config file." + check=0 + else + echo "${1}: " ${!1} + fi +} + +# User must give an argument(config file path) when running this script +if [ -z "$1" ]; +then + print_usage + exit 1 +fi + +CONFIG_FILE=$1 + +# User must give a valid file as argument +if [ -f $CONFIG_FILE ]; +then + echo "Reading from config file..." +else + print_usage + exit 1 +fi + +source $CONFIG_FILE + +# db_url, db_name ad db_user must be present in the config file +check=1 +check_config db_url +check_config db_name +check_config db_user + +if [ $check = 0 ]; +then + echo "error: Failed to get configs for dr.Elephant. Please check the config file." + exit 1 +fi + +db_loc="jdbc:mysql://"$db_url"/"$db_name"?characterEncoding=UTF-8" + +# db_password is optional. default is "" +db_password="${db_password:-""}" +echo "db_password: " $db_password + +# keytab_user is optional. defalt is "elephant" +keytab_user="${keytab_user:-elephant}" +echo "keytab_user: " $keytab_user + +#keytab_location is optional. +keytab_location="${keytab_location:-/export/apps/hadoop/keytabs/dr_elephant-service.keytab}" +echo "keytab location: " $keytab_location + +#port is optional. default is 8080 +port="${port:-8080}" +echo "http port: " $port + +echo "Starting Dr. Elephant ...." + +# Dr. Elephant executable not found +if [ ! -f bin/dr-elephant ]; +then + echo "error: I couldn't find any dr. Elephant executable." + exit 1 +fi + +# Start Dr. Elaphant +nohup ./bin/dr-elephant -Dhttp.port=$port -Dkeytab.user=$keytab_user -Dkeytab.location=$keytab_location -Ddb.default.url=$db_loc -Ddb.default.user=$db_user -Ddb.default.password=$db_password > /dev/null 2>&1 & + +sleep 1 + +# If Dr. Elephant starts successfully, Play should create a file 'RUNNING_PID' under project root +if [ -f RUNNING_PID ]; +then + echo "Dr. Elephant started." +else + echo "error: Failed to start Dr. Elephant. Please check if this is a valid dr.E executable or logs under 'logs' directory." + exit 1 +fi diff --git a/stop.sh b/stop.sh new file mode 100755 index 000000000..c5505810c --- /dev/null +++ b/stop.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash + +# If file RUNNING_PID exists, it means Dr. Elephant is running +if [ -f RUNNING_PID ]; +then + echo "Dr.Elephant is running." +else + echo "Dr.Elephant is not running." + exit 1 +fi + +# RUNNING_PID contains PID of our Dr. Elephant instance +proc=`cat RUNNING_PID` + +echo "Killing Dr.Elephant...." +kill $proc + +# Wait for a while +sleep 1 + +# Play should remove RUNNING_PID when we kill the running process +if [ ! -f RUNNING_PID ]; +then + echo "Dr.Elephant is killed." +else + echo "Failed to kill Dr.Elephant." + exit 1 +fi From 40ee725f422ad938663b0d1e8b73ef4cbff61695 Mon Sep 17 00:00:00 2001 From: fli Date: Wed, 22 Oct 2014 16:20:18 -0700 Subject: [PATCH 018/272] HADOOP-7636: Dr.E get NPE on hadoop2 if a task in a succeeded job failed --- .../drelephant/ElephantFetcherYarn.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/app/com/linkedin/drelephant/ElephantFetcherYarn.java b/app/com/linkedin/drelephant/ElephantFetcherYarn.java index 6ac662a14..6a8bc1845 100644 --- a/app/com/linkedin/drelephant/ElephantFetcherYarn.java +++ b/app/com/linkedin/drelephant/ElephantFetcherYarn.java @@ -5,6 +5,7 @@ import com.linkedin.drelephant.hadoop.HadoopCounterHolder.CounterName; import com.linkedin.drelephant.hadoop.HadoopJobData; import com.linkedin.drelephant.hadoop.HadoopTaskData; +import com.linkedin.drelephant.math.Statistics; import model.JobResult; @@ -25,6 +26,7 @@ import java.util.List; import java.util.Map; import java.util.Properties; +import java.util.Random; import java.util.concurrent.ConcurrentHashMap; @@ -132,6 +134,7 @@ public void fetchJobData(HadoopJobData jobData) throws IOException, Authenticati HadoopTaskData[] reducerData = reducerList.toArray(new HadoopTaskData[reducerList.size()]); jobData.setCounters(jobCounter).setMapperData(mapperData).setReducerData(reducerData).setJobConf(jobConf); + ThreadContextMR2.updateAuthToken(); } private String getJobDetailURL(String jobId) { @@ -303,6 +306,11 @@ private void getTaskDataAll(URL url, String jobId, List mapperLi JsonNode tasks = rootNode.path("tasks").path("task"); for (JsonNode task : tasks) { + String state = task.get("state").getValueAsText(); + if (!state.equals("SUCCEEDED")) { + // This is a failed task. + continue; + } String taskId = task.get("id").getValueAsText(); String attemptId = task.get("successfulAttempt").getValueAsText(); boolean isMapper = task.get("type").getValueAsText().equals("MAP"); @@ -366,10 +374,14 @@ private void checkAndRemoveFromRetryList(HadoopJobData jobData) { final class ThreadContextMR2 { + private static final Logger logger = Logger.getLogger(ThreadContextMR2.class); + private static final ThreadLocal _LOCAL_THREAD_ID = new ThreadLocal(); private static final ThreadLocal _LOCAL_AUTH_TOKEN = new ThreadLocal(); private static final ThreadLocal _LOCAL_AUTH_URL = new ThreadLocal(); private static final ThreadLocal _LOCAL_MAPPER = new ThreadLocal(); + private static final ThreadLocal _LOCAL_LAST_UPDATED = new ThreadLocal(); + private static final ThreadLocal _LOCAL_UPDATE_INTERVAL = new ThreadLocal(); private ThreadContextMR2() { } @@ -378,10 +390,26 @@ static void init(int threadId) throws IOException { _LOCAL_AUTH_TOKEN.set(new AuthenticatedURL.Token()); _LOCAL_AUTH_URL.set(new AuthenticatedURL()); _LOCAL_MAPPER.set(new ObjectMapper()); + _LOCAL_THREAD_ID.set(threadId); + _LOCAL_LAST_UPDATED.set(System.currentTimeMillis()); + // Random an interval for each executor to avoid update token at the same time + _LOCAL_UPDATE_INTERVAL.set(Statistics.MINUTE_IN_MS * 30 + new Random().nextLong() % (3 * Statistics.MINUTE_IN_MS)); + logger.info("Executor " + _LOCAL_THREAD_ID.get() + " update interval " + _LOCAL_UPDATE_INTERVAL.get() * 1.0 + / Statistics.MINUTE_IN_MS); } public static JsonNode readJsonNode(URL url) throws IOException, AuthenticationException { HttpURLConnection conn = _LOCAL_AUTH_URL.get().openConnection(url, _LOCAL_AUTH_TOKEN.get()); return _LOCAL_MAPPER.get().readTree(conn.getInputStream()); } + + public static void updateAuthToken() { + long curTime = System.currentTimeMillis(); + if (curTime - _LOCAL_LAST_UPDATED.get() > _LOCAL_UPDATE_INTERVAL.get()) { + logger.info("Executor " + _LOCAL_THREAD_ID.get() + " updates its AuthenticatedToken."); + _LOCAL_AUTH_TOKEN.set(new AuthenticatedURL.Token()); + _LOCAL_AUTH_URL.set(new AuthenticatedURL()); + _LOCAL_LAST_UPDATED.set(curTime); + } + } } From 42ee5b5f9fb31afea0fd1751c9a8f37f8efeb094 Mon Sep 17 00:00:00 2001 From: fli Date: Wed, 22 Oct 2014 18:15:26 -0700 Subject: [PATCH 019/272] HADOOP-7580: Rewrite Dr. Elephant's MapperInputSize Heuristic --- .../heuristics/MapperInputSizeHeuristic.java | 59 ++++++--- app/views/helpMapperInputSize.scala.html | 14 +- .../MapperInputSizeHeuristicTest.java | 125 +++++++++++++++--- 3 files changed, 159 insertions(+), 39 deletions(-) diff --git a/app/com/linkedin/drelephant/analysis/heuristics/MapperInputSizeHeuristic.java b/app/com/linkedin/drelephant/analysis/heuristics/MapperInputSizeHeuristic.java index 32774eded..ec14ab1c6 100644 --- a/app/com/linkedin/drelephant/analysis/heuristics/MapperInputSizeHeuristic.java +++ b/app/com/linkedin/drelephant/analysis/heuristics/MapperInputSizeHeuristic.java @@ -1,5 +1,8 @@ package com.linkedin.drelephant.analysis.heuristics; +import java.util.ArrayList; +import java.util.List; + import com.linkedin.drelephant.analysis.Constants; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; @@ -8,6 +11,7 @@ import com.linkedin.drelephant.hadoop.HadoopJobData; import com.linkedin.drelephant.hadoop.HadoopTaskData; import com.linkedin.drelephant.math.Statistics; + import org.apache.commons.io.FileUtils; @@ -23,38 +27,52 @@ public String getHeuristicName() { public HeuristicResult apply(HadoopJobData data) { HadoopTaskData[] tasks = data.getMapperData(); - //Gather data - long[] inputBytes = new long[tasks.length]; + List inputBytes = new ArrayList(); + List runtimesMs = new ArrayList(); - for (int i = 0; i < tasks.length; i++) { - inputBytes[i] = tasks[i].getCounters().get(HadoopCounterHolder.CounterName.HDFS_BYTES_READ); + for (HadoopTaskData task : tasks) { + inputBytes.add(task.getCounters().get(HadoopCounterHolder.CounterName.HDFS_BYTES_READ)); + if (task.timed()) { + runtimesMs.add(task.getTotalRunTimeMs()); + } } - //Analyze data - long average = Statistics.average(inputBytes); + long averageSize = Statistics.average(inputBytes); + long averageTimeMs = Statistics.average(runtimesMs); - Severity smallFilesSeverity = smallFilesSeverity(average, tasks.length); - Severity largeFilesSeverity = largeFilesSeverity(average, tasks.length); + Severity smallFilesSeverity = smallFilesSeverity(averageSize, tasks.length, averageTimeMs); + Severity largeFilesSeverity = largeFilesSeverity(averageSize, tasks.length, averageTimeMs); Severity severity = Severity.max(smallFilesSeverity, largeFilesSeverity); HeuristicResult result = new HeuristicResult(HEURISTIC_NAME, severity); result.addDetail("Number of tasks", Integer.toString(tasks.length)); - result.addDetail("Average task input size", FileUtils.byteCountToDisplaySize(average)); + result.addDetail("Average task input size", FileUtils.byteCountToDisplaySize(averageSize)); + result.addDetail("Average task runtime", Statistics.readableTimespan(averageTimeMs)); return result; } - private Severity smallFilesSeverity(long value, long numTasks) { + private Severity smallFilesSeverity(long value, long numTasks, long averageTimeMs) { + // We want to identify jobs with small task input, large number of tasks, and low task runtime Severity severity = getSmallFilesSeverity(value); - Severity taskSeverity = getNumTasksSeverity(numTasks); - return Severity.min(severity, taskSeverity); + // Severity is reduced if number of tasks is small + Severity numTaskSeverity = getNumTasksSeverity(numTasks); + severity = Severity.min(severity, numTaskSeverity); + // Severity is reduced if task runtime is long + Severity runtimeSeverity = getRuntimeSeverityReverse(averageTimeMs); + return Severity.min(severity, runtimeSeverity); } - private Severity largeFilesSeverity(long value, long numTasks) { + private Severity largeFilesSeverity(long value, long numTasks, long averageTimeMs) { + // We want to identify jobs with large task input, small number of tasks, and long task runtime Severity severity = getLargeFilesSeverity(value); - Severity taskSeverity = getNumTasksSeverityReverse(numTasks); - return Severity.min(severity, taskSeverity); + // Severity is reduced if number of tasks is large + Severity numTaskSeverity = getNumTasksSeverityReverse(numTasks); + severity = Severity.min(severity, numTaskSeverity); + // Severity is reduced if task runtime is short + Severity runtimeSeverity = getRuntimeSeverity(averageTimeMs); + return Severity.min(severity, runtimeSeverity); } public static Severity getSmallFilesSeverity(long value) { @@ -74,4 +92,15 @@ public static Severity getNumTasksSeverity(long numTasks) { public static Severity getNumTasksSeverityReverse(long numTasks) { return Severity.getSeverityDescending(numTasks, 1000, 500, 200, 100); } + + public static Severity getRuntimeSeverity(long runtimeMs) { + return Severity.getSeverityAscending(runtimeMs, 10 * Statistics.MINUTE_IN_MS, 15 * Statistics.MINUTE_IN_MS, + 20 * Statistics.MINUTE_IN_MS, 30 * Statistics.MINUTE_IN_MS); + } + + public static Severity getRuntimeSeverityReverse(long runtimeMs) { + return Severity.getSeverityDescending(runtimeMs, 5 * Statistics.MINUTE_IN_MS, 4 * Statistics.MINUTE_IN_MS, + 3 * Statistics.MINUTE_IN_MS, 2 * Statistics.MINUTE_IN_MS); + } + } diff --git a/app/views/helpMapperInputSize.scala.html b/app/views/helpMapperInputSize.scala.html index 1828fa102..2980737d1 100644 --- a/app/views/helpMapperInputSize.scala.html +++ b/app/views/helpMapperInputSize.scala.html @@ -8,7 +8,8 @@

Too many small files

This usually happens when the Hadoop job has:
  • A large number of mappers
  • -
  • Small file size (in the KBs)
  • +
  • Small file size
  • +
  • Short mapper runtime

Example
@@ -24,9 +25,13 @@

Mapper Input Size

- + + + + +
916
Average task inputAverage task input size 19 KB
Average task runtime1min 32s
@@ -46,6 +51,7 @@

Large files/Unsplittable files

  • A small number of mappers
  • Large file size (a few GB's)
  • +
  • Long mapper runtime

Example
@@ -64,6 +70,10 @@

Mapper Input Size

Average task input 3 GB + + Average task runtime + 32 min 30s + diff --git a/test/com/linkedin/drelephant/analysis/heuristics/MapperInputSizeHeuristicTest.java b/test/com/linkedin/drelephant/analysis/heuristics/MapperInputSizeHeuristicTest.java index 46a83e8a6..8d4d4f65b 100644 --- a/test/com/linkedin/drelephant/analysis/heuristics/MapperInputSizeHeuristicTest.java +++ b/test/com/linkedin/drelephant/analysis/heuristics/MapperInputSizeHeuristicTest.java @@ -11,6 +11,7 @@ import com.linkedin.drelephant.hadoop.HadoopCounterHolder.CounterName; import com.linkedin.drelephant.hadoop.HadoopJobData; import com.linkedin.drelephant.hadoop.HadoopTaskData; +import com.linkedin.drelephant.math.Statistics; import junit.framework.TestCase; @@ -20,47 +21,127 @@ public class MapperInputSizeHeuristicTest extends TestCase { private static final long UNITSIZE = Constants.HDFS_BLOCK_SIZE; Heuristic _heuristic = new MapperInputSizeHeuristic(); - public void testLargeFileCritical() throws IOException { - assertEquals(Severity.CRITICAL, analyzeJob(100, 5 * UNITSIZE)); + // Test batch 1: Large file test with large runtime and various number of tasks */ + + public void testLargeFileNumTasksCritical() throws IOException { + // Should decrease file size and increase number of tasks + assertEquals(Severity.CRITICAL, analyzeJob(10, 5 * UNITSIZE, 60 * Statistics.MINUTE_IN_MS)); + } + + public void testLargeFileNumTasksSevere() throws IOException { + // Should decrease file size and increase number of tasks + assertEquals(Severity.SEVERE, analyzeJob(200, 5 * UNITSIZE, 60 * Statistics.MINUTE_IN_MS)); + } + + public void testLargeFileNumTasksModerate() throws IOException { + assertEquals(Severity.MODERATE, analyzeJob(500, 5 * UNITSIZE, 60 * Statistics.MINUTE_IN_MS)); } - public void testLargeFileSevere() throws IOException { - assertEquals(Severity.SEVERE, analyzeJob(200, 5 * UNITSIZE)); + public void testLargeFileNumTasksLow() throws IOException { + assertEquals(Severity.LOW, analyzeJob(1000, 5 * UNITSIZE, 60 * Statistics.MINUTE_IN_MS)); } - public void testLargeFileModerate() throws IOException { - assertEquals(Severity.MODERATE, analyzeJob(500, 5 * UNITSIZE)); + public void testLargeFileNumTasksNone() throws IOException { + // Large file with large number of tasks and long runtime. + // Looks horrible but nothing we could suggest in this case (Probably suggest something other than hadoop?) + assertEquals(Severity.NONE, analyzeJob(2000, 5 * UNITSIZE, 60 * Statistics.MINUTE_IN_MS)); } - public void testLargeFileLow() throws IOException { - assertEquals(Severity.LOW, analyzeJob(1000, 5 * UNITSIZE)); + // Test batch 2: Large file test with various runtime and small number of tasks + + public void testLargeFileRuntimeCritical() throws IOException { + // Should decrease file size and increase number of tasks + assertEquals(Severity.CRITICAL, analyzeJob(100, 5 * UNITSIZE, 60 * Statistics.MINUTE_IN_MS)); + } + + public void testLargeFileRuntimeSevere() throws IOException { + // Should decrease file size and increase number of tasks + assertEquals(Severity.SEVERE, analyzeJob(100, 5 * UNITSIZE, 20 * Statistics.MINUTE_IN_MS)); + } + + public void testLargeFileRuntimeModerate() throws IOException { + assertEquals(Severity.MODERATE, analyzeJob(100, 5 * UNITSIZE, 15 * Statistics.MINUTE_IN_MS)); + } + + public void testLargeFileRuntimeLow() throws IOException { + assertEquals(Severity.LOW, analyzeJob(100, 5 * UNITSIZE, 10 * Statistics.MINUTE_IN_MS)); + } + + public void testLargeFileRuntimeNone() throws IOException { + // Large file, but already has small number of tasks and small runtime. Looks good. + assertEquals(Severity.NONE, analyzeJob(100, 5 * UNITSIZE, 5 * Statistics.MINUTE_IN_MS)); + } + + // Test batch 3: Small file test with short runtime and various number of tasks + + public void testSmallFileNumTasksCritical() throws IOException { + // Should increase file size and decrease number of tasks + assertEquals(Severity.CRITICAL, analyzeJob(1000, UNITSIZE / 32, 2 * Statistics.MINUTE_IN_MS)); } - public void testLargeFileNone() throws IOException { - assertEquals(Severity.NONE, analyzeJob(2000, 5 * UNITSIZE)); + public void testSmallFileNumTasksSevere() throws IOException { + // Should increase file size and decrease number of tasks + assertEquals(Severity.SEVERE, analyzeJob(200, UNITSIZE / 32, 2 * Statistics.MINUTE_IN_MS)); } - public void testSmallFileCritical() throws IOException { - assertEquals(Severity.CRITICAL, analyzeJob(500, UNITSIZE / 32)); + public void testSmallFileNumTasksModerate() throws IOException { + assertEquals(Severity.MODERATE, analyzeJob(50, UNITSIZE / 32, 2 * Statistics.MINUTE_IN_MS)); } - public void testSmallFileSevere() throws IOException { - assertEquals(Severity.SEVERE, analyzeJob(200, UNITSIZE / 32)); + public void testSmallFileNumTasksLow() throws IOException { + assertEquals(Severity.LOW, analyzeJob(10, UNITSIZE / 32, 2 * Statistics.MINUTE_IN_MS)); + } + + public void testSmallFileNumTasksNone() throws IOException { + // Small file with small number of tasks and short runtime. This should be the common case. + assertEquals(Severity.NONE, analyzeJob(5, UNITSIZE / 32, 2 * Statistics.MINUTE_IN_MS)); + } + + // Test batch 4: Small file test with large number of tasks and various runtime + + public void testRuntimeSmallFileCritical() throws IOException { + // Should increase file size and decrease number of tasks + assertEquals(Severity.CRITICAL, analyzeJob(500, UNITSIZE / 32, 1 * Statistics.MINUTE_IN_MS)); + } + + public void testRuntimeSmallFileSevere() throws IOException { + // Should increase file size and decrease number of tasks + assertEquals(Severity.SEVERE, analyzeJob(500, UNITSIZE / 32, 3 * Statistics.MINUTE_IN_MS)); + } + + public void testRuntimeSmallFileModerate() throws IOException { + assertEquals(Severity.MODERATE, analyzeJob(500, UNITSIZE / 32, 4 * Statistics.MINUTE_IN_MS)); + } + + public void testRuntimeSmallFileLow() throws IOException { + assertEquals(Severity.LOW, analyzeJob(500, UNITSIZE / 32, 5 * Statistics.MINUTE_IN_MS)); + } + + public void testRuntimeSmallFileNone() throws IOException { + // Small file with large number of tasks and long runtime. This should be a CPU-bound job + // Probably not a good idea to suggest file size increase(even longer runtime) or decrease(even more mappers) + assertEquals(Severity.NONE, analyzeJob(500, UNITSIZE / 32, 10 * Statistics.MINUTE_IN_MS)); + } + + // Test batch 5: Test with normal file size (one HDFS block). This should be a common case + + public void testNormalFileSmallNumTasks() throws IOException { + assertEquals(Severity.NONE, analyzeJob(10, UNITSIZE, 10 * Statistics.MINUTE_IN_MS)); } - public void testSmallFileModerate() throws IOException { - assertEquals(Severity.MODERATE, analyzeJob(50, UNITSIZE / 32)); + public void testNormalFileLargeNumTasks() throws IOException { + assertEquals(Severity.NONE, analyzeJob(300, UNITSIZE, 10 * Statistics.MINUTE_IN_MS)); } - public void testSmallFileLow() throws IOException { - assertEquals(Severity.LOW, analyzeJob(10, UNITSIZE / 32)); + public void testNormalFileLongRuntime() throws IOException { + assertEquals(Severity.NONE, analyzeJob(100, UNITSIZE, 30 * Statistics.MINUTE_IN_MS)); } - public void testSmallFileNone() throws IOException { - assertEquals(Severity.NONE, analyzeJob(5, UNITSIZE / 32)); + public void testNormalFileShortRuntime() throws IOException { + assertEquals(Severity.NONE, analyzeJob(100, UNITSIZE, 1 * Statistics.MINUTE_IN_MS)); } - private Severity analyzeJob(int numTasks, long inputSize) throws IOException { + private Severity analyzeJob(int numTasks, long inputSize, long runtime) throws IOException { HadoopCounterHolder jobCounter = new HadoopCounterHolder(null); HadoopTaskData[] mappers = new HadoopTaskData[numTasks]; @@ -69,7 +150,7 @@ private Severity analyzeJob(int numTasks, long inputSize) throws IOException { int i = 0; for (; i < numTasks; i++) { - mappers[i] = new HadoopTaskData(taskCounter, new long[3]); + mappers[i] = new HadoopTaskData(taskCounter, new long[] { runtime, 0, 0 }); } HadoopJobData data = new HadoopJobData().setCounters(jobCounter).setMapperData(mappers); From d35c112c3f4c4d50d831920bb363dcb77fd0e179 Mon Sep 17 00:00:00 2001 From: fli Date: Thu, 23 Oct 2014 10:06:33 -0700 Subject: [PATCH 020/272] HADOOP-7642: Release Dr.E v0.6 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 42daa8bfd..efd9bfb4b 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ import play.Project._ name := "dr-elephant" -version := "0.6-SNAPSHOT" +version := "0.6" javacOptions in Compile ++= Seq("-source", "1.6", "-target", "1.6") From 4d0e02979b0e9e66fdb15f20e57f3502c4188b2d Mon Sep 17 00:00:00 2001 From: fli Date: Thu, 23 Oct 2014 13:24:27 -0700 Subject: [PATCH 021/272] HADOOP-7477: Deploy dr.elephant v0.6 on nertz --- start.sh | 15 +++++++++++++++ stop.sh | 6 ++++++ 2 files changed, 21 insertions(+) diff --git a/start.sh b/start.sh index 0750f3077..77f91fae6 100755 --- a/start.sh +++ b/start.sh @@ -14,6 +14,11 @@ function check_config(){ fi } +# Save project root dir +script_dir=`which $0` +script_dir=`dirname $script_dir` +project_root=$script_dir + # User must give an argument(config file path) when running this script if [ -z "$1" ]; then @@ -66,6 +71,16 @@ echo "http port: " $port echo "Starting Dr. Elephant ...." +# Navigate to project root +cd $project_root + +# Check if Dr. Elephant already started +if [ -f RUNNING_PID ]; +then + echo "error: Dr. Elephant already started!" + exit 1 +fi + # Dr. Elephant executable not found if [ ! -f bin/dr-elephant ]; then diff --git a/stop.sh b/stop.sh index c5505810c..f5cd2ee89 100755 --- a/stop.sh +++ b/stop.sh @@ -1,5 +1,11 @@ #!/usr/bin/env bash +# Navigate to project root dir +script_dir=`which $0` +script_dir=`dirname $script_dir` +project_root=$script_dir +cd $project_root + # If file RUNNING_PID exists, it means Dr. Elephant is running if [ -f RUNNING_PID ]; then From 221f28f4c6d562f8798a3815e082526fadb45e99 Mon Sep 17 00:00:00 2001 From: fli Date: Wed, 5 Nov 2014 18:47:48 -0800 Subject: [PATCH 022/272] HADOOP-7810: Update Dr.Elephant version number to 0.6.1-SNAPSHOT --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index efd9bfb4b..b2967c497 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ import play.Project._ name := "dr-elephant" -version := "0.6" +version := "0.6.1-SNAPSHOT" javacOptions in Compile ++= Seq("-source", "1.6", "-target", "1.6") From 16022d192eb9bbcd35ea53629beee78a9ecd9cdd Mon Sep 17 00:00:00 2001 From: Akshay Rai Date: Mon, 10 Nov 2014 20:53:07 -0800 Subject: [PATCH 023/272] HADOOP-7581: Fix for filtering jobs by Start Date in Hadoop 2 --- app/com/linkedin/drelephant/ElephantFetcherYarn.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/com/linkedin/drelephant/ElephantFetcherYarn.java b/app/com/linkedin/drelephant/ElephantFetcherYarn.java index 6a8bc1845..a154cf96b 100644 --- a/app/com/linkedin/drelephant/ElephantFetcherYarn.java +++ b/app/com/linkedin/drelephant/ElephantFetcherYarn.java @@ -98,14 +98,14 @@ private void clearJobData(HadoopJobData jobData) { // OnJobFinish Add to retry list upon failure public void finishJob(HadoopJobData jobData, boolean success) { if (!success) { - if(!jobData.isRetryJob()) { + if (!jobData.isRetryJob()) { jobData.setRetry(true); } clearJobData(jobData); // Add to retry list _retryFactory.addJobToRetryList(jobData); } else { - if(jobData.isRetryJob()) { + if (jobData.isRetryJob()) { // If it is retry job, remove it from retry map _retryFactory.checkAndRemoveFromRetryList(jobData); } @@ -214,7 +214,8 @@ private List getJobData(URL url, boolean checkDB) throws IOExcept // New job HadoopJobData jobData = new HadoopJobData(); jobData.setJobId(jobId).setUsername(job.get("user").getValueAsText()) - .setJobName(job.get("name").getValueAsText()).setUrl(getJobDetailURL(jobId)); + .setJobName(job.get("name").getValueAsText()).setUrl(getJobDetailURL(jobId)) + .setStartTime(job.get("startTime").getLongValue()); jobList.add(jobData); } @@ -289,11 +290,11 @@ private long[] getTaskExecTime(URL url) throws IOException, AuthenticationExcept long[] time; if (isMapper) { // No shuffle sore time in Mapper - time = new long[] { finishTime-startTime, 0, 0 }; + time = new long[] { finishTime - startTime, 0, 0 }; } else { long shuffleTime = taskAttempt.get("elapsedShuffleTime").getLongValue(); long sortTime = taskAttempt.get("elapsedMergeTime").getLongValue(); - time = new long[] { finishTime-startTime, shuffleTime, sortTime }; + time = new long[] { finishTime - startTime, shuffleTime, sortTime }; } return time; @@ -372,7 +373,6 @@ private void checkAndRemoveFromRetryList(HadoopJobData jobData) { } } - final class ThreadContextMR2 { private static final Logger logger = Logger.getLogger(ThreadContextMR2.class); private static final ThreadLocal _LOCAL_THREAD_ID = new ThreadLocal(); From c54ccc6fdac06a13977f3384c18816c292a6dbaa Mon Sep 17 00:00:00 2001 From: fli Date: Sun, 9 Nov 2014 23:33:53 -0800 Subject: [PATCH 024/272] HADOOP-7852: Dr. Elephant needs to periodically re-authenticate with job history server on hadoop2 --- .../drelephant/ElephantFetcherClassic.java | 119 +++++++++--------- .../drelephant/ElephantFetcherYarn.java | 45 ++++--- .../linkedin/drelephant/ElephantRunner.java | 42 ++++--- conf/log4j.properties | 2 +- 4 files changed, 116 insertions(+), 92 deletions(-) diff --git a/app/com/linkedin/drelephant/ElephantFetcherClassic.java b/app/com/linkedin/drelephant/ElephantFetcherClassic.java index 5cda6ca34..7d0cce136 100644 --- a/app/com/linkedin/drelephant/ElephantFetcherClassic.java +++ b/app/com/linkedin/drelephant/ElephantFetcherClassic.java @@ -43,6 +43,7 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; + public class ElephantFetcherClassic implements ElephantFetcher { private static final Logger logger = Logger.getLogger(ElephantFetcher.class); private static final int DEFAULT_RETRY = 2; @@ -94,12 +95,12 @@ public List fetchJobList() throws IOException { public void finishJob(HadoopJobData jobData, boolean success) { if (success) { // Job succeeded, remove from retry list if this is a retry job - if(jobData.isRetryJob()) { + if (jobData.isRetryJob()) { checkAndRemoveJobFromRetryList(jobData); } } else { // Job failed, clear job data and add to retry list - if(!jobData.isRetryJob()) { + if (!jobData.isRetryJob()) { jobData.setRetry(true); } clearJobData(jobData); @@ -108,67 +109,69 @@ public void finishJob(HadoopJobData jobData, boolean success) { } public void fetchJobData(HadoopJobData jobData) throws IOException, AuthenticationException { - - JobID jobId = JobID.forName(jobData.getJobId()); - JobClient jobClient = ThreadContextMR1.getClient(); - TaskReport[] mapperTasks = null; - TaskReport[] reducerTasks = null; - RunningJob job = jobClient.getJob(jobId); - - if (job != null) { - mapperTasks = jobClient.getMapTaskReports(jobId); - reducerTasks = jobClient.getReduceTaskReports(jobId); - } - - if (job == null || (mapperTasks.length == 0 && reducerTasks.length == 0)) { - boolean retired = checkRetiredAndFetchJobData(jobData); - if (retired) { - // This is a retired job - logger.info(jobData.getJobId() + " retired. Fetch data from web UI."); - ThreadContextMR1.updateAuthToken(); - return; + try { + JobID jobId = JobID.forName(jobData.getJobId()); + JobClient jobClient = ThreadContextMR1.getClient(); + TaskReport[] mapperTasks = null; + TaskReport[] reducerTasks = null; + RunningJob job = jobClient.getJob(jobId); + + if (job != null) { + mapperTasks = jobClient.getMapTaskReports(jobId); + reducerTasks = jobClient.getReduceTaskReports(jobId); } - // This is a no data job - } - - JobStatus status = job.getJobStatus(); - String username = status.getUsername(); - long startTime = status.getStartTime(); - String jobUrl = job.getTrackingURL(); - String jobName = job.getJobName(); - jobData.setUsername(username).setStartTime(startTime).setUrl(jobUrl).setJobName(jobName); - // Fetch job counter - HadoopCounterHolder counterHolder = fetchCounter(job.getCounters()); - - int sampleSize = Constants.SHUFFLE_SORT_MAX_SAMPLE_SIZE; + if (job == null || (mapperTasks.length == 0 && reducerTasks.length == 0)) { + boolean retired = checkRetiredAndFetchJobData(jobData); + if (retired) { + // This is a retired job + logger.info(jobData.getJobId() + " retired. Fetch data from web UI."); + return; + } + // This is a no data job + } - // Fetch mapper task data - List mapperList = new ArrayList(); - Statistics.shuffleArraySample(mapperTasks, sampleSize); - for (int i = 0; i < mapperTasks.length; i++) { - HadoopTaskData mapper = fetchTaskData(jobUrl, mapperTasks[i], true, (i < sampleSize)); - if (mapper != null) { - mapperList.add(mapper); + JobStatus status = job.getJobStatus(); + String username = status.getUsername(); + long startTime = status.getStartTime(); + String jobUrl = job.getTrackingURL(); + String jobName = job.getJobName(); + jobData.setUsername(username).setStartTime(startTime).setUrl(jobUrl).setJobName(jobName); + + // Fetch job counter + HadoopCounterHolder counterHolder = fetchCounter(job.getCounters()); + + int sampleSize = Constants.SHUFFLE_SORT_MAX_SAMPLE_SIZE; + + // Fetch mapper task data + List mapperList = new ArrayList(); + Statistics.shuffleArraySample(mapperTasks, sampleSize); + for (int i = 0; i < mapperTasks.length; i++) { + HadoopTaskData mapper = fetchTaskData(jobUrl, mapperTasks[i], true, (i < sampleSize)); + if (mapper != null) { + mapperList.add(mapper); + } } - } - HadoopTaskData[] mappers = mapperList.toArray(new HadoopTaskData[mapperList.size()]); - - // Fetch reducer task data - List reducerList = new ArrayList(); - Statistics.shuffleArraySample(reducerTasks, sampleSize); - for (int i = 0; i < reducerTasks.length; i++) { - HadoopTaskData reducer = fetchTaskData(jobUrl, reducerTasks[i], false, (i < sampleSize)); - if (reducer != null) { - reducerList.add(reducer); + HadoopTaskData[] mappers = mapperList.toArray(new HadoopTaskData[mapperList.size()]); + + // Fetch reducer task data + List reducerList = new ArrayList(); + Statistics.shuffleArraySample(reducerTasks, sampleSize); + for (int i = 0; i < reducerTasks.length; i++) { + HadoopTaskData reducer = fetchTaskData(jobUrl, reducerTasks[i], false, (i < sampleSize)); + if (reducer != null) { + reducerList.add(reducer); + } } - } - HadoopTaskData[] reducers = reducerList.toArray(new HadoopTaskData[reducerList.size()]); + HadoopTaskData[] reducers = reducerList.toArray(new HadoopTaskData[reducerList.size()]); - // Fetch job config - Properties jobConf = fetchJobConf(job); - jobData.setCounters(counterHolder).setMapperData(mappers).setReducerData(reducers).setJobConf(jobConf); - ThreadContextMR1.updateAuthToken(); + // Fetch job config + Properties jobConf = fetchJobConf(job); + jobData.setCounters(counterHolder).setMapperData(mappers).setReducerData(reducers).setJobConf(jobConf); + + } finally { + ThreadContextMR1.updateAuthToken(); + } } private Properties fetchJobConf(RunningJob job) throws IOException, AuthenticationException { @@ -356,7 +359,7 @@ private List fetchAllFailedJobs() { private void checkAndRemoveJobFromRetryList(HadoopJobData jobData) { if (_failedJobsInProgress.containsKey(jobData)) { - logger.info(jobData.getJobId()+" succeeded on retry."); + logger.info(jobData.getJobId() + " succeeded on retry."); _failedJobsInProgress.remove(jobData); } } diff --git a/app/com/linkedin/drelephant/ElephantFetcherYarn.java b/app/com/linkedin/drelephant/ElephantFetcherYarn.java index a154cf96b..cd162eaff 100644 --- a/app/com/linkedin/drelephant/ElephantFetcherYarn.java +++ b/app/com/linkedin/drelephant/ElephantFetcherYarn.java @@ -65,9 +65,14 @@ public List fetchJobList() throws IOException, AuthenticationExce int retrySize = 0; _currentTime = System.currentTimeMillis(); - URL joblistURL = _urlFactory.fetchJobListURL(_lastTime, _currentTime); + URL joblistURL= _urlFactory.fetchJobListURL(_lastTime, _currentTime); + + try { + jobList = _jsonFactory.getJobData(joblistURL, _firstRun); + } finally { + ThreadContextMR2.updateAuthToken(); + } - jobList = _jsonFactory.getJobData(joblistURL, _firstRun); if (_firstRun) { _firstRun = false; } else { @@ -114,27 +119,31 @@ public void finishJob(HadoopJobData jobData, boolean success) { // Fetch job detailed data. Return true on success public void fetchJobData(HadoopJobData jobData) throws IOException, AuthenticationException { - String jobId = jobData.getJobId(); + try { + String jobId = jobData.getJobId(); - // Fetch job counter - URL jobCounterURL = _urlFactory.getJobCounterURL(jobId); - HadoopCounterHolder jobCounter = _jsonFactory.getJobCounter(jobCounterURL); + // Fetch job counter + URL jobCounterURL = _urlFactory.getJobCounterURL(jobId); + HadoopCounterHolder jobCounter = _jsonFactory.getJobCounter(jobCounterURL); - // Fetch job config - URL jobConfigURL = _urlFactory.getJobConfigURL(jobId); - Properties jobConf = _jsonFactory.getProperties(jobConfigURL); + // Fetch job config + URL jobConfigURL = _urlFactory.getJobConfigURL(jobId); + Properties jobConf = _jsonFactory.getProperties(jobConfigURL); - // Fetch task data - URL taskListURL = _urlFactory.getTaskListURL(jobId); - List mapperList = new ArrayList(); - List reducerList = new ArrayList(); - _jsonFactory.getTaskDataAll(taskListURL, jobId, mapperList, reducerList); + // Fetch task data + URL taskListURL = _urlFactory.getTaskListURL(jobId); + List mapperList = new ArrayList(); + List reducerList = new ArrayList(); + _jsonFactory.getTaskDataAll(taskListURL, jobId, mapperList, reducerList); - HadoopTaskData[] mapperData = mapperList.toArray(new HadoopTaskData[mapperList.size()]); - HadoopTaskData[] reducerData = reducerList.toArray(new HadoopTaskData[reducerList.size()]); + HadoopTaskData[] mapperData = mapperList.toArray(new HadoopTaskData[mapperList.size()]); + HadoopTaskData[] reducerData = reducerList.toArray(new HadoopTaskData[reducerList.size()]); - jobData.setCounters(jobCounter).setMapperData(mapperData).setReducerData(reducerData).setJobConf(jobConf); - ThreadContextMR2.updateAuthToken(); + jobData.setCounters(jobCounter).setMapperData(mapperData).setReducerData(reducerData).setJobConf(jobConf); + + } finally { + ThreadContextMR2.updateAuthToken(); + } } private String getJobDetailURL(String jobId) { diff --git a/app/com/linkedin/drelephant/ElephantRunner.java b/app/com/linkedin/drelephant/ElephantRunner.java index 7488d458b..b1023a08f 100644 --- a/app/com/linkedin/drelephant/ElephantRunner.java +++ b/app/com/linkedin/drelephant/ElephantRunner.java @@ -30,6 +30,7 @@ public class ElephantRunner implements Runnable { private static final int EXECUTOR_NUM = 3; private static final Logger logger = Logger.getLogger(ElephantRunner.class); private AtomicBoolean _running = new AtomicBoolean(true); + private long lastRun; private EmailThread _emailer = new EmailThread(); private HadoopSecurity _hadoopSecurity; private InfoExtractor _urlRetriever = new InfoExtractor(); @@ -46,7 +47,6 @@ public void run() { public Void run() { Constants.load(); _emailer.start(); - long lastRun; ElephantFetcher fetcher = null; try { @@ -96,7 +96,7 @@ public Void run() { return null; } - while (_running.get()) { + while (_running.get() && !Thread.currentThread().isInterrupted()) { lastRun = System.currentTimeMillis(); logger.info("Fetching job list....."); @@ -105,6 +105,8 @@ public Void run() { _hadoopSecurity.checkLogin(); } catch (IOException e) { logger.info("Error with hadoop kerberos login", e); + //Wait for a while before retry + waitInterval(); continue; } @@ -113,24 +115,18 @@ public Void run() { successJobs = fetcher.fetchJobList(); } catch (Exception e) { logger.error("Error fetching job list. Try again later...", e); + //Wait for a while before retry + waitInterval(); continue; } _jobQueue.addAll(successJobs); logger.info("Job queue size is " + _jobQueue.size()); - // Wait for long enough - long nextRun = lastRun + WAIT_INTERVAL; - long waitTime = nextRun - System.currentTimeMillis(); - while (_running.get() && waitTime > 0) { - try { - Thread.sleep(waitTime); - } catch (InterruptedException e) { - logger.error("Runner thread interrupted.", e); - } - waitTime = nextRun - System.currentTimeMillis(); - } + //Wait for a while before next fetch + waitInterval(); } + logger.info("Main thread is terminated."); return null; } }); @@ -158,7 +154,7 @@ public void run() { } catch (IOException e) { logger.error("Error initialize fetcher in executor " + _threadId, e); } - while (!Thread.currentThread().isInterrupted()) { + while (_running.get() && !Thread.currentThread().isInterrupted()) { HadoopJobData jobData = null; try { jobData = _jobQueue.take(); @@ -166,13 +162,13 @@ public void run() { analyzeJob(jobData, _threadId); _fetcher.finishJob(jobData, true); } catch (InterruptedException ex) { - logger.info("Executor Thread is interrupted and terminated."); Thread.currentThread().interrupt(); } catch (Exception e) { logger.error("Error analyzing " + jobData.getJobId(), e); _fetcher.finishJob(jobData, false); } } + logger.info("Executor Thread" + _threadId + " is terminated."); } } @@ -222,6 +218,22 @@ private void analyzeJob(HadoopJobData jobData, int execThreadNum) { _emailer.enqueue(result); } + private void waitInterval() { + // Wait for long enough + long nextRun = lastRun + WAIT_INTERVAL; + long waitTime = nextRun - System.currentTimeMillis(); + + if (waitTime <= 0) { + return; + } + + try { + Thread.sleep(waitTime); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + public void kill() { _running.set(false); _emailer.kill(); diff --git a/conf/log4j.properties b/conf/log4j.properties index 6ccdde941..cf1833a47 100644 --- a/conf/log4j.properties +++ b/conf/log4j.properties @@ -3,7 +3,7 @@ log4j.rootLogger = INFO, FA #File Appender log4j.appender.FA=org.apache.log4j.DailyRollingFileAppender -log4j.appender.FA.File=logs/dr_elephant.log +log4j.appender.FA.File=../logs/dr_elephant.log log4j.appender.FA.layout=org.apache.log4j.PatternLayout log4j.appender.FA.layout.ConversionPattern=%d{MM-dd-yyyy HH:mm:ss} %-5p %c %x: %m%n log4j.appender.FA.DatePattern='.'yyyy-MM-dd From f4a63cf74f5a5721db0d60152de6cc99fa282562 Mon Sep 17 00:00:00 2001 From: fli Date: Mon, 10 Nov 2014 17:53:02 -0800 Subject: [PATCH 025/272] HADOOP-7809: Cluster deployment for Dr. Elephant Part II --- compile.sh | 8 ++++---- start.sh | 18 +++++++++--------- stop.sh | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/compile.sh b/compile.sh index a7b36b50e..cb3acf423 100755 --- a/compile.sh +++ b/compile.sh @@ -25,9 +25,9 @@ sed -i.bak $'/declare -r app_classpath/s/.$/:$HADOOP_HOME\/*:$HADOOP_HOME\/lib\/ chmod +x $DIST_NAME/bin/dr-elephant -cp $start_script $DIST_NAME/ +cp $start_script $DIST_NAME/bin/ -cp $stop_script $DIST_NAME/ +cp $stop_script $DIST_NAME/bin/ zip -r $DIST_NAME-h1.zip $DIST_NAME -x $DIST_NAME/*.zip *.zip tmp/\* * @@ -45,9 +45,9 @@ sed -i.bak $'/declare -r app_classpath/s/.$/:$HADOOP_HOME\/share\/hadoop\/common chmod +x $DIST_NAME/bin/dr-elephant -cp $start_script $DIST_NAME/ +cp $start_script $DIST_NAME/bin -cp $stop_script $DIST_NAME/ +cp $stop_script $DIST_NAME/bin zip -r $DIST_NAME-h2.zip $DIST_NAME -x $DIST_NAME/*.zip *.zip tmp/\* * diff --git a/start.sh b/start.sh index 77f91fae6..7d5f0695f 100755 --- a/start.sh +++ b/start.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash function print_usage(){ - echo "usage: ./start.sh PATH_TO_CONFIG_FILE" + echo "usage: ./start.sh PATH_TO_CONFIG_FILE(optional)" } function check_config(){ @@ -17,22 +17,23 @@ function check_config(){ # Save project root dir script_dir=`which $0` script_dir=`dirname $script_dir` -project_root=$script_dir +project_root=$script_dir/../ -# User must give an argument(config file path) when running this script +# User could give an optional argument(config file path) or we will provide a default one if [ -z "$1" ]; then - print_usage - exit 1 + echo "Using default config file: /export/apps/elephant/conf/elephant.conf" + CONFIG_FILE="/export/apps/elephant/conf/elephant.conf" +else + CONFIG_FILE=$1 fi -CONFIG_FILE=$1 - # User must give a valid file as argument if [ -f $CONFIG_FILE ]; then echo "Reading from config file..." else + echo "error: Couldn't find a valid config file at: " $CONFIG_FILE print_usage exit 1 fi @@ -55,7 +56,6 @@ db_loc="jdbc:mysql://"$db_url"/"$db_name"?characterEncoding=UTF-8" # db_password is optional. default is "" db_password="${db_password:-""}" -echo "db_password: " $db_password # keytab_user is optional. defalt is "elephant" keytab_user="${keytab_user:-elephant}" @@ -91,7 +91,7 @@ fi # Start Dr. Elaphant nohup ./bin/dr-elephant -Dhttp.port=$port -Dkeytab.user=$keytab_user -Dkeytab.location=$keytab_location -Ddb.default.url=$db_loc -Ddb.default.user=$db_user -Ddb.default.password=$db_password > /dev/null 2>&1 & -sleep 1 +sleep 2 # If Dr. Elephant starts successfully, Play should create a file 'RUNNING_PID' under project root if [ -f RUNNING_PID ]; diff --git a/stop.sh b/stop.sh index f5cd2ee89..77be18a29 100755 --- a/stop.sh +++ b/stop.sh @@ -3,7 +3,7 @@ # Navigate to project root dir script_dir=`which $0` script_dir=`dirname $script_dir` -project_root=$script_dir +project_root=$script_dir/../ cd $project_root # If file RUNNING_PID exists, it means Dr. Elephant is running From 8b513d7309df9f0521264bb2726f8c0df20c7230 Mon Sep 17 00:00:00 2001 From: fli Date: Tue, 11 Nov 2014 16:39:51 -0800 Subject: [PATCH 026/272] HADOOP-7885: Release Dr. Elephant v0.6.1 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index b2967c497..250f05e39 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ import play.Project._ name := "dr-elephant" -version := "0.6.1-SNAPSHOT" +version := "0.6.1" javacOptions in Compile ++= Seq("-source", "1.6", "-target", "1.6") From 0fd5501d7e93df1e23b155dad4ce215771897eb8 Mon Sep 17 00:00:00 2001 From: fli Date: Tue, 11 Nov 2014 17:53:27 -0800 Subject: [PATCH 027/272] HADOOP-7651: Update log location in Dr. Elephant v0.6.1 --- conf/log4j.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/log4j.properties b/conf/log4j.properties index cf1833a47..33869c139 100644 --- a/conf/log4j.properties +++ b/conf/log4j.properties @@ -3,7 +3,7 @@ log4j.rootLogger = INFO, FA #File Appender log4j.appender.FA=org.apache.log4j.DailyRollingFileAppender -log4j.appender.FA.File=../logs/dr_elephant.log +log4j.appender.FA.File=../logs/elephant/dr_elephant.log log4j.appender.FA.layout=org.apache.log4j.PatternLayout log4j.appender.FA.layout.ConversionPattern=%d{MM-dd-yyyy HH:mm:ss} %-5p %c %x: %m%n log4j.appender.FA.DatePattern='.'yyyy-MM-dd From f2a8875d5ec426a2cab5fbc2fe8ed413952de369 Mon Sep 17 00:00:00 2001 From: fli Date: Thu, 13 Nov 2014 11:47:30 -0800 Subject: [PATCH 028/272] HADOOP-7931: Update Dr.Elephant version number to 0.6.2-SNAPSHOT --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 250f05e39..bf8db8324 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ import play.Project._ name := "dr-elephant" -version := "0.6.1" +version := "0.6.2-SNAPSHOT" javacOptions in Compile ++= Seq("-source", "1.6", "-target", "1.6") From 9c3e7836183342d938bb2bde8e3a7b2f65221bd9 Mon Sep 17 00:00:00 2001 From: fli Date: Wed, 12 Nov 2014 19:21:45 -0800 Subject: [PATCH 029/272] HADOOP-7652: Rewrite search box in Dr. Elephant --- app/controllers/Application.java | 14 +++++++---- app/views/main.scala.html | 2 +- app/views/related.scala.html | 31 ------------------------ app/views/relatedjob.scala.html | 33 ++++++++++++++++++++++++++ app/views/search.scala.html | 4 ++++ public/js/{script.js => searchform.js} | 19 +++++++++++++-- 6 files changed, 65 insertions(+), 38 deletions(-) delete mode 100644 app/views/related.scala.html create mode 100644 app/views/relatedjob.scala.html rename public/js/{script.js => searchform.js} (76%) diff --git a/app/controllers/Application.java b/app/controllers/Application.java index 87fe9e11c..cb5e0359a 100644 --- a/app/controllers/Application.java +++ b/app/controllers/Application.java @@ -53,9 +53,11 @@ public class Application extends Controller { public static Result search() { DynamicForm form = Form.form().bindFromRequest(request()); String jobId = form.get("jobid"); - jobId = jobId != null ? jobId.trim() : null; + jobId = (jobId != null) ? jobId.trim() : null; + String flowUrl = form.get("flowurl"); + flowUrl = (flowUrl != null) ? flowUrl.trim() : null; String username = form.get("user"); - username = username != null ? username.trim() : null; + username = (username != null) ? username.trim() : null; String severity = form.get("severity"); String jobtype = form.get("jobtype"); String analysis = form.get("analysis"); @@ -69,6 +71,10 @@ public static Result search() { } else { return ok(search.render(singlejob.render(null))); } + } else if(flowUrl != null && !flowUrl.isEmpty()) { + List results = JobResult.find.where().eq("flow_exec_url", flowUrl).findList(); + Map> map = groupJobsByExec(results); + return ok(search.render(relatedjob.render(flowUrl, map))); } else { ExpressionList query = JobResult.find.where(); if (username != null && !username.isEmpty()) { @@ -198,7 +204,7 @@ public static Result allJobExecs() { } Map> map = groupJobsByExec(results); - return ok(related.render(jobUrl, map)); + return ok(search.render(relatedjob.render(jobUrl, map))); } /** @@ -214,7 +220,7 @@ public static Result flowRelated() { } Map> map = groupJobsByExec(results); - return ok(related.render(execUrl, map)); + return ok(search.render(relatedjob.render(execUrl, map))); } public static Result restJobResult(String jobId) { diff --git a/app/views/main.scala.html b/app/views/main.scala.html index eca403f7f..096b4d93a 100644 --- a/app/views/main.scala.html +++ b/app/views/main.scala.html @@ -55,6 +55,6 @@ - + diff --git a/app/views/related.scala.html b/app/views/related.scala.html deleted file mode 100644 index 805d1b2a9..000000000 --- a/app/views/related.scala.html +++ /dev/null @@ -1,31 +0,0 @@ -@(execurl: String, results: Map[String, List[model.JobResult]]) - -@main("Dr. Elephant", "jobs") { -

Jobs related to @execurl

- -} - - diff --git a/app/views/relatedjob.scala.html b/app/views/relatedjob.scala.html new file mode 100644 index 000000000..b32ed9dd4 --- /dev/null +++ b/app/views/relatedjob.scala.html @@ -0,0 +1,33 @@ +@(execurl: String, results: Map[String, List[model.JobResult]]) + +
+ +
+

+ Flow Execution URL: @execurl +

+
+ + +
+ + + diff --git a/app/views/search.scala.html b/app/views/search.scala.html index 0aabf6f5c..afe99c0a3 100644 --- a/app/views/search.scala.html +++ b/app/views/search.scala.html @@ -8,6 +8,10 @@
+
+ + +
diff --git a/public/js/script.js b/public/js/searchform.js similarity index 76% rename from public/js/script.js rename to public/js/searchform.js index 4594c7855..638272a37 100644 --- a/public/js/script.js +++ b/public/js/searchform.js @@ -2,8 +2,8 @@ $(document).ready(function(){ var form = $("#search-form"); - var jobid = $("#form-job-id"); + var flowurl = $("#form-flow-url"); var user = $("#form-user"); var jobtypeEnable = $("#form-jobtype-enable"); var jobtype = $("#form-jobtype"); @@ -19,9 +19,22 @@ $(document).ready(function(){ var updateForm = function(){ if(jobid.val()) { + flowurl.prop('disabled', true); + user.prop('disabled', true); + severity.prop('disabled', true); + analysis.prop('disabled', true); + jobtype.prop('disabled', true); + jobtypeEnable.prop('disabled', true); + severityEnable.prop('disabled', true); + datetimeEnable.prop('disabled', true); + startDate.prop('disabled', true); + endDate.prop('disabled', true); + } else if(flowurl.val()) { + jobid.prop('disabled', true); user.prop('disabled', true); severity.prop('disabled', true); analysis.prop('disabled', true); + jobtype.prop('disabled', true); jobtypeEnable.prop('disabled', true); severityEnable.prop('disabled', true); datetimeEnable.prop('disabled', true); @@ -29,6 +42,8 @@ $(document).ready(function(){ endDate.prop('disabled', true); } else{ + jobid.prop('disabled', false); + flowurl.prop('disabled', false); jobtypeEnable.prop('disabled', false); severityEnable.prop('disabled', false); datetimeEnable.prop('disabled', false); @@ -57,8 +72,8 @@ $(document).ready(function(){ } } } - jobid.on("propertychange keyup input paste", updateForm); + flowurl.on("propertychange keyup input paste", updateForm); jobtypeEnable.change(updateForm); severityEnable.change(updateForm); datetimeEnable.change(updateForm); From b92ec7e8be0a8c5419b9523acec29fcc846f101f Mon Sep 17 00:00:00 2001 From: fli Date: Wed, 12 Nov 2014 21:08:34 -0800 Subject: [PATCH 030/272] HADOOP-7930: Add hash index for execution url in db schema for Dr. Elephant --- conf/evolutions/default/4.sql | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 conf/evolutions/default/4.sql diff --git a/conf/evolutions/default/4.sql b/conf/evolutions/default/4.sql new file mode 100644 index 000000000..83b304688 --- /dev/null +++ b/conf/evolutions/default/4.sql @@ -0,0 +1,11 @@ +# add/remove index on flow_exec_url + +# --- !Ups + +create index ix_job_result_flow_exec_url on job_result (flow_exec_url) using HASH; + + +# --- !Downs + + +drop index ix_job_result_flow_exec_url ON job_result; From 05676b23ab47e0639342a63e936e7d95c1c8f359 Mon Sep 17 00:00:00 2001 From: fli Date: Thu, 13 Nov 2014 11:50:46 -0800 Subject: [PATCH 031/272] HADOOP-7941: Release Dr. Elephant v0.6.2 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index bf8db8324..12fc7385f 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ import play.Project._ name := "dr-elephant" -version := "0.6.2-SNAPSHOT" +version := "0.6.2" javacOptions in Compile ++= Seq("-source", "1.6", "-target", "1.6") From 867215b25659a69b96bd00ccfa1ecc6a34ca6fd6 Mon Sep 17 00:00:00 2001 From: Subbu Subramaniam Date: Mon, 24 Nov 2014 16:53:27 -0800 Subject: [PATCH 032/272] Fixed the build script. Changed the script so it prints out what it is doing. I suspect that somehow the directory name was not getting set correctly. Used a one-liner that seems to work on both Mac and Linux to determine directory of a script. Used braces to delimit the shell variable names. May be random variables were getting set? The zip command included a -x that pretty much amounted to nothing, since it excluded '*'. Changed the zip command to be more specific. Finally, added a trap command that fields ^C and exits the script. It seems to survive ctrl-C now. RB=399693 BUGS=HADOOP-7816 R=fli,akrai A=fli --- compile.sh | 52 ++++++++++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/compile.sh b/compile.sh index cb3acf423..2592bc672 100755 --- a/compile.sh +++ b/compile.sh @@ -1,39 +1,43 @@ #!/usr/bin/env bash -script_dir=`which $0` -script_dir=`dirname $script_dir` -script_dir=`cd $script_dir; pwd` +set -x +trap "exit" SIGINT SIGTERM -project_root=$script_dir -start_script=$project_root/start.sh -stop_script=$project_root/stop.sh +project_root=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) +cd ${project_root} -cd $project_root +start_script=${project_root}/start.sh +stop_script=${project_root}/stop.sh -rm -rf dist +# Echo the value of pwd in the script so that it is clear what is being removed. +rm -rf ${project_root}/dist mkdir dist play -Dhadoop.version=1 clean compile test dist cd target/universal -unzip *.zip - -DIST_NAME=$(find * -maxdepth 0 -type d -not -name "tmp") +# TODO Good to add a check for exactly one zip here +ZIP_NAME=`/bin/ls *.zip` +unzip ${ZIP_NAME} +DIST_NAME=${ZIP_NAME%.zip} -sed -i.bak $'/declare -r app_classpath/s/.$/:$HADOOP_HOME\/*:$HADOOP_HOME\/lib\/*:$HADOOP_HOME\/conf"\\\naddJava "-Djava.library.path=$HADOOP_HOME\/lib\/native\/Linux-amd64-64"\\\n/' $DIST_NAME/bin/dr-elephant +# Make two changes to bin/dr-elephant file +# Add :$HADOOP_HOME/*:$HADOOP_HOME/lib/*:$HADOOP_HOME/conf to 'declare -r ...' line +# Add a line addJava "-Djava.library.path=$HADOOP_HOME/lib/native/Linux-amd64-64" right after +sed -i.bak $'/declare -r app_classpath/s/.$/:$HADOOP_HOME\/*:$HADOOP_HOME\/lib\/*:$HADOOP_HOME\/conf"\\\naddJava "-Djava.library.path=$HADOOP_HOME\/lib\/native\/Linux-amd64-64"\\\n/' ${DIST_NAME}/bin/dr-elephant -chmod +x $DIST_NAME/bin/dr-elephant +chmod +x ${DIST_NAME}/bin/dr-elephant -cp $start_script $DIST_NAME/bin/ +cp $start_script ${DIST_NAME}/bin/ -cp $stop_script $DIST_NAME/bin/ +cp $stop_script ${DIST_NAME}/bin/ -zip -r $DIST_NAME-h1.zip $DIST_NAME -x $DIST_NAME/*.zip *.zip tmp/\* * +zip -r ${DIST_NAME}-h1.zip ${DIST_NAME} -x "${DIST_NAME}/*.zip" -mv $DIST_NAME-h1.zip $project_root/dist/ +mv ${DIST_NAME}-h1.zip ${project_root}/dist/ -cd $project_root +cd ${project_root} play -Dhadoop.version=2 clean compile test dist @@ -41,14 +45,14 @@ cd target/universal unzip *.zip -sed -i.bak $'/declare -r app_classpath/s/.$/:$HADOOP_HOME\/share\/hadoop\/common\/*:$HADOOP_HOME\/share\/hadoop\/common\/lib\/*:$HADOOP_HOME\/share\/hadoop\/hdfs\/*:$HADOOP_CONF_DIR"\\\naddJava "-Djava.library.path=$HADOOP_HOME\/lib\/native"\\\n/' $DIST_NAME/bin/dr-elephant +sed -i.bak $'/declare -r app_classpath/s/.$/:$HADOOP_HOME\/share\/hadoop\/common\/*:$HADOOP_HOME\/share\/hadoop\/common\/lib\/*:$HADOOP_HOME\/share\/hadoop\/hdfs\/*:$HADOOP_CONF_DIR"\\\naddJava "-Djava.library.path=$HADOOP_HOME\/lib\/native"\\\n/' ${DIST_NAME}/bin/dr-elephant -chmod +x $DIST_NAME/bin/dr-elephant +chmod +x ${DIST_NAME}/bin/dr-elephant -cp $start_script $DIST_NAME/bin +cp $start_script ${DIST_NAME}/bin -cp $stop_script $DIST_NAME/bin +cp $stop_script ${DIST_NAME}/bin -zip -r $DIST_NAME-h2.zip $DIST_NAME -x $DIST_NAME/*.zip *.zip tmp/\* * +zip -r ${DIST_NAME}-h2.zip ${DIST_NAME} -x "${DIST_NAME}/*.zip" -mv $DIST_NAME-h2.zip $project_root/dist/ +mv ${DIST_NAME}-h2.zip $project_root/dist/ From 482ad16253415f0f7035482a3ab346c381fc05bd Mon Sep 17 00:00:00 2001 From: fli Date: Mon, 1 Dec 2014 11:28:51 -0800 Subject: [PATCH 033/272] HADOOP-8123: Update Dr.Elephant version number to 0.6.3-SNAPSHOT --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 12fc7385f..1f8e592b0 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ import play.Project._ name := "dr-elephant" -version := "0.6.2" +version := "0.6.3-SNAPSHOT" javacOptions in Compile ++= Seq("-source", "1.6", "-target", "1.6") From 7fbc7e05a514c255c1bfd32bfc61908c0815dce8 Mon Sep 17 00:00:00 2001 From: fli Date: Mon, 1 Dec 2014 11:30:34 -0800 Subject: [PATCH 034/272] HADOOP-7996: Dr. Elephant fails to relogin using keytab on java8 --- build.sbt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.sbt b/build.sbt index 1f8e592b0..0f8c551a3 100644 --- a/build.sbt +++ b/build.sbt @@ -20,11 +20,11 @@ libraryDependencies ++= Seq( libraryDependencies ++= ( if(sys.props.get("hadoop.version").exists(_ == "1")) Seq( - "com.linkedin.li-hadoop" % "hadoop-core" % "1.2.1.45" + "com.linkedin.hadoop" % "hadoop-core" % "1.2.1.+" ) else if(sys.props.get("hadoop.version").exists(_ == "2")) Seq( - "com.linkedin.li-hadoop" % "hadoop-common" % "2.3.0.27", - "com.linkedin.li-hadoop" % "hadoop-mapreduce-client-core" % "2.3.0.27" + "com.linkedin.hadoop" % "hadoop-common" % "2.3.0.+", + "com.linkedin.hadoop" % "hadoop-mapreduce-client-core" % "2.3.0.+" ) else Seq() ) From a6d951a7bd92d4318ac3e80647f44c2af4424671 Mon Sep 17 00:00:00 2001 From: fli Date: Mon, 24 Nov 2014 09:44:26 -0800 Subject: [PATCH 035/272] HADOOP-8060: Dr. Elephant should use hadoop lib and conf from HADOOP_HOME --- .../drelephant/ElephantFetcherClassic.java | 9 +++-- .../drelephant/ElephantFetcherYarn.java | 4 +-- .../linkedin/drelephant/ElephantRunner.java | 4 +-- .../drelephant/hadoop/HadoopSecurity.java | 6 ++++ build.sbt | 18 +++++----- compile.sh | 35 ++++--------------- start.sh | 20 +++++++++-- 7 files changed, 45 insertions(+), 51 deletions(-) diff --git a/app/com/linkedin/drelephant/ElephantFetcherClassic.java b/app/com/linkedin/drelephant/ElephantFetcherClassic.java index 7d0cce136..133ea96fb 100644 --- a/app/com/linkedin/drelephant/ElephantFetcherClassic.java +++ b/app/com/linkedin/drelephant/ElephantFetcherClassic.java @@ -10,7 +10,6 @@ import model.JobResult; import org.apache.commons.io.IOUtils; -import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.mapred.Counters; import org.apache.hadoop.mapred.Counters.Counter; import org.apache.hadoop.mapred.JobClient; @@ -47,7 +46,7 @@ public class ElephantFetcherClassic implements ElephantFetcher { private static final Logger logger = Logger.getLogger(ElephantFetcher.class); private static final int DEFAULT_RETRY = 2; - private Configuration _conf; + private JobConf _conf; private Set _previousJobs; private Map _failedJobsInWait = new ConcurrentHashMap(); @@ -55,7 +54,7 @@ public class ElephantFetcherClassic implements ElephantFetcher { private boolean _firstRun = true; private String _jobtrackerHttpRoot; - public ElephantFetcherClassic(Configuration hadoopConf) throws IOException { + public ElephantFetcherClassic(JobConf hadoopConf) throws IOException { _conf = hadoopConf; _jobtrackerHttpRoot = "http://" + hadoopConf.get("mapred.job.tracker.http.address") + "/"; } @@ -563,8 +562,8 @@ final class ThreadContextMR1 { private ThreadContextMR1() { } - public static void init(Configuration hadoopConf, int threadId) throws IOException { - _LOCAL_JOB_CLIENT.set(new JobClient(new JobConf(hadoopConf))); + public static void init(JobConf hadoopConf, int threadId) throws IOException { + _LOCAL_JOB_CLIENT.set(new JobClient(hadoopConf)); _LOCAL_AUTH_TOKEN.set(new AuthenticatedURL.Token()); _LOCAL_AUTH_URL.set(new AuthenticatedURL()); _LOCAL_THREAD_ID.set(threadId); diff --git a/app/com/linkedin/drelephant/ElephantFetcherYarn.java b/app/com/linkedin/drelephant/ElephantFetcherYarn.java index cd162eaff..0a2e838fa 100644 --- a/app/com/linkedin/drelephant/ElephantFetcherYarn.java +++ b/app/com/linkedin/drelephant/ElephantFetcherYarn.java @@ -9,7 +9,7 @@ import model.JobResult; -import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.mapred.JobConf; import org.apache.hadoop.security.authentication.client.AuthenticatedURL; import org.apache.hadoop.security.authentication.client.AuthenticationException; import org.apache.log4j.Logger; @@ -40,7 +40,7 @@ public class ElephantFetcherYarn implements ElephantFetcher { private long _lastTime = 0; private long _currentTime = 0; - public ElephantFetcherYarn(Configuration hadoopConf) throws IOException { + public ElephantFetcherYarn(JobConf hadoopConf) throws IOException { logger.info("Connecting to the job history server..."); String jhistoryAddr = hadoopConf.get("mapreduce.jobhistory.webapp.address"); _urlFactory = new URLFactory(jhistoryAddr); diff --git a/app/com/linkedin/drelephant/ElephantRunner.java b/app/com/linkedin/drelephant/ElephantRunner.java index b1023a08f..53fd497b0 100644 --- a/app/com/linkedin/drelephant/ElephantRunner.java +++ b/app/com/linkedin/drelephant/ElephantRunner.java @@ -14,7 +14,7 @@ import model.JobResult; import model.JobType; -import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.mapred.JobConf; import org.apache.log4j.Logger; import com.linkedin.drelephant.analysis.Constants; @@ -52,7 +52,7 @@ public Void run() { try { // Tell which hadoop version from hadoop configuration, // and start fetcher accordingly - Configuration hadoopConf = new Configuration(); + JobConf hadoopConf = new JobConf(); String framework = hadoopConf.get("mapreduce.framework.name"); if (framework != null) { diff --git a/app/com/linkedin/drelephant/hadoop/HadoopSecurity.java b/app/com/linkedin/drelephant/hadoop/HadoopSecurity.java index 457e8b3b3..71d28e544 100644 --- a/app/com/linkedin/drelephant/hadoop/HadoopSecurity.java +++ b/app/com/linkedin/drelephant/hadoop/HadoopSecurity.java @@ -3,6 +3,7 @@ import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.security.UserGroupInformation; import org.apache.log4j.Logger; + import play.Play; import java.io.IOException; @@ -42,6 +43,11 @@ public void checkLogin() throws IOException { UserGroupInformation.loginUserFromKeytab(_keytabUser, _keytabLocation); _loginUser = UserGroupInformation.getLoginUser(); logger.info("Logged in with user " + _loginUser); + if(UserGroupInformation.isLoginKeytabBased()) { + logger.info("Login is keytab based"); + } else { + logger.info("Login is not keytab based"); + } } else { _loginUser.checkTGTAndReloginFromKeytab(); } diff --git a/build.sbt b/build.sbt index 0f8c551a3..4cb91357e 100644 --- a/build.sbt +++ b/build.sbt @@ -12,21 +12,19 @@ libraryDependencies ++= Seq( cache, "commons-io" % "commons-io" % "2.4", "mysql" % "mysql-connector-java" % "5.1.22", - "org.apache.commons" % "commons-email" % "1.3.2", "org.apache.hadoop" % "hadoop-auth" % "2.3.0", + "org.apache.commons" % "commons-email" % "1.3.2", "org.codehaus.jackson" % "jackson-mapper-asl" % "1.7.3", "org.jsoup" % "jsoup" % "1.7.3" ) -libraryDependencies ++= ( -if(sys.props.get("hadoop.version").exists(_ == "1")) Seq( - "com.linkedin.hadoop" % "hadoop-core" % "1.2.1.+" -) -else if(sys.props.get("hadoop.version").exists(_ == "2")) Seq( - "com.linkedin.hadoop" % "hadoop-common" % "2.3.0.+", - "com.linkedin.hadoop" % "hadoop-mapreduce-client-core" % "2.3.0.+" -) -else Seq() +ivyConfigurations += config("compileonly").hide + +unmanagedClasspath in Compile ++= update.value.select(configurationFilter("compileonly")) + +libraryDependencies ++= Seq( + "com.linkedin.hadoop" % "hadoop-common" % "2.3.0.+" % "compileonly", + "com.linkedin.hadoop" % "hadoop-mapreduce-client-core" % "2.3.0.+" % "compileonly" ) val LinkedInPatterns = Patterns( diff --git a/compile.sh b/compile.sh index 2592bc672..efa8786d7 100755 --- a/compile.sh +++ b/compile.sh @@ -13,46 +13,23 @@ stop_script=${project_root}/stop.sh rm -rf ${project_root}/dist mkdir dist -play -Dhadoop.version=1 clean compile test dist +play clean compile test dist cd target/universal -# TODO Good to add a check for exactly one zip here ZIP_NAME=`/bin/ls *.zip` unzip ${ZIP_NAME} +rm ${ZIP_NAME} DIST_NAME=${ZIP_NAME%.zip} -# Make two changes to bin/dr-elephant file -# Add :$HADOOP_HOME/*:$HADOOP_HOME/lib/*:$HADOOP_HOME/conf to 'declare -r ...' line -# Add a line addJava "-Djava.library.path=$HADOOP_HOME/lib/native/Linux-amd64-64" right after -sed -i.bak $'/declare -r app_classpath/s/.$/:$HADOOP_HOME\/*:$HADOOP_HOME\/lib\/*:$HADOOP_HOME\/conf"\\\naddJava "-Djava.library.path=$HADOOP_HOME\/lib\/native\/Linux-amd64-64"\\\n/' ${DIST_NAME}/bin/dr-elephant - chmod +x ${DIST_NAME}/bin/dr-elephant +sed -i.bak $'/declare -r app_classpath/s/.$/:`hadoop classpath`"/' ${DIST_NAME}/bin/dr-elephant + cp $start_script ${DIST_NAME}/bin/ cp $stop_script ${DIST_NAME}/bin/ -zip -r ${DIST_NAME}-h1.zip ${DIST_NAME} -x "${DIST_NAME}/*.zip" - -mv ${DIST_NAME}-h1.zip ${project_root}/dist/ - -cd ${project_root} - -play -Dhadoop.version=2 clean compile test dist - -cd target/universal - -unzip *.zip - -sed -i.bak $'/declare -r app_classpath/s/.$/:$HADOOP_HOME\/share\/hadoop\/common\/*:$HADOOP_HOME\/share\/hadoop\/common\/lib\/*:$HADOOP_HOME\/share\/hadoop\/hdfs\/*:$HADOOP_CONF_DIR"\\\naddJava "-Djava.library.path=$HADOOP_HOME\/lib\/native"\\\n/' ${DIST_NAME}/bin/dr-elephant - -chmod +x ${DIST_NAME}/bin/dr-elephant - -cp $start_script ${DIST_NAME}/bin - -cp $stop_script ${DIST_NAME}/bin - -zip -r ${DIST_NAME}-h2.zip ${DIST_NAME} -x "${DIST_NAME}/*.zip" +zip -r ${DIST_NAME}.zip ${DIST_NAME} -mv ${DIST_NAME}-h2.zip $project_root/dist/ +mv ${DIST_NAME}.zip ${project_root}/dist/ diff --git a/start.sh b/start.sh index 7d5f0695f..7824f585b 100755 --- a/start.sh +++ b/start.sh @@ -69,8 +69,6 @@ echo "keytab location: " $keytab_location port="${port:-8080}" echo "http port: " $port -echo "Starting Dr. Elephant ...." - # Navigate to project root cd $project_root @@ -88,8 +86,24 @@ then exit 1 fi +# Get hadoop version by executing 'hadoop version' and parse the result +HADOOP_VERSION=$(hadoop version | awk '{if (NR == 1) {print $2;}}') +if [[ $HADOOP_VERSION == 1* ]]; +then + JAVA_LIB_PATH=$HADOOP_HOME"/lib/native/Linux-amd64-64" + echo "This is hadoop1.x grid. Add Java library path: "$JAVA_LIB_PATH +elif [[ $HADOOP_VERSION == 2* ]]; +then + JAVA_LIB_PATH=$HADOOP_HOME"/lib/native" + echo "This is hadoop2.x grid. Add Java library path: "$JAVA_LIB_PATH +else + echo "error: Hadoop isn't properly set on this machine. Could you verify cmd 'hadoop version'? " + exit 1 +fi + # Start Dr. Elaphant -nohup ./bin/dr-elephant -Dhttp.port=$port -Dkeytab.user=$keytab_user -Dkeytab.location=$keytab_location -Ddb.default.url=$db_loc -Ddb.default.user=$db_user -Ddb.default.password=$db_password > /dev/null 2>&1 & +echo "Starting Dr. Elephant ...." +nohup ./bin/dr-elephant -Djava.library.path=$JAVA_LIB_PATH -Dhttp.port=$port -Dkeytab.user=$keytab_user -Dkeytab.location=$keytab_location -Ddb.default.url=$db_loc -Ddb.default.user=$db_user -Ddb.default.password=$db_password > /dev/null 2>&1 & sleep 2 From 799c34f1b3ca1165c3217bab9eec0d249f3d9ecb Mon Sep 17 00:00:00 2001 From: fli Date: Mon, 1 Dec 2014 20:35:01 -0800 Subject: [PATCH 036/272] HADOOP-8135: Dr. Elephant's MapperSpeedHeuristic throws exception when job has no mappers --- .../heuristics/MapperSpeedHeuristic.java | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/app/com/linkedin/drelephant/analysis/heuristics/MapperSpeedHeuristic.java b/app/com/linkedin/drelephant/analysis/heuristics/MapperSpeedHeuristic.java index 3c5ac9f18..2117b9219 100644 --- a/app/com/linkedin/drelephant/analysis/heuristics/MapperSpeedHeuristic.java +++ b/app/com/linkedin/drelephant/analysis/heuristics/MapperSpeedHeuristic.java @@ -42,9 +42,19 @@ public HeuristicResult apply(HadoopJobData data) { } } - long medianSpeed = Statistics.median(speeds); - long medianSize = Statistics.median(inputByteSizes); - long medianRuntimeMs = Statistics.median(runtimesMs); + long medianSpeed; + long medianSize; + long medianRuntimeMs; + + if (tasks.length != 0) { + medianSpeed = Statistics.median(speeds); + medianSize = Statistics.median(inputByteSizes); + medianRuntimeMs = Statistics.median(runtimesMs); + } else { + medianSpeed = 0; + medianSize = 0; + medianRuntimeMs = 0; + } Severity severity = getDiskSpeedSeverity(medianSpeed); From 48ff1b914585bfe274a488cc6b583e1f69b774f3 Mon Sep 17 00:00:00 2001 From: fli Date: Tue, 2 Dec 2014 10:41:16 -0800 Subject: [PATCH 037/272] HADOOP-8134: Release Dr. Elephant v0.6.3 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 4cb91357e..e2e5cdd7f 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ import play.Project._ name := "dr-elephant" -version := "0.6.3-SNAPSHOT" +version := "0.6.3" javacOptions in Compile ++= Seq("-source", "1.6", "-target", "1.6") From 1a4390f3412351b0bf38adda88ea0ce9bb32882f Mon Sep 17 00:00:00 2001 From: fli Date: Thu, 11 Dec 2014 11:22:41 -0800 Subject: [PATCH 038/272] HADOOP-8316: Update Dr.Elephant version number to 0.6.4-SNAPSHOT --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index e2e5cdd7f..c8533c8a0 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ import play.Project._ name := "dr-elephant" -version := "0.6.3" +version := "0.6.4-SNAPSHOT" javacOptions in Compile ++= Seq("-source", "1.6", "-target", "1.6") From 65ae460912094ffee89e3851a13df3193a4960e1 Mon Sep 17 00:00:00 2001 From: Akshay Rai Date: Sat, 13 Dec 2014 10:07:47 -0800 Subject: [PATCH 039/272] HADOOP-8096: Property 'username' is converted to lowercase before supplying it to the query instead of asking the query to perform the case-insensitive comparison. --- app/controllers/Application.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/Application.java b/app/controllers/Application.java index cb5e0359a..b8d6124de 100644 --- a/app/controllers/Application.java +++ b/app/controllers/Application.java @@ -57,7 +57,7 @@ public static Result search() { String flowUrl = form.get("flowurl"); flowUrl = (flowUrl != null) ? flowUrl.trim() : null; String username = form.get("user"); - username = (username != null) ? username.trim() : null; + username = (username != null) ? username.trim().toLowerCase() : null; String severity = form.get("severity"); String jobtype = form.get("jobtype"); String analysis = form.get("analysis"); @@ -78,7 +78,7 @@ public static Result search() { } else { ExpressionList query = JobResult.find.where(); if (username != null && !username.isEmpty()) { - query = query.ilike("username", username); + query = query.like("username", username); } if (jobtype != null && !jobtype.isEmpty()) { query = query.eq("job_type", JobType.getDbName(jobtype)); From bced3399e3175adba32509fff0a27e5485e6e473 Mon Sep 17 00:00:00 2001 From: Subbu Subramaniam Date: Wed, 10 Dec 2014 13:19:04 -0800 Subject: [PATCH 040/272] [HADOOP-7814] Prepare to add metrics library to dr-elephant Changed HadoopJobData to include finishTime since that is needed for metrics. Changed the signature of getJobCounter to include jobConf and jobData so that it can publish metrics Updated README.md Tested locally on my box and on spades RB=406817 BUGS=HADOOP-7814 R=fli,mwagner A=fli --- README.md | 60 +++++++------- .../drelephant/ElephantFetcherClassic.java | 81 +++++++++++-------- .../drelephant/ElephantFetcherYarn.java | 71 ++++++++-------- .../hadoop/HadoopCounterHolder.java | 71 ++++++++++++++-- .../drelephant/hadoop/HadoopJobData.java | 10 +++ .../JobQueueLimitHeuristicTest.java | 2 +- .../MapperDataSkewHeuristicTest.java | 12 +-- .../MapperInputSizeHeuristicTest.java | 10 +-- .../heuristics/MapperSpeedHeuristicTest.java | 10 +-- .../heuristics/MapperSpillHeuristicTest.java | 9 +-- .../ReducerDataSkewHeuristicTest.java | 12 +-- .../heuristics/ReducerTimeHeuristicTest.java | 2 +- .../heuristics/ShuffleSortHeuristicTest.java | 2 +- 13 files changed, 207 insertions(+), 145 deletions(-) diff --git a/README.md b/README.md index f9707e153..ab670ed36 100644 --- a/README.md +++ b/README.md @@ -5,30 +5,26 @@ * To be able to build & run the application, download and install [Play framework 2.2.2](http://downloads.typesafe.com/play/2.2.2/play-2.2.2.zip). * The pre-installed play command on our boxes will not work as it is configured to look at LinkedIns repos * If this is your first time working with Dr. Elephant, take the deployed Hadoop jars and put them in the /lib directory: - scp eat1-magicgw01.grid.linkedin.com:/export/apps/hadoop/latest/hadoop-core-1.2.1-p3.jar ./lib/. - -* To build and run the application in dev mode, run from command line "play run" in the project directory. -* There is need to investigate the framework to see how one can add parameters to the classpath in dev mode. - -### Deployment - -* To create a deployment package, use "play dist" to create a zip package, or use "play universal:package-zip-tarball" to create a tarball -* To run the deployed package with Hadoop properly, some changes needs to be added to the startup script located at ./bin/dr-elephant - -* in the classpath ("declare -r app\_classpath=...") , add to the end of the string, before the end quotes - - :$HADOOP_HOME/*:$HADOOP_HOME/lib/*:$HADOOP_HOME/conf - -* after the next line ("addJava ... ;"), add new line - - addJava "-Djava.library.path=$HADOOP_HOME/lib/native/Linux-amd64-64" - -### New Deployment (All previous instructions are deprecated!) - -* ./compile.sh will create two zips under 'dist' dir which can deploy with h1 and h2 directly without changing classpath -* When test dr.e in hadoop2.x locally, HADOOP_HOME and HADOOP_CONF_DIR need to be set properly -* Upon deployment on cluster, we can specify keytab and database location at runtime: ./bin/dr-elephant -Dhttp.port=xxxx -Dkeytab.user="xxxx" -Dkeytab.location="xxxx" -Ddb.default.url="jdbc:mysql://xxxx" -Ddb.default.user=xxxx -Ddb.default.password=xxxx so that we don't have to change application.conf at compile time - + scp eat1-magicgw01.grid.linkedin.com:/export/apps/hadoop/latest/hadoop-core-*.jar ./lib/. + NOTE: When building dr-elephant, this jar file should be removed or renamed. Otherwise, the hadoop-2 executable built will not work. +* To compile dr-elephant, run the script ./compile.sh. A zip file is created in the 'dist' directory. +* You can run dr-elephant on your desktop and have it point to a cluster. Do the following: + - cd dist;unzip *.zip; # and then cd to the dr-elephant release directory created. + - Copy the cluster's configuration onto a local directory on your machine. + - Copy the hadoop distribution onto a local directory (or, build hadoop locally). + - For Hadoop-2, the following setup is needed: + export HADOOP_HOME=/path/to/project/hadoop/hadoop-dist/target/hadoop-2.3.0_li-SNAPSHOT + export HADOOP_CONF_DIR=/path/to/config/etc/hadoop + export PATH=$HADOOP_HOME/bin:$PATH # because dr-elephant uses 'hadoop classpath' to load the right classes + vim $HADOOP_CONF_DIR/mapred-site.xml + # Make sure to set the job history server co-ordinates if it is not already set + # mapreduce.jobhistory.webapp.addresseat1-hcl0764.grid:19888 + - Set up and start mysql locally on your box, the default port is 3306. Create a database called 'drelephant', if it does not exist. + - You can now start dr-elephant with the following command (assuming you are running mysql locally on port 3306): + ./bin/dr-elephant -Dhttp.port=8089 -Ddb.default.url="jdbc:mysql://ssubrama-ld1.linkedin.biz:3306/drelephant?characterEncoding=UTF-8" -Devolutionplugin=enabled -DapplyEvolutions.default=true + - Note that you can add other properties with the -D option, as -Dprop.name=value + - You can debug dr-elephant using eclispse or idea by adding the argument -Djvm_args="-Xdebug -Xrunjdwp:transport=dt_socket,address=8876,server=y,suspend=y" + This will start dr-elephant and wait until you attach eclipse/idea to port 8876. You can then set breakpoints and debug interactively. ### DB Schema evolutions @@ -47,18 +43,16 @@ When the schema in the model package changes, play will need to be ran to automa * Remove the header in the sql file so it does not get overwritten * Browse the page again to refresh the schema to add the indices. -### Running on the cluster +### Deployment on the cluster * SSH into the machine * sudo as elephant * go to /export/apps/elephant/ -* To start: ./run.sh -* To kill: ./kill.sh -* To deploy new version: - * scp machine:location-to-drelephant.zip /export/apps/elephant/ - * ./kill.sh - * unzip dr-elephant-0.1-SNAPSHOT.zip - * ./run.sh +* unzip the file +* cd dr-elephant*/bin +* To start: ./start.sh +* To stop: ./stop.sh +* To deploy new version, be sure to kill the running process first ### Adding new heuristics @@ -68,4 +62,4 @@ When the schema in the model package changes, play will need to be ran to automa * heuristicname: Name of the heuristic. (e.g. Mapper Spill) * classname: This should be the fully qualified name of the class (e.g. com.linkedin.drelephant.analysis.heuristics.MapperSpillHeuristic) * viewname: This should be the fully qualified name of the view. ( e.g. views.html.helpMapperSpill ) -*Run Doctor Elephant, it should now include the new heuristics. \ No newline at end of file +*Run Doctor Elephant, it should now include the new heuristics. diff --git a/app/com/linkedin/drelephant/ElephantFetcherClassic.java b/app/com/linkedin/drelephant/ElephantFetcherClassic.java index 133ea96fb..66ac31013 100644 --- a/app/com/linkedin/drelephant/ElephantFetcherClassic.java +++ b/app/com/linkedin/drelephant/ElephantFetcherClassic.java @@ -6,9 +6,20 @@ import com.linkedin.drelephant.hadoop.HadoopJobData; import com.linkedin.drelephant.hadoop.HadoopTaskData; import com.linkedin.drelephant.math.Statistics; - +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Random; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import model.JobResult; - import org.apache.commons.io.IOUtils; import org.apache.hadoop.mapred.Counters; import org.apache.hadoop.mapred.Counters.Counter; @@ -27,21 +38,6 @@ import org.jsoup.nodes.Element; import org.jsoup.select.Elements; -import java.io.IOException; -import java.net.HttpURLConnection; -import java.net.URL; -import java.text.SimpleDateFormat; -import java.text.ParseException; -import java.util.ArrayList; -import java.util.EnumMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.Random; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - public class ElephantFetcherClassic implements ElephantFetcher { private static final Logger logger = Logger.getLogger(ElephantFetcher.class); @@ -133,9 +129,10 @@ public void fetchJobData(HadoopJobData jobData) throws IOException, Authenticati JobStatus status = job.getJobStatus(); String username = status.getUsername(); long startTime = status.getStartTime(); + long finishTime = status.getFinishTime(); String jobUrl = job.getTrackingURL(); String jobName = job.getJobName(); - jobData.setUsername(username).setStartTime(startTime).setUrl(jobUrl).setJobName(jobName); + jobData.setUsername(username).setStartTime(startTime).setFinishTime(finishTime).setUrl(jobUrl).setJobName(jobName); // Fetch job counter HadoopCounterHolder counterHolder = fetchCounter(job.getCounters()); @@ -315,16 +312,16 @@ private long[] tryExtractDetailFromRow(Element row, boolean isMapper) throws Par } private HadoopCounterHolder fetchCounter(Counters counters) { - Map counterMap = new EnumMap(CounterName.class); + HadoopCounterHolder holder = new HadoopCounterHolder(); for (Counters.Group group : counters) { for (Counter ctr : group) { CounterName cn = CounterName.getCounterFromName(ctr.getName()); if (cn != null) { - counterMap.put(cn, ctr.getValue()); + holder.set(cn.getGroupName(), cn.getName(), ctr.getValue()); } } } - return new HadoopCounterHolder(counterMap); + return holder; } private void addJobToRetryList(HadoopJobData job) { @@ -400,17 +397,17 @@ private boolean checkRetiredAndFetchJobData(HadoopJobData jobData) throws IOExce HadoopTaskData[] mapperData = fetchAllTaskDataForRetiredJob(mapperUrl, true); HadoopTaskData[] reducerData = fetchAllTaskDataForRetiredJob(reducerUrl, false); - long startTime = fetchStartTimeForRetiredJob(doc, jobUrl); + StartAndEndTimes times = fetchTimesForRetiredJob(doc, jobUrl); String username = jobConf.getProperty("user.name"); String jobName = jobConf.getProperty("mapred.job.name"); - jobData.setUsername(username).setStartTime(startTime).setUrl(jobUrl).setJobName(jobName); + jobData.setUsername(username).setStartTime(times._startTime).setFinishTime(times._finishTime).setUrl(jobUrl).setJobName(jobName); jobData.setCounters(jobCounter).setJobConf(jobConf).setMapperData(mapperData).setReducerData(reducerData); return true; } // Fetch job counter from job's main page private HadoopCounterHolder fetchJobCounterForRetiredJob(Document doc) throws IOException, AuthenticationException { - Map counterMap = new EnumMap(CounterName.class); + HadoopCounterHolder holder = new HadoopCounterHolder(); Elements rows = doc.select("table").select("tr"); for (Element row : rows) { Elements cells = row.select("> td"); @@ -418,23 +415,25 @@ private HadoopCounterHolder fetchJobCounterForRetiredJob(Document doc) throws IO String countername = cells.get(1).text().trim(); CounterName cn = CounterName.getCounterFromDisplayName(countername); if (cn != null) { - counterMap.put(cn, Long.parseLong(cells.get(4).text().trim().replace(",", ""))); + long value = Long.parseLong(cells.get(4).text().trim().replace(",", "")); + holder.set(cn.getGroupName(), cn.getName(), value); } } else if (cells.size() == 4) { String countername = cells.get(0).text().trim(); CounterName cn = CounterName.getCounterFromDisplayName(countername); if (cn != null) { - counterMap.put(cn, Long.parseLong(cells.get(3).text().trim().replace(",", ""))); + long value = Long.parseLong(cells.get(3).text().trim().replace(",", "")); + holder.set(cn.getGroupName(), cn.getName(), value); } } } - return new HadoopCounterHolder(counterMap); + return holder; } // Fetch task counter from task's counter page public HadoopCounterHolder fetchTaskCounterForRetiredJob(String taskCounterUrl) throws IOException, AuthenticationException { - Map counterMap = new EnumMap(CounterName.class); + HadoopCounterHolder holder = new HadoopCounterHolder(); Document doc = ThreadContextMR1.fetchHtmlDoc(taskCounterUrl); Elements rows = doc.select("table").select("tr"); for (Element row : rows) { @@ -443,26 +442,40 @@ public HadoopCounterHolder fetchTaskCounterForRetiredJob(String taskCounterUrl) String countername = cells.get(1).text().trim(); CounterName cn = CounterName.getCounterFromDisplayName(countername); if (cn != null) { - counterMap.put(cn, Long.parseLong(cells.get(2).text().trim().replace(",", ""))); + long value = Long.parseLong(cells.get(2).text().trim().replace(",", "")); + holder.set(cn.getGroupName(), cn.getName(), value); } } } - return new HadoopCounterHolder(counterMap); + return holder; } // We fetch the start time of job's Setup task as the job's start time, shown in job's main page - private long fetchStartTimeForRetiredJob(Document doc, String jobUrl) throws IOException { + + static class StartAndEndTimes { + public long _startTime; + public long _finishTime; + } + + private StartAndEndTimes fetchTimesForRetiredJob(Document doc, String jobUrl) + throws IOException { Elements rows = doc.select("table").select("tr"); for (Element row : rows) { Elements cells = row.select("> td"); if (cells.size() == 7 && cells.get(0).text().trim().equals("Setup")) { SimpleDateFormat dateFormat = new SimpleDateFormat("d-MMM-yyyy HH:mm:ss"); + StartAndEndTimes times = new StartAndEndTimes(); try { - long time = dateFormat.parse(cells.get(5).text().trim()).getTime(); - return time; + times._startTime = dateFormat.parse(cells.get(5).text().trim()).getTime(); + } catch (ParseException e) { + throw new IOException("Error in fetching start time from job page in URL : " + jobUrl); + } + try { + times._finishTime = dateFormat.parse(cells.get(6).text().trim()).getTime(); } catch (ParseException e) { - throw new IOException("Error in fetching start time data from job page in URL : " + jobUrl); + throw new IOException("Error in fetching finish time from job page in URL : " + jobUrl); } + return times; } } throw new IOException("Unable to fetch start time data from job page in URL : " + jobUrl); diff --git a/app/com/linkedin/drelephant/ElephantFetcherYarn.java b/app/com/linkedin/drelephant/ElephantFetcherYarn.java index 0a2e838fa..1711fea92 100644 --- a/app/com/linkedin/drelephant/ElephantFetcherYarn.java +++ b/app/com/linkedin/drelephant/ElephantFetcherYarn.java @@ -6,28 +6,24 @@ import com.linkedin.drelephant.hadoop.HadoopJobData; import com.linkedin.drelephant.hadoop.HadoopTaskData; import com.linkedin.drelephant.math.Statistics; - -import model.JobResult; - -import org.apache.hadoop.mapred.JobConf; -import org.apache.hadoop.security.authentication.client.AuthenticatedURL; -import org.apache.hadoop.security.authentication.client.AuthenticationException; -import org.apache.log4j.Logger; -import org.codehaus.jackson.JsonNode; -import org.codehaus.jackson.map.ObjectMapper; - import java.io.IOException; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; -import java.util.EnumMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Random; import java.util.concurrent.ConcurrentHashMap; +import model.JobResult; +import org.apache.hadoop.mapred.JobConf; +import org.apache.hadoop.security.authentication.client.AuthenticatedURL; +import org.apache.hadoop.security.authentication.client.AuthenticationException; +import org.apache.log4j.Logger; +import org.codehaus.jackson.JsonNode; +import org.codehaus.jackson.map.ObjectMapper; public class ElephantFetcherYarn implements ElephantFetcher { @@ -221,10 +217,27 @@ private List getJobData(URL url, boolean checkDB) throws IOExcept } // New job + /* + { + "finishTime": 1415816989533, + "id": "job_1415656299984_0014", + "mapsCompleted": 1, + "mapsTotal": 1, + "name": "word count", + "queue": "default", + "reducesCompleted": 1, + "reducesTotal": 1, + "startTime": 1415816979666, + "state": "SUCCEEDED", + "submitTime": 1415816976300, + "user": "ssubrama" + } + */ HadoopJobData jobData = new HadoopJobData(); jobData.setJobId(jobId).setUsername(job.get("user").getValueAsText()) .setJobName(job.get("name").getValueAsText()).setUrl(getJobDetailURL(jobId)) - .setStartTime(job.get("startTime").getLongValue()); + .setStartTime(job.get("startTime").getLongValue()) + .setFinishTime(job.get("finishTime").getLongValue()); jobList.add(jobData); } @@ -245,46 +258,40 @@ private Properties getProperties(URL url) throws IOException, AuthenticationExce return jobConf; } - private HadoopCounterHolder getJobCounter(URL url) throws IOException, AuthenticationException { - Map counterMap = new EnumMap(CounterName.class); + private HadoopCounterHolder getJobCounter(URL url) + throws IOException, AuthenticationException { + HadoopCounterHolder holder = new HadoopCounterHolder(); JsonNode rootNode = ThreadContextMR2.readJsonNode(url); JsonNode groups = rootNode.path("jobCounters").path("counterGroup"); for (JsonNode group : groups) { for (JsonNode counter : group.path("counter")) { - String name = counter.get("name").getValueAsText(); - CounterName cn = CounterName.getCounterFromName(name); - if (cn != null) { - counterMap.put(cn, counter.get("totalCounterValue").getLongValue()); - } + String counterName = counter.get("name").getValueAsText(); + CounterName cn = CounterName.getCounterFromName(counterName); + Long counterValue = counter.get("totalCounterValue").getLongValue(); + String groupName = group.get("counterGroupName").getValueAsText(); + holder.set(groupName, counterName, counterValue); } } - return new HadoopCounterHolder(counterMap); + return holder; } private HadoopCounterHolder getTaskCounter(URL url) throws IOException, AuthenticationException { - Map counterMap = new EnumMap(CounterName.class); - JsonNode rootNode = ThreadContextMR2.readJsonNode(url); JsonNode groups = rootNode.path("jobTaskCounters").path("taskCounterGroup"); + HadoopCounterHolder holder = new HadoopCounterHolder(); for (JsonNode group : groups) { for (JsonNode counter : group.path("counter")) { String name = counter.get("name").getValueAsText(); - CounterName cn = CounterName.getCounterFromName(name); - if (cn != null) { - counterMap.put(cn, counter.get("value").getLongValue()); - } + String groupName = group.get("counterGroupName").getValueAsText(); + Long value = counter.get("value").getLongValue(); + holder.set(groupName, name, value); } } - for (CounterName name : CounterName.values()) { - if (!counterMap.containsKey(name)) { - counterMap.put(name, 0L); - } - } - return new HadoopCounterHolder(counterMap); + return holder; } private long[] getTaskExecTime(URL url) throws IOException, AuthenticationException { diff --git a/app/com/linkedin/drelephant/hadoop/HadoopCounterHolder.java b/app/com/linkedin/drelephant/hadoop/HadoopCounterHolder.java index 968cafc00..c485466e1 100644 --- a/app/com/linkedin/drelephant/hadoop/HadoopCounterHolder.java +++ b/app/com/linkedin/drelephant/hadoop/HadoopCounterHolder.java @@ -6,22 +6,75 @@ public class HadoopCounterHolder { - private Map _counters; + // This is a map of group to all the counters in the group and their values. + private final Map> _pubCounters; - public HadoopCounterHolder(Map counterMap) { - _counters = counterMap; + public HadoopCounterHolder() { + _pubCounters = new HashMap>(8); } + /** + * @return the value of the counter, 0 if not present. + */ public long get(CounterName counterName) { - Long value = _counters.get(counterName); - if (value == null) { + try { + long value = get(counterName.getGroupName(), counterName.getName()); + return value; + } catch (NoSuchFieldException e) { return 0; } - return value; } public void set(CounterName counterName, long value) { - _counters.put(counterName, value); + set(counterName.getGroupName(), counterName.getName(), value); + } + + /** + * Set the value of a counter that we may want to publish later + * + * @param groupName + * @param counterName + * @param value + */ + public void set(String groupName, String counterName, long value) { + Map counterMap = _pubCounters.get(groupName); + if (counterMap == null) { + counterMap = new HashMap(4); + _pubCounters.put(groupName, counterMap); + } + counterMap.put(counterName, value); + } + + /** + * Get the vlaue of a counter from counters to publish + * @param groupName + * @param counterName + * @return -1 if the counter or group is not present. + */ + public long get(String groupName, String counterName) + throws NoSuchFieldException { + Map counterMap = _pubCounters.get(groupName); + if (counterMap == null) { + throw new NoSuchFieldException("Unknown counter group:" + groupName); + } + Long value = counterMap.get(counterName); + if (value == null) { + throw new NoSuchFieldException("Unknown counter name:" + counterName); + } + return value != null ? value : -1; + } + + /** + * Get the values of all counters in a group + * @param groupName + * @return A map containing all the values of counters in a group. + */ + public Map getAllCountersInGroup(String groupName) { + Map counterMap = _pubCounters.get(groupName); + if (counterMap == null) { + counterMap = new HashMap(1); + } + return counterMap; } public static enum GroupName { @@ -109,5 +162,9 @@ public String getDisplayName() { return _displayName; } + public String getGroupName() { + return _group._name; + } + } } diff --git a/app/com/linkedin/drelephant/hadoop/HadoopJobData.java b/app/com/linkedin/drelephant/hadoop/HadoopJobData.java index 570964696..65475f396 100644 --- a/app/com/linkedin/drelephant/hadoop/HadoopJobData.java +++ b/app/com/linkedin/drelephant/hadoop/HadoopJobData.java @@ -9,6 +9,7 @@ public class HadoopJobData { private String _url = ""; private String _jobName = ""; private long _startTime = 0; + private long _finishTime = 0; private HadoopCounterHolder _counterHolder; private HadoopTaskData[] _mapperData; private HadoopTaskData[] _reducerData; @@ -40,6 +41,11 @@ public HadoopJobData setStartTime(long startTime) { return this; } + public HadoopJobData setFinishTime(long finishTime) { + this._finishTime = finishTime; + return this; + } + public HadoopJobData setUrl(String url) { this._url = url; return this; @@ -89,6 +95,10 @@ public long getStartTime() { return _startTime; } + public long getFinishTime() { + return _finishTime; + } + public String getUrl() { return _url; } diff --git a/test/com/linkedin/drelephant/analysis/heuristics/JobQueueLimitHeuristicTest.java b/test/com/linkedin/drelephant/analysis/heuristics/JobQueueLimitHeuristicTest.java index 341bea59f..baa74054f 100644 --- a/test/com/linkedin/drelephant/analysis/heuristics/JobQueueLimitHeuristicTest.java +++ b/test/com/linkedin/drelephant/analysis/heuristics/JobQueueLimitHeuristicTest.java @@ -47,7 +47,7 @@ public void testNonDefaultRuntimeNone() throws IOException { } private Severity analyzeJob(long runtimeMs, String queueName) throws IOException { - HadoopCounterHolder dummyCounter = new HadoopCounterHolder(null); + HadoopCounterHolder dummyCounter = new HadoopCounterHolder(); HadoopTaskData[] mappers = new HadoopTaskData[2 * NUM_TASKS / 3]; HadoopTaskData[] reducers = new HadoopTaskData[NUM_TASKS / 3]; Properties jobConf = new Properties(); diff --git a/test/com/linkedin/drelephant/analysis/heuristics/MapperDataSkewHeuristicTest.java b/test/com/linkedin/drelephant/analysis/heuristics/MapperDataSkewHeuristicTest.java index 00c8936e1..08c58947f 100644 --- a/test/com/linkedin/drelephant/analysis/heuristics/MapperDataSkewHeuristicTest.java +++ b/test/com/linkedin/drelephant/analysis/heuristics/MapperDataSkewHeuristicTest.java @@ -1,17 +1,13 @@ package com.linkedin.drelephant.analysis.heuristics; -import java.io.IOException; -import java.util.HashMap; - import com.linkedin.drelephant.analysis.Constants; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; import com.linkedin.drelephant.hadoop.HadoopCounterHolder; -import com.linkedin.drelephant.hadoop.HadoopCounterHolder.CounterName; import com.linkedin.drelephant.hadoop.HadoopJobData; import com.linkedin.drelephant.hadoop.HadoopTaskData; - +import java.io.IOException; import junit.framework.TestCase; @@ -50,13 +46,13 @@ public void testSmallTasks() throws IOException { private Severity analyzeJob(int numSmallTasks, int numLargeTasks, long smallInputSize, long largeInputSize) throws IOException { - HadoopCounterHolder jobCounter = new HadoopCounterHolder(null); + HadoopCounterHolder jobCounter = new HadoopCounterHolder(); HadoopTaskData[] mappers = new HadoopTaskData[numSmallTasks + numLargeTasks]; - HadoopCounterHolder smallCounter = new HadoopCounterHolder(new HashMap()); + HadoopCounterHolder smallCounter = new HadoopCounterHolder(); smallCounter.set(HadoopCounterHolder.CounterName.HDFS_BYTES_READ, smallInputSize); - HadoopCounterHolder largeCounter = new HadoopCounterHolder(new HashMap()); + HadoopCounterHolder largeCounter = new HadoopCounterHolder(); largeCounter.set(HadoopCounterHolder.CounterName.HDFS_BYTES_READ, largeInputSize); int i = 0; diff --git a/test/com/linkedin/drelephant/analysis/heuristics/MapperInputSizeHeuristicTest.java b/test/com/linkedin/drelephant/analysis/heuristics/MapperInputSizeHeuristicTest.java index 8d4d4f65b..16b8a44a1 100644 --- a/test/com/linkedin/drelephant/analysis/heuristics/MapperInputSizeHeuristicTest.java +++ b/test/com/linkedin/drelephant/analysis/heuristics/MapperInputSizeHeuristicTest.java @@ -1,18 +1,14 @@ package com.linkedin.drelephant.analysis.heuristics; -import java.io.IOException; -import java.util.HashMap; - import com.linkedin.drelephant.analysis.Constants; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; import com.linkedin.drelephant.hadoop.HadoopCounterHolder; -import com.linkedin.drelephant.hadoop.HadoopCounterHolder.CounterName; import com.linkedin.drelephant.hadoop.HadoopJobData; import com.linkedin.drelephant.hadoop.HadoopTaskData; import com.linkedin.drelephant.math.Statistics; - +import java.io.IOException; import junit.framework.TestCase; @@ -142,10 +138,10 @@ public void testNormalFileShortRuntime() throws IOException { } private Severity analyzeJob(int numTasks, long inputSize, long runtime) throws IOException { - HadoopCounterHolder jobCounter = new HadoopCounterHolder(null); + HadoopCounterHolder jobCounter = new HadoopCounterHolder(); HadoopTaskData[] mappers = new HadoopTaskData[numTasks]; - HadoopCounterHolder taskCounter = new HadoopCounterHolder(new HashMap()); + HadoopCounterHolder taskCounter = new HadoopCounterHolder(); taskCounter.set(HadoopCounterHolder.CounterName.HDFS_BYTES_READ, inputSize); int i = 0; diff --git a/test/com/linkedin/drelephant/analysis/heuristics/MapperSpeedHeuristicTest.java b/test/com/linkedin/drelephant/analysis/heuristics/MapperSpeedHeuristicTest.java index 1d71682ad..e20209d59 100644 --- a/test/com/linkedin/drelephant/analysis/heuristics/MapperSpeedHeuristicTest.java +++ b/test/com/linkedin/drelephant/analysis/heuristics/MapperSpeedHeuristicTest.java @@ -1,18 +1,14 @@ package com.linkedin.drelephant.analysis.heuristics; -import java.io.IOException; -import java.util.HashMap; - import com.linkedin.drelephant.analysis.Constants; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; import com.linkedin.drelephant.hadoop.HadoopCounterHolder; -import com.linkedin.drelephant.hadoop.HadoopCounterHolder.CounterName; import com.linkedin.drelephant.hadoop.HadoopJobData; import com.linkedin.drelephant.hadoop.HadoopTaskData; import com.linkedin.drelephant.math.Statistics; - +import java.io.IOException; import junit.framework.TestCase; @@ -47,10 +43,10 @@ public void testShortTask() throws IOException { } private Severity analyzeJob(long runtimeMs, long readBytes) throws IOException { - HadoopCounterHolder jobCounter = new HadoopCounterHolder(null); + HadoopCounterHolder jobCounter = new HadoopCounterHolder(); HadoopTaskData[] mappers = new HadoopTaskData[NUMTASKS]; - HadoopCounterHolder counter = new HadoopCounterHolder(new HashMap()); + HadoopCounterHolder counter = new HadoopCounterHolder(); counter.set(HadoopCounterHolder.CounterName.HDFS_BYTES_READ, readBytes); int i = 0; diff --git a/test/com/linkedin/drelephant/analysis/heuristics/MapperSpillHeuristicTest.java b/test/com/linkedin/drelephant/analysis/heuristics/MapperSpillHeuristicTest.java index 5773090ad..7f46d8adc 100644 --- a/test/com/linkedin/drelephant/analysis/heuristics/MapperSpillHeuristicTest.java +++ b/test/com/linkedin/drelephant/analysis/heuristics/MapperSpillHeuristicTest.java @@ -1,16 +1,13 @@ package com.linkedin.drelephant.analysis.heuristics; -import java.io.IOException; -import java.util.HashMap; - import com.linkedin.drelephant.analysis.Constants; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; import com.linkedin.drelephant.hadoop.HadoopCounterHolder; -import com.linkedin.drelephant.hadoop.HadoopCounterHolder.CounterName; import com.linkedin.drelephant.hadoop.HadoopJobData; import com.linkedin.drelephant.hadoop.HadoopTaskData; +import java.io.IOException; import junit.framework.TestCase; @@ -40,10 +37,10 @@ public void testNone() throws IOException { } private Severity analyzeJob(long spilledRecords, long mapRecords) throws IOException { - HadoopCounterHolder jobCounter = new HadoopCounterHolder(null); + HadoopCounterHolder jobCounter = new HadoopCounterHolder(); HadoopTaskData[] mappers = new HadoopTaskData[numTasks]; - HadoopCounterHolder counter = new HadoopCounterHolder(new HashMap()); + HadoopCounterHolder counter = new HadoopCounterHolder(); counter.set(HadoopCounterHolder.CounterName.SPILLED_RECORDS, spilledRecords); counter.set(HadoopCounterHolder.CounterName.MAP_OUTPUT_RECORDS, mapRecords); diff --git a/test/com/linkedin/drelephant/analysis/heuristics/ReducerDataSkewHeuristicTest.java b/test/com/linkedin/drelephant/analysis/heuristics/ReducerDataSkewHeuristicTest.java index 5132efd1e..97a7f51a9 100644 --- a/test/com/linkedin/drelephant/analysis/heuristics/ReducerDataSkewHeuristicTest.java +++ b/test/com/linkedin/drelephant/analysis/heuristics/ReducerDataSkewHeuristicTest.java @@ -1,17 +1,13 @@ package com.linkedin.drelephant.analysis.heuristics; -import java.io.IOException; -import java.util.HashMap; - import com.linkedin.drelephant.analysis.Constants; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; import com.linkedin.drelephant.hadoop.HadoopCounterHolder; -import com.linkedin.drelephant.hadoop.HadoopCounterHolder.CounterName; import com.linkedin.drelephant.hadoop.HadoopJobData; import com.linkedin.drelephant.hadoop.HadoopTaskData; - +import java.io.IOException; import junit.framework.TestCase; @@ -49,13 +45,13 @@ public void testSmallTasks() throws IOException { private Severity analyzeJob(int numSmallTasks, int numLargeTasks, long smallInputSize, long largeInputSize) throws IOException { - HadoopCounterHolder jobCounter = new HadoopCounterHolder(null); + HadoopCounterHolder jobCounter = new HadoopCounterHolder(); HadoopTaskData[] reducers = new HadoopTaskData[numSmallTasks + numLargeTasks]; - HadoopCounterHolder smallCounter = new HadoopCounterHolder(new HashMap()); + HadoopCounterHolder smallCounter = new HadoopCounterHolder(); smallCounter.set(HadoopCounterHolder.CounterName.REDUCE_SHUFFLE_BYTES, smallInputSize); - HadoopCounterHolder largeCounter = new HadoopCounterHolder(new HashMap()); + HadoopCounterHolder largeCounter = new HadoopCounterHolder(); largeCounter.set(HadoopCounterHolder.CounterName.REDUCE_SHUFFLE_BYTES, largeInputSize); int i = 0; diff --git a/test/com/linkedin/drelephant/analysis/heuristics/ReducerTimeHeuristicTest.java b/test/com/linkedin/drelephant/analysis/heuristics/ReducerTimeHeuristicTest.java index 4d19569f2..bae417469 100644 --- a/test/com/linkedin/drelephant/analysis/heuristics/ReducerTimeHeuristicTest.java +++ b/test/com/linkedin/drelephant/analysis/heuristics/ReducerTimeHeuristicTest.java @@ -58,7 +58,7 @@ public void testLongRunetimeNone() throws IOException { } private Severity analyzeJob(long runtimeMs, int numTasks) throws IOException { - HadoopCounterHolder dummyCounter = new HadoopCounterHolder(null); + HadoopCounterHolder dummyCounter = new HadoopCounterHolder(); HadoopTaskData[] reducers = new HadoopTaskData[numTasks]; int i = 0; diff --git a/test/com/linkedin/drelephant/analysis/heuristics/ShuffleSortHeuristicTest.java b/test/com/linkedin/drelephant/analysis/heuristics/ShuffleSortHeuristicTest.java index 6beb43e5d..b528d22e8 100644 --- a/test/com/linkedin/drelephant/analysis/heuristics/ShuffleSortHeuristicTest.java +++ b/test/com/linkedin/drelephant/analysis/heuristics/ShuffleSortHeuristicTest.java @@ -68,7 +68,7 @@ public void testShortSort() throws IOException { } private Severity analyzeJob(long shuffleTimeMs, long sortTimeMs, long reduceTimeMs) throws IOException { - HadoopCounterHolder dummyCounter = new HadoopCounterHolder(null); + HadoopCounterHolder dummyCounter = new HadoopCounterHolder(); HadoopTaskData[] reducers = new HadoopTaskData[NUMTASKS]; int i = 0; From 467794bb6e09fe49d2e9d1851a02d07967a9847e Mon Sep 17 00:00:00 2001 From: Subbu Subramaniam Date: Mon, 17 Nov 2014 13:27:36 -0800 Subject: [PATCH 041/272] [HADOOP-7814] Publish counters via dr-elephant The java file DaliMetricsAPI.java has a flavor of the APIs that we will be exposing from the dali library. We can split these classes into individual files when we move this functionality to the dali library. Changed start script to look for a config file that configures a publisher. If the file is present, then dr-elephant is started with an option that has the file name. If the file is not present, then the behavior is unchanged (i.e. no metrics are published). If the file is parsed correctly then dr-elephant publishes metrics in HDFS (one avro file per job) for jobs that are configured to publish the metrics. The job needs to set something like mapreduce.job.publish-counters='org.apache.hadoop.examples.WordCount$AppCounter:*' to publish all counters in the group given. The format is : 'groupName:counterName' where counterName can be an asterisk to indicate all counters in the group. See the class DaliMetricsAPI.CountersToPublish The HDFSPublisher is configured with a base path under which metrics are published. The date/hour hierarchy is added to the base path. The XML file for configuring dr-elephant is checked in as a template. A config file needs to be added to the 'conf' path of dr-elephant (manually, as per meeting with hadoop-admin) on clusters where we want dr-elephant to publish metrics. RB=409443 BUGS=HADOOP-7814 R=fli,csteinba,mwagner,cbotev,ahsu A=fli,ahsu --- .../linkedin/drelephant/DaliMetricsAPI.java | 777 ++++++++++++++++++ .../linkedin/drelephant/ElephantRunner.java | 85 +- .../hadoop/HadoopCounterHolder.java | 7 + build.sbt | 5 +- conf/CounterPublisherConf.xml.template | 25 + start.sh | 13 +- 6 files changed, 900 insertions(+), 12 deletions(-) create mode 100644 app/com/linkedin/drelephant/DaliMetricsAPI.java create mode 100644 conf/CounterPublisherConf.xml.template diff --git a/app/com/linkedin/drelephant/DaliMetricsAPI.java b/app/com/linkedin/drelephant/DaliMetricsAPI.java new file mode 100644 index 000000000..c8ca6425c --- /dev/null +++ b/app/com/linkedin/drelephant/DaliMetricsAPI.java @@ -0,0 +1,777 @@ +package com.linkedin.drelephant; + +import com.linkedin.events.KafkaAuditHeader; +import com.linkedin.events.hadoop.AggregationScope; +import com.linkedin.events.hadoop.AzkabanContext; +import com.linkedin.events.hadoop.Counter; +import com.linkedin.events.hadoop.ExecutionContext; +import com.linkedin.events.hadoop.HadoopHeader; +import com.linkedin.events.hadoop.HadoopMetricsEvent; +import com.linkedin.events.hadoop.State; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.StringTokenizer; +import java.util.TimeZone; +import java.util.UUID; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import org.apache.avro.Schema; +import org.apache.avro.file.DataFileWriter; +import org.apache.avro.generic.GenericDatumWriter; +import org.apache.avro.generic.IndexedRecord; +import org.apache.avro.io.DatumWriter; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.FSDataOutputStream; +import org.apache.hadoop.fs.FileSystem; +import org.apache.hadoop.fs.Path; +import org.apache.hadoop.fs.permission.FsPermission; +import org.apache.log4j.Logger; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + + +/** + * TODO Decide if the contained classes should be split out. + * + * This class provides a set of classes that can be used to construct and publish hadoop + * MetricsEvent. The inner classes are: + * + * CountersToPublish: This class takes in a hadoop configuration value (string) that indicates which counters are to be published + * when the job finishes. The key for this configuration is defined in the JobProperties class. + * EventContext: This is one of the two objects that an application constructs if a metrics event is to be published. The event context + * has the start and end time window in which the metrics were gathered,and also the job name and container ID. + * JobProperties: THis is the other object that the application needs to construct if a metrics event is to be published. + * It is filled in from the hadoop job configuration. It has the azkaban related fields, and a couple of other config + * properties. It also has the config property of the counters to publish. JobProperties uses that to construct + * CountersToPublish + * HadoopCounters: Represents the metrics event that we need to publish. In the final API exposed to (map-reduce) applications, + * I expect another class called HadoopMetrics (or some such) that allows applications to add counters, gauges, etc. + * Application must construct this class (with JobProperties and EventContext) in order to publish an event. + * The build() method in this class returns an avro IndexedRecord that can then be handed to the publisher to publish. + * CommonEventsHeaderBuilder: A private class used by the metrics library to construct the kafka audit header. + * + * MetricsPublisher: An interface that every event publisher must implement. Has a publish() method to publish an + * IndexedRecord. + * HDFSMetricsPublisher: Implements a class that publishes metrics to HDFS. It is constructed via a static method + * (should really be in a factory) that takes an XML file as input. The XML file has configuration parameters for + * the HDFS publisher (e.g. the root directory under which events should be published). + */ +public class DaliMetricsAPI { + private static final Logger LOG = Logger.getLogger(DaliMetricsAPI.class); + + /** + * A helper class to keep track of which metrics to publish. + */ + public static class CountersToPublish { + private static final String ALL_COUNTERS = "*"; + private final Map> _countersToPublish; + /** + * @param counterNames A string of the format "groupName:counterName,groupName:counterName,..." + * counterName can be '*' meaning all counters in the group. + * @return A map with one key per counter group -- the value being a set + */ + public CountersToPublish(String counterNames) { + Map> countersToPublish = null; + + if (counterNames != null && counterNames.length() != 0) { + countersToPublish = new HashMap>(4); + StringTokenizer st = new StringTokenizer(counterNames, ","); + + while (st.hasMoreTokens()) { + String val = st.nextToken(); + String[] groupCounter = val.split(":"); + if (groupCounter.length != 2) { + // Malformed group+counter + LOG.error("Ignoring malformed group/counter pair:'" + val + "'"); + continue; + } + if (groupCounter[1] == null) { + // TODO + LOG.error("Ignoring malformed counter name:'" + val + "'"); + continue; + } + String groupName = groupCounter[0]; + String counterName = groupCounter[1]; + if (!countersToPublish.containsKey(groupName)) { + countersToPublish.put(groupName, new HashSet(4)); + } + countersToPublish.get(groupName).add(counterName); + } + } + _countersToPublish = countersToPublish; + } + + /* + * Returns true if no counters need to be published. + */ + public boolean isEmpty() { + return _countersToPublish == null; + } + + /* + * Returns true if the specified counter is to be published. + */ + public boolean hasCounter(String groupName, String counterName) { + if (_countersToPublish == null) { + return false; + } + Set countersInGroup = _countersToPublish.get(groupName); + if (countersInGroup == null) { + return false; + } + if (countersInGroup.contains(ALL_COUNTERS) || countersInGroup.contains(counterName)) { + return true; + } + return false; + } + } + + /** + * EventContext is a helper class that the application needs to fill in before + * requesting to publish counters for a job. + * + * In the more general case of a task publishing metrics, we need to expand this + * to contain a builder that can populate the members. + * + * Keeping code to a minimum for now. + */ + public static class EventContext { + private final String _containerId; + private final AggregationScope _scope; + private final State _state; + private final String _jobName; + private final String _jobId; + private final long _startTime; + private final long _endTime; + + /** + * For now, we only publish for successful jobs, so this constructor works. + * @param jobName hadoop job name from JobReport + * @param jobId hadoop job Id from JobReport + */ + public EventContext(String jobName, String jobId, long startTime, long endTime) { + _containerId = null; + _scope = AggregationScope.JOB; + _state = State.SUCCEEDED; + _jobName = jobName; + _jobId = jobId; + _startTime = startTime; + _endTime = endTime; + } + + public String getContainerId() { + return _containerId; + } + + public String getJobName() { + return _jobName; + } + + public String getJobId() { + return _jobId; + } + + public State getState() { + return _state; + } + + public AggregationScope getScope() { + return _scope; + } + + public long getStartTime() { + return _startTime; + } + + public long getEndTime() { + return _endTime; + } + + /* + * TODO + * - Add a builder (setSuccess(), setJobScope() etc. may make more sense than exposing 'State', for example) + * - May be sub-class with a container Id for task-level API? + */ + } + + /** + * JobProperties is a helper class that the application needs to fill in before + * requesting to publish counters for a job. + * + * Inside the cluster, Configuration can be used to populate this. + */ + public static class JobProperties { + private static final String UNKNOWN_CLUSTERNAME = "Unknown"; + private static final String UNKNOWN_USERNAME = "Unknown"; + + private static final String CONFIG_KEY_PUB_COUNTERS = "mapreduce.job.publish-counters"; + private static final String CONFIG_KEY_AZ_JOBID = "azkaban.job.id"; + private static final String CONFIG_KEY_AZ_FLOWID = "azkaban.flow.flowid"; + private static final String CONFIG_KEY_AZ_PROJNAME = "azkaban.flow.projectname"; + private static final String CONFIG_KEY_AZ_PROJVER = "azkaban.flow.projectversion"; + private static final String CONFIG_KEY_AZ_PROJ_LAST_CHANGED_DATE = "azkaban.flow.projectlastchangeddate"; + private static final String CONFIG_KEY_AZ_EXECURL = "azkaban.link.jobexec.url"; + private static final String CONFIG_KEY_AZ_EXECID = "azkaban.flow.execid"; + private static final String CONFIG_KEY_NN_HOSTPORT = "dfs.namenode.http-address"; + private static final String CONFIG_KEY_USER_NAME = "mapreduce.job.user.name"; + + private final CountersToPublish _countersToPublish; + private final String _azkabanJobId; + private final String _azkabanFlowId; + private final String _azkabanProjectName; + private final String _azkabanProjectVersion; + private final long _azkabanProjectLastChangedDate; + private final String _azkabanHostName; + private final int _execId; + private final String _clusterName; + private final String _userName; + + public JobProperties(Properties properties) { + _countersToPublish = new CountersToPublish(properties.getProperty(CONFIG_KEY_PUB_COUNTERS)); + _azkabanJobId = properties.getProperty(CONFIG_KEY_AZ_JOBID); + _azkabanFlowId = properties.getProperty(CONFIG_KEY_AZ_FLOWID); + _execId = decodeExecId(properties.getProperty(CONFIG_KEY_AZ_EXECID)); + _azkabanProjectName = properties.getProperty(CONFIG_KEY_AZ_PROJNAME); + _azkabanProjectVersion = properties.getProperty(CONFIG_KEY_AZ_PROJVER); + _azkabanProjectLastChangedDate = parseDate(properties.getProperty(CONFIG_KEY_AZ_PROJ_LAST_CHANGED_DATE)); + _azkabanHostName = url2HostName(properties.getProperty(CONFIG_KEY_AZ_EXECURL)); + _clusterName = nnHostPort2ClusterName(properties.getProperty(CONFIG_KEY_NN_HOSTPORT)); + _userName = properties.getProperty(CONFIG_KEY_USER_NAME); + } + + // TODO Add a constructor with org.apache.hadoop.mapreduce.v2.api.records.JobReport for use inside cluster? + + /* + * We get a string that should parse into a long value of msecs since epoch. + */ + private long parseDate(final String msecsSinceEpoch) { + long secsSinceEpoch = -1; + if (msecsSinceEpoch == null) { + return secsSinceEpoch; + } + try { + secsSinceEpoch = Long.parseLong(msecsSinceEpoch)/1000L; + } catch (NumberFormatException e) { + LOG.warn("Could not parse msecs since epoch " + msecsSinceEpoch); + } + return secsSinceEpoch; + } + + private int decodeExecId(String execIdStr) { + int execId = -1; + if (execIdStr != null) { + try { + execId = Integer.parseInt(execIdStr); + } catch (NumberFormatException e) { + // TODO Log the exception? + LOG.warn("Illegal value for execId:'" + execIdStr + "'"); + } + } + return execId; + } + + private String url2HostName(String urlStr) { + String hostName = null; + if (urlStr != null && urlStr.length() > 0) { + try { + URL url = new URL(urlStr); + hostName = url.getHost(); + } catch (MalformedURLException e) { + // TODO TBD log the exception? + LOG.warn("Could not determine hostname from URL:'" + urlStr + "'"); + } + } + return hostName; + } + + /** + * Helper method to map a name node URL to a cluster name. It is assumed that the URL is of the form + * datacenter-clusternamennNN.grid.linkedin.com (where N is a digit, 'clustername' is the name of the cluster) + * @param hostPort + * @return name of the cluster or "Unknown" if cluster name cannot be determined. + */ + private String nnHostPort2ClusterName(String hostPort) { + if (hostPort == null || hostPort.length() == 0) { + return UNKNOWN_CLUSTERNAME; + } + String hostName = url2HostName("http://" + hostPort); + if (hostName == null) { + return UNKNOWN_CLUSTERNAME; + } + // Assume the hostname is of the type -nn.grid.linkedin.com + // May not work all the time! + String hostNameSansDomain = hostName.replace(".grid.linkedin.com", ""); + if (hostNameSansDomain.length() < 7) { + // need 4 chars for nn and one char for '-' and at least one char for datacenter, and one char for clusterName + return UNKNOWN_CLUSTERNAME; + } + int clusterNameStart = hostNameSansDomain.indexOf('-'); + int clusterNameEnd = hostNameSansDomain.length()-5; + return hostNameSansDomain.substring(clusterNameStart, clusterNameEnd); + } + + public CountersToPublish getCountersToPublish() { + return _countersToPublish; + } + + public long getAzkabanProjectLastChangedDate() { + return _azkabanProjectLastChangedDate; + } + + public int getExecId() { + return _execId; + } + + public String getAzkabanHostName(final String defaultValue) { + return _azkabanHostName == null ? defaultValue : _azkabanHostName; + } + + public String getAzkabanProjectVersion(final String defaultValue) { + return _azkabanProjectVersion == null ? defaultValue : _azkabanProjectVersion; + } + + public String getAzkabanFlowId(final String defaultValue) { + return _azkabanFlowId == null ? defaultValue : _azkabanFlowId; + } + + public String getAzkabanJobId(final String defaultValue) { + return _azkabanJobId == null ? defaultValue : _azkabanJobId; + } + + public String getAzkabanProjectName(final String defaultValue) { + return _azkabanProjectName == null ? defaultValue : _azkabanProjectName; + } + + public String getClusterName() { + return _clusterName; + } + + public String getUserName() { + return _userName; + } + + // If any of the properties are set, then the job is coming through azkaban. + public boolean hasAnyAzkabanProperties() { + return _execId != -1 || + _azkabanFlowId != null || + _azkabanHostName != null || + _azkabanJobId != null || + _azkabanProjectLastChangedDate != -1L || + _azkabanProjectName != null || + _azkabanProjectVersion != null; + } + + // TODO Add a constructor from org.apache.hadoop.conf.Configuration + } + + /** + * A class that the applications can use to construct the HadoopMetricsEvent avro + * event and hand it to the publisher to publish. + * + * This class takes care of populating the headers from the data given, and provides methods + * to add counters to the event. + */ + public static class HadoopCounters { + private final CountersToPublish _countersToPublish; + private final HadoopMetricsEvent _hadoopMetricsEvent; + private final List _counters; + private int _numCounters = 0; + + public HadoopCounters(EventContext eventContext, JobProperties jobProperties) { + _countersToPublish = jobProperties.getCountersToPublish(); + + _counters = new LinkedList(); + + AzkabanContext azkabanContext = null; + // Fill in Azkaban Context + if (jobProperties.hasAnyAzkabanProperties()) { + azkabanContext = new AzkabanContext(); + azkabanContext.executionId = jobProperties.getExecId(); + azkabanContext.flowName = jobProperties.getAzkabanFlowId("Unknown"); + azkabanContext.hostName = jobProperties.getAzkabanHostName("Unknown"); + azkabanContext.lastChangedDate = jobProperties.getAzkabanProjectLastChangedDate(); + azkabanContext.projectName = jobProperties.getAzkabanProjectName("Unknown"); + azkabanContext.projectVersion = jobProperties.getAzkabanProjectVersion("Unknown"); + azkabanContext.jobName = jobProperties.getAzkabanJobId("Unknown"); + } + + + ExecutionContext executionContext = new ExecutionContext(); + executionContext = new ExecutionContext(); + executionContext.applicationId = eventContext.getJobId(); + executionContext.applicationName = eventContext.getJobName(); + executionContext.state = eventContext.getState(); + executionContext.containerId = eventContext.getContainerId(); + executionContext.userName = jobProperties.getUserName(); + executionContext.clusterName = jobProperties.getClusterName(); + + HadoopHeader hadoopHeader = new HadoopHeader(); + hadoopHeader = new HadoopHeader(); + hadoopHeader.aggregationScope = eventContext.getScope(); + hadoopHeader.startTime = eventContext.getStartTime(); + hadoopHeader.endTime = eventContext.getEndTime(); + hadoopHeader.azkabanContext = azkabanContext; + hadoopHeader.executionContext = executionContext; + + _hadoopMetricsEvent = new HadoopMetricsEvent(); + _hadoopMetricsEvent.hadoopHeader = hadoopHeader; + _hadoopMetricsEvent.auditHeader = new CommonEventHeaderBuilder().build(); + } + + /** + * Add a counter to the list of counters to be published, if the counter is configured to be + * published. If not, then do nothing. + * + * TODO Replace a counter that is already added? Or throw error? + * + * @param groupName + * @param counterName + * @param value + */ + public void addCounter(String groupName, String counterName, long value) { + if (_countersToPublish.hasCounter(groupName, counterName)) { + Counter counter = new Counter(); + counter.counterGroup = groupName; + counter.counterName = counterName; + counter.value = value; + _counters.add(counter); + _numCounters++; + } + } + + /** + * @return the total number of counters added to the publish list. + */ + public int getNumCounters() { + return _numCounters; + } + + /** + * Build the avro event and return an IndexedRecord. + * @note It is not guaranteed that this record will be serialized correctly, since no verification is done here + * TODO Add verification so that avro publish will not fail. Or, return a serialized bytebuffer? + * @return + */ + public IndexedRecord build() { + _hadoopMetricsEvent.counters = _counters; + return _hadoopMetricsEvent; + } + } + + /** + * A helper class to fill in the kafka audit header. + * This class should not be exposed outside the DaliMetrics API. + */ + static class CommonEventHeaderBuilder { + private static final String HADOOP_SERVICE = "hadoop"; + private static final String UNKNOWN_HOSTNAME = "Unknown"; + + private static String hostName = null; + + private final KafkaAuditHeader _auditHeader; + public CommonEventHeaderBuilder() { + _auditHeader = new KafkaAuditHeader(); + } + public KafkaAuditHeader build() { + _auditHeader.time = System.currentTimeMillis(); + _auditHeader.server = getHostName(); + _auditHeader.appName = HADOOP_SERVICE; + byte[] bytes = getUUID(); + com.linkedin.events.UUID liUUID = new com.linkedin.events.UUID(); + System.arraycopy(bytes, 0, liUUID.bytes(), 0, bytes.length); + _auditHeader.messageId = liUUID; + + return _auditHeader; + } + + /** + * A helper method to get (and cache) the hostname + * @return name of this host or "Unknown" if there are errors + */ + private synchronized static String getHostName() { + + if (hostName != null) { + return hostName; + } + try { + hostName = InetAddress.getLocalHost().getHostName(); + return hostName; + } catch (UnknownHostException e) { + LOG.warn("Could not determine hostname", e); + return UNKNOWN_HOSTNAME; + } + } + + /** + * A helper method to return a 16-byte UUID + * @return UUID of 16 bytes + */ + private byte[] getUUID() { + final UUID uuid = UUID.randomUUID(); + final ByteBuffer bb = ByteBuffer.allocate(16); // Default is big-endian + bb.putLong(uuid.getMostSignificantBits()); + bb.putLong(uuid.getLeastSignificantBits()); + return bb.array(); + } + + } + + /** + * This interface us to be implemented by all the publishers. + */ + public interface MetricsPublisher { + /** + * A thread-safe publish method that publishes an event + * @param event + * @throws IOException + */ + public void publish(IndexedRecord event) throws IOException; + } + + /** + * An implementation of a publisher to publish to HDFS file system. + */ + public static class HDFSMetricsPublisher implements MetricsPublisher { + private static String DEFAULT_BASE_PATH = "/data/HadoopMetrics/jobs"; + + private final String _basePath; + + private HDFSMetricsPublisher(String basePath) { + if (basePath.endsWith("/")) { + _basePath = basePath; + } else { + _basePath = basePath + "/"; + } + } + + /** + * @param configFileName + * @return null if the config file does not exist or hdfs publishers not configured, or any misconfiguration is detected. + */ + public static MetricsPublisher createFromXml(String configFileName) { + MetricsPublisher metricsPublisher = null; + if (configFileName != null) { + LOG.info("Attempting to read metrics publish configuration from " + configFileName); + File configFile = new File(configFileName); + try { + FileInputStream instream = new FileInputStream(configFile); + metricsPublisher = getHdfsMetricsPublisher(configFileName, instream); + } catch (FileNotFoundException e) { + // Nothing to publish if file is not there. + LOG.info(configFileName + " not found"); + } + } + if (metricsPublisher == null) { + LOG.info("Metrics will not be published"); + } + return metricsPublisher; + } + + private static HDFSMetricsPublisher getHdfsMetricsPublisher(String logmsgPrefix, InputStream instream) { + try { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + Document document = builder.parse(instream); + NodeList publishers = document.getElementsByTagName("publisher"); + if (publishers.getLength() == 0) { + LOG.info("No publishers configured"); + return null; + } + Element publisherNode = (Element)publishers.item(0); + NodeList schemes = publisherNode.getElementsByTagName("scheme"); + if (schemes.getLength() == 0) { + LOG.info("No publish scheme configured"); + return null; + } + Element schemeElement = (Element)schemes.item(0); + if (schemeElement.getTextContent() == null || + !schemeElement.getTextContent().equals("hdfs")) { + LOG.info("Publish scheme must be \"hdfs\""); + return null; + } + NodeList basePathNodes = publisherNode.getElementsByTagName("basepath"); + if (basePathNodes == null || basePathNodes.getLength() == 0) { + LOG.info("HDFS Base path not configured, assumed " + DEFAULT_BASE_PATH); + return new HDFSMetricsPublisher(DEFAULT_BASE_PATH); + } + Element basePathElement = (Element)basePathNodes.item(0); + if (basePathElement == null || basePathElement.getTextContent()== null) { + LOG.info("HDFS Base path not configured, assumed " + DEFAULT_BASE_PATH); + return new HDFSMetricsPublisher(DEFAULT_BASE_PATH); + } + String basePath = basePathElement.getTextContent(); + if (!basePath.startsWith("/")) { + LOG.error("Ignoring illegal value for basepath:'" + basePath + "'(must start with '/'). Using " + + DEFAULT_BASE_PATH + " instead"); + return new HDFSMetricsPublisher(DEFAULT_BASE_PATH); + } + return new HDFSMetricsPublisher(basePath); + } catch (ParserConfigurationException e) { + throw new RuntimeException(logmsgPrefix + ":Exception during parse", e); + } catch (SAXException e) { + throw new RuntimeException(logmsgPrefix + ":Exception during parse", e); + } catch (IOException e) { + throw new RuntimeException(logmsgPrefix + ":Exception during parse", e); + } + } + + private Object extractField(final IndexedRecord indexedRecord, + final String fieldName, + final boolean mustBeRecord) + throws IOException { + + final Schema schema = indexedRecord.getSchema(); + final Schema.Field field = schema.getField(fieldName); + if (field == null) { + throw new IOException(fieldName + ":No such field in " + schema.getName()); + } + final int fieldPos = field.pos(); + final Object fieldValue = indexedRecord.get(fieldPos); + if (fieldValue == null) { + throw new IOException(fieldName + ":Null value in " + schema.getName()); + } + if (mustBeRecord) { + final Schema fieldSchema = field.schema(); + if (fieldSchema == null) { + throw new IOException(fieldName + ":No such record in " + schema.getName()); + } + } + + return fieldValue; + } + + private long extractPublishTime(IndexedRecord event) + throws IOException { + + IndexedRecord auditHeader = (IndexedRecord)extractField(event, "auditHeader", true); + Long time = (Long)extractField(auditHeader, "time", false); + return time; + } + + private String extractApplicationId(IndexedRecord event) + throws IOException { + + IndexedRecord hadoopHeader = (IndexedRecord)extractField(event, "hadoopHeader", true); + IndexedRecord executionContext = (IndexedRecord)extractField(hadoopHeader, "executionContext", true); + String applicationId = (String)extractField(executionContext, "applicationId", false); + return applicationId; + } + + @Override + public void publish(IndexedRecord event) + throws IOException { + + long pubTime = extractPublishTime(event); + String applicationId = extractApplicationId(event); + String path = getPathName(pubTime, applicationId); + + OutputStream outStream = getOutputStream(path); + + writeToStream(outStream, event); + } + + private OutputStream getOutputStream(String pathName) + throws IOException { + + Configuration config = new Configuration(); + // TODO Decide permissions correctly. Right now, this is world-reasable, but perhaps it should be group-readable? + FsPermission perms = new FsPermission((short)0644); + Path path = new Path(pathName); + FileSystem fs = null; + try { + fs = path.getFileSystem(config); + } catch (IOException e) { + LOG.error("Could not get file system", e); + throw e; + } + LOG.debug("Publishing metrics to " + fs + ", path=" + path); + + try { + FSDataOutputStream fileOutputStream = FileSystem.create(fs, path, perms); + return fileOutputStream; + } catch (IOException e) { + LOG.error("Exception writing to avro file", e); + throw e; + } + } + + private String getPathName(long pubTime, String fileName) { + GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("UTC")); + cal.setTime(new Date(pubTime)); + String year = Integer.toString(cal.get(Calendar.YEAR)); + String month = String.format("%02d", cal.get(Calendar.MONTH) + 1); // Month seems to start from 0! + String day = String.format("%02d", cal.get(Calendar.DATE)); + String hour = String.format("%02d", cal.get(Calendar.HOUR_OF_DAY)); + String path = _basePath + "/hourly/" + year + "/" + month + "/" + day + "/" + hour + "/" + fileName + ".avro"; + return path; + } + + private void writeToStream(OutputStream outStream, IndexedRecord event) + throws IOException { + DatumWriter dw = new GenericDatumWriter(event.getSchema()); + DataFileWriter dfw = new DataFileWriter(dw); + dfw.create(event.getSchema(), outStream); + dfw.append(event); + dfw.close(); + } + } + + // For testing purposes + public static void main(String[] args) throws Exception { + Properties props = new Properties(); + props.setProperty(JobProperties.CONFIG_KEY_PUB_COUNTERS, "fruits:*"); + props.setProperty(JobProperties.CONFIG_KEY_AZ_EXECID, "21345"); + props.setProperty(JobProperties.CONFIG_KEY_AZ_EXECURL, "http://host:6556/exec/url"); + props.setProperty(JobProperties.CONFIG_KEY_AZ_FLOWID, "flowId"); + props.setProperty(JobProperties.CONFIG_KEY_AZ_JOBID, "jobId"); + props.setProperty(JobProperties.CONFIG_KEY_AZ_PROJ_LAST_CHANGED_DATE, "2014-12-10"); + props.setProperty(JobProperties.CONFIG_KEY_AZ_PROJNAME, "projName"); + props.setProperty(JobProperties.CONFIG_KEY_AZ_PROJVER, "v1.3"); + props.setProperty(JobProperties.CONFIG_KEY_NN_HOSTPORT, "http://eat1-magicnn01:8723/name/node"); + props.setProperty(JobProperties.CONFIG_KEY_USER_NAME, "user"); + JobProperties jp = new JobProperties(props); + + EventContext ec = new EventContext("jobName", "jobId", 123456L, 234567L); + + HadoopCounters hm = new HadoopCounters(ec, jp); + hm.addCounter("fruits", "apple", 45); + IndexedRecord hme = hm.build(); + publish(hme); + } + + public static void publish(IndexedRecord event) + throws IOException { + String path = "/tmp/xx.avro"; + OutputStream outStream = new FileOutputStream(new File(path)); + + DatumWriter dw = new GenericDatumWriter(event.getSchema()); + DataFileWriter dfw = new DataFileWriter(dw); + dfw.create(event.getSchema(), outStream); + dfw.append(event); + dfw.close(); + } + +} diff --git a/app/com/linkedin/drelephant/ElephantRunner.java b/app/com/linkedin/drelephant/ElephantRunner.java index 53fd497b0..2e7df01c7 100644 --- a/app/com/linkedin/drelephant/ElephantRunner.java +++ b/app/com/linkedin/drelephant/ElephantRunner.java @@ -1,42 +1,61 @@ package com.linkedin.drelephant; +import com.linkedin.drelephant.analysis.Constants; +import com.linkedin.drelephant.analysis.HeuristicResult; +import com.linkedin.drelephant.analysis.Severity; +import com.linkedin.drelephant.hadoop.HadoopCounterHolder; +import com.linkedin.drelephant.hadoop.HadoopJobData; +import com.linkedin.drelephant.hadoop.HadoopSecurity; +import com.linkedin.drelephant.notifications.EmailThread; import java.io.IOException; import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicBoolean; - import model.JobHeuristicResult; import model.JobResult; import model.JobType; - +import org.apache.avro.generic.IndexedRecord; import org.apache.hadoop.mapred.JobConf; import org.apache.log4j.Logger; - -import com.linkedin.drelephant.analysis.Constants; -import com.linkedin.drelephant.analysis.HeuristicResult; -import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.hadoop.HadoopJobData; -import com.linkedin.drelephant.hadoop.HadoopSecurity; -import com.linkedin.drelephant.notifications.EmailThread; +import play.Play; public class ElephantRunner implements Runnable { private static final long WAIT_INTERVAL = 60 * 1000; private static final int EXECUTOR_NUM = 3; + private static final String OPT_METRICS_PUB_CONF = "metrics.publisher-conf"; private static final Logger logger = Logger.getLogger(ElephantRunner.class); private AtomicBoolean _running = new AtomicBoolean(true); private long lastRun; private EmailThread _emailer = new EmailThread(); + private final DaliMetricsAPI.MetricsPublisher _metricsPublisher; private HadoopSecurity _hadoopSecurity; private InfoExtractor _urlRetriever = new InfoExtractor(); private ExecutorService _service; private BlockingQueue _jobQueue; + public ElephantRunner() { + // The getFile() API of Play returns a File object whether or not the actual file exists. + String metricsPublisherConfPath = Play.application().configuration().getString(OPT_METRICS_PUB_CONF); + if (metricsPublisherConfPath == null) { + logger.info("Metrics publisher not configured. No metrics will be published"); + _metricsPublisher = null; + } else { + _metricsPublisher = DaliMetricsAPI.HDFSMetricsPublisher.createFromXml(metricsPublisherConfPath); + if (_metricsPublisher == null) { + logger.info("No metrics will be published"); + } + } + } + @Override public void run() { logger.info("Dr.elephant has started"); @@ -136,6 +155,7 @@ public Void run() { } private class ExecutorThread implements Runnable { + private int _threadId; private BlockingQueue _jobQueue; private ElephantFetcher _fetcher; @@ -213,11 +233,58 @@ private void analyzeJob(HadoopJobData jobData, int execThreadNum) { result.severity = worstSeverity; _urlRetriever.retrieveURLs(result, jobData); + if (_metricsPublisher != null) { + publishMetrics(jobData); + } + result.save(); _emailer.enqueue(result); } + private void publishMetrics(HadoopJobData jobData) { + Properties jobConf = jobData.getJobConf(); + // We may have something to publish, but we don't know until we have iterated through the counters that we have. + // We assume that we need to publish something until we find out we don't. + DaliMetricsAPI.JobProperties jobProperties = new DaliMetricsAPI.JobProperties(jobConf); + if (jobProperties.getCountersToPublish().isEmpty()) { + // Nothing to do + return; + } + DaliMetricsAPI.EventContext eventContext = new DaliMetricsAPI.EventContext(jobData.getJobName(), + jobData.getJobId(), + jobData.getStartTime(), + jobData.getFinishTime()); + DaliMetricsAPI.HadoopCounters metricsEvent = new DaliMetricsAPI.HadoopCounters(eventContext, jobProperties); + + HadoopCounterHolder counterHolder = jobData.getCounters(); + Set groupNames = counterHolder.getGroupNames(); + for (String group : groupNames) { + Map counters = counterHolder.getAllCountersInGroup(group); + for (Map.Entry entry : counters.entrySet()) { + String counterName = entry.getKey(); + Long value = entry.getValue(); + metricsEvent.addCounter(group, counterName, value); + } + } + if (metricsEvent.getNumCounters() == 0) { + // The counters that were configured were not collected in HadoopCounterHolder. + return; + } + IndexedRecord event = metricsEvent.build(); + try { + _metricsPublisher.publish(event); + } catch (IOException e) { + // The lower level should have logged a message. + // A checked exception from the publish() call should mean that the event was not formed correctly for some reason. + // Aside from a code bug, the most common reason for this will probably be that some mandatory fields in the + // event were missing. + // Could also mean that some derived values (e.g. hostname to URL, execId translation to an integer, etc.) + // may have failed. There is little we can do at this point to fix those, so ignore the exception. + logger.warn("Publish failed:" + e); + } + } + private void waitInterval() { // Wait for long enough long nextRun = lastRun + WAIT_INTERVAL; diff --git a/app/com/linkedin/drelephant/hadoop/HadoopCounterHolder.java b/app/com/linkedin/drelephant/hadoop/HadoopCounterHolder.java index c485466e1..faf87949f 100644 --- a/app/com/linkedin/drelephant/hadoop/HadoopCounterHolder.java +++ b/app/com/linkedin/drelephant/hadoop/HadoopCounterHolder.java @@ -1,7 +1,9 @@ package com.linkedin.drelephant.hadoop; +import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.Set; public class HadoopCounterHolder { @@ -64,6 +66,11 @@ public long get(String groupName, String counterName) return value != null ? value : -1; } + public Set getGroupNames() { + Set groupNames = _pubCounters.keySet(); + return Collections.unmodifiableSet(groupNames); + } + /** * Get the values of all counters in a group * @param groupName diff --git a/build.sbt b/build.sbt index c8533c8a0..24c5f619c 100644 --- a/build.sbt +++ b/build.sbt @@ -15,7 +15,10 @@ libraryDependencies ++= Seq( "org.apache.hadoop" % "hadoop-auth" % "2.3.0", "org.apache.commons" % "commons-email" % "1.3.2", "org.codehaus.jackson" % "jackson-mapper-asl" % "1.7.3", - "org.jsoup" % "jsoup" % "1.7.3" + "org.jsoup" % "jsoup" % "1.7.3", + // The following two dependencies are pulled in by the metrics library. + "org.apache.avro" % "avro" % "1.4.0", + "com.linkedin.avro-schemas" % "avro-schemas-tracking" % "6.0.518" ) ivyConfigurations += config("compileonly").hide diff --git a/conf/CounterPublisherConf.xml.template b/conf/CounterPublisherConf.xml.template new file mode 100644 index 000000000..be682d201 --- /dev/null +++ b/conf/CounterPublisherConf.xml.template @@ -0,0 +1,25 @@ + + + + + + + hdfs + /data/HadoopCounters + + + + diff --git a/start.sh b/start.sh index 7824f585b..eb187e229 100755 --- a/start.sh +++ b/start.sh @@ -15,6 +15,7 @@ function check_config(){ } # Save project root dir +METRICS_PUBLISHER_CONF_FILE="CounterPublisherConf.xml" script_dir=`which $0` script_dir=`dirname $script_dir` project_root=$script_dir/../ @@ -101,13 +102,21 @@ else exit 1 fi +OPTS="-Djava.library.path=$JAVA_LIB_PATH -Dhttp.port=$port -Dkeytab.user=$keytab_user -Dkeytab.location=$keytab_location -Ddb.default.url=$db_loc -Ddb.default.user=$db_user -Ddb.default.password=$db_password" + +CONFIG_DIR=`dirname ${CONFIG_FILE}` +METRICS_PUBLISHER_CONF_PATH="${CONFIG_DIR}/${METRICS_PUBLISHER_CONF_FILE}" +if [ -r ${METRICS_PUBLISHER_CONF_PATH} ]; then + OPTS="${OPTS} -Dmetrics.publisher-conf=${METRICS_PUBLISHER_CONF_PATH}" +fi + # Start Dr. Elaphant echo "Starting Dr. Elephant ...." -nohup ./bin/dr-elephant -Djava.library.path=$JAVA_LIB_PATH -Dhttp.port=$port -Dkeytab.user=$keytab_user -Dkeytab.location=$keytab_location -Ddb.default.url=$db_loc -Ddb.default.user=$db_user -Ddb.default.password=$db_password > /dev/null 2>&1 & +nohup ./bin/dr-elephant ${OPTS} > /dev/null 2>&1 & sleep 2 -# If Dr. Elephant starts successfully, Play should create a file 'RUNNING_PID' under project root +# If Dr. Elephant starts successfully, Play should create a file 'RUNNING_PID' under project root if [ -f RUNNING_PID ]; then echo "Dr. Elephant started." From 6fc595c5aebb1ca9fec645d10b4e91d5c0a26a33 Mon Sep 17 00:00:00 2001 From: Subbu Subramaniam Date: Fri, 16 Jan 2015 11:16:02 -0800 Subject: [PATCH 042/272] [HADOOP-8640] Fix hang in dr-elephant due to h1 and h2 incompatibility hadoop-1 does not have JobStatus.getFinishTime(). This causes dr-elephant to hang. Set the start time to be same as finish time for h1 jobs. For consistency, reverted to the old method of scraping the job tracker url so that we get only start time, and set the finish time to be equal to start time for retired jobs as well. RB=417975 BUGS=HADOOP-8640 R=fli,mwagner A=fli --- .../drelephant/ElephantFetcherClassic.java | 36 +++++++++---------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/app/com/linkedin/drelephant/ElephantFetcherClassic.java b/app/com/linkedin/drelephant/ElephantFetcherClassic.java index 66ac31013..37209f6ae 100644 --- a/app/com/linkedin/drelephant/ElephantFetcherClassic.java +++ b/app/com/linkedin/drelephant/ElephantFetcherClassic.java @@ -129,7 +129,12 @@ public void fetchJobData(HadoopJobData jobData) throws IOException, Authenticati JobStatus status = job.getJobStatus(); String username = status.getUsername(); long startTime = status.getStartTime(); - long finishTime = status.getFinishTime(); + // ALERT: + // In Hadoop-1 the getFinishTime() call is not there. Unfortunately, calling this method does not + // result in a compile error, neither a MethodNotFound exception at run time. The program just hangs. + // Since we don't have metrics (the only consumer of finish time as of now) in Hadoop-1, set the finish time + // to be the same as start time. + long finishTime = startTime; String jobUrl = job.getTrackingURL(); String jobName = job.getJobName(); jobData.setUsername(username).setStartTime(startTime).setFinishTime(finishTime).setUrl(jobUrl).setJobName(jobName); @@ -221,6 +226,7 @@ private Set filterPreviousJobs(Set jobs) { } _previousJobs = jobs; _firstRun = false; + logger.info("Database check completed"); return newJobs; } else { @SuppressWarnings("unchecked") @@ -397,10 +403,13 @@ private boolean checkRetiredAndFetchJobData(HadoopJobData jobData) throws IOExce HadoopTaskData[] mapperData = fetchAllTaskDataForRetiredJob(mapperUrl, true); HadoopTaskData[] reducerData = fetchAllTaskDataForRetiredJob(reducerUrl, false); - StartAndEndTimes times = fetchTimesForRetiredJob(doc, jobUrl); + long startTime = fetchStartTimeForRetiredJob(doc, jobUrl); + // We can scrape the job tracker page to get the finish time, but in order to be consistent with the + // non-retired jobs, we set the finish time to be same as start time here. + long finishTime = startTime; String username = jobConf.getProperty("user.name"); String jobName = jobConf.getProperty("mapred.job.name"); - jobData.setUsername(username).setStartTime(times._startTime).setFinishTime(times._finishTime).setUrl(jobUrl).setJobName(jobName); + jobData.setUsername(username).setStartTime(startTime).setFinishTime(finishTime).setUrl(jobUrl).setJobName(jobName); jobData.setCounters(jobCounter).setJobConf(jobConf).setMapperData(mapperData).setReducerData(reducerData); return true; } @@ -451,31 +460,18 @@ public HadoopCounterHolder fetchTaskCounterForRetiredJob(String taskCounterUrl) } // We fetch the start time of job's Setup task as the job's start time, shown in job's main page - - static class StartAndEndTimes { - public long _startTime; - public long _finishTime; - } - - private StartAndEndTimes fetchTimesForRetiredJob(Document doc, String jobUrl) - throws IOException { + private long fetchStartTimeForRetiredJob(Document doc, String jobUrl) throws IOException { Elements rows = doc.select("table").select("tr"); for (Element row : rows) { Elements cells = row.select("> td"); if (cells.size() == 7 && cells.get(0).text().trim().equals("Setup")) { SimpleDateFormat dateFormat = new SimpleDateFormat("d-MMM-yyyy HH:mm:ss"); - StartAndEndTimes times = new StartAndEndTimes(); try { - times._startTime = dateFormat.parse(cells.get(5).text().trim()).getTime(); - } catch (ParseException e) { - throw new IOException("Error in fetching start time from job page in URL : " + jobUrl); - } - try { - times._finishTime = dateFormat.parse(cells.get(6).text().trim()).getTime(); + long time = dateFormat.parse(cells.get(5).text().trim()).getTime(); + return time; } catch (ParseException e) { - throw new IOException("Error in fetching finish time from job page in URL : " + jobUrl); + throw new IOException("Error in fetching start time data from job page in URL : " + jobUrl); } - return times; } } throw new IOException("Unable to fetch start time data from job page in URL : " + jobUrl); From be47552c5510b828e8a52c07352535170fe7c575 Mon Sep 17 00:00:00 2001 From: Subbu Subramaniam Date: Thu, 15 Jan 2015 11:08:56 -0800 Subject: [PATCH 043/272] [HADOOP-8648]Updating dr-elephant release ID to 0.6.4 RB=417448 BUGS=HADOOP-8648 R=fli A=fli --- build.sbt | 2 +- conf/CounterPublisherConf.xml.template | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.sbt b/build.sbt index 24c5f619c..b6ae70b8e 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ import play.Project._ name := "dr-elephant" -version := "0.6.4-SNAPSHOT" +version := "0.6.4" javacOptions in Compile ++= Seq("-source", "1.6", "-target", "1.6") diff --git a/conf/CounterPublisherConf.xml.template b/conf/CounterPublisherConf.xml.template index be682d201..0a7020c35 100644 --- a/conf/CounterPublisherConf.xml.template +++ b/conf/CounterPublisherConf.xml.template @@ -11,8 +11,8 @@ HDFS Publishers: For HDFS publishers, the top-level directory can be configured. If it is not configured - the the default is "/data/HadoopCounters/". Then the date hierarchy is created under the top - level directory as YYYY/MM/DD/HH/jobs. Under this hierarchy, a file is created for each job + the the default is "/data/HadoopCounters". Then the date hierarchy is created under the top + level directory as hourly/YYYY/MM/DD/HH/. Under this hierarchy, a file is created for each job that puts out counters. --> From b6ddadd05119abf1e292a56647e795f27ed34043 Mon Sep 17 00:00:00 2001 From: Subbu Subramaniam Date: Tue, 20 Jan 2015 16:49:34 -0800 Subject: [PATCH 044/272] [HADOOP-8648] Reset version to 0.6.5-SNAPSHOT --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index b6ae70b8e..61e083b19 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ import play.Project._ name := "dr-elephant" -version := "0.6.4" +version := "0.6.5-SNAPSHOT" javacOptions in Compile ++= Seq("-source", "1.6", "-target", "1.6") From 194dee40d4abde0f048c437172e46f76a2633b4a Mon Sep 17 00:00:00 2001 From: Akshay Rai Date: Wed, 21 Jan 2015 21:16:34 -0800 Subject: [PATCH 045/272] HADOOP-8294: Improved slow performance of join queries --- .../linkedin/drelephant/ElephantRunner.java | 4 +- app/controllers/Application.java | 63 ++++++++++++------- app/model/JobHeuristicResult.java | 24 +++++++ app/model/JobResult.java | 40 +++++++++++- app/views/emailcritical.scala.html | 6 +- app/views/multijob.scala.html | 4 +- app/views/relatedjob.scala.html | 4 +- app/views/singlejob.scala.html | 2 +- 8 files changed, 114 insertions(+), 33 deletions(-) diff --git a/app/com/linkedin/drelephant/ElephantRunner.java b/app/com/linkedin/drelephant/ElephantRunner.java index 2e7df01c7..aaf5fe0b6 100644 --- a/app/com/linkedin/drelephant/ElephantRunner.java +++ b/app/com/linkedin/drelephant/ElephantRunner.java @@ -201,7 +201,7 @@ private void analyzeJob(HadoopJobData jobData, int execThreadNum) { // Save to DB JobResult result = new JobResult(); - result.job_id = jobData.getJobId(); + result.jobId = jobData.getJobId(); result.url = jobData.getUrl(); result.username = jobData.getUsername(); result.startTime = jobData.getStartTime(); @@ -306,4 +306,4 @@ public void kill() { _emailer.kill(); _service.shutdownNow(); } -} +} \ No newline at end of file diff --git a/app/controllers/Application.java b/app/controllers/Application.java index b8d6124de..eb8258cad 100644 --- a/app/controllers/Application.java +++ b/app/controllers/Application.java @@ -11,8 +11,10 @@ import java.util.List; import java.util.Map; +import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; +import model.JobHeuristicResult; import model.JobResult; import model.JobType; import views.html.*; @@ -25,6 +27,8 @@ import play.mvc.Result; import com.avaje.ebean.ExpressionList; +import com.avaje.ebean.RawSql; +import com.avaje.ebean.RawSqlBuilder; import com.linkedin.drelephant.analysis.Severity; import com.linkedin.drelephant.util.HeuristicConf; import com.linkedin.drelephant.util.HeuristicConfData; @@ -50,6 +54,15 @@ public class Application extends Controller { } } + public static StringBuilder getSqlJoinQuery() { + final StringBuilder sqlJoinQueryBuilder = new StringBuilder(); + sqlJoinQueryBuilder.append("SELECT " + JobResult.getColumnList()); + sqlJoinQueryBuilder.append(" FROM " + JobResult.TABLE.TABLE_NAME + " JOIN " + JobHeuristicResult.TABLE.TABLE_NAME); + sqlJoinQueryBuilder.append(" ON " + JobHeuristicResult.TABLE.TABLE_NAME + "." + JobHeuristicResult.TABLE.JOB_JOB_ID + + " = " + JobResult.TABLE.TABLE_NAME + "." + JobResult.TABLE.JOB_ID); + return sqlJoinQueryBuilder; + } + public static Result search() { DynamicForm form = Form.form().bindFromRequest(request()); String jobId = form.get("jobid"); @@ -71,28 +84,33 @@ public static Result search() { } else { return ok(search.render(singlejob.render(null))); } - } else if(flowUrl != null && !flowUrl.isEmpty()) { - List results = JobResult.find.where().eq("flow_exec_url", flowUrl).findList(); + } else if (flowUrl != null && !flowUrl.isEmpty()) { + List results = JobResult.find.where().eq(JobResult.TABLE.FLOW_EXEC_URL, flowUrl).findList(); Map> map = groupJobsByExec(results); return ok(search.render(relatedjob.render(flowUrl, map))); } else { ExpressionList query = JobResult.find.where(); if (username != null && !username.isEmpty()) { - query = query.like("username", username); + query = query.like(JobResult.TABLE.USERNAME, username); } if (jobtype != null && !jobtype.isEmpty()) { - query = query.eq("job_type", JobType.getDbName(jobtype)); + query = query.eq(JobResult.TABLE.JOB_TYPE, JobType.getDbName(jobtype)); } if (severity != null && !severity.isEmpty()) { - query = query.ge("heuristicResults.severity", severity); - } - if (analysis != null && !analysis.isEmpty()) { - query = query.eq("heuristicResults.analysisName", analysis); + if (analysis == null || analysis.isEmpty()) { + query = query.ge(JobResult.TABLE.SEVERITY, severity); + } else { + RawSql rawsql = RawSqlBuilder.parse(getSqlJoinQuery().toString()).create(); + query = + query.query().setRawSql(rawsql).where() + .eq(JobHeuristicResult.TABLE.TABLE_NAME + "." + JobHeuristicResult.TABLE.ANALYSIS_NAME, analysis) + .ge(JobHeuristicResult.TABLE.TABLE_NAME + "." + JobHeuristicResult.TABLE.SEVERITY, severity); + } } if (dateStart != null && !dateStart.isEmpty()) { try { Date date = dateFormat.parse(dateStart); - query = query.gt("startTime", date.getTime()); + query = query.gt(JobResult.TABLE.START_TIME, date.getTime()); } catch (ParseException e) { e.printStackTrace(); } @@ -104,12 +122,13 @@ public static Result search() { c.setTime(date); c.add(Calendar.DATE, 1); date = c.getTime(); - query = query.lt("startTime", date.getTime()); + query = query.lt(JobResult.TABLE.START_TIME, date.getTime()); } catch (ParseException e) { e.printStackTrace(); } } - List results = query.order().desc("analysisTime").setMaxRows(50).fetch("heuristicResults").findList(); + + List results = query.order().desc(JobResult.TABLE.ANALYSIS_TIME).setMaxRows(50).findList(); return ok(search.render(multijob.render("Results", results))); } } @@ -117,18 +136,18 @@ public static Result search() { public static Result dashboard(int page) { long now = System.currentTimeMillis(); if (now - _lastFetch > FETCH_DELAY) { - _numJobsAnalyzed = JobResult.find.where().gt("analysisTime", now - DAY).findRowCount(); + _numJobsAnalyzed = JobResult.find.where().gt(JobResult.TABLE.ANALYSIS_TIME, now - DAY).findRowCount(); _numJobsCritical = - JobResult.find.where().gt("analysisTime", now - DAY).eq("severity", Severity.CRITICAL.getValue()) - .findRowCount(); + JobResult.find.where().gt(JobResult.TABLE.ANALYSIS_TIME, now - DAY) + .eq(JobResult.TABLE.SEVERITY, Severity.CRITICAL.getValue()).findRowCount(); _numJobsSevere = - JobResult.find.where().gt("analysisTime", now - DAY).eq("severity", Severity.SEVERE.getValue()) - .findRowCount(); + JobResult.find.where().gt(JobResult.TABLE.ANALYSIS_TIME, now - DAY) + .eq(JobResult.TABLE.SEVERITY, Severity.SEVERE.getValue()).findRowCount(); _lastFetch = now; } List results = - JobResult.find.where().gt("analysisTime", now - DAY).order().desc("analysisTime").setMaxRows(50) - .fetch("heuristicResults").findList(); + JobResult.find.where().gt(JobResult.TABLE.ANALYSIS_TIME, now - DAY).order().desc(JobResult.TABLE.ANALYSIS_TIME) + .setMaxRows(50).fetch("heuristicResults").findList(); return ok(index.render(_numJobsAnalyzed, _numJobsSevere, _numJobsCritical, multijob.render("Latest analysis", results))); @@ -197,7 +216,7 @@ private static void fillHelpPages() { public static Result allJobExecs() { String jobUrl = request().queryString().get("job")[0]; - List results = JobResult.find.where().eq("job_url", jobUrl).findList(); + List results = JobResult.find.where().eq(JobResult.TABLE.JOB_URL, jobUrl).findList(); if (results.size() == 0) { return notFound("Unable to find record on job definition url: " + jobUrl); @@ -213,7 +232,7 @@ public static Result allJobExecs() { public static Result flowRelated() { String execUrl = request().queryString().get("flowexec")[0]; - List results = JobResult.find.where().eq("flow_exec_url", execUrl).findList(); + List results = JobResult.find.where().eq(JobResult.TABLE.FLOW_EXEC_URL, execUrl).findList(); if (results.size() == 0) { return notFound("Unable to find record on flow exec: " + execUrl); @@ -244,7 +263,7 @@ public static Result restJobExecResult(String jobExecUrl) { return badRequest("No job exec url provided."); } - List result = JobResult.find.where().eq("job_exec_url", jobExecUrl).findList(); + List result = JobResult.find.where().eq(JobResult.TABLE.JOB_EXEC_URL, jobExecUrl).findList(); if (result.size() == 0) { return notFound("Unable to find record on job exec url: " + jobExecUrl); @@ -259,7 +278,7 @@ public static Result restFlowExecResult(String flowExecUrl) { return badRequest("No flow exec url provided."); } - List results = JobResult.find.where().eq("flow_exec_url", flowExecUrl).findList(); + List results = JobResult.find.where().eq(JobResult.TABLE.FLOW_EXEC_URL, flowExecUrl).findList(); if (results.size() == 0) { return notFound("Unable to find record on flow exec url: " + flowExecUrl); diff --git a/app/model/JobHeuristicResult.java b/app/model/JobHeuristicResult.java index 637fa03e5..c1538d35f 100644 --- a/app/model/JobHeuristicResult.java +++ b/app/model/JobHeuristicResult.java @@ -7,6 +7,8 @@ import javax.persistence.Lob; import javax.persistence.ManyToOne; +import org.apache.commons.lang.StringUtils; + import com.fasterxml.jackson.annotation.JsonBackReference; import com.fasterxml.jackson.annotation.JsonIgnore; import com.linkedin.drelephant.analysis.Severity; @@ -20,6 +22,28 @@ public class JobHeuristicResult extends Model { private static final long serialVersionUID = 123L; + public static class TABLE { + public static final String TABLE_NAME = "job_heuristic_result"; + public static final String ID = "id"; + public static final String JOB_JOB_ID = "job_job_id"; + public static final String SEVERITY = "severity"; + public static final String ANALYSIS_NAME = "analysis_name"; + public static final String DATA_COLUMNS = "data_columns"; + public static final String DATA = "data"; + public static final String TABLE_COLUMNS[] = { + "job_heuristic_result.id", + "job_heuristic_result.job_job_id", + "job_heuristic_result.severity", + "job_heuristic_result.analysis_name", + "job_heuristic_result.data_columns", + "job_heuristic_result.data" + }; + } + + public static String getColumnList() { + return StringUtils.join(TABLE.TABLE_COLUMNS, ','); + } + @JsonIgnore @Id public int id; diff --git a/app/model/JobResult.java b/app/model/JobResult.java index b76739545..6721c9a31 100644 --- a/app/model/JobResult.java +++ b/app/model/JobResult.java @@ -13,6 +13,8 @@ import javax.persistence.Id; import javax.persistence.OneToMany; +import org.apache.commons.lang.StringUtils; + @Entity public class JobResult extends Model { @@ -20,9 +22,45 @@ public class JobResult extends Model { private static final long serialVersionUID = 1L; public static final int URL_LEN_LIMIT = 2048; + public static class TABLE { + public static final String TABLE_NAME = "job_result"; + public static final String JOB_ID = "job_id"; + public static final String USERNAME = "username"; + public static final String JOB_NAME = "job_name"; + public static final String START_TIME = "start_time"; + public static final String ANALYSIS_TIME = "analysis_time"; + public static final String SEVERITY = "severity"; + public static final String JOB_TYPE = "job_type"; + public static final String URL = "url"; + public static final String CLUSTER = "cluster"; + public static final String JOB_EXEC_URL = "job_exec_url"; + public static final String JOB_URL = "job_url"; + public static final String FLOW_EXEC_URL = "flow_exec_url"; + public static final String FLOW_URL = "flow_url"; + public static final String[] TABLE_COLUMNS = { + "job_result.job_id", + "job_result.username", + "job_result.job_name", + "job_result.start_time", + "job_result.analysis_time", + "job_result.severity", + "job_result.job_type", + "job_result.url", + "job_result.cluster", + "job_result.job_exec_url", + "job_result.job_url", + "job_result.flow_exec_url", + "job_result.flow_url" + }; + } + + public static String getColumnList() { + return StringUtils.join(TABLE.TABLE_COLUMNS, ','); + } + @Id @Column(length = 50) - public String job_id; + public String jobId; @Column(length = 50) public String username; diff --git a/app/views/emailcritical.scala.html b/app/views/emailcritical.scala.html index 38de4c72d..984b123c7 100644 --- a/app/views/emailcritical.scala.html +++ b/app/views/emailcritical.scala.html @@ -10,7 +10,7 @@

Dr. Elephant needs your help!

Your Hadoop job is endangering the elephants living in our cluster.

- Please take a look here to figure out + Please take a look here to figure out what chemicals is causing the elephants to go nuts.

@@ -24,9 +24,9 @@

Dr. Elephant needs your help!

- [Dr. Elephant] + [Dr. Elephant] [Jobtracker] -
@result.job_id
@result.jobName +
@result.jobId
@result.jobName

Thanks! diff --git a/app/views/multijob.scala.html b/app/views/multijob.scala.html index 06fa5d309..299246ed2 100644 --- a/app/views/multijob.scala.html +++ b/app/views/multijob.scala.html @@ -6,8 +6,8 @@

@title

@for(result <- results) { - -

[@result.username][@result.jobType.getName()] @result.job_id

@result.jobName

+
+

[@result.username][@result.jobType.getName()] @result.jobId

@result.jobName

@for(heuristicResult <- result.heuristicResults) { @heuristicResult.analysisName diff --git a/app/views/relatedjob.scala.html b/app/views/relatedjob.scala.html index b32ed9dd4..393bafea2 100644 --- a/app/views/relatedjob.scala.html +++ b/app/views/relatedjob.scala.html @@ -14,9 +14,9 @@

Job Execution URL: @url
@for(result <- jobs) { + href="@routes.Application.search()?jobid=@result.jobId">

-

[@result.username][@result.jobType.getName()] @result.job_id

@result.jobName +

[@result.username][@result.jobType.getName()] @result.jobId

@result.jobName

@for(heuristicResult <- result.heuristicResults) {

-

[@result.username] [@result.jobType.getName()] @result.job_id

+

[@result.username] [@result.jobType.getName()] @result.jobId

@result.jobName
From 45e3ca2440e029c694b9a7cfdc1f4f1753bb26f8 Mon Sep 17 00:00:00 2001 From: Akshay Rai Date: Mon, 2 Feb 2015 02:08:32 -0800 Subject: [PATCH 046/272] HADOOP-5369: Paginate search results of Dr. Elephant --- app/controllers/Application.java | 191 +++++++++++++++++++-------- app/controllers/PaginationStats.java | 61 +++++++++ app/views/pagination.scala.html | 35 +++++ app/views/search.scala.html | 5 +- 4 files changed, 233 insertions(+), 59 deletions(-) create mode 100644 app/controllers/PaginationStats.java create mode 100644 app/views/pagination.scala.html diff --git a/app/controllers/Application.java b/app/controllers/Application.java index eb8258cad..34293b28f 100644 --- a/app/controllers/Application.java +++ b/app/controllers/Application.java @@ -8,16 +8,19 @@ import java.util.Calendar; import java.util.Date; import java.util.HashMap; +import java.util.LinkedList; import java.util.List; import java.util.Map; - -import org.apache.commons.lang.StringUtils; -import org.apache.log4j.Logger; +import java.util.Set; import model.JobHeuristicResult; import model.JobResult; import model.JobType; -import views.html.*; + +import org.apache.http.client.utils.URLEncodedUtils; +import org.apache.http.message.BasicNameValuePair; +import org.apache.log4j.Logger; + import play.api.Play; import play.api.templates.Html; import play.data.DynamicForm; @@ -25,6 +28,13 @@ import play.libs.Json; import play.mvc.Controller; import play.mvc.Result; +import views.html.emailcritical; +import views.html.help; +import views.html.index; +import views.html.multijob; +import views.html.relatedjob; +import views.html.search; +import views.html.singlejob; import com.avaje.ebean.ExpressionList; import com.avaje.ebean.RawSql; @@ -38,11 +48,22 @@ public class Application extends Controller { private static final Logger logger = Logger.getLogger(Application.class); private static final long DAY = 24 * 60 * 60 * 1000; private static final long FETCH_DELAY = 60 * 1000; + private static final int PAGE_LENGTH = 25; + private static final int PAGE_BAR_LENGTH = 10; + private static final String FORM_JOB_ID = "jobid"; + private static final String FORM_FLOW_URL = "flowurl"; + private static final String FORM_USER = "user"; + private static final String FORM_SEVERITY = "severity"; + private static final String FORM_JOB_TYPE = "jobtype"; + private static final String FORM_ANALYSIS = "analysis"; + private static final String FORM_START_DATE = "start-date"; + private static final String FORM_END_DATE = "end-date"; private static long _lastFetch = 0; private static int _numJobsAnalyzed = 0; private static int _numJobsCritical = 0; private static int _numJobsSevere = 0; private static Map _helpPages = new HashMap(); + private static PaginationStats paginationStats = new PaginationStats(PAGE_LENGTH, PAGE_BAR_LENGTH); static { try { @@ -64,73 +85,127 @@ public static StringBuilder getSqlJoinQuery() { } public static Result search() { + // Search and display job information when job id or flow execution url is provided. DynamicForm form = Form.form().bindFromRequest(request()); - String jobId = form.get("jobid"); - jobId = (jobId != null) ? jobId.trim() : null; - String flowUrl = form.get("flowurl"); + String jobId = form.get(FORM_JOB_ID); + jobId = jobId != null ? jobId.trim() : ""; + String flowUrl = form.get(FORM_FLOW_URL); flowUrl = (flowUrl != null) ? flowUrl.trim() : null; - String username = form.get("user"); - username = (username != null) ? username.trim().toLowerCase() : null; - String severity = form.get("severity"); - String jobtype = form.get("jobtype"); - String analysis = form.get("analysis"); - String dateStart = form.get("start-date"); - String dateEnd = form.get("end-date"); - SimpleDateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy"); - if (jobId != null && !jobId.isEmpty()) { + if (!jobId.isEmpty()) { JobResult result = JobResult.find.byId(jobId); if (result != null) { - return ok(search.render(singlejob.render(result))); + return ok(search.render(null, singlejob.render(result))); } else { - return ok(search.render(singlejob.render(null))); + return ok(search.render(null, singlejob.render(null))); } } else if (flowUrl != null && !flowUrl.isEmpty()) { List results = JobResult.find.where().eq(JobResult.TABLE.FLOW_EXEC_URL, flowUrl).findList(); Map> map = groupJobsByExec(results); - return ok(search.render(relatedjob.render(flowUrl, map))); - } else { - ExpressionList query = JobResult.find.where(); - if (username != null && !username.isEmpty()) { - query = query.like(JobResult.TABLE.USERNAME, username); + return ok(search.render(null, relatedjob.render(flowUrl, map))); + } + + // Paginate the results + int pageLength = paginationStats.getPageLength(); + paginationStats.setCurrentPage(1); + final Map searchString = request().queryString(); + if (searchString.containsKey("page")) { + try { + paginationStats.setCurrentPage(Integer.parseInt(searchString.get("page")[0])); + } catch (NumberFormatException ex) { + logger.error("Error parsing page number. Setting current page to 1."); + paginationStats.setCurrentPage(1); } - if (jobtype != null && !jobtype.isEmpty()) { - query = query.eq(JobResult.TABLE.JOB_TYPE, JobType.getDbName(jobtype)); + } + int currentPage = paginationStats.getCurrentPage(); + int paginationBarStartIndex = paginationStats.getPaginationBarStartIndex(); + ExpressionList query = generateQuery(); + List results = + query.order().desc("analysisTime").fetch("heuristicResults") + .setFirstRow((paginationBarStartIndex - 1) * pageLength) + .setMaxRows((paginationStats.getPageBarLength() - 1) * pageLength + 1).findList(); + paginationStats.setQueryString(getQueryString()); + if (results.isEmpty() || currentPage > paginationStats.computePaginationBarEndIndex(results.size())) { + return ok(search.render(null, singlejob.render(null))); + } else { + return ok(search.render( + paginationStats, + multijob.render( + "Results", + results.subList((currentPage - paginationBarStartIndex) * pageLength, + Math.min(results.size(), (currentPage - paginationBarStartIndex + 1) * pageLength))))); + } + } + + private static String getQueryString() { + List fields = new LinkedList(); + final Set> entries = request().queryString().entrySet(); + for (Map.Entry entry : entries) { + final String key = entry.getKey(); + final String value = entry.getValue()[0]; + if (!key.equals("page")) { + fields.add(new BasicNameValuePair(key, value)); } - if (severity != null && !severity.isEmpty()) { - if (analysis == null || analysis.isEmpty()) { - query = query.ge(JobResult.TABLE.SEVERITY, severity); - } else { - RawSql rawsql = RawSqlBuilder.parse(getSqlJoinQuery().toString()).create(); - query = - query.query().setRawSql(rawsql).where() - .eq(JobHeuristicResult.TABLE.TABLE_NAME + "." + JobHeuristicResult.TABLE.ANALYSIS_NAME, analysis) - .ge(JobHeuristicResult.TABLE.TABLE_NAME + "." + JobHeuristicResult.TABLE.SEVERITY, severity); - } + } + if (fields.isEmpty()) { + return null; + } else { + return URLEncodedUtils.format(fields, "utf-8"); + } + } + + private static ExpressionList generateQuery() { + SimpleDateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy"); + DynamicForm form = Form.form().bindFromRequest(request()); + String username = form.get(FORM_USER); + username = username != null ? username.trim().toLowerCase() : null; + String severity = form.get(FORM_SEVERITY); + String jobType = form.get(FORM_JOB_TYPE); + String analysis = form.get(FORM_ANALYSIS); + String dateStart = form.get(FORM_START_DATE); + String dateEnd = form.get(FORM_END_DATE); + + ExpressionList query = JobResult.find.where(); + + if (username != null && !username.isEmpty()) { + query = query.like(JobResult.TABLE.USERNAME, username); + } + if (jobType != null && !jobType.isEmpty()) { + query = query.eq(JobResult.TABLE.JOB_TYPE, JobType.getDbName(jobType)); + } + if (severity != null && !severity.isEmpty()) { + if (analysis == null || analysis.isEmpty()) { + query = query.ge(JobResult.TABLE.SEVERITY, severity); + } else { + RawSql rawsql = RawSqlBuilder.parse(getSqlJoinQuery().toString()).create(); + query = + query.query().setRawSql(rawsql).where() + .eq(JobHeuristicResult.TABLE.TABLE_NAME + "." + JobHeuristicResult.TABLE.ANALYSIS_NAME, analysis) + .ge(JobHeuristicResult.TABLE.TABLE_NAME + "." + JobHeuristicResult.TABLE.SEVERITY, severity); } - if (dateStart != null && !dateStart.isEmpty()) { - try { - Date date = dateFormat.parse(dateStart); - query = query.gt(JobResult.TABLE.START_TIME, date.getTime()); - } catch (ParseException e) { - e.printStackTrace(); - } + } + if (dateStart != null && !dateStart.isEmpty()) { + try { + Date date = dateFormat.parse(dateStart); + query = query.gt(JobResult.TABLE.START_TIME, date.getTime()); + } catch (ParseException e) { + logger.error("Error while parsing dateStart", e); + e.printStackTrace(); } - if (dateEnd != null && !dateEnd.isEmpty()) { - try { - Date date = dateFormat.parse(dateEnd); - Calendar c = Calendar.getInstance(); - c.setTime(date); - c.add(Calendar.DATE, 1); - date = c.getTime(); - query = query.lt(JobResult.TABLE.START_TIME, date.getTime()); - } catch (ParseException e) { - e.printStackTrace(); - } + } + if (dateEnd != null && !dateEnd.isEmpty()) { + try { + Date date = dateFormat.parse(dateEnd); + Calendar c = Calendar.getInstance(); + c.setTime(date); + c.add(Calendar.DATE, 1); + date = c.getTime(); + query = query.lt(JobResult.TABLE.START_TIME, date.getTime()); + } catch (ParseException e) { + logger.error("Error while parsing dateEnd", e); + e.printStackTrace(); } - - List results = query.order().desc(JobResult.TABLE.ANALYSIS_TIME).setMaxRows(50).findList(); - return ok(search.render(multijob.render("Results", results))); } + return query; } public static Result dashboard(int page) { @@ -223,7 +298,7 @@ public static Result allJobExecs() { } Map> map = groupJobsByExec(results); - return ok(search.render(relatedjob.render(jobUrl, map))); + return ok(search.render(null, relatedjob.render(jobUrl, map))); } /** @@ -239,7 +314,7 @@ public static Result flowRelated() { } Map> map = groupJobsByExec(results); - return ok(search.render(relatedjob.render(execUrl, map))); + return ok(search.render(null, relatedjob.render(execUrl, map))); } public static Result restJobResult(String jobId) { diff --git a/app/controllers/PaginationStats.java b/app/controllers/PaginationStats.java new file mode 100644 index 000000000..d3d2a6b5e --- /dev/null +++ b/app/controllers/PaginationStats.java @@ -0,0 +1,61 @@ +package controllers; + +public class PaginationStats { + public int currentPage = 1; + public int paginationBarStartIndex = 1; + public int paginationBarEndIndex = 1; + public int pageLength; + public int pageBarLength; + public String queryString = null; + + public PaginationStats(int pageLength, int pageBarLength) { + this.pageLength = pageLength; + this.pageBarLength = pageBarLength; + } + + public int getCurrentPage() { + return currentPage; + } + + public void setCurrentPage(int currentPage) { + if (currentPage < 1) { + this.currentPage = 1; + } else { + this.currentPage = currentPage; + } + } + + /** + * Compute paginationBarStartIndex. + * paginationBarStartIndex is computed such that the currentPage remains at the center of the Pagination Bar. + */ + public int getPaginationBarStartIndex() { + this.paginationBarStartIndex = Math.max(this.currentPage - this.pageBarLength / 2, 1); + return this.paginationBarStartIndex; + } + + public int computePaginationBarEndIndex(int resultSize) { + this.paginationBarEndIndex = this.paginationBarStartIndex + (resultSize - 1) / this.pageLength; + return this.paginationBarEndIndex; + } + + public int getPaginationBarEndIndex() { + return this.paginationBarEndIndex; + } + + public String getQueryString() { + return queryString; + } + + public void setQueryString(String queryString) { + this.queryString = queryString; + } + + public int getPageBarLength() { + return pageBarLength; + } + + public int getPageLength() { + return pageLength; + } +} diff --git a/app/views/pagination.scala.html b/app/views/pagination.scala.html new file mode 100644 index 000000000..11ffa3fc0 --- /dev/null +++ b/app/views/pagination.scala.html @@ -0,0 +1,35 @@ +@(paginationStats: controllers.PaginationStats, route:Call) + + diff --git a/app/views/search.scala.html b/app/views/search.scala.html index afe99c0a3..07d8d5583 100644 --- a/app/views/search.scala.html +++ b/app/views/search.scala.html @@ -1,4 +1,4 @@ -@()(results: Html) +@(paginationStats: controllers.PaginationStats)(results: Html) @main("Dr. Elephant - Search", "search") { @tags.column(3) { @@ -59,5 +59,8 @@ } @tags.column(9) { @results + @if(paginationStats != null) { + @pagination(paginationStats, routes.Application.search()) + } } } From f7562cc0f393a33af0d647f68c5bb71cb8b349dc Mon Sep 17 00:00:00 2001 From: Fangshi Li Date: Tue, 3 Feb 2015 13:02:13 -0800 Subject: [PATCH 047/272] HADOOP-7948: Make Dr. Elephant's Reducer Time Moderate Severity cut-off 51 reducers instead of 50 --- .../drelephant/analysis/heuristics/ReducerTimeHeuristic.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/com/linkedin/drelephant/analysis/heuristics/ReducerTimeHeuristic.java b/app/com/linkedin/drelephant/analysis/heuristics/ReducerTimeHeuristic.java index 84b79f9f6..05209ecd8 100644 --- a/app/com/linkedin/drelephant/analysis/heuristics/ReducerTimeHeuristic.java +++ b/app/com/linkedin/drelephant/analysis/heuristics/ReducerTimeHeuristic.java @@ -69,10 +69,10 @@ public static Severity getLongRuntimeSeverity(long runtimeMs) { } public static Severity getNumTasksSeverity(long numTasks) { - return Severity.getSeverityAscending(numTasks, 10, 50, 200, 500); + return Severity.getSeverityAscending(numTasks, 10, 51, 200, 500); } public static Severity getNumTasksSeverityReverse(long numTasks) { - return Severity.getSeverityDescending(numTasks, 100, 50, 20, 10); + return Severity.getSeverityDescending(numTasks, 100, 49, 20, 10); } } From f6a2a95d803462b8e2a9ab390727c5fa83e7ebaf Mon Sep 17 00:00:00 2001 From: Fangshi Li Date: Tue, 3 Feb 2015 14:37:15 -0800 Subject: [PATCH 048/272] HADOOP-8320: Add detailed suggestions in Dr. Elephant help page --- app/views/helpReducerTime.scala.html | 3 ++- app/views/helpShuffleSort.scala.html | 10 +++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/app/views/helpReducerTime.scala.html b/app/views/helpReducerTime.scala.html index 08daa3658..92c933083 100644 --- a/app/views/helpReducerTime.scala.html +++ b/app/views/helpReducerTime.scala.html @@ -68,5 +68,6 @@

Suggestions

For Hadoop/Java jobs: Use "jobConf.setNumReduceTasks(NUMBER_OF_REDUCERS);"
For Apache-Pig jobs: Use PARALLEL [num] clause on the operator which caused this job (Though this will probably be hard for people to understand without Lipstick)
For Apache-Hive jobs: Use "set mapred.reduce.tasks=NUMBER_OF_REDUCERS"
- (change NUMBER_OF_TASKS to an appropriate number of tasks) + For Azkaban flows, add jvm.args=-Dmapred.reduce.tasks=NUMBER_OF_REDUCERS to your job properties
+ Go go/hadooptuningtips for further information.

\ No newline at end of file diff --git a/app/views/helpShuffleSort.scala.html b/app/views/helpShuffleSort.scala.html index 7db398b4e..94da368ef 100644 --- a/app/views/helpShuffleSort.scala.html +++ b/app/views/helpShuffleSort.scala.html @@ -36,5 +36,13 @@

Shuffle & Sort

Suggestions

- If your shuffle time is high but sort is low, you likely need to turn slowstart on + If your shuffle time is high but sort is low, you likely need to tune slowstart by setting "mapred.reduce.slowstart.completed.maps" to 0.90+ up to 1.0
+ Warning: Setting this parameter close to 1.0 will decrease shuffle time, but may increase this job's total runtime
+
+ If you want to set it to 0.95:
+ For Hadoop/Java jobs: Use "jobConf.setFloat("mapred.reduce.slowstart.completed.maps", 0.95f);"
+ For Apache-Pig jobs: Use "set mapred.reduce.slowstart.completed.maps 0.95"
+ For Apache-Hive jobs: Use "set mapred.reduce.slowstart.completed.maps=0.95"
+ For Azkaban flows, add jvm.args=-Dmapred.reduce.slowstart.completed.maps=0.95 to your job properties
+ Go go/hadooptuningtips for further information.

\ No newline at end of file From 8272cf0f3686bd178517b2616bae9994c4abe0a3 Mon Sep 17 00:00:00 2001 From: Fangshi Li Date: Tue, 3 Feb 2015 14:55:24 -0800 Subject: [PATCH 049/272] HADOOP-8320: Add detailed suggestions in Dr. Elephant help page --- app/views/helpMapperInputSize.scala.html | 1 + app/views/helpReducerTime.scala.html | 2 +- app/views/helpShuffleSort.scala.html | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/views/helpMapperInputSize.scala.html b/app/views/helpMapperInputSize.scala.html index 2980737d1..1ba393106 100644 --- a/app/views/helpMapperInputSize.scala.html +++ b/app/views/helpMapperInputSize.scala.html @@ -86,4 +86,5 @@

Suggestions

The maximum map split size is controlled by the "mapred.max.split.size" parameter. By decreasing this value below dfs.block.size, you can reduce the maximum input size for each mapper, thereby increase the number of mappers in your job.
+ See Hadoop Tuning Tips for further information.

diff --git a/app/views/helpReducerTime.scala.html b/app/views/helpReducerTime.scala.html index 92c933083..1a9255d65 100644 --- a/app/views/helpReducerTime.scala.html +++ b/app/views/helpReducerTime.scala.html @@ -69,5 +69,5 @@

Suggestions

For Apache-Pig jobs: Use PARALLEL [num] clause on the operator which caused this job (Though this will probably be hard for people to understand without Lipstick)
For Apache-Hive jobs: Use "set mapred.reduce.tasks=NUMBER_OF_REDUCERS"
For Azkaban flows, add jvm.args=-Dmapred.reduce.tasks=NUMBER_OF_REDUCERS to your job properties
- Go go/hadooptuningtips for further information. + See Hadoop Tuning Tips for further information.

\ No newline at end of file diff --git a/app/views/helpShuffleSort.scala.html b/app/views/helpShuffleSort.scala.html index 94da368ef..5d2e3542a 100644 --- a/app/views/helpShuffleSort.scala.html +++ b/app/views/helpShuffleSort.scala.html @@ -44,5 +44,5 @@

Suggestions

For Apache-Pig jobs: Use "set mapred.reduce.slowstart.completed.maps 0.95"
For Apache-Hive jobs: Use "set mapred.reduce.slowstart.completed.maps=0.95"
For Azkaban flows, add jvm.args=-Dmapred.reduce.slowstart.completed.maps=0.95 to your job properties
- Go go/hadooptuningtips for further information. + See Hadoop Tuning Tips for further information.

\ No newline at end of file From 138693aea265bde86b7ab820cb24bc58f8f0c3ee Mon Sep 17 00:00:00 2001 From: Fangshi Li Date: Tue, 3 Feb 2015 15:03:04 -0800 Subject: [PATCH 050/272] HADOOP-8856: Fix a broken Dr.Elephant test case for reducer time heuristic --- .../analysis/heuristics/ReducerTimeHeuristicTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/com/linkedin/drelephant/analysis/heuristics/ReducerTimeHeuristicTest.java b/test/com/linkedin/drelephant/analysis/heuristics/ReducerTimeHeuristicTest.java index bae417469..7eeae2801 100644 --- a/test/com/linkedin/drelephant/analysis/heuristics/ReducerTimeHeuristicTest.java +++ b/test/com/linkedin/drelephant/analysis/heuristics/ReducerTimeHeuristicTest.java @@ -26,11 +26,11 @@ public void testShortRunetimeSevere() throws IOException { } public void testShortRunetimeModerate() throws IOException { - assertEquals(Severity.MODERATE, analyzeJob(1 * MINUTE_IN_MS, 50)); + assertEquals(Severity.MODERATE, analyzeJob(1 * MINUTE_IN_MS, 51)); } public void testShortRunetimeLow() throws IOException { - assertEquals(Severity.LOW, analyzeJob(1 * MINUTE_IN_MS, 10)); + assertEquals(Severity.LOW, analyzeJob(1 * MINUTE_IN_MS, 50)); } public void testShortRunetimeNone() throws IOException { @@ -46,7 +46,7 @@ public void testLongRunetimeSevere() throws IOException { } public void testLongRunetimeModerate() throws IOException { - assertEquals(Severity.MODERATE, analyzeJob(120 * MINUTE_IN_MS, 50)); + assertEquals(Severity.MODERATE, analyzeJob(120 * MINUTE_IN_MS, 40)); } public void testLongRunetimeLow() throws IOException { From 81a27f742eff956c519f681c32338b1595ac0b53 Mon Sep 17 00:00:00 2001 From: Fangshi Li Date: Tue, 3 Feb 2015 16:54:24 -0800 Subject: [PATCH 051/272] HADOOP-8859: Add ideal task time suggestion in Dr. Elephant 's reducer time help page --- app/views/helpReducerTime.scala.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/views/helpReducerTime.scala.html b/app/views/helpReducerTime.scala.html index 1a9255d65..5fa2a55ea 100644 --- a/app/views/helpReducerTime.scala.html +++ b/app/views/helpReducerTime.scala.html @@ -69,5 +69,7 @@

Suggestions

For Apache-Pig jobs: Use PARALLEL [num] clause on the operator which caused this job (Though this will probably be hard for people to understand without Lipstick)
For Apache-Hive jobs: Use "set mapred.reduce.tasks=NUMBER_OF_REDUCERS"
For Azkaban flows, add jvm.args=-Dmapred.reduce.tasks=NUMBER_OF_REDUCERS to your job properties
+
+ Generally, Dr. Elephant(and Hadoop team) advises the ideal task time to be 5-10 minutes.
See Hadoop Tuning Tips for further information.

\ No newline at end of file From 6beb02d5fd8a892617e99465a719774320e52e9c Mon Sep 17 00:00:00 2001 From: Fangshi Li Date: Wed, 4 Feb 2015 12:05:48 -0800 Subject: [PATCH 052/272] HADOOP-8846: Release Dr. Elephant v0.6.5 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 61e083b19..fb98688e2 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ import play.Project._ name := "dr-elephant" -version := "0.6.5-SNAPSHOT" +version := "0.6.5" javacOptions in Compile ++= Seq("-source", "1.6", "-target", "1.6") From 12408ba7229652f1776c5f222ee981632064523a Mon Sep 17 00:00:00 2001 From: Fangshi Li Date: Mon, 9 Feb 2015 14:47:36 -0800 Subject: [PATCH 053/272] HADOOP-8929: Update Dr.Elephant version number to 0.6.6-SNAPSHOT --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index fb98688e2..9535e2a8a 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ import play.Project._ name := "dr-elephant" -version := "0.6.5" +version := "0.6.6-SNAPSHOT" javacOptions in Compile ++= Seq("-source", "1.6", "-target", "1.6") From 28d1283e683b702441ad24d8a6d8b9734458383b Mon Sep 17 00:00:00 2001 From: Fangshi Li Date: Fri, 27 Mar 2015 16:33:10 -0700 Subject: [PATCH 054/272] HADOOP-9685: Dr. Elephant fails to fetch task counter data on hadoop2 --- .../hadoop/HadoopCounterHolder.java | 57 +++++++------------ 1 file changed, 21 insertions(+), 36 deletions(-) diff --git a/app/com/linkedin/drelephant/hadoop/HadoopCounterHolder.java b/app/com/linkedin/drelephant/hadoop/HadoopCounterHolder.java index faf87949f..cb97a1fdb 100644 --- a/app/com/linkedin/drelephant/hadoop/HadoopCounterHolder.java +++ b/app/com/linkedin/drelephant/hadoop/HadoopCounterHolder.java @@ -1,5 +1,6 @@ package com.linkedin.drelephant.hadoop; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -11,20 +12,30 @@ public class HadoopCounterHolder { // This is a map of group to all the counters in the group and their values. private final Map> _pubCounters; + public String toString() { + return _pubCounters.toString(); + } + public HadoopCounterHolder() { _pubCounters = new HashMap>(8); } /** * @return the value of the counter, 0 if not present. + * This method is only used for job heuristics + * Due to h1 & h2 counter group incompatibility, we iterate every counter group (4 by default) + * to find a matching counter name, otherwise we have to hardcode the h1&h2 version of counter group + * and try twice with two names for each counter in this method. + * This approach is less efficient, but cleaner. */ public long get(CounterName counterName) { - try { - long value = get(counterName.getGroupName(), counterName.getName()); - return value; - } catch (NoSuchFieldException e) { - return 0; + // For each counter group, try to match the counter name + for(Map counterGrp : _pubCounters.values()) { + if(counterGrp.containsKey(counterName._name)) { + return counterGrp.get(counterName._name); + } } + return 0; } public void set(CounterName counterName, long value) { @@ -47,25 +58,6 @@ public void set(String groupName, String counterName, long value) { counterMap.put(counterName, value); } - /** - * Get the vlaue of a counter from counters to publish - * @param groupName - * @param counterName - * @return -1 if the counter or group is not present. - */ - public long get(String groupName, String counterName) - throws NoSuchFieldException { - Map counterMap = _pubCounters.get(groupName); - if (counterMap == null) { - throw new NoSuchFieldException("Unknown counter group:" + groupName); - } - Long value = counterMap.get(counterName); - if (value == null) { - throw new NoSuchFieldException("Unknown counter name:" + counterName); - } - return value != null ? value : -1; - } - public Set getGroupNames() { Set groupNames = _pubCounters.keySet(); return Collections.unmodifiableSet(groupNames); @@ -85,16 +77,10 @@ public Map getAllCountersInGroup(String groupName) { } public static enum GroupName { - FileInput("org.apache.hadoop.mapred.FileInputFormat$Counter"), - FileSystemCounters("FileSystemCounters"), - MapReduce("org.apache.hadoop.mapred.Task$Counter"), - FileOutput("org.apache.hadoop.mapred.FileOutputFormat$Counter"); - - String _name; - - GroupName(String name) { - this._name = name; - } + FileInput, + FileSystemCounters, + MapReduce, + FileOutput; } public static enum CounterName { @@ -170,8 +156,7 @@ public String getDisplayName() { } public String getGroupName() { - return _group._name; + return _group.name(); } - } } From 335198a189fd85405a47291fa344f24c624bd1b6 Mon Sep 17 00:00:00 2001 From: Akshay Rai Date: Mon, 16 Mar 2015 04:50:34 -0700 Subject: [PATCH 055/272] HADOOP-8941: Further improve search performance for Dr. Elephant --- app/controllers/Application.java | 68 +++++++++++++++++-------------- app/controllers/QueryHandler.java | 42 +++++++++++++++++++ app/model/JobResult.java | 1 + app/views/search.scala.html | 2 +- conf/evolutions/default/5.sql | 13 ++++++ 5 files changed, 94 insertions(+), 32 deletions(-) create mode 100644 app/controllers/QueryHandler.java create mode 100644 conf/evolutions/default/5.sql diff --git a/app/controllers/Application.java b/app/controllers/Application.java index 34293b28f..08977abba 100644 --- a/app/controllers/Application.java +++ b/app/controllers/Application.java @@ -48,8 +48,8 @@ public class Application extends Controller { private static final Logger logger = Logger.getLogger(Application.class); private static final long DAY = 24 * 60 * 60 * 1000; private static final long FETCH_DELAY = 60 * 1000; - private static final int PAGE_LENGTH = 25; - private static final int PAGE_BAR_LENGTH = 10; + private static final int PAGE_LENGTH = 20; + private static final int PAGE_BAR_LENGTH = 5; private static final String FORM_JOB_ID = "jobid"; private static final String FORM_FLOW_URL = "flowurl"; private static final String FORM_USER = "user"; @@ -75,15 +75,6 @@ public class Application extends Controller { } } - public static StringBuilder getSqlJoinQuery() { - final StringBuilder sqlJoinQueryBuilder = new StringBuilder(); - sqlJoinQueryBuilder.append("SELECT " + JobResult.getColumnList()); - sqlJoinQueryBuilder.append(" FROM " + JobResult.TABLE.TABLE_NAME + " JOIN " + JobHeuristicResult.TABLE.TABLE_NAME); - sqlJoinQueryBuilder.append(" ON " + JobHeuristicResult.TABLE.TABLE_NAME + "." + JobHeuristicResult.TABLE.JOB_JOB_ID - + " = " + JobResult.TABLE.TABLE_NAME + "." + JobResult.TABLE.JOB_ID); - return sqlJoinQueryBuilder; - } - public static Result search() { // Search and display job information when job id or flow execution url is provided. DynamicForm form = Form.form().bindFromRequest(request()); @@ -120,8 +111,7 @@ public static Result search() { int paginationBarStartIndex = paginationStats.getPaginationBarStartIndex(); ExpressionList query = generateQuery(); List results = - query.order().desc("analysisTime").fetch("heuristicResults") - .setFirstRow((paginationBarStartIndex - 1) * pageLength) + query.order().desc("analysisTime").setFirstRow((paginationBarStartIndex - 1) * pageLength) .setMaxRows((paginationStats.getPageBarLength() - 1) * pageLength + 1).findList(); paginationStats.setQueryString(getQueryString()); if (results.isEmpty() || currentPage > paginationStats.computePaginationBarEndIndex(results.size())) { @@ -166,48 +156,64 @@ private static ExpressionList generateQuery() { ExpressionList query = JobResult.find.where(); - if (username != null && !username.isEmpty()) { + RawSql rawsql = null; + // Hint usage of username index to mysql whenever our query contains a predicate on username + if (isSet(severity) && isSet(analysis)) { + if (isSet(username)) { + rawsql = RawSqlBuilder.parse(QueryHandler.getSqlJoinQueryWithUsernameIndex().toString()).create(); + } else { + rawsql = RawSqlBuilder.parse(QueryHandler.getSqlJoinQuery().toString()).create(); + } + } else { + if (isSet(username)) { + rawsql = RawSqlBuilder.parse(QueryHandler.getJobResultQueryWithUsernameIndex().toString()).create(); + } + } + query = query.query().setRawSql(rawsql).where(); + + // Build predicates + if (isSet(username)) { query = query.like(JobResult.TABLE.USERNAME, username); } - if (jobType != null && !jobType.isEmpty()) { + if (isSet(jobType)) { query = query.eq(JobResult.TABLE.JOB_TYPE, JobType.getDbName(jobType)); } - if (severity != null && !severity.isEmpty()) { - if (analysis == null || analysis.isEmpty()) { - query = query.ge(JobResult.TABLE.SEVERITY, severity); - } else { - RawSql rawsql = RawSqlBuilder.parse(getSqlJoinQuery().toString()).create(); + if (isSet(severity)) { + if (isSet(analysis)) { query = - query.query().setRawSql(rawsql).where() - .eq(JobHeuristicResult.TABLE.TABLE_NAME + "." + JobHeuristicResult.TABLE.ANALYSIS_NAME, analysis) - .ge(JobHeuristicResult.TABLE.TABLE_NAME + "." + JobHeuristicResult.TABLE.SEVERITY, severity); + query.eq(JobHeuristicResult.TABLE.TABLE_NAME + "." + JobHeuristicResult.TABLE.ANALYSIS_NAME, analysis).ge( + JobHeuristicResult.TABLE.TABLE_NAME + "." + JobHeuristicResult.TABLE.SEVERITY, severity); + } else { + query = query.ge(JobResult.TABLE.SEVERITY, severity); } } - if (dateStart != null && !dateStart.isEmpty()) { + if (isSet(dateStart)) { try { Date date = dateFormat.parse(dateStart); - query = query.gt(JobResult.TABLE.START_TIME, date.getTime()); + query = query.gt(JobResult.TABLE.ANALYSIS_TIME, date.getTime()); } catch (ParseException e) { - logger.error("Error while parsing dateStart", e); - e.printStackTrace(); + logger.error("Error while parsing dateStart. " + dateStart + " is an invalid date. Filter not applied."); } } - if (dateEnd != null && !dateEnd.isEmpty()) { + if (isSet(dateEnd)) { try { Date date = dateFormat.parse(dateEnd); Calendar c = Calendar.getInstance(); c.setTime(date); c.add(Calendar.DATE, 1); date = c.getTime(); - query = query.lt(JobResult.TABLE.START_TIME, date.getTime()); + query = query.lt(JobResult.TABLE.ANALYSIS_TIME, date.getTime()); } catch (ParseException e) { - logger.error("Error while parsing dateEnd", e); - e.printStackTrace(); + logger.error("Error while parsing dateEnd. " + dateEnd + " is an invalid date. Filter not applied."); } } return query; } + private static boolean isSet(String property) { + return property != null && !property.isEmpty(); + } + public static Result dashboard(int page) { long now = System.currentTimeMillis(); if (now - _lastFetch > FETCH_DELAY) { diff --git a/app/controllers/QueryHandler.java b/app/controllers/QueryHandler.java new file mode 100644 index 000000000..3da0e2d63 --- /dev/null +++ b/app/controllers/QueryHandler.java @@ -0,0 +1,42 @@ +package controllers; + +import model.JobHeuristicResult; +import model.JobResult; + +class QueryHandler { + + static StringBuilder getJobResultQuery() { + final StringBuilder sqlQueryBuilder = new StringBuilder(); + sqlQueryBuilder.append("SELECT " + JobResult.getColumnList()); + sqlQueryBuilder.append(" FROM " + JobResult.TABLE.TABLE_NAME); + return sqlQueryBuilder; + } + + static StringBuilder getJobResultQueryWithUsernameIndex() { + StringBuilder sqlQueryBuilder = getJobResultQuery(); + setUseIndex(sqlQueryBuilder, JobResult.TABLE.USERNAME_INDEX); + return sqlQueryBuilder; + } + + static StringBuilder getSqlJoinQuery() { + StringBuilder sqlQueryBuilder = getJobResultQuery(); + sqlQueryBuilder.append(" JOIN " + JobHeuristicResult.TABLE.TABLE_NAME); + sqlQueryBuilder.append(" ON " + JobHeuristicResult.TABLE.TABLE_NAME + "." + JobHeuristicResult.TABLE.JOB_JOB_ID + + " = " + JobResult.TABLE.TABLE_NAME + "." + JobResult.TABLE.JOB_ID); + return sqlQueryBuilder; + } + + static StringBuilder getSqlJoinQueryWithUsernameIndex() { + StringBuilder sqlQueryBuilder = getJobResultQuery(); + sqlQueryBuilder = setUseIndex(sqlQueryBuilder, JobResult.TABLE.USERNAME_INDEX); + sqlQueryBuilder.append(" JOIN " + JobHeuristicResult.TABLE.TABLE_NAME); + sqlQueryBuilder.append(" ON " + JobHeuristicResult.TABLE.TABLE_NAME + "." + JobHeuristicResult.TABLE.JOB_JOB_ID + + " = " + JobResult.TABLE.TABLE_NAME + "." + JobResult.TABLE.JOB_ID); + return sqlQueryBuilder; + } + + private static StringBuilder setUseIndex(StringBuilder sqlQueryBuilder, String index) { + return sqlQueryBuilder.append(" USE INDEX ( " + index + ")"); + } + +} diff --git a/app/model/JobResult.java b/app/model/JobResult.java index 6721c9a31..ef231dd29 100644 --- a/app/model/JobResult.java +++ b/app/model/JobResult.java @@ -52,6 +52,7 @@ public static class TABLE { "job_result.flow_exec_url", "job_result.flow_url" }; + public static final String USERNAME_INDEX = "ix_job_result_username_1"; } public static String getColumnList() { diff --git a/app/views/search.scala.html b/app/views/search.scala.html index 07d8d5583..8e4452b83 100644 --- a/app/views/search.scala.html +++ b/app/views/search.scala.html @@ -45,7 +45,7 @@
- +
diff --git a/conf/evolutions/default/5.sql b/conf/evolutions/default/5.sql new file mode 100644 index 000000000..fed9d1fe2 --- /dev/null +++ b/conf/evolutions/default/5.sql @@ -0,0 +1,13 @@ +# add/remove index + +# --- !Ups + +create index ix_combined_severity_analysis_name on job_heuristic_result (severity, analysis_name); +drop index ix_job_result_severity_1 on job_result; +drop index ix_job_result_cluster_1 on job_result; + +# --- !Downs + +drop index ix_combined_severity_analysis_name on job_heuristic_result; +create index ix_job_result_severity_1 on job_result (severity); +create index ix_job_result_cluster_1 on job_result (cluster); \ No newline at end of file From adf1cc21c3bd46236b3e688cb85cdbc884638403 Mon Sep 17 00:00:00 2001 From: Fangshi Li Date: Tue, 31 Mar 2015 11:08:35 -0700 Subject: [PATCH 056/272] HADOOP-9714: Add Pluggable jobtype to Dr. Elephant --- .../linkedin/drelephant/ElephantAnalyser.java | 57 ++++---- .../linkedin/drelephant/ElephantRunner.java | 3 +- .../linkedin/drelephant/hadoop/JobType.java | 30 +++++ .../drelephant/util/HeuristicConf.java | 12 +- .../linkedin/drelephant/util/JobTypeConf.java | 124 ++++++++++++++++++ app/controllers/Application.java | 3 +- app/model/JobResult.java | 2 +- app/model/JobType.java | 45 ------- app/views/multijob.scala.html | 2 +- app/views/relatedjob.scala.html | 2 +- app/views/search.scala.html | 2 +- app/views/singlejob.scala.html | 2 +- conf/JobType.xml | 38 ++++++ conf/evolutions/default/6.sql | 11 ++ 14 files changed, 248 insertions(+), 85 deletions(-) create mode 100644 app/com/linkedin/drelephant/hadoop/JobType.java create mode 100644 app/com/linkedin/drelephant/util/JobTypeConf.java delete mode 100644 app/model/JobType.java create mode 100644 conf/JobType.xml create mode 100644 conf/evolutions/default/6.sql diff --git a/app/com/linkedin/drelephant/ElephantAnalyser.java b/app/com/linkedin/drelephant/ElephantAnalyser.java index 5c7d1ae23..6631b5f93 100644 --- a/app/com/linkedin/drelephant/ElephantAnalyser.java +++ b/app/com/linkedin/drelephant/ElephantAnalyser.java @@ -2,26 +2,32 @@ import java.util.ArrayList; import java.util.List; -import model.JobType; +import java.util.Properties; + import play.api.Play; + import org.apache.log4j.Logger; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; import com.linkedin.drelephant.hadoop.HadoopJobData; +import com.linkedin.drelephant.hadoop.JobType; import com.linkedin.drelephant.util.HeuristicConf; import com.linkedin.drelephant.util.HeuristicConfData; +import com.linkedin.drelephant.util.JobTypeConf; public class ElephantAnalyser { public static final String NO_DATA = "No Data Received"; - - private List _heuristicNames = new ArrayList(); + public static final String DEFAULT_TYPE = "HadoopJava"; private static final Logger logger = Logger.getLogger(ElephantAnalyser.class); private static ElephantAnalyser _analyzer = null; + + private List _heuristicNames = new ArrayList(); private HeuristicResult _nodata; private List _heuristics = new ArrayList(); + private List _jobTypeList; public static void init() { if (_analyzer == null) { @@ -39,15 +45,8 @@ public static ElephantAnalyser instance() { private ElephantAnalyser() { logger.info("Initialize Analyzer... Loading heuristics...."); _nodata = new HeuristicResult(NO_DATA, Severity.LOW); - List heuristics = loadHeuristics(); - for (Heuristic heuristic : heuristics) { - addHeuristic(heuristic); - } - } - - public void addHeuristic(Heuristic heuristic) { - _heuristics.add(heuristic); - _heuristicNames.add(heuristic.getHeuristicName()); + loadHeuristics(); + loadJobType(); } public HeuristicResult[] analyse(HadoopJobData data) { @@ -63,32 +62,41 @@ public HeuristicResult[] analyse(HadoopJobData data) { return results.toArray(new HeuristicResult[results.size()]); } - public JobType getJobType(HadoopJobData data) { - String pigVersion = data.getJobConf().getProperty("pig.script"); - if (pigVersion != null && !pigVersion.isEmpty()) { - return JobType.PIG; - } - String hiveMapredMode = data.getJobConf().getProperty("hive.mapred.mode"); - if (hiveMapredMode != null && !hiveMapredMode.isEmpty()) { - return JobType.HIVE; + public String getJobType(HadoopJobData data) { + Properties conf = data.getJobConf(); + for (JobType type: _jobTypeList) { + if(type.matchType(conf)) { + return type.getName(); + } } - return JobType.HADOOPJAVA; + // Return default type. This line shouldn't reached as the job type list + // has a default type as its last element. + return DEFAULT_TYPE; } public List getHeuristicNames() { return this._heuristicNames; } - private List loadHeuristics() { + public List getJobTypes() { + return _jobTypeList; + } + + private void loadJobType() { + _jobTypeList = JobTypeConf.instance().getJobTypeList(); + } + + private void loadHeuristics() { - List heuristics = new ArrayList(); HeuristicConf conf = HeuristicConf.instance(); List heuristicsConfData = conf.getHeuristicsConfData(); for (HeuristicConfData data : heuristicsConfData) { try { Class heuristicClass = Play.current().classloader().loadClass(data.getClassName()); - heuristics.add((Heuristic) heuristicClass.newInstance()); + Heuristic heuristicInstance = (Heuristic) heuristicClass.newInstance(); + _heuristics.add(heuristicInstance); + _heuristicNames.add(heuristicInstance.getHeuristicName()); logger.info("Load Heuristic : " + data.getClassName()); } catch (ClassNotFoundException e) { throw new RuntimeException("Could not find class " + data.getClassName(), e); @@ -101,6 +109,5 @@ private List loadHeuristics() { throw new RuntimeException(data.getClassName() + " is not a valid Heuristic class.", e); } } - return heuristics; } } diff --git a/app/com/linkedin/drelephant/ElephantRunner.java b/app/com/linkedin/drelephant/ElephantRunner.java index aaf5fe0b6..5f3c88f6c 100644 --- a/app/com/linkedin/drelephant/ElephantRunner.java +++ b/app/com/linkedin/drelephant/ElephantRunner.java @@ -21,7 +21,6 @@ import java.util.concurrent.atomic.AtomicBoolean; import model.JobHeuristicResult; import model.JobResult; -import model.JobType; import org.apache.avro.generic.IndexedRecord; import org.apache.hadoop.mapred.JobConf; import org.apache.log4j.Logger; @@ -197,7 +196,7 @@ private void analyzeJob(HadoopJobData jobData, int execThreadNum) { logger.info("Analyze " + jobData.getJobId() + " by executor " + execThreadNum); HeuristicResult[] analysisResults = analyser.analyse(jobData); - JobType jobType = analyser.getJobType(jobData); + String jobType = analyser.getJobType(jobData); // Save to DB JobResult result = new JobResult(); diff --git a/app/com/linkedin/drelephant/hadoop/JobType.java b/app/com/linkedin/drelephant/hadoop/JobType.java new file mode 100644 index 000000000..b78240306 --- /dev/null +++ b/app/com/linkedin/drelephant/hadoop/JobType.java @@ -0,0 +1,30 @@ +package com.linkedin.drelephant.hadoop; + +import java.util.Properties; +import java.util.regex.Pattern; + +public class JobType { + + private String _type; + private String _confName; + private Pattern _confPattern; + + public JobType(String type, String confName, String confPattern) { + this._type = type; + this._confName = confName; + this._confPattern = Pattern.compile(confPattern); + } + + public boolean matchType(Properties jobProp) { + return jobProp.containsKey(_confName) && + _confPattern.matcher((String)jobProp.get(_confName)).matches(); + } + + public String getName() { + return _type; + } + + public String toString() { + return _type; + } +} diff --git a/app/com/linkedin/drelephant/util/HeuristicConf.java b/app/com/linkedin/drelephant/util/HeuristicConf.java index cc1bbd47a..2707d4ad9 100644 --- a/app/com/linkedin/drelephant/util/HeuristicConf.java +++ b/app/com/linkedin/drelephant/util/HeuristicConf.java @@ -72,9 +72,9 @@ private void parseHeuristicConf() { } NodeList nodes = document.getDocumentElement().getChildNodes(); + int n = 0; for (int i = 0; i < nodes.getLength(); i++) { Node node = nodes.item(i); - int n = 0; if (node.getNodeType() == Node.ELEMENT_NODE) { n++; Element heuristicNode = (Element) node; @@ -92,26 +92,26 @@ private void parseHeuristicConf() { String heuristicName; Node heuristicNameNode = heuristicNode.getElementsByTagName("heuristicname").item(0); if (heuristicNameNode == null) { - throw new RuntimeException("No tag 'heuristicname' in heuristic " + n); + throw new RuntimeException("No tag 'heuristicname' in heuristic " + n + " classname " + className); } heuristicName = heuristicNameNode.getTextContent(); if (heuristicName.equals("")) { - throw new RuntimeException("Empty tag 'heuristicname' in heuristic " + n); + throw new RuntimeException("Empty tag 'heuristicname' in heuristic " + n + " classname " + className); } String viewName; Node viewNameNode = heuristicNode.getElementsByTagName("viewname").item(0); if (viewNameNode == null) { - throw new RuntimeException("No tag 'viewname' in heuristic " + n); + throw new RuntimeException("No tag 'viewname' in heuristic " + n + " classname " + className); } viewName = viewNameNode.getTextContent(); if (viewName.equals("")) { - throw new RuntimeException("Empty tag 'viewname' in heuristic " + n); + throw new RuntimeException("Empty tag 'viewname' in heuristic " + n + " classname " + className); } Node versionsNode = heuristicNode.getElementsByTagName("hadoopversions").item(0); if (versionsNode == null || versionsNode.getNodeType() != Node.ELEMENT_NODE) { - throw new RuntimeException("No tag or invalid tag 'hadoopversions' in heuristic " + n); + throw new RuntimeException("No tag or invalid tag 'hadoopversions' in heuristic " + n + " classname " + className); } NodeList versionList = ((Element) versionsNode).getElementsByTagName("version"); for (int j = 0; j < versionList.getLength(); j++) { diff --git a/app/com/linkedin/drelephant/util/JobTypeConf.java b/app/com/linkedin/drelephant/util/JobTypeConf.java new file mode 100644 index 000000000..ed8bf18b3 --- /dev/null +++ b/app/com/linkedin/drelephant/util/JobTypeConf.java @@ -0,0 +1,124 @@ +package com.linkedin.drelephant.util; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.PatternSyntaxException; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.apache.log4j.Logger; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import com.linkedin.drelephant.hadoop.JobType; + +import play.Play; + + +public class JobTypeConf { + private static final Logger logger = Logger.getLogger(JobTypeConf.class); + private static final int TYPE_LEN_LIMIT = 20; + private static final String DEFAULT_TYPE = "HadoopJava"; + private static final String CONFIG_FILE_PATH = "JobType.xml"; + private static JobTypeConf _heuristicConfInstance = new JobTypeConf(); + + private List _jobTypeList; + + private JobTypeConf() { + parseJobTypeConf(); + } + + public static JobTypeConf instance() { + return _heuristicConfInstance; + } + + public List getJobTypeList() { + return _jobTypeList; + } + + private void parseJobTypeConf() { + + logger.info("Loading job type config file " + CONFIG_FILE_PATH); + _jobTypeList = new ArrayList(); + + InputStream instream = Play.application().resourceAsStream(CONFIG_FILE_PATH); + if (instream == null) { + throw new RuntimeException("File " + CONFIG_FILE_PATH + " does not exist."); + } + + Document document = null; + try { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + document = builder.parse(instream); + } catch (ParserConfigurationException e) { + throw new RuntimeException("XML Parser could not be created.", e); + } catch (SAXException e) { + throw new RuntimeException(CONFIG_FILE_PATH + " is not properly formed", e); + } catch (IOException e) { + throw new RuntimeException("Unable to read " + CONFIG_FILE_PATH, e); + } + + NodeList nodes = document.getDocumentElement().getChildNodes(); + for (int i = 0; i < nodes.getLength(); i++) { + Node node = nodes.item(i); + int n = 0; + if (node.getNodeType() == Node.ELEMENT_NODE) { + n++; + Element jobTypeNode = (Element) node; + String jobTypeName; + Node jobTypeNameNode = jobTypeNode.getElementsByTagName("type").item(0); + if (jobTypeNameNode == null) { + throw new RuntimeException("No tag 'jobtype' in jobtype " + n); + } + jobTypeName = jobTypeNameNode.getTextContent(); + if (jobTypeName.equals("")) { + throw new RuntimeException("Empty tag 'jobtype' in jobtype " + n); + } + // Truncate jobtype length for db constraint + if (jobTypeName.length() > TYPE_LEN_LIMIT) { + logger.info("Truncate type " + jobTypeName.length()); + jobTypeName = jobTypeName.substring(0, TYPE_LEN_LIMIT); + } + + String jobConfName; + Node jobConfNameNode = jobTypeNode.getElementsByTagName("conf").item(0); + if (jobConfNameNode == null) { + throw new RuntimeException("No tag 'conf' in jobtype " + jobTypeName ); + } + jobConfName = jobConfNameNode.getTextContent(); + if (jobConfName.equals("")) { + throw new RuntimeException("Empty tag 'conf' in jobtype " + jobTypeName); + } + + String jobConfValue; + Node jobConfValueNode = jobTypeNode.getElementsByTagName("value").item(0); + if (jobConfValueNode == null) { + // Default regex. match any char one or more times + jobConfValue = ".*"; + } else { + jobConfValue = jobConfValueNode.getTextContent(); + if (jobConfValue.equals("")) { + jobConfValue = ".*"; + } + } + logger.info("Loaded jobType:" + jobTypeName + " confName:" + jobConfName + " confValue:" + jobConfValue); + try { + _jobTypeList.add(new JobType(jobTypeName, jobConfName, jobConfValue)); + } catch (PatternSyntaxException e) { + throw new RuntimeException("Error processing this pattern. Pattern:" + jobConfValue + " jobtype:" + jobTypeName); + } + } + } + // Add default type to the list. All jobs will match this type because this conf always exists + _jobTypeList.add(new JobType(DEFAULT_TYPE, "mapreduce.framework.name", ".*")); + logger.info("Loaded total " + _jobTypeList.size() + " job types."); + } +} diff --git a/app/controllers/Application.java b/app/controllers/Application.java index 08977abba..2f6be75ad 100644 --- a/app/controllers/Application.java +++ b/app/controllers/Application.java @@ -15,7 +15,6 @@ import model.JobHeuristicResult; import model.JobResult; -import model.JobType; import org.apache.http.client.utils.URLEncodedUtils; import org.apache.http.message.BasicNameValuePair; @@ -176,7 +175,7 @@ private static ExpressionList generateQuery() { query = query.like(JobResult.TABLE.USERNAME, username); } if (isSet(jobType)) { - query = query.eq(JobResult.TABLE.JOB_TYPE, JobType.getDbName(jobType)); + query = query.eq(JobResult.TABLE.JOB_TYPE, jobType); } if (isSet(severity)) { if (isSet(analysis)) { diff --git a/app/model/JobResult.java b/app/model/JobResult.java index ef231dd29..5b8278e98 100644 --- a/app/model/JobResult.java +++ b/app/model/JobResult.java @@ -79,7 +79,7 @@ public static String getColumnList() { public Severity severity; @Column - public JobType jobType; + public String jobType; @Column(length = 200) public String url; diff --git a/app/model/JobType.java b/app/model/JobType.java deleted file mode 100644 index 1e91b594c..000000000 --- a/app/model/JobType.java +++ /dev/null @@ -1,45 +0,0 @@ -package model; - -import java.util.HashMap; -import java.util.Map; - -import com.avaje.ebean.annotation.EnumValue; - - -public enum JobType { - @EnumValue("Hadoop") - HADOOPJAVA("HadoopJava", "Hadoop"), - - @EnumValue("Pig") - PIG("Pig", "Pig"), - - @EnumValue("Hive") - HIVE("Hive", "Hive"); - - private String _name; - private String _dbName; - - JobType(String name, String dbName) { - this._name = name; - this._dbName = dbName; - } - - private static Map _jobTypeNameMap; - static { - _jobTypeNameMap = new HashMap(); - for (JobType jobType : JobType.values()) { - _jobTypeNameMap.put(jobType._name, jobType._dbName); - } - } - - public String getName() { - return _name; - } - - public static String getDbName(String jobType) { - if (_jobTypeNameMap.containsKey(jobType)) { - return _jobTypeNameMap.get(jobType); - } - return null; - } -} diff --git a/app/views/multijob.scala.html b/app/views/multijob.scala.html index 299246ed2..9e57bf122 100644 --- a/app/views/multijob.scala.html +++ b/app/views/multijob.scala.html @@ -7,7 +7,7 @@

@title

diff --git a/conf/JobType.xml b/conf/JobType.xml new file mode 100644 index 000000000..622187f3c --- /dev/null +++ b/conf/JobType.xml @@ -0,0 +1,38 @@ + + + + + + Pig + pig.script + + + Hive + hive.mapred.mode + + + Cascading + cascading.app.frameworks + + + Voldemort + mapred.reducer.class + voldemort.store.readonly.mr.HadoopStoreBuilderReducer + + + Kafka + kafka.url + + diff --git a/conf/evolutions/default/6.sql b/conf/evolutions/default/6.sql new file mode 100644 index 000000000..54d5c07ef --- /dev/null +++ b/conf/evolutions/default/6.sql @@ -0,0 +1,11 @@ +# change col size + +# --- !Ups + +alter table job_result + modify job_type varchar(20); + +# --- !Downs + +alter table job_result + modify job_type varchar(6); From c16a050f690cdebb8007d34f360347f355f54a8d Mon Sep 17 00:00:00 2001 From: Fangshi Li Date: Thu, 2 Apr 2015 09:50:41 -0700 Subject: [PATCH 057/272] HADOOP-9716: Dr. Elephant rest interface needs search endpoint --- app/controllers/Application.java | 34 ++++++++++++++++++++++++++++++++ conf/routes | 1 + 2 files changed, 35 insertions(+) diff --git a/app/controllers/Application.java b/app/controllers/Application.java index 2f6be75ad..db0527047 100644 --- a/app/controllers/Application.java +++ b/app/controllers/Application.java @@ -49,6 +49,7 @@ public class Application extends Controller { private static final long FETCH_DELAY = 60 * 1000; private static final int PAGE_LENGTH = 20; private static final int PAGE_BAR_LENGTH = 5; + private static final int REST_PAGE_LENGTH = 100; private static final String FORM_JOB_ID = "jobid"; private static final String FORM_FLOW_URL = "flowurl"; private static final String FORM_USER = "user"; @@ -386,6 +387,39 @@ private static Map> groupJobsByExec(List resu return resultMap; } + public static Result restSearch() { + DynamicForm form = Form.form().bindFromRequest(request()); + String jobId = form.get(FORM_JOB_ID); + jobId = jobId != null ? jobId.trim() : ""; + String flowUrl = form.get(FORM_FLOW_URL); + flowUrl = (flowUrl != null) ? flowUrl.trim() : null; + if (!jobId.isEmpty()) { + JobResult result = JobResult.find.byId(jobId); + if (result != null) { + return ok(Json.toJson(result)); + } else { + return notFound("Unable to find record on job id: " + jobId); + } + } else if (flowUrl != null && !flowUrl.isEmpty()) { + List results = JobResult.find.where().eq(JobResult.TABLE.FLOW_EXEC_URL, flowUrl).findList(); + return ok(Json.toJson(results)); + } + + int page = 1; + if (request().queryString().containsKey("page")) { + page = Integer.parseInt(request().queryString().get("page")[0]); + if (page <= 0) { + page = 1; + } + } + + ExpressionList query = generateQuery(); + List results = + query.order().desc("analysisTime").setFirstRow((page - 1) * REST_PAGE_LENGTH) + .setMaxRows(REST_PAGE_LENGTH).findList(); + return ok(Json.toJson(results)); + } + public static Result testEmail() { DynamicForm form = Form.form().bindFromRequest(request()); diff --git a/conf/routes b/conf/routes index e7e129585..c3748d818 100644 --- a/conf/routes +++ b/conf/routes @@ -13,6 +13,7 @@ GET /alljobexecs controllers.Application.allJobExecs() GET /rest/job controllers.Application.restJobResult(id: String) GET /rest/jobexec controllers.Application.restJobExecResult(url: String) GET /rest/flowexec controllers.Application.restFlowExecResult(url: String) +GET /rest/search controllers.Application.restSearch() # Map static resources from the /public folder to the /assets URL path From d73bc48daccd77cbfd2658ba3a8e6fb841a8deee Mon Sep 17 00:00:00 2001 From: Fangshi Li Date: Thu, 2 Apr 2015 10:04:53 -0700 Subject: [PATCH 058/272] HADOOP-9853: Release Dr. Elephant v0.6.6 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 9535e2a8a..c997a522a 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ import play.Project._ name := "dr-elephant" -version := "0.6.6-SNAPSHOT" +version := "0.6.6" javacOptions in Compile ++= Seq("-source", "1.6", "-target", "1.6") From d163a69ea688973397fb7cd3edb9ddf6823da4a8 Mon Sep 17 00:00:00 2001 From: Akshay Rai Date: Sun, 5 Apr 2015 22:04:55 -0700 Subject: [PATCH 059/272] HADOOP-9886: Fix for making Dr. Elephant's pagination thread safe --- app/controllers/Application.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/Application.java b/app/controllers/Application.java index db0527047..6029e46c1 100644 --- a/app/controllers/Application.java +++ b/app/controllers/Application.java @@ -63,7 +63,6 @@ public class Application extends Controller { private static int _numJobsCritical = 0; private static int _numJobsSevere = 0; private static Map _helpPages = new HashMap(); - private static PaginationStats paginationStats = new PaginationStats(PAGE_LENGTH, PAGE_BAR_LENGTH); static { try { @@ -96,6 +95,7 @@ public static Result search() { } // Paginate the results + PaginationStats paginationStats = new PaginationStats(PAGE_LENGTH, PAGE_BAR_LENGTH); int pageLength = paginationStats.getPageLength(); paginationStats.setCurrentPage(1); final Map searchString = request().queryString(); From 0fcaa7bcc360a254eb91553f4929ddbcdfb3dd11 Mon Sep 17 00:00:00 2001 From: Fangshi Li Date: Mon, 20 Apr 2015 10:56:27 -0700 Subject: [PATCH 060/272] HADOOP-10289: Update Dr. Elephant version number to 0.6.7-SNAPSHOT --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index c997a522a..62e704d05 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ import play.Project._ name := "dr-elephant" -version := "0.6.6" +version := "0.6.7-SNAPSHOT" javacOptions in Compile ++= Seq("-source", "1.6", "-target", "1.6") From 9e722892a9029ddfb3083cadc845737e6dbbffef Mon Sep 17 00:00:00 2001 From: Fangshi Li Date: Mon, 20 Apr 2015 11:00:57 -0700 Subject: [PATCH 061/272] HADOOP-10089: Dr. Elephant misses on type of Voldemort bnp job --- app/com/linkedin/drelephant/ElephantFetcherYarn.java | 4 ++-- conf/JobType.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/com/linkedin/drelephant/ElephantFetcherYarn.java b/app/com/linkedin/drelephant/ElephantFetcherYarn.java index 1711fea92..f85d959d2 100644 --- a/app/com/linkedin/drelephant/ElephantFetcherYarn.java +++ b/app/com/linkedin/drelephant/ElephantFetcherYarn.java @@ -78,10 +78,10 @@ public List fetchJobList() throws IOException, AuthenticationExce jobList.addAll(retryList); } + logger.info("Fetcher gets total " + jobList.size() + " jobs (" + retrySize + " retry jobs) from time range [" + + _lastTime + "," + _currentTime + "]"); _lastTime = _currentTime; - logger.info("Fetcher gets total " + jobList.size() + " jobs (" + retrySize + " retry jobs)"); - return jobList; } diff --git a/conf/JobType.xml b/conf/JobType.xml index 622187f3c..c1924ff74 100644 --- a/conf/JobType.xml +++ b/conf/JobType.xml @@ -29,7 +29,7 @@ Voldemort mapred.reducer.class - voldemort.store.readonly.mr.HadoopStoreBuilderReducer + voldemort.store.readonly.mr.* Kafka From 872b20e4c3a25c08b7271414c39d84c6b628d1b4 Mon Sep 17 00:00:00 2001 From: Fangshi Li Date: Mon, 20 Apr 2015 13:51:03 -0700 Subject: [PATCH 062/272] HADOOP-10301: Dr. Elephant occasionally misses a job --- app/com/linkedin/drelephant/ElephantFetcherYarn.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/com/linkedin/drelephant/ElephantFetcherYarn.java b/app/com/linkedin/drelephant/ElephantFetcherYarn.java index f85d959d2..b8f185cc9 100644 --- a/app/com/linkedin/drelephant/ElephantFetcherYarn.java +++ b/app/com/linkedin/drelephant/ElephantFetcherYarn.java @@ -28,6 +28,8 @@ public class ElephantFetcherYarn implements ElephantFetcher { private static final Logger logger = Logger.getLogger(ElephantFetcher.class); + // We provide one minute job fetch delay due to the job sending lag from AM/NM to JobHistoryServer HDFS + private static final long FETCH_DELAY = 60000; private RetryFactory _retryFactory; private URLFactory _urlFactory; @@ -60,7 +62,10 @@ public List fetchJobList() throws IOException, AuthenticationExce List jobList; int retrySize = 0; - _currentTime = System.currentTimeMillis(); + // There is a lag of job data from AM/NM to JobHistoryServer HDFS, we shouldn't use the current + // time, since there might be new jobs arriving after we fetch jobs. + // We provide one minute delay to address this lag. + _currentTime = System.currentTimeMillis() - FETCH_DELAY; URL joblistURL= _urlFactory.fetchJobListURL(_lastTime, _currentTime); try { From 877ae896800ff9dd8327560883833dd0b61762eb Mon Sep 17 00:00:00 2001 From: Fangshi Li Date: Mon, 20 Apr 2015 18:25:24 -0700 Subject: [PATCH 063/272] HADOOP-9900: Dr. Elephant Mapper Input Size Help Page should suggest increasing mapred.min.split.size for too many mappers, NOT mapred.max.split.size --- app/views/helpMapperInputSize.scala.html | 37 ++++++++++++++++++------ 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/app/views/helpMapperInputSize.scala.html b/app/views/helpMapperInputSize.scala.html index 1ba393106..88f96cc55 100644 --- a/app/views/helpMapperInputSize.scala.html +++ b/app/views/helpMapperInputSize.scala.html @@ -39,11 +39,28 @@

Mapper Input Size

Suggestions

- Set the number of mappers smaller by specifying a number or combining small files using Pig or Hive.
-
- For Hadoop/Java jobs: Try increasing mapred.max.split.size in the job conf so that each mapper will now process larger data.
- For Apache-Pig jobs : Try setting pig.maxCombinedSplitSize and mapred.max.split.size to something larger.
- For Apache-Hive jobs: Try increasing mapred.max.split.size in the job conf so that each mapper will now process larger data.
+ Note: By default HadoopJava will not combine small files. if that’s the case, you should either use CombineFileInputFormat or use Pig/Hive. +
+
+ Otherwise, you should tune mapper split size to reduce number of mappers and let each mapper process larger data
+ The parameter for changing split size is:
+

    +
  • mapred.max/min.split.size (Hadoop 1)
  • +
  • mapreduce.input.fileinputformat.split.minsize/maxsize (Hadoop 2)
  • +
  • pig.maxCombinedSplitSize (Pig Only)
  • +
+ Examples on how to set them: +
    +
  • HadoopJava: conf.setLong("mapred.min.split.size",XXXXX)
  • +
  • Pig: set mapred.min.split.size XXXXX
  • +
  • Hive: set mapred.min.split.size=XXXXX
  • +
+ + The split size is controlled by formula max(minSplitSize, min(maxSplitSize, blockSize)). By default, blockSize=512MB and minSplit < blockSize < maxSplit.
+ You should always refer to this formula.
+ In the case above, try increase min split size and let each mapper process larger data.
+ See
Hadoop Tuning Tips for further information.
+

Large files/Unsplittable files

@@ -81,10 +98,12 @@

Mapper Input Size

Suggestions

- Set the number of mappers larger by giving a specific number.
+ The split size is too large. You should tune mapper split size to increase number of mappers and let each mapper process less data.

- The maximum map split size is controlled by the "mapred.max.split.size" parameter. By decreasing this value - below dfs.block.size, you can reduce the maximum input size for each mapper, thereby increase the number of mappers - in your job.
+ The input split size is controlled by formula max(minSplitSize, min(maxSplitSize, blockSize)).
+ See the previous section for further details.
+ + In the case above, since mapper input size >> block size and you want to increase mappers, you should decrease min split size close to BlockSize(512MB).
+ See Hadoop Tuning Tips for further information.

From 71a7fc5e45729cc8231e15f391c66066b5562ee6 Mon Sep 17 00:00:00 2001 From: Fangshi Li Date: Mon, 20 Apr 2015 18:25:58 -0700 Subject: [PATCH 064/272] HADOOP-10290: Release Dr. Elephant v0.6.7 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 62e704d05..3188ebd7e 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ import play.Project._ name := "dr-elephant" -version := "0.6.7-SNAPSHOT" +version := "0.6.7" javacOptions in Compile ++= Seq("-source", "1.6", "-target", "1.6") From 9009da5404c0534a40630d37ab086f2477431e74 Mon Sep 17 00:00:00 2001 From: Fangshi Li Date: Tue, 21 Apr 2015 13:59:02 -0700 Subject: [PATCH 065/272] HADOOP-10314: Dr. Elephant should not mention deprecated properties in Help topics page --- app/views/helpShuffleSort.scala.html | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/views/helpShuffleSort.scala.html b/app/views/helpShuffleSort.scala.html index 5d2e3542a..4177779b2 100644 --- a/app/views/helpShuffleSort.scala.html +++ b/app/views/helpShuffleSort.scala.html @@ -36,13 +36,13 @@

Shuffle & Sort

Suggestions

- If your shuffle time is high but sort is low, you likely need to tune slowstart by setting "mapred.reduce.slowstart.completed.maps" to 0.90+ up to 1.0
+ If your shuffle time is high but sort is low, you likely need to tune slowstart by setting "mapred.reduce.slowstart.completed.maps"(hadoop1) or "mapreduce.job.reduce.slowstart.completedmaps"(hadoop2) to 0.90+ up to 1.0
Warning: Setting this parameter close to 1.0 will decrease shuffle time, but may increase this job's total runtime

- If you want to set it to 0.95:
- For Hadoop/Java jobs: Use "jobConf.setFloat("mapred.reduce.slowstart.completed.maps", 0.95f);"
- For Apache-Pig jobs: Use "set mapred.reduce.slowstart.completed.maps 0.95"
- For Apache-Hive jobs: Use "set mapred.reduce.slowstart.completed.maps=0.95"
- For Azkaban flows, add jvm.args=-Dmapred.reduce.slowstart.completed.maps=0.95 to your job properties
+ If you want to set it to 0.95 on Nertz:
+ For Hadoop/Java jobs: Use "jobConf.setFloat("mapreduce.job.reduce.slowstart.completedmaps", 0.95f);"
+ For Apache-Pig jobs: Use "set mapreduce.job.reduce.slowstart.completedmaps 0.95"
+ For Apache-Hive jobs: Use "set mapreduce.job.reduce.slowstart.completedmaps=0.95"
+ For Azkaban flows, add jvm.args=-Dmapreduce.job.reduce.slowstart.completedmaps=0.95 to your job properties(will affect all MapReduce jobs under this azkaban job)
See Hadoop Tuning Tips for further information.

\ No newline at end of file From 1bab26e521c12854b93f8bb4985d6a9b59a5e7b8 Mon Sep 17 00:00:00 2001 From: Subbu Subramaniam Date: Thu, 23 Apr 2015 14:33:13 -0700 Subject: [PATCH 066/272] [HADOOP-10405] Fix one-off error in getting cluster name from NN hostname RB=468832 BUGS=HADOOP-10405 R=fli A=fli,ahsu --- app/com/linkedin/drelephant/DaliMetricsAPI.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/com/linkedin/drelephant/DaliMetricsAPI.java b/app/com/linkedin/drelephant/DaliMetricsAPI.java index c8ca6425c..b8a338e2d 100644 --- a/app/com/linkedin/drelephant/DaliMetricsAPI.java +++ b/app/com/linkedin/drelephant/DaliMetricsAPI.java @@ -329,7 +329,11 @@ private String nnHostPort2ClusterName(String hostPort) { return UNKNOWN_CLUSTERNAME; } int clusterNameStart = hostNameSansDomain.indexOf('-'); - int clusterNameEnd = hostNameSansDomain.length()-5; + if (clusterNameStart == -1) { // There is no dash here, just return unknown clustername + return UNKNOWN_CLUSTERNAME; + } + clusterNameStart++; + int clusterNameEnd = hostNameSansDomain.length()-4; return hostNameSansDomain.substring(clusterNameStart, clusterNameEnd); } From 478b2d5ebb294ae1adcf28ada35f25e57108a192 Mon Sep 17 00:00:00 2001 From: Fangshi Li Date: Fri, 12 Jun 2015 14:25:58 -0700 Subject: [PATCH 067/272] HADOOP-11768: Update Dr. Elephant version to v1.0.0-SNAPSHOT --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 3188ebd7e..8bc81247e 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ import play.Project._ name := "dr-elephant" -version := "0.6.7" +version := "1.0.0-SNAPSHOT" javacOptions in Compile ++= Seq("-source", "1.6", "-target", "1.6") From df3f7a6c6520ba7a024f9e84b24f8564981dcd80 Mon Sep 17 00:00:00 2001 From: Yitong Zhou Date: Tue, 26 May 2015 18:18:18 -0700 Subject: [PATCH 068/272] OFFREL-234: Adding Spark log analysers into Dr. Elephant --- README.md | 4 +- .../linkedin/drelephant/ElephantAnalyser.java | 113 ------- .../linkedin/drelephant/ElephantContext.java | 257 ++++++++++++++++ .../linkedin/drelephant/ElephantFetcher.java | 42 --- .../linkedin/drelephant/ElephantRunner.java | 238 ++++----------- .../{hadoop => }/HadoopSecurity.java | 12 +- .../linkedin/drelephant/InfoExtractor.java | 44 --- .../drelephant/analysis/AnalysisPromise.java | 160 ++++++++++ .../drelephant/analysis/AnalysisProvider.java | 40 +++ .../drelephant/analysis/ApplicationType.java | 84 ++++++ .../analysis/ClassicAnalysisProvider.java | 137 +++++++++ .../drelephant/analysis/ElephantFetcher.java | 28 ++ .../analysis/HadoopApplicationData.java | 25 ++ .../drelephant/analysis/Heuristic.java | 23 +- .../drelephant/analysis/HeuristicResult.java | 8 + .../linkedin/drelephant/analysis/JobType.java | 43 +++ .../drelephant/analysis/Severity.java | 20 +- .../analysis/YarnAnalysisProvider.java | 126 ++++++++ .../linkedin/drelephant/hadoop/JobType.java | 30 -- .../HadoopCounterHolder.java | 3 +- .../{hadoop => mapreduce}/HadoopTaskData.java | 2 +- .../MapreduceApplicationData.java} | 40 ++- .../MapreduceFetcherClassic.java} | 149 ++-------- .../MapreduceFetcherYarn.java} | 61 ++-- .../heuristics/GenericDataSkewHeuristic.java | 14 +- .../heuristics/JobQueueLimitHeuristic.java | 12 +- .../heuristics/MapperDataSkewHeuristic.java | 10 +- .../heuristics/MapperInputSizeHeuristic.java | 12 +- .../heuristics/MapperSpeedHeuristic.java | 12 +- .../heuristics/MapperSpillHeuristic.java | 12 +- .../heuristics/ReducerDataSkewHeuristic.java | 10 +- .../heuristics/ReducerTimeHeuristic.java | 10 +- .../heuristics/ShuffleSortHeuristic.java | 10 +- .../spark/SparkApplicationData.java | 21 ++ .../spark/SparkEnvironmentData.java | 53 ++++ .../drelephant/spark/SparkExecutorData.java | 55 ++++ .../drelephant/spark/SparkGeneralData.java | 74 +++++ .../spark/SparkJobProgressData.java | 246 ++++++++++++++++ .../drelephant/spark/SparkStorageData.java | 31 ++ .../BestPropertiesConventionHeuristic.java | 77 +++++ .../heuristics/ExecutorLoadHeuristic.java | 142 +++++++++ .../spark/heuristics/JobRuntimeHeuristic.java | 85 ++++++ .../heuristics/MemoryLimitHeuristic.java | 154 ++++++++++ .../heuristics/StageRuntimeHeuristic.java | 96 ++++++ .../drelephant/util/HeuristicConf.java | 80 ++--- .../drelephant/util/HeuristicConfData.java | 25 +- .../drelephant/util/InfoExtractor.java | 98 ++++++ .../linkedin/drelephant/util/JobTypeConf.java | 23 +- .../drelephant/util/MemoryFormatUtils.java | 86 ++++++ app/com/linkedin/drelephant/util/Utils.java | 173 +++++++++++ .../drelephant/util/YarnURLUtils.java | 54 ++++ app/controllers/Application.java | 4 +- .../deploy/history/SparkDataCollection.scala | 278 ++++++++++++++++++ .../spark/deploy/history/SparkFSFetcher.scala | 127 ++++++++ .../StorageStatusTrackingListener.scala | 89 ++++++ app/views/help.scala.html | 4 +- app/views/helpSparkBestProperties.scala.html | 6 + app/views/helpSparkMemoryLimit.scala.html | 9 + app/views/search.scala.html | 4 +- build.sbt | 8 +- conf/HeuristicConf.xml | 82 ------ conf/application.conf | 18 +- conf/elephant-conf.xml | 168 +++++++++++ conf/{JobType.xml => job-types.xml} | 12 +- .../JobQueueLimitHeuristicTest.java | 13 +- .../MapperDataSkewHeuristicTest.java | 11 +- .../MapperInputSizeHeuristicTest.java | 11 +- .../heuristics/MapperSpeedHeuristicTest.java | 11 +- .../heuristics/MapperSpillHeuristicTest.java | 11 +- .../ReducerDataSkewHeuristicTest.java | 11 +- .../heuristics/ReducerTimeHeuristicTest.java | 11 +- .../heuristics/ShuffleSortHeuristicTest.java | 11 +- .../spark/MockSparkApplicationData.java | 61 ++++ ...BestPropertiesConventionHeuristicTest.java | 69 +++++ .../heuristics/JobRuntimeHeuristicTest.java | 14 + .../heuristics/MemoryLimitHeuristicTest.java | 119 ++++++++ .../util/MemoryFormatUtilsTest.java | 85 ++++++ 77 files changed, 3757 insertions(+), 854 deletions(-) delete mode 100644 app/com/linkedin/drelephant/ElephantAnalyser.java create mode 100644 app/com/linkedin/drelephant/ElephantContext.java delete mode 100644 app/com/linkedin/drelephant/ElephantFetcher.java rename app/com/linkedin/drelephant/{hadoop => }/HadoopSecurity.java (86%) delete mode 100644 app/com/linkedin/drelephant/InfoExtractor.java create mode 100644 app/com/linkedin/drelephant/analysis/AnalysisPromise.java create mode 100644 app/com/linkedin/drelephant/analysis/AnalysisProvider.java create mode 100644 app/com/linkedin/drelephant/analysis/ApplicationType.java create mode 100644 app/com/linkedin/drelephant/analysis/ClassicAnalysisProvider.java create mode 100644 app/com/linkedin/drelephant/analysis/ElephantFetcher.java create mode 100644 app/com/linkedin/drelephant/analysis/HadoopApplicationData.java create mode 100644 app/com/linkedin/drelephant/analysis/JobType.java create mode 100644 app/com/linkedin/drelephant/analysis/YarnAnalysisProvider.java delete mode 100644 app/com/linkedin/drelephant/hadoop/JobType.java rename app/com/linkedin/drelephant/{hadoop => mapreduce}/HadoopCounterHolder.java (98%) rename app/com/linkedin/drelephant/{hadoop => mapreduce}/HadoopTaskData.java (95%) rename app/com/linkedin/drelephant/{hadoop/HadoopJobData.java => mapreduce/MapreduceApplicationData.java} (58%) rename app/com/linkedin/drelephant/{ElephantFetcherClassic.java => mapreduce/MapreduceFetcherClassic.java} (81%) rename app/com/linkedin/drelephant/{ElephantFetcherYarn.java => mapreduce/MapreduceFetcherYarn.java} (86%) rename app/com/linkedin/drelephant/{analysis => mapreduce}/heuristics/GenericDataSkewHeuristic.java (85%) rename app/com/linkedin/drelephant/{analysis => mapreduce}/heuristics/JobQueueLimitHeuristic.java (90%) rename app/com/linkedin/drelephant/{analysis => mapreduce}/heuristics/MapperDataSkewHeuristic.java (50%) rename app/com/linkedin/drelephant/{analysis => mapreduce}/heuristics/MapperInputSizeHeuristic.java (91%) rename app/com/linkedin/drelephant/{analysis => mapreduce}/heuristics/MapperSpeedHeuristic.java (87%) rename app/com/linkedin/drelephant/{analysis => mapreduce}/heuristics/MapperSpillHeuristic.java (84%) rename app/com/linkedin/drelephant/{analysis => mapreduce}/heuristics/ReducerDataSkewHeuristic.java (50%) rename app/com/linkedin/drelephant/{analysis => mapreduce}/heuristics/ReducerTimeHeuristic.java (88%) rename app/com/linkedin/drelephant/{analysis => mapreduce}/heuristics/ShuffleSortHeuristic.java (88%) create mode 100644 app/com/linkedin/drelephant/spark/SparkApplicationData.java create mode 100644 app/com/linkedin/drelephant/spark/SparkEnvironmentData.java create mode 100644 app/com/linkedin/drelephant/spark/SparkExecutorData.java create mode 100644 app/com/linkedin/drelephant/spark/SparkGeneralData.java create mode 100644 app/com/linkedin/drelephant/spark/SparkJobProgressData.java create mode 100644 app/com/linkedin/drelephant/spark/SparkStorageData.java create mode 100644 app/com/linkedin/drelephant/spark/heuristics/BestPropertiesConventionHeuristic.java create mode 100644 app/com/linkedin/drelephant/spark/heuristics/ExecutorLoadHeuristic.java create mode 100644 app/com/linkedin/drelephant/spark/heuristics/JobRuntimeHeuristic.java create mode 100644 app/com/linkedin/drelephant/spark/heuristics/MemoryLimitHeuristic.java create mode 100644 app/com/linkedin/drelephant/spark/heuristics/StageRuntimeHeuristic.java create mode 100644 app/com/linkedin/drelephant/util/InfoExtractor.java create mode 100644 app/com/linkedin/drelephant/util/MemoryFormatUtils.java create mode 100644 app/com/linkedin/drelephant/util/YarnURLUtils.java create mode 100644 app/org/apache/spark/deploy/history/SparkDataCollection.scala create mode 100644 app/org/apache/spark/deploy/history/SparkFSFetcher.scala create mode 100644 app/org/apache/spark/storage/StorageStatusTrackingListener.scala create mode 100644 app/views/helpSparkBestProperties.scala.html create mode 100644 app/views/helpSparkMemoryLimit.scala.html delete mode 100644 conf/HeuristicConf.xml create mode 100644 conf/elephant-conf.xml rename conf/{JobType.xml => job-types.xml} (90%) rename test/com/linkedin/drelephant/{analysis => mapreduce}/heuristics/JobQueueLimitHeuristicTest.java (82%) rename test/com/linkedin/drelephant/{analysis => mapreduce}/heuristics/MapperDataSkewHeuristicTest.java (84%) rename test/com/linkedin/drelephant/{analysis => mapreduce}/heuristics/MapperInputSizeHeuristicTest.java (93%) rename test/com/linkedin/drelephant/{analysis => mapreduce}/heuristics/MapperSpeedHeuristicTest.java (82%) rename test/com/linkedin/drelephant/{analysis => mapreduce}/heuristics/MapperSpillHeuristicTest.java (79%) rename test/com/linkedin/drelephant/{analysis => mapreduce}/heuristics/ReducerDataSkewHeuristicTest.java (84%) rename test/com/linkedin/drelephant/{analysis => mapreduce}/heuristics/ReducerTimeHeuristicTest.java (83%) rename test/com/linkedin/drelephant/{analysis => mapreduce}/heuristics/ShuffleSortHeuristicTest.java (86%) create mode 100644 test/com/linkedin/drelephant/spark/MockSparkApplicationData.java create mode 100644 test/com/linkedin/drelephant/spark/heuristics/BestPropertiesConventionHeuristicTest.java create mode 100644 test/com/linkedin/drelephant/spark/heuristics/JobRuntimeHeuristicTest.java create mode 100644 test/com/linkedin/drelephant/spark/heuristics/MemoryLimitHeuristicTest.java create mode 100644 test/com/linkedin/drelephant/util/MemoryFormatUtilsTest.java diff --git a/README.md b/README.md index ab670ed36..01b823921 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ # mapreduce.jobhistory.webapp.addresseat1-hcl0764.grid:19888 - Set up and start mysql locally on your box, the default port is 3306. Create a database called 'drelephant', if it does not exist. - You can now start dr-elephant with the following command (assuming you are running mysql locally on port 3306): - ./bin/dr-elephant -Dhttp.port=8089 -Ddb.default.url="jdbc:mysql://ssubrama-ld1.linkedin.biz:3306/drelephant?characterEncoding=UTF-8" -Devolutionplugin=enabled -DapplyEvolutions.default=true + ./bin/dr-elephant -Dhttp.port=8089 -Ddb.default.url="jdbc:mysql://ssubrama-ld1.linkedin.biz:3306/drelephant?characterEncoding=UTF-8" -Devolutionplugin=enabled -DapplyEvolutions.default=true - Note that you can add other properties with the -D option, as -Dprop.name=value - You can debug dr-elephant using eclispse or idea by adding the argument -Djvm_args="-Xdebug -Xrunjdwp:transport=dt_socket,address=8876,server=y,suspend=y" This will start dr-elephant and wait until you attach eclipse/idea to port 8876. You can then set breakpoints and debug interactively. @@ -60,6 +60,6 @@ When the schema in the model package changes, play will need to be ran to automa * Create a new view for the heuristic for example helpMapperSpill.scala.html * Add the details of the heuristic in the HeuristicConf.xml file. The HeuristicConf.xml file requires the following details for each heuristic: * heuristicname: Name of the heuristic. (e.g. Mapper Spill) - * classname: This should be the fully qualified name of the class (e.g. com.linkedin.drelephant.analysis.heuristics.MapperSpillHeuristic) + * classname: This should be the fully qualified name of the class (e.g. com.linkedin.drelephant.analysis.mapreduce.heuristics.MapperSpillHeuristic) * viewname: This should be the fully qualified name of the view. ( e.g. views.html.helpMapperSpill ) *Run Doctor Elephant, it should now include the new heuristics. diff --git a/app/com/linkedin/drelephant/ElephantAnalyser.java b/app/com/linkedin/drelephant/ElephantAnalyser.java deleted file mode 100644 index 6631b5f93..000000000 --- a/app/com/linkedin/drelephant/ElephantAnalyser.java +++ /dev/null @@ -1,113 +0,0 @@ -package com.linkedin.drelephant; - -import java.util.ArrayList; -import java.util.List; -import java.util.Properties; - -import play.api.Play; - -import org.apache.log4j.Logger; - -import com.linkedin.drelephant.analysis.Heuristic; -import com.linkedin.drelephant.analysis.HeuristicResult; -import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.hadoop.HadoopJobData; -import com.linkedin.drelephant.hadoop.JobType; -import com.linkedin.drelephant.util.HeuristicConf; -import com.linkedin.drelephant.util.HeuristicConfData; -import com.linkedin.drelephant.util.JobTypeConf; - - -public class ElephantAnalyser { - public static final String NO_DATA = "No Data Received"; - public static final String DEFAULT_TYPE = "HadoopJava"; - private static final Logger logger = Logger.getLogger(ElephantAnalyser.class); - private static ElephantAnalyser _analyzer = null; - - private List _heuristicNames = new ArrayList(); - private HeuristicResult _nodata; - private List _heuristics = new ArrayList(); - private List _jobTypeList; - - public static void init() { - if (_analyzer == null) { - _analyzer = new ElephantAnalyser(); - } - } - - public static ElephantAnalyser instance() { - if (_analyzer == null) { - init(); - } - return _analyzer; - } - - private ElephantAnalyser() { - logger.info("Initialize Analyzer... Loading heuristics...."); - _nodata = new HeuristicResult(NO_DATA, Severity.LOW); - loadHeuristics(); - loadJobType(); - } - - public HeuristicResult[] analyse(HadoopJobData data) { - if (data.getMapperData().length == 0 && data.getReducerData().length == 0) { - logger.info("No Data Received for job: " + data.getJobId()); - return new HeuristicResult[] { _nodata }; - } - - List results = new ArrayList(); - for (Heuristic heuristic : _heuristics) { - results.add(heuristic.apply(data)); - } - return results.toArray(new HeuristicResult[results.size()]); - } - - public String getJobType(HadoopJobData data) { - Properties conf = data.getJobConf(); - for (JobType type: _jobTypeList) { - if(type.matchType(conf)) { - return type.getName(); - } - } - // Return default type. This line shouldn't reached as the job type list - // has a default type as its last element. - return DEFAULT_TYPE; - } - - public List getHeuristicNames() { - return this._heuristicNames; - } - - public List getJobTypes() { - return _jobTypeList; - } - - private void loadJobType() { - _jobTypeList = JobTypeConf.instance().getJobTypeList(); - } - - private void loadHeuristics() { - - HeuristicConf conf = HeuristicConf.instance(); - List heuristicsConfData = conf.getHeuristicsConfData(); - - for (HeuristicConfData data : heuristicsConfData) { - try { - Class heuristicClass = Play.current().classloader().loadClass(data.getClassName()); - Heuristic heuristicInstance = (Heuristic) heuristicClass.newInstance(); - _heuristics.add(heuristicInstance); - _heuristicNames.add(heuristicInstance.getHeuristicName()); - logger.info("Load Heuristic : " + data.getClassName()); - } catch (ClassNotFoundException e) { - throw new RuntimeException("Could not find class " + data.getClassName(), e); - } catch (InstantiationException e) { - throw new RuntimeException("Could not instantiate class " + data.getClassName(), e); - } catch (IllegalAccessException e) { - throw new RuntimeException("Could not access constructor for class" + data.getClassName(), e); - } catch (RuntimeException e) { - //More descriptive on other runtime exception such as ClassCastException - throw new RuntimeException(data.getClassName() + " is not a valid Heuristic class.", e); - } - } - } -} diff --git a/app/com/linkedin/drelephant/ElephantContext.java b/app/com/linkedin/drelephant/ElephantContext.java new file mode 100644 index 000000000..68414c2ef --- /dev/null +++ b/app/com/linkedin/drelephant/ElephantContext.java @@ -0,0 +1,257 @@ +package com.linkedin.drelephant; + +import com.linkedin.drelephant.analysis.ApplicationType; +import com.linkedin.drelephant.analysis.ElephantFetcher; +import com.linkedin.drelephant.analysis.Heuristic; +import com.linkedin.drelephant.util.HeuristicConf; +import com.linkedin.drelephant.util.HeuristicConfData; +import com.linkedin.drelephant.util.Utils; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.log4j.Logger; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import play.api.Play; + + +/** + * This is a general singleton instance that provides globally accessible resources. + * + * It is not mandatory that an AnalysisPromise implementation must leverage this instance, but this context provides + * a way for Promises to access shared objects (singletons, thread-local variables and etc.). + */ +public class ElephantContext { + private static final Logger logger = Logger.getLogger(ElephantContext.class); + private static ElephantContext INSTANCE = new ElephantContext(); + private static final String CONFIGURATION_FILE_PATH = "elephant-conf.xml"; + private static final String OPT_METRICS_PUB_CONF = "metrics.publisher-conf"; + + private final List _heuristicNames = new ArrayList(); + private List _heuristicsConfData; + + private final Map> _typeToHeuristics = + new HashMap>(); + private final Map _typeToFetcher = new HashMap(); + + private final DaliMetricsAPI.MetricsPublisher _metricsPublisher; + private final String _hadoopVersion; + + private ElephantContext() { + // private on purpose + _hadoopVersion = Utils.getHadoopVersion(); + + loadConfiguration(); + + // Load metrics publisher + // The getFile() API of Play returns a File object whether or not the actual file exists. + String metricsPublisherConfPath = play.Play.application().configuration().getString(OPT_METRICS_PUB_CONF); + if (metricsPublisherConfPath == null) { + logger.info("Metrics publisher not configured. No metrics will be published"); + _metricsPublisher = null; + } else { + _metricsPublisher = DaliMetricsAPI.HDFSMetricsPublisher.createFromXml(metricsPublisherConfPath); + if (_metricsPublisher == null) { + logger.info("No metrics will be published"); + } + } + } + + private void loadConfiguration() { + Document document = Utils.loadXMLDoc(CONFIGURATION_FILE_PATH); + NodeList nodes = document.getDocumentElement().getChildNodes(); + + Element heuristicsElement = null; + Element fetchersElement = null; + + for (int i = 0; i < nodes.getLength(); i++) { + Node node = nodes.item(i); + if (node.getNodeType() == Node.ELEMENT_NODE) { + Element element = (Element) node; + String sectionName = element.getTagName(); + if (sectionName.equals("heuristics")) { + heuristicsElement = element; + } else if (sectionName.equals("fetchers")) { + fetchersElement = element; + } + } + } + + if (fetchersElement == null) { + throw new RuntimeException( + "No configuration block is presented in configuration file: " + CONFIGURATION_FILE_PATH); + } + if (heuristicsElement == null) { + throw new RuntimeException( + "No configuration block is presented in configuration file: " + CONFIGURATION_FILE_PATH); + } + + // It is important to load fetchers first because we also need to figure out the supported application + // types via fetcher configurations. + loadFetchers(fetchersElement); + loadHeuristics(heuristicsElement); + } + + private void loadFetchers(Element configuration) { + NodeList nodes = configuration.getChildNodes(); + for (int i = 0; i < nodes.getLength(); i++) { + Node node = nodes.item(i); + int n = 0; + if (node.getNodeType() == Node.ELEMENT_NODE) { + n++; + Element fetcherNode = (Element) node; + + Node hadoopVersionNode = fetcherNode.getElementsByTagName("hadoopversion").item(0); + if (hadoopVersionNode == null) { + throw new RuntimeException("No hadoopversion tag presented in fetcher #" + n); + } + + Node applicationTypeNode = fetcherNode.getElementsByTagName("applicationtype").item(0); + if (applicationTypeNode == null) { + throw new RuntimeException("No applicationtype tag presented in fetcher #" + n); + } + + Node classNameNode = fetcherNode.getElementsByTagName("classname").item(0); + if (classNameNode == null) { + throw new RuntimeException("No classname tag presented in fetcher #" + n); + } + + String hadoopVersion = hadoopVersionNode.getTextContent().toLowerCase().trim(); + if (hadoopVersion.equals(_hadoopVersion)) { + String typeName = applicationTypeNode.getTextContent(); + if (!ApplicationType.isSupported(typeName)) { + ApplicationType.addType(typeName); + ApplicationType type = ApplicationType.getType(typeName); + String className = classNameNode.getTextContent(); + try { + Class fetcherClass = Play.current().classloader().loadClass(className); + _typeToFetcher.put(type, (ElephantFetcher) fetcherClass.newInstance()); + } catch (ClassNotFoundException e) { + throw new RuntimeException("Class" + className + " not found for fetcher #" + n, e); + } catch (InstantiationException e) { + throw new RuntimeException("Could not instantiate class " + className, e); + } catch (IllegalAccessException e) { + throw new RuntimeException("Could not access constructor for class " + className, e); + } + } else { + throw new RuntimeException( + "Given a hadoop version and an application type, there could only be one fetcher. Fetcher #" + n + + " is duplicated with the previous fetchers."); + } + } else { + logger.info("Skipping fetcher #" + n + ", because its hadoop version [" + hadoopVersion + + "] does not match our current version [" + _hadoopVersion + "]"); + } + } + } + } + + private void loadHeuristics(Element configuration) { + _heuristicsConfData = new HeuristicConf(configuration).getHeuristicsConfData(); + + for (HeuristicConfData data : _heuristicsConfData) { + try { + Class heuristicClass = Play.current().classloader().loadClass(data.getClassName()); + Heuristic heuristicInstance = (Heuristic) heuristicClass.newInstance(); + + ApplicationType type = data.getAppType(); + List heuristics = _typeToHeuristics.get(type); + if (heuristics == null) { + heuristics = new ArrayList(); + _typeToHeuristics.put(type, heuristics); + } + heuristics.add(heuristicInstance); + + logger.info("Load Heuristic : " + data.getClassName()); + } catch (ClassNotFoundException e) { + throw new RuntimeException("Could not find class " + data.getClassName(), e); + } catch (InstantiationException e) { + throw new RuntimeException("Could not instantiate class " + data.getClassName(), e); + } catch (IllegalAccessException e) { + throw new RuntimeException("Could not access constructor for class" + data.getClassName(), e); + } catch (RuntimeException e) { + //More descriptive on other runtime exception such as ClassCastException + throw new RuntimeException(data.getClassName() + " is not a valid Heuristic class.", e); + } + } + } + + /** + * Get the singleton instance + * + * @return the ElephantContext singleton + */ + public static ElephantContext instance() { + return INSTANCE; + } + + /** Init necessary thread context information. If needed, this method should be called when a thread is initiated. + * + * @param threadId the thread id to init + */ + public void initThread(int threadId) + throws IOException { + for (ElephantFetcher fetcher : _typeToFetcher.values()) { + fetcher.init(threadId); + } + } + + /** + * Given an application type, return the currently bound heuristics + * + * @param type The application type + * @return The corresponding heuristics + */ + public List getHeuristics(ApplicationType type) { + return _typeToHeuristics.get(type); + } + + /** + * Given an application type, return the all the heuristic names. + * + * @return A list of heuristic names + */ + public List getAllHeuristicNames() { + if (_heuristicNames.isEmpty()) { + for (List list : _typeToHeuristics.values()) { + for (Heuristic heuristic : list) { + _heuristicNames.add(heuristic.getHeuristicName()); + } + } + } + + return _heuristicNames; + } + + /** + * Get the heuristic configuration data + * + * @return The configuration data of heuristics + */ + public List getHeuristicsConfData() { + return _heuristicsConfData; + } + + /** + * Given an application type, return the currently ElephantFetcher that binds with the type. + * + * @param type The application type + * @return The corresponding fetcher + */ + public ElephantFetcher getFetcher(ApplicationType type) { + return _typeToFetcher.get(type); + } + + /** + * Get the DALI metrics publisher + * + * @return the DALI Metrics publisher + */ + public DaliMetricsAPI.MetricsPublisher getMetricsPublisher() { + return _metricsPublisher; + } +} diff --git a/app/com/linkedin/drelephant/ElephantFetcher.java b/app/com/linkedin/drelephant/ElephantFetcher.java deleted file mode 100644 index 5cd519036..000000000 --- a/app/com/linkedin/drelephant/ElephantFetcher.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.linkedin.drelephant; - -import com.linkedin.drelephant.hadoop.HadoopJobData; -import org.apache.hadoop.security.authentication.client.AuthenticationException; - -import java.io.IOException; -import java.util.List; - - -/** - * The interface to define common methods for each fetcher - * We have different fetcher implementation for hadoop 1.x and hadoop 2.x - */ -public interface ElephantFetcher { - - /** - * This method is called in Runner's main or each executor thread, before calling other fetcher methods - * in order to initialize thread local variables for the thread. - * @param a thread identifier of Runner's main/executor thread - */ - public void init(int threadId) throws IOException; - - /** - * This method is called periodically in main thread to fetch a list of jobs. - * @return A list of Hadoop jobs (only job id, no data) - */ - public List fetchJobList() throws IOException, AuthenticationException; - - /** - * This method is called in executor thread to fetch job data before calling analyzer method for the job - * @param The job to fetch data for - */ - public void fetchJobData(HadoopJobData jobData) throws IOException, AuthenticationException; - - /** - * This method is called in executor thread to report if this job is successfully fetched and analyzed - * Failed Job will be fetched again by fetchJobList() - * @param The processed job - * @param Whether the job is successfully fetched and analyzed - */ - public void finishJob(HadoopJobData jobData, boolean success); -} diff --git a/app/com/linkedin/drelephant/ElephantRunner.java b/app/com/linkedin/drelephant/ElephantRunner.java index 5f3c88f6c..b7de8ec95 100644 --- a/app/com/linkedin/drelephant/ElephantRunner.java +++ b/app/com/linkedin/drelephant/ElephantRunner.java @@ -1,57 +1,57 @@ package com.linkedin.drelephant; +import com.linkedin.drelephant.analysis.AnalysisPromise; +import com.linkedin.drelephant.analysis.AnalysisProvider; +import com.linkedin.drelephant.analysis.ClassicAnalysisProvider; import com.linkedin.drelephant.analysis.Constants; -import com.linkedin.drelephant.analysis.HeuristicResult; -import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.hadoop.HadoopCounterHolder; -import com.linkedin.drelephant.hadoop.HadoopJobData; -import com.linkedin.drelephant.hadoop.HadoopSecurity; +import com.linkedin.drelephant.analysis.YarnAnalysisProvider; import com.linkedin.drelephant.notifications.EmailThread; +import com.linkedin.drelephant.util.Utils; import java.io.IOException; import java.security.PrivilegedAction; -import java.util.ArrayList; import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.Set; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicBoolean; -import model.JobHeuristicResult; import model.JobResult; -import org.apache.avro.generic.IndexedRecord; +import org.apache.commons.lang.exception.ExceptionUtils; import org.apache.hadoop.mapred.JobConf; import org.apache.log4j.Logger; -import play.Play; public class ElephantRunner implements Runnable { private static final long WAIT_INTERVAL = 60 * 1000; private static final int EXECUTOR_NUM = 3; - private static final String OPT_METRICS_PUB_CONF = "metrics.publisher-conf"; private static final Logger logger = Logger.getLogger(ElephantRunner.class); private AtomicBoolean _running = new AtomicBoolean(true); private long lastRun; private EmailThread _emailer = new EmailThread(); - private final DaliMetricsAPI.MetricsPublisher _metricsPublisher; private HadoopSecurity _hadoopSecurity; - private InfoExtractor _urlRetriever = new InfoExtractor(); private ExecutorService _service; - private BlockingQueue _jobQueue; + private BlockingQueue _jobQueue; + private AnalysisProvider _analysisProvider; - public ElephantRunner() { - // The getFile() API of Play returns a File object whether or not the actual file exists. - String metricsPublisherConfPath = Play.application().configuration().getString(OPT_METRICS_PUB_CONF); - if (metricsPublisherConfPath == null) { - logger.info("Metrics publisher not configured. No metrics will be published"); - _metricsPublisher = null; + private void loadAnalysisProvider() { + JobConf configuration = new JobConf(); + String hadoopVersion = Utils.getHadoopVersion(); configuration.get("mapreduce.framework.name"); + + if (hadoopVersion.equals("yarn")) { + _analysisProvider = new YarnAnalysisProvider(); } else { - _metricsPublisher = DaliMetricsAPI.HDFSMetricsPublisher.createFromXml(metricsPublisherConfPath); - if (_metricsPublisher == null) { - logger.info("No metrics will be published"); - } + _analysisProvider = new ClassicAnalysisProvider(); + } + + if (_analysisProvider == null) { + throw new RuntimeException("FutureProvider cannot be initialized correctly. Please check your configuration."); + } + + try { + _analysisProvider.configure(configuration); + } catch (Exception e) { + logger.error("Error occurred when configuring the analysis provider."); + throw new RuntimeException(e); } } @@ -65,53 +65,12 @@ public void run() { public Void run() { Constants.load(); _emailer.start(); - ElephantFetcher fetcher = null; - - try { - // Tell which hadoop version from hadoop configuration, - // and start fetcher accordingly - JobConf hadoopConf = new JobConf(); - String framework = hadoopConf.get("mapreduce.framework.name"); - - if (framework != null) { - if (framework.equals("yarn")) { - fetcher = new ElephantFetcherYarn(hadoopConf); - } else if (framework.equals("classic")) { - fetcher = new ElephantFetcherClassic(hadoopConf); - } else { - logger.error("mapreduce.framework.name must be either 'classic' or 'yarn'. Current: " + framework); - return null; - } - } else { - if (hadoopConf.get("mapred.job.tracker.http.address") != null) { - fetcher = new ElephantFetcherClassic(hadoopConf); - } else { - logger.error("Either mapreduce.framework.name or mapred.job.tracker.http.address must be set."); - logger.error("Plseae check your configuration."); - return null; - } - } - - logger.info("Initializing fetcher in main thread 0"); - fetcher.init(0); - - } catch (IOException e) { - logger.error("Error initializing dr elephant fetcher in main thread", e); - return null; - } + loadAnalysisProvider(); _service = Executors.newFixedThreadPool(EXECUTOR_NUM); - _jobQueue = new LinkedBlockingQueue(); - + _jobQueue = new LinkedBlockingQueue(); for (int i = 0; i < EXECUTOR_NUM; i++) { - _service.submit(new ExecutorThread(i + 1, _jobQueue, fetcher)); - } - - try { - ElephantAnalyser.init(); - } catch (Exception e) { - logger.error("Error loading pluggable heuristics. ", e); - return null; + _service.submit(new ExecutorThread(i + 1, _jobQueue)); } while (_running.get() && !Thread.currentThread().isInterrupted()) { @@ -128,9 +87,9 @@ public Void run() { continue; } - List successJobs; + List todos; try { - successJobs = fetcher.fetchJobList(); + todos = _analysisProvider.fetchPromises(); } catch (Exception e) { logger.error("Error fetching job list. Try again later...", e); //Wait for a while before retry @@ -138,7 +97,7 @@ public Void run() { continue; } - _jobQueue.addAll(successJobs); + _jobQueue.addAll(todos); logger.info("Job queue size is " + _jobQueue.size()); //Wait for a while before next fetch @@ -149,6 +108,8 @@ public Void run() { } }); } catch (IOException e) { + logger.error(e.getMessage()); + logger.error(ExceptionUtils.getStackTrace(e)); logger.error("Error on Hadoop Security setup. Failed to login with Kerberos"); } } @@ -156,134 +117,49 @@ public Void run() { private class ExecutorThread implements Runnable { private int _threadId; - private BlockingQueue _jobQueue; - private ElephantFetcher _fetcher; + private BlockingQueue _jobQueue; - ExecutorThread(int threadNum, BlockingQueue jobQueue, ElephantFetcher fetcher) { + ExecutorThread(int threadNum, BlockingQueue jobQueue) { this._threadId = threadNum; this._jobQueue = jobQueue; - this._fetcher = fetcher; } @Override public void run() { try { - logger.info("Initializing fetcher in executor " + _threadId); - this._fetcher.init(_threadId); + ElephantContext.instance().initThread(_threadId); } catch (IOException e) { - logger.error("Error initialize fetcher in executor " + _threadId, e); + logger.error("Error initialize context configuration for the thread " + _threadId, e); + logger.error(e.getMessage()); + logger.error(ExceptionUtils.getStackTrace(e)); } + while (_running.get() && !Thread.currentThread().isInterrupted()) { - HadoopJobData jobData = null; + AnalysisPromise promise = null; try { - jobData = _jobQueue.take(); - _fetcher.fetchJobData(jobData); - analyzeJob(jobData, _threadId); - _fetcher.finishJob(jobData, true); + promise = _jobQueue.take(); + JobResult result = promise.getAnalysis(); + result.save(); + // TODO: how to test email sending? + _emailer.enqueue(result); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } catch (Exception e) { - logger.error("Error analyzing " + jobData.getJobId(), e); - _fetcher.finishJob(jobData, false); + logger.error(e.getMessage()); + logger.error(ExceptionUtils.getStackTrace(e)); + + if (promise != null && promise.retry()) { + _analysisProvider.addIntoRetries(promise); + } else { + logger.error( + "Drop analysis job. Reason: reached the max retries for application id = [" + promise.getAppId() + "]."); + } } } logger.info("Executor Thread" + _threadId + " is terminated."); } } - private void analyzeJob(HadoopJobData jobData, int execThreadNum) { - ElephantAnalyser analyser = ElephantAnalyser.instance(); - logger.info("Analyze " + jobData.getJobId() + " by executor " + execThreadNum); - - HeuristicResult[] analysisResults = analyser.analyse(jobData); - String jobType = analyser.getJobType(jobData); - - // Save to DB - JobResult result = new JobResult(); - result.jobId = jobData.getJobId(); - result.url = jobData.getUrl(); - result.username = jobData.getUsername(); - result.startTime = jobData.getStartTime(); - result.analysisTime = System.currentTimeMillis(); - result.jobName = jobData.getJobName(); - result.jobType = jobType; - - // Truncate long names - if (result.jobName.length() > 100) { - result.jobName = result.jobName.substring(0, 97) + "..."; - } - result.heuristicResults = new ArrayList(); - - Severity worstSeverity = Severity.NONE; - - for (HeuristicResult heuristicResult : analysisResults) { - JobHeuristicResult detail = new JobHeuristicResult(); - detail.analysisName = heuristicResult.getAnalysis(); - detail.data = heuristicResult.getDetailsCSV(); - detail.dataColumns = heuristicResult.getDetailsColumns(); - detail.severity = heuristicResult.getSeverity(); - if (detail.dataColumns < 1) { - detail.dataColumns = 1; - } - result.heuristicResults.add(detail); - worstSeverity = Severity.max(worstSeverity, detail.severity); - } - - result.severity = worstSeverity; - _urlRetriever.retrieveURLs(result, jobData); - - if (_metricsPublisher != null) { - publishMetrics(jobData); - } - - result.save(); - - _emailer.enqueue(result); - } - - private void publishMetrics(HadoopJobData jobData) { - Properties jobConf = jobData.getJobConf(); - // We may have something to publish, but we don't know until we have iterated through the counters that we have. - // We assume that we need to publish something until we find out we don't. - DaliMetricsAPI.JobProperties jobProperties = new DaliMetricsAPI.JobProperties(jobConf); - if (jobProperties.getCountersToPublish().isEmpty()) { - // Nothing to do - return; - } - DaliMetricsAPI.EventContext eventContext = new DaliMetricsAPI.EventContext(jobData.getJobName(), - jobData.getJobId(), - jobData.getStartTime(), - jobData.getFinishTime()); - DaliMetricsAPI.HadoopCounters metricsEvent = new DaliMetricsAPI.HadoopCounters(eventContext, jobProperties); - - HadoopCounterHolder counterHolder = jobData.getCounters(); - Set groupNames = counterHolder.getGroupNames(); - for (String group : groupNames) { - Map counters = counterHolder.getAllCountersInGroup(group); - for (Map.Entry entry : counters.entrySet()) { - String counterName = entry.getKey(); - Long value = entry.getValue(); - metricsEvent.addCounter(group, counterName, value); - } - } - if (metricsEvent.getNumCounters() == 0) { - // The counters that were configured were not collected in HadoopCounterHolder. - return; - } - IndexedRecord event = metricsEvent.build(); - try { - _metricsPublisher.publish(event); - } catch (IOException e) { - // The lower level should have logged a message. - // A checked exception from the publish() call should mean that the event was not formed correctly for some reason. - // Aside from a code bug, the most common reason for this will probably be that some mandatory fields in the - // event were missing. - // Could also mean that some derived values (e.g. hostname to URL, execId translation to an integer, etc.) - // may have failed. There is little we can do at this point to fix those, so ignore the exception. - logger.warn("Publish failed:" + e); - } - } - private void waitInterval() { // Wait for long enough long nextRun = lastRun + WAIT_INTERVAL; @@ -305,4 +181,4 @@ public void kill() { _emailer.kill(); _service.shutdownNow(); } -} \ No newline at end of file +} diff --git a/app/com/linkedin/drelephant/hadoop/HadoopSecurity.java b/app/com/linkedin/drelephant/HadoopSecurity.java similarity index 86% rename from app/com/linkedin/drelephant/hadoop/HadoopSecurity.java rename to app/com/linkedin/drelephant/HadoopSecurity.java index 71d28e544..4cd8f3407 100644 --- a/app/com/linkedin/drelephant/hadoop/HadoopSecurity.java +++ b/app/com/linkedin/drelephant/HadoopSecurity.java @@ -1,13 +1,14 @@ -package com.linkedin.drelephant.hadoop; +package com.linkedin.drelephant; import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.mapred.JobConf; import org.apache.hadoop.security.UserGroupInformation; import org.apache.log4j.Logger; -import play.Play; - +import java.io.File; import java.io.IOException; import java.security.PrivilegedAction; +import play.Play; public class HadoopSecurity { @@ -20,11 +21,14 @@ public class HadoopSecurity { private boolean _securityEnabled = false; public HadoopSecurity() throws IOException { - Configuration conf = new Configuration(); + Configuration conf = new JobConf(); UserGroupInformation.setConfiguration(conf); _securityEnabled = UserGroupInformation.isSecurityEnabled(); if (_securityEnabled) { _keytabLocation = Play.application().configuration().getString("keytab.location"); + if (!new File(_keytabLocation).exists()) { + logger.error("The keytab file at location [" + _keytabLocation + "] does not exist."); + } _keytabUser = Play.application().configuration().getString("keytab.user"); checkLogin(); } diff --git a/app/com/linkedin/drelephant/InfoExtractor.java b/app/com/linkedin/drelephant/InfoExtractor.java deleted file mode 100644 index a7f02f5f2..000000000 --- a/app/com/linkedin/drelephant/InfoExtractor.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.linkedin.drelephant; - -import java.util.Properties; - -import org.apache.log4j.Logger; - -import model.JobResult; - -import com.linkedin.drelephant.hadoop.HadoopJobData; - - -/** - * InfoExtractor is responsible for retrieving information and context about a - * job from the job's configuration which will be leveraged by the UI - */ -public class InfoExtractor { - private static final Logger logger = Logger.getLogger(InfoExtractor.class); - private static final String AZK_URL_PREFIX = "azkaban.link"; - private static final String AZK_WORKFLOW_URL = "azkaban.link.workflow.url"; - private static final String AZK_JOB_URL = "azkaban.link.job.url"; - private static final String AZK_JOB_EXECUTION_URL = "azkaban.link.jobexec.url"; - private static final String AZK_EXECUTION_URL = "azkaban.link.execution.url"; - private static final String AZK_ATTEMPT_URL = "azkaban.link.attempt.url"; - private static final String AZK_URN_KEY = "azk.urn"; - - void retrieveURLs(JobResult result, HadoopJobData jobData) { - Properties jobConf = jobData.getJobConf(); - String jobId = jobData.getJobId(); - result.jobExecUrl = truncate(jobConf.getProperty(AZK_ATTEMPT_URL), jobId); - // For jobs launched by Azkaban, we consider different attempts to be - // different jobs - result.jobUrl = truncate(jobConf.getProperty(AZK_JOB_URL), jobId); - result.flowExecUrl = truncate(jobConf.getProperty(AZK_EXECUTION_URL), jobId); - result.flowUrl = truncate(jobConf.getProperty(AZK_WORKFLOW_URL), jobId); - } - - String truncate(String value, String jobId) { - if (value != null && value.length() > JobResult.URL_LEN_LIMIT) { - logger.info("Truncate long URL in job result for job: " + jobId + ". Original Url: " + value); - value = value.substring(0, JobResult.URL_LEN_LIMIT); - } - return value; - } -} diff --git a/app/com/linkedin/drelephant/analysis/AnalysisPromise.java b/app/com/linkedin/drelephant/analysis/AnalysisPromise.java new file mode 100644 index 000000000..e384489bf --- /dev/null +++ b/app/com/linkedin/drelephant/analysis/AnalysisPromise.java @@ -0,0 +1,160 @@ +package com.linkedin.drelephant.analysis; + +import com.linkedin.drelephant.ElephantContext; +import com.linkedin.drelephant.util.InfoExtractor; +import com.linkedin.drelephant.util.JobTypeConf; +import java.util.ArrayList; +import java.util.List; +import model.JobHeuristicResult; +import model.JobResult; + + +/** + * This class wraps some basic meta data of a completed application run (notice that the information is generally the + * same regardless of hadoop versions and application types), and then promises to return the analyzed result + * later. + */ +public class AnalysisPromise { + private static final int _RETRY_LIMIT = 3; + + private int _retries = 0; + private ApplicationType _type; + private String _appId; + private String _jobId; + private String _name; + private String _user; + private String _trackingUrl; + private long _startTime; + private long _finishTime; + + public ApplicationType getAppType() { + return _type; + } + + public void setAppType(ApplicationType type) { + _type = type; + } + + public void setAppId(String appId) { + _appId = appId; + } + + public void setJobId(String jobId) { + _jobId = jobId; + } + + public void setName(String name) { + _name = name; + } + + public void setUser(String user) { + _user = user; + } + + public void setStartTime(long startTime) { + _startTime = startTime; + } + + public void setFinishTime(long finishTime) { + _finishTime = finishTime; + } + + public String getAppId() { + return _appId; + } + + public String getJobId() { + return _jobId; + } + + public String getName() { + return _name; + } + + public String getUser() { + return _user; + } + + public long getStartTime() { + return _startTime; + } + + public long getFinishTime() { + return _finishTime; + } + + public String getTrackingUrl() { + return _trackingUrl; + } + + public void setTrackingUrl(String trackingUrl) { + _trackingUrl = trackingUrl; + } + + /** + * Returns the analysed JobResult that could be directly serialized into DB. + * + * @throws Exception if the analysis process encountered a problem. + * @return the analysed JobResult + */ + public JobResult getAnalysis() throws Exception { + ElephantFetcher fetcher = ElephantContext.instance().getFetcher(getAppType()); + + HadoopApplicationData data = fetcher.fetchData(getAppId()); + + List analysisResults = new ArrayList(); + List heuristics = ElephantContext.instance().getHeuristics(getAppType()); + for (Heuristic heuristic : heuristics) { + analysisResults.add(heuristic.apply(data)); + } + + String jobType = JobTypeConf.instance().getJobType(data).getName(); + + JobResult result = new JobResult(); + // Need to get it from ApplicationData because different applications might use different unique ids. (e.g. + // application_id v.s job_id) + result.jobId = data.getUid(); + result.url = getTrackingUrl(); + result.username = getUser(); + result.startTime = getStartTime(); + result.analysisTime = System.currentTimeMillis(); + result.jobName = getName(); + result.jobType = jobType; + + // Truncate long names + if (result.jobName.length() > 100) { + result.jobName = result.jobName.substring(0, 97) + "..."; + } + result.heuristicResults = new ArrayList(); + + Severity worstSeverity = Severity.NONE; + + for (HeuristicResult heuristicResult : analysisResults) { + JobHeuristicResult detail = new JobHeuristicResult(); + detail.analysisName = heuristicResult.getAnalysis(); + detail.data = heuristicResult.getDetailsCSV(); + detail.dataColumns = heuristicResult.getDetailsColumns(); + detail.severity = heuristicResult.getSeverity(); + if (detail.dataColumns < 1) { + detail.dataColumns = 1; + } + result.heuristicResults.add(detail); + worstSeverity = Severity.max(worstSeverity, detail.severity); + } + + result.severity = worstSeverity; + // Retrieve Azkaban execution, flow and jobs URLs from jobData and store them into result. + InfoExtractor.retrieveURLs(result, data); + + return result; + } + + /** + * Indicate this promise should retry itself again. + * + * @return true if should retry, else false + */ + public boolean retry() { + return (_retries++) < _RETRY_LIMIT; + } +} diff --git a/app/com/linkedin/drelephant/analysis/AnalysisProvider.java b/app/com/linkedin/drelephant/analysis/AnalysisProvider.java new file mode 100644 index 000000000..4da1c98b5 --- /dev/null +++ b/app/com/linkedin/drelephant/analysis/AnalysisProvider.java @@ -0,0 +1,40 @@ +package com.linkedin.drelephant.analysis; + +import java.io.IOException; +import java.util.List; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.security.authentication.client.AuthenticationException; + + +/** + * Provides analysis promises that will yield to analysis results finally. + * + */ +public interface AnalysisProvider { + /** + * Configures the provider instance + * + * @param configuration The Hadoop configuration object + * @throws Exception + */ + public void configure(Configuration configuration) + throws Exception; + + /** + * Provides a list of analysis promises that should be calculated + * + * @return a list of analysis promises + * @throws IOException + * @throws AuthenticationException + */ + public List fetchPromises() + throws IOException, AuthenticationException; + + /** + * Add an analysis promise into retry list. Those promises will be provided again via #fetchPromises under + * the analysis provider's decision. + * + * @param promise The promise to add + */ + public void addIntoRetries(AnalysisPromise promise); +} diff --git a/app/com/linkedin/drelephant/analysis/ApplicationType.java b/app/com/linkedin/drelephant/analysis/ApplicationType.java new file mode 100644 index 000000000..fdb5fbef3 --- /dev/null +++ b/app/com/linkedin/drelephant/analysis/ApplicationType.java @@ -0,0 +1,84 @@ +package com.linkedin.drelephant.analysis; + +import java.util.HashMap; +import java.util.Map; + + +/** + * Manages and represents supported application types. + * + * @author yizhou + */ +public class ApplicationType { + private final String _name; + + private ApplicationType(String name) { + _name = name.toUpperCase(); + } + + @Override + public int hashCode() { + return _name.hashCode(); + } + + @Override + public boolean equals(Object other) { + if (other instanceof ApplicationType) { + return ((ApplicationType) other).getName().equals(getName()); + } + return false; + } + + /** + * Get the name + * + * @return the name of the application type + */ + public String getName() { + return _name; + } + + private static final Map VALUES = new HashMap(); + + /** + * Register an application type into the supported map. + * + * @param typeName The type name + */ + public static void addType(String typeName) { + if (typeName == null) { + return; + } + + ApplicationType type = new ApplicationType(typeName); + VALUES.put(type.getName(), type); + } + + /** + * Get the corresponding application type instance via type name + * @param typeName The type name + * @return the application type, null if not found + */ + public static ApplicationType getType(String typeName) { + if (typeName == null) { + return null; + } + return VALUES.get(typeName.toUpperCase()); + } + + /** + * Indicate if a particular application type is supported. + * + * @param typeName The type name + * @return true if supported else false + */ + public static boolean isSupported(String typeName) { + ApplicationType type = getType(typeName); + + if (type == null) { + return false; + } + + return true; + } +} diff --git a/app/com/linkedin/drelephant/analysis/ClassicAnalysisProvider.java b/app/com/linkedin/drelephant/analysis/ClassicAnalysisProvider.java new file mode 100644 index 000000000..123ec4d9b --- /dev/null +++ b/app/com/linkedin/drelephant/analysis/ClassicAnalysisProvider.java @@ -0,0 +1,137 @@ +package com.linkedin.drelephant.analysis; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.ConcurrentLinkedQueue; +import model.JobResult; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.mapred.JobClient; +import org.apache.hadoop.mapred.JobConf; +import org.apache.hadoop.mapred.JobStatus; +import org.apache.hadoop.mapred.RunningJob; +import org.apache.hadoop.security.authentication.client.AuthenticationException; +import org.apache.log4j.Logger; + + +/** + * This class provides a future list to be fetched/analyzed in Hadoop 1 environment. + * + */ +public class ClassicAnalysisProvider implements AnalysisProvider { + private static final Logger logger = Logger.getLogger(ClassicAnalysisProvider.class); + private JobClient _jobClient; + + private boolean _firstRun = true; + private Set _previousJobs; + + private final Queue _retryQueue = new ConcurrentLinkedQueue(); + private final String ONLY_SUPPORTED_TYPE_NAME = "MAPREDUCE"; + private ApplicationType _onlySupportedType; + + @Override + public void configure(Configuration configuration) + throws Exception { + _jobClient = new JobClient(new JobConf(configuration)); + + _onlySupportedType = ApplicationType.getType(ONLY_SUPPORTED_TYPE_NAME); + if (_onlySupportedType == null) { + throw new RuntimeException("Cannot configure the analysis provider, " + ONLY_SUPPORTED_TYPE_NAME + + " application type is not supported."); + } + } + + @Override + public List fetchPromises() + throws IOException, AuthenticationException { + JobStatus[] result = _jobClient.getAllJobs(); + + if (result == null) { + throw new IOException("Error fetching joblist from jobtracker."); + } + + // Get all completed jobs from jobtracker + Set successJobs = filterSuccessfulJobs(result); + + // Filter out newly completed jobs + Set todoJobs = filterPreviousJobs(successJobs); + + // Add newly completed jobs to return list + List jobList = new ArrayList(); + for (String jobId : todoJobs) { + RunningJob job = _jobClient.getJob(jobId); + JobStatus jobStatus = job.getJobStatus(); + + AnalysisPromise promise = new AnalysisPromise(); + + promise.setAppId(jobId); + promise.setAppType(_onlySupportedType); + promise.setJobId(jobId); + promise.setUser(jobStatus.getUsername()); + promise.setName(job.getJobName()); + promise.setTrackingUrl(job.getTrackingURL()); + + long startTime = jobStatus.getStartTime(); + promise.setStartTime(startTime); + // ALERT: + // In Hadoop-1 the getFinishTime() call is not there. Unfortunately, calling this method does not + // result in a compile error, neither a MethodNotFound exception at run time. The program just hangs. + // Since we don't have metrics (the only consumer of finish time as of now) in Hadoop-1, set the finish time + // to be the same as start time. + promise.setFinishTime(startTime); + + jobList.add(promise); + } + // Append promises from the retry queue at the end of the list + while (!_retryQueue.isEmpty()) { + jobList.add(_retryQueue.poll()); + } + + return jobList; + } + + @Override + public void addIntoRetries(AnalysisPromise promise) { + _retryQueue.add(promise); + } + + // Return a set of succeeded job ids + private Set filterSuccessfulJobs(JobStatus[] jobs) { + HashSet successJobs = new HashSet(); + for (JobStatus job : jobs) { + if (job.getRunState() == JobStatus.SUCCEEDED && job.isJobComplete()) { + successJobs.add(job.getJobID().toString()); + } + } + return successJobs; + } + + private Set filterPreviousJobs(Set jobs) { + // On first run, check each job against DB + if (_firstRun) { + logger.info("Checking database ...... "); + Set newJobs = new HashSet(); + for (String jobId : jobs) { + JobResult prevResult = JobResult.find.byId(jobId); + if (prevResult == null) { + // Job not found, add to new jobs list + newJobs.add(jobId); + } + } + _previousJobs = jobs; + _firstRun = false; + logger.info("Database check completed"); + return newJobs; + } else { + @SuppressWarnings("unchecked") + Set tempPrevJobs = (HashSet) ((HashSet) jobs).clone(); + // Leave only the newly completed jobs + jobs.removeAll(_previousJobs); + _previousJobs = tempPrevJobs; + return jobs; + } + } +} diff --git a/app/com/linkedin/drelephant/analysis/ElephantFetcher.java b/app/com/linkedin/drelephant/analysis/ElephantFetcher.java new file mode 100644 index 000000000..3b402db4d --- /dev/null +++ b/app/com/linkedin/drelephant/analysis/ElephantFetcher.java @@ -0,0 +1,28 @@ +package com.linkedin.drelephant.analysis; + +import java.io.IOException; + + +/** + * The interface to define common methods for each fetcher. + * + * There would be a different fetcher implementation given a different Hadoop version and a different application type. + */ +public interface ElephantFetcher { + + /** + * This method is called in Runner's main or each executor thread, before calling other fetcher methods + * in order to initialize thread local variables for the thread. + * @param threadId a thread identifier of Runner's main/executor thread + */ + public void init(int threadId) throws IOException; + + /** + * Given an application id, fetches the data object + * + * @param appId the application id + * @return the fetched data + * @throws Exception + */ + public T fetchData(String appId) throws Exception; +} diff --git a/app/com/linkedin/drelephant/analysis/HadoopApplicationData.java b/app/com/linkedin/drelephant/analysis/HadoopApplicationData.java new file mode 100644 index 000000000..2765fea95 --- /dev/null +++ b/app/com/linkedin/drelephant/analysis/HadoopApplicationData.java @@ -0,0 +1,25 @@ +package com.linkedin.drelephant.analysis; + +import java.util.Properties; + + +/** + * This interface indicates that a class is holding the information of a Hadoop application + */ +public interface HadoopApplicationData { + + /** + * Returns the unique id to identify an application run. This id could be application id or job id according to + * different applications, as long as this is unique. + * + * @return the uid + */ + public String getUid(); + + /** + * Returns the configuration of an application. + * + * @return A java Properties that contains the application configuration + */ + public Properties getConf(); +} diff --git a/app/com/linkedin/drelephant/analysis/Heuristic.java b/app/com/linkedin/drelephant/analysis/Heuristic.java index fd7f89dd3..fae7b2504 100644 --- a/app/com/linkedin/drelephant/analysis/Heuristic.java +++ b/app/com/linkedin/drelephant/analysis/Heuristic.java @@ -1,10 +1,23 @@ package com.linkedin.drelephant.analysis; -import com.linkedin.drelephant.hadoop.HadoopJobData; - - -public interface Heuristic { - public HeuristicResult apply(HadoopJobData data); +/** + * This interface defines the Heuristic rule interface. + * + * @param An implementation that extends from HadoopApplicationData + */ +public interface Heuristic { + /** + * Given an application data instance, returns the analyzed heuristic result. + * + * @param data The data to analyze + * @return The heuristic result + */ + public HeuristicResult apply(T data); + /** + * Get the heuristic name + * + * @return the name + */ public String getHeuristicName(); } diff --git a/app/com/linkedin/drelephant/analysis/HeuristicResult.java b/app/com/linkedin/drelephant/analysis/HeuristicResult.java index 20729f32d..d7fda4524 100644 --- a/app/com/linkedin/drelephant/analysis/HeuristicResult.java +++ b/app/com/linkedin/drelephant/analysis/HeuristicResult.java @@ -4,9 +4,12 @@ import java.util.ArrayList; import java.util.List; +import org.apache.commons.lang.StringUtils; public class HeuristicResult { + public static final HeuristicResult NO_DATA = new HeuristicResult("No Data Received", Severity.LOW); + private String _analysis; private Severity _severity; private List _details; @@ -68,4 +71,9 @@ public void addDetail(String... parts) { public void setSeverity(Severity severity) { this._severity = severity; } + + public String toString() { + return "{analysis: " + _analysis + ", severity: " + _severity + ", details: [" + StringUtils.join(_details, " ") + + "]}"; + } } diff --git a/app/com/linkedin/drelephant/analysis/JobType.java b/app/com/linkedin/drelephant/analysis/JobType.java new file mode 100644 index 000000000..47a8ac3f5 --- /dev/null +++ b/app/com/linkedin/drelephant/analysis/JobType.java @@ -0,0 +1,43 @@ +package com.linkedin.drelephant.analysis; + +import java.util.Properties; +import java.util.regex.Pattern; + + +public class JobType { + public static final JobType NO_DATA = new JobType("No data received"); + + private final String _type; + private final String _confName; + private final Pattern _confPattern; + + public JobType(String type) { + _type = type; + _confName = null; + _confPattern = null; + } + + public JobType(String type, String confName, String confPattern) { + _type = type; + _confName = confName; + _confPattern = Pattern.compile(confPattern); + } + + public boolean matchType(Properties jobProp) { + // Always return false if confName/confPattern is undefined, + // which means we cannot tell if the properties are matching the pattern + if (_confName == null || _confPattern == null) { + return false; + } + + return jobProp.containsKey(_confName) && _confPattern.matcher((String) jobProp.get(_confName)).matches(); + } + + public String getName() { + return _type; + } + + public String toString() { + return _type; + } +} diff --git a/app/com/linkedin/drelephant/analysis/Severity.java b/app/com/linkedin/drelephant/analysis/Severity.java index 5e46296b1..c4a62404f 100644 --- a/app/com/linkedin/drelephant/analysis/Severity.java +++ b/app/com/linkedin/drelephant/analysis/Severity.java @@ -72,33 +72,33 @@ public static Severity min(Severity a, Severity b) { return b; } - public static Severity getSeverityAscending(long value, long low, long moderate, long severe, long critical) { - if (value >= critical) { + public static Severity getSeverityAscending(Number value, Number low, Number moderate, Number severe, Number critical) { + if (value.doubleValue() >= critical.doubleValue()) { return CRITICAL; } - if (value >= severe) { + if (value.doubleValue() >= severe.doubleValue()) { return SEVERE; } - if (value >= moderate) { + if (value.doubleValue() >= moderate.doubleValue()) { return MODERATE; } - if (value >= low) { + if (value.doubleValue() >= low.doubleValue()) { return LOW; } return NONE; } - public static Severity getSeverityDescending(long value, long low, long moderate, long severe, long critical) { - if (value <= critical) { + public static Severity getSeverityDescending(Number value, Number low, Number moderate, Number severe, Number critical) { + if (value.doubleValue() <= critical.doubleValue()) { return CRITICAL; } - if (value <= severe) { + if (value.doubleValue() <= severe.doubleValue()) { return SEVERE; } - if (value <= moderate) { + if (value.doubleValue() <= moderate.doubleValue()) { return MODERATE; } - if (value <= low) { + if (value.doubleValue() <= low.doubleValue()) { return LOW; } return NONE; diff --git a/app/com/linkedin/drelephant/analysis/YarnAnalysisProvider.java b/app/com/linkedin/drelephant/analysis/YarnAnalysisProvider.java new file mode 100644 index 000000000..3655824c0 --- /dev/null +++ b/app/com/linkedin/drelephant/analysis/YarnAnalysisProvider.java @@ -0,0 +1,126 @@ +package com.linkedin.drelephant.analysis; + +import com.linkedin.drelephant.math.Statistics; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.Queue; +import java.util.Random; +import java.util.concurrent.ConcurrentLinkedQueue; +import model.JobResult; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.security.authentication.client.AuthenticatedURL; +import org.apache.hadoop.security.authentication.client.AuthenticationException; +import org.apache.log4j.Logger; +import org.codehaus.jackson.JsonNode; +import org.codehaus.jackson.map.ObjectMapper; + + +/** + * This class provides a list of analysis promises to be generated under Hadoop YARN environment + * + */ +public class YarnAnalysisProvider implements AnalysisProvider { + private static final Logger logger = Logger.getLogger(YarnAnalysisProvider.class); + // We provide one minute job fetch delay due to the job sending lag from AM/NM to JobHistoryServer HDFS + private static final long FETCH_DELAY = 60000; + private static final long TOKEN_UPDATE_INTERVAL = + Statistics.MINUTE_IN_MS * 30 + new Random().nextLong() % (3 * Statistics.MINUTE_IN_MS); + + private String _resourceManagerAddress; + private long _lastTime = 0; + private long _currentTime = 0; + private long _tokenUpdatedTime = 0; + private AuthenticatedURL.Token _token; + private AuthenticatedURL _authenticatedURL; + private final ObjectMapper _objectMapper = new ObjectMapper(); + + private final Queue _retryQueue = new ConcurrentLinkedQueue(); + + @Override + public void configure(Configuration configuration) + throws Exception { + _resourceManagerAddress = configuration.get("yarn.resourcemanager.webapp.address"); + } + + @Override + public List fetchPromises() + throws IOException, AuthenticationException { + List appList = new ArrayList(); + + // There is a lag of job data from AM/NM to JobHistoryServer HDFS, we shouldn't use the current + // time, since there might be new jobs arriving after we fetch jobs. + // We provide one minute delay to address this lag. + _currentTime = System.currentTimeMillis() - FETCH_DELAY; + updateAuthToken(); + + URL appsURL = new URL(new URL("http://" + _resourceManagerAddress), String + .format("/ws/v1/cluster/apps?finalStatus=SUCCEEDED&finishedTimeBegin=%s&finishedTimeEnd=%s", + String.valueOf(_lastTime), String.valueOf(_currentTime))); + + logger.info("Fetching recent finished application runs via URL: " + appsURL); + + JsonNode rootNode = readJsonNode(appsURL); + JsonNode apps = rootNode.path("apps").path("app"); + + for (JsonNode app : apps) { + String id = app.get("id").getValueAsText(); + String jobId = id.replaceFirst("application", "job"); + + if (JobResult.find.byId(jobId) == null) { + String user = app.get("user").getValueAsText(); + String name = app.get("name").getValueAsText(); + String trackingUrl = app.get("trackingUrl").getValueAsText(); + long startTime = app.get("startedTime").getLongValue(); + long finishTime = app.get("finishedTime").getLongValue(); + + ApplicationType type = ApplicationType.getType(app.get("applicationType").getValueAsText()); + + // If the application type is supported + if (type != null) { + AnalysisPromise promise = new AnalysisPromise(); + promise.setAppId(id); + promise.setAppType(type); + promise.setJobId(jobId); + promise.setUser(user); + promise.setName(name); + promise.setTrackingUrl(trackingUrl); + promise.setStartTime(startTime); + promise.setFinishTime(finishTime); + + appList.add(promise); + } + } + } + + // Append promises from the retry queue at the end of the list + while (!_retryQueue.isEmpty()) { + appList.add(_retryQueue.poll()); + } + + _lastTime = _currentTime; + return appList; + } + + @Override + public void addIntoRetries(AnalysisPromise promise) { + _retryQueue.add(promise); + } + + private void updateAuthToken() { + if (_currentTime - _tokenUpdatedTime > TOKEN_UPDATE_INTERVAL) { + logger.info("FutureProvider updating its Authenticate Token...."); + _token = new AuthenticatedURL.Token(); + _authenticatedURL = new AuthenticatedURL(); + _tokenUpdatedTime = _currentTime; + } + } + + private JsonNode readJsonNode(URL url) + throws IOException, AuthenticationException { + HttpURLConnection conn = _authenticatedURL.openConnection(url, _token); + return _objectMapper.readTree(conn.getInputStream()); + } +} diff --git a/app/com/linkedin/drelephant/hadoop/JobType.java b/app/com/linkedin/drelephant/hadoop/JobType.java deleted file mode 100644 index b78240306..000000000 --- a/app/com/linkedin/drelephant/hadoop/JobType.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.linkedin.drelephant.hadoop; - -import java.util.Properties; -import java.util.regex.Pattern; - -public class JobType { - - private String _type; - private String _confName; - private Pattern _confPattern; - - public JobType(String type, String confName, String confPattern) { - this._type = type; - this._confName = confName; - this._confPattern = Pattern.compile(confPattern); - } - - public boolean matchType(Properties jobProp) { - return jobProp.containsKey(_confName) && - _confPattern.matcher((String)jobProp.get(_confName)).matches(); - } - - public String getName() { - return _type; - } - - public String toString() { - return _type; - } -} diff --git a/app/com/linkedin/drelephant/hadoop/HadoopCounterHolder.java b/app/com/linkedin/drelephant/mapreduce/HadoopCounterHolder.java similarity index 98% rename from app/com/linkedin/drelephant/hadoop/HadoopCounterHolder.java rename to app/com/linkedin/drelephant/mapreduce/HadoopCounterHolder.java index cb97a1fdb..2132cf216 100644 --- a/app/com/linkedin/drelephant/hadoop/HadoopCounterHolder.java +++ b/app/com/linkedin/drelephant/mapreduce/HadoopCounterHolder.java @@ -1,6 +1,5 @@ -package com.linkedin.drelephant.hadoop; +package com.linkedin.drelephant.mapreduce; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; diff --git a/app/com/linkedin/drelephant/hadoop/HadoopTaskData.java b/app/com/linkedin/drelephant/mapreduce/HadoopTaskData.java similarity index 95% rename from app/com/linkedin/drelephant/hadoop/HadoopTaskData.java rename to app/com/linkedin/drelephant/mapreduce/HadoopTaskData.java index 8d7f8189e..befbf93e2 100644 --- a/app/com/linkedin/drelephant/hadoop/HadoopTaskData.java +++ b/app/com/linkedin/drelephant/mapreduce/HadoopTaskData.java @@ -1,4 +1,4 @@ -package com.linkedin.drelephant.hadoop; +package com.linkedin.drelephant.mapreduce; public class HadoopTaskData { private HadoopCounterHolder _counterHolder; diff --git a/app/com/linkedin/drelephant/hadoop/HadoopJobData.java b/app/com/linkedin/drelephant/mapreduce/MapreduceApplicationData.java similarity index 58% rename from app/com/linkedin/drelephant/hadoop/HadoopJobData.java rename to app/com/linkedin/drelephant/mapreduce/MapreduceApplicationData.java index 65475f396..033c3143e 100644 --- a/app/com/linkedin/drelephant/hadoop/HadoopJobData.java +++ b/app/com/linkedin/drelephant/mapreduce/MapreduceApplicationData.java @@ -1,9 +1,11 @@ -package com.linkedin.drelephant.hadoop; +package com.linkedin.drelephant.mapreduce; +import com.linkedin.drelephant.analysis.ApplicationType; +import com.linkedin.drelephant.analysis.HadoopApplicationData; import java.util.Properties; -public class HadoopJobData { +public class MapreduceApplicationData implements HadoopApplicationData { private String _jobId = ""; private String _username = ""; private String _url = ""; @@ -16,57 +18,57 @@ public class HadoopJobData { private Properties _jobConf; private boolean _isRetry = false; - public HadoopJobData setRetry(boolean isRetry) { + public MapreduceApplicationData setRetry(boolean isRetry) { this._isRetry = isRetry; return this; } - public HadoopJobData setJobId(String jobId) { + public MapreduceApplicationData setJobId(String jobId) { this._jobId = jobId; return this; } - public HadoopJobData setJobName(String jobName) { + public MapreduceApplicationData setJobName(String jobName) { this._jobName = jobName; return this; } - public HadoopJobData setUsername(String username) { + public MapreduceApplicationData setUsername(String username) { this._username = username; return this; } - public HadoopJobData setStartTime(long startTime) { + public MapreduceApplicationData setStartTime(long startTime) { this._startTime = startTime; return this; } - public HadoopJobData setFinishTime(long finishTime) { + public MapreduceApplicationData setFinishTime(long finishTime) { this._finishTime = finishTime; return this; } - public HadoopJobData setUrl(String url) { + public MapreduceApplicationData setUrl(String url) { this._url = url; return this; } - public HadoopJobData setCounters(HadoopCounterHolder counterHolder) { + public MapreduceApplicationData setCounters(HadoopCounterHolder counterHolder) { this._counterHolder = counterHolder; return this; } - public HadoopJobData setMapperData(HadoopTaskData[] mappers) { + public MapreduceApplicationData setMapperData(HadoopTaskData[] mappers) { this._mapperData = mappers; return this; } - public HadoopJobData setReducerData(HadoopTaskData[] reducers) { + public MapreduceApplicationData setReducerData(HadoopTaskData[] reducers) { this._reducerData = reducers; return this; } - public HadoopJobData setJobConf(Properties jobConf) { + public MapreduceApplicationData setJobConf(Properties jobConf) { this._jobConf = jobConf; return this; } @@ -83,7 +85,13 @@ public HadoopTaskData[] getReducerData() { return _reducerData; } - public Properties getJobConf() { + @Override + public String getUid() { + return _jobId; + } + + @Override + public Properties getConf() { return _jobConf; } @@ -114,4 +122,8 @@ public boolean isRetryJob() { public String getJobId() { return _jobId; } + + public String toString() { + return "id: " + getJobId() + ", name:" + getJobName(); + } } diff --git a/app/com/linkedin/drelephant/ElephantFetcherClassic.java b/app/com/linkedin/drelephant/mapreduce/MapreduceFetcherClassic.java similarity index 81% rename from app/com/linkedin/drelephant/ElephantFetcherClassic.java rename to app/com/linkedin/drelephant/mapreduce/MapreduceFetcherClassic.java index 37209f6ae..8771fc931 100644 --- a/app/com/linkedin/drelephant/ElephantFetcherClassic.java +++ b/app/com/linkedin/drelephant/mapreduce/MapreduceFetcherClassic.java @@ -1,25 +1,21 @@ -package com.linkedin.drelephant; +package com.linkedin.drelephant.mapreduce; +import com.linkedin.drelephant.analysis.ElephantFetcher; import com.linkedin.drelephant.analysis.Constants; -import com.linkedin.drelephant.hadoop.HadoopCounterHolder; -import com.linkedin.drelephant.hadoop.HadoopCounterHolder.CounterName; -import com.linkedin.drelephant.hadoop.HadoopJobData; -import com.linkedin.drelephant.hadoop.HadoopTaskData; +import com.linkedin.drelephant.mapreduce.HadoopCounterHolder.CounterName; import com.linkedin.drelephant.math.Statistics; +import com.linkedin.drelephant.util.Utils; import java.io.IOException; import java.net.HttpURLConnection; import java.net.URL; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Random; -import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import model.JobResult; import org.apache.commons.io.IOUtils; import org.apache.hadoop.mapred.Counters; import org.apache.hadoop.mapred.Counters.Counter; @@ -39,71 +35,29 @@ import org.jsoup.select.Elements; -public class ElephantFetcherClassic implements ElephantFetcher { +public class MapreduceFetcherClassic implements ElephantFetcher { private static final Logger logger = Logger.getLogger(ElephantFetcher.class); private static final int DEFAULT_RETRY = 2; private JobConf _conf; - private Set _previousJobs; - private Map _failedJobsInWait = new ConcurrentHashMap(); - private Map _failedJobsInProgress = new ConcurrentHashMap(); - private boolean _firstRun = true; + private Map _failedJobsInWait = new ConcurrentHashMap(); + private Map _failedJobsInProgress = new ConcurrentHashMap(); private String _jobtrackerHttpRoot; - public ElephantFetcherClassic(JobConf hadoopConf) throws IOException { - _conf = hadoopConf; - _jobtrackerHttpRoot = "http://" + hadoopConf.get("mapred.job.tracker.http.address") + "/"; + public MapreduceFetcherClassic() throws IOException { + _conf = new JobConf(); + _jobtrackerHttpRoot = "http://" + _conf.get("mapred.job.tracker.http.address") + "/"; } + @Override public void init(int threadId) throws IOException { ThreadContextMR1.init(_conf, threadId); } - public List fetchJobList() throws IOException { - JobStatus[] result = null; - - JobClient jobClient = ThreadContextMR1.getClient(); - result = jobClient.getAllJobs(); - - if (result == null) { - throw new IOException("Error fetching joblist from jobtracker."); - } - - // Get all completed jobs from jobtracker - Set successJobs = filterSuccessfulJobs(result); - - // Filter out newly completed jobs - successJobs = filterPreviousJobs(successJobs); - - // Add newly completed jobs to return list - List jobList = new ArrayList(); - for (String jobId : successJobs) { - jobList.add(new HadoopJobData().setJobId(jobId)); - } - // Add previously failed jobs to return list - List retryList = fetchAllFailedJobs(); - jobList.addAll(retryList); - logger.info("Fetcher gets total " + jobList.size() + " jobs (" + retryList.size() + " retry jobs)"); - return jobList; - } - - public void finishJob(HadoopJobData jobData, boolean success) { - if (success) { - // Job succeeded, remove from retry list if this is a retry job - if (jobData.isRetryJob()) { - checkAndRemoveJobFromRetryList(jobData); - } - } else { - // Job failed, clear job data and add to retry list - if (!jobData.isRetryJob()) { - jobData.setRetry(true); - } - clearJobData(jobData); - addJobToRetryList(jobData); - } - } - - public void fetchJobData(HadoopJobData jobData) throws IOException, AuthenticationException { + @Override + public MapreduceApplicationData fetchData(String id) throws IOException, AuthenticationException { + MapreduceApplicationData jobData = new MapreduceApplicationData(); + jobData.setJobId(id); try { JobID jobId = JobID.forName(jobData.getJobId()); JobClient jobClient = ThreadContextMR1.getClient(); @@ -117,11 +71,10 @@ public void fetchJobData(HadoopJobData jobData) throws IOException, Authenticati } if (job == null || (mapperTasks.length == 0 && reducerTasks.length == 0)) { - boolean retired = checkRetiredAndFetchJobData(jobData); - if (retired) { + if (checkRetiredAndFetchJobData(jobData)) { // This is a retired job logger.info(jobData.getJobId() + " retired. Fetch data from web UI."); - return; + return jobData; } // This is a no data job } @@ -173,6 +126,10 @@ public void fetchJobData(HadoopJobData jobData) throws IOException, Authenticati } finally { ThreadContextMR1.updateAuthToken(); } + + Utils.publishMetrics(jobData); + + return jobData; } private Properties fetchJobConf(RunningJob job) throws IOException, AuthenticationException { @@ -200,44 +157,6 @@ private Properties fetchJobConfFromURL(String jobconfUrl) throws IOException, Au return properties; } - // Return a set of succeeded job ids - private HashSet filterSuccessfulJobs(JobStatus[] jobs) { - HashSet successJobs = new HashSet(); - for (JobStatus job : jobs) { - if (job.getRunState() == JobStatus.SUCCEEDED && job.isJobComplete()) { - successJobs.add(job.getJobID().toString()); - } - } - return successJobs; - } - - private Set filterPreviousJobs(Set jobs) { - - // On first run, check each job against DB - if (_firstRun) { - logger.info("Checking database ...... "); - Set newJobs = new HashSet(); - for (String jobId : jobs) { - JobResult prevResult = JobResult.find.byId(jobId); - if (prevResult == null) { - // Job not found, add to new jobs list - newJobs.add(jobId); - } - } - _previousJobs = jobs; - _firstRun = false; - logger.info("Database check completed"); - return newJobs; - } else { - @SuppressWarnings("unchecked") - Set tempPrevJobs = (HashSet) ((HashSet) jobs).clone(); - // Leave only the newly completed jobs - jobs.removeAll(_previousJobs); - _previousJobs = tempPrevJobs; - return jobs; - } - } - private HadoopTaskData fetchTaskData(String jobDetailUrl, TaskReport task, boolean isMapper, boolean sampled) throws IOException, AuthenticationException { @@ -330,7 +249,7 @@ private HadoopCounterHolder fetchCounter(Counters counters) { return holder; } - private void addJobToRetryList(HadoopJobData job) { + private void addJobToRetryList(MapreduceApplicationData job) { if (_failedJobsInProgress.containsKey(job)) { // This is old retry job int retryLeft = _failedJobsInProgress.get(job); @@ -348,30 +267,8 @@ private void addJobToRetryList(HadoopJobData job) { } } - private List fetchAllFailedJobs() { - List retryList = new ArrayList(); - for (HadoopJobData job : _failedJobsInWait.keySet()) { - int retry = _failedJobsInWait.get(job); - retryList.add(job); - _failedJobsInWait.remove(job); - _failedJobsInProgress.put(job, retry - 1); - } - return retryList; - } - - private void checkAndRemoveJobFromRetryList(HadoopJobData jobData) { - if (_failedJobsInProgress.containsKey(jobData)) { - logger.info(jobData.getJobId() + " succeeded on retry."); - _failedJobsInProgress.remove(jobData); - } - } - - private void clearJobData(HadoopJobData jobData) { - jobData.setCounters(null).setJobConf(null).setMapperData(null).setReducerData(null); - } - // Return false if this is a real nodata received job ( 0 mapper & 0 reducer ) rather than a retired job - private boolean checkRetiredAndFetchJobData(HadoopJobData jobData) throws IOException, AuthenticationException { + private boolean checkRetiredAndFetchJobData(MapreduceApplicationData jobData) throws IOException, AuthenticationException { String jobUrl = _jobtrackerHttpRoot + "jobdetails.jsp?jobid=" + jobData.getJobId(); Document doc = ThreadContextMR1.fetchHtmlDoc(jobUrl); Elements hrefs = doc.select("a"); diff --git a/app/com/linkedin/drelephant/ElephantFetcherYarn.java b/app/com/linkedin/drelephant/mapreduce/MapreduceFetcherYarn.java similarity index 86% rename from app/com/linkedin/drelephant/ElephantFetcherYarn.java rename to app/com/linkedin/drelephant/mapreduce/MapreduceFetcherYarn.java index b8f185cc9..09f50f23a 100644 --- a/app/com/linkedin/drelephant/ElephantFetcherYarn.java +++ b/app/com/linkedin/drelephant/mapreduce/MapreduceFetcherYarn.java @@ -1,11 +1,10 @@ -package com.linkedin.drelephant; +package com.linkedin.drelephant.mapreduce; import com.google.common.collect.Lists; -import com.linkedin.drelephant.hadoop.HadoopCounterHolder; -import com.linkedin.drelephant.hadoop.HadoopCounterHolder.CounterName; -import com.linkedin.drelephant.hadoop.HadoopJobData; -import com.linkedin.drelephant.hadoop.HadoopTaskData; +import com.linkedin.drelephant.analysis.ElephantFetcher; +import com.linkedin.drelephant.mapreduce.HadoopCounterHolder.CounterName; import com.linkedin.drelephant.math.Statistics; +import com.linkedin.drelephant.util.Utils; import java.io.IOException; import java.net.HttpURLConnection; import java.net.MalformedURLException; @@ -26,7 +25,7 @@ import org.codehaus.jackson.map.ObjectMapper; -public class ElephantFetcherYarn implements ElephantFetcher { +public class MapreduceFetcherYarn implements ElephantFetcher { private static final Logger logger = Logger.getLogger(ElephantFetcher.class); // We provide one minute job fetch delay due to the job sending lag from AM/NM to JobHistoryServer HDFS private static final long FETCH_DELAY = 60000; @@ -38,9 +37,9 @@ public class ElephantFetcherYarn implements ElephantFetcher { private long _lastTime = 0; private long _currentTime = 0; - public ElephantFetcherYarn(JobConf hadoopConf) throws IOException { + public MapreduceFetcherYarn() throws IOException { logger.info("Connecting to the job history server..."); - String jhistoryAddr = hadoopConf.get("mapreduce.jobhistory.webapp.address"); + String jhistoryAddr = new JobConf().get("mapreduce.jobhistory.webapp.address"); _urlFactory = new URLFactory(jhistoryAddr); _jsonFactory = new JSONFactory(); _retryFactory = new RetryFactory(); @@ -57,9 +56,9 @@ public void init(int threadId) throws IOException { * If not first time, search time span since last fetch, also re-fetch failed jobs * Return list on success, throw Exception on error */ - public List fetchJobList() throws IOException, AuthenticationException { + public List fetchJobList() throws IOException, AuthenticationException { - List jobList; + List jobList; int retrySize = 0; // There is a lag of job data from AM/NM to JobHistoryServer HDFS, we shouldn't use the current @@ -77,7 +76,7 @@ public List fetchJobList() throws IOException, AuthenticationExce if (_firstRun) { _firstRun = false; } else { - List retryList = _retryFactory.fetchAllRetryJobs(); + List retryList = _retryFactory.fetchAllRetryJobs(); // If not first time, also fetch jobs that need to retry retrySize = retryList.size(); jobList.addAll(retryList); @@ -97,12 +96,12 @@ private boolean checkDBforJob(String jobId) { } // Clear all data stored on the job object - private void clearJobData(HadoopJobData jobData) { + private void clearJobData(MapreduceApplicationData jobData) { jobData.setCounters(null).setJobConf(null).setMapperData(null).setReducerData(null); } // OnJobFinish Add to retry list upon failure - public void finishJob(HadoopJobData jobData, boolean success) { + public void finishJob(MapreduceApplicationData jobData, boolean success) { if (!success) { if (!jobData.isRetryJob()) { jobData.setRetry(true); @@ -119,10 +118,12 @@ public void finishJob(HadoopJobData jobData, boolean success) { } // Fetch job detailed data. Return true on success - public void fetchJobData(HadoopJobData jobData) throws IOException, AuthenticationException { + @Override + public MapreduceApplicationData fetchData(String appId) throws IOException, AuthenticationException { + MapreduceApplicationData jobData = new MapreduceApplicationData(); + String jobId = Utils.getJobIdFromApplicationId(appId); + jobData.setJobId(jobId); try { - String jobId = jobData.getJobId(); - // Fetch job counter URL jobCounterURL = _urlFactory.getJobCounterURL(jobId); HadoopCounterHolder jobCounter = _jsonFactory.getJobCounter(jobCounterURL); @@ -145,6 +146,10 @@ public void fetchJobData(HadoopJobData jobData) throws IOException, Authenticati } finally { ThreadContextMR2.updateAuthToken(); } + + Utils.publishMetrics(jobData); + + return jobData; } private String getJobDetailURL(String jobId) { @@ -164,7 +169,7 @@ private class URLFactory { private String _root; private String _restRoot; - public URLFactory(String hserverAddr) throws IOException { + private URLFactory(String hserverAddr) throws IOException { _root = "http://" + hserverAddr; _restRoot = "http://" + hserverAddr + "/ws/v1/history/mapreduce/jobs"; verifyURL(_restRoot); @@ -181,7 +186,7 @@ private String getJobDetailURLString(String jobId) { return _root + "/jobhistory/job/" + jobId; } - private URL fetchJobListURL(long startTime, long endTime) throws MalformedURLException { + public URL fetchJobListURL(long startTime, long endTime) throws MalformedURLException { return new URL(_restRoot + "?finishedTimeBegin=" + startTime + "&finishedTimeEnd=" + endTime + "&state=SUCCEEDED"); } @@ -207,8 +212,8 @@ private URL getTaskAttemptURL(String jobId, String taskId, String attemptId) thr } private class JSONFactory { - private List getJobData(URL url, boolean checkDB) throws IOException, AuthenticationException { - List jobList = new ArrayList(); + private List getJobData(URL url, boolean checkDB) throws IOException, AuthenticationException { + List jobList = new ArrayList(); JsonNode rootNode = ThreadContextMR2.readJsonNode(url); JsonNode jobs = rootNode.path("jobs").path("job"); @@ -238,7 +243,7 @@ private List getJobData(URL url, boolean checkDB) throws IOExcept "user": "ssubrama" } */ - HadoopJobData jobData = new HadoopJobData(); + MapreduceApplicationData jobData = new MapreduceApplicationData(); jobData.setJobId(jobId).setUsername(job.get("user").getValueAsText()) .setJobName(job.get("name").getValueAsText()).setUrl(getJobDetailURL(jobId)) .setStartTime(job.get("startTime").getLongValue()) @@ -355,10 +360,10 @@ private void getTaskDataAll(URL url, String jobId, List mapperLi private class RetryFactory { private static final int DEFAULT_RETRY = 2; - private Map _retryMapInWait = new ConcurrentHashMap(); - private Map _retryMapInProgress = new ConcurrentHashMap(); + private Map _retryMapInWait = new ConcurrentHashMap(); + private Map _retryMapInProgress = new ConcurrentHashMap(); - private void addJobToRetryList(HadoopJobData job) { + private void addJobToRetryList(MapreduceApplicationData job) { if (_retryMapInProgress.containsKey(job)) { // This is old retry job int retryLeft = _retryMapInProgress.get(job); @@ -375,9 +380,9 @@ private void addJobToRetryList(HadoopJobData job) { } } - private List fetchAllRetryJobs() { - List retryList = new ArrayList(); - for (HadoopJobData job : _retryMapInWait.keySet()) { + private List fetchAllRetryJobs() { + List retryList = new ArrayList(); + for (MapreduceApplicationData job : _retryMapInWait.keySet()) { int retry = _retryMapInWait.get(job); _retryMapInWait.remove(job); retryList.add(job); @@ -386,7 +391,7 @@ private List fetchAllRetryJobs() { return Lists.newArrayList(retryList); } - private void checkAndRemoveFromRetryList(HadoopJobData jobData) { + private void checkAndRemoveFromRetryList(MapreduceApplicationData jobData) { if (_retryMapInProgress.containsKey(jobData)) { _retryMapInProgress.remove(jobData); } diff --git a/app/com/linkedin/drelephant/analysis/heuristics/GenericDataSkewHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericDataSkewHeuristic.java similarity index 85% rename from app/com/linkedin/drelephant/analysis/heuristics/GenericDataSkewHeuristic.java rename to app/com/linkedin/drelephant/mapreduce/heuristics/GenericDataSkewHeuristic.java index 867d9ecac..6c23cf45d 100644 --- a/app/com/linkedin/drelephant/analysis/heuristics/GenericDataSkewHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericDataSkewHeuristic.java @@ -1,17 +1,17 @@ -package com.linkedin.drelephant.analysis.heuristics; +package com.linkedin.drelephant.mapreduce.heuristics; import com.linkedin.drelephant.analysis.Constants; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.hadoop.HadoopCounterHolder; -import com.linkedin.drelephant.hadoop.HadoopJobData; -import com.linkedin.drelephant.hadoop.HadoopTaskData; +import com.linkedin.drelephant.mapreduce.HadoopCounterHolder; +import com.linkedin.drelephant.mapreduce.MapreduceApplicationData; +import com.linkedin.drelephant.mapreduce.HadoopTaskData; import com.linkedin.drelephant.math.Statistics; import org.apache.commons.io.FileUtils; -public abstract class GenericDataSkewHeuristic implements Heuristic { +public abstract class GenericDataSkewHeuristic implements Heuristic { private HadoopCounterHolder.CounterName _counterName; private String _heuristicName; @@ -25,10 +25,10 @@ protected GenericDataSkewHeuristic(HadoopCounterHolder.CounterName counterName, this._heuristicName = heuristicName; } - protected abstract HadoopTaskData[] getTasks(HadoopJobData data); + protected abstract HadoopTaskData[] getTasks(MapreduceApplicationData data); @Override - public HeuristicResult apply(HadoopJobData data) { + public HeuristicResult apply(MapreduceApplicationData data) { HadoopTaskData[] tasks = getTasks(data); //Gather data diff --git a/app/com/linkedin/drelephant/analysis/heuristics/JobQueueLimitHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/JobQueueLimitHeuristic.java similarity index 90% rename from app/com/linkedin/drelephant/analysis/heuristics/JobQueueLimitHeuristic.java rename to app/com/linkedin/drelephant/mapreduce/heuristics/JobQueueLimitHeuristic.java index 1bf15ac31..29150a76f 100644 --- a/app/com/linkedin/drelephant/analysis/heuristics/JobQueueLimitHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/JobQueueLimitHeuristic.java @@ -1,16 +1,16 @@ -package com.linkedin.drelephant.analysis.heuristics; +package com.linkedin.drelephant.mapreduce.heuristics; +import com.linkedin.drelephant.mapreduce.MapreduceApplicationData; +import com.linkedin.drelephant.mapreduce.HadoopTaskData; import java.util.Properties; import java.util.concurrent.TimeUnit; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.hadoop.HadoopJobData; -import com.linkedin.drelephant.hadoop.HadoopTaskData; -public class JobQueueLimitHeuristic implements Heuristic { +public class JobQueueLimitHeuristic implements Heuristic { public static final String HEURISTIC_NAME = "Queue Time Limit"; @Override @@ -19,9 +19,9 @@ public String getHeuristicName() { } @Override - public HeuristicResult apply(HadoopJobData data) { + public HeuristicResult apply(MapreduceApplicationData data) { HeuristicResult result = new HeuristicResult(HEURISTIC_NAME, Severity.NONE); - Properties jobConf = data.getJobConf(); + Properties jobConf = data.getConf(); long queueTimeoutLimitMs = TimeUnit.MINUTES.toMillis(15); // Fetch the Queue to which the job is submitted. diff --git a/app/com/linkedin/drelephant/analysis/heuristics/MapperDataSkewHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperDataSkewHeuristic.java similarity index 50% rename from app/com/linkedin/drelephant/analysis/heuristics/MapperDataSkewHeuristic.java rename to app/com/linkedin/drelephant/mapreduce/heuristics/MapperDataSkewHeuristic.java index 2bed7ef77..3cbe494fa 100644 --- a/app/com/linkedin/drelephant/analysis/heuristics/MapperDataSkewHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperDataSkewHeuristic.java @@ -1,8 +1,8 @@ -package com.linkedin.drelephant.analysis.heuristics; +package com.linkedin.drelephant.mapreduce.heuristics; -import com.linkedin.drelephant.hadoop.HadoopCounterHolder; -import com.linkedin.drelephant.hadoop.HadoopJobData; -import com.linkedin.drelephant.hadoop.HadoopTaskData; +import com.linkedin.drelephant.mapreduce.HadoopCounterHolder; +import com.linkedin.drelephant.mapreduce.MapreduceApplicationData; +import com.linkedin.drelephant.mapreduce.HadoopTaskData; public class MapperDataSkewHeuristic extends GenericDataSkewHeuristic { @@ -13,7 +13,7 @@ public MapperDataSkewHeuristic() { } @Override - protected HadoopTaskData[] getTasks(HadoopJobData data) { + protected HadoopTaskData[] getTasks(MapreduceApplicationData data) { return data.getMapperData(); } } diff --git a/app/com/linkedin/drelephant/analysis/heuristics/MapperInputSizeHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperInputSizeHeuristic.java similarity index 91% rename from app/com/linkedin/drelephant/analysis/heuristics/MapperInputSizeHeuristic.java rename to app/com/linkedin/drelephant/mapreduce/heuristics/MapperInputSizeHeuristic.java index ec14ab1c6..22c1b8aef 100644 --- a/app/com/linkedin/drelephant/analysis/heuristics/MapperInputSizeHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperInputSizeHeuristic.java @@ -1,5 +1,7 @@ -package com.linkedin.drelephant.analysis.heuristics; +package com.linkedin.drelephant.mapreduce.heuristics; +import com.linkedin.drelephant.mapreduce.MapreduceApplicationData; +import com.linkedin.drelephant.mapreduce.HadoopCounterHolder; import java.util.ArrayList; import java.util.List; @@ -7,15 +9,13 @@ import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.hadoop.HadoopCounterHolder; -import com.linkedin.drelephant.hadoop.HadoopJobData; -import com.linkedin.drelephant.hadoop.HadoopTaskData; +import com.linkedin.drelephant.mapreduce.HadoopTaskData; import com.linkedin.drelephant.math.Statistics; import org.apache.commons.io.FileUtils; -public class MapperInputSizeHeuristic implements Heuristic { +public class MapperInputSizeHeuristic implements Heuristic { public static final String HEURISTIC_NAME = "Mapper Input Size"; @Override @@ -24,7 +24,7 @@ public String getHeuristicName() { } @Override - public HeuristicResult apply(HadoopJobData data) { + public HeuristicResult apply(MapreduceApplicationData data) { HadoopTaskData[] tasks = data.getMapperData(); List inputBytes = new ArrayList(); diff --git a/app/com/linkedin/drelephant/analysis/heuristics/MapperSpeedHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristic.java similarity index 87% rename from app/com/linkedin/drelephant/analysis/heuristics/MapperSpeedHeuristic.java rename to app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristic.java index 2117b9219..5892e0146 100644 --- a/app/com/linkedin/drelephant/analysis/heuristics/MapperSpeedHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristic.java @@ -1,5 +1,7 @@ -package com.linkedin.drelephant.analysis.heuristics; +package com.linkedin.drelephant.mapreduce.heuristics; +import com.linkedin.drelephant.mapreduce.MapreduceApplicationData; +import com.linkedin.drelephant.mapreduce.HadoopCounterHolder; import java.util.ArrayList; import java.util.List; @@ -7,14 +9,12 @@ import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.hadoop.HadoopCounterHolder; -import com.linkedin.drelephant.hadoop.HadoopJobData; -import com.linkedin.drelephant.hadoop.HadoopTaskData; +import com.linkedin.drelephant.mapreduce.HadoopTaskData; import com.linkedin.drelephant.math.Statistics; import org.apache.commons.io.FileUtils; -public class MapperSpeedHeuristic implements Heuristic { +public class MapperSpeedHeuristic implements Heuristic { public static final String HEURISTIC_NAME = "Mapper Speed"; @Override @@ -23,7 +23,7 @@ public String getHeuristicName() { } @Override - public HeuristicResult apply(HadoopJobData data) { + public HeuristicResult apply(MapreduceApplicationData data) { HadoopTaskData[] tasks = data.getMapperData(); diff --git a/app/com/linkedin/drelephant/analysis/heuristics/MapperSpillHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristic.java similarity index 84% rename from app/com/linkedin/drelephant/analysis/heuristics/MapperSpillHeuristic.java rename to app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristic.java index 841824e78..736addaa5 100644 --- a/app/com/linkedin/drelephant/analysis/heuristics/MapperSpillHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristic.java @@ -1,19 +1,19 @@ -package com.linkedin.drelephant.analysis.heuristics; +package com.linkedin.drelephant.mapreduce.heuristics; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.hadoop.HadoopCounterHolder; -import com.linkedin.drelephant.hadoop.HadoopJobData; -import com.linkedin.drelephant.hadoop.HadoopTaskData; +import com.linkedin.drelephant.mapreduce.HadoopCounterHolder; +import com.linkedin.drelephant.mapreduce.HadoopTaskData; +import com.linkedin.drelephant.mapreduce.MapreduceApplicationData; -public class MapperSpillHeuristic implements Heuristic { +public class MapperSpillHeuristic implements Heuristic { public static final String HEURISTIC_NAME = "Mapper Spill"; private static final long THRESHOLD_SPILL_FACTOR = 10000; @Override - public HeuristicResult apply(HadoopJobData data) { + public HeuristicResult apply(MapreduceApplicationData data) { HadoopTaskData[] tasks = data.getMapperData(); long totalSpills = 0; diff --git a/app/com/linkedin/drelephant/analysis/heuristics/ReducerDataSkewHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerDataSkewHeuristic.java similarity index 50% rename from app/com/linkedin/drelephant/analysis/heuristics/ReducerDataSkewHeuristic.java rename to app/com/linkedin/drelephant/mapreduce/heuristics/ReducerDataSkewHeuristic.java index 218033a68..f9acc86e9 100644 --- a/app/com/linkedin/drelephant/analysis/heuristics/ReducerDataSkewHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerDataSkewHeuristic.java @@ -1,8 +1,8 @@ -package com.linkedin.drelephant.analysis.heuristics; +package com.linkedin.drelephant.mapreduce.heuristics; -import com.linkedin.drelephant.hadoop.HadoopCounterHolder; -import com.linkedin.drelephant.hadoop.HadoopJobData; -import com.linkedin.drelephant.hadoop.HadoopTaskData; +import com.linkedin.drelephant.mapreduce.HadoopCounterHolder; +import com.linkedin.drelephant.mapreduce.MapreduceApplicationData; +import com.linkedin.drelephant.mapreduce.HadoopTaskData; public class ReducerDataSkewHeuristic extends GenericDataSkewHeuristic { @@ -13,7 +13,7 @@ public ReducerDataSkewHeuristic() { } @Override - protected HadoopTaskData[] getTasks(HadoopJobData data) { + protected HadoopTaskData[] getTasks(MapreduceApplicationData data) { return data.getReducerData(); } } diff --git a/app/com/linkedin/drelephant/analysis/heuristics/ReducerTimeHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristic.java similarity index 88% rename from app/com/linkedin/drelephant/analysis/heuristics/ReducerTimeHeuristic.java rename to app/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristic.java index 05209ecd8..7fa817dcc 100644 --- a/app/com/linkedin/drelephant/analysis/heuristics/ReducerTimeHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristic.java @@ -1,17 +1,17 @@ -package com.linkedin.drelephant.analysis.heuristics; +package com.linkedin.drelephant.mapreduce.heuristics; +import com.linkedin.drelephant.mapreduce.MapreduceApplicationData; +import com.linkedin.drelephant.mapreduce.HadoopTaskData; import java.util.ArrayList; import java.util.List; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.hadoop.HadoopJobData; -import com.linkedin.drelephant.hadoop.HadoopTaskData; import com.linkedin.drelephant.math.Statistics; -public class ReducerTimeHeuristic implements Heuristic { +public class ReducerTimeHeuristic implements Heuristic { public static final String HEURISTIC_NAME = "Reducer Time"; @Override @@ -20,7 +20,7 @@ public String getHeuristicName() { } @Override - public HeuristicResult apply(HadoopJobData data) { + public HeuristicResult apply(MapreduceApplicationData data) { HadoopTaskData[] tasks = data.getReducerData(); List runTimesMs = new ArrayList(); diff --git a/app/com/linkedin/drelephant/analysis/heuristics/ShuffleSortHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristic.java similarity index 88% rename from app/com/linkedin/drelephant/analysis/heuristics/ShuffleSortHeuristic.java rename to app/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristic.java index 333931e40..e9cfc25e8 100644 --- a/app/com/linkedin/drelephant/analysis/heuristics/ShuffleSortHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristic.java @@ -1,17 +1,17 @@ -package com.linkedin.drelephant.analysis.heuristics; +package com.linkedin.drelephant.mapreduce.heuristics; +import com.linkedin.drelephant.mapreduce.MapreduceApplicationData; import java.util.ArrayList; import java.util.List; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.hadoop.HadoopJobData; -import com.linkedin.drelephant.hadoop.HadoopTaskData; +import com.linkedin.drelephant.mapreduce.HadoopTaskData; import com.linkedin.drelephant.math.Statistics; -public class ShuffleSortHeuristic implements Heuristic { +public class ShuffleSortHeuristic implements Heuristic { public static final String HEURISTIC_NAME = "Shuffle & Sort"; @Override @@ -20,7 +20,7 @@ public String getHeuristicName() { } @Override - public HeuristicResult apply(HadoopJobData data) { + public HeuristicResult apply(MapreduceApplicationData data) { HadoopTaskData[] tasks = data.getReducerData(); diff --git a/app/com/linkedin/drelephant/spark/SparkApplicationData.java b/app/com/linkedin/drelephant/spark/SparkApplicationData.java new file mode 100644 index 000000000..cc4c5e217 --- /dev/null +++ b/app/com/linkedin/drelephant/spark/SparkApplicationData.java @@ -0,0 +1,21 @@ +package com.linkedin.drelephant.spark; + +import com.linkedin.drelephant.analysis.HadoopApplicationData; + + +/** + * This holds a collection of all SparkApplicationData + * + */ +public interface SparkApplicationData extends HadoopApplicationData { + + public SparkGeneralData getGeneralData(); + + public SparkEnvironmentData getEnvironmentData(); + + public SparkExecutorData getExecutorData(); + + public SparkJobProgressData getJobProgressData(); + + public SparkStorageData getStorageData(); +} diff --git a/app/com/linkedin/drelephant/spark/SparkEnvironmentData.java b/app/com/linkedin/drelephant/spark/SparkEnvironmentData.java new file mode 100644 index 000000000..a67b45e75 --- /dev/null +++ b/app/com/linkedin/drelephant/spark/SparkEnvironmentData.java @@ -0,0 +1,53 @@ +package com.linkedin.drelephant.spark; + +import java.util.Properties; + + +/** + * This data class holds Spark environment data (Spark properties, JVM properties and etc.) + */ +public class SparkEnvironmentData { + private final Properties _sparkProperties; + private final Properties _systemProperties; + + public SparkEnvironmentData() { + _sparkProperties = new Properties(); + _systemProperties = new Properties(); + } + + public void addSparkProperty(String key, String value) { + _sparkProperties.put(key, value); + } + + public void addSystemProperty(String key, String value) { + _systemProperties.put(key, value); + } + + public String getSparkProperty(String key) { + return _sparkProperties.getProperty(key); + } + + public String getSparkProperty(String key, String defaultValue) { + String val = getSparkProperty(key); + if (val == null) { + return defaultValue; + } + return val; + } + + public String getSystemProperty(String key) { + return _systemProperties.getProperty(key); + } + + public Properties getSparkProperties() { + return _sparkProperties; + } + + public Properties getSystemProperties() { + return _systemProperties; + } + + public String toString() { + return _sparkProperties.toString() + "\n\n\n" + _systemProperties.toString(); + } +} diff --git a/app/com/linkedin/drelephant/spark/SparkExecutorData.java b/app/com/linkedin/drelephant/spark/SparkExecutorData.java new file mode 100644 index 000000000..fe04d112d --- /dev/null +++ b/app/com/linkedin/drelephant/spark/SparkExecutorData.java @@ -0,0 +1,55 @@ +package com.linkedin.drelephant.spark; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + + +/** + * This class contains Spark exeuctor information. + * + */ +public class SparkExecutorData { + public static final String EXECUTOR_DRIVER_NAME = ""; + + public static class ExecutorInfo { + public String execId; + public String hostPort; + public int rddBlocks = 0; + public long memUsed = 0L; + public long maxMem = 0L; + public long diskUsed = 0L; + + public int activeTasks = 0; + public int completedTasks = 0; + public int failedTasks = 0; + public int totalTasks = 0; + public long duration = 0L; + public long inputBytes = 0L; + public long outputBytes = 0L; + public long shuffleRead = 0L; + public long shuffleWrite = 0L; + + public String toString() { + return "{execId: " + execId + ", hostPort:" + hostPort + " , rddBlocks: " + rddBlocks + ", memUsed: " + memUsed + + ", maxMem: " + maxMem + ", diskUsed: " + diskUsed + ", totalTasks" + totalTasks + ", tasksActive: " + + activeTasks + ", tasksComplete: " + completedTasks + ", tasksFailed: " + failedTasks + ", duration: " + + duration + ", inputBytes: " + inputBytes + ", outputBytes:" + outputBytes + ", shuffleRead: " + shuffleRead + + ", shuffleWrite: " + shuffleWrite + "}"; + } + } + + private final Map _executorInfoMap = new HashMap(); + + public void setExecutorInfo(String executorId, ExecutorInfo info) { + _executorInfoMap.put(executorId, info); + } + + public ExecutorInfo getExecutorInfo(String executorId) { + return _executorInfoMap.get(executorId); + } + + public Set getExecutors() { + return _executorInfoMap.keySet(); + } +} diff --git a/app/com/linkedin/drelephant/spark/SparkGeneralData.java b/app/com/linkedin/drelephant/spark/SparkGeneralData.java new file mode 100644 index 000000000..55600f30f --- /dev/null +++ b/app/com/linkedin/drelephant/spark/SparkGeneralData.java @@ -0,0 +1,74 @@ +package com.linkedin.drelephant.spark; + +import java.util.Set; + + +/** + * This class holds Spark application information + * + */ +public class SparkGeneralData { + private Set _adminAcls; + private Set _viewAcls; + private String _applicationId; + private String _applicationName; + private String _sparkUser; + private long _startTime; + private long _endTime; + + public Set getAdminAcls() { + return _adminAcls; + } + + public void setAdminAcls(Set adminAcls) { + _adminAcls = adminAcls; + } + + public Set getViewAcls() { + return _viewAcls; + } + + public void setViewAcls(Set viewAcls) { + _viewAcls = viewAcls; + } + + public String getApplicationId() { + return _applicationId; + } + + public void setApplicationId(String applicationId) { + _applicationId = applicationId; + } + + public String getApplicationName() { + return _applicationName; + } + + public void setApplicationName(String applicationName) { + _applicationName = applicationName; + } + + public String getSparkUser() { + return _sparkUser; + } + + public void setSparkUser(String sparkUser) { + _sparkUser = sparkUser; + } + + public long getStartTime() { + return _startTime; + } + + public void setStartTime(long startTime) { + _startTime = startTime; + } + + public long getEndTime() { + return _endTime; + } + + public void setEndTime(long endTime) { + _endTime = endTime; + } +} diff --git a/app/com/linkedin/drelephant/spark/SparkJobProgressData.java b/app/com/linkedin/drelephant/spark/SparkJobProgressData.java new file mode 100644 index 000000000..3e8241820 --- /dev/null +++ b/app/com/linkedin/drelephant/spark/SparkJobProgressData.java @@ -0,0 +1,246 @@ +package com.linkedin.drelephant.spark; + +import com.linkedin.drelephant.math.Statistics; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.apache.commons.lang.StringUtils; +import org.apache.log4j.Logger; + + +/** + * This class represents information contained in a job runtime process. + * + */ +public class SparkJobProgressData { + private static final Logger logger = Logger.getLogger(SparkJobProgressData.class); + private final Map _jobIdToInfo = new HashMap(); + private final Set _completedJobs = new HashSet(); + private final Set _failedJobs = new HashSet(); + + private final Map _stageIdToInfo = new HashMap(); + private final Set _completedStages = new HashSet(); + private final Set _failedStages = new HashSet(); + + public void addJobInfo(int jobId, JobInfo info) { + _jobIdToInfo.put(jobId, info); + } + + public void addCompletedJob(int jobId) { + _completedJobs.add(jobId); + } + + public void addFailedJob(int jobId) { + _failedJobs.add(jobId); + } + + public void addStageInfo(int stageId, int attemptId, StageInfo info) { + _stageIdToInfo.put(new StageId(stageId, attemptId), info); + } + + public void addCompletedStages(int stageId, int attemptId) { + _completedStages.add(new StageId(stageId, attemptId)); + } + + public void addFailedStages(int stageId, int attemptId) { + _failedStages.add(new StageId(stageId, attemptId)); + } + + public Set getCompletedJobs() { + return _completedJobs; + } + + public Set getFailedJobs() { + return _failedJobs; + } + + private static double getFailureRate(int numCompleted, int numFailed) { + int num = numCompleted + numFailed; + + if (num == 0) { + return 0d; + } + + return numFailed * 1.0d / num; + } + + public double getJobFailureRate() { + return getFailureRate(_completedJobs.size(), _failedJobs.size()); + } + + public double getStageFailureRate() { + return getFailureRate(_completedStages.size(), _failedStages.size()); + } + + public JobInfo getJobInfo(int jobId) { + return _jobIdToInfo.get(jobId); + } + + public StageInfo getStageInfo(int stageId, int attemptId) { + return _stageIdToInfo.get(new StageId(stageId, attemptId)); + } + + public Set getCompletedStages() { + return _completedStages; + } + + public Set getFailedStages() { + return _failedStages; + } + + /** + * Job itself does not have a name, it will use its latest stage as the name. + * + * @param jobId + * @return + */ + public String getJobDescription(int jobId) { + List stageIds = _jobIdToInfo.get(jobId).stageIds; + int id = -1; + for (int stageId : stageIds) { + id = Math.max(id, stageId); + } + if (id == -1) { + logger.error("Spark Job id [" + jobId + "] does not contain any stage."); + return null; + } + return _stageIdToInfo.get(new StageId(id, 0)).name; + } + + public List getFailedJobDescriptions() { + List result = new ArrayList(); + for (int id : _failedJobs) { + result.add(getJobDescription(id)); + } + return result; + } + + // For debug purpose + public String toString() { + StringBuilder s = new StringBuilder(); + s.append("JobInfo: ["); + + for (Map.Entry entry : _jobIdToInfo.entrySet()) { + s.append("{id:" + entry.getKey() + ", value: " + entry.getValue() + "}"); + } + + s.append("]\nStageInfo: ["); + for (Map.Entry entry : _stageIdToInfo.entrySet()) { + s.append("{id:" + entry.getKey() + ", value: " + entry.getValue() + "}"); + } + s.append("]"); + + return s.toString(); + } + + public static class StageId { + public int stageId; + public int attemptId; + + public StageId(int stageId, int attemptId) { + this.stageId = stageId; + this.attemptId = attemptId; + } + + @Override + public int hashCode() { + return new Integer(stageId).hashCode() * 31 + new Integer(attemptId).hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof StageId) { + StageId other = (StageId) obj; + return stageId == other.stageId && attemptId == other.attemptId; + } + return false; + } + + public String toString() { + return "id: " + stageId + " # attemptId: " + attemptId; + } + } + + public static class JobInfo { + public int jobId; + public String jobGroup; + public long startTime; + public long endTime; + public final List stageIds = new ArrayList(); + + /* Tasks */ + public int numTasks = 0; + public int numActiveTasks = 0; + public int numCompletedTasks = 0; + public int numSkippedTasks = 0; + public int numFailedTasks = 0; + + /* Stages */ + public int numActiveStages = 0; + // This needs to be a set instead of a simple count to prevent double-counting of rerun stages: + public final Set completedStageIndices = new HashSet(); + public int numSkippedStages = 0; + public int numFailedStages = 0; + + public void addStageId(int stageId) { + stageIds.add(stageId); + } + + public double getFailureRate() { + return SparkJobProgressData.getFailureRate(numCompletedTasks, numFailedTasks); + } + + public String toString() { + return String.format("{jobId:%s, jobGroup:%s, startTime:%s, endTime:%s, numTask:%s, numActiveTasks:%s, " + + "numCompletedTasks:%s, numSkippedTasks:%s, numFailedTasks:%s, numActiveStages:%s, " + + "completedStageIndices:%s, stages:%s, numSkippedStages:%s, numFailedStages:%s}", jobId, jobGroup, + startTime, endTime, numTasks, numActiveTasks, numCompletedTasks, numSkippedTasks, numFailedTasks, + numActiveStages, getListString(completedStageIndices), getListString(stageIds), numSkippedStages, + numFailedStages); + } + } + + public static class StageInfo { + public int numActiveTasks; + public int numCompleteTasks; + public final Set completedIndices = new HashSet(); + public int numFailedTasks; + + public long executorRunTime; + + // Note, currently calculating I/O speed on stage level does not make sense + // since we do not have information about specific I/O time. + public long inputBytes = 0; + public long outputBytes = 0; + public long shuffleReadBytes = 0; + public long shuffleWriteBytes = 0; + public long memoryBytesSpilled = 0; + public long diskBytesSpilled = 0; + + public String name; + public String description; + + public double getFailureRate() { + return SparkJobProgressData.getFailureRate(numCompleteTasks, numFailedTasks); + } + + // TODO: accumulables info seem to be unnecessary, might might be useful later on + // sample code from Spark source: var accumulables = new HashMap[Long, AccumulableInfo] + + public String toString() { + return String.format("{numActiveTasks:%s, numCompleteTasks:%s, completedIndices:%s, numFailedTasks:%s," + + " executorRunTime:%s, inputBytes:%s, outputBytes:%s, shuffleReadBytes:%s, shuffleWriteBytes:%s, memoryBytesSpilled:%s," + + " diskBytesSpilled:%s, name:%s, description:%s}", numActiveTasks, numCompleteTasks, + getListString(completedIndices), numFailedTasks, executorRunTime, inputBytes, outputBytes, shuffleReadBytes, + shuffleWriteBytes, memoryBytesSpilled, diskBytesSpilled, name, description); + } + } + + private static String getListString(Collection collection) { + return "[" + StringUtils.join(collection, ",") + "]"; + } +} diff --git a/app/com/linkedin/drelephant/spark/SparkStorageData.java b/app/com/linkedin/drelephant/spark/SparkStorageData.java new file mode 100644 index 000000000..53692aeab --- /dev/null +++ b/app/com/linkedin/drelephant/spark/SparkStorageData.java @@ -0,0 +1,31 @@ +package com.linkedin.drelephant.spark; + +import java.util.List; +import org.apache.spark.storage.RDDInfo; +import org.apache.spark.storage.StorageStatus; + + +/** + * This class holds information related to Spark storage (RDDs specifically) information. + * + */ +public class SparkStorageData { + private List _rddInfoList; + private List _storageStatusList; + + public List getRddInfoList() { + return _rddInfoList; + } + + public void setRddInfoList(List rddInfoList) { + _rddInfoList = rddInfoList; + } + + public List getStorageStatusList() { + return _storageStatusList; + } + + public void setStorageStatusList(List storageStatusList) { + _storageStatusList = storageStatusList; + } +} diff --git a/app/com/linkedin/drelephant/spark/heuristics/BestPropertiesConventionHeuristic.java b/app/com/linkedin/drelephant/spark/heuristics/BestPropertiesConventionHeuristic.java new file mode 100644 index 000000000..8d4d5ed2f --- /dev/null +++ b/app/com/linkedin/drelephant/spark/heuristics/BestPropertiesConventionHeuristic.java @@ -0,0 +1,77 @@ +package com.linkedin.drelephant.spark.heuristics; + +import com.linkedin.drelephant.analysis.Heuristic; +import com.linkedin.drelephant.analysis.HeuristicResult; +import com.linkedin.drelephant.analysis.Severity; +import com.linkedin.drelephant.spark.SparkApplicationData; +import com.linkedin.drelephant.spark.SparkEnvironmentData; +import com.linkedin.drelephant.util.MemoryFormatUtils; + + +/** + * This heuristic rule check some of the most commonly set spark properties and make sure the user is following + * a best convention of them. + * + * @author yizhou + */ +public class BestPropertiesConventionHeuristic implements Heuristic { + public static final String HEURISTIC_NAME = "Spark Configuration Best Practice"; + public static final String SPARK_SERIALIZER = "spark.serializer"; + public static final String SPARK_DRIVER_MEMORY = "spark.driver.memory"; + public static final String SPARK_SHUFFLE_MANAGER = "spark.shuffle.manager"; + public static final String SPARK_EXECUTOR_CORES = "spark.executor.cores"; + + @Override + public HeuristicResult apply(SparkApplicationData data) { + SparkEnvironmentData env = data.getEnvironmentData(); + String sparkSerializer = env.getSparkProperty(SPARK_SERIALIZER); + String sparkDriverMemory = env.getSparkProperty(SPARK_DRIVER_MEMORY); + String sparkShuffleManager = env.getSparkProperty(SPARK_SHUFFLE_MANAGER); + String sparkExecutorCores = env.getSparkProperty(SPARK_EXECUTOR_CORES); + + HeuristicResult result = new HeuristicResult(getHeuristicName(), Severity + .max(binarySeverity("org.apache.spark.serializer.KryoSerializer", sparkSerializer, false, Severity.MODERATE), + getDriverMemorySeverity(MemoryFormatUtils.stringToBytes(sparkDriverMemory)), + binarySeverity("sort", sparkShuffleManager, true, Severity.MODERATE), + binarySeverity("1", sparkExecutorCores, true, Severity.CRITICAL))); + + result.addDetail(SPARK_SERIALIZER, propertyToString(sparkSerializer)); + result.addDetail(SPARK_DRIVER_MEMORY, propertyToString(sparkDriverMemory)); + result.addDetail(SPARK_SHUFFLE_MANAGER, propertyToString(sparkShuffleManager)); + result.addDetail(SPARK_EXECUTOR_CORES, propertyToString(sparkExecutorCores)); + + return result; + } + + private static Severity getDriverMemorySeverity(long mem) { + return Severity + .getSeverityAscending(mem, MemoryFormatUtils.stringToBytes("4G"), MemoryFormatUtils.stringToBytes("4G"), + MemoryFormatUtils.stringToBytes("8G"), MemoryFormatUtils.stringToBytes("8G")); + } + + private static Severity binarySeverity(String expectedValue, String actualValue, boolean ignoreNull, + Severity severity) { + if (actualValue == null) { + if (ignoreNull) { + return Severity.NONE; + } else { + return severity; + } + } + + if (actualValue.equals(expectedValue)) { + return Severity.NONE; + } else { + return severity; + } + } + + private static String propertyToString(String val) { + return val == null ? "not presented, using default" : val; + } + + @Override + public String getHeuristicName() { + return HEURISTIC_NAME; + } +} diff --git a/app/com/linkedin/drelephant/spark/heuristics/ExecutorLoadHeuristic.java b/app/com/linkedin/drelephant/spark/heuristics/ExecutorLoadHeuristic.java new file mode 100644 index 000000000..895e111bb --- /dev/null +++ b/app/com/linkedin/drelephant/spark/heuristics/ExecutorLoadHeuristic.java @@ -0,0 +1,142 @@ +package com.linkedin.drelephant.spark.heuristics; + +import com.linkedin.drelephant.analysis.Heuristic; +import com.linkedin.drelephant.analysis.HeuristicResult; +import com.linkedin.drelephant.analysis.Severity; +import com.linkedin.drelephant.math.Statistics; +import com.linkedin.drelephant.spark.SparkApplicationData; +import com.linkedin.drelephant.spark.SparkExecutorData; +import com.linkedin.drelephant.util.MemoryFormatUtils; +import java.util.Set; + +import static com.linkedin.drelephant.spark.SparkExecutorData.EXECUTOR_DRIVER_NAME; + + +/** + * This heuristic rule observes load details of executors + */ +public class ExecutorLoadHeuristic implements Heuristic { + public static final String HEURISTIC_NAME = "Spark Executor Load Balance"; + + private class ValueObserver { + private final long[] _values; + private Long _min; + private Long _max; + private Long _avg; + + public ValueObserver(int size) { + _values = new long[size]; + } + + public void set(int index, long value) { + _values[index] = value; + if (_min == null) { + _min = value; + } else { + _min = Math.min(value, _min); + } + + if (_max == null) { + _max = value; + } else { + _max = Math.max(value, _max); + } + } + + public long getMin() { + return _min == null ? 0L : _min; + } + + public long getMax() { + return _max == null ? 0L : _max; + } + + public long getAvg() { + if (_avg == null) { + if (_values == null) { + return 0L; + } + _avg = Statistics.average(_values); + } + return _avg; + } + + /** + * Max(|max-avg|, |min-avg|) / avg + * + * @return + */ + public double getDeviationFactor() { + long avg = getAvg(); + if (avg == 0) { + return 0d; + } + long diff = Math.max(getMax() - avg, avg - getMin()); + return diff * 1.0d / avg; + } + } + + @Override + public HeuristicResult apply(SparkApplicationData data) { + SparkExecutorData executorData = data.getExecutorData(); + Set executors = executorData.getExecutors(); + + int numNonDriverExe = executors.size(); + if (executors.contains(EXECUTOR_DRIVER_NAME)) { + numNonDriverExe -= 1; + } + ValueObserver peakMems = new ValueObserver(numNonDriverExe); + ValueObserver durations = new ValueObserver(numNonDriverExe); + ValueObserver inputBytes = new ValueObserver(numNonDriverExe); + ValueObserver outputBytes = new ValueObserver(numNonDriverExe); + ValueObserver totalTasks = new ValueObserver(numNonDriverExe); + + int i = 0; + for (String exeId : executors) { + if (!exeId.equals(EXECUTOR_DRIVER_NAME)) { + SparkExecutorData.ExecutorInfo info = executorData.getExecutorInfo(exeId); + peakMems.set(i, info.memUsed); + durations.set(i, info.duration); + inputBytes.set(i, info.inputBytes); + outputBytes.set(i, info.outputBytes); + totalTasks.set(i, info.totalTasks); + i += 1; + } + } + + Severity severity = Severity.max(getMerticDeviationSeverity(peakMems), getMerticDeviationSeverity(durations), + getMerticDeviationSeverity(inputBytes), getMerticDeviationSeverity(outputBytes), + getMerticDeviationSeverity(totalTasks)); + + HeuristicResult result = new HeuristicResult(getHeuristicName(), severity); + + result.addDetail("Average peak memory used", String + .format("%s (%s~%s)", MemoryFormatUtils.bytesToString(peakMems.getAvg()), + MemoryFormatUtils.bytesToString(peakMems.getMin()), MemoryFormatUtils.bytesToString(peakMems.getMax()))); + result.addDetail("Average runtime duration", String + .format("%s (%s~%s)", Statistics.readableTimespan(durations.getAvg()), + Statistics.readableTimespan(durations.getMin()), Statistics.readableTimespan(durations.getMax()))); + result.addDetail("Average input data", String + .format("%s (%s~%s)", MemoryFormatUtils.bytesToString(inputBytes.getAvg()), + MemoryFormatUtils.bytesToString(inputBytes.getMin()), + MemoryFormatUtils.bytesToString(inputBytes.getMax()))); + result.addDetail("Average output data", String + .format("%s (%s~%s)", MemoryFormatUtils.bytesToString(outputBytes.getAvg()), + MemoryFormatUtils.bytesToString(outputBytes.getMin()), + MemoryFormatUtils.bytesToString(outputBytes.getMax()))); + result.addDetail("Average total tasks", + String.format("%s (%s~%s)", totalTasks.getAvg(), totalTasks.getMin(), totalTasks.getMax())); + + return result; + } + + private static Severity getMerticDeviationSeverity(ValueObserver ob) { + double diffFactor = ob.getDeviationFactor(); + return Severity.getSeverityAscending(diffFactor, 0.2d, 0.4d, 0.6d, 0.8d); + } + + @Override + public String getHeuristicName() { + return HEURISTIC_NAME; + } +} diff --git a/app/com/linkedin/drelephant/spark/heuristics/JobRuntimeHeuristic.java b/app/com/linkedin/drelephant/spark/heuristics/JobRuntimeHeuristic.java new file mode 100644 index 000000000..2a1577384 --- /dev/null +++ b/app/com/linkedin/drelephant/spark/heuristics/JobRuntimeHeuristic.java @@ -0,0 +1,85 @@ +package com.linkedin.drelephant.spark.heuristics; + +import com.linkedin.drelephant.analysis.Heuristic; +import com.linkedin.drelephant.analysis.HeuristicResult; +import com.linkedin.drelephant.analysis.Severity; +import com.linkedin.drelephant.spark.SparkApplicationData; +import com.linkedin.drelephant.spark.SparkJobProgressData; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import org.apache.commons.lang.StringUtils; + + +/** + * This heuristic rule observes the runtime characteristics of the spark application run. + * + * @author yizhou + */ +public class JobRuntimeHeuristic implements Heuristic { + public static final String HEURISTIC_NAME = "Spark Job Runtime"; + + @Override + public HeuristicResult apply(SparkApplicationData data) { + SparkJobProgressData jobProgressData = data.getJobProgressData(); + Severity endSeverity = Severity.NONE; + + Set completedJobs = jobProgressData.getCompletedJobs(); + Set failedJobs = jobProgressData.getFailedJobs(); + + // Average job failure rate + double avgJobFailureRate = jobProgressData.getJobFailureRate(); + Severity jobFailureRateSeverity = getAvgJobFailureRateSeverity(avgJobFailureRate); + endSeverity = Severity.max(endSeverity, jobFailureRateSeverity); + + // For each completed individual job + List highFailureRateJobs = new ArrayList(); + for (int jobId : completedJobs) { + SparkJobProgressData.JobInfo job = jobProgressData.getJobInfo(jobId); + double jobFailureRate = job.getFailureRate(); + Severity severity = getSingleJobFailureRateSeverity(jobFailureRate); + if (severity.getValue() > Severity.MODERATE.getValue()) { + highFailureRateJobs.add( + jobProgressData.getJobDescription(jobId) + " (task failure rate:" + String.format("%1.3f", jobFailureRate) + + ")"); + } + endSeverity = Severity.max(endSeverity, severity); + } + + HeuristicResult result = new HeuristicResult(getHeuristicName(), endSeverity); + + result.addDetail("Spark completed jobs number", String.valueOf(completedJobs.size())); + result.addDetail("Spark failed jobs number", String.valueOf(failedJobs.size())); + result.addDetail("Spark failed jobs list", getJobListString(jobProgressData.getFailedJobDescriptions())); + result.addDetail("Spark average job failure rate", String.format("%1.3f", avgJobFailureRate)); + result.addDetail("Spark jobs with high task failure rate", getJobListString(highFailureRateJobs)); + + return result; + } + + @Override + public String getHeuristicName() { + return HEURISTIC_NAME; + } + + private static Severity getAvgJobFailureRateSeverity(double rate) { + return Severity.getSeverityAscending(rate, 0.1d, 0.3d, 0.5d, 0.5d); + } + + private static Severity getSingleJobFailureRateSeverity(double rate) { + if (rate > 0.5d) { + return Severity.CRITICAL; + } + + if (rate > 0.3d) { + return Severity.MODERATE; + } + + return Severity.NONE; + } + + private static String getJobListString(Collection names) { + return "[" + StringUtils.join(names, ",") + "]"; + } +} diff --git a/app/com/linkedin/drelephant/spark/heuristics/MemoryLimitHeuristic.java b/app/com/linkedin/drelephant/spark/heuristics/MemoryLimitHeuristic.java new file mode 100644 index 000000000..bcd5c4ac9 --- /dev/null +++ b/app/com/linkedin/drelephant/spark/heuristics/MemoryLimitHeuristic.java @@ -0,0 +1,154 @@ +package com.linkedin.drelephant.spark.heuristics; + +import com.linkedin.drelephant.analysis.Heuristic; +import com.linkedin.drelephant.analysis.HeuristicResult; +import com.linkedin.drelephant.analysis.Severity; +import com.linkedin.drelephant.spark.SparkApplicationData; +import com.linkedin.drelephant.spark.SparkEnvironmentData; +import com.linkedin.drelephant.spark.SparkExecutorData; +import com.linkedin.drelephant.util.MemoryFormatUtils; +import org.apache.log4j.Logger; +import org.apache.spark.SparkConf; + +import static com.linkedin.drelephant.spark.SparkExecutorData.EXECUTOR_DRIVER_NAME; + + +/** + * This heuristic checks for memory consumption. + * + * @author yizhou + */ +public class MemoryLimitHeuristic implements Heuristic { + public static final String HEURISTIC_NAME = "Spark Memory Limit"; + private static final Logger logger = Logger.getLogger(MemoryLimitHeuristic.class); + + public static final String SPARK_EXECUTOR_MEMORY = "spark.executor.memory"; + public static final String SPARK_DRIVER_MEMORY = "spark.driver.memory"; + public static final String SPARK_EXECUTOR_INSTANCES = "spark.executor.instances"; + + public static final String SPARK_STORAGE_MEMORY_FRACTION = "spark.storage.memoryFraction"; + public static final double DEFAULT_SPARK_STORAGE_MEMORY_FRACTION = 0.6d; + + @Override + public HeuristicResult apply(SparkApplicationData data) { + + long totalExecutorMem = getTotalExecutorMem(data); + long totalStorageMem = getTotalStorageMem(data); + long totalDriverMem = getTotalDriverMem(data); + long peakMem = getStoragePeakMemory(data); + + Severity totalMemorySeverity = getTotalMemorySeverity(totalExecutorMem); + Severity memoryUtilizationServerity = getMemoryUtilizationSeverity(peakMem, totalStorageMem); + + HeuristicResult result = + new HeuristicResult(getHeuristicName(), Severity.max(totalMemorySeverity, memoryUtilizationServerity)); + + result.addDetail("Total executor memory allocated", MemoryFormatUtils.bytesToString(totalExecutorMem)); + result.addDetail("Total driver memory allocated", MemoryFormatUtils.bytesToString(totalDriverMem)); + result.addDetail("Total memory allocated for storage", MemoryFormatUtils.bytesToString(totalStorageMem)); + result.addDetail("Total memory used at peak", MemoryFormatUtils.bytesToString(peakMem)); + result.addDetail("Memory utilization rate", String.format("%1.3f", peakMem * 1.0 / totalStorageMem)); + return result; + } + + /** + * Get the total executor memory + * + * @param data The spark application data that contains the information + * @return The memory in bytes + */ + private static long getTotalExecutorMem(SparkApplicationData data) { + int executorNum = Integer.parseInt(data.getEnvironmentData().getSparkProperty(SPARK_EXECUTOR_INSTANCES, "0")); + long perExecutorMem = + MemoryFormatUtils.stringToBytes(data.getEnvironmentData().getSparkProperty(SPARK_EXECUTOR_MEMORY, "0")); + return executorNum * perExecutorMem; + } + + /** + * Get the total driver memory + * + * @param data The spark application data that contains the information + * @return The memory in bytes + */ + private static long getTotalDriverMem(SparkApplicationData data) { + long bytes = MemoryFormatUtils.stringToBytes(data.getEnvironmentData().getSparkProperty(SPARK_DRIVER_MEMORY)); + // spark.driver.memory might not be present, in which case we would infer it from the executor data + if (bytes == 0L) { + SparkExecutorData.ExecutorInfo info = data.getExecutorData().getExecutorInfo(EXECUTOR_DRIVER_NAME); + if (info == null) { + logger.error("Application id [" + data.getGeneralData().getApplicationId() + + "] does not contain driver memory configuration info and also does not contain executor driver info." + + " Unable to detect is driver memory usage."); + return 0L; + } + // This maxmium memory only counts in memory for storage + bytes = (long) (info.maxMem / getStorageMemoryFraction(data.getEnvironmentData())); + } + + return bytes; + } + + /** + * Get the storage memory fraction ratio used for storage + * + * @param data The spark environment data + * @return the memory fraction + */ + private static double getStorageMemoryFraction(SparkEnvironmentData data) { + String ratio = data.getSparkProperty(SPARK_STORAGE_MEMORY_FRACTION); + if (ratio == null) { + ratio = new SparkConf().get(SPARK_STORAGE_MEMORY_FRACTION, String.valueOf(DEFAULT_SPARK_STORAGE_MEMORY_FRACTION)); + } + return Double.parseDouble(ratio); + } + + /** + * Get the peak storage memory used during all running time of the spark application + * + * @param data The spark application data that contains the information + * @return The memory in bytes + */ + private static long getStoragePeakMemory(SparkApplicationData data) { + SparkExecutorData executorData = data.getExecutorData(); + long mem = 0L; + for (String id : executorData.getExecutors()) { + mem += executorData.getExecutorInfo(id).memUsed; + } + return mem; + } + + /** + * Get the total memory allocated for storage + * + * @param data The spark application data that contains the information + * @return The memory in bytes + */ + private static long getTotalStorageMem(SparkApplicationData data) { + SparkExecutorData executorData = data.getExecutorData(); + long totalStorageMem = 0L; + for (String id : executorData.getExecutors()) { + totalStorageMem += executorData.getExecutorInfo(id).maxMem; + } + return totalStorageMem; + } + + @Override + public String getHeuristicName() { + return HEURISTIC_NAME; + } + + public static Severity getTotalMemorySeverity(long memory) { + return Severity + .getSeverityAscending(memory, MemoryFormatUtils.stringToBytes("0.5T"), MemoryFormatUtils.stringToBytes("1T"), + MemoryFormatUtils.stringToBytes("1.5T"), MemoryFormatUtils.stringToBytes("2T")); + } + + private static Severity getMemoryUtilizationSeverity(long peakMemory, long totalStorageMemory) { + double fraction = peakMemory * 1.0 / totalStorageMemory; + if (totalStorageMemory < MemoryFormatUtils.stringToBytes("10 GB")) { + return Severity.NONE; + } else { + return Severity.getSeverityDescending(fraction, 0.8d, 0.6d, 0.4d, 0.2d); + } + } +} diff --git a/app/com/linkedin/drelephant/spark/heuristics/StageRuntimeHeuristic.java b/app/com/linkedin/drelephant/spark/heuristics/StageRuntimeHeuristic.java new file mode 100644 index 000000000..ab072066e --- /dev/null +++ b/app/com/linkedin/drelephant/spark/heuristics/StageRuntimeHeuristic.java @@ -0,0 +1,96 @@ +package com.linkedin.drelephant.spark.heuristics; + +import com.linkedin.drelephant.analysis.Heuristic; +import com.linkedin.drelephant.analysis.HeuristicResult; +import com.linkedin.drelephant.analysis.Severity; +import com.linkedin.drelephant.math.Statistics; +import com.linkedin.drelephant.spark.SparkApplicationData; +import com.linkedin.drelephant.spark.SparkJobProgressData; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import org.apache.commons.lang.StringUtils; + + +/** + * Spark heuristic that detects stage runtime anomalies. + * + */ +public class StageRuntimeHeuristic implements Heuristic { + public static final String HEURISTIC_NAME = "Spark Stage Runtime"; + + @Override + public HeuristicResult apply(SparkApplicationData data) { + SparkJobProgressData jobProgressData = data.getJobProgressData(); + Severity endSeverity = Severity.NONE; + + Set completedStages = jobProgressData.getCompletedStages(); + Set failedStages = jobProgressData.getFailedStages(); + + // Average stage failure rate + double avgStageFailureRate = jobProgressData.getStageFailureRate(); + endSeverity = Severity.max(endSeverity, getStageFailureRateSeverity(avgStageFailureRate)); + + // For each completed stage, the task failure rate + List problematicStages = new ArrayList(); + + for (SparkJobProgressData.StageId id : completedStages) { + SparkJobProgressData.StageInfo info = jobProgressData.getStageInfo(id.stageId, id.attemptId); + double stageTasksFailureRate = info.getFailureRate(); + Severity tasksFailureRateSeverity = getSingleStageTasksFailureRate(stageTasksFailureRate); + + if (tasksFailureRateSeverity.getValue() > Severity.MODERATE.getValue()) { + problematicStages.add(String.format("%s (task failure rate: %1.3f)", id, stageTasksFailureRate)); + } + + long executorTime = info.executorRunTime; + Severity runtimeSeverity = getStageRuntimeSeverity(executorTime); + if (runtimeSeverity.getValue() > Severity.MODERATE.getValue()) { + problematicStages + .add(String.format("%s (runtime: %s)", id, Statistics.readableTimespan(executorTime))); + } + + endSeverity = Severity.max(endSeverity, tasksFailureRateSeverity, runtimeSeverity); + } + + HeuristicResult result = new HeuristicResult(getHeuristicName(), endSeverity); + + result.addDetail("Spark stage completed", String.valueOf(completedStages.size())); + result.addDetail("Spark stage failed", String.valueOf(failedStages.size())); + result.addDetail("Spark average stage failure rate", String.format("%1.3f", avgStageFailureRate)); + result.addDetail("Spark problematic stages:", getStageListString(problematicStages)); + + return result; + } + + @Override + public String getHeuristicName() { + return HEURISTIC_NAME; + } + + private static Severity getStageRuntimeSeverity(long runtime) { + return Severity.getSeverityAscending(runtime, 15 * Statistics.MINUTE_IN_MS, 15 * Statistics.MINUTE_IN_MS, + 30 * Statistics.MINUTE_IN_MS, 30 * Statistics.MINUTE_IN_MS); + } + + private static Severity getStageFailureRateSeverity(double rate) { + return Severity.getSeverityAscending(rate, 0.3d, 0.3d, 0.5d, 0.5d); + } + + private static Severity getSingleStageTasksFailureRate(double rate) { + if (rate > 0.5d) { + return Severity.CRITICAL; + } + + if (rate > 0.3) { + return Severity.MODERATE; + } + + return Severity.NONE; + } + + private static String getStageListString(Collection names) { + return "[" + StringUtils.join(names, ",") + "]"; + } +} diff --git a/app/com/linkedin/drelephant/util/HeuristicConf.java b/app/com/linkedin/drelephant/util/HeuristicConf.java index 2707d4ad9..980110e14 100644 --- a/app/com/linkedin/drelephant/util/HeuristicConf.java +++ b/app/com/linkedin/drelephant/util/HeuristicConf.java @@ -1,77 +1,33 @@ package com.linkedin.drelephant.util; -import java.io.IOException; -import java.io.InputStream; +import com.linkedin.drelephant.analysis.ApplicationType; import java.util.ArrayList; import java.util.List; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; - -import org.apache.hadoop.conf.Configuration; -import org.w3c.dom.Document; +import org.apache.log4j.Logger; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; -import org.xml.sax.SAXException; - -import play.Logger; -import play.Play; public class HeuristicConf { - - private static final String CONFIG_FILE_PATH = "HeuristicConf.xml"; + private static final Logger logger = Logger.getLogger(HeuristicConf.class); private List _heuristicsConfDataList; - private static HeuristicConf _heuristicConfInstance = new HeuristicConf(); - - private HeuristicConf() { - parseHeuristicConf(); - } - public static HeuristicConf instance() { - return _heuristicConfInstance; + public HeuristicConf(Element configuration) { + parseHeuristicConf(configuration); } public List getHeuristicsConfData() { return _heuristicsConfDataList; } - private void parseHeuristicConf() { - - Configuration hadoopConf = new Configuration(); - String hadoopVersion = hadoopConf.get("mapreduce.framework.name"); - if (hadoopVersion == null) { - if (hadoopConf.get("mapred.job.tracker.http.address") != null) { - hadoopVersion = "classic"; - } else { - throw new RuntimeException("Hadoop config error. No framework name provided."); - } - } + private void parseHeuristicConf(Element configuration) { + String hadoopVersion = Utils.getHadoopVersion(); - Logger.info("Loading pluggable heuristics config file " + CONFIG_FILE_PATH); _heuristicsConfDataList = new ArrayList(); - InputStream instream = Play.application().resourceAsStream(CONFIG_FILE_PATH); - if (instream == null) { - throw new RuntimeException("File " + CONFIG_FILE_PATH + " does not exist."); - } - - Document document = null; - try { - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - DocumentBuilder builder = factory.newDocumentBuilder(); - document = builder.parse(instream); - } catch (ParserConfigurationException e) { - throw new RuntimeException("XML Parser could not be created.", e); - } catch (SAXException e) { - throw new RuntimeException(CONFIG_FILE_PATH + " is not properly formed", e); - } catch (IOException e) { - throw new RuntimeException("Unable to read " + CONFIG_FILE_PATH, e); - } - - NodeList nodes = document.getDocumentElement().getChildNodes(); + NodeList nodes = configuration.getChildNodes(); int n = 0; for (int i = 0; i < nodes.getLength(); i++) { Node node = nodes.item(i); @@ -111,13 +67,29 @@ private void parseHeuristicConf() { Node versionsNode = heuristicNode.getElementsByTagName("hadoopversions").item(0); if (versionsNode == null || versionsNode.getNodeType() != Node.ELEMENT_NODE) { - throw new RuntimeException("No tag or invalid tag 'hadoopversions' in heuristic " + n + " classname " + className); + throw new RuntimeException( + "No tag or invalid tag 'hadoopversions' in heuristic " + n + " classname " + className); } NodeList versionList = ((Element) versionsNode).getElementsByTagName("version"); + + Node appTypeNode = heuristicNode.getElementsByTagName("applicationtype").item(0); + if (appTypeNode == null) { + throw new RuntimeException( + "No tag or invalid tag 'applicationtype' in heuristic " + n + " classname " + className); + } + String appTypeStr = appTypeNode.getTextContent(); + ApplicationType appType = ApplicationType.getType(appTypeStr); + if (appType == null) { + logger.error( + "[" + appTypeStr + "] is not a valid application type in heuristic " + n + " classname " + className + + ". Skipping this configuration."); + continue; + } + for (int j = 0; j < versionList.getLength(); j++) { String version = versionList.item(j).getTextContent(); if (version.equals(hadoopVersion)) { - HeuristicConfData heuristicData = new HeuristicConfData(heuristicName, className, viewName); + HeuristicConfData heuristicData = new HeuristicConfData(heuristicName, className, viewName, appType); _heuristicsConfDataList.add(heuristicData); break; } diff --git a/app/com/linkedin/drelephant/util/HeuristicConfData.java b/app/com/linkedin/drelephant/util/HeuristicConfData.java index f732e4d64..d731419cb 100644 --- a/app/com/linkedin/drelephant/util/HeuristicConfData.java +++ b/app/com/linkedin/drelephant/util/HeuristicConfData.java @@ -1,14 +1,19 @@ package com.linkedin.drelephant.util; +import com.linkedin.drelephant.analysis.ApplicationType; + + public class HeuristicConfData { - private String _heuristicName; - private String _className; - private String _viewName; - - public HeuristicConfData(String heuristicName, String className, String viewName) { - this._heuristicName = heuristicName; - this._className = className; - this._viewName = viewName; + private final String _heuristicName; + private final String _className; + private final String _viewName; + private final ApplicationType _appType; + + public HeuristicConfData(String heuristicName, String className, String viewName, ApplicationType appType) { + _heuristicName = heuristicName; + _className = className; + _viewName = viewName; + _appType = appType; } public String getHeuristicName() { @@ -22,4 +27,8 @@ public String getClassName() { public String getViewName() { return _viewName; } + + public ApplicationType getAppType() { + return _appType; + } } diff --git a/app/com/linkedin/drelephant/util/InfoExtractor.java b/app/com/linkedin/drelephant/util/InfoExtractor.java new file mode 100644 index 000000000..d1f2b2e37 --- /dev/null +++ b/app/com/linkedin/drelephant/util/InfoExtractor.java @@ -0,0 +1,98 @@ +package com.linkedin.drelephant.util; + +import com.linkedin.drelephant.analysis.HadoopApplicationData; +import com.linkedin.drelephant.spark.SparkApplicationData; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import org.apache.commons.lang.StringUtils; +import org.apache.log4j.Logger; + +import model.JobResult; + +import com.linkedin.drelephant.mapreduce.MapreduceApplicationData; + + +/** + * InfoExtractor is responsible for retrieving information and context about a + * job from the job's configuration which will be leveraged by the UI + */ +public class InfoExtractor { + private static final Logger logger = Logger.getLogger(InfoExtractor.class); + private static final String SPARK_EXTRA_JAVA_OPTIONS = "spark.driver.extraJavaOptions"; + + private static final String AZK_WORKFLOW_URL = "azkaban.link.workflow.url"; + private static final String AZK_JOB_URL = "azkaban.link.job.url"; + private static final String AZK_EXECUTION_URL = "azkaban.link.execution.url"; + private static final String AZK_ATTEMPT_URL = "azkaban.link.attempt.url"; + + // TODO: this utils class is not ideal, probably should merge retrieve URLs logics directly into the data interface? + public static void retrieveURLs(JobResult result, HadoopApplicationData data) { + if (data instanceof MapreduceApplicationData) { + retrieveURLs(result, (MapreduceApplicationData) data); + } else if (data instanceof SparkApplicationData) { + retrieveURLs(result, (SparkApplicationData) data); + } + } + + public static void retrieveURLs(JobResult result, MapreduceApplicationData appData) { + Properties jobConf = appData.getConf(); + String jobId = appData.getJobId(); + result.jobExecUrl = truncate(jobConf.getProperty(AZK_ATTEMPT_URL), jobId); + // For jobs launched by Azkaban, we consider different attempts to be + // different jobs + result.jobUrl = truncate(jobConf.getProperty(AZK_JOB_URL), jobId); + result.flowExecUrl = truncate(jobConf.getProperty(AZK_EXECUTION_URL), jobId); + result.flowUrl = truncate(jobConf.getProperty(AZK_WORKFLOW_URL), jobId); + } + + public static void retrieveURLs(JobResult result, SparkApplicationData appData) { + String prop = appData.getEnvironmentData().getSparkProperty(SPARK_EXTRA_JAVA_OPTIONS); + if (prop != null) { + try { + Map options = Utils.parseJavaOptions(prop); + + List s = new ArrayList(); + for (Map.Entry e : options.entrySet()) { + s.add(e.getKey() + "->" + e.getValue()); + } + logger.info("Parsed options:" + StringUtils.join(s, ",")); + + result.jobExecUrl = unescapeString(options.get(AZK_ATTEMPT_URL)); + result.jobUrl = unescapeString(options.get(AZK_JOB_URL)); + result.flowExecUrl = unescapeString(options.get(AZK_EXECUTION_URL)); + result.flowUrl = unescapeString(options.get(AZK_WORKFLOW_URL)); + } catch (IllegalArgumentException e) { + logger.error("Encountered error while parsing java options into urls: " + e.getMessage()); + } + } else { + logger.error("Unable to retrieve azkaban urls from application + [" + + appData.getGeneralData().getApplicationId() + "] it does not contain [" + SPARK_EXTRA_JAVA_OPTIONS + + "] property in its spark properties."); + } + } + + /** + * A temporary solution that SPARK 1.2 need to escape '&' with '\&' in its javaOptions. + * This is the reverse process that recovers the escaped string. + * + * @param s The string to unescape + * @return The original string + */ + private static String unescapeString(String s) { + if (s == null) { + return null; + } + return s.replaceAll("\\\\\\&", "\\&"); + } + + public static String truncate(String value, String jobId) { + if (value != null && value.length() > JobResult.URL_LEN_LIMIT) { + logger.info("Truncate long URL in job result for job: " + jobId + ". Original Url: " + value); + value = value.substring(0, JobResult.URL_LEN_LIMIT); + } + return value; + } +} diff --git a/app/com/linkedin/drelephant/util/JobTypeConf.java b/app/com/linkedin/drelephant/util/JobTypeConf.java index ed8bf18b3..7894a1342 100644 --- a/app/com/linkedin/drelephant/util/JobTypeConf.java +++ b/app/com/linkedin/drelephant/util/JobTypeConf.java @@ -1,9 +1,11 @@ package com.linkedin.drelephant.util; +import com.linkedin.drelephant.analysis.HadoopApplicationData; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; +import java.util.Properties; import java.util.regex.PatternSyntaxException; import javax.xml.parsers.DocumentBuilder; @@ -17,7 +19,7 @@ import org.w3c.dom.NodeList; import org.xml.sax.SAXException; -import com.linkedin.drelephant.hadoop.JobType; +import com.linkedin.drelephant.analysis.JobType; import play.Play; @@ -25,8 +27,9 @@ public class JobTypeConf { private static final Logger logger = Logger.getLogger(JobTypeConf.class); private static final int TYPE_LEN_LIMIT = 20; + // This is a default job type we will add into the type list even not configured. private static final String DEFAULT_TYPE = "HadoopJava"; - private static final String CONFIG_FILE_PATH = "JobType.xml"; + private static final String CONFIG_FILE_PATH = "job-types.xml"; private static JobTypeConf _heuristicConfInstance = new JobTypeConf(); private List _jobTypeList; @@ -43,6 +46,16 @@ public List getJobTypeList() { return _jobTypeList; } + public JobType getJobType(HadoopApplicationData data) { + Properties conf = data.getConf(); + for (JobType type : _jobTypeList) { + if (type.matchType(conf)) { + return type; + } + } + return JobType.NO_DATA; + } + private void parseJobTypeConf() { logger.info("Loading job type config file " + CONFIG_FILE_PATH); @@ -118,7 +131,11 @@ private void parseJobTypeConf() { } } // Add default type to the list. All jobs will match this type because this conf always exists - _jobTypeList.add(new JobType(DEFAULT_TYPE, "mapreduce.framework.name", ".*")); + if (Utils.getHadoopVersion().equals("yarn")) { + _jobTypeList.add(new JobType(DEFAULT_TYPE, "mapreduce.framework.name", ".*")); + } else { + _jobTypeList.add(new JobType(DEFAULT_TYPE, "mapred.job.tracker", ".*")); + } logger.info("Loaded total " + _jobTypeList.size() + " job types."); } } diff --git a/app/com/linkedin/drelephant/util/MemoryFormatUtils.java b/app/com/linkedin/drelephant/util/MemoryFormatUtils.java new file mode 100644 index 000000000..4c9b05b52 --- /dev/null +++ b/app/com/linkedin/drelephant/util/MemoryFormatUtils.java @@ -0,0 +1,86 @@ +package com.linkedin.drelephant.util; + +import java.text.DecimalFormat; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.apache.commons.lang.StringUtils; + + +/** + * This is a utils class that handles memory string parsing and formatting problem. + */ +public class MemoryFormatUtils { + private MemoryFormatUtils() { + // Do nothing, empty on purpose + } + + private static final long B = 1L; + private static final long K = 1L << 10; + private static final long M = 1L << 20; + private static final long G = 1L << 30; + private static final long T = 1L << 40; + private static final String[] UNITS = new String[]{"TB", "GB", "MB", "KB", "B"}; + private static final long[] DIVIDERS = new long[]{T, G, M, K, B}; + private static final DecimalFormat FORMATTER = new DecimalFormat("#,##0.##"); + private static final Pattern REGEX_MATCHER = + Pattern.compile("((?:\\d+)|(?:\\d*\\.\\d+))\\s*((?:[T|G|M|K])?B?)?", Pattern.CASE_INSENSITIVE); + + /** + * Given a memory value in bytes, convert it to a string with the unit that round to a >0 integer part. + * + * @param value The memory value in long bytes + * @return The formatted string, null if + */ + public static String bytesToString(long value) { + if (value < 0) { + throw new IllegalArgumentException("Invalid memory size: " + value); + } + for (int i = 0; i < DIVIDERS.length; i++) { + if (value >= DIVIDERS[i]) { + double numResult = DIVIDERS[i] > 1 ? (double) value / (double) DIVIDERS[i] : (double) value; + return FORMATTER.format(numResult) + " " + UNITS[i]; + } + } + return value + " " + UNITS[UNITS.length - 1]; + } + + /** + * Convert a formatted string into a long value in bytes. + * This method handles + * + * @param formattedString The string to convert + * @return The bytes value + */ + public static long stringToBytes(String formattedString) { + if (formattedString == null) { + return 0L; + } + + Matcher matcher = REGEX_MATCHER.matcher(formattedString); + if (!matcher.matches()) { + throw new IllegalArgumentException( + "The formatted string [" + formattedString + "] does not match with the regex /" + REGEX_MATCHER.toString() + + "/"); + } + if (matcher.groupCount() != 1 && matcher.groupCount() != 2) { + throw new IllegalArgumentException(); + } + + double numPart = Double.parseDouble(matcher.group(1)); + if (numPart < 0) { + throw new IllegalArgumentException("The number part of the memory cannot be less than zero: [" + numPart + "]."); + } + String unitPart = matcher.groupCount() == 2 ? matcher.group(2).toUpperCase() : ""; + if (!unitPart.endsWith("B")) { + unitPart += "B"; + } + for (int i = 0; i < UNITS.length; i++) { + if (unitPart.equals(UNITS[i])) { + return (long) (numPart * DIVIDERS[i]); + } + } + throw new IllegalArgumentException("The formatted string [" + formattedString + "] 's unit part [" + unitPart + + "] does not match any unit. The supported units are (case-insensitive, and also the 'B' is ignorable): [" + + StringUtils.join(UNITS) + "]."); + } +} diff --git a/app/com/linkedin/drelephant/util/Utils.java b/app/com/linkedin/drelephant/util/Utils.java index 1b24f0cde..211a0a800 100644 --- a/app/com/linkedin/drelephant/util/Utils.java +++ b/app/com/linkedin/drelephant/util/Utils.java @@ -1,12 +1,185 @@ package com.linkedin.drelephant.util; +import com.linkedin.drelephant.DaliMetricsAPI; +import com.linkedin.drelephant.ElephantContext; +import com.linkedin.drelephant.mapreduce.HadoopCounterHolder; +import com.linkedin.drelephant.mapreduce.MapreduceApplicationData; +import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import org.apache.avro.generic.IndexedRecord; +import org.apache.commons.lang.StringUtils; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.mapred.JobConf; +import org.apache.log4j.Logger; +import org.w3c.dom.Document; +import org.xml.sax.SAXException; +import play.Play; public final class Utils { + private static final Logger logger = Logger.getLogger(Utils.class); private Utils() { + // do nothing + } + + /** + * Given a mapreduce job's application id, get its corresponding job id. + * + * @param appId + * @return the corresponding job id + */ + public static String getJobIdFromApplicationId(String appId) { + return appId.replaceAll("application", "job"); + } + + /** + * Given a MapreduceApplicationData instance, publish the corresponding Dali Metrics if needed. + * + * @param jobData the data to check for publishing + */ + public static void publishMetrics(MapreduceApplicationData jobData) { + DaliMetricsAPI.MetricsPublisher metricsPublisher = ElephantContext.instance().getMetricsPublisher(); + if (metricsPublisher == null) { + return; + } + + logger.info(metricsPublisher.getClass()); + + Properties jobConf = jobData.getConf(); + // We may have something to publish, but we don't know until we have iterated through the counters that we have. + // We assume that we need to publish something until we find out we don't. + DaliMetricsAPI.JobProperties jobProperties = new DaliMetricsAPI.JobProperties(jobConf); + if (jobProperties.getCountersToPublish().isEmpty()) { + // Nothing to do + logger.info("No counters-to-publish property presented in job [" + jobData.getJobId() + "]"); + return; + } + logger.info("Publishing counters for job[" + jobData.getJobId() + "]"); + DaliMetricsAPI.EventContext eventContext = + new DaliMetricsAPI.EventContext(jobData.getJobName(), jobData.getJobId(), jobData.getStartTime(), + jobData.getFinishTime()); + DaliMetricsAPI.HadoopCounters metricsEvent = new DaliMetricsAPI.HadoopCounters(eventContext, jobProperties); + + HadoopCounterHolder counterHolder = jobData.getCounters(); + logger.info("HadoopCounterHolder: {" + counterHolder + "}"); + Set groupNames = counterHolder.getGroupNames(); + logger.info("group names: [" + StringUtils.join(groupNames, ",") + "]"); + for (String group : groupNames) { + Map counters = counterHolder.getAllCountersInGroup(group); + for (Map.Entry entry : counters.entrySet()) { + String counterName = entry.getKey(); + Long value = entry.getValue(); + logger.info(String.format("%s,,,,,,,%s", counterName, String.valueOf(value))); + metricsEvent.addCounter(group, counterName, value); + } + } + + if (metricsEvent.getNumCounters() == 0) { + logger.info("No counters need to be published for job [" + jobData.getJobId() + "]"); + // The counters that were configured were not collected in HadoopCounterHolder. + return; + } + IndexedRecord event = metricsEvent.build(); + try { + metricsPublisher.publish(event); + } catch (IOException e) { + // The lower level should have logged a message. + // A checked exception from the publish() call should mean that the event was not formed correctly for some reason. + // Aside from a code bug, the most common reason for this will probably be that some mandatory fields in the + // event were missing. + // Could also mean that some derived values (e.g. hostname to URL, execId translation to an integer, etc.) + // may have failed. There is little we can do at this point to fix those, so ignore the exception. + logger.warn("Publish failed:" + e); + } + } + + /** + * Load an XML document from a file path + * + * @param filePath The file path to load + * @return The loaded Document object + */ + public static Document loadXMLDoc(String filePath) { + InputStream instream = Play.application().resourceAsStream(filePath); + if (instream == null) { + throw new RuntimeException("File " + filePath + " does not exist."); + } + + Document document = null; + try { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + document = builder.parse(instream); + } catch (ParserConfigurationException e) { + throw new RuntimeException("XML Parser could not be created.", e); + } catch (SAXException e) { + throw new RuntimeException(filePath + " is not properly formed", e); + } catch (IOException e) { + throw new RuntimeException("Unable to read " + filePath, e); + } + + return document; + } + + /** + * Detect the current Hadoop version Dr Elephant is deployed for. + * + * @return the current hadoop version ('yarn' or 'classic' typically). + */ + public static String getHadoopVersion() { + // Important, new Configuration might not be timely loaded in Hadoop 1 environment + Configuration hadoopConf = new JobConf(); + Map vals = hadoopConf.getValByRegex(".*"); + String hadoopVersion = hadoopConf.get("mapreduce.framework.name"); + if (hadoopVersion == null) { + if (hadoopConf.get("mapred.job.tracker.http.address") != null) { + hadoopVersion = "classic"; + } else { + throw new RuntimeException("Hadoop config error. No framework name provided."); + } + } + + if (hadoopVersion == null || (!hadoopVersion.equals("classic") && !hadoopVersion.equals("yarn"))) { + throw new RuntimeException( + "Cannot support hadoopVersion [" + hadoopVersion + "] other than classic or yarn currently."); + } + + return hadoopVersion; + } + + /** + * Parse a java option string in the format of "-Dfoo=bar -Dfoo2=bar ..." into a {optionName -> optionValue} map. + * + * @param str The option string to parse + * @return A map of options + */ + public static Map parseJavaOptions(String str) { + Map options = new HashMap(); + String[] tokens = str.split(" "); + for (String token : tokens) { + if (!token.startsWith("-D")) { + throw new IllegalArgumentException( + "Cannot parse java option string [" + str + "]. Some options does not begin with -D prefix."); + } + String[] parts = token.substring(2).split("=", 2); + if (parts.length != 2) { + throw new IllegalArgumentException( + "Cannot parse java option string [" + str + "]. The part [" + token + "] does not contain a =."); + } + + options.put(parts[0], parts[1]); + } + return options; } public static String combineCsvLines(String[] lines) { diff --git a/app/com/linkedin/drelephant/util/YarnURLUtils.java b/app/com/linkedin/drelephant/util/YarnURLUtils.java new file mode 100644 index 000000000..d3a135552 --- /dev/null +++ b/app/com/linkedin/drelephant/util/YarnURLUtils.java @@ -0,0 +1,54 @@ +package com.linkedin.drelephant.util; + +import java.net.MalformedURLException; +import java.net.URL; + + +/** + * This provide URLs for YARN APIs. + * + */ +public class YarnURLUtils { + private static final String MAPREDUCE_JOBS_PATH = "/ws/v1/history/mapreduce/jobs"; + private static final String JOB_DETAIL_PATH = "/jobhistory/job"; + + public static String getMapreduceJobsURLString(String historyServerRoot) { + return "http://" + historyServerRoot + "/ws/v1/history/mapreduce/jobs"; + } + + public static URL getMapreduceJobsURL(String historyServerRoot, long startTime, long endTime) + throws MalformedURLException { + return new URL(String.format("%s?finishedTimeBegin=%s&finishedTimeEnd=%s&state=SUCCEEDED", + getMapreduceJobsURLString(historyServerRoot), startTime, endTime)); + } + + public static String getJobDetailURLString(String historyServerRoot, String jobId) { + return historyServerRoot + "/jobhistory/job/" + jobId; + } + + public static URL getJobConfigURL(String historyServerRoot, String jobId) + throws MalformedURLException { + return new URL(getMapreduceJobsURLString(historyServerRoot) + "/" + jobId + "/conf"); + } + + public static URL getJobCounterURL(String historyServerRoot, String jobId) + throws MalformedURLException { + return new URL(getMapreduceJobsURLString(historyServerRoot) + "/" + jobId + "/counters"); + } + + public static URL getTaskListURL(String historyServerRoot, String jobId) + throws MalformedURLException { + return new URL(getMapreduceJobsURLString(historyServerRoot) + "/" + jobId + "/tasks"); + } + + public static URL getTaskCounterURL(String historyServerRoot, String jobId, String taskId) + throws MalformedURLException { + return new URL(getMapreduceJobsURLString(historyServerRoot) + "/" + jobId + "/tasks/" + taskId + "/counters"); + } + + public static URL getTaskAttemptURL(String historyServerRoot, String jobId, String taskId, String attemptId) + throws MalformedURLException { + return new URL( + getMapreduceJobsURLString(historyServerRoot) + "/" + jobId + "/tasks/" + taskId + "/attempts/" + attemptId); + } +} diff --git a/app/controllers/Application.java b/app/controllers/Application.java index 6029e46c1..46ae6c496 100644 --- a/app/controllers/Application.java +++ b/app/controllers/Application.java @@ -1,5 +1,6 @@ package controllers; +import com.linkedin.drelephant.ElephantContext; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.text.ParseException; @@ -251,8 +252,7 @@ public static Result help() { //create a map to cache pages. private static void fillHelpPages() { logger.info("Loading help pages for pluggable heuristics"); - HeuristicConf conf = HeuristicConf.instance(); - List heuristicsConfList = conf.getHeuristicsConfData(); + List heuristicsConfList = ElephantContext.instance().getHeuristicsConfData(); for (HeuristicConfData heuristicConf : heuristicsConfList) { Class viewClass = null; String heuristicName = null; diff --git a/app/org/apache/spark/deploy/history/SparkDataCollection.scala b/app/org/apache/spark/deploy/history/SparkDataCollection.scala new file mode 100644 index 000000000..614d1428e --- /dev/null +++ b/app/org/apache/spark/deploy/history/SparkDataCollection.scala @@ -0,0 +1,278 @@ +package org.apache.spark.deploy.history + + +import com.linkedin.drelephant.analysis.ApplicationType +import com.linkedin.drelephant.spark.SparkExecutorData.ExecutorInfo +import com.linkedin.drelephant.spark.SparkJobProgressData.JobInfo +import com.linkedin.drelephant.spark._ +import org.apache.spark.scheduler.{StageInfo, ApplicationEventListener} +import org.apache.spark.storage.{StorageStatusTrackingListener, StorageStatus, RDDInfo, StorageStatusListener} +import org.apache.spark.ui.env.EnvironmentListener +import org.apache.spark.ui.exec.ExecutorsListener +import org.apache.spark.ui.jobs.JobProgressListener +import org.apache.spark.ui.storage.StorageListener + +import java.lang.{Long => JLong} +import java.util.{Map => JMap} +import java.util.{Set => JSet} +import java.util.{HashSet => JHashSet} +import java.util.{List => JList} +import java.util.{ArrayList => JArrayList} +import java.util.Properties + +import org.apache.spark.util.collection.OpenHashSet + + +/** + * This class wraps the logic of collecting the data in SparkEventListeners into the + * HadoopApplicationData instances. + * + * Notice: + * This has to live in Spark's scope because ApplicationEventListener is in private[spark] scope. And it is problematic + * to compile if written in Java. + * + * @author yizhou + */ +class SparkDataCollection(applicationEventListener: ApplicationEventListener, + jobProgressListener: JobProgressListener, + storageStatusListener: StorageStatusListener, + environmentListener: EnvironmentListener, + executorsListener: ExecutorsListener, + storageListener: StorageListener, + storageStatusTrackingListener: StorageStatusTrackingListener) extends SparkApplicationData { + private var _applicationData: SparkGeneralData = null; + private var _jobProgressData: SparkJobProgressData = null; + private var _environmentData: SparkEnvironmentData = null; + private var _executorData: SparkExecutorData = null; + private var _storageData: SparkStorageData = null; + + + import SparkDataCollection._ + + + override def getConf(): Properties = getEnvironmentData().getSparkProperties() + + override def getGeneralData(): SparkGeneralData = { + if (_applicationData == null) { + _applicationData = new SparkGeneralData() + + applicationEventListener.adminAcls match { + case Some(s: String) => { + _applicationData.setAdminAcls(stringToSet(s)) + } + case None => { + // do nothing + } + } + + applicationEventListener.viewAcls match { + case Some(s: String) => { + _applicationData.setViewAcls(stringToSet(s)) + } + case None => { + // do nothing + } + } + + applicationEventListener.appId match { + case Some(s: String) => { + _applicationData.setApplicationId(s) + } + case None => { + // do nothing + } + } + + applicationEventListener.appName match { + case Some(s: String) => { + _applicationData.setApplicationName(s) + } + case None => { + // do nothing + } + } + + applicationEventListener.sparkUser match { + case Some(s: String) => { + _applicationData.setSparkUser(s) + } + case None => { + // do nothing + } + } + + applicationEventListener.startTime match { + case Some(s: Long) => { + _applicationData.setStartTime(s) + } + case None => { + // do nothing + } + } + + applicationEventListener.endTime match { + case Some(s: Long) => { + _applicationData.setEndTime(s) + } + case None => { + // do nothing + } + } + } + _applicationData + } + + override def getEnvironmentData(): SparkEnvironmentData = { + if (_environmentData == null) { + // Notice: we ignore jvmInformation and classpathEntries, because they are less likely to be used by any analyzer. + _environmentData = new SparkEnvironmentData() + environmentListener.systemProperties.foreach { case (name, value) => + _environmentData.addSystemProperty(name, value) + } + environmentListener.sparkProperties.foreach { case (name, value) => + _environmentData.addSparkProperty(name, value) + } + } + _environmentData + } + + override def getExecutorData(): SparkExecutorData = { + if (_executorData == null) { + _executorData = new SparkExecutorData() + + for (statusId <- 0 until executorsListener.storageStatusList.size) { + val info = new ExecutorInfo() + + val status = executorsListener.storageStatusList(statusId) + + info.execId = status.blockManagerId.executorId + info.hostPort = status.blockManagerId.hostPort + info.rddBlocks = status.numBlocks + + // Use a customized listener to fetch the peak memory used, the data contained in status are + // the current used memory that is not useful in offline settings. + info.memUsed = storageStatusTrackingListener.executorIdToMaxUsedMem.getOrElse(info.execId, 0L) + info.maxMem = status.maxMem + info.diskUsed = status.diskUsed + info.activeTasks = executorsListener.executorToTasksActive.getOrElse(info.execId, 0) + info.failedTasks = executorsListener.executorToTasksFailed.getOrElse(info.execId, 0) + info.completedTasks = executorsListener.executorToTasksComplete.getOrElse(info.execId, 0) + info.totalTasks = info.activeTasks + info.failedTasks + info.completedTasks + info.duration = executorsListener.executorToDuration.getOrElse(info.execId, 0L) + info.inputBytes = executorsListener.executorToInputBytes.getOrElse(info.execId, 0L) + info.shuffleRead = executorsListener.executorToShuffleRead.getOrElse(info.execId, 0L) + info.shuffleWrite = executorsListener.executorToShuffleWrite.getOrElse(info.execId, 0L) + + _executorData.setExecutorInfo(info.execId, info) + } + } + _executorData + } + + override def getJobProgressData(): SparkJobProgressData = { + if (_jobProgressData == null) { + _jobProgressData = new SparkJobProgressData() + + // Add JobInfo + jobProgressListener.jobIdToData.foreach { case (id, data) => + val jobInfo = new JobInfo() + + jobInfo.jobId = data.jobId + jobInfo.jobGroup = data.jobGroup.getOrElse("") + jobInfo.numActiveStages = data.numActiveStages + jobInfo.numActiveTasks = data.numActiveTasks + jobInfo.numCompletedTasks = data.numCompletedTasks + jobInfo.numFailedStages = data.numFailedStages + jobInfo.numFailedTasks = data.numFailedTasks + jobInfo.numSkippedStages = data.numSkippedStages + jobInfo.numSkippedTasks = data.numSkippedTasks + jobInfo.numTasks = data.numTasks + + jobInfo.startTime = data.startTime.getOrElse(0) + jobInfo.endTime = data.endTime.getOrElse(0) + + data.stageIds.foreach{ case (id: Int) => jobInfo.addStageId(id)} + addIntSetToJSet(data.completedStageIndices, jobInfo.completedStageIndices) + + _jobProgressData.addJobInfo(id, jobInfo) + } + + // Add Stage Info + jobProgressListener.stageIdToData.foreach { case (id, data) => + val stageInfo = new SparkJobProgressData.StageInfo() + val sparkStageInfo = jobProgressListener.stageIdToInfo.get(id._1) + stageInfo.name = sparkStageInfo match { + case Some(info: StageInfo) => { + info.name + } + case None => { + "" + } + } + stageInfo.description = data.description.getOrElse("") + stageInfo.diskBytesSpilled = data.diskBytesSpilled + stageInfo.executorRunTime = data.executorRunTime + stageInfo.inputBytes = data.inputBytes + stageInfo.memoryBytesSpilled = data.memoryBytesSpilled + stageInfo.numActiveTasks = data.numActiveTasks + stageInfo.numCompleteTasks = data.numCompleteTasks + stageInfo.numFailedTasks = data.numFailedTasks + stageInfo.outputBytes = data.outputBytes + stageInfo.shuffleReadBytes = data.shuffleReadBytes + stageInfo.shuffleWriteBytes = data.shuffleWriteBytes + addIntSetToJSet(data.completedIndices, stageInfo.completedIndices) + + _jobProgressData.addStageInfo(id._1, id._2, stageInfo) + } + + // Add completed jobs + jobProgressListener.completedJobs.foreach { case (data) => _jobProgressData.addCompletedJob(data.jobId) } + // Add failed jobs + jobProgressListener.failedJobs.foreach { case (data) => _jobProgressData.addFailedJob(data.jobId) } + // Add completed stages + jobProgressListener.completedStages.foreach { case (data) => + _jobProgressData.addCompletedStages(data.stageId, data.attemptId) + } + // Add failed stages + jobProgressListener.failedStages.foreach { case (data) => + _jobProgressData.addFailedStages(data.stageId, data.attemptId) + } + } + _jobProgressData + } + + // This method returns a combined information from StorageStatusListener and StorageListener + override def getStorageData(): SparkStorageData = { + if (_storageData == null) { + _storageData = new SparkStorageData() + _storageData.setRddInfoList(toJList[RDDInfo](storageListener.rddInfoList)) + _storageData.setStorageStatusList(toJList[StorageStatus](storageStatusListener.storageStatusList)) + } + _storageData + } + + override def getUid: String = { + getGeneralData().getApplicationId + } +} + +object SparkDataCollection { + def stringToSet(str: String): JSet[String] = { + val set = new JHashSet[String]() + str.split(",").foreach { case t: String => set.add(t)} + set + } + + def toJList[T](seq: Seq[T]): JList[T] = { + val list = new JArrayList[T]() + seq.foreach { case (item: T) => list.add(item)} + list + } + + def addIntSetToJSet(set: OpenHashSet[Int], jset: JSet[Integer]): Unit = { + val it = set.iterator + while (it.hasNext) { + jset.add(it.next()) + } + } +} diff --git a/app/org/apache/spark/deploy/history/SparkFSFetcher.scala b/app/org/apache/spark/deploy/history/SparkFSFetcher.scala new file mode 100644 index 000000000..03872f839 --- /dev/null +++ b/app/org/apache/spark/deploy/history/SparkFSFetcher.scala @@ -0,0 +1,127 @@ +package org.apache.spark.deploy.history + + +import java.net.URI +import java.security.PrivilegedAction + +import com.linkedin.drelephant.HadoopSecurity +import com.linkedin.drelephant.analysis.ElephantFetcher +import com.linkedin.drelephant.spark.SparkApplicationData +import org.apache.hadoop.conf.Configuration +import org.apache.hadoop.fs.{Path, FileSystem, FileStatus} +import org.apache.hadoop.hdfs.web.WebHdfsFileSystem +import org.apache.log4j.Logger +import org.apache.spark.SparkConf +import org.apache.spark.scheduler.{EventLoggingListener, ReplayListenerBus, ApplicationEventListener} +import org.apache.spark.storage.{StorageStatusTrackingListener, StorageStatusListener} +import org.apache.spark.ui.env.EnvironmentListener +import org.apache.spark.ui.exec.ExecutorsListener +import org.apache.spark.ui.jobs.JobProgressListener +import org.apache.spark.ui.storage.StorageListener + + +/** + * A wrapper that replays Spark event history from files and then fill proper data objects. + * + * @author yizhou + */ +class SparkFSFetcher extends ElephantFetcher[SparkApplicationData] { + + + import SparkFSFetcher._ + + + /* Lazy loading for the log directory is very important. Hadoop Configuration() takes time to load itself to reflect + * properties in the configuration files. Triggering it too early will sometimes make the configuration object empty. + */ + private lazy val _logDir: String = { + val conf = new Configuration() + val nodeAddress = conf.get("dfs.namenode.http-address", null) + val hdfsAddress = if (nodeAddress == null) "" else "webhdfs://" + nodeAddress + + val uri = new URI(_sparkConf.get("spark.eventLog.dir", DEFAULT_LOG_DIR)) + System.out.println("logDIR: " + hdfsAddress + uri.getPath) + hdfsAddress + uri.getPath + } + + private val _sparkConf = new SparkConf() + private val _security = new HadoopSecurity() + + private lazy val fs: FileSystem = { + _security.checkLogin() + logger.info("Looking for spark logs at " + _logDir) + + if (new URI(_logDir).getHost == null) { + FileSystem.getLocal(new Configuration()) + } else { + val filesystem = new WebHdfsFileSystem() + filesystem.initialize(new URI(_logDir), new Configuration()) + filesystem + } + } + + def fetchData(appId: String): SparkApplicationData = { + _security.doAs[SparkDataCollection](new PrivilegedAction[SparkDataCollection] { + override def run(): SparkDataCollection = { + /* Most of Spark logs will be in directory structure: /LOG_DIR/[application_id]. + * + * Some logs (Spark 1.3+) are in /LOG_DIR/[application_id].snappy + * + * Currently we won't be able to parse them even we manually set up the codec. There is problem + * in JsonProtocol#sparkEventFromJson that it does not handle unmatched SparkListenerEvent, which means + * it is only backward compatible but not forward. And switching the dependency to Spark 1.3 will raise more + * problems due to the fact that we are touching the internal codes. + * + * In short, this fetcher only works with Spark <=1.2, and we should switch to JSON endpoints with Spark's + * future release. + */ + val replayBus = createReplayBus(fs.getFileStatus(new Path(_logDir, appId))) + val applicationEventListener = new ApplicationEventListener + val jobProgressListener = new JobProgressListener(new SparkConf()) + val environmentListener = new EnvironmentListener + val storageStatusListener = new StorageStatusListener + val executorsListener = new ExecutorsListener(storageStatusListener) + val storageListener = new StorageListener(storageStatusListener) + + // This is a customized listener that tracks peak used memory + // The original listener only tracks the current in use memory which is useless in offline scenario. + val storageStatusTrackingListener = new StorageStatusTrackingListener() + replayBus.addListener(storageStatusTrackingListener) + + val dataCollection = new SparkDataCollection(applicationEventListener = applicationEventListener, + jobProgressListener = jobProgressListener, + environmentListener = environmentListener, + storageStatusListener = storageStatusListener, + executorsListener = executorsListener, + storageListener = storageListener, + storageStatusTrackingListener = storageStatusTrackingListener) + + replayBus.addListener(applicationEventListener) + replayBus.addListener(jobProgressListener) + replayBus.addListener(environmentListener) + replayBus.addListener(storageStatusListener) + replayBus.addListener(executorsListener) + replayBus.addListener(storageListener) + + replayBus.replay() + + dataCollection + } + }) + } + + private def createReplayBus(logDir: FileStatus): ReplayListenerBus = { + val path = logDir.getPath() + val elogInfo = EventLoggingListener.parseLoggingInfo(path, fs) + new ReplayListenerBus(elogInfo.logPaths, fs, elogInfo.compressionCodec) + } + + override def init(threadId: Int): Unit = { + // do nothing + } +} + +private object SparkFSFetcher { + private val logger = Logger.getLogger(SparkFSFetcher.getClass) + val DEFAULT_LOG_DIR = "/system/spark-history" +} diff --git a/app/org/apache/spark/storage/StorageStatusTrackingListener.scala b/app/org/apache/spark/storage/StorageStatusTrackingListener.scala new file mode 100644 index 000000000..87c83c753 --- /dev/null +++ b/app/org/apache/spark/storage/StorageStatusTrackingListener.scala @@ -0,0 +1,89 @@ +package org.apache.spark.storage + +import scala.collection.mutable + +import org.apache.spark.SparkContext +import org.apache.spark.annotation.DeveloperApi +import org.apache.spark.scheduler._ + +/** + * :: DeveloperApi :: + * A modified version of StorageStatusListener that tracks the peak memory usage during the entire application runtime. + * + * NOTICE: this class copies StorageStatusListener's codes instead of extending from it. + */ +@DeveloperApi +class StorageStatusTrackingListener extends SparkListener { + // This maintains only blocks that are cached (i.e. storage level is not StorageLevel.NONE) + private[storage] val executorIdToStorageStatus = mutable.Map[String, StorageStatus]() + + def storageStatusList = executorIdToStorageStatus.values.toSeq + + val executorIdToMaxUsedMem = mutable.Map[String, Long]() + + /** Update storage status list to reflect updated block statuses */ + private def updateStorageStatus(execId: String, updatedBlocks: Seq[(BlockId, BlockStatus)]) { + executorIdToStorageStatus.get(execId).foreach { storageStatus => + updatedBlocks.foreach { case (blockId, updatedStatus) => + if (updatedStatus.storageLevel == StorageLevel.NONE) { + storageStatus.removeBlock(blockId) + } else { + storageStatus.updateBlock(blockId, updatedStatus) + } + } + } + updateUsedMem() + } + + /** Update storage status list to reflect the removal of an RDD from the cache */ + private def updateStorageStatus(unpersistedRDDId: Int) { + storageStatusList.foreach { storageStatus => + storageStatus.rddBlocksById(unpersistedRDDId).foreach { case (blockId, _) => + storageStatus.removeBlock(blockId) + } + } + updateUsedMem() + } + + private def updateUsedMem(): Unit = { + executorIdToStorageStatus.foreach { case (execId, storageStatus) => + val currentMemUsed = storageStatus.memUsed + if (currentMemUsed > executorIdToMaxUsedMem.getOrElse(execId, 0L)) { + executorIdToMaxUsedMem(execId) = currentMemUsed + } + } + } + + override def onTaskEnd(taskEnd: SparkListenerTaskEnd) = synchronized { + val info = taskEnd.taskInfo + val metrics = taskEnd.taskMetrics + if (info != null && metrics != null) { + val updatedBlocks = metrics.updatedBlocks.getOrElse(Seq[(BlockId, BlockStatus)]()) + if (updatedBlocks.length > 0) { + updateStorageStatus(info.executorId, updatedBlocks) + } + } + } + + override def onUnpersistRDD(unpersistRDD: SparkListenerUnpersistRDD) = synchronized { + updateStorageStatus(unpersistRDD.rddId) + } + + override def onBlockManagerAdded(blockManagerAdded: SparkListenerBlockManagerAdded) { + synchronized { + val blockManagerId = blockManagerAdded.blockManagerId + val executorId = blockManagerId.executorId + val maxMem = blockManagerAdded.maxMem + val storageStatus = new StorageStatus(blockManagerId, maxMem) + executorIdToStorageStatus(executorId) = storageStatus + } + } + + override def onBlockManagerRemoved(blockManagerRemoved: SparkListenerBlockManagerRemoved) { + synchronized { + val executorId = blockManagerRemoved.blockManagerId.executorId + executorIdToStorageStatus.remove(executorId) + } + } + +} diff --git a/app/views/help.scala.html b/app/views/help.scala.html index 28f02bfa2..d3ea5131e 100644 --- a/app/views/help.scala.html +++ b/app/views/help.scala.html @@ -3,10 +3,10 @@ @main("Dr. Elephant", "help") { @tags.column(3) { @tags.panel(){ Topics } { - @for(heuristic <- com.linkedin.drelephant.ElephantAnalyser.instance().getHeuristicNames()) { + @for(heuristic <- com.linkedin.drelephant.ElephantContext.instance().getAllHeuristicNames()) { @heuristic
} - @com.linkedin.drelephant.ElephantAnalyser.NO_DATA
+ @com.linkedin.drelephant.analysis.HeuristicResult.NO_DATA.getAnalysis
} } @tags.column(9) { diff --git a/app/views/helpSparkBestProperties.scala.html b/app/views/helpSparkBestProperties.scala.html new file mode 100644 index 000000000..5b9bbcf4b --- /dev/null +++ b/app/views/helpSparkBestProperties.scala.html @@ -0,0 +1,6 @@ +

There are some spark property settings that are known to be good choices in most of the scenerios. You better have +a good reason to not use some of the settings.

+

Suggestions

+

+ Use KryoSerializer. +

diff --git a/app/views/helpSparkMemoryLimit.scala.html b/app/views/helpSparkMemoryLimit.scala.html new file mode 100644 index 000000000..26d3e8e49 --- /dev/null +++ b/app/views/helpSparkMemoryLimit.scala.html @@ -0,0 +1,9 @@ +

The total memory limit of a Spark job is XXX. You really, really want to be clear of that limit or your job may + occupy too many resources that slow down the entire cluster. Also we keep track of the peak + memory used for storage, normally the memory utilization rate should be higher than [a number]. Otherwise, it means + you don't the memory you claimed to need. +

+

Suggestions

+

+ Make memory utilization rate higher than 0.5. +

diff --git a/app/views/search.scala.html b/app/views/search.scala.html index f7eb96015..ec9304bf9 100644 --- a/app/views/search.scala.html +++ b/app/views/search.scala.html @@ -21,7 +21,7 @@
@@ -39,7 +39,7 @@
diff --git a/build.sbt b/build.sbt index 8bc81247e..9d5c04892 100644 --- a/build.sbt +++ b/build.sbt @@ -18,7 +18,13 @@ libraryDependencies ++= Seq( "org.jsoup" % "jsoup" % "1.7.3", // The following two dependencies are pulled in by the metrics library. "org.apache.avro" % "avro" % "1.4.0", - "com.linkedin.avro-schemas" % "avro-schemas-tracking" % "6.0.518" + "com.linkedin.avro-schemas" % "avro-schemas-tracking" % "6.0.518", + // Spark dependencies, exclude avro transitive dependencies to avoid being overriden by 1.7.x + "org.apache.spark" % "spark-core_2.10" % "1.2.0" excludeAll( + ExclusionRule(organization = "org.apache.avro") + ), + // Hadoop defaultly are using guava 11.0, might raise NoSuchMethodException + "com.google.guava" % "guava" % "18.0" ) ivyConfigurations += config("compileonly").hide diff --git a/conf/HeuristicConf.xml b/conf/HeuristicConf.xml deleted file mode 100644 index ac5361ee9..000000000 --- a/conf/HeuristicConf.xml +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - Mapper Data Skew - com.linkedin.drelephant.analysis.heuristics.MapperDataSkewHeuristic - views.html.helpMapperDataSkew - - classic - yarn - - - - - Mapper Input Size - com.linkedin.drelephant.analysis.heuristics.MapperInputSizeHeuristic - views.html.helpMapperInputSize - - classic - yarn - - - - - Mapper Speed - com.linkedin.drelephant.analysis.heuristics.MapperSpeedHeuristic - views.html.helpMapperSpeed - - classic - yarn - - - - - Mapper Spill - com.linkedin.drelephant.analysis.heuristics.MapperSpillHeuristic - views.html.helpMapperSpill - - - - - - Reducer Data Skew - com.linkedin.drelephant.analysis.heuristics.ReducerDataSkewHeuristic - views.html.helpReducerDataSkew - - classic - yarn - - - - - Reducer Time - com.linkedin.drelephant.analysis.heuristics.ReducerTimeHeuristic - views.html.helpReducerTime - - classic - yarn - - - - - Shuffle & Sort - com.linkedin.drelephant.analysis.heuristics.ShuffleSortHeuristic - views.html.helpShuffleSort - - classic - yarn - - - - - Job Queue Limit - com.linkedin.drelephant.analysis.heuristics.JobQueueLimitHeuristic - views.html.helpJobQueueLimit - - classic - - - - diff --git a/conf/application.conf b/conf/application.conf index db166e9d0..fe852e5b2 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -77,6 +77,20 @@ smtp.from="azkaban-noreply@linkedin.com" # smtp.user=azkaban-noreply # smtp.password= +# TODO: JUST FOR REFERENCE RIGHT NOW, THESE SHOULD BE NOT CHECK THESE INTO CODE BASE +# Dali metrics publisher +metrics.publisher-conf=conf/CounterPublisherConf.xml + #Kerberos -keytab.location="/export/apps/hadoop/keytabs/dr_elephant-service.keytab" -keytab.user="elephant/eat1-magicaz01.grid.linkedin.com" \ No newline at end of file +keytab.location=/home/yizhou/Dev/dr-elephant-test/yizhou.keytab +keytab.user=yizhou + + +spark.authenticate=true +spark.ui.acls.enable=true +spark.logConf=true +spark.yarn.historyServer.address="eat1-nertzjh01.grid.linkedin.com:18080" +spark.eventLog.enabled=true +spark.eventLog.compress=true +spark.eventLog.dir="hdfs://eat1-nertznn01.grid.linkedin.com:9000/system/spark-history" +spark.ui.port=0 diff --git a/conf/elephant-conf.xml b/conf/elephant-conf.xml new file mode 100644 index 000000000..11afa345f --- /dev/null +++ b/conf/elephant-conf.xml @@ -0,0 +1,168 @@ + + + + + + + mapreduce + classic + com.linkedin.drelephant.mapreduce.MapreduceFetcherClassic + + + mapreduce + yarn + com.linkedin.drelephant.mapreduce.MapreduceFetcherYarn + + + spark + yarn + org.apache.spark.deploy.history.SparkFSFetcher + + > + + + + + mapreduce + Mapper Data Skew + com.linkedin.drelephant.mapreduce.heuristics.MapperDataSkewHeuristic + views.html.helpMapperDataSkew + + classic + yarn + + + + + mapreduce + Mapper Input Size + com.linkedin.drelephant.mapreduce.heuristics.MapperInputSizeHeuristic + views.html.helpMapperInputSize + + classic + yarn + + + + + mapreduce + Mapper Speed + com.linkedin.drelephant.mapreduce.heuristics.MapperSpeedHeuristic + views.html.helpMapperSpeed + + classic + yarn + + + + + mapreduce + Mapper Spill + com.linkedin.drelephant.mapreduce.heuristics.MapperSpillHeuristic + views.html.helpMapperSpill + + + + + + mapreduce + Reducer Data Skew + com.linkedin.drelephant.mapreduce.heuristics.ReducerDataSkewHeuristic + views.html.helpReducerDataSkew + + classic + yarn + + + + + mapreduce + Reducer Time + com.linkedin.drelephant.mapreduce.heuristics.ReducerTimeHeuristic + views.html.helpReducerTime + + classic + yarn + + + + + mapreduce + Shuffle & Sort + com.linkedin.drelephant.mapreduce.heuristics.ShuffleSortHeuristic + views.html.helpShuffleSort + + classic + yarn + + + + + mapreduce + Job Queue Limit + com.linkedin.drelephant.mapreduce.heuristics.JobQueueLimitHeuristic + views.html.helpJobQueueLimit + + classic + + + + + spark + Spark Configuration Best Practice + com.linkedin.drelephant.spark.heuristics.BestPropertiesConventionHeuristic + + views.html.helpSparkBestProperties + + yarn + + + + + spark + Spark Memory Limit + com.linkedin.drelephant.spark.heuristics.MemoryLimitHeuristic + + views.html.helpSparkMemoryLimit + + yarn + + + + + spark + Spark Stage Runtime + com.linkedin.drelephant.spark.heuristics.StageRuntimeHeuristic + + views.html.helpSparkMemoryLimit + + yarn + + + + + spark + Spark Job Runtime + com.linkedin.drelephant.spark.heuristics.JobRuntimeHeuristic + + views.html.helpSparkMemoryLimit + + yarn + + + + + spark + Spark Executor Load Balance + com.linkedin.drelephant.spark.heuristics.ExecutorLoadHeuristic + + views.html.helpSparkMemoryLimit + + yarn + + + + + diff --git a/conf/JobType.xml b/conf/job-types.xml similarity index 90% rename from conf/JobType.xml rename to conf/job-types.xml index c1924ff74..caa9ee899 100644 --- a/conf/JobType.xml +++ b/conf/job-types.xml @@ -1,19 +1,23 @@ + + Spark + spark.app.id + Pig pig.script diff --git a/test/com/linkedin/drelephant/analysis/heuristics/JobQueueLimitHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/JobQueueLimitHeuristicTest.java similarity index 82% rename from test/com/linkedin/drelephant/analysis/heuristics/JobQueueLimitHeuristicTest.java rename to test/com/linkedin/drelephant/mapreduce/heuristics/JobQueueLimitHeuristicTest.java index baa74054f..ad2fa7fc4 100644 --- a/test/com/linkedin/drelephant/analysis/heuristics/JobQueueLimitHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/JobQueueLimitHeuristicTest.java @@ -1,5 +1,6 @@ -package com.linkedin.drelephant.analysis.heuristics; +package com.linkedin.drelephant.mapreduce.heuristics; +import com.linkedin.drelephant.mapreduce.heuristics.JobQueueLimitHeuristic; import java.io.IOException; import java.util.Properties; @@ -11,9 +12,9 @@ import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.hadoop.HadoopCounterHolder; -import com.linkedin.drelephant.hadoop.HadoopJobData; -import com.linkedin.drelephant.hadoop.HadoopTaskData; +import com.linkedin.drelephant.mapreduce.HadoopCounterHolder; +import com.linkedin.drelephant.mapreduce.MapreduceApplicationData; +import com.linkedin.drelephant.mapreduce.HadoopTaskData; public class JobQueueLimitHeuristicTest extends TestCase { @@ -59,8 +60,8 @@ private Severity analyzeJob(long runtimeMs, String queueName) throws IOException for (i = 0; i < NUM_TASKS / 3; i++) { reducers[i] = new HadoopTaskData(dummyCounter, new long[] { runtimeMs, 0, 0 }); } - HadoopJobData data = - new HadoopJobData().setCounters(dummyCounter).setReducerData(reducers).setMapperData(mappers) + MapreduceApplicationData data = + new MapreduceApplicationData().setCounters(dummyCounter).setReducerData(reducers).setMapperData(mappers) .setJobConf(jobConf); HeuristicResult result = _heuristic.apply(data); return result.getSeverity(); diff --git a/test/com/linkedin/drelephant/analysis/heuristics/MapperDataSkewHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperDataSkewHeuristicTest.java similarity index 84% rename from test/com/linkedin/drelephant/analysis/heuristics/MapperDataSkewHeuristicTest.java rename to test/com/linkedin/drelephant/mapreduce/heuristics/MapperDataSkewHeuristicTest.java index 08c58947f..dc94b4b89 100644 --- a/test/com/linkedin/drelephant/analysis/heuristics/MapperDataSkewHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperDataSkewHeuristicTest.java @@ -1,12 +1,13 @@ -package com.linkedin.drelephant.analysis.heuristics; +package com.linkedin.drelephant.mapreduce.heuristics; import com.linkedin.drelephant.analysis.Constants; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.hadoop.HadoopCounterHolder; -import com.linkedin.drelephant.hadoop.HadoopJobData; -import com.linkedin.drelephant.hadoop.HadoopTaskData; +import com.linkedin.drelephant.mapreduce.HadoopCounterHolder; +import com.linkedin.drelephant.mapreduce.MapreduceApplicationData; +import com.linkedin.drelephant.mapreduce.HadoopTaskData; +import com.linkedin.drelephant.mapreduce.heuristics.MapperDataSkewHeuristic; import java.io.IOException; import junit.framework.TestCase; @@ -63,7 +64,7 @@ private Severity analyzeJob(int numSmallTasks, int numLargeTasks, long smallInpu mappers[i] = new HadoopTaskData(largeCounter, new long[3]); } - HadoopJobData data = new HadoopJobData().setCounters(jobCounter).setMapperData(mappers); + MapreduceApplicationData data = new MapreduceApplicationData().setCounters(jobCounter).setMapperData(mappers); HeuristicResult result = _heuristic.apply(data); return result.getSeverity(); diff --git a/test/com/linkedin/drelephant/analysis/heuristics/MapperInputSizeHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperInputSizeHeuristicTest.java similarity index 93% rename from test/com/linkedin/drelephant/analysis/heuristics/MapperInputSizeHeuristicTest.java rename to test/com/linkedin/drelephant/mapreduce/heuristics/MapperInputSizeHeuristicTest.java index 16b8a44a1..6488871a4 100644 --- a/test/com/linkedin/drelephant/analysis/heuristics/MapperInputSizeHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperInputSizeHeuristicTest.java @@ -1,12 +1,13 @@ -package com.linkedin.drelephant.analysis.heuristics; +package com.linkedin.drelephant.mapreduce.heuristics; import com.linkedin.drelephant.analysis.Constants; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.hadoop.HadoopCounterHolder; -import com.linkedin.drelephant.hadoop.HadoopJobData; -import com.linkedin.drelephant.hadoop.HadoopTaskData; +import com.linkedin.drelephant.mapreduce.HadoopCounterHolder; +import com.linkedin.drelephant.mapreduce.MapreduceApplicationData; +import com.linkedin.drelephant.mapreduce.HadoopTaskData; +import com.linkedin.drelephant.mapreduce.heuristics.MapperInputSizeHeuristic; import com.linkedin.drelephant.math.Statistics; import java.io.IOException; import junit.framework.TestCase; @@ -149,7 +150,7 @@ private Severity analyzeJob(int numTasks, long inputSize, long runtime) throws I mappers[i] = new HadoopTaskData(taskCounter, new long[] { runtime, 0, 0 }); } - HadoopJobData data = new HadoopJobData().setCounters(jobCounter).setMapperData(mappers); + MapreduceApplicationData data = new MapreduceApplicationData().setCounters(jobCounter).setMapperData(mappers); HeuristicResult result = _heuristic.apply(data); return result.getSeverity(); } diff --git a/test/com/linkedin/drelephant/analysis/heuristics/MapperSpeedHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristicTest.java similarity index 82% rename from test/com/linkedin/drelephant/analysis/heuristics/MapperSpeedHeuristicTest.java rename to test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristicTest.java index e20209d59..5f8c10f5b 100644 --- a/test/com/linkedin/drelephant/analysis/heuristics/MapperSpeedHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristicTest.java @@ -1,12 +1,13 @@ -package com.linkedin.drelephant.analysis.heuristics; +package com.linkedin.drelephant.mapreduce.heuristics; import com.linkedin.drelephant.analysis.Constants; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.hadoop.HadoopCounterHolder; -import com.linkedin.drelephant.hadoop.HadoopJobData; -import com.linkedin.drelephant.hadoop.HadoopTaskData; +import com.linkedin.drelephant.mapreduce.HadoopCounterHolder; +import com.linkedin.drelephant.mapreduce.MapreduceApplicationData; +import com.linkedin.drelephant.mapreduce.HadoopTaskData; +import com.linkedin.drelephant.mapreduce.heuristics.MapperSpeedHeuristic; import com.linkedin.drelephant.math.Statistics; import java.io.IOException; import junit.framework.TestCase; @@ -54,7 +55,7 @@ private Severity analyzeJob(long runtimeMs, long readBytes) throws IOException { mappers[i] = new HadoopTaskData(counter, new long[] { runtimeMs, 0, 0 }); } - HadoopJobData data = new HadoopJobData().setCounters(jobCounter).setMapperData(mappers); + MapreduceApplicationData data = new MapreduceApplicationData().setCounters(jobCounter).setMapperData(mappers); HeuristicResult result = _heuristic.apply(data); return result.getSeverity(); } diff --git a/test/com/linkedin/drelephant/analysis/heuristics/MapperSpillHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristicTest.java similarity index 79% rename from test/com/linkedin/drelephant/analysis/heuristics/MapperSpillHeuristicTest.java rename to test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristicTest.java index 7f46d8adc..b423993fb 100644 --- a/test/com/linkedin/drelephant/analysis/heuristics/MapperSpillHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristicTest.java @@ -1,12 +1,13 @@ -package com.linkedin.drelephant.analysis.heuristics; +package com.linkedin.drelephant.mapreduce.heuristics; import com.linkedin.drelephant.analysis.Constants; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.hadoop.HadoopCounterHolder; -import com.linkedin.drelephant.hadoop.HadoopJobData; -import com.linkedin.drelephant.hadoop.HadoopTaskData; +import com.linkedin.drelephant.mapreduce.HadoopCounterHolder; +import com.linkedin.drelephant.mapreduce.MapreduceApplicationData; +import com.linkedin.drelephant.mapreduce.HadoopTaskData; +import com.linkedin.drelephant.mapreduce.heuristics.MapperSpillHeuristic; import java.io.IOException; import junit.framework.TestCase; @@ -48,7 +49,7 @@ private Severity analyzeJob(long spilledRecords, long mapRecords) throws IOExcep mappers[i] = new HadoopTaskData(counter, new long[] { 0, 5, 5, 5 }); } - HadoopJobData data = new HadoopJobData().setCounters(jobCounter).setMapperData(mappers); + MapreduceApplicationData data = new MapreduceApplicationData().setCounters(jobCounter).setMapperData(mappers); HeuristicResult result = heuristic.apply(data); return result.getSeverity(); } diff --git a/test/com/linkedin/drelephant/analysis/heuristics/ReducerDataSkewHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerDataSkewHeuristicTest.java similarity index 84% rename from test/com/linkedin/drelephant/analysis/heuristics/ReducerDataSkewHeuristicTest.java rename to test/com/linkedin/drelephant/mapreduce/heuristics/ReducerDataSkewHeuristicTest.java index 97a7f51a9..a4a7f80f1 100644 --- a/test/com/linkedin/drelephant/analysis/heuristics/ReducerDataSkewHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerDataSkewHeuristicTest.java @@ -1,12 +1,13 @@ -package com.linkedin.drelephant.analysis.heuristics; +package com.linkedin.drelephant.mapreduce.heuristics; import com.linkedin.drelephant.analysis.Constants; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.hadoop.HadoopCounterHolder; -import com.linkedin.drelephant.hadoop.HadoopJobData; -import com.linkedin.drelephant.hadoop.HadoopTaskData; +import com.linkedin.drelephant.mapreduce.HadoopCounterHolder; +import com.linkedin.drelephant.mapreduce.MapreduceApplicationData; +import com.linkedin.drelephant.mapreduce.HadoopTaskData; +import com.linkedin.drelephant.mapreduce.heuristics.ReducerDataSkewHeuristic; import java.io.IOException; import junit.framework.TestCase; @@ -62,7 +63,7 @@ private Severity analyzeJob(int numSmallTasks, int numLargeTasks, long smallInpu reducers[i] = new HadoopTaskData(largeCounter, new long[3]); } - HadoopJobData data = new HadoopJobData().setCounters(jobCounter).setReducerData(reducers); + MapreduceApplicationData data = new MapreduceApplicationData().setCounters(jobCounter).setReducerData(reducers); HeuristicResult result = _heuristic.apply(data); return result.getSeverity(); } diff --git a/test/com/linkedin/drelephant/analysis/heuristics/ReducerTimeHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristicTest.java similarity index 83% rename from test/com/linkedin/drelephant/analysis/heuristics/ReducerTimeHeuristicTest.java rename to test/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristicTest.java index 7eeae2801..fe8d78b1a 100644 --- a/test/com/linkedin/drelephant/analysis/heuristics/ReducerTimeHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristicTest.java @@ -1,13 +1,14 @@ -package com.linkedin.drelephant.analysis.heuristics; +package com.linkedin.drelephant.mapreduce.heuristics; +import com.linkedin.drelephant.mapreduce.heuristics.ReducerTimeHeuristic; import java.io.IOException; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.hadoop.HadoopCounterHolder; -import com.linkedin.drelephant.hadoop.HadoopJobData; -import com.linkedin.drelephant.hadoop.HadoopTaskData; +import com.linkedin.drelephant.mapreduce.HadoopCounterHolder; +import com.linkedin.drelephant.mapreduce.MapreduceApplicationData; +import com.linkedin.drelephant.mapreduce.HadoopTaskData; import com.linkedin.drelephant.math.Statistics; import junit.framework.TestCase; @@ -66,7 +67,7 @@ private Severity analyzeJob(long runtimeMs, int numTasks) throws IOException { reducers[i] = new HadoopTaskData(dummyCounter, new long[] { runtimeMs, 0, 0 }); } - HadoopJobData data = new HadoopJobData().setCounters(dummyCounter).setReducerData(reducers); + MapreduceApplicationData data = new MapreduceApplicationData().setCounters(dummyCounter).setReducerData(reducers); HeuristicResult result = _heuristic.apply(data); return result.getSeverity(); } diff --git a/test/com/linkedin/drelephant/analysis/heuristics/ShuffleSortHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristicTest.java similarity index 86% rename from test/com/linkedin/drelephant/analysis/heuristics/ShuffleSortHeuristicTest.java rename to test/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristicTest.java index b528d22e8..863b23e01 100644 --- a/test/com/linkedin/drelephant/analysis/heuristics/ShuffleSortHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristicTest.java @@ -1,14 +1,15 @@ -package com.linkedin.drelephant.analysis.heuristics; +package com.linkedin.drelephant.mapreduce.heuristics; +import com.linkedin.drelephant.mapreduce.heuristics.ShuffleSortHeuristic; import java.io.IOException; import com.linkedin.drelephant.analysis.Constants; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.hadoop.HadoopCounterHolder; -import com.linkedin.drelephant.hadoop.HadoopJobData; -import com.linkedin.drelephant.hadoop.HadoopTaskData; +import com.linkedin.drelephant.mapreduce.HadoopCounterHolder; +import com.linkedin.drelephant.mapreduce.MapreduceApplicationData; +import com.linkedin.drelephant.mapreduce.HadoopTaskData; import com.linkedin.drelephant.math.Statistics; import junit.framework.TestCase; @@ -76,7 +77,7 @@ private Severity analyzeJob(long shuffleTimeMs, long sortTimeMs, long reduceTime reducers[i] = new HadoopTaskData(dummyCounter, new long[] { shuffleTimeMs + sortTimeMs + reduceTimeMs, shuffleTimeMs, sortTimeMs }); } - HadoopJobData data = new HadoopJobData().setCounters(dummyCounter).setReducerData(reducers); + MapreduceApplicationData data = new MapreduceApplicationData().setCounters(dummyCounter).setReducerData(reducers); HeuristicResult result = _heuristic.apply(data); return result.getSeverity(); } diff --git a/test/com/linkedin/drelephant/spark/MockSparkApplicationData.java b/test/com/linkedin/drelephant/spark/MockSparkApplicationData.java new file mode 100644 index 000000000..f8476ee4a --- /dev/null +++ b/test/com/linkedin/drelephant/spark/MockSparkApplicationData.java @@ -0,0 +1,61 @@ +package com.linkedin.drelephant.spark; + +import com.linkedin.drelephant.analysis.ApplicationType; +import java.util.Properties; + + +/** + * This is a pseudo local implementation of SparkApplicationData interface, supposed to be used for test purpose. + * + * @author yizhou + */ +public class MockSparkApplicationData implements SparkApplicationData { + private final SparkGeneralData _sparkGeneralData; + private final SparkEnvironmentData _sparkEnvironmentData; + private final SparkExecutorData _sparkExecutorData; + private final SparkJobProgressData _sparkJobProgressData; + private final SparkStorageData _sparkStorageData; + + public MockSparkApplicationData() { + _sparkGeneralData = new SparkGeneralData(); + _sparkEnvironmentData = new SparkEnvironmentData(); + _sparkExecutorData = new SparkExecutorData(); + _sparkJobProgressData = new SparkJobProgressData(); + _sparkStorageData = new SparkStorageData(); + } + + @Override + public SparkGeneralData getGeneralData() { + return _sparkGeneralData; + } + + @Override + public SparkEnvironmentData getEnvironmentData() { + return _sparkEnvironmentData; + } + + @Override + public SparkExecutorData getExecutorData() { + return _sparkExecutorData; + } + + @Override + public SparkJobProgressData getJobProgressData() { + return _sparkJobProgressData; + } + + @Override + public SparkStorageData getStorageData() { + return _sparkStorageData; + } + + @Override + public Properties getConf() { + return getEnvironmentData().getSparkProperties(); + } + + @Override + public String getUid() { + return getGeneralData().getApplicationId(); + } +} diff --git a/test/com/linkedin/drelephant/spark/heuristics/BestPropertiesConventionHeuristicTest.java b/test/com/linkedin/drelephant/spark/heuristics/BestPropertiesConventionHeuristicTest.java new file mode 100644 index 000000000..1fae1bf87 --- /dev/null +++ b/test/com/linkedin/drelephant/spark/heuristics/BestPropertiesConventionHeuristicTest.java @@ -0,0 +1,69 @@ +package com.linkedin.drelephant.spark.heuristics; + +import com.linkedin.drelephant.analysis.Severity; +import com.linkedin.drelephant.spark.MockSparkApplicationData; +import com.linkedin.drelephant.spark.SparkApplicationData; +import java.util.Properties; +import junit.framework.TestCase; + +import static com.linkedin.drelephant.spark.heuristics.BestPropertiesConventionHeuristic.SPARK_DRIVER_MEMORY; +import static com.linkedin.drelephant.spark.heuristics.BestPropertiesConventionHeuristic.SPARK_EXECUTOR_CORES; +import static com.linkedin.drelephant.spark.heuristics.BestPropertiesConventionHeuristic.SPARK_SERIALIZER; +import static com.linkedin.drelephant.spark.heuristics.BestPropertiesConventionHeuristic.SPARK_SHUFFLE_MANAGER; + + +/** + * This class test the BestPropertiesConventionHeuristic + * + */ +public class BestPropertiesConventionHeuristicTest extends TestCase { + public void testPropertiesCheck() { + assertEquals(analyzeJob(getDefaultGoodProperteis()), Severity.NONE); + + assertEquals(Severity.MODERATE, analyzeJob(getPropertiesAndOverideOne(SPARK_DRIVER_MEMORY, "7G"))); + assertEquals(Severity.CRITICAL, analyzeJob(getPropertiesAndOverideOne(SPARK_DRIVER_MEMORY, "8G"))); + assertEquals(Severity.CRITICAL, analyzeJob(getPropertiesAndOverideOne(SPARK_DRIVER_MEMORY, "9G"))); + + assertEquals(Severity.NONE, analyzeJob(getPropertiesAndOverideOne(SPARK_EXECUTOR_CORES, "1"))); + assertEquals(Severity.CRITICAL, analyzeJob(getPropertiesAndOverideOne(SPARK_EXECUTOR_CORES, "2"))); + + assertEquals(Severity.MODERATE, analyzeJob(getPropertiesAndOverideOne(SPARK_SERIALIZER, "foo"))); + assertEquals(Severity.MODERATE, analyzeJob(getPropertiesAndOverideOne(SPARK_SHUFFLE_MANAGER, "hash"))); + } + + public void testNullSettings() { + assertEquals(Severity.MODERATE, analyzeJob(getPropertiesAndRemove(SPARK_SERIALIZER))); + assertEquals(Severity.NONE, analyzeJob(getPropertiesAndRemove(SPARK_SHUFFLE_MANAGER))); + assertEquals(Severity.NONE, analyzeJob(getPropertiesAndRemove(SPARK_EXECUTOR_CORES))); + } + + private static Properties getDefaultGoodProperteis() { + Properties properties = new Properties(); + properties.put(SPARK_DRIVER_MEMORY, "1G"); + properties.put(SPARK_EXECUTOR_CORES, "1"); + properties.put(SPARK_SERIALIZER, "org.apache.spark.serializer.KryoSerializer"); + properties.put(SPARK_SHUFFLE_MANAGER, "sort"); + + return properties; + } + + private static Properties getPropertiesAndOverideOne(String key, String value) { + Properties properties = getDefaultGoodProperteis(); + properties.put(key, value); + return properties; + } + + private static Properties getPropertiesAndRemove(String key) { + Properties properties = getDefaultGoodProperteis(); + properties.remove(key); + return properties; + } + + private Severity analyzeJob(Properties sparkProperties) { + SparkApplicationData data = new MockSparkApplicationData(); + for (String key : sparkProperties.stringPropertyNames()) { + data.getEnvironmentData().addSparkProperty(key, sparkProperties.getProperty(key)); + } + return new BestPropertiesConventionHeuristic().apply(data).getSeverity(); + } +} diff --git a/test/com/linkedin/drelephant/spark/heuristics/JobRuntimeHeuristicTest.java b/test/com/linkedin/drelephant/spark/heuristics/JobRuntimeHeuristicTest.java new file mode 100644 index 000000000..0aa346a1a --- /dev/null +++ b/test/com/linkedin/drelephant/spark/heuristics/JobRuntimeHeuristicTest.java @@ -0,0 +1,14 @@ +package com.linkedin.drelephant.spark.heuristics; + +import junit.framework.TestCase; + + +/** + * This class tests JobRuntimeHeuristic + */ +public class JobRuntimeHeuristicTest extends TestCase { + + public void test() { + + } +} diff --git a/test/com/linkedin/drelephant/spark/heuristics/MemoryLimitHeuristicTest.java b/test/com/linkedin/drelephant/spark/heuristics/MemoryLimitHeuristicTest.java new file mode 100644 index 000000000..e1feafccf --- /dev/null +++ b/test/com/linkedin/drelephant/spark/heuristics/MemoryLimitHeuristicTest.java @@ -0,0 +1,119 @@ +package com.linkedin.drelephant.spark.heuristics; + +import com.linkedin.drelephant.analysis.HeuristicResult; +import com.linkedin.drelephant.analysis.Severity; +import com.linkedin.drelephant.spark.MockSparkApplicationData; +import com.linkedin.drelephant.spark.SparkApplicationData; +import com.linkedin.drelephant.spark.SparkEnvironmentData; +import com.linkedin.drelephant.spark.SparkExecutorData; +import com.linkedin.drelephant.util.MemoryFormatUtils; +import java.util.HashSet; +import java.util.Set; +import junit.framework.TestCase; + +import static com.linkedin.drelephant.spark.SparkExecutorData.EXECUTOR_DRIVER_NAME; +import static com.linkedin.drelephant.spark.heuristics.MemoryLimitHeuristic.SPARK_DRIVER_MEMORY; +import static com.linkedin.drelephant.spark.heuristics.MemoryLimitHeuristic.SPARK_EXECUTOR_INSTANCES; +import static com.linkedin.drelephant.spark.heuristics.MemoryLimitHeuristic.SPARK_EXECUTOR_MEMORY; +import static com.linkedin.drelephant.spark.heuristics.MemoryLimitHeuristic.DEFAULT_SPARK_STORAGE_MEMORY_FRACTION; + + +/** + * This class tests the heuristic rule: MemoryLimitHeuristic + * + * @author yizhou + */ +public class MemoryLimitHeuristicTest extends TestCase { + public void testTotalMemoryRule() { + // Test if the total memory limit is working, set all peak memory to arbirarity 100% + assertEquals(Severity.NONE, analyzeJob(1, "1G", "1G", "1G")); + assertEquals(Severity.NONE, analyzeJob(100, "1G", "1G", "100G")); + assertEquals(Severity.NONE, analyzeJob(10, "10G", "1G", "100G")); + assertEquals(Severity.LOW, analyzeJob(600, "1G", "1G", "600G")); + assertEquals(Severity.MODERATE, analyzeJob(2400, "512M", "1G", "1.2T")); + assertEquals(Severity.SEVERE, analyzeJob(1600, "1G", "1G", "1.6T")); + assertEquals(Severity.CRITICAL, analyzeJob(4200, "512M", "1G", "2.1T")); + } + + public void testMemoryUtilizationRule() { + // Test if the total memory utilization is working + + // When the total memory is too low, ignore the ratio calculation + assertEquals(Severity.NONE, analyzeJob(1, "1G", "1G", "0B")); + // When we barely pass the safe zone + assertEquals(Severity.CRITICAL, analyzeJob(20, "1G", "1G", "0B")); + + // Normal situations + assertEquals(Severity.NONE, analyzeJob(100, "1G", "1G", getPeakMemory(1.0d, 100, "1G"))); + assertEquals(Severity.LOW, analyzeJob(100, "1G", "1G", getPeakMemory(0.7d, 100, "1G"))); + assertEquals(Severity.MODERATE, analyzeJob(100, "1G", "1G", getPeakMemory(0.5d, 100, "1G"))); + assertEquals(Severity.SEVERE, analyzeJob(100, "1G", "1G", getPeakMemory(0.3d, 100, "1G"))); + assertEquals(Severity.CRITICAL, analyzeJob(100, "1G", "1G", getPeakMemory(0.1d, 100, "1G"))); + } + + public void testCombinedRules() { + // Mix multiple rules together, majorly check the combined logic + assertEquals(Severity.CRITICAL, analyzeJob(1, "1G", "10T", "0B")); + assertEquals(Severity.CRITICAL, analyzeJob(2500, "1G", "1G", getPeakMemory(0.7d, 2500, "1G"))); + } + + public void testMissingSparkDriverMemoryProperty() { + HeuristicResult result = getJobresult(100, "1G", "700M", getPeakMemory(1.0d, 100, "1G"), SPARK_DRIVER_MEMORY); + for (String detail : result.getDetails()) { + System.out.println(detail); + if (detail.startsWith("\"Total driver memory allocated")) { + assertEquals("\"Total driver memory allocated\",\"700 MB\"", detail); + } + } + } + + private static String getPeakMemory(double utilRatio, int executors, String memPerExecutor) { + long totalMem = + (long) (executors * MemoryFormatUtils.stringToBytes(memPerExecutor) * DEFAULT_SPARK_STORAGE_MEMORY_FRACTION); + return MemoryFormatUtils.bytesToString((long) (utilRatio * totalMem)); + } + + private Severity analyzeJob(int executors, String memPerExecutor, String driverMem, String peakTotalMem, + String... skippedProperties) { + return getJobresult(executors, memPerExecutor, driverMem, peakTotalMem, skippedProperties).getSeverity(); + } + + private HeuristicResult getJobresult(int executors, String memPerExecutor, String driverMem, String peakTotalMem, + String... skippedProperties) { + Set filters = new HashSet(); + for (int i = 0; i < skippedProperties.length; i++) { + filters.add(skippedProperties[i]); + } + + SparkApplicationData data = new MockSparkApplicationData(); + SparkEnvironmentData env = data.getEnvironmentData(); + if (!filters.contains(SPARK_EXECUTOR_INSTANCES)) { + env.addSparkProperty(SPARK_EXECUTOR_INSTANCES, String.valueOf(executors)); + } + if (!filters.contains(SPARK_EXECUTOR_MEMORY)) { + env.addSparkProperty(SPARK_EXECUTOR_MEMORY, memPerExecutor); + } + if (!filters.contains(SPARK_DRIVER_MEMORY)) { + env.addSparkProperty(SPARK_DRIVER_MEMORY, driverMem); + } + + SparkExecutorData exe = data.getExecutorData(); + SparkExecutorData.ExecutorInfo driverInfo = new SparkExecutorData.ExecutorInfo(); + driverInfo.maxMem = (long) (MemoryFormatUtils.stringToBytes(driverMem) * DEFAULT_SPARK_STORAGE_MEMORY_FRACTION); + exe.setExecutorInfo(EXECUTOR_DRIVER_NAME, driverInfo); + + long bytesPerExecutor = MemoryFormatUtils.stringToBytes(memPerExecutor); + + /* Assign evenly the peak memory to each executor, in practical cases, we might observe the executor peak memory + * can vary a bit due to data skewness and imperfect partitioning. + */ + long peakMemToGenerate = MemoryFormatUtils.stringToBytes(peakTotalMem) / executors; + for (int i = 0; i < executors; i++) { + SparkExecutorData.ExecutorInfo info = new SparkExecutorData.ExecutorInfo(); + info.maxMem = (long) (bytesPerExecutor * DEFAULT_SPARK_STORAGE_MEMORY_FRACTION); + info.memUsed = peakMemToGenerate; + exe.setExecutorInfo(String.valueOf(i), info); + } + return new MemoryLimitHeuristic().apply(data); + } +} diff --git a/test/com/linkedin/drelephant/util/MemoryFormatUtilsTest.java b/test/com/linkedin/drelephant/util/MemoryFormatUtilsTest.java new file mode 100644 index 000000000..396e3ea85 --- /dev/null +++ b/test/com/linkedin/drelephant/util/MemoryFormatUtilsTest.java @@ -0,0 +1,85 @@ +package com.linkedin.drelephant.util; + +import java.util.Random; +import junit.framework.TestCase; + + +/** + * This class test MemoryFormatUtils/ + * + * @author yizhou + */ +public class MemoryFormatUtilsTest extends TestCase { + public void testBytesToString() { + // Check integer values + assertEquals("0 B", MemoryFormatUtils.bytesToString(0L)); + assertEquals("1 B", MemoryFormatUtils.bytesToString(1L)); + assertEquals("1 KB", MemoryFormatUtils.bytesToString(1L << 10)); + assertEquals("1 MB", MemoryFormatUtils.bytesToString(1L << 20)); + assertEquals("1 GB", MemoryFormatUtils.bytesToString(1L << 30)); + assertEquals("1,000 GB", MemoryFormatUtils.bytesToString(1000L << 30)); + assertEquals("1 TB", MemoryFormatUtils.bytesToString(1L << 40)); + assertEquals("1,024 TB", MemoryFormatUtils.bytesToString(1L << 50)); + + // Check double values + assertEquals("8.79 KB", MemoryFormatUtils.bytesToString(9000L)); + assertEquals("8.79 MB", MemoryFormatUtils.bytesToString(9000L << 10)); + assertEquals("8.79 GB", MemoryFormatUtils.bytesToString(9000L << 20)); + assertEquals("8.79 TB", MemoryFormatUtils.bytesToString(9000L << 30)); + assertEquals("87,890.62 TB", MemoryFormatUtils.bytesToString((9000L * 10000) << 30)); + } + + public void testStringToBytes() { + // Null + assertEquals(0L, MemoryFormatUtils.stringToBytes(null)); + + // Integer tests + String[] units = new String[]{"", "K", "M", "G", "T"}; + long[] multiplers = new long[]{1L, 1L << 10, 1L << 20, 1L << 30, 1L << 40}; + Random random = new Random(System.currentTimeMillis()); + for (int i = 0; i < units.length; i++) { + String unit = units[i]; + + // Int values + int[] integerNums = + units.equals("T") ? new int[]{0, random.nextInt(1024), 2048} : new int[]{0, random.nextInt(1024)}; + for (int num : integerNums) { + long expectedNum = num * multiplers[i]; + assertEquals(expectedNum, MemoryFormatUtils.stringToBytes(num + unit)); + assertEquals(expectedNum, MemoryFormatUtils.stringToBytes(num + " " + unit)); + assertEquals(expectedNum, MemoryFormatUtils.stringToBytes(num + unit + "b")); + assertEquals(expectedNum, MemoryFormatUtils.stringToBytes(num + unit + "B")); + assertEquals(expectedNum, MemoryFormatUtils.stringToBytes(num + " " + unit + "B")); + assertEquals(expectedNum, MemoryFormatUtils.stringToBytes(num + " " + unit + "b")); + + unit = unit.toLowerCase(); + assertEquals(expectedNum, MemoryFormatUtils.stringToBytes(num + unit)); + assertEquals(expectedNum, MemoryFormatUtils.stringToBytes(num + " " + unit)); + assertEquals(expectedNum, MemoryFormatUtils.stringToBytes(num + unit + "b")); + assertEquals(expectedNum, MemoryFormatUtils.stringToBytes(num + unit + "B")); + assertEquals(expectedNum, MemoryFormatUtils.stringToBytes(num + " " + unit + "B")); + assertEquals(expectedNum, MemoryFormatUtils.stringToBytes(num + " " + unit + "b")); + } + + // Double values, notice that converting double values to long might lose some precision + double[] doublleNums = new double[]{random.nextDouble(), random.nextDouble() + random.nextInt(1024), 8.79d}; + for (double num : doublleNums) { + long expectedNum = (long) (num * multiplers[i]); + assertEquals(expectedNum, MemoryFormatUtils.stringToBytes(num + unit)); + assertEquals(expectedNum, MemoryFormatUtils.stringToBytes(num + " " + unit)); + assertEquals(expectedNum, MemoryFormatUtils.stringToBytes(num + unit + "b")); + assertEquals(expectedNum, MemoryFormatUtils.stringToBytes(num + unit + "B")); + assertEquals(expectedNum, MemoryFormatUtils.stringToBytes(num + " " + unit + "B")); + assertEquals(expectedNum, MemoryFormatUtils.stringToBytes(num + " " + unit + "b")); + + unit = unit.toLowerCase(); + assertEquals(expectedNum, MemoryFormatUtils.stringToBytes(num + unit)); + assertEquals(expectedNum, MemoryFormatUtils.stringToBytes(num + " " + unit)); + assertEquals(expectedNum, MemoryFormatUtils.stringToBytes(num + unit + "b")); + assertEquals(expectedNum, MemoryFormatUtils.stringToBytes(num + unit + "B")); + assertEquals(expectedNum, MemoryFormatUtils.stringToBytes(num + " " + unit + "B")); + assertEquals(expectedNum, MemoryFormatUtils.stringToBytes(num + " " + unit + "b")); + } + } + } +} From 5845765bcc439e849c06d7ee4360970f8000de82 Mon Sep 17 00:00:00 2001 From: Yitong Zhou Date: Mon, 1 Jun 2015 18:21:59 -0700 Subject: [PATCH 069/272] Addressing naming problems, code cleaness and added more help pages --- .../linkedin/drelephant/ElephantContext.java | 105 +++++++++++--- .../linkedin/drelephant/ElephantRunner.java | 8 -- .../drelephant/analysis/ApplicationType.java | 50 +------ .../analysis/ClassicAnalysisProvider.java | 10 +- .../drelephant/analysis/ElephantFetcher.java | 13 +- .../drelephant/analysis/HeuristicResult.java | 1 + .../linkedin/drelephant/analysis/JobType.java | 1 + .../analysis/YarnAnalysisProvider.java | 13 +- .../mapreduce/MapreduceApplicationData.java | 1 + .../mapreduce/MapreduceFetcherClassic.java | 129 +++++++++++------- .../mapreduce/MapreduceFetcherYarn.java | 62 ++++++--- .../linkedin/drelephant/math/Statistics.java | 4 + .../spark/SparkEnvironmentData.java | 1 + .../spark/SparkJobProgressData.java | 1 + .../BestPropertiesConventionHeuristic.java | 22 ++- .../heuristics/ExecutorLoadHeuristic.java | 10 +- .../spark/heuristics/JobRuntimeHeuristic.java | 12 +- .../heuristics/MemoryLimitHeuristic.java | 24 ++-- .../heuristics/StageRuntimeHeuristic.java | 16 +-- ...cConf.java => HeuristicConfiguration.java} | 26 ++-- ...a.java => HeuristicConfigurationData.java} | 4 +- .../drelephant/util/InfoExtractor.java | 24 ++-- .../linkedin/drelephant/util/JobTypeConf.java | 2 +- .../drelephant/util/MemoryFormatUtils.java | 54 +++++--- app/com/linkedin/drelephant/util/Utils.java | 7 +- app/controllers/Application.java | 18 +-- .../spark/deploy/history/SparkFSFetcher.scala | 4 - .../StorageStatusTrackingListener.scala | 3 +- app/views/helpSparkBestProperties.scala.html | 38 +++++- app/views/helpSparkExecutorLoad.scala.html | 58 ++++++++ app/views/helpSparkJobRuntime.scala.html | 19 +++ app/views/helpSparkMemoryLimit.scala.html | 107 ++++++++++++++- app/views/helpSparkStageRuntime.scala.html | 19 +++ conf/elephant-conf.xml | 13 +- ...BestPropertiesConventionHeuristicTest.java | 4 +- .../util/MemoryFormatUtilsTest.java | 9 ++ 36 files changed, 592 insertions(+), 300 deletions(-) rename app/com/linkedin/drelephant/util/{HeuristicConf.java => HeuristicConfiguration.java} (80%) rename app/com/linkedin/drelephant/util/{HeuristicConfData.java => HeuristicConfigurationData.java} (79%) create mode 100644 app/views/helpSparkExecutorLoad.scala.html create mode 100644 app/views/helpSparkJobRuntime.scala.html create mode 100644 app/views/helpSparkStageRuntime.scala.html diff --git a/app/com/linkedin/drelephant/ElephantContext.java b/app/com/linkedin/drelephant/ElephantContext.java index 68414c2ef..b93e2c57e 100644 --- a/app/com/linkedin/drelephant/ElephantContext.java +++ b/app/com/linkedin/drelephant/ElephantContext.java @@ -1,16 +1,19 @@ package com.linkedin.drelephant; +import com.google.common.collect.Sets; import com.linkedin.drelephant.analysis.ApplicationType; import com.linkedin.drelephant.analysis.ElephantFetcher; import com.linkedin.drelephant.analysis.Heuristic; -import com.linkedin.drelephant.util.HeuristicConf; -import com.linkedin.drelephant.util.HeuristicConfData; +import com.linkedin.drelephant.analysis.HeuristicResult; +import com.linkedin.drelephant.util.HeuristicConfiguration; +import com.linkedin.drelephant.util.HeuristicConfigurationData; import com.linkedin.drelephant.util.Utils; -import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; +import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -27,13 +30,14 @@ */ public class ElephantContext { private static final Logger logger = Logger.getLogger(ElephantContext.class); - private static ElephantContext INSTANCE = new ElephantContext(); + private static final ElephantContext INSTANCE = new ElephantContext(); private static final String CONFIGURATION_FILE_PATH = "elephant-conf.xml"; private static final String OPT_METRICS_PUB_CONF = "metrics.publisher-conf"; private final List _heuristicNames = new ArrayList(); - private List _heuristicsConfData; + private List _heuristicsConfData; + private final Map _nameToType = new HashMap(); private final Map> _typeToHeuristics = new HashMap>(); private final Map _typeToFetcher = new HashMap(); @@ -94,6 +98,7 @@ private void loadConfiguration() { // types via fetcher configurations. loadFetchers(fetchersElement); loadHeuristics(heuristicsElement); + configureSupportedApplicationTypes(); } private void loadFetchers(Element configuration) { @@ -123,12 +128,18 @@ private void loadFetchers(Element configuration) { String hadoopVersion = hadoopVersionNode.getTextContent().toLowerCase().trim(); if (hadoopVersion.equals(_hadoopVersion)) { String typeName = applicationTypeNode.getTextContent(); - if (!ApplicationType.isSupported(typeName)) { - ApplicationType.addType(typeName); - ApplicationType type = ApplicationType.getType(typeName); + if (!isApplicationTypeSupported(typeName)) { + ApplicationType type = new ApplicationType(typeName); + String className = classNameNode.getTextContent(); try { Class fetcherClass = Play.current().classloader().loadClass(className); + Object instance = fetcherClass.newInstance(); + if (!(instance instanceof ElephantFetcher)) { + throw new IllegalArgumentException( + "Class " + fetcherClass.getName() + " is not an implementation of " + ElephantFetcher.class + .getName()); + } _typeToFetcher.put(type, (ElephantFetcher) fetcherClass.newInstance()); } catch (ClassNotFoundException e) { throw new RuntimeException("Class" + className + " not found for fetcher #" + n, e); @@ -151,11 +162,16 @@ private void loadFetchers(Element configuration) { } private void loadHeuristics(Element configuration) { - _heuristicsConfData = new HeuristicConf(configuration).getHeuristicsConfData(); + _heuristicsConfData = new HeuristicConfiguration(configuration).getHeuristicsConfigurationData(); - for (HeuristicConfData data : _heuristicsConfData) { + for (HeuristicConfigurationData data : _heuristicsConfData) { try { Class heuristicClass = Play.current().classloader().loadClass(data.getClassName()); + Object instance = heuristicClass.newInstance(); + if (!(instance instanceof Heuristic)) { + throw new IllegalArgumentException( + "Class " + heuristicClass.getName() + " is not an implementation of " + Heuristic.class.getName()); + } Heuristic heuristicInstance = (Heuristic) heuristicClass.newInstance(); ApplicationType type = data.getAppType(); @@ -178,6 +194,43 @@ private void loadHeuristics(Element configuration) { throw new RuntimeException(data.getClassName() + " is not a valid Heuristic class.", e); } } + + // Bind No_DATA heuristic to its helper pages, no need to add any real configurations + _heuristicsConfData.add( + new HeuristicConfigurationData(HeuristicResult.NO_DATA.getAnalysis(), null, "views.html.helpNoData", null)); + } + + private void configureSupportedApplicationTypes() { + Set supportedTypes = Sets.intersection(_typeToFetcher.keySet(), _typeToHeuristics.keySet()); + for (ApplicationType type : _typeToFetcher.keySet()) { + if (!supportedTypes.contains(type)) { + ElephantFetcher removedFetcher = _typeToFetcher.remove(type); + logger.warn("ElephantFetcher class " + removedFetcher.getClass().getName() + + " does not have any heuristic rule for application type " + type.getName() + ", being ignored."); + } + } + + for (ApplicationType type : _typeToHeuristics.keySet()) { + if (!supportedTypes.contains(type)) { + List removedHeuristics = _typeToHeuristics.remove(type); + for (Heuristic removedHeuristic : removedHeuristics) { + logger.warn("Heuristic class " + removedHeuristic.getClass().getName() + + "does not have any fetcher for application type " + type.getName() + ", being ignored."); + } + } + } + + logger.info("ElephantContext configured:"); + for (ApplicationType type : supportedTypes) { + _nameToType.put(type.getName(), type); + List classes = new ArrayList(); + List heuristics = _typeToHeuristics.get(type); + for (Heuristic heuristic : heuristics) { + classes.add(heuristics.getClass().getName()); + } + logger.info("ApplicationType: " + type.getName() + ", ElephantFetcher class: " + _typeToFetcher.get(type) + + "Heuristics: [" + StringUtils.join(classes, ", ") + "]"); + } } /** @@ -189,17 +242,6 @@ public static ElephantContext instance() { return INSTANCE; } - /** Init necessary thread context information. If needed, this method should be called when a thread is initiated. - * - * @param threadId the thread id to init - */ - public void initThread(int threadId) - throws IOException { - for (ElephantFetcher fetcher : _typeToFetcher.values()) { - fetcher.init(threadId); - } - } - /** * Given an application type, return the currently bound heuristics * @@ -232,7 +274,7 @@ public List getAllHeuristicNames() { * * @return The configuration data of heuristics */ - public List getHeuristicsConfData() { + public List getHeuristicsConfigurationData() { return _heuristicsConfData; } @@ -254,4 +296,23 @@ public ElephantFetcher getFetcher(ApplicationType type) { public DaliMetricsAPI.MetricsPublisher getMetricsPublisher() { return _metricsPublisher; } + + /** + * Get the application type given a type name. + * + * @return The corresponding application type, null if not found + */ + public ApplicationType getApplicationType(String typeName) { + return _nameToType.get(typeName.toUpperCase()); + } + + /** + * Indicate if an application type is supported by the current Dr. Elephant context + * + * @param typeName The type name to look for + * @return true if supported else false + */ + public boolean isApplicationTypeSupported(String typeName) { + return _nameToType.containsKey(typeName.toUpperCase()); + } } diff --git a/app/com/linkedin/drelephant/ElephantRunner.java b/app/com/linkedin/drelephant/ElephantRunner.java index b7de8ec95..511dfad8e 100644 --- a/app/com/linkedin/drelephant/ElephantRunner.java +++ b/app/com/linkedin/drelephant/ElephantRunner.java @@ -126,14 +126,6 @@ private class ExecutorThread implements Runnable { @Override public void run() { - try { - ElephantContext.instance().initThread(_threadId); - } catch (IOException e) { - logger.error("Error initialize context configuration for the thread " + _threadId, e); - logger.error(e.getMessage()); - logger.error(ExceptionUtils.getStackTrace(e)); - } - while (_running.get() && !Thread.currentThread().isInterrupted()) { AnalysisPromise promise = null; try { diff --git a/app/com/linkedin/drelephant/analysis/ApplicationType.java b/app/com/linkedin/drelephant/analysis/ApplicationType.java index fdb5fbef3..8c017d34c 100644 --- a/app/com/linkedin/drelephant/analysis/ApplicationType.java +++ b/app/com/linkedin/drelephant/analysis/ApplicationType.java @@ -1,9 +1,5 @@ package com.linkedin.drelephant.analysis; -import java.util.HashMap; -import java.util.Map; - - /** * Manages and represents supported application types. * @@ -12,7 +8,7 @@ public class ApplicationType { private final String _name; - private ApplicationType(String name) { + public ApplicationType(String name) { _name = name.toUpperCase(); } @@ -37,48 +33,4 @@ public boolean equals(Object other) { public String getName() { return _name; } - - private static final Map VALUES = new HashMap(); - - /** - * Register an application type into the supported map. - * - * @param typeName The type name - */ - public static void addType(String typeName) { - if (typeName == null) { - return; - } - - ApplicationType type = new ApplicationType(typeName); - VALUES.put(type.getName(), type); - } - - /** - * Get the corresponding application type instance via type name - * @param typeName The type name - * @return the application type, null if not found - */ - public static ApplicationType getType(String typeName) { - if (typeName == null) { - return null; - } - return VALUES.get(typeName.toUpperCase()); - } - - /** - * Indicate if a particular application type is supported. - * - * @param typeName The type name - * @return true if supported else false - */ - public static boolean isSupported(String typeName) { - ApplicationType type = getType(typeName); - - if (type == null) { - return false; - } - - return true; - } } diff --git a/app/com/linkedin/drelephant/analysis/ClassicAnalysisProvider.java b/app/com/linkedin/drelephant/analysis/ClassicAnalysisProvider.java index 123ec4d9b..f8304cc83 100644 --- a/app/com/linkedin/drelephant/analysis/ClassicAnalysisProvider.java +++ b/app/com/linkedin/drelephant/analysis/ClassicAnalysisProvider.java @@ -1,5 +1,6 @@ package com.linkedin.drelephant.analysis; +import com.linkedin.drelephant.ElephantContext; import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; @@ -29,7 +30,7 @@ public class ClassicAnalysisProvider implements AnalysisProvider { private Set _previousJobs; private final Queue _retryQueue = new ConcurrentLinkedQueue(); - private final String ONLY_SUPPORTED_TYPE_NAME = "MAPREDUCE"; + private static final String ONLY_SUPPORTED_TYPE_NAME = "MAPREDUCE"; private ApplicationType _onlySupportedType; @Override @@ -37,7 +38,7 @@ public void configure(Configuration configuration) throws Exception { _jobClient = new JobClient(new JobConf(configuration)); - _onlySupportedType = ApplicationType.getType(ONLY_SUPPORTED_TYPE_NAME); + _onlySupportedType = ElephantContext.instance().getApplicationType(ONLY_SUPPORTED_TYPE_NAME); if (_onlySupportedType == null) { throw new RuntimeException("Cannot configure the analysis provider, " + ONLY_SUPPORTED_TYPE_NAME + " application type is not supported."); @@ -76,7 +77,7 @@ public List fetchPromises() long startTime = jobStatus.getStartTime(); promise.setStartTime(startTime); - // ALERT: + // Note: // In Hadoop-1 the getFinishTime() call is not there. Unfortunately, calling this method does not // result in a compile error, neither a MethodNotFound exception at run time. The program just hangs. // Since we don't have metrics (the only consumer of finish time as of now) in Hadoop-1, set the finish time @@ -126,8 +127,7 @@ private Set filterPreviousJobs(Set jobs) { logger.info("Database check completed"); return newJobs; } else { - @SuppressWarnings("unchecked") - Set tempPrevJobs = (HashSet) ((HashSet) jobs).clone(); + Set tempPrevJobs = new HashSet(jobs); // Leave only the newly completed jobs jobs.removeAll(_previousJobs); _previousJobs = tempPrevJobs; diff --git a/app/com/linkedin/drelephant/analysis/ElephantFetcher.java b/app/com/linkedin/drelephant/analysis/ElephantFetcher.java index 3b402db4d..0e40beac1 100644 --- a/app/com/linkedin/drelephant/analysis/ElephantFetcher.java +++ b/app/com/linkedin/drelephant/analysis/ElephantFetcher.java @@ -1,8 +1,5 @@ package com.linkedin.drelephant.analysis; -import java.io.IOException; - - /** * The interface to define common methods for each fetcher. * @@ -10,13 +7,6 @@ */ public interface ElephantFetcher { - /** - * This method is called in Runner's main or each executor thread, before calling other fetcher methods - * in order to initialize thread local variables for the thread. - * @param threadId a thread identifier of Runner's main/executor thread - */ - public void init(int threadId) throws IOException; - /** * Given an application id, fetches the data object * @@ -24,5 +14,6 @@ public interface ElephantFetcher { * @return the fetched data * @throws Exception */ - public T fetchData(String appId) throws Exception; + public T fetchData(String appId) + throws Exception; } diff --git a/app/com/linkedin/drelephant/analysis/HeuristicResult.java b/app/com/linkedin/drelephant/analysis/HeuristicResult.java index d7fda4524..697d3bba2 100644 --- a/app/com/linkedin/drelephant/analysis/HeuristicResult.java +++ b/app/com/linkedin/drelephant/analysis/HeuristicResult.java @@ -72,6 +72,7 @@ public void setSeverity(Severity severity) { this._severity = severity; } + @Override public String toString() { return "{analysis: " + _analysis + ", severity: " + _severity + ", details: [" + StringUtils.join(_details, " ") + "]}"; diff --git a/app/com/linkedin/drelephant/analysis/JobType.java b/app/com/linkedin/drelephant/analysis/JobType.java index 47a8ac3f5..537af25c1 100644 --- a/app/com/linkedin/drelephant/analysis/JobType.java +++ b/app/com/linkedin/drelephant/analysis/JobType.java @@ -37,6 +37,7 @@ public String getName() { return _type; } + @Override public String toString() { return _type; } diff --git a/app/com/linkedin/drelephant/analysis/YarnAnalysisProvider.java b/app/com/linkedin/drelephant/analysis/YarnAnalysisProvider.java index 3655824c0..a67bdf5d5 100644 --- a/app/com/linkedin/drelephant/analysis/YarnAnalysisProvider.java +++ b/app/com/linkedin/drelephant/analysis/YarnAnalysisProvider.java @@ -1,6 +1,8 @@ package com.linkedin.drelephant.analysis; +import com.linkedin.drelephant.ElephantContext; import com.linkedin.drelephant.math.Statistics; +import com.linkedin.drelephant.util.Utils; import java.io.IOException; import java.net.HttpURLConnection; import java.net.URL; @@ -26,6 +28,8 @@ public class YarnAnalysisProvider implements AnalysisProvider { private static final Logger logger = Logger.getLogger(YarnAnalysisProvider.class); // We provide one minute job fetch delay due to the job sending lag from AM/NM to JobHistoryServer HDFS private static final long FETCH_DELAY = 60000; + // Generate a token update interval with a random deviation so that it does not update the token exactly at the same + // time with other token updaters (e.g. ElephantFetchers). private static final long TOKEN_UPDATE_INTERVAL = Statistics.MINUTE_IN_MS * 30 + new Random().nextLong() % (3 * Statistics.MINUTE_IN_MS); @@ -67,16 +71,17 @@ public List fetchPromises() for (JsonNode app : apps) { String id = app.get("id").getValueAsText(); - String jobId = id.replaceFirst("application", "job"); + String jobId = Utils.getJobIdFromApplicationId(id); - if (JobResult.find.byId(jobId) == null) { + if (JobResult.find.byId(jobId) == null && JobResult.find.byId(id) == null) { String user = app.get("user").getValueAsText(); String name = app.get("name").getValueAsText(); String trackingUrl = app.get("trackingUrl").getValueAsText(); long startTime = app.get("startedTime").getLongValue(); long finishTime = app.get("finishedTime").getLongValue(); - ApplicationType type = ApplicationType.getType(app.get("applicationType").getValueAsText()); + ApplicationType type = + ElephantContext.instance().getApplicationType(app.get("applicationType").getValueAsText()); // If the application type is supported if (type != null) { @@ -111,7 +116,7 @@ public void addIntoRetries(AnalysisPromise promise) { private void updateAuthToken() { if (_currentTime - _tokenUpdatedTime > TOKEN_UPDATE_INTERVAL) { - logger.info("FutureProvider updating its Authenticate Token...."); + logger.info("AnalysisProvider updating its Authenticate Token...."); _token = new AuthenticatedURL.Token(); _authenticatedURL = new AuthenticatedURL(); _tokenUpdatedTime = _currentTime; diff --git a/app/com/linkedin/drelephant/mapreduce/MapreduceApplicationData.java b/app/com/linkedin/drelephant/mapreduce/MapreduceApplicationData.java index 033c3143e..cd22a438f 100644 --- a/app/com/linkedin/drelephant/mapreduce/MapreduceApplicationData.java +++ b/app/com/linkedin/drelephant/mapreduce/MapreduceApplicationData.java @@ -123,6 +123,7 @@ public String getJobId() { return _jobId; } + @Override public String toString() { return "id: " + getJobId() + ", name:" + getJobName(); } diff --git a/app/com/linkedin/drelephant/mapreduce/MapreduceFetcherClassic.java b/app/com/linkedin/drelephant/mapreduce/MapreduceFetcherClassic.java index 8771fc931..5dfd11981 100644 --- a/app/com/linkedin/drelephant/mapreduce/MapreduceFetcherClassic.java +++ b/app/com/linkedin/drelephant/mapreduce/MapreduceFetcherClassic.java @@ -16,6 +16,7 @@ import java.util.Properties; import java.util.Random; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; import org.apache.commons.io.IOUtils; import org.apache.hadoop.mapred.Counters; import org.apache.hadoop.mapred.Counters.Counter; @@ -40,22 +41,21 @@ public class MapreduceFetcherClassic implements ElephantFetcher _failedJobsInWait = new ConcurrentHashMap(); - private Map _failedJobsInProgress = new ConcurrentHashMap(); + private Map _failedJobsInWait = + new ConcurrentHashMap(); + private Map _failedJobsInProgress = + new ConcurrentHashMap(); private String _jobtrackerHttpRoot; - public MapreduceFetcherClassic() throws IOException { + public MapreduceFetcherClassic() + throws IOException { _conf = new JobConf(); _jobtrackerHttpRoot = "http://" + _conf.get("mapred.job.tracker.http.address") + "/"; } @Override - public void init(int threadId) throws IOException { - ThreadContextMR1.init(_conf, threadId); - } - - @Override - public MapreduceApplicationData fetchData(String id) throws IOException, AuthenticationException { + public MapreduceApplicationData fetchData(String id) + throws IOException, AuthenticationException { MapreduceApplicationData jobData = new MapreduceApplicationData(); jobData.setJobId(id); try { @@ -90,7 +90,8 @@ public MapreduceApplicationData fetchData(String id) throws IOException, Authent long finishTime = startTime; String jobUrl = job.getTrackingURL(); String jobName = job.getJobName(); - jobData.setUsername(username).setStartTime(startTime).setFinishTime(finishTime).setUrl(jobUrl).setJobName(jobName); + jobData.setUsername(username).setStartTime(startTime).setFinishTime(finishTime).setUrl(jobUrl) + .setJobName(jobName); // Fetch job counter HadoopCounterHolder counterHolder = fetchCounter(job.getCounters()); @@ -122,7 +123,6 @@ public MapreduceApplicationData fetchData(String id) throws IOException, Authent // Fetch job config Properties jobConf = fetchJobConf(job); jobData.setCounters(counterHolder).setMapperData(mappers).setReducerData(reducers).setJobConf(jobConf); - } finally { ThreadContextMR1.updateAuthToken(); } @@ -132,7 +132,8 @@ public MapreduceApplicationData fetchData(String id) throws IOException, Authent return jobData; } - private Properties fetchJobConf(RunningJob job) throws IOException, AuthenticationException { + private Properties fetchJobConf(RunningJob job) + throws IOException, AuthenticationException { String jobconfUrl = getJobconfUrl(job); if (jobconfUrl == null) { return new Properties(); @@ -140,7 +141,8 @@ private Properties fetchJobConf(RunningJob job) throws IOException, Authenticati return fetchJobConfFromURL(jobconfUrl); } - private Properties fetchJobConfFromURL(String jobconfUrl) throws IOException, AuthenticationException { + private Properties fetchJobConfFromURL(String jobconfUrl) + throws IOException, AuthenticationException { //Parse job config from config page Properties properties = new Properties(); @@ -190,7 +192,8 @@ private String getTaskDetailsPageUrl(String jobDetails, String taskId) { } // Fetch task time from task detail page - private long[] fetchTaskDetails(String taskDetailUrl, boolean isMapper) throws IOException, AuthenticationException { + private long[] fetchTaskDetails(String taskDetailUrl, boolean isMapper) + throws IOException, AuthenticationException { Document doc = ThreadContextMR1.fetchHtmlDoc(taskDetailUrl); Elements rows = doc.select("table").select("tr"); long[] time = null; @@ -209,7 +212,8 @@ private long[] fetchTaskDetails(String taskDetailUrl, boolean isMapper) throws I //This method return time array if successfully extracts data from table row (
) //Return [totalMs,shuffleMs,sortMs] for reducers, [totalMs,0,0] for mappers - private long[] tryExtractDetailFromRow(Element row, boolean isMapper) throws ParseException { + private long[] tryExtractDetailFromRow(Element row, boolean isMapper) + throws ParseException { Elements cells = row.select("> td"); // For rows() in reducer task page with other than 12 cols(),or 10 cols in mapper page, @@ -225,11 +229,11 @@ private long[] tryExtractDetailFromRow(Element row, boolean isMapper) throws Par long shuffleTimeMs = parseTimeInMs(cells.get(5).html().trim()); long sortTimeMs = parseTimeInMs(cells.get(6).html().trim()); long totalTimeMs = parseTimeInMs(cells.get(7).html().trim()); - return new long[] { totalTimeMs, shuffleTimeMs, sortTimeMs }; + return new long[]{totalTimeMs, shuffleTimeMs, sortTimeMs}; } else { // Mapper task. Get total time from time span long totalTimeMs = parseTimeInMs(cells.get(5).html().trim()); - return new long[] { totalTimeMs, 0, 0 }; + return new long[]{totalTimeMs, 0, 0}; } } // This is a failed task attempt. @@ -268,7 +272,8 @@ private void addJobToRetryList(MapreduceApplicationData job) { } // Return false if this is a real nodata received job ( 0 mapper & 0 reducer ) rather than a retired job - private boolean checkRetiredAndFetchJobData(MapreduceApplicationData jobData) throws IOException, AuthenticationException { + private boolean checkRetiredAndFetchJobData(MapreduceApplicationData jobData) + throws IOException, AuthenticationException { String jobUrl = _jobtrackerHttpRoot + "jobdetails.jsp?jobid=" + jobData.getJobId(); Document doc = ThreadContextMR1.fetchHtmlDoc(jobUrl); Elements hrefs = doc.select("a"); @@ -312,7 +317,8 @@ private boolean checkRetiredAndFetchJobData(MapreduceApplicationData jobData) th } // Fetch job counter from job's main page - private HadoopCounterHolder fetchJobCounterForRetiredJob(Document doc) throws IOException, AuthenticationException { + private HadoopCounterHolder fetchJobCounterForRetiredJob(Document doc) + throws IOException, AuthenticationException { HadoopCounterHolder holder = new HadoopCounterHolder(); Elements rows = doc.select("table").select("tr"); for (Element row : rows) { @@ -337,8 +343,8 @@ private HadoopCounterHolder fetchJobCounterForRetiredJob(Document doc) throws IO } // Fetch task counter from task's counter page - public HadoopCounterHolder fetchTaskCounterForRetiredJob(String taskCounterUrl) throws IOException, - AuthenticationException { + public HadoopCounterHolder fetchTaskCounterForRetiredJob(String taskCounterUrl) + throws IOException, AuthenticationException { HadoopCounterHolder holder = new HadoopCounterHolder(); Document doc = ThreadContextMR1.fetchHtmlDoc(taskCounterUrl); Elements rows = doc.select("table").select("tr"); @@ -357,7 +363,8 @@ public HadoopCounterHolder fetchTaskCounterForRetiredJob(String taskCounterUrl) } // We fetch the start time of job's Setup task as the job's start time, shown in job's main page - private long fetchStartTimeForRetiredJob(Document doc, String jobUrl) throws IOException { + private long fetchStartTimeForRetiredJob(Document doc, String jobUrl) + throws IOException { Elements rows = doc.select("table").select("tr"); for (Element row : rows) { Elements cells = row.select("> td"); @@ -374,8 +381,8 @@ private long fetchStartTimeForRetiredJob(Document doc, String jobUrl) throws IOE throw new IOException("Unable to fetch start time data from job page in URL : " + jobUrl); } - private HadoopTaskData[] fetchAllTaskDataForRetiredJob(String taskIndexPage, boolean isMapper) throws IOException, - AuthenticationException { + private HadoopTaskData[] fetchAllTaskDataForRetiredJob(String taskIndexPage, boolean isMapper) + throws IOException, AuthenticationException { List taskList = new ArrayList(); Document doc = ThreadContextMR1.fetchHtmlDoc(taskIndexPage); Elements hrefs = doc.select("a"); @@ -390,8 +397,8 @@ private HadoopTaskData[] fetchAllTaskDataForRetiredJob(String taskIndexPage, boo return taskList.toArray(new HadoopTaskData[taskList.size()]); } - private HadoopTaskData fetchTaskDataForRetiredJob(String taskDetailUrl, boolean isMapper) throws IOException, - AuthenticationException { + private HadoopTaskData fetchTaskDataForRetiredJob(String taskDetailUrl, boolean isMapper) + throws IOException, AuthenticationException { Document doc = ThreadContextMR1.fetchHtmlDoc(taskDetailUrl); Elements rows = doc.select("table").select("tr"); HadoopTaskData taskData = null; @@ -408,14 +415,14 @@ private HadoopTaskData fetchTaskDataForRetiredJob(String taskDetailUrl, boolean throw new IOException("No valid time data found from task detail page. TASK URL=" + taskDetailUrl); } - private HadoopTaskData tryExtractTaskDataForRetiredJob(Element row, boolean isMapper) throws IOException, - ParseException, AuthenticationException { + private HadoopTaskData tryExtractTaskDataForRetiredJob(Element row, boolean isMapper) + throws IOException, ParseException, AuthenticationException { Elements cells = row.select("> td"); if (isMapper && cells.size() == 7) { String c = cells.get(6).text().trim(); if (c.matches("[^0][0-9]+")) { long totalTimeMs = parseTimeInMs(cells.get(2).html().trim()); - long[] time = new long[] { totalTimeMs, 0, 0 }; + long[] time = new long[]{totalTimeMs, 0, 0}; String counterUrl = _jobtrackerHttpRoot + cells.get(6).select("a").attr("href"); HadoopCounterHolder ctrholder = fetchTaskCounterForRetiredJob(counterUrl); return new HadoopTaskData(ctrholder, time); @@ -426,7 +433,7 @@ private HadoopTaskData tryExtractTaskDataForRetiredJob(Element row, boolean isMa long shuffleTimeMs = parseTimeInMs(cells.get(2).html().trim()); long sortTimeMs = parseTimeInMs(cells.get(3).html().trim()); long totalTimeMs = parseTimeInMs(cells.get(4).html().trim()); - long[] time = new long[] { totalTimeMs, shuffleTimeMs, sortTimeMs }; + long[] time = new long[]{totalTimeMs, shuffleTimeMs, sortTimeMs}; String counterUrl = _jobtrackerHttpRoot + cells.get(8).select("a").attr("href"); HadoopCounterHolder ctrholder = fetchTaskCounterForRetiredJob(counterUrl); return new HadoopTaskData(ctrholder, time); @@ -456,35 +463,61 @@ private long parseTimeInMs(String rawDateStr) { } final class ThreadContextMR1 { + private static final AtomicInteger THREAD_ID = new AtomicInteger(1); + private static final Logger logger = Logger.getLogger(ThreadContextMR1.class); - private static final ThreadLocal _LOCAL_THREAD_ID = new ThreadLocal(); - private static final ThreadLocal _LOCAL_JOB_CLIENT = new ThreadLocal(); - private static final ThreadLocal _LOCAL_AUTH_TOKEN = - new ThreadLocal(); - private static final ThreadLocal _LOCAL_AUTH_URL = new ThreadLocal(); + private static final ThreadLocal _LOCAL_THREAD_ID = new ThreadLocal() { + @Override + protected Integer initialValue() { + return THREAD_ID.getAndIncrement(); + } + }; + + private static final ThreadLocal _LOCAL_JOB_CLIENT = new ThreadLocal() { + @Override + protected JobClient initialValue() { + JobClient client = null; + try { + client = new JobClient(new JobConf()); + } catch (IOException e) { + throw new RuntimeException("Exception occurred while initializing the job client.", e); + } + + return client; + } + }; + private static final ThreadLocal _LOCAL_LAST_UPDATED = new ThreadLocal(); private static final ThreadLocal _LOCAL_UPDATE_INTERVAL = new ThreadLocal(); + private static final ThreadLocal _LOCAL_AUTH_TOKEN = + new ThreadLocal() { + @Override + protected AuthenticatedURL.Token initialValue() { + _LOCAL_LAST_UPDATED.set(System.currentTimeMillis()); + _LOCAL_UPDATE_INTERVAL + .set(Statistics.MINUTE_IN_MS * 30 + new Random().nextLong() % (3 * Statistics.MINUTE_IN_MS)); + logger.info("Executor " + _LOCAL_THREAD_ID.get() + " update interval " + + _LOCAL_UPDATE_INTERVAL.get() * 1.0 / Statistics.MINUTE_IN_MS); + return new AuthenticatedURL.Token(); + } + }; - private ThreadContextMR1() { - } + private static final ThreadLocal _LOCAL_AUTH_URL = new ThreadLocal() { + @Override + protected AuthenticatedURL initialValue() { + return new AuthenticatedURL(); + } + }; - public static void init(JobConf hadoopConf, int threadId) throws IOException { - _LOCAL_JOB_CLIENT.set(new JobClient(hadoopConf)); - _LOCAL_AUTH_TOKEN.set(new AuthenticatedURL.Token()); - _LOCAL_AUTH_URL.set(new AuthenticatedURL()); - _LOCAL_THREAD_ID.set(threadId); - _LOCAL_LAST_UPDATED.set(System.currentTimeMillis()); - // Random an interval for each executor to avoid update token at the same time - _LOCAL_UPDATE_INTERVAL.set(Statistics.MINUTE_IN_MS * 30 + new Random().nextLong() % (3 * Statistics.MINUTE_IN_MS)); - logger.info("Executor " + _LOCAL_THREAD_ID.get() + " update interval " + _LOCAL_UPDATE_INTERVAL.get() * 1.0 - / Statistics.MINUTE_IN_MS); + private ThreadContextMR1() { } public static JobClient getClient() { return _LOCAL_JOB_CLIENT.get(); } - public static Document fetchHtmlDoc(String url) throws IOException, AuthenticationException { + public static Document fetchHtmlDoc(String url) + throws IOException, AuthenticationException { HttpURLConnection conn = _LOCAL_AUTH_URL.get().openConnection(new URL(url), _LOCAL_AUTH_TOKEN.get()); Document doc = Jsoup.parse(IOUtils.toString(conn.getInputStream())); return doc; diff --git a/app/com/linkedin/drelephant/mapreduce/MapreduceFetcherYarn.java b/app/com/linkedin/drelephant/mapreduce/MapreduceFetcherYarn.java index 09f50f23a..0c737036e 100644 --- a/app/com/linkedin/drelephant/mapreduce/MapreduceFetcherYarn.java +++ b/app/com/linkedin/drelephant/mapreduce/MapreduceFetcherYarn.java @@ -16,6 +16,7 @@ import java.util.Properties; import java.util.Random; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; import model.JobResult; import org.apache.hadoop.mapred.JobConf; import org.apache.hadoop.security.authentication.client.AuthenticatedURL; @@ -39,17 +40,13 @@ public class MapreduceFetcherYarn implements ElephantFetcher _LOCAL_THREAD_ID = new ThreadLocal(); - private static final ThreadLocal _LOCAL_AUTH_TOKEN = - new ThreadLocal(); - private static final ThreadLocal _LOCAL_AUTH_URL = new ThreadLocal(); - private static final ThreadLocal _LOCAL_MAPPER = new ThreadLocal(); + + private static final AtomicInteger THREAD_ID = new AtomicInteger(1); + + private static final ThreadLocal _LOCAL_THREAD_ID = new ThreadLocal() { + @Override + public Integer initialValue() { + return THREAD_ID.getAndIncrement(); + } + }; + private static final ThreadLocal _LOCAL_LAST_UPDATED = new ThreadLocal(); private static final ThreadLocal _LOCAL_UPDATE_INTERVAL = new ThreadLocal(); - private ThreadContextMR2() { - } + private static final ThreadLocal _LOCAL_AUTH_TOKEN = + new ThreadLocal() { + @Override + public AuthenticatedURL.Token initialValue() { + _LOCAL_LAST_UPDATED.set(System.currentTimeMillis()); + // Random an interval for each executor to avoid update token at the same time + _LOCAL_UPDATE_INTERVAL.set(Statistics.MINUTE_IN_MS * 30 + new Random().nextLong() % (3 * Statistics.MINUTE_IN_MS)); + logger.info("Executor " + _LOCAL_THREAD_ID.get() + " update interval " + _LOCAL_UPDATE_INTERVAL.get() * 1.0 + / Statistics.MINUTE_IN_MS); + return new AuthenticatedURL.Token(); + } + }; + + private static final ThreadLocal _LOCAL_AUTH_URL = new ThreadLocal() { + @Override + public AuthenticatedURL initialValue() { + return new AuthenticatedURL(); + } + }; + + private static final ThreadLocal _LOCAL_MAPPER = new ThreadLocal() { + @Override + public ObjectMapper initialValue() { + return new ObjectMapper(); + } + }; - static void init(int threadId) throws IOException { - _LOCAL_AUTH_TOKEN.set(new AuthenticatedURL.Token()); - _LOCAL_AUTH_URL.set(new AuthenticatedURL()); - _LOCAL_MAPPER.set(new ObjectMapper()); - _LOCAL_THREAD_ID.set(threadId); - _LOCAL_LAST_UPDATED.set(System.currentTimeMillis()); - // Random an interval for each executor to avoid update token at the same time - _LOCAL_UPDATE_INTERVAL.set(Statistics.MINUTE_IN_MS * 30 + new Random().nextLong() % (3 * Statistics.MINUTE_IN_MS)); - logger.info("Executor " + _LOCAL_THREAD_ID.get() + " update interval " + _LOCAL_UPDATE_INTERVAL.get() * 1.0 - / Statistics.MINUTE_IN_MS); + private ThreadContextMR2() { + // Empty on purpose } public static JsonNode readJsonNode(URL url) throws IOException, AuthenticationException { diff --git a/app/com/linkedin/drelephant/math/Statistics.java b/app/com/linkedin/drelephant/math/Statistics.java index 8082caf21..4721dd8ce 100644 --- a/app/com/linkedin/drelephant/math/Statistics.java +++ b/app/com/linkedin/drelephant/math/Statistics.java @@ -135,6 +135,10 @@ public static String describeFactor(long value, long compare, String suffix) { } public static String readableTimespan(long milliseconds) { + if (milliseconds == 0) { + return "0 sec"; + } + long seconds = milliseconds / 1000; long minutes = seconds / 60; long hours = minutes / 60; diff --git a/app/com/linkedin/drelephant/spark/SparkEnvironmentData.java b/app/com/linkedin/drelephant/spark/SparkEnvironmentData.java index a67b45e75..9bfc87c56 100644 --- a/app/com/linkedin/drelephant/spark/SparkEnvironmentData.java +++ b/app/com/linkedin/drelephant/spark/SparkEnvironmentData.java @@ -47,6 +47,7 @@ public Properties getSystemProperties() { return _systemProperties; } + @Override public String toString() { return _sparkProperties.toString() + "\n\n\n" + _systemProperties.toString(); } diff --git a/app/com/linkedin/drelephant/spark/SparkJobProgressData.java b/app/com/linkedin/drelephant/spark/SparkJobProgressData.java index 3e8241820..ea488326a 100644 --- a/app/com/linkedin/drelephant/spark/SparkJobProgressData.java +++ b/app/com/linkedin/drelephant/spark/SparkJobProgressData.java @@ -231,6 +231,7 @@ public double getFailureRate() { // TODO: accumulables info seem to be unnecessary, might might be useful later on // sample code from Spark source: var accumulables = new HashMap[Long, AccumulableInfo] + @Override public String toString() { return String.format("{numActiveTasks:%s, numCompleteTasks:%s, completedIndices:%s, numFailedTasks:%s," + " executorRunTime:%s, inputBytes:%s, outputBytes:%s, shuffleReadBytes:%s, shuffleWriteBytes:%s, memoryBytesSpilled:%s," diff --git a/app/com/linkedin/drelephant/spark/heuristics/BestPropertiesConventionHeuristic.java b/app/com/linkedin/drelephant/spark/heuristics/BestPropertiesConventionHeuristic.java index 8d4d5ed2f..62c6201d6 100644 --- a/app/com/linkedin/drelephant/spark/heuristics/BestPropertiesConventionHeuristic.java +++ b/app/com/linkedin/drelephant/spark/heuristics/BestPropertiesConventionHeuristic.java @@ -28,12 +28,16 @@ public HeuristicResult apply(SparkApplicationData data) { String sparkDriverMemory = env.getSparkProperty(SPARK_DRIVER_MEMORY); String sparkShuffleManager = env.getSparkProperty(SPARK_SHUFFLE_MANAGER); String sparkExecutorCores = env.getSparkProperty(SPARK_EXECUTOR_CORES); + int coreNum = sparkExecutorCores == null ? 1 : Integer.parseInt(sparkExecutorCores); - HeuristicResult result = new HeuristicResult(getHeuristicName(), Severity - .max(binarySeverity("org.apache.spark.serializer.KryoSerializer", sparkSerializer, false, Severity.MODERATE), - getDriverMemorySeverity(MemoryFormatUtils.stringToBytes(sparkDriverMemory)), - binarySeverity("sort", sparkShuffleManager, true, Severity.MODERATE), - binarySeverity("1", sparkExecutorCores, true, Severity.CRITICAL))); + Severity kryoSeverity = + binarySeverity("org.apache.spark.serializer.KryoSerializer", sparkSerializer, false, Severity.MODERATE); + Severity driverMemSeverity = getDriverMemorySeverity(MemoryFormatUtils.stringToBytes(sparkDriverMemory)); + Severity sortSeverity = binarySeverity("sort", sparkShuffleManager, true, Severity.MODERATE); + Severity executorCoreSeverity = getCoreNumSeverity(coreNum); + + HeuristicResult result = new HeuristicResult(getHeuristicName(), + Severity.max(kryoSeverity, driverMemSeverity, sortSeverity, executorCoreSeverity)); result.addDetail(SPARK_SERIALIZER, propertyToString(sparkSerializer)); result.addDetail(SPARK_DRIVER_MEMORY, propertyToString(sparkDriverMemory)); @@ -43,6 +47,14 @@ public HeuristicResult apply(SparkApplicationData data) { return result; } + private static Severity getCoreNumSeverity(int cores) { + if (cores > 2) { + return Severity.CRITICAL; + } else { + return Severity.NONE; + } + } + private static Severity getDriverMemorySeverity(long mem) { return Severity .getSeverityAscending(mem, MemoryFormatUtils.stringToBytes("4G"), MemoryFormatUtils.stringToBytes("4G"), diff --git a/app/com/linkedin/drelephant/spark/heuristics/ExecutorLoadHeuristic.java b/app/com/linkedin/drelephant/spark/heuristics/ExecutorLoadHeuristic.java index 895e111bb..b0ebce7eb 100644 --- a/app/com/linkedin/drelephant/spark/heuristics/ExecutorLoadHeuristic.java +++ b/app/com/linkedin/drelephant/spark/heuristics/ExecutorLoadHeuristic.java @@ -110,21 +110,21 @@ public HeuristicResult apply(SparkApplicationData data) { HeuristicResult result = new HeuristicResult(getHeuristicName(), severity); - result.addDetail("Average peak memory used", String + result.addDetail("Average peak storage memory", String .format("%s (%s~%s)", MemoryFormatUtils.bytesToString(peakMems.getAvg()), MemoryFormatUtils.bytesToString(peakMems.getMin()), MemoryFormatUtils.bytesToString(peakMems.getMax()))); - result.addDetail("Average runtime duration", String + result.addDetail("Average runtime", String .format("%s (%s~%s)", Statistics.readableTimespan(durations.getAvg()), Statistics.readableTimespan(durations.getMin()), Statistics.readableTimespan(durations.getMax()))); - result.addDetail("Average input data", String + result.addDetail("Average input size", String .format("%s (%s~%s)", MemoryFormatUtils.bytesToString(inputBytes.getAvg()), MemoryFormatUtils.bytesToString(inputBytes.getMin()), MemoryFormatUtils.bytesToString(inputBytes.getMax()))); - result.addDetail("Average output data", String + result.addDetail("Average output size", String .format("%s (%s~%s)", MemoryFormatUtils.bytesToString(outputBytes.getAvg()), MemoryFormatUtils.bytesToString(outputBytes.getMin()), MemoryFormatUtils.bytesToString(outputBytes.getMax()))); - result.addDetail("Average total tasks", + result.addDetail("Average task number", String.format("%s (%s~%s)", totalTasks.getAvg(), totalTasks.getMin(), totalTasks.getMax())); return result; diff --git a/app/com/linkedin/drelephant/spark/heuristics/JobRuntimeHeuristic.java b/app/com/linkedin/drelephant/spark/heuristics/JobRuntimeHeuristic.java index 2a1577384..db5238757 100644 --- a/app/com/linkedin/drelephant/spark/heuristics/JobRuntimeHeuristic.java +++ b/app/com/linkedin/drelephant/spark/heuristics/JobRuntimeHeuristic.java @@ -52,7 +52,7 @@ public HeuristicResult apply(SparkApplicationData data) { result.addDetail("Spark completed jobs number", String.valueOf(completedJobs.size())); result.addDetail("Spark failed jobs number", String.valueOf(failedJobs.size())); result.addDetail("Spark failed jobs list", getJobListString(jobProgressData.getFailedJobDescriptions())); - result.addDetail("Spark average job failure rate", String.format("%1.3f", avgJobFailureRate)); + result.addDetail("Spark average job failure rate", String.format("%.3f", avgJobFailureRate)); result.addDetail("Spark jobs with high task failure rate", getJobListString(highFailureRateJobs)); return result; @@ -68,15 +68,7 @@ private static Severity getAvgJobFailureRateSeverity(double rate) { } private static Severity getSingleJobFailureRateSeverity(double rate) { - if (rate > 0.5d) { - return Severity.CRITICAL; - } - - if (rate > 0.3d) { - return Severity.MODERATE; - } - - return Severity.NONE; + return Severity.getSeverityAscending(rate, 0.0d, 0.3d, 0.5d, 0.5d); } private static String getJobListString(Collection names) { diff --git a/app/com/linkedin/drelephant/spark/heuristics/MemoryLimitHeuristic.java b/app/com/linkedin/drelephant/spark/heuristics/MemoryLimitHeuristic.java index bcd5c4ac9..dc1155e9c 100644 --- a/app/com/linkedin/drelephant/spark/heuristics/MemoryLimitHeuristic.java +++ b/app/com/linkedin/drelephant/spark/heuristics/MemoryLimitHeuristic.java @@ -32,7 +32,12 @@ public class MemoryLimitHeuristic implements Heuristic { @Override public HeuristicResult apply(SparkApplicationData data) { - long totalExecutorMem = getTotalExecutorMem(data); + int executorNum = Integer.parseInt(data.getEnvironmentData().getSparkProperty(SPARK_EXECUTOR_INSTANCES, "0")); + long perExecutorMem = + MemoryFormatUtils.stringToBytes(data.getEnvironmentData().getSparkProperty(SPARK_EXECUTOR_MEMORY, "0")); + + long totalExecutorMem = executorNum * perExecutorMem; + long totalStorageMem = getTotalStorageMem(data); long totalDriverMem = getTotalDriverMem(data); long peakMem = getStoragePeakMemory(data); @@ -43,7 +48,9 @@ public HeuristicResult apply(SparkApplicationData data) { HeuristicResult result = new HeuristicResult(getHeuristicName(), Severity.max(totalMemorySeverity, memoryUtilizationServerity)); - result.addDetail("Total executor memory allocated", MemoryFormatUtils.bytesToString(totalExecutorMem)); + result.addDetail("Total executor memory allocated", String + .format("%s (%s x %s)", MemoryFormatUtils.bytesToString(totalExecutorMem), + MemoryFormatUtils.bytesToString(perExecutorMem), executorNum)); result.addDetail("Total driver memory allocated", MemoryFormatUtils.bytesToString(totalDriverMem)); result.addDetail("Total memory allocated for storage", MemoryFormatUtils.bytesToString(totalStorageMem)); result.addDetail("Total memory used at peak", MemoryFormatUtils.bytesToString(peakMem)); @@ -51,19 +58,6 @@ public HeuristicResult apply(SparkApplicationData data) { return result; } - /** - * Get the total executor memory - * - * @param data The spark application data that contains the information - * @return The memory in bytes - */ - private static long getTotalExecutorMem(SparkApplicationData data) { - int executorNum = Integer.parseInt(data.getEnvironmentData().getSparkProperty(SPARK_EXECUTOR_INSTANCES, "0")); - long perExecutorMem = - MemoryFormatUtils.stringToBytes(data.getEnvironmentData().getSparkProperty(SPARK_EXECUTOR_MEMORY, "0")); - return executorNum * perExecutorMem; - } - /** * Get the total driver memory * diff --git a/app/com/linkedin/drelephant/spark/heuristics/StageRuntimeHeuristic.java b/app/com/linkedin/drelephant/spark/heuristics/StageRuntimeHeuristic.java index ab072066e..a76b06e7a 100644 --- a/app/com/linkedin/drelephant/spark/heuristics/StageRuntimeHeuristic.java +++ b/app/com/linkedin/drelephant/spark/heuristics/StageRuntimeHeuristic.java @@ -58,7 +58,7 @@ public HeuristicResult apply(SparkApplicationData data) { result.addDetail("Spark stage completed", String.valueOf(completedStages.size())); result.addDetail("Spark stage failed", String.valueOf(failedStages.size())); - result.addDetail("Spark average stage failure rate", String.format("%1.3f", avgStageFailureRate)); + result.addDetail("Spark average stage failure rate", String.format("%.3f", avgStageFailureRate)); result.addDetail("Spark problematic stages:", getStageListString(problematicStages)); return result; @@ -70,8 +70,8 @@ public String getHeuristicName() { } private static Severity getStageRuntimeSeverity(long runtime) { - return Severity.getSeverityAscending(runtime, 15 * Statistics.MINUTE_IN_MS, 15 * Statistics.MINUTE_IN_MS, - 30 * Statistics.MINUTE_IN_MS, 30 * Statistics.MINUTE_IN_MS); + return Severity.getSeverityAscending(runtime, 15 * Statistics.MINUTE_IN_MS, 30 * Statistics.MINUTE_IN_MS, + 60 * Statistics.MINUTE_IN_MS, 60 * Statistics.MINUTE_IN_MS); } private static Severity getStageFailureRateSeverity(double rate) { @@ -79,15 +79,7 @@ private static Severity getStageFailureRateSeverity(double rate) { } private static Severity getSingleStageTasksFailureRate(double rate) { - if (rate > 0.5d) { - return Severity.CRITICAL; - } - - if (rate > 0.3) { - return Severity.MODERATE; - } - - return Severity.NONE; + return Severity.getSeverityAscending(rate, 0.0d, 0.3d, 0.5d, 0.5d); } private static String getStageListString(Collection names) { diff --git a/app/com/linkedin/drelephant/util/HeuristicConf.java b/app/com/linkedin/drelephant/util/HeuristicConfiguration.java similarity index 80% rename from app/com/linkedin/drelephant/util/HeuristicConf.java rename to app/com/linkedin/drelephant/util/HeuristicConfiguration.java index 980110e14..9315a93a4 100644 --- a/app/com/linkedin/drelephant/util/HeuristicConf.java +++ b/app/com/linkedin/drelephant/util/HeuristicConfiguration.java @@ -10,22 +10,22 @@ import org.w3c.dom.NodeList; -public class HeuristicConf { - private static final Logger logger = Logger.getLogger(HeuristicConf.class); - private List _heuristicsConfDataList; +public class HeuristicConfiguration { + private static final Logger logger = Logger.getLogger(HeuristicConfiguration.class); + private List _heuristicsConfDataList; - public HeuristicConf(Element configuration) { - parseHeuristicConf(configuration); + public HeuristicConfiguration(Element configuration) { + parseHeuristicConfiguration(configuration); } - public List getHeuristicsConfData() { + public List getHeuristicsConfigurationData() { return _heuristicsConfDataList; } - private void parseHeuristicConf(Element configuration) { + private void parseHeuristicConfiguration(Element configuration) { String hadoopVersion = Utils.getHadoopVersion(); - _heuristicsConfDataList = new ArrayList(); + _heuristicsConfDataList = new ArrayList(); NodeList nodes = configuration.getChildNodes(); int n = 0; @@ -78,18 +78,20 @@ private void parseHeuristicConf(Element configuration) { "No tag or invalid tag 'applicationtype' in heuristic " + n + " classname " + className); } String appTypeStr = appTypeNode.getTextContent(); - ApplicationType appType = ApplicationType.getType(appTypeStr); - if (appType == null) { + if (appTypeStr == null) { logger.error( - "[" + appTypeStr + "] is not a valid application type in heuristic " + n + " classname " + className + "Application type is not specified in heuristic " + n + " classname " + className + ". Skipping this configuration."); continue; } + ApplicationType appType = new ApplicationType(appTypeStr); + for (int j = 0; j < versionList.getLength(); j++) { String version = versionList.item(j).getTextContent(); if (version.equals(hadoopVersion)) { - HeuristicConfData heuristicData = new HeuristicConfData(heuristicName, className, viewName, appType); + HeuristicConfigurationData + heuristicData = new HeuristicConfigurationData(heuristicName, className, viewName, appType); _heuristicsConfDataList.add(heuristicData); break; } diff --git a/app/com/linkedin/drelephant/util/HeuristicConfData.java b/app/com/linkedin/drelephant/util/HeuristicConfigurationData.java similarity index 79% rename from app/com/linkedin/drelephant/util/HeuristicConfData.java rename to app/com/linkedin/drelephant/util/HeuristicConfigurationData.java index d731419cb..3a7c3fcae 100644 --- a/app/com/linkedin/drelephant/util/HeuristicConfData.java +++ b/app/com/linkedin/drelephant/util/HeuristicConfigurationData.java @@ -3,13 +3,13 @@ import com.linkedin.drelephant.analysis.ApplicationType; -public class HeuristicConfData { +public class HeuristicConfigurationData { private final String _heuristicName; private final String _className; private final String _viewName; private final ApplicationType _appType; - public HeuristicConfData(String heuristicName, String className, String viewName, ApplicationType appType) { + public HeuristicConfigurationData(String heuristicName, String className, String viewName, ApplicationType appType) { _heuristicName = heuristicName; _className = className; _viewName = viewName; diff --git a/app/com/linkedin/drelephant/util/InfoExtractor.java b/app/com/linkedin/drelephant/util/InfoExtractor.java index d1f2b2e37..1d2cadac9 100644 --- a/app/com/linkedin/drelephant/util/InfoExtractor.java +++ b/app/com/linkedin/drelephant/util/InfoExtractor.java @@ -23,10 +23,10 @@ public class InfoExtractor { private static final Logger logger = Logger.getLogger(InfoExtractor.class); private static final String SPARK_EXTRA_JAVA_OPTIONS = "spark.driver.extraJavaOptions"; - private static final String AZK_WORKFLOW_URL = "azkaban.link.workflow.url"; - private static final String AZK_JOB_URL = "azkaban.link.job.url"; - private static final String AZK_EXECUTION_URL = "azkaban.link.execution.url"; - private static final String AZK_ATTEMPT_URL = "azkaban.link.attempt.url"; + private static final String AZKABAN_WORKFLOW_URL = "azkaban.link.workflow.url"; + private static final String AZKABAN_JOB_URL = "azkaban.link.job.url"; + private static final String AZKABAN_EXECUTION_URL = "azkaban.link.execution.url"; + private static final String AZKABAN_ATTEMPT_URL = "azkaban.link.attempt.url"; // TODO: this utils class is not ideal, probably should merge retrieve URLs logics directly into the data interface? public static void retrieveURLs(JobResult result, HadoopApplicationData data) { @@ -40,12 +40,12 @@ public static void retrieveURLs(JobResult result, HadoopApplicationData data) { public static void retrieveURLs(JobResult result, MapreduceApplicationData appData) { Properties jobConf = appData.getConf(); String jobId = appData.getJobId(); - result.jobExecUrl = truncate(jobConf.getProperty(AZK_ATTEMPT_URL), jobId); + result.jobExecUrl = truncate(jobConf.getProperty(AZKABAN_ATTEMPT_URL), jobId); // For jobs launched by Azkaban, we consider different attempts to be // different jobs - result.jobUrl = truncate(jobConf.getProperty(AZK_JOB_URL), jobId); - result.flowExecUrl = truncate(jobConf.getProperty(AZK_EXECUTION_URL), jobId); - result.flowUrl = truncate(jobConf.getProperty(AZK_WORKFLOW_URL), jobId); + result.jobUrl = truncate(jobConf.getProperty(AZKABAN_JOB_URL), jobId); + result.flowExecUrl = truncate(jobConf.getProperty(AZKABAN_EXECUTION_URL), jobId); + result.flowUrl = truncate(jobConf.getProperty(AZKABAN_WORKFLOW_URL), jobId); } public static void retrieveURLs(JobResult result, SparkApplicationData appData) { @@ -60,10 +60,10 @@ public static void retrieveURLs(JobResult result, SparkApplicationData appData) } logger.info("Parsed options:" + StringUtils.join(s, ",")); - result.jobExecUrl = unescapeString(options.get(AZK_ATTEMPT_URL)); - result.jobUrl = unescapeString(options.get(AZK_JOB_URL)); - result.flowExecUrl = unescapeString(options.get(AZK_EXECUTION_URL)); - result.flowUrl = unescapeString(options.get(AZK_WORKFLOW_URL)); + result.jobExecUrl = unescapeString(options.get(AZKABAN_ATTEMPT_URL)); + result.jobUrl = unescapeString(options.get(AZKABAN_JOB_URL)); + result.flowExecUrl = unescapeString(options.get(AZKABAN_EXECUTION_URL)); + result.flowUrl = unescapeString(options.get(AZKABAN_WORKFLOW_URL)); } catch (IllegalArgumentException e) { logger.error("Encountered error while parsing java options into urls: " + e.getMessage()); } diff --git a/app/com/linkedin/drelephant/util/JobTypeConf.java b/app/com/linkedin/drelephant/util/JobTypeConf.java index 7894a1342..b2a5d941b 100644 --- a/app/com/linkedin/drelephant/util/JobTypeConf.java +++ b/app/com/linkedin/drelephant/util/JobTypeConf.java @@ -30,7 +30,7 @@ public class JobTypeConf { // This is a default job type we will add into the type list even not configured. private static final String DEFAULT_TYPE = "HadoopJava"; private static final String CONFIG_FILE_PATH = "job-types.xml"; - private static JobTypeConf _heuristicConfInstance = new JobTypeConf(); + private static final JobTypeConf _heuristicConfInstance = new JobTypeConf(); private List _jobTypeList; diff --git a/app/com/linkedin/drelephant/util/MemoryFormatUtils.java b/app/com/linkedin/drelephant/util/MemoryFormatUtils.java index 4c9b05b52..56a372ce8 100644 --- a/app/com/linkedin/drelephant/util/MemoryFormatUtils.java +++ b/app/com/linkedin/drelephant/util/MemoryFormatUtils.java @@ -14,16 +14,37 @@ private MemoryFormatUtils() { // Do nothing, empty on purpose } - private static final long B = 1L; - private static final long K = 1L << 10; - private static final long M = 1L << 20; - private static final long G = 1L << 30; - private static final long T = 1L << 40; - private static final String[] UNITS = new String[]{"TB", "GB", "MB", "KB", "B"}; - private static final long[] DIVIDERS = new long[]{T, G, M, K, B}; + private static class MemoryUnit { + private final String _name; + private final long _bytes; + + public MemoryUnit(String name, long bytes) { + _name = name; + _bytes = bytes; + } + + public String getName() { + return _name; + } + + public long getBytes() { + return _bytes; + } + + @Override + public String toString() { + return _name; + } + } + + // Units must be in a descent order + private static final MemoryUnit[] UNITS = + new MemoryUnit[]{new MemoryUnit("TB", 1L << 40), new MemoryUnit("GB", 1L << 30), new MemoryUnit("MB", + 1L << 20), new MemoryUnit("KB", 1L << 10), new MemoryUnit("B", 1L)}; + private static final DecimalFormat FORMATTER = new DecimalFormat("#,##0.##"); - private static final Pattern REGEX_MATCHER = - Pattern.compile("((?:\\d+)|(?:\\d*\\.\\d+))\\s*((?:[T|G|M|K])?B?)?", Pattern.CASE_INSENSITIVE); + private static final Pattern REGEX_MATCHER = + Pattern.compile("([-+]?\\d*\\.?\\d+(?:[eE][-+]?\\d+)?)\\s*((?:[T|G|M|K])?B?)?", Pattern.CASE_INSENSITIVE); /** * Given a memory value in bytes, convert it to a string with the unit that round to a >0 integer part. @@ -35,13 +56,14 @@ public static String bytesToString(long value) { if (value < 0) { throw new IllegalArgumentException("Invalid memory size: " + value); } - for (int i = 0; i < DIVIDERS.length; i++) { - if (value >= DIVIDERS[i]) { - double numResult = DIVIDERS[i] > 1 ? (double) value / (double) DIVIDERS[i] : (double) value; - return FORMATTER.format(numResult) + " " + UNITS[i]; + for (int i = 0; i < UNITS.length; i++) { + long bytes = UNITS[i].getBytes(); + if (value >= bytes) { + double numResult = bytes > 1 ? (double) value / (double) bytes : (double) value; + return FORMATTER.format(numResult) + " " + UNITS[i].getName(); } } - return value + " " + UNITS[UNITS.length - 1]; + return value + " " + UNITS[UNITS.length - 1].getName(); } /** @@ -75,8 +97,8 @@ public static long stringToBytes(String formattedString) { unitPart += "B"; } for (int i = 0; i < UNITS.length; i++) { - if (unitPart.equals(UNITS[i])) { - return (long) (numPart * DIVIDERS[i]); + if (unitPart.equals(UNITS[i].getName())) { + return (long) (numPart * UNITS[i].getBytes()); } } throw new IllegalArgumentException("The formatted string [" + formattedString + "] 's unit part [" + unitPart diff --git a/app/com/linkedin/drelephant/util/Utils.java b/app/com/linkedin/drelephant/util/Utils.java index 211a0a800..92e8f3013 100644 --- a/app/com/linkedin/drelephant/util/Utils.java +++ b/app/com/linkedin/drelephant/util/Utils.java @@ -19,6 +19,7 @@ import org.apache.commons.lang.StringUtils; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.mapred.JobConf; +import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.w3c.dom.Document; import org.xml.sax.SAXException; @@ -53,8 +54,6 @@ public static void publishMetrics(MapreduceApplicationData jobData) { return; } - logger.info(metricsPublisher.getClass()); - Properties jobConf = jobData.getConf(); // We may have something to publish, but we don't know until we have iterated through the counters that we have. // We assume that we need to publish something until we find out we don't. @@ -99,7 +98,7 @@ public static void publishMetrics(MapreduceApplicationData jobData) { // event were missing. // Could also mean that some derived values (e.g. hostname to URL, execId translation to an integer, etc.) // may have failed. There is little we can do at this point to fix those, so ignore the exception. - logger.warn("Publish failed:" + e); + logger.log(Level.WARN, "Publish failed:", e); } } @@ -154,7 +153,7 @@ public static String getHadoopVersion() { "Cannot support hadoopVersion [" + hadoopVersion + "] other than classic or yarn currently."); } - return hadoopVersion; + return hadoopVersion.toLowerCase().trim(); } /** diff --git a/app/controllers/Application.java b/app/controllers/Application.java index 46ae6c496..0ab1d1093 100644 --- a/app/controllers/Application.java +++ b/app/controllers/Application.java @@ -40,8 +40,7 @@ import com.avaje.ebean.RawSql; import com.avaje.ebean.RawSqlBuilder; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.util.HeuristicConf; -import com.linkedin.drelephant.util.HeuristicConfData; +import com.linkedin.drelephant.util.HeuristicConfigurationData; public class Application extends Controller { @@ -252,26 +251,17 @@ public static Result help() { //create a map to cache pages. private static void fillHelpPages() { logger.info("Loading help pages for pluggable heuristics"); - List heuristicsConfList = ElephantContext.instance().getHeuristicsConfData(); - for (HeuristicConfData heuristicConf : heuristicsConfList) { + List heuristicsConfList = ElephantContext.instance().getHeuristicsConfigurationData(); + for (HeuristicConfigurationData heuristicConf : heuristicsConfList) { Class viewClass = null; String heuristicName = null; try { String viewName = heuristicConf.getViewName(); logger.info("Loading help page " + viewName); viewClass = Play.current().classloader().loadClass(viewName); - Class heuristicClass = Play.current().classloader().loadClass(heuristicConf.getClassName()); - heuristicName = (String) heuristicClass.getDeclaredField("HEURISTIC_NAME").get(null); + heuristicName = heuristicConf.getHeuristicName(); } catch (ClassNotFoundException e) { throw new RuntimeException("Could not find class " + heuristicConf.getViewName(), e); - } catch (IllegalAccessException e) { - throw new RuntimeException("field HEURISTIC_NAME in class " + heuristicConf.getClassName() - + " is not accessible."); - } catch (NoSuchFieldException e) { - throw new RuntimeException("No field HEURISTIC_NAME in class " + heuristicConf.getClassName()); - } catch (Exception e) { - // More descriptive on other Runtime Exceptions such as NullPointerException IllegalArgumentException - throw new RuntimeException("No valid field HEURISTIC_NAME in class" + heuristicConf.getClassName()); } try { diff --git a/app/org/apache/spark/deploy/history/SparkFSFetcher.scala b/app/org/apache/spark/deploy/history/SparkFSFetcher.scala index 03872f839..5bde4297b 100644 --- a/app/org/apache/spark/deploy/history/SparkFSFetcher.scala +++ b/app/org/apache/spark/deploy/history/SparkFSFetcher.scala @@ -115,10 +115,6 @@ class SparkFSFetcher extends ElephantFetcher[SparkApplicationData] { val elogInfo = EventLoggingListener.parseLoggingInfo(path, fs) new ReplayListenerBus(elogInfo.logPaths, fs, elogInfo.compressionCodec) } - - override def init(threadId: Int): Unit = { - // do nothing - } } private object SparkFSFetcher { diff --git a/app/org/apache/spark/storage/StorageStatusTrackingListener.scala b/app/org/apache/spark/storage/StorageStatusTrackingListener.scala index 87c83c753..56f433f12 100644 --- a/app/org/apache/spark/storage/StorageStatusTrackingListener.scala +++ b/app/org/apache/spark/storage/StorageStatusTrackingListener.scala @@ -10,7 +10,8 @@ import org.apache.spark.scheduler._ * :: DeveloperApi :: * A modified version of StorageStatusListener that tracks the peak memory usage during the entire application runtime. * - * NOTICE: this class copies StorageStatusListener's codes instead of extending from it. + * NOTICE: this class copies StorageStatusListener's codes instead of extending from it, because the methods that + * require being overridden are all in private scope. */ @DeveloperApi class StorageStatusTrackingListener extends SparkListener { diff --git a/app/views/helpSparkBestProperties.scala.html b/app/views/helpSparkBestProperties.scala.html index 5b9bbcf4b..928dc3b27 100644 --- a/app/views/helpSparkBestProperties.scala.html +++ b/app/views/helpSparkBestProperties.scala.html @@ -1,6 +1,38 @@ -

There are some spark property settings that are known to be good choices in most of the scenerios. You better have -a good reason to not use some of the settings.

+

There are some spark property settings that are known to be good choices in most of the scenarios. Spark users are + highly encouraged to adopt best practices whenever possible. Unaligned property settings will either yield to higher + than severe level or merely moderate warnings. Please also refer to + Spark Configuration Doc + for property tuning beyond this page's suggestions.

Suggestions

+ +

spark.serializer

- Use KryoSerializer. + Class to use for serializing objects that will be sent over the network or need to be cached in serialized form. + The default of Java serialization works with any Serializable Java object but is quite slow, so we recommend using + org.apache.spark.serializer.KryoSerializer and configuring Kryo serialization whenever possible. + This also becomes a default choice in Spark 1.3. +

+ +

spark.driver.memory

+

+ spark.driver.memory has a default value of 512m. Allocating more memory than default + is generally acceptable, but users should realize that a too large driver memory ask against the cluster could yield + to long queueing time. Generally we would recommend to keep the memory allocation <=8g. +

+ +

spark.shuffle.manager

+

+ Implementation to use for shuffling data. Available choices are shuffle or sort. + Sort-based shuffle is more memory-efficient and is the default option starting in 1.2. We'd recommend using + sort in almost all scenario. +

+ + +

spark.executor.cores

+

+ spark.executor.cores has a default value of 1. Our Hadoop 2 clusters currently turn + off CPU scheduling, even if you specify a large number for executor-cores, your Spark executor is not guaranteed to + get the specified number of virtual cores (vCores). This will cause the executor to run more concurrent tasks than + vCores available to it, causing frequent context switching and eventually slowing down your application + with high failure rate. We suggest setting executor-cores <= 2.

diff --git a/app/views/helpSparkExecutorLoad.scala.html b/app/views/helpSparkExecutorLoad.scala.html new file mode 100644 index 000000000..1a2cec620 --- /dev/null +++ b/app/views/helpSparkExecutorLoad.scala.html @@ -0,0 +1,58 @@ +

+ Unlike Map/Reduce jobs, a Spark application allocates its resources all at once and never release any during the + the entire runtime process until everything is finished. It is critical to optimize the load balance situation of + executors to avoid excessive usage of the cluster. +

+
Example
+
+

Spark Executor Load Balance

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Severity: Critical +
Average peak storage memory1.09 GB (0 B~2.04 GB)
Average runtime12min 55sec (0 sec~31min 50sec)
Average input size125.95 GB (0 B~304.21 GB)
Average output size0 B (0 B~0 B)
Average task number399 (0~962)
+
+ +

Suggestions

+
1. If there are unused executors (0 tasks, 0 seconds to run and etc.)
+

+ Even though a Spark application asks for all the resources all at once, YARN will only grant resources gradually. Users should try to allocate less in this case since those resources won't be used anyway. An + application will kick off even only part of the resources are allocated. The allocation speed is majorly depending on + how busy the cluster is. However, oftentime, asking for less memory per executor and less executor in total will + always help shorten the entire allocation time. +

+ +
2. Some executors are getting much more tasks than others.
+

+ Each RDD contains a certain number of partitions. While computing those partitions, one partition will be assigned to + only one executor. This means if a to-be-computed RDD has 10 partitions but we have 100 executors in total, only 10 + of the 100 executors will be used. Lots of low partitioned RDD can yield to an unbalanced executor leverage. A way to + improve this is to load RDD from HDFS with more partition numbers or leverage RDD#repartition method. + A good choice of partition number should be equal to or slightly less than k*[executor num] where + k is an integer around 2~5. +

diff --git a/app/views/helpSparkJobRuntime.scala.html b/app/views/helpSparkJobRuntime.scala.html new file mode 100644 index 000000000..9f898922e --- /dev/null +++ b/app/views/helpSparkJobRuntime.scala.html @@ -0,0 +1,19 @@ +

+One Spark application can be broken into multiple jobs and each jobs can be broken into multiple stages. +

+

Suggestions

+ +
1. High failure rate
+

+ High failure rate can have multiple causes. Using more than 2 cores per executor in YARN, unstable implementation, + unbalanced work load, not enough allocated memory, and etc. can all be the causes. Users are highly suggested to look + into detailed error logs and figure out the exact cause. +

+ +
2. Slow job runtime
+

+ Slow job runtime is typically due to unbalanced work load. Partitioning RDD into an enough number (equal or slightly + less than k*[executor num], where k is an integer between 2~5); + However, if the slow job runtime seems to happen for all executors, this might suggest the executor number allocated + is not large enough. +

diff --git a/app/views/helpSparkMemoryLimit.scala.html b/app/views/helpSparkMemoryLimit.scala.html index 26d3e8e49..12957248a 100644 --- a/app/views/helpSparkMemoryLimit.scala.html +++ b/app/views/helpSparkMemoryLimit.scala.html @@ -1,9 +1,106 @@ -

The total memory limit of a Spark job is XXX. You really, really want to be clear of that limit or your job may - occupy too many resources that slow down the entire cluster. Also we keep track of the peak - memory used for storage, normally the memory utilization rate should be higher than [a number]. Otherwise, it means - you don't the memory you claimed to need. +

The current Spark applications lacks elasticity while allocating resources. Unlike Mapreduce jobs that allocates + resources only for one map-reduce process and releases resources gradually during runtime, Spark allocates all the + resources needed for the entire application, and does not release unused ones during the life cycle. Too much meomory + allocation is dangerous for the entire cluster health. As a result, we are setting limits for both the total memory + allowed memory utilization ratio for Spark applications.

+ +
Example
+ +
+

Spark Memory Limit

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Severity: Severe +
Total executor memory allocated1.95 TB
Total driver memory allocated3.2 GB
Total memory allocated for storage743.01 GB
Total memory used at peak287.63 GB
Memory utilization rate0.387
+
+

Suggestions

+
1. If memory utilization rate is too low (< 0.6)

- Make memory utilization rate higher than 0.5. + memory utilization rate = [Peak memory used for storage] / [Total memory allocated for storage] +
+ A low memory utilization typically suggests that an application does not need the total amount of memory that it + currently needs. Users could choose to tune down --executor-memory/spark.executor.memory settings + proportional to the expected utilization rate needed. +

+
+ +
2. If total memory allocated is too large
+

+ There are different levels of RDD persistence (quoting Spark Programming Guide ): +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Storage Level Meaning
MEMORY_ONLY Store RDD as deserialized Java objects in the JVM. If the RDD does not fit in memory, some partitions will + not be cached and will be recomputed on the fly each time they're needed. This is the default level.
MEMORY_AND_DISK Store RDD as deserialized Java objects in the JVM. If the RDD does not fit in memory, store the + partitions that don't fit on disk, and read them from there when they're needed.
MEMORY_ONLY_SER Store RDD as serialized Java objects (one byte array per partition). + This is generally more space-efficient than deserialized objects, especially when using a + fast serializer, but more CPU-intensive to read. +
MEMORY_AND_DISK_SER Similar to MEMORY_ONLY_SER, but spill partitions that don't fit in memory to disk instead of + recomputing them on the fly each time they're needed.
DISK_ONLY Store the RDD partitions only on disk.
MEMORY_ONLY_2, MEMORY_AND_DISK_2, etc. Same as the levels above, but replicate each partition on two cluster nodes.
OFF_HEAP (experimental, not available in LinkedIn) Store RDD in serialized format in Tachyon. + Compared to MEMORY_ONLY_SER, OFF_HEAP reduces garbage collection overhead and allows executors + to be smaller and to share a pool of memory, making it attractive in environments with + large heaps or multiple concurrent applications. Furthermore, as the RDDs reside in Tachyon, + the crash of an executor does not lead to losing the in-memory cache. In this mode, the memory + in Tachyon is discardable. Thus, Tachyon does not attempt to reconstruct a block that it evicts + from memory. +
+

+Caching RDDs into memories is not the only benefits of Spark framework. In fact, even caching RDDs into disk and access it will still be +much faster compared to accessing them in Mapreduce framwork. Spark disk persistence is using the local disk instead of HDFS which is +pretty efficient. Users are encouraged to switch from MEMORY_ONLY to MEMORY_ONLY_SER and even to DISK_ONLY to reduce the total memory needed.

diff --git a/app/views/helpSparkStageRuntime.scala.html b/app/views/helpSparkStageRuntime.scala.html new file mode 100644 index 000000000..e655d8c8d --- /dev/null +++ b/app/views/helpSparkStageRuntime.scala.html @@ -0,0 +1,19 @@ +

+One Spark application can be broken into multiple jobs and each jobs can be broken into multiple stages. +

+

Suggestions

+ +
1. High failure rate
+

+ High failure rate can have multiple causes. Using more than 2 cores per executor in YARN, unstable implementation, + unbalanced work load, not enough allocated memory, and etc. can all be the causes. Users are highly suggested to look + into detailed error logs and figure out the exact cause. +

+ +
2. Slow stage runtime
+

+ Slow stage runtime is typically due to unbalanced work load. Partitioning RDD into an enough number (equal or slightly + less than k*[executor num], where k is an integer between 2~5); + However, if the slow job runtime seems to happen for all executors, this might suggest the executor number allocated + is not large enough. +

diff --git a/conf/elephant-conf.xml b/conf/elephant-conf.xml index 11afa345f..194603301 100644 --- a/conf/elephant-conf.xml +++ b/conf/elephant-conf.xml @@ -9,7 +9,7 @@ classic com.linkedin.drelephant.mapreduce.MapreduceFetcherClassic - + mapreduce yarn com.linkedin.drelephant.mapreduce.MapreduceFetcherYarn @@ -113,7 +113,6 @@ spark Spark Configuration Best Practice com.linkedin.drelephant.spark.heuristics.BestPropertiesConventionHeuristic - views.html.helpSparkBestProperties yarn @@ -124,7 +123,6 @@ spark Spark Memory Limit com.linkedin.drelephant.spark.heuristics.MemoryLimitHeuristic - views.html.helpSparkMemoryLimit yarn @@ -135,8 +133,7 @@ spark Spark Stage Runtime com.linkedin.drelephant.spark.heuristics.StageRuntimeHeuristic - - views.html.helpSparkMemoryLimit + views.html.helpSparkStageRuntime yarn @@ -146,8 +143,7 @@ spark Spark Job Runtime com.linkedin.drelephant.spark.heuristics.JobRuntimeHeuristic - - views.html.helpSparkMemoryLimit + views.html.helpSparkJobRuntime yarn @@ -157,8 +153,7 @@ spark Spark Executor Load Balance com.linkedin.drelephant.spark.heuristics.ExecutorLoadHeuristic - - views.html.helpSparkMemoryLimit + views.html.helpSparkExecutorLoad yarn diff --git a/test/com/linkedin/drelephant/spark/heuristics/BestPropertiesConventionHeuristicTest.java b/test/com/linkedin/drelephant/spark/heuristics/BestPropertiesConventionHeuristicTest.java index 1fae1bf87..eeb2e5ead 100644 --- a/test/com/linkedin/drelephant/spark/heuristics/BestPropertiesConventionHeuristicTest.java +++ b/test/com/linkedin/drelephant/spark/heuristics/BestPropertiesConventionHeuristicTest.java @@ -25,7 +25,9 @@ public void testPropertiesCheck() { assertEquals(Severity.CRITICAL, analyzeJob(getPropertiesAndOverideOne(SPARK_DRIVER_MEMORY, "9G"))); assertEquals(Severity.NONE, analyzeJob(getPropertiesAndOverideOne(SPARK_EXECUTOR_CORES, "1"))); - assertEquals(Severity.CRITICAL, analyzeJob(getPropertiesAndOverideOne(SPARK_EXECUTOR_CORES, "2"))); + assertEquals(Severity.NONE, analyzeJob(getPropertiesAndOverideOne(SPARK_EXECUTOR_CORES, "2"))); + assertEquals(Severity.CRITICAL, analyzeJob(getPropertiesAndOverideOne(SPARK_EXECUTOR_CORES, "4"))); + assertEquals(Severity.MODERATE, analyzeJob(getPropertiesAndOverideOne(SPARK_SERIALIZER, "foo"))); assertEquals(Severity.MODERATE, analyzeJob(getPropertiesAndOverideOne(SPARK_SHUFFLE_MANAGER, "hash"))); diff --git a/test/com/linkedin/drelephant/util/MemoryFormatUtilsTest.java b/test/com/linkedin/drelephant/util/MemoryFormatUtilsTest.java index 396e3ea85..78c85e347 100644 --- a/test/com/linkedin/drelephant/util/MemoryFormatUtilsTest.java +++ b/test/com/linkedin/drelephant/util/MemoryFormatUtilsTest.java @@ -29,6 +29,15 @@ public void testBytesToString() { assertEquals("87,890.62 TB", MemoryFormatUtils.bytesToString((9000L * 10000) << 30)); } + public void testScientificStringNumberToBytes() { + long expectedBytes = (long ) (8.79 * (1 << 20)); + assertEquals(expectedBytes, MemoryFormatUtils.stringToBytes("8.79e0 MB")); + assertEquals(expectedBytes, MemoryFormatUtils.stringToBytes("8.79E0 MB")); + assertEquals(expectedBytes, MemoryFormatUtils.stringToBytes("0.879e1 MB")); + assertEquals(expectedBytes, MemoryFormatUtils.stringToBytes("+0.879e+1 MB")); + assertEquals(expectedBytes, MemoryFormatUtils.stringToBytes("87.9e-1 MB")); + } + public void testStringToBytes() { // Null assertEquals(0L, MemoryFormatUtils.stringToBytes(null)); From 1e12e8cddd222d1908026cc2289a28570bb28c53 Mon Sep 17 00:00:00 2001 From: Yitong Zhou Date: Tue, 2 Jun 2015 11:01:19 -0700 Subject: [PATCH 070/272] Renaming Mapreduce -> MapReduce, Classic -> Hadoop1, Yarn -> Hadoop2 --- .../linkedin/drelephant/ElephantRunner.java | 8 +- ...ider.java => AnalysisProviderHadoop1.java} | 4 +- ...ider.java => AnalysisProviderHadoop2.java} | 4 +- ...ata.java => MapReduceApplicationData.java} | 37 ++++---- ...older.java => MapReduceCounterHolder.java} | 4 +- ...ssic.java => MapReduceFetcherHadoop1.java} | 84 +++++++++---------- ...Yarn.java => MapReduceFetcherHadoop2.java} | 64 +++++++------- ...opTaskData.java => MapReduceTaskData.java} | 10 +-- .../heuristics/GenericDataSkewHeuristic.java | 18 ++-- .../heuristics/JobQueueLimitHeuristic.java | 16 ++-- .../heuristics/MapperDataSkewHeuristic.java | 10 +-- .../heuristics/MapperInputSizeHeuristic.java | 16 ++-- .../heuristics/MapperSpeedHeuristic.java | 16 ++-- .../heuristics/MapperSpillHeuristic.java | 18 ++-- .../heuristics/ReducerDataSkewHeuristic.java | 10 +-- .../heuristics/ReducerTimeHeuristic.java | 12 +-- .../heuristics/ShuffleSortHeuristic.java | 12 +-- .../drelephant/util/InfoExtractor.java | 8 +- app/com/linkedin/drelephant/util/Utils.java | 8 +- conf/elephant-conf.xml | 4 +- .../JobQueueLimitHeuristicTest.java | 21 +++-- .../MapperDataSkewHeuristicTest.java | 25 +++--- .../MapperInputSizeHeuristicTest.java | 19 ++--- .../heuristics/MapperSpeedHeuristicTest.java | 19 ++--- .../heuristics/MapperSpillHeuristicTest.java | 21 +++-- .../ReducerDataSkewHeuristicTest.java | 25 +++--- .../heuristics/ReducerTimeHeuristicTest.java | 15 ++-- .../heuristics/ShuffleSortHeuristicTest.java | 15 ++-- 28 files changed, 257 insertions(+), 266 deletions(-) rename app/com/linkedin/drelephant/analysis/{ClassicAnalysisProvider.java => AnalysisProviderHadoop1.java} (96%) rename app/com/linkedin/drelephant/analysis/{YarnAnalysisProvider.java => AnalysisProviderHadoop2.java} (96%) rename app/com/linkedin/drelephant/mapreduce/{MapreduceApplicationData.java => MapReduceApplicationData.java} (60%) rename app/com/linkedin/drelephant/mapreduce/{HadoopCounterHolder.java => MapReduceCounterHolder.java} (98%) rename app/com/linkedin/drelephant/mapreduce/{MapreduceFetcherClassic.java => MapReduceFetcherHadoop1.java} (85%) rename app/com/linkedin/drelephant/mapreduce/{MapreduceFetcherYarn.java => MapReduceFetcherHadoop2.java} (85%) rename app/com/linkedin/drelephant/mapreduce/{HadoopTaskData.java => MapReduceTaskData.java} (73%) diff --git a/app/com/linkedin/drelephant/ElephantRunner.java b/app/com/linkedin/drelephant/ElephantRunner.java index 511dfad8e..0d14b2854 100644 --- a/app/com/linkedin/drelephant/ElephantRunner.java +++ b/app/com/linkedin/drelephant/ElephantRunner.java @@ -2,9 +2,9 @@ import com.linkedin.drelephant.analysis.AnalysisPromise; import com.linkedin.drelephant.analysis.AnalysisProvider; -import com.linkedin.drelephant.analysis.ClassicAnalysisProvider; +import com.linkedin.drelephant.analysis.AnalysisProviderHadoop1; import com.linkedin.drelephant.analysis.Constants; -import com.linkedin.drelephant.analysis.YarnAnalysisProvider; +import com.linkedin.drelephant.analysis.AnalysisProviderHadoop2; import com.linkedin.drelephant.notifications.EmailThread; import com.linkedin.drelephant.util.Utils; import java.io.IOException; @@ -38,9 +38,9 @@ private void loadAnalysisProvider() { String hadoopVersion = Utils.getHadoopVersion(); configuration.get("mapreduce.framework.name"); if (hadoopVersion.equals("yarn")) { - _analysisProvider = new YarnAnalysisProvider(); + _analysisProvider = new AnalysisProviderHadoop2(); } else { - _analysisProvider = new ClassicAnalysisProvider(); + _analysisProvider = new AnalysisProviderHadoop1(); } if (_analysisProvider == null) { diff --git a/app/com/linkedin/drelephant/analysis/ClassicAnalysisProvider.java b/app/com/linkedin/drelephant/analysis/AnalysisProviderHadoop1.java similarity index 96% rename from app/com/linkedin/drelephant/analysis/ClassicAnalysisProvider.java rename to app/com/linkedin/drelephant/analysis/AnalysisProviderHadoop1.java index f8304cc83..ae5e34973 100644 --- a/app/com/linkedin/drelephant/analysis/ClassicAnalysisProvider.java +++ b/app/com/linkedin/drelephant/analysis/AnalysisProviderHadoop1.java @@ -22,8 +22,8 @@ * This class provides a future list to be fetched/analyzed in Hadoop 1 environment. * */ -public class ClassicAnalysisProvider implements AnalysisProvider { - private static final Logger logger = Logger.getLogger(ClassicAnalysisProvider.class); +public class AnalysisProviderHadoop1 implements AnalysisProvider { + private static final Logger logger = Logger.getLogger(AnalysisProviderHadoop1.class); private JobClient _jobClient; private boolean _firstRun = true; diff --git a/app/com/linkedin/drelephant/analysis/YarnAnalysisProvider.java b/app/com/linkedin/drelephant/analysis/AnalysisProviderHadoop2.java similarity index 96% rename from app/com/linkedin/drelephant/analysis/YarnAnalysisProvider.java rename to app/com/linkedin/drelephant/analysis/AnalysisProviderHadoop2.java index a67bdf5d5..856b97c6b 100644 --- a/app/com/linkedin/drelephant/analysis/YarnAnalysisProvider.java +++ b/app/com/linkedin/drelephant/analysis/AnalysisProviderHadoop2.java @@ -24,8 +24,8 @@ * This class provides a list of analysis promises to be generated under Hadoop YARN environment * */ -public class YarnAnalysisProvider implements AnalysisProvider { - private static final Logger logger = Logger.getLogger(YarnAnalysisProvider.class); +public class AnalysisProviderHadoop2 implements AnalysisProvider { + private static final Logger logger = Logger.getLogger(AnalysisProviderHadoop2.class); // We provide one minute job fetch delay due to the job sending lag from AM/NM to JobHistoryServer HDFS private static final long FETCH_DELAY = 60000; // Generate a token update interval with a random deviation so that it does not update the token exactly at the same diff --git a/app/com/linkedin/drelephant/mapreduce/MapreduceApplicationData.java b/app/com/linkedin/drelephant/mapreduce/MapReduceApplicationData.java similarity index 60% rename from app/com/linkedin/drelephant/mapreduce/MapreduceApplicationData.java rename to app/com/linkedin/drelephant/mapreduce/MapReduceApplicationData.java index cd22a438f..c794d7b93 100644 --- a/app/com/linkedin/drelephant/mapreduce/MapreduceApplicationData.java +++ b/app/com/linkedin/drelephant/mapreduce/MapReduceApplicationData.java @@ -1,87 +1,86 @@ package com.linkedin.drelephant.mapreduce; -import com.linkedin.drelephant.analysis.ApplicationType; import com.linkedin.drelephant.analysis.HadoopApplicationData; import java.util.Properties; -public class MapreduceApplicationData implements HadoopApplicationData { +public class MapReduceApplicationData implements HadoopApplicationData { private String _jobId = ""; private String _username = ""; private String _url = ""; private String _jobName = ""; private long _startTime = 0; private long _finishTime = 0; - private HadoopCounterHolder _counterHolder; - private HadoopTaskData[] _mapperData; - private HadoopTaskData[] _reducerData; + private MapReduceCounterHolder _counterHolder; + private MapReduceTaskData[] _mapperData; + private MapReduceTaskData[] _reducerData; private Properties _jobConf; private boolean _isRetry = false; - public MapreduceApplicationData setRetry(boolean isRetry) { + public MapReduceApplicationData setRetry(boolean isRetry) { this._isRetry = isRetry; return this; } - public MapreduceApplicationData setJobId(String jobId) { + public MapReduceApplicationData setJobId(String jobId) { this._jobId = jobId; return this; } - public MapreduceApplicationData setJobName(String jobName) { + public MapReduceApplicationData setJobName(String jobName) { this._jobName = jobName; return this; } - public MapreduceApplicationData setUsername(String username) { + public MapReduceApplicationData setUsername(String username) { this._username = username; return this; } - public MapreduceApplicationData setStartTime(long startTime) { + public MapReduceApplicationData setStartTime(long startTime) { this._startTime = startTime; return this; } - public MapreduceApplicationData setFinishTime(long finishTime) { + public MapReduceApplicationData setFinishTime(long finishTime) { this._finishTime = finishTime; return this; } - public MapreduceApplicationData setUrl(String url) { + public MapReduceApplicationData setUrl(String url) { this._url = url; return this; } - public MapreduceApplicationData setCounters(HadoopCounterHolder counterHolder) { + public MapReduceApplicationData setCounters(MapReduceCounterHolder counterHolder) { this._counterHolder = counterHolder; return this; } - public MapreduceApplicationData setMapperData(HadoopTaskData[] mappers) { + public MapReduceApplicationData setMapperData(MapReduceTaskData[] mappers) { this._mapperData = mappers; return this; } - public MapreduceApplicationData setReducerData(HadoopTaskData[] reducers) { + public MapReduceApplicationData setReducerData(MapReduceTaskData[] reducers) { this._reducerData = reducers; return this; } - public MapreduceApplicationData setJobConf(Properties jobConf) { + public MapReduceApplicationData setJobConf(Properties jobConf) { this._jobConf = jobConf; return this; } - public HadoopCounterHolder getCounters() { + public MapReduceCounterHolder getCounters() { return _counterHolder; } - public HadoopTaskData[] getMapperData() { + public MapReduceTaskData[] getMapperData() { return _mapperData; } - public HadoopTaskData[] getReducerData() { + public MapReduceTaskData[] getReducerData() { return _reducerData; } diff --git a/app/com/linkedin/drelephant/mapreduce/HadoopCounterHolder.java b/app/com/linkedin/drelephant/mapreduce/MapReduceCounterHolder.java similarity index 98% rename from app/com/linkedin/drelephant/mapreduce/HadoopCounterHolder.java rename to app/com/linkedin/drelephant/mapreduce/MapReduceCounterHolder.java index 2132cf216..07e26eab9 100644 --- a/app/com/linkedin/drelephant/mapreduce/HadoopCounterHolder.java +++ b/app/com/linkedin/drelephant/mapreduce/MapReduceCounterHolder.java @@ -6,7 +6,7 @@ import java.util.Set; -public class HadoopCounterHolder { +public class MapReduceCounterHolder { // This is a map of group to all the counters in the group and their values. private final Map> _pubCounters; @@ -15,7 +15,7 @@ public String toString() { return _pubCounters.toString(); } - public HadoopCounterHolder() { + public MapReduceCounterHolder() { _pubCounters = new HashMap>(8); } diff --git a/app/com/linkedin/drelephant/mapreduce/MapreduceFetcherClassic.java b/app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop1.java similarity index 85% rename from app/com/linkedin/drelephant/mapreduce/MapreduceFetcherClassic.java rename to app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop1.java index 5dfd11981..7df2d9b33 100644 --- a/app/com/linkedin/drelephant/mapreduce/MapreduceFetcherClassic.java +++ b/app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop1.java @@ -2,7 +2,7 @@ import com.linkedin.drelephant.analysis.ElephantFetcher; import com.linkedin.drelephant.analysis.Constants; -import com.linkedin.drelephant.mapreduce.HadoopCounterHolder.CounterName; +import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder.CounterName; import com.linkedin.drelephant.math.Statistics; import com.linkedin.drelephant.util.Utils; import java.io.IOException; @@ -36,27 +36,27 @@ import org.jsoup.select.Elements; -public class MapreduceFetcherClassic implements ElephantFetcher { +public class MapReduceFetcherHadoop1 implements ElephantFetcher { private static final Logger logger = Logger.getLogger(ElephantFetcher.class); private static final int DEFAULT_RETRY = 2; private JobConf _conf; - private Map _failedJobsInWait = - new ConcurrentHashMap(); - private Map _failedJobsInProgress = - new ConcurrentHashMap(); + private Map _failedJobsInWait = + new ConcurrentHashMap(); + private Map _failedJobsInProgress = + new ConcurrentHashMap(); private String _jobtrackerHttpRoot; - public MapreduceFetcherClassic() + public MapReduceFetcherHadoop1() throws IOException { _conf = new JobConf(); _jobtrackerHttpRoot = "http://" + _conf.get("mapred.job.tracker.http.address") + "/"; } @Override - public MapreduceApplicationData fetchData(String id) + public MapReduceApplicationData fetchData(String id) throws IOException, AuthenticationException { - MapreduceApplicationData jobData = new MapreduceApplicationData(); + MapReduceApplicationData jobData = new MapReduceApplicationData(); jobData.setJobId(id); try { JobID jobId = JobID.forName(jobData.getJobId()); @@ -94,31 +94,31 @@ public MapreduceApplicationData fetchData(String id) .setJobName(jobName); // Fetch job counter - HadoopCounterHolder counterHolder = fetchCounter(job.getCounters()); + MapReduceCounterHolder counterHolder = fetchCounter(job.getCounters()); int sampleSize = Constants.SHUFFLE_SORT_MAX_SAMPLE_SIZE; // Fetch mapper task data - List mapperList = new ArrayList(); + List mapperList = new ArrayList(); Statistics.shuffleArraySample(mapperTasks, sampleSize); for (int i = 0; i < mapperTasks.length; i++) { - HadoopTaskData mapper = fetchTaskData(jobUrl, mapperTasks[i], true, (i < sampleSize)); + MapReduceTaskData mapper = fetchTaskData(jobUrl, mapperTasks[i], true, (i < sampleSize)); if (mapper != null) { mapperList.add(mapper); } } - HadoopTaskData[] mappers = mapperList.toArray(new HadoopTaskData[mapperList.size()]); + MapReduceTaskData[] mappers = mapperList.toArray(new MapReduceTaskData[mapperList.size()]); // Fetch reducer task data - List reducerList = new ArrayList(); + List reducerList = new ArrayList(); Statistics.shuffleArraySample(reducerTasks, sampleSize); for (int i = 0; i < reducerTasks.length; i++) { - HadoopTaskData reducer = fetchTaskData(jobUrl, reducerTasks[i], false, (i < sampleSize)); + MapReduceTaskData reducer = fetchTaskData(jobUrl, reducerTasks[i], false, (i < sampleSize)); if (reducer != null) { reducerList.add(reducer); } } - HadoopTaskData[] reducers = reducerList.toArray(new HadoopTaskData[reducerList.size()]); + MapReduceTaskData[] reducers = reducerList.toArray(new MapReduceTaskData[reducerList.size()]); // Fetch job config Properties jobConf = fetchJobConf(job); @@ -159,7 +159,7 @@ private Properties fetchJobConfFromURL(String jobconfUrl) return properties; } - private HadoopTaskData fetchTaskData(String jobDetailUrl, TaskReport task, boolean isMapper, boolean sampled) + private MapReduceTaskData fetchTaskData(String jobDetailUrl, TaskReport task, boolean isMapper, boolean sampled) throws IOException, AuthenticationException { if (task.getCurrentStatus() != TIPStatus.COMPLETE) { @@ -167,17 +167,17 @@ private HadoopTaskData fetchTaskData(String jobDetailUrl, TaskReport task, boole return null; } - HadoopCounterHolder taskCounter = fetchCounter(task.getCounters()); + MapReduceCounterHolder taskCounter = fetchCounter(task.getCounters()); // This is not a sampled task, return task data with only counters if (!sampled) { - return new HadoopTaskData(taskCounter); + return new MapReduceTaskData(taskCounter); } // This is a sampled task, fetch task time from task detail page String taskDetailsUrl = getTaskDetailsPageUrl(jobDetailUrl, task.getTaskID().toString()); long[] time = fetchTaskDetails(taskDetailsUrl, isMapper); - return new HadoopTaskData(taskCounter, time); + return new MapReduceTaskData(taskCounter, time); } private String getJobconfUrl(RunningJob job) { @@ -240,8 +240,8 @@ private long[] tryExtractDetailFromRow(Element row, boolean isMapper) return null; } - private HadoopCounterHolder fetchCounter(Counters counters) { - HadoopCounterHolder holder = new HadoopCounterHolder(); + private MapReduceCounterHolder fetchCounter(Counters counters) { + MapReduceCounterHolder holder = new MapReduceCounterHolder(); for (Counters.Group group : counters) { for (Counter ctr : group) { CounterName cn = CounterName.getCounterFromName(ctr.getName()); @@ -253,7 +253,7 @@ private HadoopCounterHolder fetchCounter(Counters counters) { return holder; } - private void addJobToRetryList(MapreduceApplicationData job) { + private void addJobToRetryList(MapReduceApplicationData job) { if (_failedJobsInProgress.containsKey(job)) { // This is old retry job int retryLeft = _failedJobsInProgress.get(job); @@ -272,7 +272,7 @@ private void addJobToRetryList(MapreduceApplicationData job) { } // Return false if this is a real nodata received job ( 0 mapper & 0 reducer ) rather than a retired job - private boolean checkRetiredAndFetchJobData(MapreduceApplicationData jobData) + private boolean checkRetiredAndFetchJobData(MapReduceApplicationData jobData) throws IOException, AuthenticationException { String jobUrl = _jobtrackerHttpRoot + "jobdetails.jsp?jobid=" + jobData.getJobId(); Document doc = ThreadContextMR1.fetchHtmlDoc(jobUrl); @@ -300,10 +300,10 @@ private boolean checkRetiredAndFetchJobData(MapreduceApplicationData jobData) return false; } - HadoopCounterHolder jobCounter = fetchJobCounterForRetiredJob(doc); + MapReduceCounterHolder jobCounter = fetchJobCounterForRetiredJob(doc); Properties jobConf = fetchJobConfFromURL(confUrl); - HadoopTaskData[] mapperData = fetchAllTaskDataForRetiredJob(mapperUrl, true); - HadoopTaskData[] reducerData = fetchAllTaskDataForRetiredJob(reducerUrl, false); + MapReduceTaskData[] mapperData = fetchAllTaskDataForRetiredJob(mapperUrl, true); + MapReduceTaskData[] reducerData = fetchAllTaskDataForRetiredJob(reducerUrl, false); long startTime = fetchStartTimeForRetiredJob(doc, jobUrl); // We can scrape the job tracker page to get the finish time, but in order to be consistent with the @@ -317,9 +317,9 @@ private boolean checkRetiredAndFetchJobData(MapreduceApplicationData jobData) } // Fetch job counter from job's main page - private HadoopCounterHolder fetchJobCounterForRetiredJob(Document doc) + private MapReduceCounterHolder fetchJobCounterForRetiredJob(Document doc) throws IOException, AuthenticationException { - HadoopCounterHolder holder = new HadoopCounterHolder(); + MapReduceCounterHolder holder = new MapReduceCounterHolder(); Elements rows = doc.select("table").select("tr"); for (Element row : rows) { Elements cells = row.select("> td"); @@ -343,9 +343,9 @@ private HadoopCounterHolder fetchJobCounterForRetiredJob(Document doc) } // Fetch task counter from task's counter page - public HadoopCounterHolder fetchTaskCounterForRetiredJob(String taskCounterUrl) + public MapReduceCounterHolder fetchTaskCounterForRetiredJob(String taskCounterUrl) throws IOException, AuthenticationException { - HadoopCounterHolder holder = new HadoopCounterHolder(); + MapReduceCounterHolder holder = new MapReduceCounterHolder(); Document doc = ThreadContextMR1.fetchHtmlDoc(taskCounterUrl); Elements rows = doc.select("table").select("tr"); for (Element row : rows) { @@ -381,27 +381,27 @@ private long fetchStartTimeForRetiredJob(Document doc, String jobUrl) throw new IOException("Unable to fetch start time data from job page in URL : " + jobUrl); } - private HadoopTaskData[] fetchAllTaskDataForRetiredJob(String taskIndexPage, boolean isMapper) + private MapReduceTaskData[] fetchAllTaskDataForRetiredJob(String taskIndexPage, boolean isMapper) throws IOException, AuthenticationException { - List taskList = new ArrayList(); + List taskList = new ArrayList(); Document doc = ThreadContextMR1.fetchHtmlDoc(taskIndexPage); Elements hrefs = doc.select("a"); for (Element href : hrefs) { String hrefStr = href.attr("href"); if (hrefStr.contains("taskdetailshistory.jsp?")) { String taskUrl = _jobtrackerHttpRoot + hrefStr; - HadoopTaskData tdata = fetchTaskDataForRetiredJob(taskUrl, isMapper); + MapReduceTaskData tdata = fetchTaskDataForRetiredJob(taskUrl, isMapper); taskList.add(tdata); } } - return taskList.toArray(new HadoopTaskData[taskList.size()]); + return taskList.toArray(new MapReduceTaskData[taskList.size()]); } - private HadoopTaskData fetchTaskDataForRetiredJob(String taskDetailUrl, boolean isMapper) + private MapReduceTaskData fetchTaskDataForRetiredJob(String taskDetailUrl, boolean isMapper) throws IOException, AuthenticationException { Document doc = ThreadContextMR1.fetchHtmlDoc(taskDetailUrl); Elements rows = doc.select("table").select("tr"); - HadoopTaskData taskData = null; + MapReduceTaskData taskData = null; for (Element row : rows) { try { taskData = tryExtractTaskDataForRetiredJob(row, isMapper); @@ -415,7 +415,7 @@ private HadoopTaskData fetchTaskDataForRetiredJob(String taskDetailUrl, boolean throw new IOException("No valid time data found from task detail page. TASK URL=" + taskDetailUrl); } - private HadoopTaskData tryExtractTaskDataForRetiredJob(Element row, boolean isMapper) + private MapReduceTaskData tryExtractTaskDataForRetiredJob(Element row, boolean isMapper) throws IOException, ParseException, AuthenticationException { Elements cells = row.select("> td"); if (isMapper && cells.size() == 7) { @@ -424,8 +424,8 @@ private HadoopTaskData tryExtractTaskDataForRetiredJob(Element row, boolean isMa long totalTimeMs = parseTimeInMs(cells.get(2).html().trim()); long[] time = new long[]{totalTimeMs, 0, 0}; String counterUrl = _jobtrackerHttpRoot + cells.get(6).select("a").attr("href"); - HadoopCounterHolder ctrholder = fetchTaskCounterForRetiredJob(counterUrl); - return new HadoopTaskData(ctrholder, time); + MapReduceCounterHolder ctrholder = fetchTaskCounterForRetiredJob(counterUrl); + return new MapReduceTaskData(ctrholder, time); } } else if (!isMapper && cells.size() == 9) { String c = cells.get(8).text().trim(); @@ -435,8 +435,8 @@ private HadoopTaskData tryExtractTaskDataForRetiredJob(Element row, boolean isMa long totalTimeMs = parseTimeInMs(cells.get(4).html().trim()); long[] time = new long[]{totalTimeMs, shuffleTimeMs, sortTimeMs}; String counterUrl = _jobtrackerHttpRoot + cells.get(8).select("a").attr("href"); - HadoopCounterHolder ctrholder = fetchTaskCounterForRetiredJob(counterUrl); - return new HadoopTaskData(ctrholder, time); + MapReduceCounterHolder ctrholder = fetchTaskCounterForRetiredJob(counterUrl); + return new MapReduceTaskData(ctrholder, time); } } return null; diff --git a/app/com/linkedin/drelephant/mapreduce/MapreduceFetcherYarn.java b/app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop2.java similarity index 85% rename from app/com/linkedin/drelephant/mapreduce/MapreduceFetcherYarn.java rename to app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop2.java index 0c737036e..3f4aa5698 100644 --- a/app/com/linkedin/drelephant/mapreduce/MapreduceFetcherYarn.java +++ b/app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop2.java @@ -2,7 +2,7 @@ import com.google.common.collect.Lists; import com.linkedin.drelephant.analysis.ElephantFetcher; -import com.linkedin.drelephant.mapreduce.HadoopCounterHolder.CounterName; +import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder.CounterName; import com.linkedin.drelephant.math.Statistics; import com.linkedin.drelephant.util.Utils; import java.io.IOException; @@ -26,7 +26,7 @@ import org.codehaus.jackson.map.ObjectMapper; -public class MapreduceFetcherYarn implements ElephantFetcher { +public class MapReduceFetcherHadoop2 implements ElephantFetcher { private static final Logger logger = Logger.getLogger(ElephantFetcher.class); // We provide one minute job fetch delay due to the job sending lag from AM/NM to JobHistoryServer HDFS private static final long FETCH_DELAY = 60000; @@ -38,7 +38,7 @@ public class MapreduceFetcherYarn implements ElephantFetcher fetchJobList() throws IOException, AuthenticationException { + public List fetchJobList() throws IOException, AuthenticationException { - List jobList; + List jobList; int retrySize = 0; // There is a lag of job data from AM/NM to JobHistoryServer HDFS, we shouldn't use the current @@ -73,7 +73,7 @@ public List fetchJobList() throws IOException, Authent if (_firstRun) { _firstRun = false; } else { - List retryList = _retryFactory.fetchAllRetryJobs(); + List retryList = _retryFactory.fetchAllRetryJobs(); // If not first time, also fetch jobs that need to retry retrySize = retryList.size(); jobList.addAll(retryList); @@ -93,12 +93,12 @@ private boolean checkDBforJob(String jobId) { } // Clear all data stored on the job object - private void clearJobData(MapreduceApplicationData jobData) { + private void clearJobData(MapReduceApplicationData jobData) { jobData.setCounters(null).setJobConf(null).setMapperData(null).setReducerData(null); } // OnJobFinish Add to retry list upon failure - public void finishJob(MapreduceApplicationData jobData, boolean success) { + public void finishJob(MapReduceApplicationData jobData, boolean success) { if (!success) { if (!jobData.isRetryJob()) { jobData.setRetry(true); @@ -116,14 +116,14 @@ public void finishJob(MapreduceApplicationData jobData, boolean success) { // Fetch job detailed data. Return true on success @Override - public MapreduceApplicationData fetchData(String appId) throws IOException, AuthenticationException { - MapreduceApplicationData jobData = new MapreduceApplicationData(); + public MapReduceApplicationData fetchData(String appId) throws IOException, AuthenticationException { + MapReduceApplicationData jobData = new MapReduceApplicationData(); String jobId = Utils.getJobIdFromApplicationId(appId); jobData.setJobId(jobId); try { // Fetch job counter URL jobCounterURL = _urlFactory.getJobCounterURL(jobId); - HadoopCounterHolder jobCounter = _jsonFactory.getJobCounter(jobCounterURL); + MapReduceCounterHolder jobCounter = _jsonFactory.getJobCounter(jobCounterURL); // Fetch job config URL jobConfigURL = _urlFactory.getJobConfigURL(jobId); @@ -131,12 +131,12 @@ public MapreduceApplicationData fetchData(String appId) throws IOException, Auth // Fetch task data URL taskListURL = _urlFactory.getTaskListURL(jobId); - List mapperList = new ArrayList(); - List reducerList = new ArrayList(); + List mapperList = new ArrayList(); + List reducerList = new ArrayList(); _jsonFactory.getTaskDataAll(taskListURL, jobId, mapperList, reducerList); - HadoopTaskData[] mapperData = mapperList.toArray(new HadoopTaskData[mapperList.size()]); - HadoopTaskData[] reducerData = reducerList.toArray(new HadoopTaskData[reducerList.size()]); + MapReduceTaskData[] mapperData = mapperList.toArray(new MapReduceTaskData[mapperList.size()]); + MapReduceTaskData[] reducerData = reducerList.toArray(new MapReduceTaskData[reducerList.size()]); jobData.setCounters(jobCounter).setMapperData(mapperData).setReducerData(reducerData).setJobConf(jobConf); @@ -209,8 +209,8 @@ private URL getTaskAttemptURL(String jobId, String taskId, String attemptId) thr } private class JSONFactory { - private List getJobData(URL url, boolean checkDB) throws IOException, AuthenticationException { - List jobList = new ArrayList(); + private List getJobData(URL url, boolean checkDB) throws IOException, AuthenticationException { + List jobList = new ArrayList(); JsonNode rootNode = ThreadContextMR2.readJsonNode(url); JsonNode jobs = rootNode.path("jobs").path("job"); @@ -240,7 +240,7 @@ private List getJobData(URL url, boolean checkDB) thro "user": "ssubrama" } */ - MapreduceApplicationData jobData = new MapreduceApplicationData(); + MapReduceApplicationData jobData = new MapReduceApplicationData(); jobData.setJobId(jobId).setUsername(job.get("user").getValueAsText()) .setJobName(job.get("name").getValueAsText()).setUrl(getJobDetailURL(jobId)) .setStartTime(job.get("startTime").getLongValue()) @@ -265,9 +265,9 @@ private Properties getProperties(URL url) throws IOException, AuthenticationExce return jobConf; } - private HadoopCounterHolder getJobCounter(URL url) + private MapReduceCounterHolder getJobCounter(URL url) throws IOException, AuthenticationException { - HadoopCounterHolder holder = new HadoopCounterHolder(); + MapReduceCounterHolder holder = new MapReduceCounterHolder(); JsonNode rootNode = ThreadContextMR2.readJsonNode(url); JsonNode groups = rootNode.path("jobCounters").path("counterGroup"); @@ -284,10 +284,10 @@ private HadoopCounterHolder getJobCounter(URL url) return holder; } - private HadoopCounterHolder getTaskCounter(URL url) throws IOException, AuthenticationException { + private MapReduceCounterHolder getTaskCounter(URL url) throws IOException, AuthenticationException { JsonNode rootNode = ThreadContextMR2.readJsonNode(url); JsonNode groups = rootNode.path("jobTaskCounters").path("taskCounterGroup"); - HadoopCounterHolder holder = new HadoopCounterHolder(); + MapReduceCounterHolder holder = new MapReduceCounterHolder(); for (JsonNode group : groups) { for (JsonNode counter : group.path("counter")) { @@ -323,7 +323,7 @@ private long[] getTaskExecTime(URL url) throws IOException, AuthenticationExcept return time; } - private void getTaskDataAll(URL url, String jobId, List mapperList, List reducerList) + private void getTaskDataAll(URL url, String jobId, List mapperList, List reducerList) throws IOException, AuthenticationException { JsonNode rootNode = ThreadContextMR2.readJsonNode(url); @@ -340,12 +340,12 @@ private void getTaskDataAll(URL url, String jobId, List mapperLi boolean isMapper = task.get("type").getValueAsText().equals("MAP"); URL taskCounterURL = getTaskCounterURL(jobId, taskId); - HadoopCounterHolder taskCounter = getTaskCounter(taskCounterURL); + MapReduceCounterHolder taskCounter = getTaskCounter(taskCounterURL); URL taskAttemptURL = getTaskAttemptURL(jobId, taskId, attemptId); long[] taskExecTime = getTaskExecTime(taskAttemptURL); - HadoopTaskData taskData = new HadoopTaskData(taskCounter, taskExecTime); + MapReduceTaskData taskData = new MapReduceTaskData(taskCounter, taskExecTime); if (isMapper) { mapperList.add(taskData); } else { @@ -357,10 +357,10 @@ private void getTaskDataAll(URL url, String jobId, List mapperLi private class RetryFactory { private static final int DEFAULT_RETRY = 2; - private Map _retryMapInWait = new ConcurrentHashMap(); - private Map _retryMapInProgress = new ConcurrentHashMap(); + private Map _retryMapInWait = new ConcurrentHashMap(); + private Map _retryMapInProgress = new ConcurrentHashMap(); - private void addJobToRetryList(MapreduceApplicationData job) { + private void addJobToRetryList(MapReduceApplicationData job) { if (_retryMapInProgress.containsKey(job)) { // This is old retry job int retryLeft = _retryMapInProgress.get(job); @@ -377,9 +377,9 @@ private void addJobToRetryList(MapreduceApplicationData job) { } } - private List fetchAllRetryJobs() { - List retryList = new ArrayList(); - for (MapreduceApplicationData job : _retryMapInWait.keySet()) { + private List fetchAllRetryJobs() { + List retryList = new ArrayList(); + for (MapReduceApplicationData job : _retryMapInWait.keySet()) { int retry = _retryMapInWait.get(job); _retryMapInWait.remove(job); retryList.add(job); @@ -388,7 +388,7 @@ private List fetchAllRetryJobs() { return Lists.newArrayList(retryList); } - private void checkAndRemoveFromRetryList(MapreduceApplicationData jobData) { + private void checkAndRemoveFromRetryList(MapReduceApplicationData jobData) { if (_retryMapInProgress.containsKey(jobData)) { _retryMapInProgress.remove(jobData); } diff --git a/app/com/linkedin/drelephant/mapreduce/HadoopTaskData.java b/app/com/linkedin/drelephant/mapreduce/MapReduceTaskData.java similarity index 73% rename from app/com/linkedin/drelephant/mapreduce/HadoopTaskData.java rename to app/com/linkedin/drelephant/mapreduce/MapReduceTaskData.java index befbf93e2..17f98bdeb 100644 --- a/app/com/linkedin/drelephant/mapreduce/HadoopTaskData.java +++ b/app/com/linkedin/drelephant/mapreduce/MapReduceTaskData.java @@ -1,13 +1,13 @@ package com.linkedin.drelephant.mapreduce; -public class HadoopTaskData { - private HadoopCounterHolder _counterHolder; +public class MapReduceTaskData { + private MapReduceCounterHolder _counterHolder; private long _totalTimeMs = 0; private long _shuffleTimeMs = 0; private long _sortTimeMs = 0; private boolean _timed = false; - public HadoopTaskData(HadoopCounterHolder counterHolder, long[] time) { + public MapReduceTaskData(MapReduceCounterHolder counterHolder, long[] time) { this._counterHolder = counterHolder; this._totalTimeMs = time[0]; this._shuffleTimeMs = time[1]; @@ -15,11 +15,11 @@ public HadoopTaskData(HadoopCounterHolder counterHolder, long[] time) { this._timed = true; } - public HadoopTaskData(HadoopCounterHolder counterHolder) { + public MapReduceTaskData(MapReduceCounterHolder counterHolder) { this._counterHolder = counterHolder; } - public HadoopCounterHolder getCounters() { + public MapReduceCounterHolder getCounters() { return _counterHolder; } diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/GenericDataSkewHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericDataSkewHeuristic.java index 6c23cf45d..c4be200fb 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/GenericDataSkewHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericDataSkewHeuristic.java @@ -4,15 +4,15 @@ import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.mapreduce.HadoopCounterHolder; -import com.linkedin.drelephant.mapreduce.MapreduceApplicationData; -import com.linkedin.drelephant.mapreduce.HadoopTaskData; +import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; +import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.MapReduceTaskData; import com.linkedin.drelephant.math.Statistics; import org.apache.commons.io.FileUtils; -public abstract class GenericDataSkewHeuristic implements Heuristic { - private HadoopCounterHolder.CounterName _counterName; +public abstract class GenericDataSkewHeuristic implements Heuristic { + private MapReduceCounterHolder.CounterName _counterName; private String _heuristicName; @Override @@ -20,16 +20,16 @@ public String getHeuristicName() { return _heuristicName; } - protected GenericDataSkewHeuristic(HadoopCounterHolder.CounterName counterName, String heuristicName) { + protected GenericDataSkewHeuristic(MapReduceCounterHolder.CounterName counterName, String heuristicName) { this._counterName = counterName; this._heuristicName = heuristicName; } - protected abstract HadoopTaskData[] getTasks(MapreduceApplicationData data); + protected abstract MapReduceTaskData[] getTasks(MapReduceApplicationData data); @Override - public HeuristicResult apply(MapreduceApplicationData data) { - HadoopTaskData[] tasks = getTasks(data); + public HeuristicResult apply(MapReduceApplicationData data) { + MapReduceTaskData[] tasks = getTasks(data); //Gather data long[] inputBytes = new long[tasks.length]; diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/JobQueueLimitHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/JobQueueLimitHeuristic.java index 29150a76f..6ada4f2f4 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/JobQueueLimitHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/JobQueueLimitHeuristic.java @@ -1,7 +1,7 @@ package com.linkedin.drelephant.mapreduce.heuristics; -import com.linkedin.drelephant.mapreduce.MapreduceApplicationData; -import com.linkedin.drelephant.mapreduce.HadoopTaskData; +import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.MapReduceTaskData; import java.util.Properties; import java.util.concurrent.TimeUnit; @@ -10,7 +10,7 @@ import com.linkedin.drelephant.analysis.Severity; -public class JobQueueLimitHeuristic implements Heuristic { +public class JobQueueLimitHeuristic implements Heuristic { public static final String HEURISTIC_NAME = "Queue Time Limit"; @Override @@ -19,7 +19,7 @@ public String getHeuristicName() { } @Override - public HeuristicResult apply(MapreduceApplicationData data) { + public HeuristicResult apply(MapReduceApplicationData data) { HeuristicResult result = new HeuristicResult(HEURISTIC_NAME, Severity.NONE); Properties jobConf = data.getConf(); long queueTimeoutLimitMs = TimeUnit.MINUTES.toMillis(15); @@ -31,8 +31,8 @@ public HeuristicResult apply(MapreduceApplicationData data) { } // Compute severity if job is submitted to default queue else set severity to NONE. - HadoopTaskData[] mapTasks = data.getMapperData(); - HadoopTaskData[] redTasks = data.getReducerData(); + MapReduceTaskData[] mapTasks = data.getMapperData(); + MapReduceTaskData[] redTasks = data.getReducerData(); Severity[] mapTasksSeverity = new Severity[mapTasks.length]; Severity[] redTasksSeverity = new Severity[redTasks.length]; if (queueName.equals("default")) { @@ -64,10 +64,10 @@ public HeuristicResult apply(MapreduceApplicationData data) { return result; } - private Severity[] getTasksSeverity(HadoopTaskData[] tasks, long queueTimeout) { + private Severity[] getTasksSeverity(MapReduceTaskData[] tasks, long queueTimeout) { Severity[] tasksSeverity = new Severity[tasks.length]; int i = 0; - for (HadoopTaskData task : tasks) { + for (MapReduceTaskData task : tasks) { tasksSeverity[i] = getQueueLimitSeverity(task.getTotalRunTimeMs(), queueTimeout); i++; } diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperDataSkewHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperDataSkewHeuristic.java index 3cbe494fa..3e17422f3 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperDataSkewHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperDataSkewHeuristic.java @@ -1,19 +1,19 @@ package com.linkedin.drelephant.mapreduce.heuristics; -import com.linkedin.drelephant.mapreduce.HadoopCounterHolder; -import com.linkedin.drelephant.mapreduce.MapreduceApplicationData; -import com.linkedin.drelephant.mapreduce.HadoopTaskData; +import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; +import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.MapReduceTaskData; public class MapperDataSkewHeuristic extends GenericDataSkewHeuristic { public static final String HEURISTIC_NAME = "Mapper Data Skew"; public MapperDataSkewHeuristic() { - super(HadoopCounterHolder.CounterName.HDFS_BYTES_READ, HEURISTIC_NAME); + super(MapReduceCounterHolder.CounterName.HDFS_BYTES_READ, HEURISTIC_NAME); } @Override - protected HadoopTaskData[] getTasks(MapreduceApplicationData data) { + protected MapReduceTaskData[] getTasks(MapReduceApplicationData data) { return data.getMapperData(); } } diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperInputSizeHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperInputSizeHeuristic.java index 22c1b8aef..c7f2f1dfa 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperInputSizeHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperInputSizeHeuristic.java @@ -1,7 +1,7 @@ package com.linkedin.drelephant.mapreduce.heuristics; -import com.linkedin.drelephant.mapreduce.MapreduceApplicationData; -import com.linkedin.drelephant.mapreduce.HadoopCounterHolder; +import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; import java.util.ArrayList; import java.util.List; @@ -9,13 +9,13 @@ import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.mapreduce.HadoopTaskData; +import com.linkedin.drelephant.mapreduce.MapReduceTaskData; import com.linkedin.drelephant.math.Statistics; import org.apache.commons.io.FileUtils; -public class MapperInputSizeHeuristic implements Heuristic { +public class MapperInputSizeHeuristic implements Heuristic { public static final String HEURISTIC_NAME = "Mapper Input Size"; @Override @@ -24,14 +24,14 @@ public String getHeuristicName() { } @Override - public HeuristicResult apply(MapreduceApplicationData data) { - HadoopTaskData[] tasks = data.getMapperData(); + public HeuristicResult apply(MapReduceApplicationData data) { + MapReduceTaskData[] tasks = data.getMapperData(); List inputBytes = new ArrayList(); List runtimesMs = new ArrayList(); - for (HadoopTaskData task : tasks) { - inputBytes.add(task.getCounters().get(HadoopCounterHolder.CounterName.HDFS_BYTES_READ)); + for (MapReduceTaskData task : tasks) { + inputBytes.add(task.getCounters().get(MapReduceCounterHolder.CounterName.HDFS_BYTES_READ)); if (task.timed()) { runtimesMs.add(task.getTotalRunTimeMs()); } diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristic.java index 5892e0146..7ac1bdd0c 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristic.java @@ -1,7 +1,7 @@ package com.linkedin.drelephant.mapreduce.heuristics; -import com.linkedin.drelephant.mapreduce.MapreduceApplicationData; -import com.linkedin.drelephant.mapreduce.HadoopCounterHolder; +import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; import java.util.ArrayList; import java.util.List; @@ -9,12 +9,12 @@ import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.mapreduce.HadoopTaskData; +import com.linkedin.drelephant.mapreduce.MapReduceTaskData; import com.linkedin.drelephant.math.Statistics; import org.apache.commons.io.FileUtils; -public class MapperSpeedHeuristic implements Heuristic { +public class MapperSpeedHeuristic implements Heuristic { public static final String HEURISTIC_NAME = "Mapper Speed"; @Override @@ -23,17 +23,17 @@ public String getHeuristicName() { } @Override - public HeuristicResult apply(MapreduceApplicationData data) { + public HeuristicResult apply(MapReduceApplicationData data) { - HadoopTaskData[] tasks = data.getMapperData(); + MapReduceTaskData[] tasks = data.getMapperData(); List inputByteSizes = new ArrayList(); List speeds = new ArrayList(); List runtimesMs = new ArrayList(); - for (HadoopTaskData task : tasks) { + for (MapReduceTaskData task : tasks) { if (task.timed()) { - long inputBytes = task.getCounters().get(HadoopCounterHolder.CounterName.HDFS_BYTES_READ); + long inputBytes = task.getCounters().get(MapReduceCounterHolder.CounterName.HDFS_BYTES_READ); long runtimeMs = task.getTotalRunTimeMs(); inputByteSizes.add(inputBytes); runtimesMs.add(runtimeMs); diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristic.java index 736addaa5..ea207fbd8 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristic.java @@ -3,26 +3,26 @@ import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.mapreduce.HadoopCounterHolder; -import com.linkedin.drelephant.mapreduce.HadoopTaskData; -import com.linkedin.drelephant.mapreduce.MapreduceApplicationData; +import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; +import com.linkedin.drelephant.mapreduce.MapReduceTaskData; +import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; -public class MapperSpillHeuristic implements Heuristic { +public class MapperSpillHeuristic implements Heuristic { public static final String HEURISTIC_NAME = "Mapper Spill"; private static final long THRESHOLD_SPILL_FACTOR = 10000; @Override - public HeuristicResult apply(MapreduceApplicationData data) { - HadoopTaskData[] tasks = data.getMapperData(); + public HeuristicResult apply(MapReduceApplicationData data) { + MapReduceTaskData[] tasks = data.getMapperData(); long totalSpills = 0; long totalOutputRecords = 0; double ratioSpills = 0.0; - for (HadoopTaskData task : tasks) { - totalSpills += task.getCounters().get(HadoopCounterHolder.CounterName.SPILLED_RECORDS); - totalOutputRecords += task.getCounters().get(HadoopCounterHolder.CounterName.MAP_OUTPUT_RECORDS); + for (MapReduceTaskData task : tasks) { + totalSpills += task.getCounters().get(MapReduceCounterHolder.CounterName.SPILLED_RECORDS); + totalOutputRecords += task.getCounters().get(MapReduceCounterHolder.CounterName.MAP_OUTPUT_RECORDS); } //If both totalSpills and totalOutputRecords are zero then set ratioSpills to zero. diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerDataSkewHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerDataSkewHeuristic.java index f9acc86e9..d82ebeaf8 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerDataSkewHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerDataSkewHeuristic.java @@ -1,19 +1,19 @@ package com.linkedin.drelephant.mapreduce.heuristics; -import com.linkedin.drelephant.mapreduce.HadoopCounterHolder; -import com.linkedin.drelephant.mapreduce.MapreduceApplicationData; -import com.linkedin.drelephant.mapreduce.HadoopTaskData; +import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; +import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.MapReduceTaskData; public class ReducerDataSkewHeuristic extends GenericDataSkewHeuristic { public static final String HEURISTIC_NAME = "Reducer Data Skew"; public ReducerDataSkewHeuristic() { - super(HadoopCounterHolder.CounterName.REDUCE_SHUFFLE_BYTES, HEURISTIC_NAME); + super(MapReduceCounterHolder.CounterName.REDUCE_SHUFFLE_BYTES, HEURISTIC_NAME); } @Override - protected HadoopTaskData[] getTasks(MapreduceApplicationData data) { + protected MapReduceTaskData[] getTasks(MapReduceApplicationData data) { return data.getReducerData(); } } diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristic.java index 7fa817dcc..28edabaa3 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristic.java @@ -1,7 +1,7 @@ package com.linkedin.drelephant.mapreduce.heuristics; -import com.linkedin.drelephant.mapreduce.MapreduceApplicationData; -import com.linkedin.drelephant.mapreduce.HadoopTaskData; +import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.MapReduceTaskData; import java.util.ArrayList; import java.util.List; @@ -11,7 +11,7 @@ import com.linkedin.drelephant.math.Statistics; -public class ReducerTimeHeuristic implements Heuristic { +public class ReducerTimeHeuristic implements Heuristic { public static final String HEURISTIC_NAME = "Reducer Time"; @Override @@ -20,12 +20,12 @@ public String getHeuristicName() { } @Override - public HeuristicResult apply(MapreduceApplicationData data) { - HadoopTaskData[] tasks = data.getReducerData(); + public HeuristicResult apply(MapReduceApplicationData data) { + MapReduceTaskData[] tasks = data.getReducerData(); List runTimesMs = new ArrayList(); - for (HadoopTaskData task : tasks) { + for (MapReduceTaskData task : tasks) { if (task.timed()) { runTimesMs.add(task.getTotalRunTimeMs()); } diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristic.java index e9cfc25e8..bd142955f 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristic.java @@ -1,17 +1,17 @@ package com.linkedin.drelephant.mapreduce.heuristics; -import com.linkedin.drelephant.mapreduce.MapreduceApplicationData; +import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; import java.util.ArrayList; import java.util.List; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.mapreduce.HadoopTaskData; +import com.linkedin.drelephant.mapreduce.MapReduceTaskData; import com.linkedin.drelephant.math.Statistics; -public class ShuffleSortHeuristic implements Heuristic { +public class ShuffleSortHeuristic implements Heuristic { public static final String HEURISTIC_NAME = "Shuffle & Sort"; @Override @@ -20,15 +20,15 @@ public String getHeuristicName() { } @Override - public HeuristicResult apply(MapreduceApplicationData data) { + public HeuristicResult apply(MapReduceApplicationData data) { - HadoopTaskData[] tasks = data.getReducerData(); + MapReduceTaskData[] tasks = data.getReducerData(); List execTimeMs = new ArrayList(); List shuffleTimeMs = new ArrayList(); List sortTimeMs = new ArrayList(); - for (HadoopTaskData task : tasks) { + for (MapReduceTaskData task : tasks) { if (task.timed()) { execTimeMs.add(task.getCodeExecutionTimeMs()); shuffleTimeMs.add(task.getShuffleTimeMs()); diff --git a/app/com/linkedin/drelephant/util/InfoExtractor.java b/app/com/linkedin/drelephant/util/InfoExtractor.java index 1d2cadac9..48e3962fe 100644 --- a/app/com/linkedin/drelephant/util/InfoExtractor.java +++ b/app/com/linkedin/drelephant/util/InfoExtractor.java @@ -12,7 +12,7 @@ import model.JobResult; -import com.linkedin.drelephant.mapreduce.MapreduceApplicationData; +import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; /** @@ -30,14 +30,14 @@ public class InfoExtractor { // TODO: this utils class is not ideal, probably should merge retrieve URLs logics directly into the data interface? public static void retrieveURLs(JobResult result, HadoopApplicationData data) { - if (data instanceof MapreduceApplicationData) { - retrieveURLs(result, (MapreduceApplicationData) data); + if (data instanceof MapReduceApplicationData) { + retrieveURLs(result, (MapReduceApplicationData) data); } else if (data instanceof SparkApplicationData) { retrieveURLs(result, (SparkApplicationData) data); } } - public static void retrieveURLs(JobResult result, MapreduceApplicationData appData) { + public static void retrieveURLs(JobResult result, MapReduceApplicationData appData) { Properties jobConf = appData.getConf(); String jobId = appData.getJobId(); result.jobExecUrl = truncate(jobConf.getProperty(AZKABAN_ATTEMPT_URL), jobId); diff --git a/app/com/linkedin/drelephant/util/Utils.java b/app/com/linkedin/drelephant/util/Utils.java index 92e8f3013..80e9aa1a6 100644 --- a/app/com/linkedin/drelephant/util/Utils.java +++ b/app/com/linkedin/drelephant/util/Utils.java @@ -2,8 +2,8 @@ import com.linkedin.drelephant.DaliMetricsAPI; import com.linkedin.drelephant.ElephantContext; -import com.linkedin.drelephant.mapreduce.HadoopCounterHolder; -import com.linkedin.drelephant.mapreduce.MapreduceApplicationData; +import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; +import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; @@ -48,7 +48,7 @@ public static String getJobIdFromApplicationId(String appId) { * * @param jobData the data to check for publishing */ - public static void publishMetrics(MapreduceApplicationData jobData) { + public static void publishMetrics(MapReduceApplicationData jobData) { DaliMetricsAPI.MetricsPublisher metricsPublisher = ElephantContext.instance().getMetricsPublisher(); if (metricsPublisher == null) { return; @@ -69,7 +69,7 @@ public static void publishMetrics(MapreduceApplicationData jobData) { jobData.getFinishTime()); DaliMetricsAPI.HadoopCounters metricsEvent = new DaliMetricsAPI.HadoopCounters(eventContext, jobProperties); - HadoopCounterHolder counterHolder = jobData.getCounters(); + MapReduceCounterHolder counterHolder = jobData.getCounters(); logger.info("HadoopCounterHolder: {" + counterHolder + "}"); Set groupNames = counterHolder.getGroupNames(); logger.info("group names: [" + StringUtils.join(groupNames, ",") + "]"); diff --git a/conf/elephant-conf.xml b/conf/elephant-conf.xml index 194603301..79004a909 100644 --- a/conf/elephant-conf.xml +++ b/conf/elephant-conf.xml @@ -7,12 +7,12 @@ mapreduce classic - com.linkedin.drelephant.mapreduce.MapreduceFetcherClassic + com.linkedin.drelephant.mapreduce.MapReduceFetcherHadoop1 mapreduce yarn - com.linkedin.drelephant.mapreduce.MapreduceFetcherYarn + com.linkedin.drelephant.mapreduce.MapReduceFetcherHadoop2 spark diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/JobQueueLimitHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/JobQueueLimitHeuristicTest.java index ad2fa7fc4..f61dfc859 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/JobQueueLimitHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/JobQueueLimitHeuristicTest.java @@ -1,6 +1,5 @@ package com.linkedin.drelephant.mapreduce.heuristics; -import com.linkedin.drelephant.mapreduce.heuristics.JobQueueLimitHeuristic; import java.io.IOException; import java.util.Properties; @@ -12,9 +11,9 @@ import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.mapreduce.HadoopCounterHolder; -import com.linkedin.drelephant.mapreduce.MapreduceApplicationData; -import com.linkedin.drelephant.mapreduce.HadoopTaskData; +import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; +import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.MapReduceTaskData; public class JobQueueLimitHeuristicTest extends TestCase { @@ -48,20 +47,20 @@ public void testNonDefaultRuntimeNone() throws IOException { } private Severity analyzeJob(long runtimeMs, String queueName) throws IOException { - HadoopCounterHolder dummyCounter = new HadoopCounterHolder(); - HadoopTaskData[] mappers = new HadoopTaskData[2 * NUM_TASKS / 3]; - HadoopTaskData[] reducers = new HadoopTaskData[NUM_TASKS / 3]; + MapReduceCounterHolder dummyCounter = new MapReduceCounterHolder(); + MapReduceTaskData[] mappers = new MapReduceTaskData[2 * NUM_TASKS / 3]; + MapReduceTaskData[] reducers = new MapReduceTaskData[NUM_TASKS / 3]; Properties jobConf = new Properties(); jobConf.put("mapred.job.queue.name", queueName); int i = 0; for (; i < 2 * NUM_TASKS / 3; i++) { - mappers[i] = new HadoopTaskData(dummyCounter, new long[] { runtimeMs, 0, 0 }); + mappers[i] = new MapReduceTaskData(dummyCounter, new long[] { runtimeMs, 0, 0 }); } for (i = 0; i < NUM_TASKS / 3; i++) { - reducers[i] = new HadoopTaskData(dummyCounter, new long[] { runtimeMs, 0, 0 }); + reducers[i] = new MapReduceTaskData(dummyCounter, new long[] { runtimeMs, 0, 0 }); } - MapreduceApplicationData data = - new MapreduceApplicationData().setCounters(dummyCounter).setReducerData(reducers).setMapperData(mappers) + MapReduceApplicationData data = + new MapReduceApplicationData().setCounters(dummyCounter).setReducerData(reducers).setMapperData(mappers) .setJobConf(jobConf); HeuristicResult result = _heuristic.apply(data); return result.getSeverity(); diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperDataSkewHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperDataSkewHeuristicTest.java index dc94b4b89..04d0d5314 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperDataSkewHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperDataSkewHeuristicTest.java @@ -4,10 +4,9 @@ import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.mapreduce.HadoopCounterHolder; -import com.linkedin.drelephant.mapreduce.MapreduceApplicationData; -import com.linkedin.drelephant.mapreduce.HadoopTaskData; -import com.linkedin.drelephant.mapreduce.heuristics.MapperDataSkewHeuristic; +import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; +import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.MapReduceTaskData; import java.io.IOException; import junit.framework.TestCase; @@ -47,24 +46,24 @@ public void testSmallTasks() throws IOException { private Severity analyzeJob(int numSmallTasks, int numLargeTasks, long smallInputSize, long largeInputSize) throws IOException { - HadoopCounterHolder jobCounter = new HadoopCounterHolder(); - HadoopTaskData[] mappers = new HadoopTaskData[numSmallTasks + numLargeTasks]; + MapReduceCounterHolder jobCounter = new MapReduceCounterHolder(); + MapReduceTaskData[] mappers = new MapReduceTaskData[numSmallTasks + numLargeTasks]; - HadoopCounterHolder smallCounter = new HadoopCounterHolder(); - smallCounter.set(HadoopCounterHolder.CounterName.HDFS_BYTES_READ, smallInputSize); + MapReduceCounterHolder smallCounter = new MapReduceCounterHolder(); + smallCounter.set(MapReduceCounterHolder.CounterName.HDFS_BYTES_READ, smallInputSize); - HadoopCounterHolder largeCounter = new HadoopCounterHolder(); - largeCounter.set(HadoopCounterHolder.CounterName.HDFS_BYTES_READ, largeInputSize); + MapReduceCounterHolder largeCounter = new MapReduceCounterHolder(); + largeCounter.set(MapReduceCounterHolder.CounterName.HDFS_BYTES_READ, largeInputSize); int i = 0; for (; i < numSmallTasks; i++) { - mappers[i] = new HadoopTaskData(smallCounter, new long[3]); + mappers[i] = new MapReduceTaskData(smallCounter, new long[3]); } for (; i < numSmallTasks + numLargeTasks; i++) { - mappers[i] = new HadoopTaskData(largeCounter, new long[3]); + mappers[i] = new MapReduceTaskData(largeCounter, new long[3]); } - MapreduceApplicationData data = new MapreduceApplicationData().setCounters(jobCounter).setMapperData(mappers); + MapReduceApplicationData data = new MapReduceApplicationData().setCounters(jobCounter).setMapperData(mappers); HeuristicResult result = _heuristic.apply(data); return result.getSeverity(); diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperInputSizeHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperInputSizeHeuristicTest.java index 6488871a4..cb2407403 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperInputSizeHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperInputSizeHeuristicTest.java @@ -4,10 +4,9 @@ import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.mapreduce.HadoopCounterHolder; -import com.linkedin.drelephant.mapreduce.MapreduceApplicationData; -import com.linkedin.drelephant.mapreduce.HadoopTaskData; -import com.linkedin.drelephant.mapreduce.heuristics.MapperInputSizeHeuristic; +import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; +import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.MapReduceTaskData; import com.linkedin.drelephant.math.Statistics; import java.io.IOException; import junit.framework.TestCase; @@ -139,18 +138,18 @@ public void testNormalFileShortRuntime() throws IOException { } private Severity analyzeJob(int numTasks, long inputSize, long runtime) throws IOException { - HadoopCounterHolder jobCounter = new HadoopCounterHolder(); - HadoopTaskData[] mappers = new HadoopTaskData[numTasks]; + MapReduceCounterHolder jobCounter = new MapReduceCounterHolder(); + MapReduceTaskData[] mappers = new MapReduceTaskData[numTasks]; - HadoopCounterHolder taskCounter = new HadoopCounterHolder(); - taskCounter.set(HadoopCounterHolder.CounterName.HDFS_BYTES_READ, inputSize); + MapReduceCounterHolder taskCounter = new MapReduceCounterHolder(); + taskCounter.set(MapReduceCounterHolder.CounterName.HDFS_BYTES_READ, inputSize); int i = 0; for (; i < numTasks; i++) { - mappers[i] = new HadoopTaskData(taskCounter, new long[] { runtime, 0, 0 }); + mappers[i] = new MapReduceTaskData(taskCounter, new long[] { runtime, 0, 0 }); } - MapreduceApplicationData data = new MapreduceApplicationData().setCounters(jobCounter).setMapperData(mappers); + MapReduceApplicationData data = new MapReduceApplicationData().setCounters(jobCounter).setMapperData(mappers); HeuristicResult result = _heuristic.apply(data); return result.getSeverity(); } diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristicTest.java index 5f8c10f5b..e28444c85 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristicTest.java @@ -4,10 +4,9 @@ import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.mapreduce.HadoopCounterHolder; -import com.linkedin.drelephant.mapreduce.MapreduceApplicationData; -import com.linkedin.drelephant.mapreduce.HadoopTaskData; -import com.linkedin.drelephant.mapreduce.heuristics.MapperSpeedHeuristic; +import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; +import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.MapReduceTaskData; import com.linkedin.drelephant.math.Statistics; import java.io.IOException; import junit.framework.TestCase; @@ -44,18 +43,18 @@ public void testShortTask() throws IOException { } private Severity analyzeJob(long runtimeMs, long readBytes) throws IOException { - HadoopCounterHolder jobCounter = new HadoopCounterHolder(); - HadoopTaskData[] mappers = new HadoopTaskData[NUMTASKS]; + MapReduceCounterHolder jobCounter = new MapReduceCounterHolder(); + MapReduceTaskData[] mappers = new MapReduceTaskData[NUMTASKS]; - HadoopCounterHolder counter = new HadoopCounterHolder(); - counter.set(HadoopCounterHolder.CounterName.HDFS_BYTES_READ, readBytes); + MapReduceCounterHolder counter = new MapReduceCounterHolder(); + counter.set(MapReduceCounterHolder.CounterName.HDFS_BYTES_READ, readBytes); int i = 0; for (; i < NUMTASKS; i++) { - mappers[i] = new HadoopTaskData(counter, new long[] { runtimeMs, 0, 0 }); + mappers[i] = new MapReduceTaskData(counter, new long[] { runtimeMs, 0, 0 }); } - MapreduceApplicationData data = new MapreduceApplicationData().setCounters(jobCounter).setMapperData(mappers); + MapReduceApplicationData data = new MapReduceApplicationData().setCounters(jobCounter).setMapperData(mappers); HeuristicResult result = _heuristic.apply(data); return result.getSeverity(); } diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristicTest.java index b423993fb..c4a47f63e 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristicTest.java @@ -4,10 +4,9 @@ import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.mapreduce.HadoopCounterHolder; -import com.linkedin.drelephant.mapreduce.MapreduceApplicationData; -import com.linkedin.drelephant.mapreduce.HadoopTaskData; -import com.linkedin.drelephant.mapreduce.heuristics.MapperSpillHeuristic; +import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; +import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.MapReduceTaskData; import java.io.IOException; import junit.framework.TestCase; @@ -38,18 +37,18 @@ public void testNone() throws IOException { } private Severity analyzeJob(long spilledRecords, long mapRecords) throws IOException { - HadoopCounterHolder jobCounter = new HadoopCounterHolder(); - HadoopTaskData[] mappers = new HadoopTaskData[numTasks]; + MapReduceCounterHolder jobCounter = new MapReduceCounterHolder(); + MapReduceTaskData[] mappers = new MapReduceTaskData[numTasks]; - HadoopCounterHolder counter = new HadoopCounterHolder(); - counter.set(HadoopCounterHolder.CounterName.SPILLED_RECORDS, spilledRecords); - counter.set(HadoopCounterHolder.CounterName.MAP_OUTPUT_RECORDS, mapRecords); + MapReduceCounterHolder counter = new MapReduceCounterHolder(); + counter.set(MapReduceCounterHolder.CounterName.SPILLED_RECORDS, spilledRecords); + counter.set(MapReduceCounterHolder.CounterName.MAP_OUTPUT_RECORDS, mapRecords); for (int i=0; i < numTasks; i++) { - mappers[i] = new HadoopTaskData(counter, new long[] { 0, 5, 5, 5 }); + mappers[i] = new MapReduceTaskData(counter, new long[] { 0, 5, 5, 5 }); } - MapreduceApplicationData data = new MapreduceApplicationData().setCounters(jobCounter).setMapperData(mappers); + MapReduceApplicationData data = new MapReduceApplicationData().setCounters(jobCounter).setMapperData(mappers); HeuristicResult result = heuristic.apply(data); return result.getSeverity(); } diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerDataSkewHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerDataSkewHeuristicTest.java index a4a7f80f1..5fc9872ef 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerDataSkewHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerDataSkewHeuristicTest.java @@ -4,10 +4,9 @@ import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.mapreduce.HadoopCounterHolder; -import com.linkedin.drelephant.mapreduce.MapreduceApplicationData; -import com.linkedin.drelephant.mapreduce.HadoopTaskData; -import com.linkedin.drelephant.mapreduce.heuristics.ReducerDataSkewHeuristic; +import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; +import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.MapReduceTaskData; import java.io.IOException; import junit.framework.TestCase; @@ -46,24 +45,24 @@ public void testSmallTasks() throws IOException { private Severity analyzeJob(int numSmallTasks, int numLargeTasks, long smallInputSize, long largeInputSize) throws IOException { - HadoopCounterHolder jobCounter = new HadoopCounterHolder(); - HadoopTaskData[] reducers = new HadoopTaskData[numSmallTasks + numLargeTasks]; + MapReduceCounterHolder jobCounter = new MapReduceCounterHolder(); + MapReduceTaskData[] reducers = new MapReduceTaskData[numSmallTasks + numLargeTasks]; - HadoopCounterHolder smallCounter = new HadoopCounterHolder(); - smallCounter.set(HadoopCounterHolder.CounterName.REDUCE_SHUFFLE_BYTES, smallInputSize); + MapReduceCounterHolder smallCounter = new MapReduceCounterHolder(); + smallCounter.set(MapReduceCounterHolder.CounterName.REDUCE_SHUFFLE_BYTES, smallInputSize); - HadoopCounterHolder largeCounter = new HadoopCounterHolder(); - largeCounter.set(HadoopCounterHolder.CounterName.REDUCE_SHUFFLE_BYTES, largeInputSize); + MapReduceCounterHolder largeCounter = new MapReduceCounterHolder(); + largeCounter.set(MapReduceCounterHolder.CounterName.REDUCE_SHUFFLE_BYTES, largeInputSize); int i = 0; for (; i < numSmallTasks; i++) { - reducers[i] = new HadoopTaskData(smallCounter, new long[3]); + reducers[i] = new MapReduceTaskData(smallCounter, new long[3]); } for (; i < numSmallTasks + numLargeTasks; i++) { - reducers[i] = new HadoopTaskData(largeCounter, new long[3]); + reducers[i] = new MapReduceTaskData(largeCounter, new long[3]); } - MapreduceApplicationData data = new MapreduceApplicationData().setCounters(jobCounter).setReducerData(reducers); + MapReduceApplicationData data = new MapReduceApplicationData().setCounters(jobCounter).setReducerData(reducers); HeuristicResult result = _heuristic.apply(data); return result.getSeverity(); } diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristicTest.java index fe8d78b1a..21d27e28f 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristicTest.java @@ -1,14 +1,13 @@ package com.linkedin.drelephant.mapreduce.heuristics; -import com.linkedin.drelephant.mapreduce.heuristics.ReducerTimeHeuristic; import java.io.IOException; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.mapreduce.HadoopCounterHolder; -import com.linkedin.drelephant.mapreduce.MapreduceApplicationData; -import com.linkedin.drelephant.mapreduce.HadoopTaskData; +import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; +import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.MapReduceTaskData; import com.linkedin.drelephant.math.Statistics; import junit.framework.TestCase; @@ -59,15 +58,15 @@ public void testLongRunetimeNone() throws IOException { } private Severity analyzeJob(long runtimeMs, int numTasks) throws IOException { - HadoopCounterHolder dummyCounter = new HadoopCounterHolder(); - HadoopTaskData[] reducers = new HadoopTaskData[numTasks]; + MapReduceCounterHolder dummyCounter = new MapReduceCounterHolder(); + MapReduceTaskData[] reducers = new MapReduceTaskData[numTasks]; int i = 0; for (; i < numTasks; i++) { - reducers[i] = new HadoopTaskData(dummyCounter, new long[] { runtimeMs, 0, 0 }); + reducers[i] = new MapReduceTaskData(dummyCounter, new long[] { runtimeMs, 0, 0 }); } - MapreduceApplicationData data = new MapreduceApplicationData().setCounters(dummyCounter).setReducerData(reducers); + MapReduceApplicationData data = new MapReduceApplicationData().setCounters(dummyCounter).setReducerData(reducers); HeuristicResult result = _heuristic.apply(data); return result.getSeverity(); } diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristicTest.java index 863b23e01..eaeffc8a2 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristicTest.java @@ -1,15 +1,14 @@ package com.linkedin.drelephant.mapreduce.heuristics; -import com.linkedin.drelephant.mapreduce.heuristics.ShuffleSortHeuristic; import java.io.IOException; import com.linkedin.drelephant.analysis.Constants; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.mapreduce.HadoopCounterHolder; -import com.linkedin.drelephant.mapreduce.MapreduceApplicationData; -import com.linkedin.drelephant.mapreduce.HadoopTaskData; +import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; +import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.MapReduceTaskData; import com.linkedin.drelephant.math.Statistics; import junit.framework.TestCase; @@ -69,15 +68,15 @@ public void testShortSort() throws IOException { } private Severity analyzeJob(long shuffleTimeMs, long sortTimeMs, long reduceTimeMs) throws IOException { - HadoopCounterHolder dummyCounter = new HadoopCounterHolder(); - HadoopTaskData[] reducers = new HadoopTaskData[NUMTASKS]; + MapReduceCounterHolder dummyCounter = new MapReduceCounterHolder(); + MapReduceTaskData[] reducers = new MapReduceTaskData[NUMTASKS]; int i = 0; for (; i < NUMTASKS; i++) { - reducers[i] = new HadoopTaskData(dummyCounter, + reducers[i] = new MapReduceTaskData(dummyCounter, new long[] { shuffleTimeMs + sortTimeMs + reduceTimeMs, shuffleTimeMs, sortTimeMs }); } - MapreduceApplicationData data = new MapreduceApplicationData().setCounters(dummyCounter).setReducerData(reducers); + MapReduceApplicationData data = new MapReduceApplicationData().setCounters(dummyCounter).setReducerData(reducers); HeuristicResult result = _heuristic.apply(data); return result.getSeverity(); } From 5e74010acb7574a7168d4d4765c702edda5a5df4 Mon Sep 17 00:00:00 2001 From: Yitong Zhou Date: Wed, 3 Jun 2015 16:42:47 -0700 Subject: [PATCH 071/272] Addressing major feedback from Fangshi: 1.bind application type with JobType; 2. use only application id all data holders and use job id while saving to DB; 3. Change configuration styles of heuristics, fetchers and jobtypes into separate files; 4. Make those configuration files providable via other locations so that we can seemlessly change configuration without releasing a new deployment zip. 5. Remove HadoopVersion classic, yarn, using 1 or 2 instead. --- .../linkedin/drelephant/ElephantContext.java | 167 ++++++++++++------ .../linkedin/drelephant/ElephantRunner.java | 16 +- .../drelephant/analysis/AnalysisPromise.java | 11 +- .../drelephant/analysis/Constants.java | 28 --- .../analysis/HadoopApplicationData.java | 14 +- .../analysis/HadoopSystemContext.java | 62 +++++++ .../linkedin/drelephant/analysis/JobType.java | 36 ++-- .../mapreduce/MapReduceApplicationData.java | 18 +- .../mapreduce/MapReduceFetcherHadoop1.java | 6 +- .../mapreduce/MapReduceFetcherHadoop2.java | 2 +- .../heuristics/GenericDataSkewHeuristic.java | 6 +- .../heuristics/MapperInputSizeHeuristic.java | 10 +- .../heuristics/MapperSpeedHeuristic.java | 6 +- .../util/HeuristicConfiguration.java | 18 +- .../linkedin/drelephant/util/JobTypeConf.java | 127 +++++++------ app/com/linkedin/drelephant/util/Utils.java | 50 +++--- .../deploy/history/SparkDataCollection.scala | 6 +- app/views/help.scala.html | 8 +- app/views/search.scala.html | 8 +- conf/FetcherConf.xml | 35 ++++ conf/HeuristicConf.xml | 141 +++++++++++++++ conf/{job-types.xml => JobTypeConf.xml} | 29 ++- conf/application.conf | 10 +- conf/elephant-conf.xml | 163 ----------------- .../JobQueueLimitHeuristicTest.java | 4 +- .../MapperDataSkewHeuristicTest.java | 4 +- .../MapperInputSizeHeuristicTest.java | 4 +- .../heuristics/MapperSpeedHeuristicTest.java | 6 +- .../heuristics/MapperSpillHeuristicTest.java | 4 +- .../ReducerDataSkewHeuristicTest.java | 4 +- .../heuristics/ShuffleSortHeuristicTest.java | 4 +- .../spark/MockSparkApplicationData.java | 9 +- .../linkedin/drelephant/util/UtilsTest.java | 47 +++++ 33 files changed, 646 insertions(+), 417 deletions(-) delete mode 100644 app/com/linkedin/drelephant/analysis/Constants.java create mode 100644 app/com/linkedin/drelephant/analysis/HadoopSystemContext.java create mode 100644 conf/FetcherConf.xml create mode 100644 conf/HeuristicConf.xml rename conf/{job-types.xml => JobTypeConf.xml} (61%) delete mode 100644 conf/elephant-conf.xml create mode 100644 test/com/linkedin/drelephant/util/UtilsTest.java diff --git a/app/com/linkedin/drelephant/ElephantContext.java b/app/com/linkedin/drelephant/ElephantContext.java index b93e2c57e..c6f984134 100644 --- a/app/com/linkedin/drelephant/ElephantContext.java +++ b/app/com/linkedin/drelephant/ElephantContext.java @@ -1,17 +1,24 @@ package com.linkedin.drelephant; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.Sets; import com.linkedin.drelephant.analysis.ApplicationType; import com.linkedin.drelephant.analysis.ElephantFetcher; +import com.linkedin.drelephant.analysis.HadoopApplicationData; +import com.linkedin.drelephant.analysis.HadoopSystemContext; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; +import com.linkedin.drelephant.analysis.JobType; import com.linkedin.drelephant.util.HeuristicConfiguration; import com.linkedin.drelephant.util.HeuristicConfigurationData; +import com.linkedin.drelephant.util.JobTypeConf; import com.linkedin.drelephant.util.Utils; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Properties; import java.util.Set; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; @@ -31,23 +38,26 @@ public class ElephantContext { private static final Logger logger = Logger.getLogger(ElephantContext.class); private static final ElephantContext INSTANCE = new ElephantContext(); - private static final String CONFIGURATION_FILE_PATH = "elephant-conf.xml"; + private static final String FETCHERS_CONF = "fetchers.conf.location"; + private static final String HEURISTICS_CONF = "heuristics.conf.location"; + private static final String JOB_TYPES_CONF = "jobtypes.conf.location"; private static final String OPT_METRICS_PUB_CONF = "metrics.publisher-conf"; - private final List _heuristicNames = new ArrayList(); + private final Map> _heuristicGroupedNames = new HashMap>(); private List _heuristicsConfData; private final Map _nameToType = new HashMap(); private final Map> _typeToHeuristics = new HashMap>(); private final Map _typeToFetcher = new HashMap(); + private Map> _appTypeToJobTypes = new HashMap>(); private final DaliMetricsAPI.MetricsPublisher _metricsPublisher; - private final String _hadoopVersion; + private final int _hadoopVersion; private ElephantContext() { // private on purpose - _hadoopVersion = Utils.getHadoopVersion(); + _hadoopVersion = HadoopSystemContext.getHadoopVersion(); loadConfiguration(); @@ -66,43 +76,25 @@ private ElephantContext() { } private void loadConfiguration() { - Document document = Utils.loadXMLDoc(CONFIGURATION_FILE_PATH); - NodeList nodes = document.getDocumentElement().getChildNodes(); - - Element heuristicsElement = null; - Element fetchersElement = null; + loadFetchers(); + loadHeuristics(); + loadJobTypes(); - for (int i = 0; i < nodes.getLength(); i++) { - Node node = nodes.item(i); - if (node.getNodeType() == Node.ELEMENT_NODE) { - Element element = (Element) node; - String sectionName = element.getTagName(); - if (sectionName.equals("heuristics")) { - heuristicsElement = element; - } else if (sectionName.equals("fetchers")) { - fetchersElement = element; - } - } - } + // It is important to configure supported types in the LAST step so that we could have information from all + // configurable components. + configureSupportedApplicationTypes(); + } - if (fetchersElement == null) { + private void loadFetchers() { + String filePath = play.Play.application().configuration().getString(FETCHERS_CONF); + if (filePath == null) { throw new RuntimeException( - "No configuration block is presented in configuration file: " + CONFIGURATION_FILE_PATH); + "Fetchers configuration file path is not presented, please set up configuration property: " + FETCHERS_CONF + + "."); } - if (heuristicsElement == null) { - throw new RuntimeException( - "No configuration block is presented in configuration file: " + CONFIGURATION_FILE_PATH); - } - - // It is important to load fetchers first because we also need to figure out the supported application - // types via fetcher configurations. - loadFetchers(fetchersElement); - loadHeuristics(heuristicsElement); - configureSupportedApplicationTypes(); - } + Document document = Utils.loadXMLDoc(filePath); - private void loadFetchers(Element configuration) { - NodeList nodes = configuration.getChildNodes(); + NodeList nodes = document.getDocumentElement().getChildNodes(); for (int i = 0; i < nodes.getLength(); i++) { Node node = nodes.item(i); int n = 0; @@ -126,9 +118,10 @@ private void loadFetchers(Element configuration) { } String hadoopVersion = hadoopVersionNode.getTextContent().toLowerCase().trim(); - if (hadoopVersion.equals(_hadoopVersion)) { + int hadoopMajorVersion = Utils.getMajorVersionFromString(hadoopVersion); + if (hadoopMajorVersion == _hadoopVersion) { String typeName = applicationTypeNode.getTextContent(); - if (!isApplicationTypeSupported(typeName)) { + if (getApplicationType(typeName) == null) { ApplicationType type = new ApplicationType(typeName); String className = classNameNode.getTextContent(); @@ -155,14 +148,22 @@ private void loadFetchers(Element configuration) { } } else { logger.info("Skipping fetcher #" + n + ", because its hadoop version [" + hadoopVersion - + "] does not match our current version [" + _hadoopVersion + "]"); + + "] does not match our current major version [" + _hadoopVersion + "]"); } } } } - private void loadHeuristics(Element configuration) { - _heuristicsConfData = new HeuristicConfiguration(configuration).getHeuristicsConfigurationData(); + private void loadHeuristics() { + String filePath = play.Play.application().configuration().getString(HEURISTICS_CONF); + if (filePath == null) { + throw new RuntimeException( + "Heuristics configuration file path is not presented, please set up configuration property: " + + HEURISTICS_CONF + "."); + } + + Document document = Utils.loadXMLDoc(filePath); + _heuristicsConfData = new HeuristicConfiguration(document.getDocumentElement()).getHeuristicsConfigurationData(); for (HeuristicConfigurationData data : _heuristicsConfData) { try { @@ -202,11 +203,14 @@ private void loadHeuristics(Element configuration) { private void configureSupportedApplicationTypes() { Set supportedTypes = Sets.intersection(_typeToFetcher.keySet(), _typeToHeuristics.keySet()); + supportedTypes = Sets.intersection(supportedTypes, _appTypeToJobTypes.keySet()); + for (ApplicationType type : _typeToFetcher.keySet()) { if (!supportedTypes.contains(type)) { ElephantFetcher removedFetcher = _typeToFetcher.remove(type); logger.warn("ElephantFetcher class " + removedFetcher.getClass().getName() - + " does not have any heuristic rule for application type " + type.getName() + ", being ignored."); + + " does not have any matched heuristic rule or job type for application type " + type.getName() + + ", being ignored."); } } @@ -215,7 +219,19 @@ private void configureSupportedApplicationTypes() { List removedHeuristics = _typeToHeuristics.remove(type); for (Heuristic removedHeuristic : removedHeuristics) { logger.warn("Heuristic class " + removedHeuristic.getClass().getName() - + "does not have any fetcher for application type " + type.getName() + ", being ignored."); + + "does not have any matched fetcher or job type for application type " + type.getName() + + ", being ignored."); + } + } + } + + for (ApplicationType type : _appTypeToJobTypes.keySet()) { + if (!supportedTypes.contains(type)) { + List removedJobTypes = _appTypeToJobTypes.remove(type); + for (JobType removedJobType : removedJobTypes) { + logger.warn("JobType " + removedJobType.getName() + + " does not have any matched fetcher or heuristic for application type " + type.getName() + + ", being ignored."); } } } @@ -223,16 +239,32 @@ private void configureSupportedApplicationTypes() { logger.info("ElephantContext configured:"); for (ApplicationType type : supportedTypes) { _nameToType.put(type.getName(), type); + List classes = new ArrayList(); List heuristics = _typeToHeuristics.get(type); for (Heuristic heuristic : heuristics) { - classes.add(heuristics.getClass().getName()); + classes.add(heuristic.getClass().getName()); } - logger.info("ApplicationType: " + type.getName() + ", ElephantFetcher class: " + _typeToFetcher.get(type) + - "Heuristics: [" + StringUtils.join(classes, ", ") + "]"); + + List jobTypes = _appTypeToJobTypes.get(type); + logger.info("ApplicationType: " + type.getName() + ", ElephantFetcher class: " + _typeToFetcher.get(type) + + "Heuristics: [" + StringUtils.join(classes, ", ") + "], JobTypes: [" + StringUtils.join(jobTypes, ", ") + + "]."); } } + private void loadJobTypes() { + String filePath = play.Play.application().configuration().getString(JOB_TYPES_CONF); + if (filePath == null) { + throw new RuntimeException( + "JobType configuration file path is not presented, please set up configuration property: " + JOB_TYPES_CONF + + "."); + } + + JobTypeConf conf = new JobTypeConf(filePath); + _appTypeToJobTypes = conf.getAppTypeToJobTypeList(); + } + /** * Get the singleton instance * @@ -253,20 +285,27 @@ public List getHeuristics(ApplicationType type) { } /** - * Given an application type, return the all the heuristic names. + * Return the heuristic names available grouped by application type. * - * @return A list of heuristic names + * @return A map of application type name -> a list of heuristic names */ - public List getAllHeuristicNames() { - if (_heuristicNames.isEmpty()) { - for (List list : _typeToHeuristics.values()) { + public Map> getAllHeuristicNames() { + if (_heuristicGroupedNames.isEmpty()) { + for (Map.Entry> entry : _typeToHeuristics.entrySet()) { + ApplicationType type = entry.getKey(); + List list = entry.getValue(); + + List nameList = new ArrayList(); for (Heuristic heuristic : list) { - _heuristicNames.add(heuristic.getHeuristicName()); + nameList.add(heuristic.getHeuristicName()); } + + Collections.sort(nameList); + _heuristicGroupedNames.put(type.getName(), nameList); } } - return _heuristicNames; + return _heuristicGroupedNames; } /** @@ -307,12 +346,24 @@ public ApplicationType getApplicationType(String typeName) { } /** - * Indicate if an application type is supported by the current Dr. Elephant context + * Get the matched job type given a * - * @param typeName The type name to look for - * @return true if supported else false + * @param data The HadoopApplicationData to check + * @return The matched job type */ - public boolean isApplicationTypeSupported(String typeName) { - return _nameToType.containsKey(typeName.toUpperCase()); + public JobType matchJobType(HadoopApplicationData data) { + List jobTypeList = _appTypeToJobTypes.get(data.getApplicationType()); + + Properties jobProp = data.getConf(); + for (JobType type : jobTypeList) { + if (type.matchType(jobProp)) { + return type; + } + } + return null; + } + + public Map> getAppTypeToJobTypes() { + return ImmutableMap.copyOf(_appTypeToJobTypes); } } diff --git a/app/com/linkedin/drelephant/ElephantRunner.java b/app/com/linkedin/drelephant/ElephantRunner.java index 0d14b2854..1ae966fc4 100644 --- a/app/com/linkedin/drelephant/ElephantRunner.java +++ b/app/com/linkedin/drelephant/ElephantRunner.java @@ -3,7 +3,7 @@ import com.linkedin.drelephant.analysis.AnalysisPromise; import com.linkedin.drelephant.analysis.AnalysisProvider; import com.linkedin.drelephant.analysis.AnalysisProviderHadoop1; -import com.linkedin.drelephant.analysis.Constants; +import com.linkedin.drelephant.analysis.HadoopSystemContext; import com.linkedin.drelephant.analysis.AnalysisProviderHadoop2; import com.linkedin.drelephant.notifications.EmailThread; import com.linkedin.drelephant.util.Utils; @@ -35,16 +35,14 @@ public class ElephantRunner implements Runnable { private void loadAnalysisProvider() { JobConf configuration = new JobConf(); - String hadoopVersion = Utils.getHadoopVersion(); configuration.get("mapreduce.framework.name"); + int hadoopVersion = HadoopSystemContext.getHadoopVersion(); configuration.get("mapreduce.framework.name"); - if (hadoopVersion.equals("yarn")) { + if (hadoopVersion == 2) { _analysisProvider = new AnalysisProviderHadoop2(); - } else { + } else if (hadoopVersion == 1) { _analysisProvider = new AnalysisProviderHadoop1(); - } - - if (_analysisProvider == null) { - throw new RuntimeException("FutureProvider cannot be initialized correctly. Please check your configuration."); + } else { + throw new RuntimeException("Unsupported Hadoop major version detected: " + hadoopVersion + ""); } try { @@ -63,7 +61,7 @@ public void run() { _hadoopSecurity.doAs(new PrivilegedAction() { @Override public Void run() { - Constants.load(); + HadoopSystemContext.load(); _emailer.start(); loadAnalysisProvider(); diff --git a/app/com/linkedin/drelephant/analysis/AnalysisPromise.java b/app/com/linkedin/drelephant/analysis/AnalysisPromise.java index e384489bf..24b41ad2e 100644 --- a/app/com/linkedin/drelephant/analysis/AnalysisPromise.java +++ b/app/com/linkedin/drelephant/analysis/AnalysisPromise.java @@ -2,7 +2,7 @@ import com.linkedin.drelephant.ElephantContext; import com.linkedin.drelephant.util.InfoExtractor; -import com.linkedin.drelephant.util.JobTypeConf; +import com.linkedin.drelephant.util.Utils; import java.util.ArrayList; import java.util.List; import model.JobHeuristicResult; @@ -108,12 +108,13 @@ public JobResult getAnalysis() throws Exception { analysisResults.add(heuristic.apply(data)); } - String jobType = JobTypeConf.instance().getJobType(data).getName(); + String jobType = ElephantContext.instance().matchJobType(data).getName(); JobResult result = new JobResult(); - // Need to get it from ApplicationData because different applications might use different unique ids. (e.g. - // application_id v.s job_id) - result.jobId = data.getUid(); + // Note: before adding Spark analysers, all JobResult are using job ids as the primary key. But Spark (and many + // other non-mapreduce applications) does not have a job id. To maintain backwards compatibility, we replace + // 'application' with 'job' to form a pseudo one. + result.jobId = Utils.getJobIdFromApplicationId(data.getAppId()); result.url = getTrackingUrl(); result.username = getUser(); result.startTime = getStartTime(); diff --git a/app/com/linkedin/drelephant/analysis/Constants.java b/app/com/linkedin/drelephant/analysis/Constants.java deleted file mode 100644 index 13fecc4d4..000000000 --- a/app/com/linkedin/drelephant/analysis/Constants.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.linkedin.drelephant.analysis; - -import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.fs.FileSystem; -import org.apache.hadoop.fs.Path; -import org.apache.log4j.Logger; - -import java.io.IOException; - -public final class Constants { - private static final Logger logger = Logger.getLogger(Constants.class); - public static long HDFS_BLOCK_SIZE = 64 * 1024 * 1024; - public static final long DISK_READ_SPEED = 100 * 1024 * 1024; - public static final int SHUFFLE_SORT_MAX_SAMPLE_SIZE = 50; - - private Constants() { - } - - public static void load() { - try { - HDFS_BLOCK_SIZE = FileSystem.get(new Configuration()).getDefaultBlockSize(new Path("/")); - } catch (IOException e) { - logger.error("Error getting FS Block Size!", e); - } - - logger.info("HDFS BLock size: " + HDFS_BLOCK_SIZE); - } -} diff --git a/app/com/linkedin/drelephant/analysis/HadoopApplicationData.java b/app/com/linkedin/drelephant/analysis/HadoopApplicationData.java index 2765fea95..9209f8b81 100644 --- a/app/com/linkedin/drelephant/analysis/HadoopApplicationData.java +++ b/app/com/linkedin/drelephant/analysis/HadoopApplicationData.java @@ -9,12 +9,11 @@ public interface HadoopApplicationData { /** - * Returns the unique id to identify an application run. This id could be application id or job id according to - * different applications, as long as this is unique. + * Returns the unique id to identify an application run. * - * @return the uid + * @return the id */ - public String getUid(); + public String getAppId(); /** * Returns the configuration of an application. @@ -22,4 +21,11 @@ public interface HadoopApplicationData { * @return A java Properties that contains the application configuration */ public Properties getConf(); + + /** + * Returns the application type this data is for + * + * @return the application type + */ + public ApplicationType getApplicationType(); } diff --git a/app/com/linkedin/drelephant/analysis/HadoopSystemContext.java b/app/com/linkedin/drelephant/analysis/HadoopSystemContext.java new file mode 100644 index 000000000..6dccd1694 --- /dev/null +++ b/app/com/linkedin/drelephant/analysis/HadoopSystemContext.java @@ -0,0 +1,62 @@ +package com.linkedin.drelephant.analysis; + +import java.util.Map; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.FileSystem; +import org.apache.hadoop.fs.Path; +import org.apache.hadoop.mapred.JobConf; +import org.apache.log4j.Logger; + +import java.io.IOException; + +public final class HadoopSystemContext { + private static final Logger logger = Logger.getLogger(HadoopSystemContext.class); + public static long HDFS_BLOCK_SIZE = 64 * 1024 * 1024; + public static final long DISK_READ_SPEED = 100 * 1024 * 1024; + public static final int SHUFFLE_SORT_MAX_SAMPLE_SIZE = 50; + + private HadoopSystemContext() { + // Empty on purpose + } + + public static void load() { + try { + HDFS_BLOCK_SIZE = FileSystem.get(new Configuration()).getDefaultBlockSize(new Path("/")); + } catch (IOException e) { + logger.error("Error getting FS Block Size!", e); + } + + logger.info("HDFS BLock size: " + HDFS_BLOCK_SIZE); + } + + /** + * Detect the current Hadoop version Dr Elephant is deployed for according to the current Hadoop Configuration. + * + * @return the current hadoop version (1 or 2 typically). + */ + public static int getHadoopVersion() { + // Important, new Configuration might not be timely loaded in Hadoop 1 environment + Configuration hadoopConf = new JobConf(); + Map vals = hadoopConf.getValByRegex(".*"); + String hadoopVersion = hadoopConf.get("mapreduce.framework.name"); + if (hadoopVersion == null) { + if (hadoopConf.get("mapred.job.tracker.http.address") != null) { + hadoopVersion = "classic"; + } else { + throw new RuntimeException("Hadoop config error. No framework name provided."); + } + } + + if (hadoopVersion == null) { + throw new RuntimeException("Cannot find Hadoop version information from corresponding Configuration object."); + } + if (hadoopVersion.equals("classic")) { + return 1; + } else if (hadoopVersion.equals("yarn")) { + return 2; + } + + throw new RuntimeException( + "Cannot support hadoopVersion [" + hadoopVersion + "] other than classic or yarn currently."); + } +} diff --git a/app/com/linkedin/drelephant/analysis/JobType.java b/app/com/linkedin/drelephant/analysis/JobType.java index 537af25c1..5ff817f17 100644 --- a/app/com/linkedin/drelephant/analysis/JobType.java +++ b/app/com/linkedin/drelephant/analysis/JobType.java @@ -5,24 +5,29 @@ public class JobType { - public static final JobType NO_DATA = new JobType("No data received"); - - private final String _type; + private final String _name; private final String _confName; private final Pattern _confPattern; - public JobType(String type) { - _type = type; - _confName = null; - _confPattern = null; - } - - public JobType(String type, String confName, String confPattern) { - _type = type; + /** + * Constructor for a JobType + * + * @param name The name of the job type + * @param confName The configuration to look into + * @param confPattern The regex pattern to match the configuration property + */ + public JobType(String name, String confName, String confPattern) { + _name = name; _confName = confName; _confPattern = Pattern.compile(confPattern); } + /** + * Check if a JobType matches a property + * + * @param jobProp The properties to match + * @return true if matched else false + */ public boolean matchType(Properties jobProp) { // Always return false if confName/confPattern is undefined, // which means we cannot tell if the properties are matching the pattern @@ -33,12 +38,17 @@ public boolean matchType(Properties jobProp) { return jobProp.containsKey(_confName) && _confPattern.matcher((String) jobProp.get(_confName)).matches(); } + /** + * Get the name of the job type + * + * @return The name + */ public String getName() { - return _type; + return _name; } @Override public String toString() { - return _type; + return getName(); } } diff --git a/app/com/linkedin/drelephant/mapreduce/MapReduceApplicationData.java b/app/com/linkedin/drelephant/mapreduce/MapReduceApplicationData.java index c794d7b93..134f504f7 100644 --- a/app/com/linkedin/drelephant/mapreduce/MapReduceApplicationData.java +++ b/app/com/linkedin/drelephant/mapreduce/MapReduceApplicationData.java @@ -1,10 +1,14 @@ package com.linkedin.drelephant.mapreduce; +import com.linkedin.drelephant.analysis.ApplicationType; import com.linkedin.drelephant.analysis.HadoopApplicationData; import java.util.Properties; public class MapReduceApplicationData implements HadoopApplicationData { + private static final ApplicationType APPLICATION_TYPE = new ApplicationType("MAPREDUCE"); + + private String _appId = ""; private String _jobId = ""; private String _username = ""; private String _url = ""; @@ -22,6 +26,11 @@ public MapReduceApplicationData setRetry(boolean isRetry) { return this; } + public MapReduceApplicationData setAppId(String appId) { + this._appId = appId; + return this; + } + public MapReduceApplicationData setJobId(String jobId) { this._jobId = jobId; return this; @@ -85,8 +94,8 @@ public MapReduceTaskData[] getReducerData() { } @Override - public String getUid() { - return _jobId; + public String getAppId() { + return _appId; } @Override @@ -94,6 +103,11 @@ public Properties getConf() { return _jobConf; } + @Override + public ApplicationType getApplicationType() { + return APPLICATION_TYPE; + } + public String getUsername() { return _username; } diff --git a/app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop1.java b/app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop1.java index 7df2d9b33..69708fc4e 100644 --- a/app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop1.java +++ b/app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop1.java @@ -1,7 +1,7 @@ package com.linkedin.drelephant.mapreduce; import com.linkedin.drelephant.analysis.ElephantFetcher; -import com.linkedin.drelephant.analysis.Constants; +import com.linkedin.drelephant.analysis.HadoopSystemContext; import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder.CounterName; import com.linkedin.drelephant.math.Statistics; import com.linkedin.drelephant.util.Utils; @@ -57,7 +57,7 @@ public MapReduceFetcherHadoop1() public MapReduceApplicationData fetchData(String id) throws IOException, AuthenticationException { MapReduceApplicationData jobData = new MapReduceApplicationData(); - jobData.setJobId(id); + jobData.setAppId(id).setJobId(id); try { JobID jobId = JobID.forName(jobData.getJobId()); JobClient jobClient = ThreadContextMR1.getClient(); @@ -96,7 +96,7 @@ public MapReduceApplicationData fetchData(String id) // Fetch job counter MapReduceCounterHolder counterHolder = fetchCounter(job.getCounters()); - int sampleSize = Constants.SHUFFLE_SORT_MAX_SAMPLE_SIZE; + int sampleSize = HadoopSystemContext.SHUFFLE_SORT_MAX_SAMPLE_SIZE; // Fetch mapper task data List mapperList = new ArrayList(); diff --git a/app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop2.java b/app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop2.java index 3f4aa5698..6b8424f49 100644 --- a/app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop2.java +++ b/app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop2.java @@ -119,7 +119,7 @@ public void finishJob(MapReduceApplicationData jobData, boolean success) { public MapReduceApplicationData fetchData(String appId) throws IOException, AuthenticationException { MapReduceApplicationData jobData = new MapReduceApplicationData(); String jobId = Utils.getJobIdFromApplicationId(appId); - jobData.setJobId(jobId); + jobData.setAppId(appId).setJobId(jobId); try { // Fetch job counter URL jobCounterURL = _urlFactory.getJobCounterURL(jobId); diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/GenericDataSkewHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericDataSkewHeuristic.java index c4be200fb..31a94dbfa 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/GenericDataSkewHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericDataSkewHeuristic.java @@ -1,6 +1,6 @@ package com.linkedin.drelephant.mapreduce.heuristics; -import com.linkedin.drelephant.analysis.Constants; +import com.linkedin.drelephant.analysis.HadoopSystemContext; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; @@ -73,7 +73,7 @@ public static Severity getDeviationSeverity(long averageMin, long averageDiff) { } public static Severity getFilesSeverity(long value) { - return Severity.getSeverityAscending(value, Constants.HDFS_BLOCK_SIZE / 8, Constants.HDFS_BLOCK_SIZE / 4, - Constants.HDFS_BLOCK_SIZE / 2, Constants.HDFS_BLOCK_SIZE); + return Severity.getSeverityAscending(value, HadoopSystemContext.HDFS_BLOCK_SIZE / 8, HadoopSystemContext.HDFS_BLOCK_SIZE / 4, + HadoopSystemContext.HDFS_BLOCK_SIZE / 2, HadoopSystemContext.HDFS_BLOCK_SIZE); } } diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperInputSizeHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperInputSizeHeuristic.java index c7f2f1dfa..77dbc6b96 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperInputSizeHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperInputSizeHeuristic.java @@ -5,7 +5,7 @@ import java.util.ArrayList; import java.util.List; -import com.linkedin.drelephant.analysis.Constants; +import com.linkedin.drelephant.analysis.HadoopSystemContext; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; @@ -76,13 +76,13 @@ private Severity largeFilesSeverity(long value, long numTasks, long averageTimeM } public static Severity getSmallFilesSeverity(long value) { - return Severity.getSeverityDescending(value, Constants.HDFS_BLOCK_SIZE / 2, Constants.HDFS_BLOCK_SIZE / 4, - Constants.HDFS_BLOCK_SIZE / 8, Constants.HDFS_BLOCK_SIZE / 32); + return Severity.getSeverityDescending(value, HadoopSystemContext.HDFS_BLOCK_SIZE / 2, HadoopSystemContext.HDFS_BLOCK_SIZE / 4, + HadoopSystemContext.HDFS_BLOCK_SIZE / 8, HadoopSystemContext.HDFS_BLOCK_SIZE / 32); } public static Severity getLargeFilesSeverity(long value) { - return Severity.getSeverityAscending(value, Constants.HDFS_BLOCK_SIZE * 2, Constants.HDFS_BLOCK_SIZE * 3, - Constants.HDFS_BLOCK_SIZE * 4, Constants.HDFS_BLOCK_SIZE * 5); + return Severity.getSeverityAscending(value, HadoopSystemContext.HDFS_BLOCK_SIZE * 2, HadoopSystemContext.HDFS_BLOCK_SIZE * 3, + HadoopSystemContext.HDFS_BLOCK_SIZE * 4, HadoopSystemContext.HDFS_BLOCK_SIZE * 5); } public static Severity getNumTasksSeverity(long numTasks) { diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristic.java index 7ac1bdd0c..def6bfcb8 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristic.java @@ -5,7 +5,7 @@ import java.util.ArrayList; import java.util.List; -import com.linkedin.drelephant.analysis.Constants; +import com.linkedin.drelephant.analysis.HadoopSystemContext; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; @@ -72,8 +72,8 @@ public HeuristicResult apply(MapReduceApplicationData data) { } public static Severity getDiskSpeedSeverity(long speed) { - return Severity.getSeverityDescending(speed, Constants.DISK_READ_SPEED / 2, Constants.DISK_READ_SPEED / 4, - Constants.DISK_READ_SPEED / 8, Constants.DISK_READ_SPEED / 32); + return Severity.getSeverityDescending(speed, HadoopSystemContext.DISK_READ_SPEED / 2, HadoopSystemContext.DISK_READ_SPEED / 4, + HadoopSystemContext.DISK_READ_SPEED / 8, HadoopSystemContext.DISK_READ_SPEED / 32); } public static Severity getRuntimeSeverity(long runtimeMs) { diff --git a/app/com/linkedin/drelephant/util/HeuristicConfiguration.java b/app/com/linkedin/drelephant/util/HeuristicConfiguration.java index 9315a93a4..15e9f0e57 100644 --- a/app/com/linkedin/drelephant/util/HeuristicConfiguration.java +++ b/app/com/linkedin/drelephant/util/HeuristicConfiguration.java @@ -1,6 +1,7 @@ package com.linkedin.drelephant.util; import com.linkedin.drelephant.analysis.ApplicationType; +import com.linkedin.drelephant.analysis.HadoopSystemContext; import java.util.ArrayList; import java.util.List; @@ -23,7 +24,7 @@ public List getHeuristicsConfigurationData() { } private void parseHeuristicConfiguration(Element configuration) { - String hadoopVersion = Utils.getHadoopVersion(); + int hadoopVersion = HadoopSystemContext.getHadoopVersion(); _heuristicsConfDataList = new ArrayList(); @@ -79,21 +80,24 @@ private void parseHeuristicConfiguration(Element configuration) { } String appTypeStr = appTypeNode.getTextContent(); if (appTypeStr == null) { - logger.error( - "Application type is not specified in heuristic " + n + " classname " + className + logger.error("Application type is not specified in heuristic " + n + " classname " + className + ". Skipping this configuration."); continue; } ApplicationType appType = new ApplicationType(appTypeStr); - for (int j = 0; j < versionList.getLength(); j++) { String version = versionList.item(j).getTextContent(); - if (version.equals(hadoopVersion)) { - HeuristicConfigurationData - heuristicData = new HeuristicConfigurationData(heuristicName, className, viewName, appType); + int majorVersion = Utils.getMajorVersionFromString(version); + if (hadoopVersion == majorVersion) { + HeuristicConfigurationData heuristicData = + new HeuristicConfigurationData(heuristicName, className, viewName, appType); _heuristicsConfDataList.add(heuristicData); break; + } else { + logger.warn( + "Ignoring Heuristic: " + heuristicName + " className: " + className + "because it's hadoop version: " + + majorVersion + " does not match the current hadoop version number " + hadoopVersion); } } } diff --git a/app/com/linkedin/drelephant/util/JobTypeConf.java b/app/com/linkedin/drelephant/util/JobTypeConf.java index b2a5d941b..5eb7038c2 100644 --- a/app/com/linkedin/drelephant/util/JobTypeConf.java +++ b/app/com/linkedin/drelephant/util/JobTypeConf.java @@ -1,11 +1,12 @@ package com.linkedin.drelephant.util; -import com.linkedin.drelephant.analysis.HadoopApplicationData; +import com.linkedin.drelephant.analysis.ApplicationType; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; -import java.util.Properties; +import java.util.Map; import java.util.regex.PatternSyntaxException; import javax.xml.parsers.DocumentBuilder; @@ -27,57 +28,25 @@ public class JobTypeConf { private static final Logger logger = Logger.getLogger(JobTypeConf.class); private static final int TYPE_LEN_LIMIT = 20; - // This is a default job type we will add into the type list even not configured. - private static final String DEFAULT_TYPE = "HadoopJava"; - private static final String CONFIG_FILE_PATH = "job-types.xml"; - private static final JobTypeConf _heuristicConfInstance = new JobTypeConf(); + private final String _configFilePath; - private List _jobTypeList; + private Map> _appTypeToJobTypeList = new HashMap>(); - private JobTypeConf() { + public JobTypeConf(String configFilePath) { + _configFilePath = configFilePath; parseJobTypeConf(); } - public static JobTypeConf instance() { - return _heuristicConfInstance; - } - - public List getJobTypeList() { - return _jobTypeList; - } - - public JobType getJobType(HadoopApplicationData data) { - Properties conf = data.getConf(); - for (JobType type : _jobTypeList) { - if (type.matchType(conf)) { - return type; - } - } - return JobType.NO_DATA; + public Map> getAppTypeToJobTypeList() { + return _appTypeToJobTypeList; } private void parseJobTypeConf() { + logger.info("Loading job type config file " + _configFilePath); - logger.info("Loading job type config file " + CONFIG_FILE_PATH); - _jobTypeList = new ArrayList(); + Map defaultMap = new HashMap(); - InputStream instream = Play.application().resourceAsStream(CONFIG_FILE_PATH); - if (instream == null) { - throw new RuntimeException("File " + CONFIG_FILE_PATH + " does not exist."); - } - - Document document = null; - try { - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - DocumentBuilder builder = factory.newDocumentBuilder(); - document = builder.parse(instream); - } catch (ParserConfigurationException e) { - throw new RuntimeException("XML Parser could not be created.", e); - } catch (SAXException e) { - throw new RuntimeException(CONFIG_FILE_PATH + " is not properly formed", e); - } catch (IOException e) { - throw new RuntimeException("Unable to read " + CONFIG_FILE_PATH, e); - } + Document document = Utils.loadXMLDoc(_configFilePath); NodeList nodes = document.getDocumentElement().getChildNodes(); for (int i = 0; i < nodes.getLength(); i++) { @@ -87,7 +56,7 @@ private void parseJobTypeConf() { n++; Element jobTypeNode = (Element) node; String jobTypeName; - Node jobTypeNameNode = jobTypeNode.getElementsByTagName("type").item(0); + Node jobTypeNameNode = jobTypeNode.getElementsByTagName("name").item(0); if (jobTypeNameNode == null) { throw new RuntimeException("No tag 'jobtype' in jobtype " + n); } @@ -104,7 +73,7 @@ private void parseJobTypeConf() { String jobConfName; Node jobConfNameNode = jobTypeNode.getElementsByTagName("conf").item(0); if (jobConfNameNode == null) { - throw new RuntimeException("No tag 'conf' in jobtype " + jobTypeName ); + throw new RuntimeException("No tag 'conf' in jobtype " + jobTypeName); } jobConfName = jobConfNameNode.getTextContent(); if (jobConfName.equals("")) { @@ -122,20 +91,70 @@ private void parseJobTypeConf() { jobConfValue = ".*"; } } - logger.info("Loaded jobType:" + jobTypeName + " confName:" + jobConfName + " confValue:" + jobConfValue); + + String appTypeName; + Node appTypeNameNode = jobTypeNode.getElementsByTagName("applicationtype").item(0); + if (appTypeNameNode == null) { + throw new RuntimeException("No tag 'applicationtype' in jobtype " + jobTypeName); + } + appTypeName = appTypeNameNode.getTextContent(); + ApplicationType appType = new ApplicationType(appTypeName); + + boolean isDefault = jobTypeNode.getElementsByTagName("isDefault").item(0) != null; + + JobType newJobType = null; try { - _jobTypeList.add(new JobType(jobTypeName, jobConfName, jobConfValue)); + newJobType = new JobType(jobTypeName, jobConfName, jobConfValue); } catch (PatternSyntaxException e) { - throw new RuntimeException("Error processing this pattern. Pattern:" + jobConfValue + " jobtype:" + jobTypeName); + throw new RuntimeException( + "Error processing this pattern. Pattern:" + jobConfValue + " jobtype:" + jobTypeName); } + + String newJobTypeStr = String + .format("jobType:%s, for application type:%s, isDefault:%s, confName:%s, confValue:%s.", jobTypeName, + appTypeName, isDefault, jobConfName, jobConfValue); + logger.info("Loaded " + newJobTypeStr); + + if (isDefault) { + if (defaultMap.containsKey(appType)) { + throw new RuntimeException( + "Each application type should have one and only one default job type. Duplicate default job type: " + + newJobTypeStr + " for application type: " + appType.getName()); + } else { + defaultMap.put(appType, newJobType); + } + } else { + List jobTypes = getJobTypeList(appType); + jobTypes.add(newJobType); + } + } + } + + // Append default maps to the end of each job type list + for (Map.Entry entry : defaultMap.entrySet()) { + ApplicationType appType = entry.getKey(); + JobType jobType = entry.getValue(); + List jobTypes = getJobTypeList(appType); + jobTypes.add(jobType); + } + + // Sanity check + for(ApplicationType appType : _appTypeToJobTypeList.keySet()) { + if (!defaultMap.containsKey(appType)) { + throw new RuntimeException("Each application type should have one and only one default job type, there is" + + " none for application type: " + appType.getName() + ". Use to tag one."); } } - // Add default type to the list. All jobs will match this type because this conf always exists - if (Utils.getHadoopVersion().equals("yarn")) { - _jobTypeList.add(new JobType(DEFAULT_TYPE, "mapreduce.framework.name", ".*")); - } else { - _jobTypeList.add(new JobType(DEFAULT_TYPE, "mapred.job.tracker", ".*")); + + logger.info("Loaded total " + _appTypeToJobTypeList.size() + " job types."); + } + + private List getJobTypeList(ApplicationType appType) { + List jobTypes = _appTypeToJobTypeList.get(appType); + if (jobTypes == null) { + jobTypes = new ArrayList(); + _appTypeToJobTypeList.put(appType, jobTypes); } - logger.info("Loaded total " + _jobTypeList.size() + " job types."); + return jobTypes; } } diff --git a/app/com/linkedin/drelephant/util/Utils.java b/app/com/linkedin/drelephant/util/Utils.java index 80e9aa1a6..3cd442bfb 100644 --- a/app/com/linkedin/drelephant/util/Utils.java +++ b/app/com/linkedin/drelephant/util/Utils.java @@ -4,6 +4,8 @@ import com.linkedin.drelephant.ElephantContext; import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; +import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; @@ -12,22 +14,23 @@ import java.util.Map; import java.util.Properties; import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.apache.avro.generic.IndexedRecord; import org.apache.commons.lang.StringUtils; -import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.mapred.JobConf; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.w3c.dom.Document; import org.xml.sax.SAXException; -import play.Play; public final class Utils { private static final Logger logger = Logger.getLogger(Utils.class); + // Matching x.x.x or x.x.x-li1 (x are numbers) + public static final Pattern VERSION_PATTERN = Pattern.compile("(\\d+)(?:\\.\\d+)*(?:\\-[\\dA-Za-z]+)?"); private Utils() { // do nothing @@ -109,9 +112,11 @@ public static void publishMetrics(MapReduceApplicationData jobData) { * @return The loaded Document object */ public static Document loadXMLDoc(String filePath) { - InputStream instream = Play.application().resourceAsStream(filePath); - if (instream == null) { - throw new RuntimeException("File " + filePath + " does not exist."); + InputStream instream = null; + try { + instream = new FileInputStream(filePath); + } catch (FileNotFoundException e) { + throw new RuntimeException("File " + filePath + " does not exist.", e); } Document document = null; @@ -131,29 +136,23 @@ public static Document loadXMLDoc(String filePath) { } /** - * Detect the current Hadoop version Dr Elephant is deployed for. + * Given a version string e.g.: 1.2.3, return the corresponding major version number * - * @return the current hadoop version ('yarn' or 'classic' typically). + * @param versionString the version string + * @return the major version number */ - public static String getHadoopVersion() { - // Important, new Configuration might not be timely loaded in Hadoop 1 environment - Configuration hadoopConf = new JobConf(); - Map vals = hadoopConf.getValByRegex(".*"); - String hadoopVersion = hadoopConf.get("mapreduce.framework.name"); - if (hadoopVersion == null) { - if (hadoopConf.get("mapred.job.tracker.http.address") != null) { - hadoopVersion = "classic"; - } else { - throw new RuntimeException("Hadoop config error. No framework name provided."); - } + public static int getMajorVersionFromString(String versionString) { + if (versionString == null) { + throw new IllegalArgumentException("Cannot get major version from null."); } - if (hadoopVersion == null || (!hadoopVersion.equals("classic") && !hadoopVersion.equals("yarn"))) { - throw new RuntimeException( - "Cannot support hadoopVersion [" + hadoopVersion + "] other than classic or yarn currently."); + Matcher matcher = VERSION_PATTERN.matcher(versionString); + if (!matcher.matches() || matcher.groupCount() < 1) { + throw new IllegalArgumentException( + "Version string [" + versionString + "] does not match pattern + [" + VERSION_PATTERN + "]."); } - return hadoopVersion.toLowerCase().trim(); + return Integer.parseInt(matcher.group(1)); } /** @@ -164,8 +163,11 @@ public static String getHadoopVersion() { */ public static Map parseJavaOptions(String str) { Map options = new HashMap(); - String[] tokens = str.split(" "); + String[] tokens = str.trim().split("\\s"); for (String token : tokens) { + if (token.isEmpty()) { + continue; + } if (!token.startsWith("-D")) { throw new IllegalArgumentException( "Cannot parse java option string [" + str + "]. Some options does not begin with -D prefix."); diff --git a/app/org/apache/spark/deploy/history/SparkDataCollection.scala b/app/org/apache/spark/deploy/history/SparkDataCollection.scala index 614d1428e..310d626cb 100644 --- a/app/org/apache/spark/deploy/history/SparkDataCollection.scala +++ b/app/org/apache/spark/deploy/history/SparkDataCollection.scala @@ -50,6 +50,8 @@ class SparkDataCollection(applicationEventListener: ApplicationEventListener, import SparkDataCollection._ + override def getApplicationType(): ApplicationType = APPLICATION_TYPE + override def getConf(): Properties = getEnvironmentData().getSparkProperties() override def getGeneralData(): SparkGeneralData = { @@ -251,12 +253,14 @@ class SparkDataCollection(applicationEventListener: ApplicationEventListener, _storageData } - override def getUid: String = { + override def getAppId: String = { getGeneralData().getApplicationId } } object SparkDataCollection { + private val APPLICATION_TYPE = new ApplicationType("SPARK") + def stringToSet(str: String): JSet[String] = { val set = new JHashSet[String]() str.split(",").foreach { case t: String => set.add(t)} diff --git a/app/views/help.scala.html b/app/views/help.scala.html index d3ea5131e..1c2b2cf80 100644 --- a/app/views/help.scala.html +++ b/app/views/help.scala.html @@ -3,8 +3,12 @@ @main("Dr. Elephant", "help") { @tags.column(3) { @tags.panel(){ Topics } { - @for(heuristic <- com.linkedin.drelephant.ElephantContext.instance().getAllHeuristicNames()) { - @heuristic
+ @for((appTypeName, heuristicList) <- com.linkedin.drelephant.ElephantContext.instance().getAllHeuristicNames()) { +
@appTypeName
+ @for(heuristic <-heuristicList) { + @heuristic
+ } +
} @com.linkedin.drelephant.analysis.HeuristicResult.NO_DATA.getAnalysis
} diff --git a/app/views/search.scala.html b/app/views/search.scala.html index ec9304bf9..e7e893d79 100644 --- a/app/views/search.scala.html +++ b/app/views/search.scala.html @@ -21,8 +21,12 @@
diff --git a/conf/FetcherConf.xml b/conf/FetcherConf.xml new file mode 100644 index 000000000..63dfdf491 --- /dev/null +++ b/conf/FetcherConf.xml @@ -0,0 +1,35 @@ + + + + + + mapreduce + 1 + com.linkedin.drelephant.mapreduce.MapReduceFetcherHadoop1 + + + mapreduce + 2 + com.linkedin.drelephant.mapreduce.MapReduceFetcherHadoop2 + + + spark + 2 + org.apache.spark.deploy.history.SparkFSFetcher + + diff --git a/conf/HeuristicConf.xml b/conf/HeuristicConf.xml new file mode 100644 index 000000000..9ead236b7 --- /dev/null +++ b/conf/HeuristicConf.xml @@ -0,0 +1,141 @@ + + + + + + mapreduce + Mapper Data Skew + com.linkedin.drelephant.mapreduce.heuristics.MapperDataSkewHeuristic + views.html.helpMapperDataSkew + + 1 + 2 + + + + + mapreduce + Mapper Input Size + com.linkedin.drelephant.mapreduce.heuristics.MapperInputSizeHeuristic + views.html.helpMapperInputSize + + 1 + 2 + + + + + mapreduce + Mapper Speed + com.linkedin.drelephant.mapreduce.heuristics.MapperSpeedHeuristic + views.html.helpMapperSpeed + + 1 + 2 + + + + + mapreduce + Mapper Spill + com.linkedin.drelephant.mapreduce.heuristics.MapperSpillHeuristic + views.html.helpMapperSpill + + + + + + mapreduce + Reducer Data Skew + com.linkedin.drelephant.mapreduce.heuristics.ReducerDataSkewHeuristic + views.html.helpReducerDataSkew + + 1 + 2 + + + + + mapreduce + Reducer Time + com.linkedin.drelephant.mapreduce.heuristics.ReducerTimeHeuristic + views.html.helpReducerTime + + 1 + 2 + + + + + mapreduce + Shuffle & Sort + com.linkedin.drelephant.mapreduce.heuristics.ShuffleSortHeuristic + views.html.helpShuffleSort + + 1 + 2 + + + + + mapreduce + Job Queue Limit + com.linkedin.drelephant.mapreduce.heuristics.JobQueueLimitHeuristic + views.html.helpJobQueueLimit + + 1 + + + + + spark + Spark Configuration Best Practice + com.linkedin.drelephant.spark.heuristics.BestPropertiesConventionHeuristic + views.html.helpSparkBestProperties + + 2 + + + + + spark + Spark Memory Limit + com.linkedin.drelephant.spark.heuristics.MemoryLimitHeuristic + views.html.helpSparkMemoryLimit + + 2 + + + + + spark + Spark Stage Runtime + com.linkedin.drelephant.spark.heuristics.StageRuntimeHeuristic + views.html.helpSparkStageRuntime + + 2 + + + + + spark + Spark Job Runtime + com.linkedin.drelephant.spark.heuristics.JobRuntimeHeuristic + views.html.helpSparkJobRuntime + + 2 + + + + + spark + Spark Executor Load Balance + com.linkedin.drelephant.spark.heuristics.ExecutorLoadHeuristic + views.html.helpSparkExecutorLoad + + 2 + + + + diff --git a/conf/job-types.xml b/conf/JobTypeConf.xml similarity index 61% rename from conf/job-types.xml rename to conf/JobTypeConf.xml index caa9ee899..8a384d777 100644 --- a/conf/job-types.xml +++ b/conf/JobTypeConf.xml @@ -5,9 +5,11 @@ defined for that job type. For example, if a MR job has 'pig.script' in its configuration, it will be classified as Pig jobtype; if a MR job has configuration 'mapred.reducer.class' and value 'HadoopStoreBuilderReducer', it will be a voldemort job. - For each jobtype defined in this file, it is required to have a type name , a conf + For each jobtype defined in this file, it is required to have a type name , a conf name , and an optional conf value . The optional requires a regex expression with '.*'(any char multiple times) by default. + when present is indicating the current JobType is the default choice for an application type. One + application type should have one and only one default choice. Type matching is top-down - If a job matches multiple types in this file, it will be First Defined Comes First. If no jobtype in this file matches the job, it will have default jobtype, which is HadoopJava. @@ -15,28 +17,41 @@ - Spark + Spark + spark spark.app.id + - Pig + Pig + mapreduce pig.script - Hive + Hive + mapreduce hive.mapred.mode - Cascading + Cascading + mapreduce cascading.app.frameworks - Voldemort + Voldemort + mapreduce mapred.reducer.class voldemort.store.readonly.mr.* - Kafka + Kafka + mapreduce kafka.url + + HadoopJava + mapreduce + mapred.child.java.opts + + diff --git a/conf/application.conf b/conf/application.conf index fe852e5b2..7453b5f9c 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -80,17 +80,13 @@ smtp.from="azkaban-noreply@linkedin.com" # TODO: JUST FOR REFERENCE RIGHT NOW, THESE SHOULD BE NOT CHECK THESE INTO CODE BASE # Dali metrics publisher metrics.publisher-conf=conf/CounterPublisherConf.xml +fetchers.conf.location=conf/FetcherConf.xml +jobtypes.conf.location=conf/JobTypeConf.xml +heuristics.conf.location=conf/HeuristicConf.xml #Kerberos keytab.location=/home/yizhou/Dev/dr-elephant-test/yizhou.keytab keytab.user=yizhou - -spark.authenticate=true -spark.ui.acls.enable=true -spark.logConf=true spark.yarn.historyServer.address="eat1-nertzjh01.grid.linkedin.com:18080" -spark.eventLog.enabled=true -spark.eventLog.compress=true spark.eventLog.dir="hdfs://eat1-nertznn01.grid.linkedin.com:9000/system/spark-history" -spark.ui.port=0 diff --git a/conf/elephant-conf.xml b/conf/elephant-conf.xml deleted file mode 100644 index 79004a909..000000000 --- a/conf/elephant-conf.xml +++ /dev/null @@ -1,163 +0,0 @@ - - - - - - - mapreduce - classic - com.linkedin.drelephant.mapreduce.MapReduceFetcherHadoop1 - - - mapreduce - yarn - com.linkedin.drelephant.mapreduce.MapReduceFetcherHadoop2 - - - spark - yarn - org.apache.spark.deploy.history.SparkFSFetcher - - > - - - - - mapreduce - Mapper Data Skew - com.linkedin.drelephant.mapreduce.heuristics.MapperDataSkewHeuristic - views.html.helpMapperDataSkew - - classic - yarn - - - - - mapreduce - Mapper Input Size - com.linkedin.drelephant.mapreduce.heuristics.MapperInputSizeHeuristic - views.html.helpMapperInputSize - - classic - yarn - - - - - mapreduce - Mapper Speed - com.linkedin.drelephant.mapreduce.heuristics.MapperSpeedHeuristic - views.html.helpMapperSpeed - - classic - yarn - - - - - mapreduce - Mapper Spill - com.linkedin.drelephant.mapreduce.heuristics.MapperSpillHeuristic - views.html.helpMapperSpill - - - - - - mapreduce - Reducer Data Skew - com.linkedin.drelephant.mapreduce.heuristics.ReducerDataSkewHeuristic - views.html.helpReducerDataSkew - - classic - yarn - - - - - mapreduce - Reducer Time - com.linkedin.drelephant.mapreduce.heuristics.ReducerTimeHeuristic - views.html.helpReducerTime - - classic - yarn - - - - - mapreduce - Shuffle & Sort - com.linkedin.drelephant.mapreduce.heuristics.ShuffleSortHeuristic - views.html.helpShuffleSort - - classic - yarn - - - - - mapreduce - Job Queue Limit - com.linkedin.drelephant.mapreduce.heuristics.JobQueueLimitHeuristic - views.html.helpJobQueueLimit - - classic - - - - - spark - Spark Configuration Best Practice - com.linkedin.drelephant.spark.heuristics.BestPropertiesConventionHeuristic - views.html.helpSparkBestProperties - - yarn - - - - - spark - Spark Memory Limit - com.linkedin.drelephant.spark.heuristics.MemoryLimitHeuristic - views.html.helpSparkMemoryLimit - - yarn - - - - - spark - Spark Stage Runtime - com.linkedin.drelephant.spark.heuristics.StageRuntimeHeuristic - views.html.helpSparkStageRuntime - - yarn - - - - - spark - Spark Job Runtime - com.linkedin.drelephant.spark.heuristics.JobRuntimeHeuristic - views.html.helpSparkJobRuntime - - yarn - - - - - spark - Spark Executor Load Balance - com.linkedin.drelephant.spark.heuristics.ExecutorLoadHeuristic - views.html.helpSparkExecutorLoad - - yarn - - - - - diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/JobQueueLimitHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/JobQueueLimitHeuristicTest.java index f61dfc859..b3af22cf3 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/JobQueueLimitHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/JobQueueLimitHeuristicTest.java @@ -7,7 +7,7 @@ import junit.framework.TestCase; -import com.linkedin.drelephant.analysis.Constants; +import com.linkedin.drelephant.analysis.HadoopSystemContext; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; @@ -19,7 +19,7 @@ public class JobQueueLimitHeuristicTest extends TestCase { Heuristic _heuristic = new JobQueueLimitHeuristic(); - private static final int NUM_TASKS = Constants.SHUFFLE_SORT_MAX_SAMPLE_SIZE; + private static final int NUM_TASKS = HadoopSystemContext.SHUFFLE_SORT_MAX_SAMPLE_SIZE; @Test public void testRuntimeCritical() throws IOException { diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperDataSkewHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperDataSkewHeuristicTest.java index 04d0d5314..7e5d8b936 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperDataSkewHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperDataSkewHeuristicTest.java @@ -1,6 +1,6 @@ package com.linkedin.drelephant.mapreduce.heuristics; -import com.linkedin.drelephant.analysis.Constants; +import com.linkedin.drelephant.analysis.HadoopSystemContext; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; @@ -13,7 +13,7 @@ public class MapperDataSkewHeuristicTest extends TestCase { - private static final long UNITSIZE = Constants.HDFS_BLOCK_SIZE / 64; + private static final long UNITSIZE = HadoopSystemContext.HDFS_BLOCK_SIZE / 64; Heuristic _heuristic = new MapperDataSkewHeuristic(); public void testCritical() throws IOException { diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperInputSizeHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperInputSizeHeuristicTest.java index cb2407403..84b383abe 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperInputSizeHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperInputSizeHeuristicTest.java @@ -1,6 +1,6 @@ package com.linkedin.drelephant.mapreduce.heuristics; -import com.linkedin.drelephant.analysis.Constants; +import com.linkedin.drelephant.analysis.HadoopSystemContext; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; @@ -14,7 +14,7 @@ public class MapperInputSizeHeuristicTest extends TestCase { - private static final long UNITSIZE = Constants.HDFS_BLOCK_SIZE; + private static final long UNITSIZE = HadoopSystemContext.HDFS_BLOCK_SIZE; Heuristic _heuristic = new MapperInputSizeHeuristic(); // Test batch 1: Large file test with large runtime and various number of tasks */ diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristicTest.java index e28444c85..fc5cdd145 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristicTest.java @@ -1,6 +1,6 @@ package com.linkedin.drelephant.mapreduce.heuristics; -import com.linkedin.drelephant.analysis.Constants; +import com.linkedin.drelephant.analysis.HadoopSystemContext; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; @@ -14,9 +14,9 @@ public class MapperSpeedHeuristicTest extends TestCase { Heuristic _heuristic = new MapperSpeedHeuristic(); - private static final long UNITSIZE = Constants.HDFS_BLOCK_SIZE / 64; + private static final long UNITSIZE = HadoopSystemContext.HDFS_BLOCK_SIZE / 64; private static final long MINUTE_IN_MS = Statistics.MINUTE_IN_MS; - private static final int NUMTASKS = Constants.SHUFFLE_SORT_MAX_SAMPLE_SIZE; + private static final int NUMTASKS = HadoopSystemContext.SHUFFLE_SORT_MAX_SAMPLE_SIZE; public void testCritical() throws IOException { assertEquals(Severity.CRITICAL, analyzeJob(120 * MINUTE_IN_MS, 10000 * UNITSIZE)); diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristicTest.java index c4a47f63e..d4862a533 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristicTest.java @@ -1,6 +1,6 @@ package com.linkedin.drelephant.mapreduce.heuristics; -import com.linkedin.drelephant.analysis.Constants; +import com.linkedin.drelephant.analysis.HadoopSystemContext; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; @@ -14,7 +14,7 @@ public class MapperSpillHeuristicTest extends TestCase { Heuristic heuristic = new MapperSpillHeuristic(); - private static final int numTasks = Constants.SHUFFLE_SORT_MAX_SAMPLE_SIZE; + private static final int numTasks = HadoopSystemContext.SHUFFLE_SORT_MAX_SAMPLE_SIZE; public void testCritical() throws IOException { assertEquals(Severity.CRITICAL, analyzeJob(2200, 1000)); diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerDataSkewHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerDataSkewHeuristicTest.java index 5fc9872ef..0f307b3b4 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerDataSkewHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerDataSkewHeuristicTest.java @@ -1,6 +1,6 @@ package com.linkedin.drelephant.mapreduce.heuristics; -import com.linkedin.drelephant.analysis.Constants; +import com.linkedin.drelephant.analysis.HadoopSystemContext; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; @@ -12,7 +12,7 @@ public class ReducerDataSkewHeuristicTest extends TestCase { - private static final long UNITSIZE = Constants.HDFS_BLOCK_SIZE / 64; + private static final long UNITSIZE = HadoopSystemContext.HDFS_BLOCK_SIZE / 64; Heuristic _heuristic = new ReducerDataSkewHeuristic(); public void testCritical() throws IOException { diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristicTest.java index eaeffc8a2..9b9781032 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristicTest.java @@ -2,7 +2,7 @@ import java.io.IOException; -import com.linkedin.drelephant.analysis.Constants; +import com.linkedin.drelephant.analysis.HadoopSystemContext; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; @@ -16,7 +16,7 @@ public class ShuffleSortHeuristicTest extends TestCase { Heuristic _heuristic = new ShuffleSortHeuristic(); - private static final int NUMTASKS = Constants.SHUFFLE_SORT_MAX_SAMPLE_SIZE; + private static final int NUMTASKS = HadoopSystemContext.SHUFFLE_SORT_MAX_SAMPLE_SIZE; private static final long MINUTE_IN_MS = Statistics.MINUTE_IN_MS;; public void testLongShuffleCritical() throws IOException { diff --git a/test/com/linkedin/drelephant/spark/MockSparkApplicationData.java b/test/com/linkedin/drelephant/spark/MockSparkApplicationData.java index f8476ee4a..d7d0adcee 100644 --- a/test/com/linkedin/drelephant/spark/MockSparkApplicationData.java +++ b/test/com/linkedin/drelephant/spark/MockSparkApplicationData.java @@ -10,6 +10,8 @@ * @author yizhou */ public class MockSparkApplicationData implements SparkApplicationData { + private static final ApplicationType APPLICATION_TYPE = new ApplicationType("SPARK"); + private final SparkGeneralData _sparkGeneralData; private final SparkEnvironmentData _sparkEnvironmentData; private final SparkExecutorData _sparkExecutorData; @@ -55,7 +57,12 @@ public Properties getConf() { } @Override - public String getUid() { + public String getAppId() { return getGeneralData().getApplicationId(); } + + @Override + public ApplicationType getApplicationType() { + return APPLICATION_TYPE; + } } diff --git a/test/com/linkedin/drelephant/util/UtilsTest.java b/test/com/linkedin/drelephant/util/UtilsTest.java new file mode 100644 index 000000000..6b6cb9bf9 --- /dev/null +++ b/test/com/linkedin/drelephant/util/UtilsTest.java @@ -0,0 +1,47 @@ +package com.linkedin.drelephant.util; + +import java.util.Map; +import junit.framework.TestCase; + + +/** + * This class tests the Utils class + * + */ +public class UtilsTest extends TestCase { + public void testParseJavaOptions() { + Map options1 = Utils.parseJavaOptions("-Dfoo=bar"); + assertEquals(1, options1.size()); + assertEquals("bar", options1.get("foo")); + + Map options2 = Utils.parseJavaOptions(" -Dfoo=bar -Dfoo2=bar2 -Dfoo3=bar3"); + assertEquals(3, options2.size()); + assertEquals("bar", options2.get("foo")); + assertEquals("bar2", options2.get("foo2")); + assertEquals("bar3", options2.get("foo3")); + } + + public void testGetMajorVersionFromString() { + assertEquals(1, Utils.getMajorVersionFromString("1")); + assertEquals(2, Utils.getMajorVersionFromString("2.1")); + assertEquals(2, Utils.getMajorVersionFromString("2.1.3")); + assertEquals(2, Utils.getMajorVersionFromString("2.1.3-LI0")); + + // Negative cases + testGetMajorVersionFromStringNegative(null); + testGetMajorVersionFromStringNegative(".1"); + testGetMajorVersionFromStringNegative("-LI0"); + testGetMajorVersionFromStringNegative("a"); + testGetMajorVersionFromStringNegative("1.a"); + } + + private void testGetMajorVersionFromStringNegative(String input){ + // Expecting IllegalArgumentException + try { + Utils.getMajorVersionFromString(input); + fail(); + } catch (IllegalArgumentException e) { + // The exception is expected, do nothing + } + } +} From 0b0ea85ebb9f15858be8c2cf017eed914565c13e Mon Sep 17 00:00:00 2001 From: Yitong Zhou Date: Thu, 4 Jun 2015 11:35:58 -0700 Subject: [PATCH 072/272] Modify Spark's dependency configuration, some more logging and a fix of SparkFSFetcher to avoid sharing the same filesystem object across different threads --- app/com/linkedin/drelephant/ElephantRunner.java | 10 +++++++--- app/com/linkedin/drelephant/util/Utils.java | 1 - .../apache/spark/deploy/history/SparkFSFetcher.scala | 8 ++++---- build.sbt | 3 ++- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/app/com/linkedin/drelephant/ElephantRunner.java b/app/com/linkedin/drelephant/ElephantRunner.java index 1ae966fc4..c37151e38 100644 --- a/app/com/linkedin/drelephant/ElephantRunner.java +++ b/app/com/linkedin/drelephant/ElephantRunner.java @@ -6,7 +6,6 @@ import com.linkedin.drelephant.analysis.HadoopSystemContext; import com.linkedin.drelephant.analysis.AnalysisProviderHadoop2; import com.linkedin.drelephant.notifications.EmailThread; -import com.linkedin.drelephant.util.Utils; import java.io.IOException; import java.security.PrivilegedAction; import java.util.List; @@ -35,7 +34,7 @@ public class ElephantRunner implements Runnable { private void loadAnalysisProvider() { JobConf configuration = new JobConf(); - int hadoopVersion = HadoopSystemContext.getHadoopVersion(); configuration.get("mapreduce.framework.name"); + int hadoopVersion = HadoopSystemContext.getHadoopVersion(); if (hadoopVersion == 2) { _analysisProvider = new AnalysisProviderHadoop2(); @@ -130,6 +129,10 @@ public void run() { promise = _jobQueue.take(); JobResult result = promise.getAnalysis(); result.save(); + + logger.info("Executor thread " + _threadId + " analyzed " + promise.getAppType().getName() + " " + promise + .getAppId()); + // TODO: how to test email sending? _emailer.enqueue(result); } catch (InterruptedException ex) { @@ -142,7 +145,8 @@ public void run() { _analysisProvider.addIntoRetries(promise); } else { logger.error( - "Drop analysis job. Reason: reached the max retries for application id = [" + promise.getAppId() + "]."); + "Drop analysis job. Reason: reached the max retries for application id = [" + promise.getAppId() + + "]."); } } } diff --git a/app/com/linkedin/drelephant/util/Utils.java b/app/com/linkedin/drelephant/util/Utils.java index 3cd442bfb..9ff4e959e 100644 --- a/app/com/linkedin/drelephant/util/Utils.java +++ b/app/com/linkedin/drelephant/util/Utils.java @@ -63,7 +63,6 @@ public static void publishMetrics(MapReduceApplicationData jobData) { DaliMetricsAPI.JobProperties jobProperties = new DaliMetricsAPI.JobProperties(jobConf); if (jobProperties.getCountersToPublish().isEmpty()) { // Nothing to do - logger.info("No counters-to-publish property presented in job [" + jobData.getJobId() + "]"); return; } logger.info("Publishing counters for job[" + jobData.getJobId() + "]"); diff --git a/app/org/apache/spark/deploy/history/SparkFSFetcher.scala b/app/org/apache/spark/deploy/history/SparkFSFetcher.scala index 5bde4297b..f35c0aa0e 100644 --- a/app/org/apache/spark/deploy/history/SparkFSFetcher.scala +++ b/app/org/apache/spark/deploy/history/SparkFSFetcher.scala @@ -40,16 +40,16 @@ class SparkFSFetcher extends ElephantFetcher[SparkApplicationData] { val hdfsAddress = if (nodeAddress == null) "" else "webhdfs://" + nodeAddress val uri = new URI(_sparkConf.get("spark.eventLog.dir", DEFAULT_LOG_DIR)) - System.out.println("logDIR: " + hdfsAddress + uri.getPath) - hdfsAddress + uri.getPath + val logDir = hdfsAddress + uri.getPath + logger.info("Looking for spark logs at logDir: " + logDir) + logDir } private val _sparkConf = new SparkConf() private val _security = new HadoopSecurity() - private lazy val fs: FileSystem = { + private def fs: FileSystem = { _security.checkLogin() - logger.info("Looking for spark logs at " + _logDir) if (new URI(_logDir).getHost == null) { FileSystem.getLocal(new Configuration()) diff --git a/build.sbt b/build.sbt index 9d5c04892..af1f92079 100644 --- a/build.sbt +++ b/build.sbt @@ -21,7 +21,8 @@ libraryDependencies ++= Seq( "com.linkedin.avro-schemas" % "avro-schemas-tracking" % "6.0.518", // Spark dependencies, exclude avro transitive dependencies to avoid being overriden by 1.7.x "org.apache.spark" % "spark-core_2.10" % "1.2.0" excludeAll( - ExclusionRule(organization = "org.apache.avro") + ExclusionRule(organization = "org.apache.avro"), + ExclusionRule(organization = "org.apache.hadoop") ), // Hadoop defaultly are using guava 11.0, might raise NoSuchMethodException "com.google.guava" % "guava" % "18.0" From 5a7e1dd0bdd6690aecf94adb88974410937f1842 Mon Sep 17 00:00:00 2001 From: Yitong Zhou Date: Thu, 4 Jun 2015 14:52:25 -0700 Subject: [PATCH 073/272] A tmp commit trying to test Nertz Spades deployment --- app/com/linkedin/drelephant/util/Utils.java | 11 ++++++----- build.sbt | 1 + compile.sh | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/app/com/linkedin/drelephant/util/Utils.java b/app/com/linkedin/drelephant/util/Utils.java index 9ff4e959e..788124646 100644 --- a/app/com/linkedin/drelephant/util/Utils.java +++ b/app/com/linkedin/drelephant/util/Utils.java @@ -25,6 +25,7 @@ import org.apache.log4j.Logger; import org.w3c.dom.Document; import org.xml.sax.SAXException; +import play.Play; public final class Utils { @@ -112,11 +113,11 @@ public static void publishMetrics(MapReduceApplicationData jobData) { */ public static Document loadXMLDoc(String filePath) { InputStream instream = null; - try { - instream = new FileInputStream(filePath); - } catch (FileNotFoundException e) { - throw new RuntimeException("File " + filePath + " does not exist.", e); - } + // try { + instream = Play.application().resourceAsStream(filePath); + // } catch (FileNotFoundException e) { + // throw new RuntimeException("File " + filePath + " does not exist.", e); + // } Document document = null; try { diff --git a/build.sbt b/build.sbt index af1f92079..78684ebe6 100644 --- a/build.sbt +++ b/build.sbt @@ -34,6 +34,7 @@ unmanagedClasspath in Compile ++= update.value.select(configurationFilter("compi libraryDependencies ++= Seq( "com.linkedin.hadoop" % "hadoop-common" % "2.3.0.+" % "compileonly", + "com.linkedin.hadoop" % "hadoop-hdfs" % "2.3.0.+" % "compileonly", "com.linkedin.hadoop" % "hadoop-mapreduce-client-core" % "2.3.0.+" % "compileonly" ) diff --git a/compile.sh b/compile.sh index efa8786d7..66f662fa4 100755 --- a/compile.sh +++ b/compile.sh @@ -13,7 +13,7 @@ stop_script=${project_root}/stop.sh rm -rf ${project_root}/dist mkdir dist -play clean compile test dist +play clean compile dist cd target/universal From 9cf450a617f7096b587595c6ba4e0b4e23d94ef2 Mon Sep 17 00:00:00 2001 From: Yitong Zhou Date: Thu, 11 Jun 2015 16:06:49 -0700 Subject: [PATCH 074/272] Addressing some renaming feedback, logging --- .../linkedin/drelephant/ElephantContext.java | 8 +-- .../linkedin/drelephant/ElephantRunner.java | 54 ++++++++++--------- ...{AnalysisPromise.java => AnalyticJob.java} | 35 +++++++----- ...rovider.java => AnalyticJobGenerator.java} | 19 +++---- ....java => AnalyticJobGeneratorHadoop1.java} | 34 +++++------- ....java => AnalyticJobGeneratorHadoop2.java} | 45 ++++++++-------- .../drelephant/util/InfoExtractor.java | 2 +- app/com/linkedin/drelephant/util/Utils.java | 12 +++-- .../spark/deploy/history/SparkFSFetcher.scala | 4 +- .../StorageStatusTrackingListener.scala | 34 ++++++------ conf/application.conf | 9 ++-- 11 files changed, 136 insertions(+), 120 deletions(-) rename app/com/linkedin/drelephant/analysis/{AnalysisPromise.java => AnalyticJob.java} (82%) rename app/com/linkedin/drelephant/analysis/{AnalysisProvider.java => AnalyticJobGenerator.java} (52%) rename app/com/linkedin/drelephant/analysis/{AnalysisProviderHadoop1.java => AnalyticJobGeneratorHadoop1.java} (79%) rename app/com/linkedin/drelephant/analysis/{AnalysisProviderHadoop2.java => AnalyticJobGeneratorHadoop2.java} (70%) diff --git a/app/com/linkedin/drelephant/ElephantContext.java b/app/com/linkedin/drelephant/ElephantContext.java index c6f984134..cffda32f1 100644 --- a/app/com/linkedin/drelephant/ElephantContext.java +++ b/app/com/linkedin/drelephant/ElephantContext.java @@ -121,7 +121,7 @@ private void loadFetchers() { int hadoopMajorVersion = Utils.getMajorVersionFromString(hadoopVersion); if (hadoopMajorVersion == _hadoopVersion) { String typeName = applicationTypeNode.getTextContent(); - if (getApplicationType(typeName) == null) { + if (getApplicationTypeForName(typeName) == null) { ApplicationType type = new ApplicationType(typeName); String className = classNameNode.getTextContent(); @@ -280,7 +280,7 @@ public static ElephantContext instance() { * @param type The application type * @return The corresponding heuristics */ - public List getHeuristics(ApplicationType type) { + public List getHeuristicsForApplicationType(ApplicationType type) { return _typeToHeuristics.get(type); } @@ -323,7 +323,7 @@ public List getHeuristicsConfigurationData() { * @param type The application type * @return The corresponding fetcher */ - public ElephantFetcher getFetcher(ApplicationType type) { + public ElephantFetcher getFetcherForApplicationType(ApplicationType type) { return _typeToFetcher.get(type); } @@ -341,7 +341,7 @@ public DaliMetricsAPI.MetricsPublisher getMetricsPublisher() { * * @return The corresponding application type, null if not found */ - public ApplicationType getApplicationType(String typeName) { + public ApplicationType getApplicationTypeForName(String typeName) { return _nameToType.get(typeName.toUpperCase()); } diff --git a/app/com/linkedin/drelephant/ElephantRunner.java b/app/com/linkedin/drelephant/ElephantRunner.java index c37151e38..ace35dd57 100644 --- a/app/com/linkedin/drelephant/ElephantRunner.java +++ b/app/com/linkedin/drelephant/ElephantRunner.java @@ -1,10 +1,10 @@ package com.linkedin.drelephant; -import com.linkedin.drelephant.analysis.AnalysisPromise; -import com.linkedin.drelephant.analysis.AnalysisProvider; -import com.linkedin.drelephant.analysis.AnalysisProviderHadoop1; +import com.linkedin.drelephant.analysis.AnalyticJob; +import com.linkedin.drelephant.analysis.AnalyticJobGenerator; +import com.linkedin.drelephant.analysis.AnalyticJobGeneratorHadoop1; import com.linkedin.drelephant.analysis.HadoopSystemContext; -import com.linkedin.drelephant.analysis.AnalysisProviderHadoop2; +import com.linkedin.drelephant.analysis.AnalyticJobGeneratorHadoop2; import com.linkedin.drelephant.notifications.EmailThread; import java.io.IOException; import java.security.PrivilegedAction; @@ -29,23 +29,23 @@ public class ElephantRunner implements Runnable { private EmailThread _emailer = new EmailThread(); private HadoopSecurity _hadoopSecurity; private ExecutorService _service; - private BlockingQueue _jobQueue; - private AnalysisProvider _analysisProvider; + private BlockingQueue _jobQueue; + private AnalyticJobGenerator _analyticJobGenerator; - private void loadAnalysisProvider() { + private void loadAnalyticJobGenerator() { JobConf configuration = new JobConf(); int hadoopVersion = HadoopSystemContext.getHadoopVersion(); if (hadoopVersion == 2) { - _analysisProvider = new AnalysisProviderHadoop2(); + _analyticJobGenerator = new AnalyticJobGeneratorHadoop2(); } else if (hadoopVersion == 1) { - _analysisProvider = new AnalysisProviderHadoop1(); + _analyticJobGenerator = new AnalyticJobGeneratorHadoop1(); } else { throw new RuntimeException("Unsupported Hadoop major version detected: " + hadoopVersion + ""); } try { - _analysisProvider.configure(configuration); + _analyticJobGenerator.configure(configuration); } catch (Exception e) { logger.error("Error occurred when configuring the analysis provider."); throw new RuntimeException(e); @@ -62,10 +62,10 @@ public void run() { public Void run() { HadoopSystemContext.load(); _emailer.start(); - loadAnalysisProvider(); + loadAnalyticJobGenerator(); _service = Executors.newFixedThreadPool(EXECUTOR_NUM); - _jobQueue = new LinkedBlockingQueue(); + _jobQueue = new LinkedBlockingQueue(); for (int i = 0; i < EXECUTOR_NUM; i++) { _service.submit(new ExecutorThread(i + 1, _jobQueue)); } @@ -73,7 +73,7 @@ public Void run() { while (_running.get() && !Thread.currentThread().isInterrupted()) { lastRun = System.currentTimeMillis(); - logger.info("Fetching job list....."); + logger.info("Fetching analytic job list....."); try { _hadoopSecurity.checkLogin(); @@ -84,9 +84,9 @@ public Void run() { continue; } - List todos; + List todos; try { - todos = _analysisProvider.fetchPromises(); + todos = _analyticJobGenerator.fetchAnalyticJobs(); } catch (Exception e) { logger.error("Error fetching job list. Try again later...", e); //Wait for a while before retry @@ -114,9 +114,9 @@ public Void run() { private class ExecutorThread implements Runnable { private int _threadId; - private BlockingQueue _jobQueue; + private BlockingQueue _jobQueue; - ExecutorThread(int threadNum, BlockingQueue jobQueue) { + ExecutorThread(int threadNum, BlockingQueue jobQueue) { this._threadId = threadNum; this._jobQueue = jobQueue; } @@ -124,14 +124,17 @@ private class ExecutorThread implements Runnable { @Override public void run() { while (_running.get() && !Thread.currentThread().isInterrupted()) { - AnalysisPromise promise = null; + AnalyticJob analyticJob = null; try { - promise = _jobQueue.take(); - JobResult result = promise.getAnalysis(); + analyticJob = _jobQueue.take(); + logger.info("Executor thread " + _threadId + " start analyzing " + analyticJob.getAppType().getName() + " " + + analyticJob.getAppId()); + JobResult result = analyticJob.getAnalysis(); result.save(); - logger.info("Executor thread " + _threadId + " analyzed " + promise.getAppType().getName() + " " + promise - .getAppId()); + logger.info( + "Executor thread " + _threadId + " analyzed " + analyticJob.getAppType().getName() + " " + analyticJob + .getAppId()); // TODO: how to test email sending? _emailer.enqueue(result); @@ -141,11 +144,12 @@ public void run() { logger.error(e.getMessage()); logger.error(ExceptionUtils.getStackTrace(e)); - if (promise != null && promise.retry()) { - _analysisProvider.addIntoRetries(promise); + if (analyticJob != null && analyticJob.retry()) { + logger.error("Add analytic job id [" + analyticJob.getAppId() + "] into the retry list."); + _analyticJobGenerator.addIntoRetries(analyticJob); } else { logger.error( - "Drop analysis job. Reason: reached the max retries for application id = [" + promise.getAppId() + "Drop the analytic job. Reason: reached the max retries for application id = [" + analyticJob.getAppId() + "]."); } } diff --git a/app/com/linkedin/drelephant/analysis/AnalysisPromise.java b/app/com/linkedin/drelephant/analysis/AnalyticJob.java similarity index 82% rename from app/com/linkedin/drelephant/analysis/AnalysisPromise.java rename to app/com/linkedin/drelephant/analysis/AnalyticJob.java index 24b41ad2e..284e6080a 100644 --- a/app/com/linkedin/drelephant/analysis/AnalysisPromise.java +++ b/app/com/linkedin/drelephant/analysis/AnalyticJob.java @@ -14,7 +14,7 @@ * same regardless of hadoop versions and application types), and then promises to return the analyzed result * later. */ -public class AnalysisPromise { +public class AnalyticJob { private static final int _RETRY_LIMIT = 3; private int _retries = 0; @@ -31,32 +31,39 @@ public ApplicationType getAppType() { return _type; } - public void setAppType(ApplicationType type) { + public AnalyticJob setAppType(ApplicationType type) { _type = type; + return this; } - public void setAppId(String appId) { + public AnalyticJob setAppId(String appId) { _appId = appId; + return this; } - public void setJobId(String jobId) { + public AnalyticJob setJobId(String jobId) { _jobId = jobId; + return this; } - public void setName(String name) { + public AnalyticJob setName(String name) { _name = name; + return this; } - public void setUser(String user) { + public AnalyticJob setUser(String user) { _user = user; + return this; } - public void setStartTime(long startTime) { + public AnalyticJob setStartTime(long startTime) { _startTime = startTime; + return this; } - public void setFinishTime(long finishTime) { + public AnalyticJob setFinishTime(long finishTime) { _finishTime = finishTime; + return this; } public String getAppId() { @@ -87,8 +94,9 @@ public String getTrackingUrl() { return _trackingUrl; } - public void setTrackingUrl(String trackingUrl) { + public AnalyticJob setTrackingUrl(String trackingUrl) { _trackingUrl = trackingUrl; + return this; } /** @@ -98,17 +106,18 @@ public void setTrackingUrl(String trackingUrl) { * @return the analysed JobResult */ public JobResult getAnalysis() throws Exception { - ElephantFetcher fetcher = ElephantContext.instance().getFetcher(getAppType()); + ElephantFetcher fetcher = ElephantContext.instance().getFetcherForApplicationType(getAppType()); HadoopApplicationData data = fetcher.fetchData(getAppId()); List analysisResults = new ArrayList(); - List heuristics = ElephantContext.instance().getHeuristics(getAppType()); + List heuristics = ElephantContext.instance().getHeuristicsForApplicationType(getAppType()); for (Heuristic heuristic : heuristics) { analysisResults.add(heuristic.apply(data)); } - String jobType = ElephantContext.instance().matchJobType(data).getName(); + JobType jobType = ElephantContext.instance().matchJobType(data); + String jobTypeName = jobType == null ? "Unkown" : jobType.getName(); JobResult result = new JobResult(); // Note: before adding Spark analysers, all JobResult are using job ids as the primary key. But Spark (and many @@ -120,7 +129,7 @@ public JobResult getAnalysis() throws Exception { result.startTime = getStartTime(); result.analysisTime = System.currentTimeMillis(); result.jobName = getName(); - result.jobType = jobType; + result.jobType = jobTypeName; // Truncate long names if (result.jobName.length() > 100) { diff --git a/app/com/linkedin/drelephant/analysis/AnalysisProvider.java b/app/com/linkedin/drelephant/analysis/AnalyticJobGenerator.java similarity index 52% rename from app/com/linkedin/drelephant/analysis/AnalysisProvider.java rename to app/com/linkedin/drelephant/analysis/AnalyticJobGenerator.java index 4da1c98b5..483c7f7a3 100644 --- a/app/com/linkedin/drelephant/analysis/AnalysisProvider.java +++ b/app/com/linkedin/drelephant/analysis/AnalyticJobGenerator.java @@ -7,10 +7,11 @@ /** - * Provides analysis promises that will yield to analysis results finally. + * Provides AnalyticJobs that will yield to analysis results later. This class basically generates to-dos that could be + * executed later. * */ -public interface AnalysisProvider { +public interface AnalyticJobGenerator { /** * Configures the provider instance * @@ -21,20 +22,20 @@ public void configure(Configuration configuration) throws Exception; /** - * Provides a list of analysis promises that should be calculated + * Provides a list of AnalyticJobs that should be calculated * - * @return a list of analysis promises + * @return a list of AnalyticJobs * @throws IOException * @throws AuthenticationException */ - public List fetchPromises() + public List fetchAnalyticJobs() throws IOException, AuthenticationException; /** - * Add an analysis promise into retry list. Those promises will be provided again via #fetchPromises under - * the analysis provider's decision. + * Add an AnalyticJob into retry list. Those jobs will be provided again via #fetchAnalyticJobs under + * the generator's decision. * - * @param promise The promise to add + * @param job The job to add */ - public void addIntoRetries(AnalysisPromise promise); + public void addIntoRetries(AnalyticJob job); } diff --git a/app/com/linkedin/drelephant/analysis/AnalysisProviderHadoop1.java b/app/com/linkedin/drelephant/analysis/AnalyticJobGeneratorHadoop1.java similarity index 79% rename from app/com/linkedin/drelephant/analysis/AnalysisProviderHadoop1.java rename to app/com/linkedin/drelephant/analysis/AnalyticJobGeneratorHadoop1.java index ae5e34973..5b184080d 100644 --- a/app/com/linkedin/drelephant/analysis/AnalysisProviderHadoop1.java +++ b/app/com/linkedin/drelephant/analysis/AnalyticJobGeneratorHadoop1.java @@ -19,17 +19,17 @@ /** - * This class provides a future list to be fetched/analyzed in Hadoop 1 environment. + * This class provides a AnalyticJob list to be fetched/analyzed in Hadoop 1 environment. * */ -public class AnalysisProviderHadoop1 implements AnalysisProvider { - private static final Logger logger = Logger.getLogger(AnalysisProviderHadoop1.class); +public class AnalyticJobGeneratorHadoop1 implements AnalyticJobGenerator { + private static final Logger logger = Logger.getLogger(AnalyticJobGeneratorHadoop1.class); private JobClient _jobClient; private boolean _firstRun = true; private Set _previousJobs; - private final Queue _retryQueue = new ConcurrentLinkedQueue(); + private final Queue _retryQueue = new ConcurrentLinkedQueue(); private static final String ONLY_SUPPORTED_TYPE_NAME = "MAPREDUCE"; private ApplicationType _onlySupportedType; @@ -38,7 +38,7 @@ public void configure(Configuration configuration) throws Exception { _jobClient = new JobClient(new JobConf(configuration)); - _onlySupportedType = ElephantContext.instance().getApplicationType(ONLY_SUPPORTED_TYPE_NAME); + _onlySupportedType = ElephantContext.instance().getApplicationTypeForName(ONLY_SUPPORTED_TYPE_NAME); if (_onlySupportedType == null) { throw new RuntimeException("Cannot configure the analysis provider, " + ONLY_SUPPORTED_TYPE_NAME + " application type is not supported."); @@ -46,7 +46,7 @@ public void configure(Configuration configuration) } @Override - public List fetchPromises() + public List fetchAnalyticJobs() throws IOException, AuthenticationException { JobStatus[] result = _jobClient.getAllJobs(); @@ -61,30 +61,24 @@ public List fetchPromises() Set todoJobs = filterPreviousJobs(successJobs); // Add newly completed jobs to return list - List jobList = new ArrayList(); + List jobList = new ArrayList(); for (String jobId : todoJobs) { RunningJob job = _jobClient.getJob(jobId); JobStatus jobStatus = job.getJobStatus(); - AnalysisPromise promise = new AnalysisPromise(); - - promise.setAppId(jobId); - promise.setAppType(_onlySupportedType); - promise.setJobId(jobId); - promise.setUser(jobStatus.getUsername()); - promise.setName(job.getJobName()); - promise.setTrackingUrl(job.getTrackingURL()); - + AnalyticJob analyticJob = new AnalyticJob(); long startTime = jobStatus.getStartTime(); - promise.setStartTime(startTime); + + analyticJob.setAppId(jobId).setAppType(_onlySupportedType).setJobId(jobId).setUser(jobStatus.getUsername()) + .setName(job.getJobName()).setTrackingUrl(job.getTrackingURL()).setStartTime(startTime); // Note: // In Hadoop-1 the getFinishTime() call is not there. Unfortunately, calling this method does not // result in a compile error, neither a MethodNotFound exception at run time. The program just hangs. // Since we don't have metrics (the only consumer of finish time as of now) in Hadoop-1, set the finish time // to be the same as start time. - promise.setFinishTime(startTime); + analyticJob.setFinishTime(startTime); - jobList.add(promise); + jobList.add(analyticJob); } // Append promises from the retry queue at the end of the list while (!_retryQueue.isEmpty()) { @@ -95,7 +89,7 @@ public List fetchPromises() } @Override - public void addIntoRetries(AnalysisPromise promise) { + public void addIntoRetries(AnalyticJob promise) { _retryQueue.add(promise); } diff --git a/app/com/linkedin/drelephant/analysis/AnalysisProviderHadoop2.java b/app/com/linkedin/drelephant/analysis/AnalyticJobGeneratorHadoop2.java similarity index 70% rename from app/com/linkedin/drelephant/analysis/AnalysisProviderHadoop2.java rename to app/com/linkedin/drelephant/analysis/AnalyticJobGeneratorHadoop2.java index 856b97c6b..2e7fe868e 100644 --- a/app/com/linkedin/drelephant/analysis/AnalysisProviderHadoop2.java +++ b/app/com/linkedin/drelephant/analysis/AnalyticJobGeneratorHadoop2.java @@ -24,8 +24,9 @@ * This class provides a list of analysis promises to be generated under Hadoop YARN environment * */ -public class AnalysisProviderHadoop2 implements AnalysisProvider { - private static final Logger logger = Logger.getLogger(AnalysisProviderHadoop2.class); +public class AnalyticJobGeneratorHadoop2 implements AnalyticJobGenerator { + private static final Logger logger = Logger.getLogger(AnalyticJobGeneratorHadoop2.class); + private static final String RESOURCE_MANAGER_ADDRESS = "yarn.resourcemanager.webapp.address"; // We provide one minute job fetch delay due to the job sending lag from AM/NM to JobHistoryServer HDFS private static final long FETCH_DELAY = 60000; // Generate a token update interval with a random deviation so that it does not update the token exactly at the same @@ -41,18 +42,23 @@ public class AnalysisProviderHadoop2 implements AnalysisProvider { private AuthenticatedURL _authenticatedURL; private final ObjectMapper _objectMapper = new ObjectMapper(); - private final Queue _retryQueue = new ConcurrentLinkedQueue(); + private final Queue _retryQueue = new ConcurrentLinkedQueue(); @Override public void configure(Configuration configuration) throws Exception { - _resourceManagerAddress = configuration.get("yarn.resourcemanager.webapp.address"); + _resourceManagerAddress = configuration.get(RESOURCE_MANAGER_ADDRESS); + if (_resourceManagerAddress == null) { + throw new RuntimeException( + "Cannot get YARN resource manager address from Hadoop Configuration property: [" + RESOURCE_MANAGER_ADDRESS + + "]."); + } } @Override - public List fetchPromises() + public List fetchAnalyticJobs() throws IOException, AuthenticationException { - List appList = new ArrayList(); + List appList = new ArrayList(); // There is a lag of job data from AM/NM to JobHistoryServer HDFS, we shouldn't use the current // time, since there might be new jobs arriving after we fetch jobs. @@ -64,7 +70,8 @@ public List fetchPromises() .format("/ws/v1/cluster/apps?finalStatus=SUCCEEDED&finishedTimeBegin=%s&finishedTimeEnd=%s", String.valueOf(_lastTime), String.valueOf(_currentTime))); - logger.info("Fetching recent finished application runs via URL: " + appsURL); + logger.info("Fetching recent finished application runs between last time: " + _lastTime + ", and current time: " + + _currentTime); JsonNode rootNode = readJsonNode(appsURL); JsonNode apps = rootNode.path("apps").path("app"); @@ -73,7 +80,9 @@ public List fetchPromises() String id = app.get("id").getValueAsText(); String jobId = Utils.getJobIdFromApplicationId(id); - if (JobResult.find.byId(jobId) == null && JobResult.find.byId(id) == null) { + // When called first time after launch, hit the DB and avoid duplicated analytic jobs that have been analyzed + // before. + if (_lastTime > 0 || _lastTime == 0 && JobResult.find.byId(jobId) == null && JobResult.find.byId(id) == null) { String user = app.get("user").getValueAsText(); String name = app.get("name").getValueAsText(); String trackingUrl = app.get("trackingUrl").getValueAsText(); @@ -81,21 +90,15 @@ public List fetchPromises() long finishTime = app.get("finishedTime").getLongValue(); ApplicationType type = - ElephantContext.instance().getApplicationType(app.get("applicationType").getValueAsText()); + ElephantContext.instance().getApplicationTypeForName(app.get("applicationType").getValueAsText()); // If the application type is supported if (type != null) { - AnalysisPromise promise = new AnalysisPromise(); - promise.setAppId(id); - promise.setAppType(type); - promise.setJobId(jobId); - promise.setUser(user); - promise.setName(name); - promise.setTrackingUrl(trackingUrl); - promise.setStartTime(startTime); - promise.setFinishTime(finishTime); - - appList.add(promise); + AnalyticJob analyticJob = new AnalyticJob(); + analyticJob.setAppId(id).setAppType(type).setJobId(jobId).setUser(user).setName(name) + .setTrackingUrl(trackingUrl).setStartTime(startTime).setFinishTime(finishTime); + + appList.add(analyticJob); } } } @@ -110,7 +113,7 @@ public List fetchPromises() } @Override - public void addIntoRetries(AnalysisPromise promise) { + public void addIntoRetries(AnalyticJob promise) { _retryQueue.add(promise); } diff --git a/app/com/linkedin/drelephant/util/InfoExtractor.java b/app/com/linkedin/drelephant/util/InfoExtractor.java index 48e3962fe..630971663 100644 --- a/app/com/linkedin/drelephant/util/InfoExtractor.java +++ b/app/com/linkedin/drelephant/util/InfoExtractor.java @@ -68,7 +68,7 @@ public static void retrieveURLs(JobResult result, SparkApplicationData appData) logger.error("Encountered error while parsing java options into urls: " + e.getMessage()); } } else { - logger.error("Unable to retrieve azkaban urls from application + [" + + logger.error("Unable to retrieve azkaban urls from application [" + appData.getGeneralData().getApplicationId() + "] it does not contain [" + SPARK_EXTRA_JAVA_OPTIONS + "] property in its spark properties."); } diff --git a/app/com/linkedin/drelephant/util/Utils.java b/app/com/linkedin/drelephant/util/Utils.java index 788124646..b7de5c86e 100644 --- a/app/com/linkedin/drelephant/util/Utils.java +++ b/app/com/linkedin/drelephant/util/Utils.java @@ -113,11 +113,13 @@ public static void publishMetrics(MapReduceApplicationData jobData) { */ public static Document loadXMLDoc(String filePath) { InputStream instream = null; - // try { - instream = Play.application().resourceAsStream(filePath); - // } catch (FileNotFoundException e) { - // throw new RuntimeException("File " + filePath + " does not exist.", e); - // } + instream = Play.application().resourceAsStream(filePath); + +// try { +// instream = new FileInputStream(filePath); +// } catch (FileNotFoundException e) { +// throw new RuntimeException("File " + filePath + " does not exist.", e); +// } Document document = null; try { diff --git a/app/org/apache/spark/deploy/history/SparkFSFetcher.scala b/app/org/apache/spark/deploy/history/SparkFSFetcher.scala index f35c0aa0e..b540b09e9 100644 --- a/app/org/apache/spark/deploy/history/SparkFSFetcher.scala +++ b/app/org/apache/spark/deploy/history/SparkFSFetcher.scala @@ -30,6 +30,7 @@ class SparkFSFetcher extends ElephantFetcher[SparkApplicationData] { import SparkFSFetcher._ + private val _sparkConf = new SparkConf() /* Lazy loading for the log directory is very important. Hadoop Configuration() takes time to load itself to reflect * properties in the configuration files. Triggering it too early will sometimes make the configuration object empty. @@ -45,12 +46,11 @@ class SparkFSFetcher extends ElephantFetcher[SparkApplicationData] { logDir } - private val _sparkConf = new SparkConf() private val _security = new HadoopSecurity() private def fs: FileSystem = { - _security.checkLogin() + // For test purpose, if no host presented, use the local file system. if (new URI(_logDir).getHost == null) { FileSystem.getLocal(new Configuration()) } else { diff --git a/app/org/apache/spark/storage/StorageStatusTrackingListener.scala b/app/org/apache/spark/storage/StorageStatusTrackingListener.scala index 56f433f12..e2a02c010 100644 --- a/app/org/apache/spark/storage/StorageStatusTrackingListener.scala +++ b/app/org/apache/spark/storage/StorageStatusTrackingListener.scala @@ -1,11 +1,12 @@ package org.apache.spark.storage + import scala.collection.mutable -import org.apache.spark.SparkContext import org.apache.spark.annotation.DeveloperApi import org.apache.spark.scheduler._ + /** * :: DeveloperApi :: * A modified version of StorageStatusListener that tracks the peak memory usage during the entire application runtime. @@ -23,7 +24,7 @@ class StorageStatusTrackingListener extends SparkListener { val executorIdToMaxUsedMem = mutable.Map[String, Long]() /** Update storage status list to reflect updated block statuses */ - private def updateStorageStatus(execId: String, updatedBlocks: Seq[(BlockId, BlockStatus)]) { + private def updateStorageStatus(execId: String, updatedBlocks: Seq[(BlockId, BlockStatus)]): Unit = { executorIdToStorageStatus.get(execId).foreach { storageStatus => updatedBlocks.foreach { case (blockId, updatedStatus) => if (updatedStatus.storageLevel == StorageLevel.NONE) { @@ -37,7 +38,7 @@ class StorageStatusTrackingListener extends SparkListener { } /** Update storage status list to reflect the removal of an RDD from the cache */ - private def updateStorageStatus(unpersistedRDDId: Int) { + private def updateStorageStatus(unpersistedRDDId: Int): Unit = { storageStatusList.foreach { storageStatus => storageStatus.rddBlocksById(unpersistedRDDId).foreach { case (blockId, _) => storageStatus.removeBlock(blockId) @@ -55,22 +56,26 @@ class StorageStatusTrackingListener extends SparkListener { } } - override def onTaskEnd(taskEnd: SparkListenerTaskEnd) = synchronized { - val info = taskEnd.taskInfo - val metrics = taskEnd.taskMetrics - if (info != null && metrics != null) { - val updatedBlocks = metrics.updatedBlocks.getOrElse(Seq[(BlockId, BlockStatus)]()) - if (updatedBlocks.length > 0) { - updateStorageStatus(info.executorId, updatedBlocks) + override def onTaskEnd(taskEnd: SparkListenerTaskEnd): Unit = { + synchronized { + val info = taskEnd.taskInfo + val metrics = taskEnd.taskMetrics + if (info != null && metrics != null) { + val updatedBlocks = metrics.updatedBlocks.getOrElse(Seq[(BlockId, BlockStatus)]()) + if (updatedBlocks.length > 0) { + updateStorageStatus(info.executorId, updatedBlocks) + } } } } - override def onUnpersistRDD(unpersistRDD: SparkListenerUnpersistRDD) = synchronized { - updateStorageStatus(unpersistRDD.rddId) + override def onUnpersistRDD(unpersistRDD: SparkListenerUnpersistRDD): Unit = { + synchronized { + updateStorageStatus(unpersistRDD.rddId) + } } - override def onBlockManagerAdded(blockManagerAdded: SparkListenerBlockManagerAdded) { + override def onBlockManagerAdded(blockManagerAdded: SparkListenerBlockManagerAdded): Unit = { synchronized { val blockManagerId = blockManagerAdded.blockManagerId val executorId = blockManagerId.executorId @@ -80,11 +85,10 @@ class StorageStatusTrackingListener extends SparkListener { } } - override def onBlockManagerRemoved(blockManagerRemoved: SparkListenerBlockManagerRemoved) { + override def onBlockManagerRemoved(blockManagerRemoved: SparkListenerBlockManagerRemoved): Unit = { synchronized { val executorId = blockManagerRemoved.blockManagerId.executorId executorIdToStorageStatus.remove(executorId) } } - } diff --git a/conf/application.conf b/conf/application.conf index 7453b5f9c..954daf4b7 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -79,14 +79,13 @@ smtp.from="azkaban-noreply@linkedin.com" # TODO: JUST FOR REFERENCE RIGHT NOW, THESE SHOULD BE NOT CHECK THESE INTO CODE BASE # Dali metrics publisher -metrics.publisher-conf=conf/CounterPublisherConf.xml -fetchers.conf.location=conf/FetcherConf.xml -jobtypes.conf.location=conf/JobTypeConf.xml -heuristics.conf.location=conf/HeuristicConf.xml +metrics.publisher-conf=CounterPublisherConf.xml +fetchers.conf.location=FetcherConf.xml +jobtypes.conf.location=JobTypeConf.xml +heuristics.conf.location=HeuristicConf.xml #Kerberos keytab.location=/home/yizhou/Dev/dr-elephant-test/yizhou.keytab keytab.user=yizhou -spark.yarn.historyServer.address="eat1-nertzjh01.grid.linkedin.com:18080" spark.eventLog.dir="hdfs://eat1-nertznn01.grid.linkedin.com:9000/system/spark-history" From 5be797a0e83418d35503079a772668ddb166d132 Mon Sep 17 00:00:00 2001 From: Yitong Zhou Date: Fri, 12 Jun 2015 15:06:28 -0700 Subject: [PATCH 075/272] Code cleaness and styles improvements RB=488607 R=fli,mwagner A=fli --- .../linkedin/drelephant/ElephantContext.java | 33 ++++++------ .../linkedin/drelephant/ElephantRunner.java | 9 ++-- .../drelephant/analysis/AnalyticJob.java | 18 +++++-- .../analysis/AnalyticJobGenerator.java | 2 +- .../analysis/AnalyticJobGeneratorHadoop1.java | 2 +- .../analysis/AnalyticJobGeneratorHadoop2.java | 2 +- .../analysis/HadoopApplicationData.java | 7 +++ .../analysis/HadoopSystemContext.java | 53 +++++++++++-------- .../mapreduce/MapReduceApplicationData.java | 5 ++ .../spark/SparkJobProgressData.java | 31 ++++++----- .../heuristics/StageRuntimeHeuristic.java | 6 +-- .../util/HeuristicConfiguration.java | 8 ++- .../deploy/history/SparkDataCollection.scala | 2 + conf/application.conf | 4 +- .../spark/MockSparkApplicationData.java | 5 ++ 15 files changed, 112 insertions(+), 75 deletions(-) diff --git a/app/com/linkedin/drelephant/ElephantContext.java b/app/com/linkedin/drelephant/ElephantContext.java index cffda32f1..05941200b 100644 --- a/app/com/linkedin/drelephant/ElephantContext.java +++ b/app/com/linkedin/drelephant/ElephantContext.java @@ -38,6 +38,11 @@ public class ElephantContext { private static final Logger logger = Logger.getLogger(ElephantContext.class); private static final ElephantContext INSTANCE = new ElephantContext(); + + private static final String HADOOP_VERSION_XML_FIELD = "hadoopversion"; + private static final String CLASS_NAME_XML_FIELD = "classname"; + private static final String APPLICATION_TYPE_XML_FIELD = "applicationtype"; + private static final String FETCHERS_CONF = "fetchers.conf.location"; private static final String HEURISTICS_CONF = "heuristics.conf.location"; private static final String JOB_TYPES_CONF = "jobtypes.conf.location"; @@ -53,12 +58,9 @@ public class ElephantContext { private Map> _appTypeToJobTypes = new HashMap>(); private final DaliMetricsAPI.MetricsPublisher _metricsPublisher; - private final int _hadoopVersion; + // private on purpose private ElephantContext() { - // private on purpose - _hadoopVersion = HadoopSystemContext.getHadoopVersion(); - loadConfiguration(); // Load metrics publisher @@ -102,24 +104,24 @@ private void loadFetchers() { n++; Element fetcherNode = (Element) node; - Node hadoopVersionNode = fetcherNode.getElementsByTagName("hadoopversion").item(0); + Node hadoopVersionNode = fetcherNode.getElementsByTagName(HADOOP_VERSION_XML_FIELD).item(0); if (hadoopVersionNode == null) { throw new RuntimeException("No hadoopversion tag presented in fetcher #" + n); } - Node applicationTypeNode = fetcherNode.getElementsByTagName("applicationtype").item(0); + Node applicationTypeNode = fetcherNode.getElementsByTagName(APPLICATION_TYPE_XML_FIELD).item(0); if (applicationTypeNode == null) { throw new RuntimeException("No applicationtype tag presented in fetcher #" + n); } - Node classNameNode = fetcherNode.getElementsByTagName("classname").item(0); + Node classNameNode = fetcherNode.getElementsByTagName(CLASS_NAME_XML_FIELD).item(0); if (classNameNode == null) { throw new RuntimeException("No classname tag presented in fetcher #" + n); } String hadoopVersion = hadoopVersionNode.getTextContent().toLowerCase().trim(); int hadoopMajorVersion = Utils.getMajorVersionFromString(hadoopVersion); - if (hadoopMajorVersion == _hadoopVersion) { + if (HadoopSystemContext.matchCurrentHadoopVersion(hadoopMajorVersion)) { String typeName = applicationTypeNode.getTextContent(); if (getApplicationTypeForName(typeName) == null) { ApplicationType type = new ApplicationType(typeName); @@ -148,7 +150,7 @@ private void loadFetchers() { } } else { logger.info("Skipping fetcher #" + n + ", because its hadoop version [" + hadoopVersion - + "] does not match our current major version [" + _hadoopVersion + "]"); + + "] does not match our current Hadoop version."); } } } @@ -352,12 +354,13 @@ public ApplicationType getApplicationTypeForName(String typeName) { * @return The matched job type */ public JobType matchJobType(HadoopApplicationData data) { - List jobTypeList = _appTypeToJobTypes.get(data.getApplicationType()); - - Properties jobProp = data.getConf(); - for (JobType type : jobTypeList) { - if (type.matchType(jobProp)) { - return type; + if (data != null) { + List jobTypeList = _appTypeToJobTypes.get(data.getApplicationType()); + Properties jobProp = data.getConf(); + for (JobType type : jobTypeList) { + if (type.matchType(jobProp)) { + return type; + } } } return null; diff --git a/app/com/linkedin/drelephant/ElephantRunner.java b/app/com/linkedin/drelephant/ElephantRunner.java index ace35dd57..3274c898f 100644 --- a/app/com/linkedin/drelephant/ElephantRunner.java +++ b/app/com/linkedin/drelephant/ElephantRunner.java @@ -34,14 +34,13 @@ public class ElephantRunner implements Runnable { private void loadAnalyticJobGenerator() { JobConf configuration = new JobConf(); - int hadoopVersion = HadoopSystemContext.getHadoopVersion(); - if (hadoopVersion == 2) { + if (HadoopSystemContext.isHadoop2Env()) { _analyticJobGenerator = new AnalyticJobGeneratorHadoop2(); - } else if (hadoopVersion == 1) { + } else if (HadoopSystemContext.isHadoop1Env()) { _analyticJobGenerator = new AnalyticJobGeneratorHadoop1(); } else { - throw new RuntimeException("Unsupported Hadoop major version detected: " + hadoopVersion + ""); + throw new RuntimeException("Unsupported Hadoop major version detected. It is neither 1.x nor 2.x."); } try { @@ -127,7 +126,7 @@ public void run() { AnalyticJob analyticJob = null; try { analyticJob = _jobQueue.take(); - logger.info("Executor thread " + _threadId + " start analyzing " + analyticJob.getAppType().getName() + " " + logger.info("Executor thread " + _threadId + " analyzing " + analyticJob.getAppType().getName() + " " + analyticJob.getAppId()); JobResult result = analyticJob.getAnalysis(); result.save(); diff --git a/app/com/linkedin/drelephant/analysis/AnalyticJob.java b/app/com/linkedin/drelephant/analysis/AnalyticJob.java index 284e6080a..cfd063aa7 100644 --- a/app/com/linkedin/drelephant/analysis/AnalyticJob.java +++ b/app/com/linkedin/drelephant/analysis/AnalyticJob.java @@ -7,6 +7,7 @@ import java.util.List; import model.JobHeuristicResult; import model.JobResult; +import org.apache.log4j.Logger; /** @@ -15,6 +16,9 @@ * later. */ public class AnalyticJob { + private static final Logger logger = Logger.getLogger(AnalyticJob.class); + // The defualt job type when the data matches nothing. + private static final String UNKNOWN_JOB_TYPE = "Unknown"; private static final int _RETRY_LIMIT = 3; private int _retries = 0; @@ -111,13 +115,19 @@ public JobResult getAnalysis() throws Exception { HadoopApplicationData data = fetcher.fetchData(getAppId()); List analysisResults = new ArrayList(); - List heuristics = ElephantContext.instance().getHeuristicsForApplicationType(getAppType()); - for (Heuristic heuristic : heuristics) { - analysisResults.add(heuristic.apply(data)); + + if (data == null || data.isEmpty()) { + logger.info("No Data Received for analytic job: " + getAppId()); + analysisResults.add(HeuristicResult.NO_DATA); + } else { + List heuristics = ElephantContext.instance().getHeuristicsForApplicationType(getAppType()); + for (Heuristic heuristic : heuristics) { + analysisResults.add(heuristic.apply(data)); + } } JobType jobType = ElephantContext.instance().matchJobType(data); - String jobTypeName = jobType == null ? "Unkown" : jobType.getName(); + String jobTypeName = jobType == null ? UNKNOWN_JOB_TYPE : jobType.getName(); JobResult result = new JobResult(); // Note: before adding Spark analysers, all JobResult are using job ids as the primary key. But Spark (and many diff --git a/app/com/linkedin/drelephant/analysis/AnalyticJobGenerator.java b/app/com/linkedin/drelephant/analysis/AnalyticJobGenerator.java index 483c7f7a3..eaaa3f804 100644 --- a/app/com/linkedin/drelephant/analysis/AnalyticJobGenerator.java +++ b/app/com/linkedin/drelephant/analysis/AnalyticJobGenerator.java @@ -19,7 +19,7 @@ public interface AnalyticJobGenerator { * @throws Exception */ public void configure(Configuration configuration) - throws Exception; + throws IOException; /** * Provides a list of AnalyticJobs that should be calculated diff --git a/app/com/linkedin/drelephant/analysis/AnalyticJobGeneratorHadoop1.java b/app/com/linkedin/drelephant/analysis/AnalyticJobGeneratorHadoop1.java index 5b184080d..88809ebd4 100644 --- a/app/com/linkedin/drelephant/analysis/AnalyticJobGeneratorHadoop1.java +++ b/app/com/linkedin/drelephant/analysis/AnalyticJobGeneratorHadoop1.java @@ -35,7 +35,7 @@ public class AnalyticJobGeneratorHadoop1 implements AnalyticJobGenerator { @Override public void configure(Configuration configuration) - throws Exception { + throws IOException { _jobClient = new JobClient(new JobConf(configuration)); _onlySupportedType = ElephantContext.instance().getApplicationTypeForName(ONLY_SUPPORTED_TYPE_NAME); diff --git a/app/com/linkedin/drelephant/analysis/AnalyticJobGeneratorHadoop2.java b/app/com/linkedin/drelephant/analysis/AnalyticJobGeneratorHadoop2.java index 2e7fe868e..c6f78e295 100644 --- a/app/com/linkedin/drelephant/analysis/AnalyticJobGeneratorHadoop2.java +++ b/app/com/linkedin/drelephant/analysis/AnalyticJobGeneratorHadoop2.java @@ -46,7 +46,7 @@ public class AnalyticJobGeneratorHadoop2 implements AnalyticJobGenerator { @Override public void configure(Configuration configuration) - throws Exception { + throws IOException { _resourceManagerAddress = configuration.get(RESOURCE_MANAGER_ADDRESS); if (_resourceManagerAddress == null) { throw new RuntimeException( diff --git a/app/com/linkedin/drelephant/analysis/HadoopApplicationData.java b/app/com/linkedin/drelephant/analysis/HadoopApplicationData.java index 9209f8b81..6cf7d1d79 100644 --- a/app/com/linkedin/drelephant/analysis/HadoopApplicationData.java +++ b/app/com/linkedin/drelephant/analysis/HadoopApplicationData.java @@ -28,4 +28,11 @@ public interface HadoopApplicationData { * @return the application type */ public ApplicationType getApplicationType(); + + /** + * Indicate if the data holder is actually empty (nothing is set up). + * + * @return true if the data holder is empty else false + */ + public boolean isEmpty(); } diff --git a/app/com/linkedin/drelephant/analysis/HadoopSystemContext.java b/app/com/linkedin/drelephant/analysis/HadoopSystemContext.java index 6dccd1694..ba991277f 100644 --- a/app/com/linkedin/drelephant/analysis/HadoopSystemContext.java +++ b/app/com/linkedin/drelephant/analysis/HadoopSystemContext.java @@ -1,6 +1,5 @@ package com.linkedin.drelephant.analysis; -import java.util.Map; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; @@ -11,6 +10,12 @@ public final class HadoopSystemContext { private static final Logger logger = Logger.getLogger(HadoopSystemContext.class); + + private static final String MAPREDUCE_FRAMEWORK_NAME_PROP = "mapreduce.framework.name"; + private static final String MAPRED_JOB_TRACKER_PROP = "mapred.job.tracker.http.address"; + private static final String YARN = "yarn"; + private static final String CLASSIC = "classic"; + public static long HDFS_BLOCK_SIZE = 64 * 1024 * 1024; public static final long DISK_READ_SPEED = 100 * 1024 * 1024; public static final int SHUFFLE_SORT_MAX_SAMPLE_SIZE = 50; @@ -30,33 +35,37 @@ public static void load() { } /** - * Detect the current Hadoop version Dr Elephant is deployed for according to the current Hadoop Configuration. + * Detect if the current Hadoop environment is 1.x * - * @return the current hadoop version (1 or 2 typically). + * @return true if it is Hadoop 1 env, else false */ - public static int getHadoopVersion() { - // Important, new Configuration might not be timely loaded in Hadoop 1 environment + public static boolean isHadoop1Env() { Configuration hadoopConf = new JobConf(); - Map vals = hadoopConf.getValByRegex(".*"); - String hadoopVersion = hadoopConf.get("mapreduce.framework.name"); + String hadoopVersion = hadoopConf.get(MAPREDUCE_FRAMEWORK_NAME_PROP); if (hadoopVersion == null) { - if (hadoopConf.get("mapred.job.tracker.http.address") != null) { - hadoopVersion = "classic"; - } else { - throw new RuntimeException("Hadoop config error. No framework name provided."); - } + return hadoopConf.get(MAPRED_JOB_TRACKER_PROP) != null; } + return hadoopVersion.equals(CLASSIC); + } - if (hadoopVersion == null) { - throw new RuntimeException("Cannot find Hadoop version information from corresponding Configuration object."); - } - if (hadoopVersion.equals("classic")) { - return 1; - } else if (hadoopVersion.equals("yarn")) { - return 2; - } + /** + * Detect if the current Hadoop environment is 2.x + * + * @return true if it is Hadoop 2 env, else false + */ + public static boolean isHadoop2Env() { + Configuration hadoopConf = new JobConf(); + String hadoopVersion = hadoopConf.get(MAPREDUCE_FRAMEWORK_NAME_PROP); + return hadoopVersion != null && hadoopVersion.equals(YARN); + } - throw new RuntimeException( - "Cannot support hadoopVersion [" + hadoopVersion + "] other than classic or yarn currently."); + /** + * Check if a Hadoop version matches the current Hadoop environment + * + * @param majorVersion the major version number of hadoop + * @return true if we have a major version match else false + */ + public static boolean matchCurrentHadoopVersion(int majorVersion) { + return majorVersion == 2 && isHadoop2Env() || majorVersion == 1 && isHadoop1Env(); } } diff --git a/app/com/linkedin/drelephant/mapreduce/MapReduceApplicationData.java b/app/com/linkedin/drelephant/mapreduce/MapReduceApplicationData.java index 134f504f7..6994cd36e 100644 --- a/app/com/linkedin/drelephant/mapreduce/MapReduceApplicationData.java +++ b/app/com/linkedin/drelephant/mapreduce/MapReduceApplicationData.java @@ -108,6 +108,11 @@ public ApplicationType getApplicationType() { return APPLICATION_TYPE; } + @Override + public boolean isEmpty() { + return getMapperData().length == 0 && getReducerData().length == 0; + } + public String getUsername() { return _username; } diff --git a/app/com/linkedin/drelephant/spark/SparkJobProgressData.java b/app/com/linkedin/drelephant/spark/SparkJobProgressData.java index ea488326a..50a2c358a 100644 --- a/app/com/linkedin/drelephant/spark/SparkJobProgressData.java +++ b/app/com/linkedin/drelephant/spark/SparkJobProgressData.java @@ -1,6 +1,5 @@ package com.linkedin.drelephant.spark; -import com.linkedin.drelephant.math.Statistics; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -22,9 +21,9 @@ public class SparkJobProgressData { private final Set _completedJobs = new HashSet(); private final Set _failedJobs = new HashSet(); - private final Map _stageIdToInfo = new HashMap(); - private final Set _completedStages = new HashSet(); - private final Set _failedStages = new HashSet(); + private final Map _stageIdToInfo = new HashMap(); + private final Set _completedStages = new HashSet(); + private final Set _failedStages = new HashSet(); public void addJobInfo(int jobId, JobInfo info) { _jobIdToInfo.put(jobId, info); @@ -39,15 +38,15 @@ public void addFailedJob(int jobId) { } public void addStageInfo(int stageId, int attemptId, StageInfo info) { - _stageIdToInfo.put(new StageId(stageId, attemptId), info); + _stageIdToInfo.put(new StageAttemptId(stageId, attemptId), info); } public void addCompletedStages(int stageId, int attemptId) { - _completedStages.add(new StageId(stageId, attemptId)); + _completedStages.add(new StageAttemptId(stageId, attemptId)); } public void addFailedStages(int stageId, int attemptId) { - _failedStages.add(new StageId(stageId, attemptId)); + _failedStages.add(new StageAttemptId(stageId, attemptId)); } public Set getCompletedJobs() { @@ -81,14 +80,14 @@ public JobInfo getJobInfo(int jobId) { } public StageInfo getStageInfo(int stageId, int attemptId) { - return _stageIdToInfo.get(new StageId(stageId, attemptId)); + return _stageIdToInfo.get(new StageAttemptId(stageId, attemptId)); } - public Set getCompletedStages() { + public Set getCompletedStages() { return _completedStages; } - public Set getFailedStages() { + public Set getFailedStages() { return _failedStages; } @@ -108,7 +107,7 @@ public String getJobDescription(int jobId) { logger.error("Spark Job id [" + jobId + "] does not contain any stage."); return null; } - return _stageIdToInfo.get(new StageId(id, 0)).name; + return _stageIdToInfo.get(new StageAttemptId(id, 0)).name; } public List getFailedJobDescriptions() { @@ -129,7 +128,7 @@ public String toString() { } s.append("]\nStageInfo: ["); - for (Map.Entry entry : _stageIdToInfo.entrySet()) { + for (Map.Entry entry : _stageIdToInfo.entrySet()) { s.append("{id:" + entry.getKey() + ", value: " + entry.getValue() + "}"); } s.append("]"); @@ -137,11 +136,11 @@ public String toString() { return s.toString(); } - public static class StageId { + public static class StageAttemptId { public int stageId; public int attemptId; - public StageId(int stageId, int attemptId) { + public StageAttemptId(int stageId, int attemptId) { this.stageId = stageId; this.attemptId = attemptId; } @@ -153,8 +152,8 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (obj instanceof StageId) { - StageId other = (StageId) obj; + if (obj instanceof StageAttemptId) { + StageAttemptId other = (StageAttemptId) obj; return stageId == other.stageId && attemptId == other.attemptId; } return false; diff --git a/app/com/linkedin/drelephant/spark/heuristics/StageRuntimeHeuristic.java b/app/com/linkedin/drelephant/spark/heuristics/StageRuntimeHeuristic.java index a76b06e7a..bd150f972 100644 --- a/app/com/linkedin/drelephant/spark/heuristics/StageRuntimeHeuristic.java +++ b/app/com/linkedin/drelephant/spark/heuristics/StageRuntimeHeuristic.java @@ -25,8 +25,8 @@ public HeuristicResult apply(SparkApplicationData data) { SparkJobProgressData jobProgressData = data.getJobProgressData(); Severity endSeverity = Severity.NONE; - Set completedStages = jobProgressData.getCompletedStages(); - Set failedStages = jobProgressData.getFailedStages(); + Set completedStages = jobProgressData.getCompletedStages(); + Set failedStages = jobProgressData.getFailedStages(); // Average stage failure rate double avgStageFailureRate = jobProgressData.getStageFailureRate(); @@ -35,7 +35,7 @@ public HeuristicResult apply(SparkApplicationData data) { // For each completed stage, the task failure rate List problematicStages = new ArrayList(); - for (SparkJobProgressData.StageId id : completedStages) { + for (SparkJobProgressData.StageAttemptId id : completedStages) { SparkJobProgressData.StageInfo info = jobProgressData.getStageInfo(id.stageId, id.attemptId); double stageTasksFailureRate = info.getFailureRate(); Severity tasksFailureRateSeverity = getSingleStageTasksFailureRate(stageTasksFailureRate); diff --git a/app/com/linkedin/drelephant/util/HeuristicConfiguration.java b/app/com/linkedin/drelephant/util/HeuristicConfiguration.java index 15e9f0e57..c1e7ba517 100644 --- a/app/com/linkedin/drelephant/util/HeuristicConfiguration.java +++ b/app/com/linkedin/drelephant/util/HeuristicConfiguration.java @@ -24,8 +24,6 @@ public List getHeuristicsConfigurationData() { } private void parseHeuristicConfiguration(Element configuration) { - int hadoopVersion = HadoopSystemContext.getHadoopVersion(); - _heuristicsConfDataList = new ArrayList(); NodeList nodes = configuration.getChildNodes(); @@ -89,15 +87,15 @@ private void parseHeuristicConfiguration(Element configuration) { for (int j = 0; j < versionList.getLength(); j++) { String version = versionList.item(j).getTextContent(); int majorVersion = Utils.getMajorVersionFromString(version); - if (hadoopVersion == majorVersion) { + if (HadoopSystemContext.matchCurrentHadoopVersion(majorVersion)) { HeuristicConfigurationData heuristicData = new HeuristicConfigurationData(heuristicName, className, viewName, appType); _heuristicsConfDataList.add(heuristicData); break; } else { logger.warn( - "Ignoring Heuristic: " + heuristicName + " className: " + className + "because it's hadoop version: " - + majorVersion + " does not match the current hadoop version number " + hadoopVersion); + "Ignoring Heuristic: " + heuristicName + " className: " + className + "because it's Hadoop version: " + + majorVersion + " does not match the current Hadoop version."); } } } diff --git a/app/org/apache/spark/deploy/history/SparkDataCollection.scala b/app/org/apache/spark/deploy/history/SparkDataCollection.scala index 310d626cb..d2395e5e5 100644 --- a/app/org/apache/spark/deploy/history/SparkDataCollection.scala +++ b/app/org/apache/spark/deploy/history/SparkDataCollection.scala @@ -54,6 +54,8 @@ class SparkDataCollection(applicationEventListener: ApplicationEventListener, override def getConf(): Properties = getEnvironmentData().getSparkProperties() + override def isEmpty(): Boolean = getExecutorData().getExecutors.isEmpty() + override def getGeneralData(): SparkGeneralData = { if (_applicationData == null) { _applicationData = new SparkGeneralData() diff --git a/conf/application.conf b/conf/application.conf index 954daf4b7..b8e5a67f0 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -85,7 +85,7 @@ jobtypes.conf.location=JobTypeConf.xml heuristics.conf.location=HeuristicConf.xml #Kerberos -keytab.location=/home/yizhou/Dev/dr-elephant-test/yizhou.keytab -keytab.user=yizhou +keytab.location="/export/apps/hadoop/keytabs/dr_elephant-service.keytab" +keytab.user="elephant/eat1-nertzaz01.grid.linkedin.com" spark.eventLog.dir="hdfs://eat1-nertznn01.grid.linkedin.com:9000/system/spark-history" diff --git a/test/com/linkedin/drelephant/spark/MockSparkApplicationData.java b/test/com/linkedin/drelephant/spark/MockSparkApplicationData.java index d7d0adcee..e296617a5 100644 --- a/test/com/linkedin/drelephant/spark/MockSparkApplicationData.java +++ b/test/com/linkedin/drelephant/spark/MockSparkApplicationData.java @@ -65,4 +65,9 @@ public String getAppId() { public ApplicationType getApplicationType() { return APPLICATION_TYPE; } + + @Override + public boolean isEmpty() { + return getExecutorData().getExecutors().isEmpty(); + } } From 9808e7940b45837c878a9fd58fe80b0214a73219 Mon Sep 17 00:00:00 2001 From: Fangshi Li Date: Wed, 17 Jun 2015 11:20:38 -0700 Subject: [PATCH 076/272] HADOOP-11876: Separate Dr. Elephant configs from deployable zip --- .../CounterPublisherConf.xml | 0 {conf => app-conf}/FetcherConf.xml | 0 {conf => app-conf}/HeuristicConf.xml | 0 {conf => app-conf}/JobTypeConf.xml | 0 .../elephant.config | 0 .../linkedin/drelephant/DaliMetricsAPI.java | 17 ++--- .../linkedin/drelephant/ElephantContext.java | 70 +++++++------------ .../linkedin/drelephant/ElephantRunner.java | 8 +-- app/com/linkedin/drelephant/util/Utils.java | 11 +-- compile.sh | 2 +- conf/application.conf | 12 ---- start.sh | 19 ++--- 12 files changed, 53 insertions(+), 86 deletions(-) rename conf/CounterPublisherConf.xml.template => app-conf/CounterPublisherConf.xml (100%) rename {conf => app-conf}/FetcherConf.xml (100%) rename {conf => app-conf}/HeuristicConf.xml (100%) rename {conf => app-conf}/JobTypeConf.xml (100%) rename conf/elephant.config.template => app-conf/elephant.config (100%) diff --git a/conf/CounterPublisherConf.xml.template b/app-conf/CounterPublisherConf.xml similarity index 100% rename from conf/CounterPublisherConf.xml.template rename to app-conf/CounterPublisherConf.xml diff --git a/conf/FetcherConf.xml b/app-conf/FetcherConf.xml similarity index 100% rename from conf/FetcherConf.xml rename to app-conf/FetcherConf.xml diff --git a/conf/HeuristicConf.xml b/app-conf/HeuristicConf.xml similarity index 100% rename from conf/HeuristicConf.xml rename to app-conf/HeuristicConf.xml diff --git a/conf/JobTypeConf.xml b/app-conf/JobTypeConf.xml similarity index 100% rename from conf/JobTypeConf.xml rename to app-conf/JobTypeConf.xml diff --git a/conf/elephant.config.template b/app-conf/elephant.config similarity index 100% rename from conf/elephant.config.template rename to app-conf/elephant.config diff --git a/app/com/linkedin/drelephant/DaliMetricsAPI.java b/app/com/linkedin/drelephant/DaliMetricsAPI.java index b8a338e2d..fded88f67 100644 --- a/app/com/linkedin/drelephant/DaliMetricsAPI.java +++ b/app/com/linkedin/drelephant/DaliMetricsAPI.java @@ -51,6 +51,7 @@ import org.w3c.dom.Element; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; +import play.Play; /** @@ -578,18 +579,18 @@ private HDFSMetricsPublisher(String basePath) { public static MetricsPublisher createFromXml(String configFileName) { MetricsPublisher metricsPublisher = null; if (configFileName != null) { + InputStream instream = null; LOG.info("Attempting to read metrics publish configuration from " + configFileName); - File configFile = new File(configFileName); - try { - FileInputStream instream = new FileInputStream(configFile); - metricsPublisher = getHdfsMetricsPublisher(configFileName, instream); - } catch (FileNotFoundException e) { - // Nothing to publish if file is not there. - LOG.info(configFileName + " not found"); + instream = Play.application().resourceAsStream(configFileName); + if (instream == null) { + LOG.info("Configuation file not present in classpath. File: " + configFileName); + return null; } + LOG.info("Configuation file loaded. File: " + configFileName); + metricsPublisher = getHdfsMetricsPublisher(configFileName, instream); } if (metricsPublisher == null) { - LOG.info("Metrics will not be published"); + LOG.info("Not valid config so metrics will not be published."); } return metricsPublisher; } diff --git a/app/com/linkedin/drelephant/ElephantContext.java b/app/com/linkedin/drelephant/ElephantContext.java index 05941200b..33b83da94 100644 --- a/app/com/linkedin/drelephant/ElephantContext.java +++ b/app/com/linkedin/drelephant/ElephantContext.java @@ -37,16 +37,16 @@ */ public class ElephantContext { private static final Logger logger = Logger.getLogger(ElephantContext.class); - private static final ElephantContext INSTANCE = new ElephantContext(); + private static ElephantContext INSTANCE; private static final String HADOOP_VERSION_XML_FIELD = "hadoopversion"; private static final String CLASS_NAME_XML_FIELD = "classname"; private static final String APPLICATION_TYPE_XML_FIELD = "applicationtype"; - private static final String FETCHERS_CONF = "fetchers.conf.location"; - private static final String HEURISTICS_CONF = "heuristics.conf.location"; - private static final String JOB_TYPES_CONF = "jobtypes.conf.location"; - private static final String OPT_METRICS_PUB_CONF = "metrics.publisher-conf"; + private static final String FETCHERS_CONF = "FetcherConf.xml"; + private static final String HEURISTICS_CONF = "HeuristicConf.xml"; + private static final String JOB_TYPES_CONF = "JobTypeConf.xml"; + private static final String OPT_METRICS_PUB_CONF = "CounterPublisherConf.xml"; private final Map> _heuristicGroupedNames = new HashMap>(); private List _heuristicsConfData; @@ -59,21 +59,27 @@ public class ElephantContext { private final DaliMetricsAPI.MetricsPublisher _metricsPublisher; + public static void init() { + INSTANCE = new ElephantContext(); + } + + public static ElephantContext instance() { + if (INSTANCE == null) { + INSTANCE = new ElephantContext(); + } + return INSTANCE; + } + // private on purpose private ElephantContext() { loadConfiguration(); // Load metrics publisher - // The getFile() API of Play returns a File object whether or not the actual file exists. - String metricsPublisherConfPath = play.Play.application().configuration().getString(OPT_METRICS_PUB_CONF); - if (metricsPublisherConfPath == null) { - logger.info("Metrics publisher not configured. No metrics will be published"); - _metricsPublisher = null; + _metricsPublisher = DaliMetricsAPI.HDFSMetricsPublisher.createFromXml(OPT_METRICS_PUB_CONF); + if (_metricsPublisher == null) { + logger.info("No metrics will be published. "); } else { - _metricsPublisher = DaliMetricsAPI.HDFSMetricsPublisher.createFromXml(metricsPublisherConfPath); - if (_metricsPublisher == null) { - logger.info("No metrics will be published"); - } + logger.info("Metrics publisher configured. "); } } @@ -88,13 +94,8 @@ private void loadConfiguration() { } private void loadFetchers() { - String filePath = play.Play.application().configuration().getString(FETCHERS_CONF); - if (filePath == null) { - throw new RuntimeException( - "Fetchers configuration file path is not presented, please set up configuration property: " + FETCHERS_CONF - + "."); - } - Document document = Utils.loadXMLDoc(filePath); + + Document document = Utils.loadXMLDoc(FETCHERS_CONF); NodeList nodes = document.getDocumentElement().getChildNodes(); for (int i = 0; i < nodes.getLength(); i++) { @@ -157,14 +158,9 @@ private void loadFetchers() { } private void loadHeuristics() { - String filePath = play.Play.application().configuration().getString(HEURISTICS_CONF); - if (filePath == null) { - throw new RuntimeException( - "Heuristics configuration file path is not presented, please set up configuration property: " - + HEURISTICS_CONF + "."); - } - Document document = Utils.loadXMLDoc(filePath); + Document document = Utils.loadXMLDoc(HEURISTICS_CONF); + _heuristicsConfData = new HeuristicConfiguration(document.getDocumentElement()).getHeuristicsConfigurationData(); for (HeuristicConfigurationData data : _heuristicsConfData) { @@ -256,26 +252,10 @@ private void configureSupportedApplicationTypes() { } private void loadJobTypes() { - String filePath = play.Play.application().configuration().getString(JOB_TYPES_CONF); - if (filePath == null) { - throw new RuntimeException( - "JobType configuration file path is not presented, please set up configuration property: " + JOB_TYPES_CONF - + "."); - } - - JobTypeConf conf = new JobTypeConf(filePath); + JobTypeConf conf = new JobTypeConf(JOB_TYPES_CONF); _appTypeToJobTypes = conf.getAppTypeToJobTypeList(); } - /** - * Get the singleton instance - * - * @return the ElephantContext singleton - */ - public static ElephantContext instance() { - return INSTANCE; - } - /** * Given an application type, return the currently bound heuristics * diff --git a/app/com/linkedin/drelephant/ElephantRunner.java b/app/com/linkedin/drelephant/ElephantRunner.java index 3274c898f..99b6e82e9 100644 --- a/app/com/linkedin/drelephant/ElephantRunner.java +++ b/app/com/linkedin/drelephant/ElephantRunner.java @@ -59,9 +59,11 @@ public void run() { _hadoopSecurity.doAs(new PrivilegedAction() { @Override public Void run() { + //TODO: We need to catch exception here HadoopSystemContext.load(); _emailer.start(); loadAnalyticJobGenerator(); + ElephantContext.init(); _service = Executors.newFixedThreadPool(EXECUTOR_NUM); _jobQueue = new LinkedBlockingQueue(); @@ -131,12 +133,6 @@ public void run() { JobResult result = analyticJob.getAnalysis(); result.save(); - logger.info( - "Executor thread " + _threadId + " analyzed " + analyticJob.getAppType().getName() + " " + analyticJob - .getAppId()); - - // TODO: how to test email sending? - _emailer.enqueue(result); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } catch (Exception e) { diff --git a/app/com/linkedin/drelephant/util/Utils.java b/app/com/linkedin/drelephant/util/Utils.java index b7de5c86e..770c2fd3d 100644 --- a/app/com/linkedin/drelephant/util/Utils.java +++ b/app/com/linkedin/drelephant/util/Utils.java @@ -113,13 +113,14 @@ public static void publishMetrics(MapReduceApplicationData jobData) { */ public static Document loadXMLDoc(String filePath) { InputStream instream = null; + logger.info("Loading configuration file " + filePath); instream = Play.application().resourceAsStream(filePath); -// try { -// instream = new FileInputStream(filePath); -// } catch (FileNotFoundException e) { -// throw new RuntimeException("File " + filePath + " does not exist.", e); -// } + if (instream == null) { + logger.info("Configuation file not present in classpath. File: " + filePath); + throw new RuntimeException("Unable to read " + filePath); + } + logger.info("Configuation file loaded. File: " + filePath); Document document = null; try { diff --git a/compile.sh b/compile.sh index 66f662fa4..69ec39506 100755 --- a/compile.sh +++ b/compile.sh @@ -24,7 +24,7 @@ DIST_NAME=${ZIP_NAME%.zip} chmod +x ${DIST_NAME}/bin/dr-elephant -sed -i.bak $'/declare -r app_classpath/s/.$/:`hadoop classpath`"/' ${DIST_NAME}/bin/dr-elephant +sed -i.bak $'/declare -r app_classpath/s/.$/:`hadoop classpath`:${ELEPHANT_CONF_DIR}"/' ${DIST_NAME}/bin/dr-elephant cp $start_script ${DIST_NAME}/bin/ diff --git a/conf/application.conf b/conf/application.conf index b8e5a67f0..e1a42b77b 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -77,15 +77,3 @@ smtp.from="azkaban-noreply@linkedin.com" # smtp.user=azkaban-noreply # smtp.password= -# TODO: JUST FOR REFERENCE RIGHT NOW, THESE SHOULD BE NOT CHECK THESE INTO CODE BASE -# Dali metrics publisher -metrics.publisher-conf=CounterPublisherConf.xml -fetchers.conf.location=FetcherConf.xml -jobtypes.conf.location=JobTypeConf.xml -heuristics.conf.location=HeuristicConf.xml - -#Kerberos -keytab.location="/export/apps/hadoop/keytabs/dr_elephant-service.keytab" -keytab.user="elephant/eat1-nertzaz01.grid.linkedin.com" - -spark.eventLog.dir="hdfs://eat1-nertznn01.grid.linkedin.com:9000/system/spark-history" diff --git a/start.sh b/start.sh index eb187e229..2829741f5 100755 --- a/start.sh +++ b/start.sh @@ -23,12 +23,19 @@ project_root=$script_dir/../ # User could give an optional argument(config file path) or we will provide a default one if [ -z "$1" ]; then - echo "Using default config file: /export/apps/elephant/conf/elephant.conf" - CONFIG_FILE="/export/apps/elephant/conf/elephant.conf" + echo "Using default config dir: /export/apps/elephant/conf" + CONF_DIR="/export/apps/elephant/conf" else - CONFIG_FILE=$1 + echo "Using config dir: "$1 + CONF_DIR=$1 fi +CONFIG_FILE=$CONF_DIR"/elephant.conf" +echo "Using config file: "$CONFIG_FILE + +# set env variable so Dr. run script will use this dir and load all confs into classpath +export ELEPHANT_CONF_DIR=$CONF_DIR + # User must give a valid file as argument if [ -f $CONFIG_FILE ]; then @@ -104,12 +111,6 @@ fi OPTS="-Djava.library.path=$JAVA_LIB_PATH -Dhttp.port=$port -Dkeytab.user=$keytab_user -Dkeytab.location=$keytab_location -Ddb.default.url=$db_loc -Ddb.default.user=$db_user -Ddb.default.password=$db_password" -CONFIG_DIR=`dirname ${CONFIG_FILE}` -METRICS_PUBLISHER_CONF_PATH="${CONFIG_DIR}/${METRICS_PUBLISHER_CONF_FILE}" -if [ -r ${METRICS_PUBLISHER_CONF_PATH} ]; then - OPTS="${OPTS} -Dmetrics.publisher-conf=${METRICS_PUBLISHER_CONF_PATH}" -fi - # Start Dr. Elaphant echo "Starting Dr. Elephant ...." nohup ./bin/dr-elephant ${OPTS} > /dev/null 2>&1 & From 0f36e0cdb211c5e81543b89ddcbe44a0334a7124 Mon Sep 17 00:00:00 2001 From: Fangshi Li Date: Wed, 17 Jun 2015 11:20:38 -0700 Subject: [PATCH 077/272] HDOOP-11456: Change Dr. Elephant's heuristic from MapperInputSize to MapperTime --- app-conf/HeuristicConf.xml | 6 +- .../linkedin/drelephant/ElephantRunner.java | 4 - .../heuristics/MapperInputSizeHeuristic.java | 106 ------------ .../heuristics/MapperTimeHeuristic.java | 89 ++++++++++ .../heuristics/ReducerTimeHeuristic.java | 8 +- ...e.scala.html => helpMapperTime.scala.html} | 19 ++- .../MapperInputSizeHeuristicTest.java | 156 ------------------ .../heuristics/MapperTimeHeuristicTest.java | 139 ++++++++++++++++ .../heuristics/ReducerTimeHeuristicTest.java | 14 +- 9 files changed, 253 insertions(+), 288 deletions(-) delete mode 100644 app/com/linkedin/drelephant/mapreduce/heuristics/MapperInputSizeHeuristic.java create mode 100644 app/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristic.java rename app/views/{helpMapperInputSize.scala.html => helpMapperTime.scala.html} (85%) delete mode 100644 test/com/linkedin/drelephant/mapreduce/heuristics/MapperInputSizeHeuristicTest.java create mode 100644 test/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristicTest.java diff --git a/app-conf/HeuristicConf.xml b/app-conf/HeuristicConf.xml index 9ead236b7..12005aa5c 100644 --- a/app-conf/HeuristicConf.xml +++ b/app-conf/HeuristicConf.xml @@ -16,9 +16,9 @@ mapreduce - Mapper Input Size - com.linkedin.drelephant.mapreduce.heuristics.MapperInputSizeHeuristic - views.html.helpMapperInputSize + Mapper Time + com.linkedin.drelephant.mapreduce.heuristics.MapperTimeHeuristic + views.html.helpMapperTime 1 2 diff --git a/app/com/linkedin/drelephant/ElephantRunner.java b/app/com/linkedin/drelephant/ElephantRunner.java index 99b6e82e9..646498b85 100644 --- a/app/com/linkedin/drelephant/ElephantRunner.java +++ b/app/com/linkedin/drelephant/ElephantRunner.java @@ -5,7 +5,6 @@ import com.linkedin.drelephant.analysis.AnalyticJobGeneratorHadoop1; import com.linkedin.drelephant.analysis.HadoopSystemContext; import com.linkedin.drelephant.analysis.AnalyticJobGeneratorHadoop2; -import com.linkedin.drelephant.notifications.EmailThread; import java.io.IOException; import java.security.PrivilegedAction; import java.util.List; @@ -26,7 +25,6 @@ public class ElephantRunner implements Runnable { private static final Logger logger = Logger.getLogger(ElephantRunner.class); private AtomicBoolean _running = new AtomicBoolean(true); private long lastRun; - private EmailThread _emailer = new EmailThread(); private HadoopSecurity _hadoopSecurity; private ExecutorService _service; private BlockingQueue _jobQueue; @@ -61,7 +59,6 @@ public void run() { public Void run() { //TODO: We need to catch exception here HadoopSystemContext.load(); - _emailer.start(); loadAnalyticJobGenerator(); ElephantContext.init(); @@ -171,7 +168,6 @@ private void waitInterval() { public void kill() { _running.set(false); - _emailer.kill(); _service.shutdownNow(); } } diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperInputSizeHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperInputSizeHeuristic.java deleted file mode 100644 index 77dbc6b96..000000000 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperInputSizeHeuristic.java +++ /dev/null @@ -1,106 +0,0 @@ -package com.linkedin.drelephant.mapreduce.heuristics; - -import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; -import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; -import java.util.ArrayList; -import java.util.List; - -import com.linkedin.drelephant.analysis.HadoopSystemContext; -import com.linkedin.drelephant.analysis.Heuristic; -import com.linkedin.drelephant.analysis.HeuristicResult; -import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.mapreduce.MapReduceTaskData; -import com.linkedin.drelephant.math.Statistics; - -import org.apache.commons.io.FileUtils; - - -public class MapperInputSizeHeuristic implements Heuristic { - public static final String HEURISTIC_NAME = "Mapper Input Size"; - - @Override - public String getHeuristicName() { - return HEURISTIC_NAME; - } - - @Override - public HeuristicResult apply(MapReduceApplicationData data) { - MapReduceTaskData[] tasks = data.getMapperData(); - - List inputBytes = new ArrayList(); - List runtimesMs = new ArrayList(); - - for (MapReduceTaskData task : tasks) { - inputBytes.add(task.getCounters().get(MapReduceCounterHolder.CounterName.HDFS_BYTES_READ)); - if (task.timed()) { - runtimesMs.add(task.getTotalRunTimeMs()); - } - } - - long averageSize = Statistics.average(inputBytes); - long averageTimeMs = Statistics.average(runtimesMs); - - Severity smallFilesSeverity = smallFilesSeverity(averageSize, tasks.length, averageTimeMs); - Severity largeFilesSeverity = largeFilesSeverity(averageSize, tasks.length, averageTimeMs); - Severity severity = Severity.max(smallFilesSeverity, largeFilesSeverity); - - HeuristicResult result = new HeuristicResult(HEURISTIC_NAME, severity); - - result.addDetail("Number of tasks", Integer.toString(tasks.length)); - result.addDetail("Average task input size", FileUtils.byteCountToDisplaySize(averageSize)); - result.addDetail("Average task runtime", Statistics.readableTimespan(averageTimeMs)); - - return result; - } - - private Severity smallFilesSeverity(long value, long numTasks, long averageTimeMs) { - // We want to identify jobs with small task input, large number of tasks, and low task runtime - Severity severity = getSmallFilesSeverity(value); - // Severity is reduced if number of tasks is small - Severity numTaskSeverity = getNumTasksSeverity(numTasks); - severity = Severity.min(severity, numTaskSeverity); - // Severity is reduced if task runtime is long - Severity runtimeSeverity = getRuntimeSeverityReverse(averageTimeMs); - return Severity.min(severity, runtimeSeverity); - } - - private Severity largeFilesSeverity(long value, long numTasks, long averageTimeMs) { - // We want to identify jobs with large task input, small number of tasks, and long task runtime - Severity severity = getLargeFilesSeverity(value); - // Severity is reduced if number of tasks is large - Severity numTaskSeverity = getNumTasksSeverityReverse(numTasks); - severity = Severity.min(severity, numTaskSeverity); - // Severity is reduced if task runtime is short - Severity runtimeSeverity = getRuntimeSeverity(averageTimeMs); - return Severity.min(severity, runtimeSeverity); - } - - public static Severity getSmallFilesSeverity(long value) { - return Severity.getSeverityDescending(value, HadoopSystemContext.HDFS_BLOCK_SIZE / 2, HadoopSystemContext.HDFS_BLOCK_SIZE / 4, - HadoopSystemContext.HDFS_BLOCK_SIZE / 8, HadoopSystemContext.HDFS_BLOCK_SIZE / 32); - } - - public static Severity getLargeFilesSeverity(long value) { - return Severity.getSeverityAscending(value, HadoopSystemContext.HDFS_BLOCK_SIZE * 2, HadoopSystemContext.HDFS_BLOCK_SIZE * 3, - HadoopSystemContext.HDFS_BLOCK_SIZE * 4, HadoopSystemContext.HDFS_BLOCK_SIZE * 5); - } - - public static Severity getNumTasksSeverity(long numTasks) { - return Severity.getSeverityAscending(numTasks, 10, 50, 200, 500); - } - - public static Severity getNumTasksSeverityReverse(long numTasks) { - return Severity.getSeverityDescending(numTasks, 1000, 500, 200, 100); - } - - public static Severity getRuntimeSeverity(long runtimeMs) { - return Severity.getSeverityAscending(runtimeMs, 10 * Statistics.MINUTE_IN_MS, 15 * Statistics.MINUTE_IN_MS, - 20 * Statistics.MINUTE_IN_MS, 30 * Statistics.MINUTE_IN_MS); - } - - public static Severity getRuntimeSeverityReverse(long runtimeMs) { - return Severity.getSeverityDescending(runtimeMs, 5 * Statistics.MINUTE_IN_MS, 4 * Statistics.MINUTE_IN_MS, - 3 * Statistics.MINUTE_IN_MS, 2 * Statistics.MINUTE_IN_MS); - } - -} diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristic.java new file mode 100644 index 000000000..a085de5b5 --- /dev/null +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristic.java @@ -0,0 +1,89 @@ +package com.linkedin.drelephant.mapreduce.heuristics; + +import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; +import java.util.ArrayList; +import java.util.List; + +import com.linkedin.drelephant.analysis.HadoopSystemContext; +import com.linkedin.drelephant.analysis.Heuristic; +import com.linkedin.drelephant.analysis.HeuristicResult; +import com.linkedin.drelephant.analysis.Severity; +import com.linkedin.drelephant.mapreduce.MapReduceTaskData; +import com.linkedin.drelephant.math.Statistics; + +import org.apache.commons.io.FileUtils; + + +public class MapperTimeHeuristic implements Heuristic { + public static final String HEURISTIC_NAME = "Mapper Time"; + + @Override + public String getHeuristicName() { + return HEURISTIC_NAME; + } + + @Override + public HeuristicResult apply(MapReduceApplicationData data) { + MapReduceTaskData[] tasks = data.getMapperData(); + + List inputBytes = new ArrayList(); + List runtimesMs = new ArrayList(); + + for (MapReduceTaskData task : tasks) { + inputBytes.add(task.getCounters().get(MapReduceCounterHolder.CounterName.HDFS_BYTES_READ)); + if (task.timed()) { + runtimesMs.add(task.getTotalRunTimeMs()); + } + } + + long averageSize = Statistics.average(inputBytes); + long averageTimeMs = Statistics.average(runtimesMs); + + Severity shortTaskSeverity = shortTaskSeverity(tasks.length, averageTimeMs); + Severity longTaskSeverity = longTaskSeverity(tasks.length, averageTimeMs); + Severity severity = Severity.max(shortTaskSeverity, longTaskSeverity); + + HeuristicResult result = new HeuristicResult(HEURISTIC_NAME, severity); + + result.addDetail("Number of tasks", Integer.toString(tasks.length)); + result.addDetail("Average task input size", FileUtils.byteCountToDisplaySize(averageSize)); + result.addDetail("Average task runtime", Statistics.readableTimespan(averageTimeMs)); + + return result; + } + + private Severity shortTaskSeverity(long numTasks, long averageTimeMs) { + // We want to identify jobs with short task runtime + Severity severity = getShortRuntimeSeverity(averageTimeMs); + // Severity is reduced if number of tasks is small. + Severity numTaskSeverity = getNumTasksSeverity(numTasks); + return Severity.min(severity, numTaskSeverity); + } + + private Severity longTaskSeverity(long numTasks, long averageTimeMs) { + // We want to identify jobs with long task runtime + Severity severity = getLongRuntimeSeverity(averageTimeMs); + // Severity is reduced if number of tasks is large + Severity numTaskSeverity = getNumTasksSeverityReverse(numTasks); + return Severity.min(severity, numTaskSeverity); + } + + public static Severity getShortRuntimeSeverity(long runtimeMs) { + return Severity.getSeverityDescending(runtimeMs, 10 * Statistics.MINUTE_IN_MS, 4 * Statistics.MINUTE_IN_MS, + 2 * Statistics.MINUTE_IN_MS, 1 * Statistics.MINUTE_IN_MS); + } + + public static Severity getLongRuntimeSeverity(long runtimeMs) { + return Severity.getSeverityAscending(runtimeMs, 15 * Statistics.MINUTE_IN_MS, 30 * Statistics.MINUTE_IN_MS, 1 * Statistics.HOUR_IN_MS, + 2 * Statistics.HOUR_IN_MS); + } + + public static Severity getNumTasksSeverity(long numTasks) { + return Severity.getSeverityAscending(numTasks, 50, 101, 500, 1000); + } + + public static Severity getNumTasksSeverityReverse(long numTasks) { + return Severity.getSeverityDescending(numTasks, 500, 100, 50, 10); + } +} diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristic.java index 28edabaa3..4ad1d5055 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristic.java @@ -48,18 +48,20 @@ public HeuristicResult apply(MapReduceApplicationData data) { private Severity shortTimeSeverity(long runtimeMs, long numTasks) { Severity timeSeverity = getShortRuntimeSeverity(runtimeMs); + // Severity is adjusted based on number of tasks Severity taskSeverity = getNumTasksSeverity(numTasks); return Severity.min(timeSeverity, taskSeverity); } private Severity longTimeSeverity(long runtimeMs, long numTasks) { Severity timeSeverity = getLongRuntimeSeverity(runtimeMs); + // Severity is adjusted based on number of tasks Severity taskSeverity = getNumTasksSeverityReverse(numTasks); return Severity.min(timeSeverity, taskSeverity); } public static Severity getShortRuntimeSeverity(long runtimeMs) { - return Severity.getSeverityDescending(runtimeMs, 10 * Statistics.MINUTE_IN_MS, 5 * Statistics.MINUTE_IN_MS, + return Severity.getSeverityDescending(runtimeMs, 10 * Statistics.MINUTE_IN_MS, 4 * Statistics.MINUTE_IN_MS, 2 * Statistics.MINUTE_IN_MS, 1 * Statistics.MINUTE_IN_MS); } @@ -69,10 +71,10 @@ public static Severity getLongRuntimeSeverity(long runtimeMs) { } public static Severity getNumTasksSeverity(long numTasks) { - return Severity.getSeverityAscending(numTasks, 10, 51, 200, 500); + return Severity.getSeverityAscending(numTasks, 50, 101, 500, 1000); } public static Severity getNumTasksSeverityReverse(long numTasks) { - return Severity.getSeverityDescending(numTasks, 100, 49, 20, 10); + return Severity.getSeverityDescending(numTasks, 500, 100, 50, 10); } } diff --git a/app/views/helpMapperInputSize.scala.html b/app/views/helpMapperTime.scala.html similarity index 85% rename from app/views/helpMapperInputSize.scala.html rename to app/views/helpMapperTime.scala.html index 88f96cc55..69596aa50 100644 --- a/app/views/helpMapperInputSize.scala.html +++ b/app/views/helpMapperTime.scala.html @@ -3,13 +3,13 @@ This should allow you to better tweak the number of mappers for your job.
There are two possible situations that needs some tuning.

-

Too many small files

+

Mapper time too short

This usually happens when the Hadoop job has:

  • A large number of mappers
  • +
  • Short mapper avg runtime
  • Small file size
  • -
  • Short mapper runtime

Example
@@ -22,7 +22,7 @@

Mapper Input Size

Number of tasks - 916 + 1516 Average task input size @@ -39,10 +39,9 @@

Mapper Input Size

Suggestions

- Note: By default HadoopJava will not combine small files. if that’s the case, you should either use CombineFileInputFormat or use Pig/Hive. -
+
- Otherwise, you should tune mapper split size to reduce number of mappers and let each mapper process larger data
+ You should tune mapper split size to reduce number of mappers and let each mapper process larger data
The parameter for changing split size is:

  • mapred.max/min.split.size (Hadoop 1)
  • @@ -59,6 +58,8 @@

    Suggestions

    The split size is controlled by formula max(minSplitSize, min(maxSplitSize, blockSize)). By default, blockSize=512MB and minSplit < blockSize < maxSplit.
    You should always refer to this formula.
    In the case above, try increase min split size and let each mapper process larger data.
    + [Note] By default HadoopJava will not combine small files, so each mapper cannot process more than one file, and changing split size won't help. If that is your case, you should either try CombineFileInputFormat or use Pig/Hive. +
    See Hadoop Tuning Tips for further information.

    @@ -67,8 +68,8 @@

    Large files/Unsplittable files

    This usually happens when the Hadoop job has:
    • A small number of mappers
    • +
    • Long mapper avg runtime
    • Large file size (a few GB's)
    • -
    • Long mapper runtime

    Example
    @@ -81,7 +82,7 @@

    Mapper Input Size

    Number of tasks - 100 + 50 Average task input @@ -89,7 +90,7 @@

    Mapper Input Size

    Average task runtime - 32 min 30s + 52 min 30s diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperInputSizeHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperInputSizeHeuristicTest.java deleted file mode 100644 index 84b383abe..000000000 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperInputSizeHeuristicTest.java +++ /dev/null @@ -1,156 +0,0 @@ -package com.linkedin.drelephant.mapreduce.heuristics; - -import com.linkedin.drelephant.analysis.HadoopSystemContext; -import com.linkedin.drelephant.analysis.Heuristic; -import com.linkedin.drelephant.analysis.HeuristicResult; -import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; -import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; -import com.linkedin.drelephant.mapreduce.MapReduceTaskData; -import com.linkedin.drelephant.math.Statistics; -import java.io.IOException; -import junit.framework.TestCase; - - -public class MapperInputSizeHeuristicTest extends TestCase { - - private static final long UNITSIZE = HadoopSystemContext.HDFS_BLOCK_SIZE; - Heuristic _heuristic = new MapperInputSizeHeuristic(); - - // Test batch 1: Large file test with large runtime and various number of tasks */ - - public void testLargeFileNumTasksCritical() throws IOException { - // Should decrease file size and increase number of tasks - assertEquals(Severity.CRITICAL, analyzeJob(10, 5 * UNITSIZE, 60 * Statistics.MINUTE_IN_MS)); - } - - public void testLargeFileNumTasksSevere() throws IOException { - // Should decrease file size and increase number of tasks - assertEquals(Severity.SEVERE, analyzeJob(200, 5 * UNITSIZE, 60 * Statistics.MINUTE_IN_MS)); - } - - public void testLargeFileNumTasksModerate() throws IOException { - assertEquals(Severity.MODERATE, analyzeJob(500, 5 * UNITSIZE, 60 * Statistics.MINUTE_IN_MS)); - } - - public void testLargeFileNumTasksLow() throws IOException { - assertEquals(Severity.LOW, analyzeJob(1000, 5 * UNITSIZE, 60 * Statistics.MINUTE_IN_MS)); - } - - public void testLargeFileNumTasksNone() throws IOException { - // Large file with large number of tasks and long runtime. - // Looks horrible but nothing we could suggest in this case (Probably suggest something other than hadoop?) - assertEquals(Severity.NONE, analyzeJob(2000, 5 * UNITSIZE, 60 * Statistics.MINUTE_IN_MS)); - } - - // Test batch 2: Large file test with various runtime and small number of tasks - - public void testLargeFileRuntimeCritical() throws IOException { - // Should decrease file size and increase number of tasks - assertEquals(Severity.CRITICAL, analyzeJob(100, 5 * UNITSIZE, 60 * Statistics.MINUTE_IN_MS)); - } - - public void testLargeFileRuntimeSevere() throws IOException { - // Should decrease file size and increase number of tasks - assertEquals(Severity.SEVERE, analyzeJob(100, 5 * UNITSIZE, 20 * Statistics.MINUTE_IN_MS)); - } - - public void testLargeFileRuntimeModerate() throws IOException { - assertEquals(Severity.MODERATE, analyzeJob(100, 5 * UNITSIZE, 15 * Statistics.MINUTE_IN_MS)); - } - - public void testLargeFileRuntimeLow() throws IOException { - assertEquals(Severity.LOW, analyzeJob(100, 5 * UNITSIZE, 10 * Statistics.MINUTE_IN_MS)); - } - - public void testLargeFileRuntimeNone() throws IOException { - // Large file, but already has small number of tasks and small runtime. Looks good. - assertEquals(Severity.NONE, analyzeJob(100, 5 * UNITSIZE, 5 * Statistics.MINUTE_IN_MS)); - } - - // Test batch 3: Small file test with short runtime and various number of tasks - - public void testSmallFileNumTasksCritical() throws IOException { - // Should increase file size and decrease number of tasks - assertEquals(Severity.CRITICAL, analyzeJob(1000, UNITSIZE / 32, 2 * Statistics.MINUTE_IN_MS)); - } - - public void testSmallFileNumTasksSevere() throws IOException { - // Should increase file size and decrease number of tasks - assertEquals(Severity.SEVERE, analyzeJob(200, UNITSIZE / 32, 2 * Statistics.MINUTE_IN_MS)); - } - - public void testSmallFileNumTasksModerate() throws IOException { - assertEquals(Severity.MODERATE, analyzeJob(50, UNITSIZE / 32, 2 * Statistics.MINUTE_IN_MS)); - } - - public void testSmallFileNumTasksLow() throws IOException { - assertEquals(Severity.LOW, analyzeJob(10, UNITSIZE / 32, 2 * Statistics.MINUTE_IN_MS)); - } - - public void testSmallFileNumTasksNone() throws IOException { - // Small file with small number of tasks and short runtime. This should be the common case. - assertEquals(Severity.NONE, analyzeJob(5, UNITSIZE / 32, 2 * Statistics.MINUTE_IN_MS)); - } - - // Test batch 4: Small file test with large number of tasks and various runtime - - public void testRuntimeSmallFileCritical() throws IOException { - // Should increase file size and decrease number of tasks - assertEquals(Severity.CRITICAL, analyzeJob(500, UNITSIZE / 32, 1 * Statistics.MINUTE_IN_MS)); - } - - public void testRuntimeSmallFileSevere() throws IOException { - // Should increase file size and decrease number of tasks - assertEquals(Severity.SEVERE, analyzeJob(500, UNITSIZE / 32, 3 * Statistics.MINUTE_IN_MS)); - } - - public void testRuntimeSmallFileModerate() throws IOException { - assertEquals(Severity.MODERATE, analyzeJob(500, UNITSIZE / 32, 4 * Statistics.MINUTE_IN_MS)); - } - - public void testRuntimeSmallFileLow() throws IOException { - assertEquals(Severity.LOW, analyzeJob(500, UNITSIZE / 32, 5 * Statistics.MINUTE_IN_MS)); - } - - public void testRuntimeSmallFileNone() throws IOException { - // Small file with large number of tasks and long runtime. This should be a CPU-bound job - // Probably not a good idea to suggest file size increase(even longer runtime) or decrease(even more mappers) - assertEquals(Severity.NONE, analyzeJob(500, UNITSIZE / 32, 10 * Statistics.MINUTE_IN_MS)); - } - - // Test batch 5: Test with normal file size (one HDFS block). This should be a common case - - public void testNormalFileSmallNumTasks() throws IOException { - assertEquals(Severity.NONE, analyzeJob(10, UNITSIZE, 10 * Statistics.MINUTE_IN_MS)); - } - - public void testNormalFileLargeNumTasks() throws IOException { - assertEquals(Severity.NONE, analyzeJob(300, UNITSIZE, 10 * Statistics.MINUTE_IN_MS)); - } - - public void testNormalFileLongRuntime() throws IOException { - assertEquals(Severity.NONE, analyzeJob(100, UNITSIZE, 30 * Statistics.MINUTE_IN_MS)); - } - - public void testNormalFileShortRuntime() throws IOException { - assertEquals(Severity.NONE, analyzeJob(100, UNITSIZE, 1 * Statistics.MINUTE_IN_MS)); - } - - private Severity analyzeJob(int numTasks, long inputSize, long runtime) throws IOException { - MapReduceCounterHolder jobCounter = new MapReduceCounterHolder(); - MapReduceTaskData[] mappers = new MapReduceTaskData[numTasks]; - - MapReduceCounterHolder taskCounter = new MapReduceCounterHolder(); - taskCounter.set(MapReduceCounterHolder.CounterName.HDFS_BYTES_READ, inputSize); - - int i = 0; - for (; i < numTasks; i++) { - mappers[i] = new MapReduceTaskData(taskCounter, new long[] { runtime, 0, 0 }); - } - - MapReduceApplicationData data = new MapReduceApplicationData().setCounters(jobCounter).setMapperData(mappers); - HeuristicResult result = _heuristic.apply(data); - return result.getSeverity(); - } -} diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristicTest.java new file mode 100644 index 000000000..73ef3d661 --- /dev/null +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristicTest.java @@ -0,0 +1,139 @@ +package com.linkedin.drelephant.mapreduce.heuristics; + +import com.linkedin.drelephant.analysis.HadoopSystemContext; +import com.linkedin.drelephant.analysis.Heuristic; +import com.linkedin.drelephant.analysis.HeuristicResult; +import com.linkedin.drelephant.analysis.Severity; +import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; +import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.MapReduceTaskData; +import com.linkedin.drelephant.math.Statistics; +import java.io.IOException; +import junit.framework.TestCase; + + +public class MapperTimeHeuristicTest extends TestCase { + + private static final long DUMMY_INPUT_SIZE = 0; + Heuristic _heuristic = new MapperTimeHeuristic(); + + // Test batch 1: Large runtime and various number of tasks */ + + public void testLongRuntimeTasksCritical() throws IOException { + // Should decrease split size and increase number of tasks + assertEquals(Severity.CRITICAL, analyzeJob(10, 120 * Statistics.MINUTE_IN_MS)); + } + + public void testLongRuntimeTasksSevere() throws IOException { + // Should decrease split size and increase number of tasks + assertEquals(Severity.SEVERE, analyzeJob(50, 120 * Statistics.MINUTE_IN_MS)); + } + + public void testLongRuntimeTasksModerate() throws IOException { + assertEquals(Severity.MODERATE, analyzeJob(100, 120 * Statistics.MINUTE_IN_MS)); + } + + public void testLongRuntimeTasksLow() throws IOException { + assertEquals(Severity.LOW, analyzeJob(500, 120 * Statistics.MINUTE_IN_MS)); + } + + public void testLongRuntimeTasksNone() throws IOException { + // Large file with large number of tasks and long runtime. + // Looks horrible but nothing we could suggest in this case (Probably suggest something other than hadoop?) + assertEquals(Severity.NONE, analyzeJob(1000, 120 * Statistics.MINUTE_IN_MS)); + } + + // Test batch 2: Short runtime and various number of tasks + + public void testShortRuntimeTasksCritical() throws IOException { + // Should increase split size and decrease number of tasks + assertEquals(Severity.CRITICAL, analyzeJob(1000, 1 * Statistics.MINUTE_IN_MS)); + } + + public void testShortRuntimeTasksSevere() throws IOException { + // Should increase split size and decrease number of tasks + assertEquals(Severity.SEVERE, analyzeJob(500, 1 * Statistics.MINUTE_IN_MS)); + } + + public void testShortRuntimeTasksModerate() throws IOException { + assertEquals(Severity.MODERATE, analyzeJob(101, 1 * Statistics.MINUTE_IN_MS)); + } + + public void testShortRuntimeTasksLow() throws IOException { + assertEquals(Severity.LOW, analyzeJob(50, 1 * Statistics.MINUTE_IN_MS)); + } + + public void testShortRuntimeTasksNone() throws IOException { + // Small file with small number of tasks and short runtime. This should be the common case. + assertEquals(Severity.NONE, analyzeJob(5, 1 * Statistics.MINUTE_IN_MS)); + } + + // Test batch 3: Large number of tasks and various runtime + + public void testLargeNumTasksCritical() throws IOException { + // Should increase split size and decrease number of tasks + assertEquals(Severity.CRITICAL, analyzeJob(1000, 1 * Statistics.MINUTE_IN_MS)); + } + + public void testLargeNumTasksSevere() throws IOException { + // Should increase split size and decrease number of tasks + assertEquals(Severity.SEVERE, analyzeJob(1000, 2 * Statistics.MINUTE_IN_MS)); + } + + public void testLargeNumTasksModerate() throws IOException { + assertEquals(Severity.MODERATE, analyzeJob(1000, 4 * Statistics.MINUTE_IN_MS)); + } + + public void testLargeNumTasksLow() throws IOException { + // This should be common case + assertEquals(Severity.LOW, analyzeJob(1000, 5 * Statistics.MINUTE_IN_MS)); + } + + public void testLargeNumTasksNone() throws IOException { + // This should be a common case + assertEquals(Severity.NONE, analyzeJob(1000, 12 * Statistics.MINUTE_IN_MS)); + } + + // Test batch 4: Small number of tasks and various runtime + + public void testSmallNumTasksCritical() throws IOException { + // Should decrease split size and increase number of tasks + assertEquals(Severity.CRITICAL, analyzeJob(10, 120 * Statistics.MINUTE_IN_MS)); + } + + public void testSmallNumTasksSevere() throws IOException { + // Should decrease split size and increase number of tasks + assertEquals(Severity.SEVERE, analyzeJob(10, 60 * Statistics.MINUTE_IN_MS)); + } + + public void testSmallNumTasksModerate() throws IOException { + assertEquals(Severity.MODERATE, analyzeJob(10, 30 * Statistics.MINUTE_IN_MS)); + } + + public void testSmallNumTasksLow() throws IOException { + // This should be common case + assertEquals(Severity.LOW, analyzeJob(10, 15 * Statistics.MINUTE_IN_MS)); + } + + public void testSmallNumTasksNone() throws IOException { + // This should be a common case + assertEquals(Severity.NONE, analyzeJob(10, 12 * Statistics.MINUTE_IN_MS)); + } + + private Severity analyzeJob(int numTasks, long runtime) throws IOException { + MapReduceCounterHolder jobCounter = new MapReduceCounterHolder(); + MapReduceTaskData[] mappers = new MapReduceTaskData[numTasks]; + + MapReduceCounterHolder taskCounter = new MapReduceCounterHolder(); + taskCounter.set(MapReduceCounterHolder.CounterName.HDFS_BYTES_READ, DUMMY_INPUT_SIZE); + + int i = 0; + for (; i < numTasks; i++) { + mappers[i] = new MapReduceTaskData(taskCounter, new long[] { runtime, 0, 0 }); + } + + MapReduceApplicationData data = new MapReduceApplicationData().setCounters(jobCounter).setMapperData(mappers); + HeuristicResult result = _heuristic.apply(data); + return result.getSeverity(); + } +} diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristicTest.java index 21d27e28f..a8849a595 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristicTest.java @@ -18,15 +18,15 @@ public class ReducerTimeHeuristicTest extends TestCase { private static final long MINUTE_IN_MS = Statistics.MINUTE_IN_MS;; public void testShortRunetimeCritical() throws IOException { - assertEquals(Severity.CRITICAL, analyzeJob(1 * MINUTE_IN_MS, 500)); + assertEquals(Severity.CRITICAL, analyzeJob(1 * MINUTE_IN_MS, 1000)); } public void testShortRunetimeSevere() throws IOException { - assertEquals(Severity.SEVERE, analyzeJob(1 * MINUTE_IN_MS, 200)); + assertEquals(Severity.SEVERE, analyzeJob(1 * MINUTE_IN_MS, 500)); } public void testShortRunetimeModerate() throws IOException { - assertEquals(Severity.MODERATE, analyzeJob(1 * MINUTE_IN_MS, 51)); + assertEquals(Severity.MODERATE, analyzeJob(1 * MINUTE_IN_MS, 101)); } public void testShortRunetimeLow() throws IOException { @@ -42,19 +42,19 @@ public void testLongRunetimeCritical() throws IOException { } public void testLongRunetimeSevere() throws IOException { - assertEquals(Severity.SEVERE, analyzeJob(120 * MINUTE_IN_MS, 20)); + assertEquals(Severity.SEVERE, analyzeJob(120 * MINUTE_IN_MS, 50)); } public void testLongRunetimeModerate() throws IOException { - assertEquals(Severity.MODERATE, analyzeJob(120 * MINUTE_IN_MS, 40)); + assertEquals(Severity.MODERATE, analyzeJob(120 * MINUTE_IN_MS, 100)); } public void testLongRunetimeLow() throws IOException { - assertEquals(Severity.LOW, analyzeJob(120 * MINUTE_IN_MS, 100)); + assertEquals(Severity.LOW, analyzeJob(120 * MINUTE_IN_MS, 500)); } public void testLongRunetimeNone() throws IOException { - assertEquals(Severity.NONE, analyzeJob(120 * MINUTE_IN_MS, 200)); + assertEquals(Severity.NONE, analyzeJob(120 * MINUTE_IN_MS, 1000)); } private Severity analyzeJob(long runtimeMs, int numTasks) throws IOException { From 9d5556978e99fad682c8f682a121e939320accc8 Mon Sep 17 00:00:00 2001 From: Fangshi Li Date: Thu, 18 Jun 2015 13:52:42 -0700 Subject: [PATCH 078/272] HADOOP-11922: Fix Dr. Elephant search by heuristic field --- app/views/search.scala.html | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/views/search.scala.html b/app/views/search.scala.html index e7e893d79..fde81d815 100644 --- a/app/views/search.scala.html +++ b/app/views/search.scala.html @@ -43,8 +43,12 @@
    From e180bea8b725c80e77aa29738856599ea5c132f2 Mon Sep 17 00:00:00 2001 From: Fangshi Li Date: Thu, 18 Jun 2015 14:08:22 -0700 Subject: [PATCH 079/272] HADOOP-11912: Release Dr. Elephant v1.0.0 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 78684ebe6..94ab15464 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ import play.Project._ name := "dr-elephant" -version := "1.0.0-SNAPSHOT" +version := "1.0.0" javacOptions in Compile ++= Seq("-source", "1.6", "-target", "1.6") From 5e61c3a5384eb52312d024a2d3b088abe0b5e4df Mon Sep 17 00:00:00 2001 From: Fangshi Li Date: Mon, 22 Jun 2015 11:45:54 -0700 Subject: [PATCH 080/272] HADOOP-12013: Dr. Elephant has a corner case to analyze duplicate entries --- .../analysis/AnalyticJobGeneratorHadoop2.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/app/com/linkedin/drelephant/analysis/AnalyticJobGeneratorHadoop2.java b/app/com/linkedin/drelephant/analysis/AnalyticJobGeneratorHadoop2.java index c6f78e295..fa95b7766 100644 --- a/app/com/linkedin/drelephant/analysis/AnalyticJobGeneratorHadoop2.java +++ b/app/com/linkedin/drelephant/analysis/AnalyticJobGeneratorHadoop2.java @@ -66,12 +66,13 @@ public List fetchAnalyticJobs() _currentTime = System.currentTimeMillis() - FETCH_DELAY; updateAuthToken(); - URL appsURL = new URL(new URL("http://" + _resourceManagerAddress), String - .format("/ws/v1/cluster/apps?finalStatus=SUCCEEDED&finishedTimeBegin=%s&finishedTimeEnd=%s", - String.valueOf(_lastTime), String.valueOf(_currentTime))); + URL appsURL = + new URL(new URL("http://" + _resourceManagerAddress), String.format( + "/ws/v1/cluster/apps?finalStatus=SUCCEEDED&finishedTimeBegin=%s&finishedTimeEnd=%s", + String.valueOf(_lastTime + 1), String.valueOf(_currentTime))); - logger.info("Fetching recent finished application runs between last time: " + _lastTime + ", and current time: " - + _currentTime); + logger.info("Fetching recent finished application runs between last time: " + (_lastTime + 1) + + ", and current time: " + _currentTime); JsonNode rootNode = readJsonNode(appsURL); JsonNode apps = rootNode.path("apps").path("app"); From 5f81420a681671f887386d355cd6d2809b77ad76 Mon Sep 17 00:00:00 2001 From: Fangshi Li Date: Mon, 29 Jun 2015 14:24:36 -0700 Subject: [PATCH 081/272] HADOOP-12247 - Update Dr. Elephant version to v1.0.1-SNAPSHOT --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 94ab15464..9fea99616 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ import play.Project._ name := "dr-elephant" -version := "1.0.0" +version := "1.0.1-SNAPSHOT" javacOptions in Compile ++= Seq("-source", "1.6", "-target", "1.6") From 49a80d8273b60b892c2f062b495090280e6be367 Mon Sep 17 00:00:00 2001 From: Fangshi Li Date: Mon, 29 Jun 2015 15:43:38 -0700 Subject: [PATCH 082/272] HADOOP-11913: Fix Dr. Elephant broken tests --- .../linkedin/drelephant/ElephantRunner.java | 6 +++- .../drelephant/analysis/HDFSContext.java | 29 +++++++++++++++++++ .../analysis/HadoopSystemContext.java | 24 --------------- .../mapreduce/MapReduceFetcherHadoop1.java | 4 +-- .../heuristics/GenericDataSkewHeuristic.java | 6 ++-- .../heuristics/MapperSpeedHeuristic.java | 7 +++-- compile.sh | 2 +- .../JobQueueLimitHeuristicTest.java | 2 +- .../MapperDataSkewHeuristicTest.java | 6 ++-- .../heuristics/MapperSpeedHeuristicTest.java | 8 +++-- .../heuristics/MapperSpillHeuristicTest.java | 3 +- .../ReducerDataSkewHeuristicTest.java | 6 ++-- .../heuristics/ShuffleSortHeuristicTest.java | 3 +- 13 files changed, 61 insertions(+), 45 deletions(-) create mode 100644 app/com/linkedin/drelephant/analysis/HDFSContext.java diff --git a/app/com/linkedin/drelephant/ElephantRunner.java b/app/com/linkedin/drelephant/ElephantRunner.java index 646498b85..2149e9589 100644 --- a/app/com/linkedin/drelephant/ElephantRunner.java +++ b/app/com/linkedin/drelephant/ElephantRunner.java @@ -3,8 +3,10 @@ import com.linkedin.drelephant.analysis.AnalyticJob; import com.linkedin.drelephant.analysis.AnalyticJobGenerator; import com.linkedin.drelephant.analysis.AnalyticJobGeneratorHadoop1; +import com.linkedin.drelephant.analysis.HDFSContext; import com.linkedin.drelephant.analysis.HadoopSystemContext; import com.linkedin.drelephant.analysis.AnalyticJobGeneratorHadoop2; + import java.io.IOException; import java.security.PrivilegedAction; import java.util.List; @@ -13,7 +15,9 @@ import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicBoolean; + import model.JobResult; + import org.apache.commons.lang.exception.ExceptionUtils; import org.apache.hadoop.mapred.JobConf; import org.apache.log4j.Logger; @@ -58,7 +62,7 @@ public void run() { @Override public Void run() { //TODO: We need to catch exception here - HadoopSystemContext.load(); + HDFSContext.load(); loadAnalyticJobGenerator(); ElephantContext.init(); diff --git a/app/com/linkedin/drelephant/analysis/HDFSContext.java b/app/com/linkedin/drelephant/analysis/HDFSContext.java new file mode 100644 index 000000000..1f6b32926 --- /dev/null +++ b/app/com/linkedin/drelephant/analysis/HDFSContext.java @@ -0,0 +1,29 @@ +package com.linkedin.drelephant.analysis; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.FileSystem; +import org.apache.hadoop.fs.Path; +import org.apache.log4j.Logger; + +import java.io.IOException; + +public final class HDFSContext { + private static final Logger logger = Logger.getLogger(HDFSContext.class); + + public static long HDFS_BLOCK_SIZE = 64 * 1024 * 1024; + public static final long DISK_READ_SPEED = 100 * 1024 * 1024; + + private HDFSContext() { + // Empty on purpose + } + + public static void load() { + try { + HDFS_BLOCK_SIZE = FileSystem.get(new Configuration()).getDefaultBlockSize(new Path("/")); + } catch (IOException e) { + logger.error("Error getting FS Block Size!", e); + } + + logger.info("HDFS BLock size: " + HDFS_BLOCK_SIZE); + } +} diff --git a/app/com/linkedin/drelephant/analysis/HadoopSystemContext.java b/app/com/linkedin/drelephant/analysis/HadoopSystemContext.java index ba991277f..01c725075 100644 --- a/app/com/linkedin/drelephant/analysis/HadoopSystemContext.java +++ b/app/com/linkedin/drelephant/analysis/HadoopSystemContext.java @@ -1,39 +1,15 @@ package com.linkedin.drelephant.analysis; import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.fs.FileSystem; -import org.apache.hadoop.fs.Path; import org.apache.hadoop.mapred.JobConf; -import org.apache.log4j.Logger; - -import java.io.IOException; public final class HadoopSystemContext { - private static final Logger logger = Logger.getLogger(HadoopSystemContext.class); private static final String MAPREDUCE_FRAMEWORK_NAME_PROP = "mapreduce.framework.name"; private static final String MAPRED_JOB_TRACKER_PROP = "mapred.job.tracker.http.address"; private static final String YARN = "yarn"; private static final String CLASSIC = "classic"; - public static long HDFS_BLOCK_SIZE = 64 * 1024 * 1024; - public static final long DISK_READ_SPEED = 100 * 1024 * 1024; - public static final int SHUFFLE_SORT_MAX_SAMPLE_SIZE = 50; - - private HadoopSystemContext() { - // Empty on purpose - } - - public static void load() { - try { - HDFS_BLOCK_SIZE = FileSystem.get(new Configuration()).getDefaultBlockSize(new Path("/")); - } catch (IOException e) { - logger.error("Error getting FS Block Size!", e); - } - - logger.info("HDFS BLock size: " + HDFS_BLOCK_SIZE); - } - /** * Detect if the current Hadoop environment is 1.x * diff --git a/app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop1.java b/app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop1.java index 69708fc4e..c38a2c7c8 100644 --- a/app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop1.java +++ b/app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop1.java @@ -1,7 +1,6 @@ package com.linkedin.drelephant.mapreduce; import com.linkedin.drelephant.analysis.ElephantFetcher; -import com.linkedin.drelephant.analysis.HadoopSystemContext; import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder.CounterName; import com.linkedin.drelephant.math.Statistics; import com.linkedin.drelephant.util.Utils; @@ -38,6 +37,7 @@ public class MapReduceFetcherHadoop1 implements ElephantFetcher { private static final Logger logger = Logger.getLogger(ElephantFetcher.class); + private static final int SHUFFLE_SORT_MAX_SAMPLE_SIZE = 50; private static final int DEFAULT_RETRY = 2; private JobConf _conf; @@ -96,7 +96,7 @@ public MapReduceApplicationData fetchData(String id) // Fetch job counter MapReduceCounterHolder counterHolder = fetchCounter(job.getCounters()); - int sampleSize = HadoopSystemContext.SHUFFLE_SORT_MAX_SAMPLE_SIZE; + int sampleSize = SHUFFLE_SORT_MAX_SAMPLE_SIZE; // Fetch mapper task data List mapperList = new ArrayList(); diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/GenericDataSkewHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericDataSkewHeuristic.java index 31a94dbfa..05cfae461 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/GenericDataSkewHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericDataSkewHeuristic.java @@ -1,5 +1,6 @@ package com.linkedin.drelephant.mapreduce.heuristics; +import com.linkedin.drelephant.analysis.HDFSContext; import com.linkedin.drelephant.analysis.HadoopSystemContext; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; @@ -8,6 +9,7 @@ import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; import com.linkedin.drelephant.mapreduce.MapReduceTaskData; import com.linkedin.drelephant.math.Statistics; + import org.apache.commons.io.FileUtils; @@ -73,7 +75,7 @@ public static Severity getDeviationSeverity(long averageMin, long averageDiff) { } public static Severity getFilesSeverity(long value) { - return Severity.getSeverityAscending(value, HadoopSystemContext.HDFS_BLOCK_SIZE / 8, HadoopSystemContext.HDFS_BLOCK_SIZE / 4, - HadoopSystemContext.HDFS_BLOCK_SIZE / 2, HadoopSystemContext.HDFS_BLOCK_SIZE); + return Severity.getSeverityAscending(value, HDFSContext.HDFS_BLOCK_SIZE / 8, HDFSContext.HDFS_BLOCK_SIZE / 4, + HDFSContext.HDFS_BLOCK_SIZE / 2, HDFSContext.HDFS_BLOCK_SIZE); } } diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristic.java index def6bfcb8..0000ad6ae 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristic.java @@ -2,10 +2,11 @@ import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; + import java.util.ArrayList; import java.util.List; -import com.linkedin.drelephant.analysis.HadoopSystemContext; +import com.linkedin.drelephant.analysis.HDFSContext; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; @@ -72,8 +73,8 @@ public HeuristicResult apply(MapReduceApplicationData data) { } public static Severity getDiskSpeedSeverity(long speed) { - return Severity.getSeverityDescending(speed, HadoopSystemContext.DISK_READ_SPEED / 2, HadoopSystemContext.DISK_READ_SPEED / 4, - HadoopSystemContext.DISK_READ_SPEED / 8, HadoopSystemContext.DISK_READ_SPEED / 32); + return Severity.getSeverityDescending(speed, HDFSContext.DISK_READ_SPEED / 2, HDFSContext.DISK_READ_SPEED / 4, + HDFSContext.DISK_READ_SPEED / 8, HDFSContext.DISK_READ_SPEED / 32); } public static Severity getRuntimeSeverity(long runtimeMs) { diff --git a/compile.sh b/compile.sh index 69ec39506..f04bcae0a 100755 --- a/compile.sh +++ b/compile.sh @@ -13,7 +13,7 @@ stop_script=${project_root}/stop.sh rm -rf ${project_root}/dist mkdir dist -play clean compile dist +play clean test compile dist cd target/universal diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/JobQueueLimitHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/JobQueueLimitHeuristicTest.java index b3af22cf3..2123265d4 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/JobQueueLimitHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/JobQueueLimitHeuristicTest.java @@ -19,7 +19,7 @@ public class JobQueueLimitHeuristicTest extends TestCase { Heuristic _heuristic = new JobQueueLimitHeuristic(); - private static final int NUM_TASKS = HadoopSystemContext.SHUFFLE_SORT_MAX_SAMPLE_SIZE; + private static final int NUM_TASKS = 100; @Test public void testRuntimeCritical() throws IOException { diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperDataSkewHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperDataSkewHeuristicTest.java index 7e5d8b936..2a78dfcfb 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperDataSkewHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperDataSkewHeuristicTest.java @@ -1,19 +1,21 @@ package com.linkedin.drelephant.mapreduce.heuristics; -import com.linkedin.drelephant.analysis.HadoopSystemContext; +import com.linkedin.drelephant.analysis.HDFSContext; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; import com.linkedin.drelephant.mapreduce.MapReduceTaskData; + import java.io.IOException; + import junit.framework.TestCase; public class MapperDataSkewHeuristicTest extends TestCase { - private static final long UNITSIZE = HadoopSystemContext.HDFS_BLOCK_SIZE / 64; + private static final long UNITSIZE = HDFSContext.HDFS_BLOCK_SIZE / 64; //1MB Heuristic _heuristic = new MapperDataSkewHeuristic(); public void testCritical() throws IOException { diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristicTest.java index fc5cdd145..5de568353 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristicTest.java @@ -1,6 +1,6 @@ package com.linkedin.drelephant.mapreduce.heuristics; -import com.linkedin.drelephant.analysis.HadoopSystemContext; +import com.linkedin.drelephant.analysis.HDFSContext; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; @@ -8,15 +8,17 @@ import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; import com.linkedin.drelephant.mapreduce.MapReduceTaskData; import com.linkedin.drelephant.math.Statistics; + import java.io.IOException; + import junit.framework.TestCase; public class MapperSpeedHeuristicTest extends TestCase { Heuristic _heuristic = new MapperSpeedHeuristic(); - private static final long UNITSIZE = HadoopSystemContext.HDFS_BLOCK_SIZE / 64; + private static final long UNITSIZE = HDFSContext.HDFS_BLOCK_SIZE / 64; private static final long MINUTE_IN_MS = Statistics.MINUTE_IN_MS; - private static final int NUMTASKS = HadoopSystemContext.SHUFFLE_SORT_MAX_SAMPLE_SIZE; + private static final int NUMTASKS = 100; public void testCritical() throws IOException { assertEquals(Severity.CRITICAL, analyzeJob(120 * MINUTE_IN_MS, 10000 * UNITSIZE)); diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristicTest.java index d4862a533..f88258ea8 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristicTest.java @@ -1,6 +1,5 @@ package com.linkedin.drelephant.mapreduce.heuristics; -import com.linkedin.drelephant.analysis.HadoopSystemContext; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; @@ -14,7 +13,7 @@ public class MapperSpillHeuristicTest extends TestCase { Heuristic heuristic = new MapperSpillHeuristic(); - private static final int numTasks = HadoopSystemContext.SHUFFLE_SORT_MAX_SAMPLE_SIZE; + private static final int numTasks = 100; public void testCritical() throws IOException { assertEquals(Severity.CRITICAL, analyzeJob(2200, 1000)); diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerDataSkewHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerDataSkewHeuristicTest.java index 0f307b3b4..d54c6fa8a 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerDataSkewHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerDataSkewHeuristicTest.java @@ -1,18 +1,20 @@ package com.linkedin.drelephant.mapreduce.heuristics; -import com.linkedin.drelephant.analysis.HadoopSystemContext; +import com.linkedin.drelephant.analysis.HDFSContext; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; import com.linkedin.drelephant.mapreduce.MapReduceTaskData; + import java.io.IOException; + import junit.framework.TestCase; public class ReducerDataSkewHeuristicTest extends TestCase { - private static final long UNITSIZE = HadoopSystemContext.HDFS_BLOCK_SIZE / 64; + private static final long UNITSIZE = HDFSContext.HDFS_BLOCK_SIZE / 64; //1mb Heuristic _heuristic = new ReducerDataSkewHeuristic(); public void testCritical() throws IOException { diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristicTest.java index 9b9781032..6f60a54d1 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristicTest.java @@ -2,7 +2,6 @@ import java.io.IOException; -import com.linkedin.drelephant.analysis.HadoopSystemContext; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; @@ -16,7 +15,7 @@ public class ShuffleSortHeuristicTest extends TestCase { Heuristic _heuristic = new ShuffleSortHeuristic(); - private static final int NUMTASKS = HadoopSystemContext.SHUFFLE_SORT_MAX_SAMPLE_SIZE; + private static final int NUMTASKS = 100; private static final long MINUTE_IN_MS = Statistics.MINUTE_IN_MS;; public void testLongShuffleCritical() throws IOException { From 31eae86e538d4a7f33062292a18b2631ac22090c Mon Sep 17 00:00:00 2001 From: Fangshi Li Date: Mon, 29 Jun 2015 16:17:03 -0700 Subject: [PATCH 083/272] HADOOP-12067: Dr. Elephant heuristic is too lenient to jobs with long running tasks --- .../heuristics/MapperTimeHeuristic.java | 18 ++--- .../heuristics/ReducerTimeHeuristic.java | 16 ++-- .../JobQueueLimitHeuristicTest.java | 1 - .../heuristics/MapperTimeHeuristicTest.java | 75 +++---------------- .../heuristics/ReducerTimeHeuristicTest.java | 17 ++--- 5 files changed, 26 insertions(+), 101 deletions(-) diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristic.java index a085de5b5..e58075593 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristic.java @@ -5,7 +5,6 @@ import java.util.ArrayList; import java.util.List; -import com.linkedin.drelephant.analysis.HadoopSystemContext; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; @@ -62,28 +61,21 @@ private Severity shortTaskSeverity(long numTasks, long averageTimeMs) { } private Severity longTaskSeverity(long numTasks, long averageTimeMs) { - // We want to identify jobs with long task runtime - Severity severity = getLongRuntimeSeverity(averageTimeMs); - // Severity is reduced if number of tasks is large - Severity numTaskSeverity = getNumTasksSeverityReverse(numTasks); - return Severity.min(severity, numTaskSeverity); + // We want to identify jobs with long task runtime. Severity is NOT reduced if num of tasks is large + return getLongRuntimeSeverity(averageTimeMs); } - public static Severity getShortRuntimeSeverity(long runtimeMs) { + private Severity getShortRuntimeSeverity(long runtimeMs) { return Severity.getSeverityDescending(runtimeMs, 10 * Statistics.MINUTE_IN_MS, 4 * Statistics.MINUTE_IN_MS, 2 * Statistics.MINUTE_IN_MS, 1 * Statistics.MINUTE_IN_MS); } - public static Severity getLongRuntimeSeverity(long runtimeMs) { + private Severity getLongRuntimeSeverity(long runtimeMs) { return Severity.getSeverityAscending(runtimeMs, 15 * Statistics.MINUTE_IN_MS, 30 * Statistics.MINUTE_IN_MS, 1 * Statistics.HOUR_IN_MS, 2 * Statistics.HOUR_IN_MS); } - public static Severity getNumTasksSeverity(long numTasks) { + private Severity getNumTasksSeverity(long numTasks) { return Severity.getSeverityAscending(numTasks, 50, 101, 500, 1000); } - - public static Severity getNumTasksSeverityReverse(long numTasks) { - return Severity.getSeverityDescending(numTasks, 500, 100, 50, 10); - } } diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristic.java index 4ad1d5055..647ceef75 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristic.java @@ -54,27 +54,21 @@ private Severity shortTimeSeverity(long runtimeMs, long numTasks) { } private Severity longTimeSeverity(long runtimeMs, long numTasks) { - Severity timeSeverity = getLongRuntimeSeverity(runtimeMs); - // Severity is adjusted based on number of tasks - Severity taskSeverity = getNumTasksSeverityReverse(numTasks); - return Severity.min(timeSeverity, taskSeverity); + // Severity is NOT adjusted based on number of tasks + return getLongRuntimeSeverity(runtimeMs); } - public static Severity getShortRuntimeSeverity(long runtimeMs) { + private Severity getShortRuntimeSeverity(long runtimeMs) { return Severity.getSeverityDescending(runtimeMs, 10 * Statistics.MINUTE_IN_MS, 4 * Statistics.MINUTE_IN_MS, 2 * Statistics.MINUTE_IN_MS, 1 * Statistics.MINUTE_IN_MS); } - public static Severity getLongRuntimeSeverity(long runtimeMs) { + private Severity getLongRuntimeSeverity(long runtimeMs) { return Severity.getSeverityAscending(runtimeMs, 15 * Statistics.MINUTE_IN_MS, 30 * Statistics.MINUTE_IN_MS, 1 * Statistics.HOUR_IN_MS, 2 * Statistics.HOUR_IN_MS); } - public static Severity getNumTasksSeverity(long numTasks) { + private Severity getNumTasksSeverity(long numTasks) { return Severity.getSeverityAscending(numTasks, 50, 101, 500, 1000); } - - public static Severity getNumTasksSeverityReverse(long numTasks) { - return Severity.getSeverityDescending(numTasks, 500, 100, 50, 10); - } } diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/JobQueueLimitHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/JobQueueLimitHeuristicTest.java index 2123265d4..5bb38a7f1 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/JobQueueLimitHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/JobQueueLimitHeuristicTest.java @@ -7,7 +7,6 @@ import junit.framework.TestCase; -import com.linkedin.drelephant.analysis.HadoopSystemContext; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristicTest.java index 73ef3d661..39f62eadb 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristicTest.java @@ -1,6 +1,5 @@ package com.linkedin.drelephant.mapreduce.heuristics; -import com.linkedin.drelephant.analysis.HadoopSystemContext; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; @@ -17,30 +16,26 @@ public class MapperTimeHeuristicTest extends TestCase { private static final long DUMMY_INPUT_SIZE = 0; Heuristic _heuristic = new MapperTimeHeuristic(); - // Test batch 1: Large runtime and various number of tasks */ + // Test batch 1: Large runtime. Heuristic is not affected by various number of tasks */ public void testLongRuntimeTasksCritical() throws IOException { // Should decrease split size and increase number of tasks assertEquals(Severity.CRITICAL, analyzeJob(10, 120 * Statistics.MINUTE_IN_MS)); } - public void testLongRuntimeTasksSevere() throws IOException { + public void testLongRuntimeTasksCriticalMore() throws IOException { // Should decrease split size and increase number of tasks - assertEquals(Severity.SEVERE, analyzeJob(50, 120 * Statistics.MINUTE_IN_MS)); - } - - public void testLongRuntimeTasksModerate() throws IOException { - assertEquals(Severity.MODERATE, analyzeJob(100, 120 * Statistics.MINUTE_IN_MS)); + assertEquals(Severity.CRITICAL, analyzeJob(1000, 120 * Statistics.MINUTE_IN_MS)); } - public void testLongRuntimeTasksLow() throws IOException { - assertEquals(Severity.LOW, analyzeJob(500, 120 * Statistics.MINUTE_IN_MS)); + public void testLongRuntimeTasksSevere() throws IOException { + // Should decrease split size and increase number of tasks + assertEquals(Severity.SEVERE, analyzeJob(10, 60 * Statistics.MINUTE_IN_MS)); } - public void testLongRuntimeTasksNone() throws IOException { - // Large file with large number of tasks and long runtime. - // Looks horrible but nothing we could suggest in this case (Probably suggest something other than hadoop?) - assertEquals(Severity.NONE, analyzeJob(1000, 120 * Statistics.MINUTE_IN_MS)); + public void testLongRuntimeTasksSevereMore() throws IOException { + // Should decrease split size and increase number of tasks + assertEquals(Severity.SEVERE, analyzeJob(1000, 60 * Statistics.MINUTE_IN_MS)); } // Test batch 2: Short runtime and various number of tasks @@ -68,58 +63,6 @@ public void testShortRuntimeTasksNone() throws IOException { assertEquals(Severity.NONE, analyzeJob(5, 1 * Statistics.MINUTE_IN_MS)); } - // Test batch 3: Large number of tasks and various runtime - - public void testLargeNumTasksCritical() throws IOException { - // Should increase split size and decrease number of tasks - assertEquals(Severity.CRITICAL, analyzeJob(1000, 1 * Statistics.MINUTE_IN_MS)); - } - - public void testLargeNumTasksSevere() throws IOException { - // Should increase split size and decrease number of tasks - assertEquals(Severity.SEVERE, analyzeJob(1000, 2 * Statistics.MINUTE_IN_MS)); - } - - public void testLargeNumTasksModerate() throws IOException { - assertEquals(Severity.MODERATE, analyzeJob(1000, 4 * Statistics.MINUTE_IN_MS)); - } - - public void testLargeNumTasksLow() throws IOException { - // This should be common case - assertEquals(Severity.LOW, analyzeJob(1000, 5 * Statistics.MINUTE_IN_MS)); - } - - public void testLargeNumTasksNone() throws IOException { - // This should be a common case - assertEquals(Severity.NONE, analyzeJob(1000, 12 * Statistics.MINUTE_IN_MS)); - } - - // Test batch 4: Small number of tasks and various runtime - - public void testSmallNumTasksCritical() throws IOException { - // Should decrease split size and increase number of tasks - assertEquals(Severity.CRITICAL, analyzeJob(10, 120 * Statistics.MINUTE_IN_MS)); - } - - public void testSmallNumTasksSevere() throws IOException { - // Should decrease split size and increase number of tasks - assertEquals(Severity.SEVERE, analyzeJob(10, 60 * Statistics.MINUTE_IN_MS)); - } - - public void testSmallNumTasksModerate() throws IOException { - assertEquals(Severity.MODERATE, analyzeJob(10, 30 * Statistics.MINUTE_IN_MS)); - } - - public void testSmallNumTasksLow() throws IOException { - // This should be common case - assertEquals(Severity.LOW, analyzeJob(10, 15 * Statistics.MINUTE_IN_MS)); - } - - public void testSmallNumTasksNone() throws IOException { - // This should be a common case - assertEquals(Severity.NONE, analyzeJob(10, 12 * Statistics.MINUTE_IN_MS)); - } - private Severity analyzeJob(int numTasks, long runtime) throws IOException { MapReduceCounterHolder jobCounter = new MapReduceCounterHolder(); MapReduceTaskData[] mappers = new MapReduceTaskData[numTasks]; diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristicTest.java index a8849a595..81e7516b5 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristicTest.java @@ -41,20 +41,17 @@ public void testLongRunetimeCritical() throws IOException { assertEquals(Severity.CRITICAL, analyzeJob(120 * MINUTE_IN_MS, 10)); } - public void testLongRunetimeSevere() throws IOException { - assertEquals(Severity.SEVERE, analyzeJob(120 * MINUTE_IN_MS, 50)); - } - - public void testLongRunetimeModerate() throws IOException { - assertEquals(Severity.MODERATE, analyzeJob(120 * MINUTE_IN_MS, 100)); + // Long runtime severity is not affected by number of tasks + public void testLongRunetimeCriticalMore() throws IOException { + assertEquals(Severity.CRITICAL, analyzeJob(120 * MINUTE_IN_MS, 1000)); } - public void testLongRunetimeLow() throws IOException { - assertEquals(Severity.LOW, analyzeJob(120 * MINUTE_IN_MS, 500)); + public void testLongRunetimeSevere() throws IOException { + assertEquals(Severity.SEVERE, analyzeJob(60 * MINUTE_IN_MS, 10)); } - public void testLongRunetimeNone() throws IOException { - assertEquals(Severity.NONE, analyzeJob(120 * MINUTE_IN_MS, 1000)); + public void testLongRunetimeSevereMore() throws IOException { + assertEquals(Severity.SEVERE, analyzeJob(60 * MINUTE_IN_MS, 1000)); } private Severity analyzeJob(long runtimeMs, int numTasks) throws IOException { From 8e8ef383946ff6c7d8de3aa1e9d6cd5f36b411ed Mon Sep 17 00:00:00 2001 From: Fangshi Li Date: Wed, 1 Jul 2015 11:03:26 -0700 Subject: [PATCH 084/272] HADOOP-12023: Dr. Elephagent new heuristics: memory util ratio --- .../heuristics/GenericMemoryHeuristic.java | 84 +++++++++++++++++++ .../heuristics/MapperMemoryHeuristic.java | 24 ++++++ .../heuristics/MapperTimeHeuristic.java | 13 ++- .../heuristics/ReducerMemoryHeuristic.java | 24 ++++++ .../heuristics/ReducerTimeHeuristic.java | 16 +++- app/views/helpMapperMemory.scala.html | 1 + app/views/helpMemory.scala.html | 57 +++++++++++++ app/views/helpReducerMemory.scala.html | 1 + .../heuristics/MapperMemoryHeuristicTest.java | 67 +++++++++++++++ .../ReducerMemoryHeuristicTest.java | 67 +++++++++++++++ .../heuristics/MemoryLimitHeuristicTest.java | 1 - 11 files changed, 350 insertions(+), 5 deletions(-) create mode 100644 app/com/linkedin/drelephant/mapreduce/heuristics/GenericMemoryHeuristic.java create mode 100644 app/com/linkedin/drelephant/mapreduce/heuristics/MapperMemoryHeuristic.java create mode 100644 app/com/linkedin/drelephant/mapreduce/heuristics/ReducerMemoryHeuristic.java create mode 100644 app/views/helpMapperMemory.scala.html create mode 100644 app/views/helpMemory.scala.html create mode 100644 app/views/helpReducerMemory.scala.html create mode 100644 test/com/linkedin/drelephant/mapreduce/heuristics/MapperMemoryHeuristicTest.java create mode 100644 test/com/linkedin/drelephant/mapreduce/heuristics/ReducerMemoryHeuristicTest.java diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/GenericMemoryHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericMemoryHeuristic.java new file mode 100644 index 000000000..2fa45ff1d --- /dev/null +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericMemoryHeuristic.java @@ -0,0 +1,84 @@ +package com.linkedin.drelephant.mapreduce.heuristics; + +import java.util.ArrayList; +import java.util.List; +import com.linkedin.drelephant.analysis.Heuristic; +import com.linkedin.drelephant.analysis.HeuristicResult; +import com.linkedin.drelephant.analysis.Severity; +import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; +import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.MapReduceTaskData; +import com.linkedin.drelephant.math.Statistics; + +import org.apache.commons.io.FileUtils; + + +public abstract class GenericMemoryHeuristic implements Heuristic { + private String _containerMemConf; + private String _heuristicName; + private long CONTAINER_MEMORY_DEFAULT_BYTES = 2048L * FileUtils.ONE_MB; + + @Override + public String getHeuristicName() { + return _heuristicName; + } + + protected GenericMemoryHeuristic(String containerMemConf, String heuristicName) { + this._heuristicName = heuristicName; + this._containerMemConf = containerMemConf; + } + + protected abstract MapReduceTaskData[] getTasks(MapReduceApplicationData data); + + @Override + public HeuristicResult apply(MapReduceApplicationData data) { + long containerMem = Long.parseLong(data.getConf().getProperty(_containerMemConf)) * FileUtils.ONE_MB; + + MapReduceTaskData[] tasks = getTasks(data); + List taskMems = new ArrayList(); + long taskMin = Long.MAX_VALUE; + long taskMax = 0; + for (MapReduceTaskData task : tasks) { + long taskMem = task.getCounters().get(MapReduceCounterHolder.CounterName.PHYSICAL_MEMORY_BYTES); + taskMems.add(taskMem); + taskMin = Math.min(taskMin, taskMem); + taskMax = Math.max(taskMax, taskMem); + } + + if(taskMin == Long.MAX_VALUE) { + taskMin = 0; + } + + long taskAvg = Statistics.average(taskMems); + + Severity severity = getTaskMemoryUtilSeverity(taskAvg, containerMem); + + HeuristicResult result = new HeuristicResult(_heuristicName, severity); + + result.addDetail("Number of tasks", Integer.toString(tasks.length)); + result.addDetail("Avg Physical Memory (MB)", Long.toString(taskAvg/FileUtils.ONE_MB)); + result.addDetail("Max Physical Memory (MB)", Long.toString(taskMax/FileUtils.ONE_MB)); + result.addDetail("Min Physical Memory (MB)", Long.toString(taskMin/FileUtils.ONE_MB)); + result.addDetail("Requested Container Memory", FileUtils.byteCountToDisplaySize(containerMem)); + + return result; + } + + private Severity getTaskMemoryUtilSeverity(long taskMemAvg, long taskMemMax) { + double ratio = ((double)taskMemAvg) / taskMemMax; + Severity sevRatio = getMemoryRatioSeverity(ratio); + // Severity is reduced if the requested container memory is close to default + Severity sevMax = getContainerMemorySeverity(taskMemMax); + + return Severity.min(sevRatio, sevMax); + } + + private Severity getContainerMemorySeverity(long taskMemMax) { + return Severity.getSeverityAscending(taskMemMax, CONTAINER_MEMORY_DEFAULT_BYTES * 1.1, + CONTAINER_MEMORY_DEFAULT_BYTES * 1.5, CONTAINER_MEMORY_DEFAULT_BYTES * 2, CONTAINER_MEMORY_DEFAULT_BYTES * 2.5); + } + + private Severity getMemoryRatioSeverity(double ratio) { + return Severity.getSeverityDescending(ratio, 0.6, 0.5, 0.4, 0.3); + } +} diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperMemoryHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperMemoryHeuristic.java new file mode 100644 index 000000000..6be9051d7 --- /dev/null +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperMemoryHeuristic.java @@ -0,0 +1,24 @@ +package com.linkedin.drelephant.mapreduce.heuristics; + +import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.MapReduceTaskData; + + +public class MapperMemoryHeuristic extends GenericMemoryHeuristic { + public static final String HEURISTIC_NAME = "Mapper Memory"; + public static final String MAPPER_MEMORY_CONF = "mapreduce.map.memory.mb"; + + public MapperMemoryHeuristic() { + super(MAPPER_MEMORY_CONF, HEURISTIC_NAME); + } + + @Override + public String getHeuristicName() { + return HEURISTIC_NAME; + } + + @Override + protected MapReduceTaskData[] getTasks(MapReduceApplicationData data) { + return data.getMapperData(); + } +} diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristic.java index e58075593..c63fc650f 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristic.java @@ -28,14 +28,23 @@ public HeuristicResult apply(MapReduceApplicationData data) { List inputBytes = new ArrayList(); List runtimesMs = new ArrayList(); + long taskMinMs = Long.MAX_VALUE; + long taskMaxMs = 0; for (MapReduceTaskData task : tasks) { inputBytes.add(task.getCounters().get(MapReduceCounterHolder.CounterName.HDFS_BYTES_READ)); if (task.timed()) { - runtimesMs.add(task.getTotalRunTimeMs()); + long taskTime = task.getTotalRunTimeMs(); + runtimesMs.add(taskTime); + taskMinMs = Math.min(taskMinMs, taskTime); + taskMaxMs = Math.max(taskMaxMs, taskTime); } } + if(taskMinMs == Long.MAX_VALUE) { + taskMinMs = 0; + } + long averageSize = Statistics.average(inputBytes); long averageTimeMs = Statistics.average(runtimesMs); @@ -48,6 +57,8 @@ public HeuristicResult apply(MapReduceApplicationData data) { result.addDetail("Number of tasks", Integer.toString(tasks.length)); result.addDetail("Average task input size", FileUtils.byteCountToDisplaySize(averageSize)); result.addDetail("Average task runtime", Statistics.readableTimespan(averageTimeMs)); + result.addDetail("Max task runtime", Statistics.readableTimespan(taskMaxMs)); + result.addDetail("Min task runtime", Statistics.readableTimespan(taskMinMs)); return result; } diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerMemoryHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerMemoryHeuristic.java new file mode 100644 index 000000000..167fedcc0 --- /dev/null +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerMemoryHeuristic.java @@ -0,0 +1,24 @@ +package com.linkedin.drelephant.mapreduce.heuristics; + +import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.MapReduceTaskData; + + +public class ReducerMemoryHeuristic extends GenericMemoryHeuristic { + public static final String HEURISTIC_NAME = "Reducer Memory"; + public static final String REDUCER_MEMORY_CONF = "mapreduce.reduce.memory.mb"; + + public ReducerMemoryHeuristic() { + super(REDUCER_MEMORY_CONF, HEURISTIC_NAME); + } + + @Override + public String getHeuristicName() { + return HEURISTIC_NAME; + } + + @Override + protected MapReduceTaskData[] getTasks(MapReduceApplicationData data) { + return data.getReducerData(); + } +} diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristic.java index 647ceef75..bb1d0cc1c 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristic.java @@ -24,13 +24,22 @@ public HeuristicResult apply(MapReduceApplicationData data) { MapReduceTaskData[] tasks = data.getReducerData(); List runTimesMs = new ArrayList(); + long taskMinMs = Long.MAX_VALUE; + long taskMaxMs = 0; for (MapReduceTaskData task : tasks) { if (task.timed()) { - runTimesMs.add(task.getTotalRunTimeMs()); + long taskTime = task.getTotalRunTimeMs(); + runTimesMs.add(taskTime); + taskMinMs = Math.min(taskMinMs, taskTime); + taskMaxMs = Math.max(taskMaxMs, taskTime); } } + if(taskMinMs == Long.MAX_VALUE) { + taskMinMs = 0; + } + //Analyze data long averageRuntimeMs = Statistics.average(runTimesMs); @@ -41,8 +50,9 @@ public HeuristicResult apply(MapReduceApplicationData data) { HeuristicResult result = new HeuristicResult(HEURISTIC_NAME, severity); result.addDetail("Number of tasks", Integer.toString(tasks.length)); - result.addDetail("Average task time", Statistics.readableTimespan(averageRuntimeMs)); - + result.addDetail("Average task runtime", Statistics.readableTimespan(averageRuntimeMs)); + result.addDetail("Max task runtime", Statistics.readableTimespan(taskMaxMs)); + result.addDetail("Min task runtime", Statistics.readableTimespan(taskMinMs)); return result; } diff --git a/app/views/helpMapperMemory.scala.html b/app/views/helpMapperMemory.scala.html new file mode 100644 index 000000000..4bb6bb029 --- /dev/null +++ b/app/views/helpMapperMemory.scala.html @@ -0,0 +1 @@ +@helpMemory() \ No newline at end of file diff --git a/app/views/helpMemory.scala.html b/app/views/helpMemory.scala.html new file mode 100644 index 000000000..8b5e284bf --- /dev/null +++ b/app/views/helpMemory.scala.html @@ -0,0 +1,57 @@ +

    + This analysis shows the task memory utilization.
    + We check the ratio between your task's consumed memory AND the requested container memory.
    + The consumed memory is the average of each task's [max consumed physical memory snapshot].
    + The requested container memory is the "mapreduce.map/reduce.memory.mb" config for this job, which is the max physical memory the job can request.
    + If this heuristic is above MODERATE, it means your used memory is relatively low compared with the requested max memory. +

    + +
    Example
    +

    +

    +

    +

    Suggestions

    +

    + +
    + The problem indicates you requested large task memory (set mapreduce.map(or reduce).memory.mb > 2048), but the task average used physical memory is low. +
    + You should try to decrease mapreduce.map(or reduce).memory.mb. +
    + If you get OutOfMemory error, think about why such error happens before increase the memory back again. +
    + For example, see if there is any mapper/reducer input data skew (some task processes larger input than others). +
    + This is a new heuristic. We are still working on that. Any feedback to hadoop-dev is welcomed! +
    + + Examples on how to set the parameter: +

      +
    • HadoopJava: conf.setLong("mapreduce.map(or reduce).memory.mb",4096)
    • +
    • Pig: set mapreduce.map(or reduce).memory.mb 4096
    • +
    • Hive: set mapreduce.map(or reduce).memory.mb=4096
    • +
    + +
    + See Hadoop Tuning Tips for further information.
    diff --git a/app/views/helpReducerMemory.scala.html b/app/views/helpReducerMemory.scala.html new file mode 100644 index 000000000..4bb6bb029 --- /dev/null +++ b/app/views/helpReducerMemory.scala.html @@ -0,0 +1 @@ +@helpMemory() \ No newline at end of file diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperMemoryHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperMemoryHeuristicTest.java new file mode 100644 index 000000000..0fa1268ce --- /dev/null +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperMemoryHeuristicTest.java @@ -0,0 +1,67 @@ +package com.linkedin.drelephant.mapreduce.heuristics; + +import com.linkedin.drelephant.analysis.Heuristic; +import com.linkedin.drelephant.analysis.HeuristicResult; +import com.linkedin.drelephant.analysis.Severity; +import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; +import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.MapReduceTaskData; + +import java.io.IOException; +import java.util.Properties; + +import org.apache.commons.io.FileUtils; + +import junit.framework.TestCase; + + +public class MapperMemoryHeuristicTest extends TestCase { + Heuristic _heuristic = new MapperMemoryHeuristic(); + private int NUMTASKS = 100; + + public void testLargeContainerSizeCritical() throws IOException { + assertEquals(Severity.CRITICAL, analyzeJob(2048, 8192)); + } + + public void testLargeContainerSizeSevere() throws IOException { + assertEquals(Severity.SEVERE, analyzeJob(3072, 8192)); + } + + public void testLargeContainerSizeModerate() throws IOException { + assertEquals(Severity.MODERATE, analyzeJob(4096, 8192)); + } + + public void testLargeContainerSizeNone() throws IOException { + assertEquals(Severity.NONE, analyzeJob(6144, 8192)); + } + + // If the task use default container size, it should not be flagged + public void testDefaultContainerNone() throws IOException { + assertEquals(Severity.NONE, analyzeJob(256, 2048)); + } + + public void testDefaultContainerNoneMore() throws IOException { + assertEquals(Severity.NONE, analyzeJob(1024, 2048)); + } + + private Severity analyzeJob(long taskAvgMemMB, long containerMemMB) throws IOException { + MapReduceCounterHolder jobCounter = new MapReduceCounterHolder(); + MapReduceTaskData[] mappers = new MapReduceTaskData[NUMTASKS]; + + MapReduceCounterHolder counter = new MapReduceCounterHolder(); + counter.set(MapReduceCounterHolder.CounterName.PHYSICAL_MEMORY_BYTES, taskAvgMemMB* FileUtils.ONE_MB); + + Properties p = new Properties(); + p.setProperty(MapperMemoryHeuristic.MAPPER_MEMORY_CONF, Long.toString(containerMemMB)); + + int i = 0; + for (; i < NUMTASKS; i++) { + mappers[i] = new MapReduceTaskData(counter, new long[3]); + } + + MapReduceApplicationData data = new MapReduceApplicationData().setCounters(jobCounter).setMapperData(mappers); + data.setJobConf(p); + HeuristicResult result = _heuristic.apply(data); + return result.getSeverity(); + } +} diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerMemoryHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerMemoryHeuristicTest.java new file mode 100644 index 000000000..5488e5928 --- /dev/null +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerMemoryHeuristicTest.java @@ -0,0 +1,67 @@ +package com.linkedin.drelephant.mapreduce.heuristics; + +import com.linkedin.drelephant.analysis.Heuristic; +import com.linkedin.drelephant.analysis.HeuristicResult; +import com.linkedin.drelephant.analysis.Severity; +import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; +import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.MapReduceTaskData; + +import java.io.IOException; +import java.util.Properties; + +import org.apache.commons.io.FileUtils; + +import junit.framework.TestCase; + + +public class ReducerMemoryHeuristicTest extends TestCase { + Heuristic _heuristic = new ReducerMemoryHeuristic(); + private int NUMTASKS = 100; + + public void testLargeContainerSizeCritical() throws IOException { + assertEquals(Severity.CRITICAL, analyzeJob(2048, 8192)); + } + + public void testLargeContainerSizeSevere() throws IOException { + assertEquals(Severity.SEVERE, analyzeJob(3072, 8192)); + } + + public void testLargeContainerSizeModerate() throws IOException { + assertEquals(Severity.MODERATE, analyzeJob(4096, 8192)); + } + + public void testLargeContainerSizeNone() throws IOException { + assertEquals(Severity.NONE, analyzeJob(6144, 8192)); + } + + // If the task use default container size, it should not be flagged + public void testDefaultContainerNone() throws IOException { + assertEquals(Severity.NONE, analyzeJob(256, 2048)); + } + + public void testDefaultContainerNoneMore() throws IOException { + assertEquals(Severity.NONE, analyzeJob(1024, 2048)); + } + + private Severity analyzeJob(long taskAvgMemMB, long containerMemMB) throws IOException { + MapReduceCounterHolder jobCounter = new MapReduceCounterHolder(); + MapReduceTaskData[] reducers = new MapReduceTaskData[NUMTASKS]; + + MapReduceCounterHolder counter = new MapReduceCounterHolder(); + counter.set(MapReduceCounterHolder.CounterName.PHYSICAL_MEMORY_BYTES, taskAvgMemMB* FileUtils.ONE_MB); + + Properties p = new Properties(); + p.setProperty(ReducerMemoryHeuristic.REDUCER_MEMORY_CONF, Long.toString(containerMemMB)); + + int i = 0; + for (; i < NUMTASKS; i++) { + reducers[i] = new MapReduceTaskData(counter, new long[3]); + } + + MapReduceApplicationData data = new MapReduceApplicationData().setCounters(jobCounter).setReducerData(reducers); + data.setJobConf(p); + HeuristicResult result = _heuristic.apply(data); + return result.getSeverity(); + } +} diff --git a/test/com/linkedin/drelephant/spark/heuristics/MemoryLimitHeuristicTest.java b/test/com/linkedin/drelephant/spark/heuristics/MemoryLimitHeuristicTest.java index e1feafccf..db0fce48f 100644 --- a/test/com/linkedin/drelephant/spark/heuristics/MemoryLimitHeuristicTest.java +++ b/test/com/linkedin/drelephant/spark/heuristics/MemoryLimitHeuristicTest.java @@ -60,7 +60,6 @@ public void testCombinedRules() { public void testMissingSparkDriverMemoryProperty() { HeuristicResult result = getJobresult(100, "1G", "700M", getPeakMemory(1.0d, 100, "1G"), SPARK_DRIVER_MEMORY); for (String detail : result.getDetails()) { - System.out.println(detail); if (detail.startsWith("\"Total driver memory allocated")) { assertEquals("\"Total driver memory allocated\",\"700 MB\"", detail); } From e818c3581a43c26fbb49075e8e3f41d9ec6dc733 Mon Sep 17 00:00:00 2001 From: Fangshi Li Date: Fri, 10 Jul 2015 11:22:59 -0700 Subject: [PATCH 085/272] HADOOP-12593: Release Dr. Elephant v1.0.1 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 9fea99616..1a2ae6bde 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ import play.Project._ name := "dr-elephant" -version := "1.0.1-SNAPSHOT" +version := "1.0.1" javacOptions in Compile ++= Seq("-source", "1.6", "-target", "1.6") From de5a0dc71b3138c79109638b518aebe98a55c10d Mon Sep 17 00:00:00 2001 From: Fangshi Li Date: Mon, 13 Jul 2015 13:49:46 -0700 Subject: [PATCH 086/272] HADOOP-12632: Enable Dr. Elephant 's mapper spill heuristics --- .../mapreduce/heuristics/MapperSpillHeuristic.java | 13 +++++++------ app/views/helpMapperSpill.scala.html | 9 +++++---- .../heuristics/MapperSpillHeuristicTest.java | 10 +++++----- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristic.java index ea207fbd8..9c9f9c2e1 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristic.java @@ -36,9 +36,10 @@ public HeuristicResult apply(MapReduceApplicationData data) { HeuristicResult result = new HeuristicResult(HEURISTIC_NAME, severity); - result.addDetail("Number of spilled records ", Long.toString(totalSpills)); - result.addDetail("Number of Mapper output records ", Long.toString(totalOutputRecords)); - result.addDetail("Ratio of spilled records to mapper output records", Double.toString(ratioSpills)); + result.addDetail("Number of tasks", Integer.toString(tasks.length)); + result.addDetail("Avg spilled records per task", Long.toString(totalSpills/tasks.length)); + result.addDetail("Avg output records per task", Long.toString(totalOutputRecords/tasks.length)); + result.addDetail("Ratio of spilled records to output records", Double.toString(ratioSpills)); return result; @@ -68,8 +69,8 @@ public static Severity getSpillSeverity(double ratioSpills) { long normalizedSpillRatio = 0; //Normalize the ratio to integer. normalizedSpillRatio = (long) (ratioSpills * THRESHOLD_SPILL_FACTOR); - return Severity.getSeverityAscending(normalizedSpillRatio, (long) (1.25 * THRESHOLD_SPILL_FACTOR), - (long) (1.5 * THRESHOLD_SPILL_FACTOR), (long) (1.75 * THRESHOLD_SPILL_FACTOR), - (long) (2 * THRESHOLD_SPILL_FACTOR)); + return Severity.getSeverityAscending(normalizedSpillRatio, (long) (1.9 * THRESHOLD_SPILL_FACTOR), + (long) (1.95 * THRESHOLD_SPILL_FACTOR), (long) (2 * THRESHOLD_SPILL_FACTOR), + (long) (3 * THRESHOLD_SPILL_FACTOR)); } } diff --git a/app/views/helpMapperSpill.scala.html b/app/views/helpMapperSpill.scala.html index d9687a7fc..d2b38c6a8 100644 --- a/app/views/helpMapperSpill.scala.html +++ b/app/views/helpMapperSpill.scala.html @@ -1,5 +1,5 @@

    - This analysis shows the amount of spill that occurs on mapper side in your code
    + This heuristic gauges your mapper performance in a disk I/0 perspective. Mapper spill ratio (spilled records/output records) is a critical indicator to your mapper performance: if the ratio is close to 2, it generally means your mappers have large outputs that couldn't fit in in-memory sort buffer. If the ratio is higher than 2, the situation is even worse. Having large disk I/O wasted on sorting output records could seriously affect your mapper speed. To make it run faster, try our tentative recommendation. We newly enabled this heuristic and is still testing it!

    @@ -31,10 +31,11 @@

    Mapper Spill

    Suggestions

    +This heuristic is less straightforward than others, and it requires deeper hadoop knowledge. We are still working on finalizing the recommendation. Feedbacks welcomed! You could try:

      +
    1. Increase the size of in-memory sort buffer (mapreduce.task.io.sort.mb), default 100M
    2. +
    3. Increase the buffer spill percentage (mapreduce.map.sort.spill.percent, when it is reached a background thread will start spill buffer to disk), default value is 0.8.
    4. Use combiner to lower the map output size.
    5. -
    6. Increase the size of map side sort buffer (io.sort.mb)
    7. -
    8. Increase the size of index buffer (io.sort.record.percent) -
    9. Increase the spill percentage (io.sort.spill.percent), default value is 0.8.
    10. +
    11. Compress mapper output (set mapreduce.map.output.compress and mapreduce.map.output.compress.codec)

    \ No newline at end of file diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristicTest.java index f88258ea8..a0186430c 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristicTest.java @@ -16,19 +16,19 @@ public class MapperSpillHeuristicTest extends TestCase { private static final int numTasks = 100; public void testCritical() throws IOException { - assertEquals(Severity.CRITICAL, analyzeJob(2200, 1000)); + assertEquals(Severity.CRITICAL, analyzeJob(3000, 1000)); } public void testSevere() throws IOException { - assertEquals(Severity.SEVERE, analyzeJob(1755, 1000)); + assertEquals(Severity.SEVERE, analyzeJob(2000, 1000)); } public void testModerate() throws IOException { - assertEquals(Severity.MODERATE, analyzeJob(1555, 1000)); + assertEquals(Severity.MODERATE, analyzeJob(1980, 1000)); } public void testLow() throws IOException { - assertEquals(Severity.LOW, analyzeJob(1352, 1000)); + assertEquals(Severity.LOW, analyzeJob(1900, 1000)); } public void testNone() throws IOException { @@ -44,7 +44,7 @@ private Severity analyzeJob(long spilledRecords, long mapRecords) throws IOExcep counter.set(MapReduceCounterHolder.CounterName.MAP_OUTPUT_RECORDS, mapRecords); for (int i=0; i < numTasks; i++) { - mappers[i] = new MapReduceTaskData(counter, new long[] { 0, 5, 5, 5 }); + mappers[i] = new MapReduceTaskData(counter, new long[4]); } MapReduceApplicationData data = new MapReduceApplicationData().setCounters(jobCounter).setMapperData(mappers); From b9998f735b9038bb451c6a6eac90a097a6a4eeef Mon Sep 17 00:00:00 2001 From: Fangshi Li Date: Tue, 14 Jul 2015 13:55:42 -0700 Subject: [PATCH 087/272] HADOOP-12669: Dr. Elephant's mapper spill heuristic is too harsh --- .../heuristics/MapperSpillHeuristic.java | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristic.java index 9c9f9c2e1..ec8e08e3c 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristic.java @@ -50,27 +50,12 @@ public String getHeuristicName() { return HEURISTIC_NAME; } - /** - * - * @param ratioSpills - * @return Severity - * - * ratioSpills: Severity - * - * <1.25: None - * 1.25-1.5: Low - * 1.5-1.75: Moderate - * 1.75-2: Severe - * >=2: Critical - * - */ - public static Severity getSpillSeverity(double ratioSpills) { long normalizedSpillRatio = 0; //Normalize the ratio to integer. normalizedSpillRatio = (long) (ratioSpills * THRESHOLD_SPILL_FACTOR); - return Severity.getSeverityAscending(normalizedSpillRatio, (long) (1.9 * THRESHOLD_SPILL_FACTOR), - (long) (1.95 * THRESHOLD_SPILL_FACTOR), (long) (2 * THRESHOLD_SPILL_FACTOR), + return Severity.getSeverityAscending(normalizedSpillRatio, (long) (2.01 * THRESHOLD_SPILL_FACTOR), + (long) (2.2 * THRESHOLD_SPILL_FACTOR), (long) (2.5 * THRESHOLD_SPILL_FACTOR), (long) (3 * THRESHOLD_SPILL_FACTOR)); } } From e2e887a86af4e8822f9caf823756645aeacee0aa Mon Sep 17 00:00:00 2001 From: Fangshi Li Date: Tue, 14 Jul 2015 13:59:13 -0700 Subject: [PATCH 088/272] HADOOP-12674: Release Dr. Elephant v1.0.2 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 1a2ae6bde..209e7bc7d 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ import play.Project._ name := "dr-elephant" -version := "1.0.1" +version := "1.0.2" javacOptions in Compile ++= Seq("-source", "1.6", "-target", "1.6") From f5e4d23f9252be9e394078b610a338784d54f68d Mon Sep 17 00:00:00 2001 From: Fangshi Li Date: Tue, 14 Jul 2015 14:06:08 -0700 Subject: [PATCH 089/272] HADOOP-12603: Dr. Elephant has a failed test in Mapper Speed Heuristic --- .../mapreduce/heuristics/MapperSpillHeuristicTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristicTest.java index a0186430c..a2d5346dd 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristicTest.java @@ -20,15 +20,15 @@ public void testCritical() throws IOException { } public void testSevere() throws IOException { - assertEquals(Severity.SEVERE, analyzeJob(2000, 1000)); + assertEquals(Severity.SEVERE, analyzeJob(2500, 1000)); } public void testModerate() throws IOException { - assertEquals(Severity.MODERATE, analyzeJob(1980, 1000)); + assertEquals(Severity.MODERATE, analyzeJob(2300, 1000)); } public void testLow() throws IOException { - assertEquals(Severity.LOW, analyzeJob(1900, 1000)); + assertEquals(Severity.LOW, analyzeJob(2100, 1000)); } public void testNone() throws IOException { From 48e58488f6c112634629d4b39babd3aa89d3fecd Mon Sep 17 00:00:00 2001 From: Fangshi Li Date: Tue, 14 Jul 2015 14:31:24 -0700 Subject: [PATCH 090/272] HADOOP-12603: Update Mapper Spill Heuristic help page --- app/views/helpMapperSpill.scala.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/helpMapperSpill.scala.html b/app/views/helpMapperSpill.scala.html index d2b38c6a8..23c53b301 100644 --- a/app/views/helpMapperSpill.scala.html +++ b/app/views/helpMapperSpill.scala.html @@ -1,5 +1,5 @@

    - This heuristic gauges your mapper performance in a disk I/0 perspective. Mapper spill ratio (spilled records/output records) is a critical indicator to your mapper performance: if the ratio is close to 2, it generally means your mappers have large outputs that couldn't fit in in-memory sort buffer. If the ratio is higher than 2, the situation is even worse. Having large disk I/O wasted on sorting output records could seriously affect your mapper speed. To make it run faster, try our tentative recommendation. We newly enabled this heuristic and is still testing it!
    + This heuristic gauges your mapper performance in a disk I/0 perspective. Mapper spill ratio (spilled records/output records) is a critical indicator to your mapper performance: if the ratio is close to 2, it means each record is spilled to disk twice(once when in-memory sort buffer is almost full, once when merging spilled splits). This usually happens when your mappers have large amount of outputs. If the ratio is higher than 2, we suggest you try our recommendation below. Having large disk I/O wasted on sorting output records could seriously affect your mapper speed. To make it run faster, try our tentative recommendation. We newly enabled this heuristic and want you to test it!

    @@ -14,15 +14,15 @@

    Mapper Spill

    Number of spilled records - 15000 + 20000 Number of Mapper output records - 7500 + 8000 Ratio of disk spills to output records - 2.0 + 2.5 From 4f4ee66c9fcc247fb1350cf295bc17b2938fc2ab Mon Sep 17 00:00:00 2001 From: Fangshi Li Date: Tue, 14 Jul 2015 18:08:19 -0700 Subject: [PATCH 091/272] HADOOP-12063: fix a potential devide by 0 exception in mapper spill heuristic --- .../drelephant/mapreduce/heuristics/MapperSpillHeuristic.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristic.java index ec8e08e3c..b2e06104e 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristic.java @@ -37,8 +37,8 @@ public HeuristicResult apply(MapReduceApplicationData data) { HeuristicResult result = new HeuristicResult(HEURISTIC_NAME, severity); result.addDetail("Number of tasks", Integer.toString(tasks.length)); - result.addDetail("Avg spilled records per task", Long.toString(totalSpills/tasks.length)); - result.addDetail("Avg output records per task", Long.toString(totalOutputRecords/tasks.length)); + result.addDetail("Avg spilled records per task", tasks.length == 0 ? "0" : Long.toString(totalSpills/tasks.length)); + result.addDetail("Avg output records per task", tasks.length == 0 ? "0" : Long.toString(totalOutputRecords/tasks.length)); result.addDetail("Ratio of spilled records to output records", Double.toString(ratioSpills)); return result; From 017836402c4a0568a474a8f638a77503166f8543 Mon Sep 17 00:00:00 2001 From: Fangshi Li Date: Wed, 15 Jul 2015 10:51:24 -0700 Subject: [PATCH 092/272] HADOOP-12688: Dr. Elephant needs task time info in Memory Heuristic --- .../mapreduce/heuristics/GenericMemoryHeuristic.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/GenericMemoryHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericMemoryHeuristic.java index 2fa45ff1d..af1980bec 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/GenericMemoryHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericMemoryHeuristic.java @@ -36,6 +36,7 @@ public HeuristicResult apply(MapReduceApplicationData data) { MapReduceTaskData[] tasks = getTasks(data); List taskMems = new ArrayList(); + List runtimesMs = new ArrayList(); long taskMin = Long.MAX_VALUE; long taskMax = 0; for (MapReduceTaskData task : tasks) { @@ -43,6 +44,9 @@ public HeuristicResult apply(MapReduceApplicationData data) { taskMems.add(taskMem); taskMin = Math.min(taskMin, taskMem); taskMax = Math.max(taskMax, taskMem); + if (task.timed()) { + runtimesMs.add(task.getTotalRunTimeMs()); + } } if(taskMin == Long.MAX_VALUE) { @@ -50,12 +54,14 @@ public HeuristicResult apply(MapReduceApplicationData data) { } long taskAvg = Statistics.average(taskMems); + long averageTimeMs = Statistics.average(runtimesMs); Severity severity = getTaskMemoryUtilSeverity(taskAvg, containerMem); HeuristicResult result = new HeuristicResult(_heuristicName, severity); result.addDetail("Number of tasks", Integer.toString(tasks.length)); + result.addDetail("Avg task runtime", Statistics.readableTimespan(averageTimeMs)); result.addDetail("Avg Physical Memory (MB)", Long.toString(taskAvg/FileUtils.ONE_MB)); result.addDetail("Max Physical Memory (MB)", Long.toString(taskMax/FileUtils.ONE_MB)); result.addDetail("Min Physical Memory (MB)", Long.toString(taskMin/FileUtils.ONE_MB)); From 6de0b0a86cde3159a467bbb0575f76f8abe05040 Mon Sep 17 00:00:00 2001 From: Fangshi Li Date: Wed, 15 Jul 2015 16:46:38 -0700 Subject: [PATCH 093/272] HADOOP-12709: Dr. Elephant has a corner case for flagging 0 tasks in Memory Heuristic --- .../mapreduce/heuristics/GenericMemoryHeuristic.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/GenericMemoryHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericMemoryHeuristic.java index af1980bec..3c5b0d8fd 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/GenericMemoryHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericMemoryHeuristic.java @@ -56,7 +56,12 @@ public HeuristicResult apply(MapReduceApplicationData data) { long taskAvg = Statistics.average(taskMems); long averageTimeMs = Statistics.average(runtimesMs); - Severity severity = getTaskMemoryUtilSeverity(taskAvg, containerMem); + Severity severity; + if (tasks.length == 0) { + severity = Severity.NONE; + } else { + severity = getTaskMemoryUtilSeverity(taskAvg, containerMem); + } HeuristicResult result = new HeuristicResult(_heuristicName, severity); From a7d6d3b241003998420ace1f84aaafea93dec316 Mon Sep 17 00:00:00 2001 From: Yitong Zhou Date: Wed, 22 Jul 2015 12:55:28 -0700 Subject: [PATCH 094/272] LIHADOOP-13019, LIHADOOP-13020: Upgrade to Spark 1.4, and add event log limit for Spark log fetcher RB=526958 BUG= G=superfriends-reviewers R=fli A=fli --- app-conf/HeuristicConf.xml | 10 ++ .../drelephant/analysis/AnalyticJob.java | 2 +- .../spark/SparkApplicationData.java | 2 + .../heuristics/EventLogLimitHeuristic.java | 41 +++++++ .../deploy/history/SparkDataCollection.scala | 16 ++- .../spark/deploy/history/SparkFSFetcher.scala | 107 ++++++++++++++++-- app/views/helpSparkEventLogLimit.scala.html | 8 ++ build.sbt | 2 +- .../spark/MockSparkApplicationData.java | 5 + 9 files changed, 179 insertions(+), 14 deletions(-) create mode 100644 app/com/linkedin/drelephant/spark/heuristics/EventLogLimitHeuristic.java create mode 100644 app/views/helpSparkEventLogLimit.scala.html diff --git a/app-conf/HeuristicConf.xml b/app-conf/HeuristicConf.xml index 12005aa5c..3bbe89c95 100644 --- a/app-conf/HeuristicConf.xml +++ b/app-conf/HeuristicConf.xml @@ -138,4 +138,14 @@ + + spark + Spark Event Log Limit + com.linkedin.drelephant.spark.heuristics.EventLogLimitHeuristic + views.html.helpSparkEventLogLimit + + 2 + + + diff --git a/app/com/linkedin/drelephant/analysis/AnalyticJob.java b/app/com/linkedin/drelephant/analysis/AnalyticJob.java index cfd063aa7..1509e3878 100644 --- a/app/com/linkedin/drelephant/analysis/AnalyticJob.java +++ b/app/com/linkedin/drelephant/analysis/AnalyticJob.java @@ -133,7 +133,7 @@ public JobResult getAnalysis() throws Exception { // Note: before adding Spark analysers, all JobResult are using job ids as the primary key. But Spark (and many // other non-mapreduce applications) does not have a job id. To maintain backwards compatibility, we replace // 'application' with 'job' to form a pseudo one. - result.jobId = Utils.getJobIdFromApplicationId(data.getAppId()); + result.jobId = Utils.getJobIdFromApplicationId(getAppId()); result.url = getTrackingUrl(); result.username = getUser(); result.startTime = getStartTime(); diff --git a/app/com/linkedin/drelephant/spark/SparkApplicationData.java b/app/com/linkedin/drelephant/spark/SparkApplicationData.java index cc4c5e217..7afff341e 100644 --- a/app/com/linkedin/drelephant/spark/SparkApplicationData.java +++ b/app/com/linkedin/drelephant/spark/SparkApplicationData.java @@ -9,6 +9,8 @@ */ public interface SparkApplicationData extends HadoopApplicationData { + public boolean isThrottled(); + public SparkGeneralData getGeneralData(); public SparkEnvironmentData getEnvironmentData(); diff --git a/app/com/linkedin/drelephant/spark/heuristics/EventLogLimitHeuristic.java b/app/com/linkedin/drelephant/spark/heuristics/EventLogLimitHeuristic.java new file mode 100644 index 000000000..6ea5c8e1a --- /dev/null +++ b/app/com/linkedin/drelephant/spark/heuristics/EventLogLimitHeuristic.java @@ -0,0 +1,41 @@ +package com.linkedin.drelephant.spark.heuristics; + +import com.linkedin.drelephant.analysis.Heuristic; +import com.linkedin.drelephant.analysis.HeuristicResult; +import com.linkedin.drelephant.analysis.Severity; +import com.linkedin.drelephant.spark.SparkApplicationData; + + +/** + * This is a safeguard heuristic rule that makes sure if a log size passes the limit, we do not automatically + * approve it. + * + * @author yizhou + */ +public class EventLogLimitHeuristic implements Heuristic { + public static final String HEURISTIC_NAME = "Spark Event Log Limit"; + + @Override + public HeuristicResult apply(SparkApplicationData data) { + Severity severity = getSeverity(data); + HeuristicResult result = new HeuristicResult(getHeuristicName(), severity); + if (severity == Severity.CRITICAL) { + result.addDetail("Spark job's event log passes the limit. No actual log data is fetched." + + " All other heuristic rules will not make sense."); + } + return result; + } + + @Override + public String getHeuristicName() { + return HEURISTIC_NAME; + } + + private Severity getSeverity(SparkApplicationData data) { + if (data.isThrottled()) { + return Severity.CRITICAL; + } else { + return Severity.NONE; + } + } +} diff --git a/app/org/apache/spark/deploy/history/SparkDataCollection.scala b/app/org/apache/spark/deploy/history/SparkDataCollection.scala index d2395e5e5..dda3bc49d 100644 --- a/app/org/apache/spark/deploy/history/SparkDataCollection.scala +++ b/app/org/apache/spark/deploy/history/SparkDataCollection.scala @@ -45,16 +45,22 @@ class SparkDataCollection(applicationEventListener: ApplicationEventListener, private var _environmentData: SparkEnvironmentData = null; private var _executorData: SparkExecutorData = null; private var _storageData: SparkStorageData = null; - + private var _isThrottled: Boolean = false; import SparkDataCollection._ + def throttle(): Unit = { + _isThrottled = true + } + + override def isThrottled(): Boolean = _isThrottled + override def getApplicationType(): ApplicationType = APPLICATION_TYPE override def getConf(): Properties = getEnvironmentData().getSparkProperties() - override def isEmpty(): Boolean = getExecutorData().getExecutors.isEmpty() + override def isEmpty(): Boolean = !isThrottled() && getExecutorData().getExecutors.isEmpty() override def getGeneralData(): SparkGeneralData = { if (_applicationData == null) { @@ -192,8 +198,8 @@ class SparkDataCollection(applicationEventListener: ApplicationEventListener, jobInfo.numSkippedTasks = data.numSkippedTasks jobInfo.numTasks = data.numTasks - jobInfo.startTime = data.startTime.getOrElse(0) - jobInfo.endTime = data.endTime.getOrElse(0) + jobInfo.startTime = data.submissionTime.getOrElse(0) + jobInfo.endTime = data.completionTime.getOrElse(0) data.stageIds.foreach{ case (id: Int) => jobInfo.addStageId(id)} addIntSetToJSet(data.completedStageIndices, jobInfo.completedStageIndices) @@ -222,7 +228,7 @@ class SparkDataCollection(applicationEventListener: ApplicationEventListener, stageInfo.numCompleteTasks = data.numCompleteTasks stageInfo.numFailedTasks = data.numFailedTasks stageInfo.outputBytes = data.outputBytes - stageInfo.shuffleReadBytes = data.shuffleReadBytes + stageInfo.shuffleReadBytes = data.shuffleReadTotalBytes stageInfo.shuffleWriteBytes = data.shuffleWriteBytes addIntSetToJSet(data.completedIndices, stageInfo.completedIndices) diff --git a/app/org/apache/spark/deploy/history/SparkFSFetcher.scala b/app/org/apache/spark/deploy/history/SparkFSFetcher.scala index b540b09e9..c568af7b2 100644 --- a/app/org/apache/spark/deploy/history/SparkFSFetcher.scala +++ b/app/org/apache/spark/deploy/history/SparkFSFetcher.scala @@ -3,12 +3,16 @@ package org.apache.spark.deploy.history import java.net.URI import java.security.PrivilegedAction +import java.io.BufferedInputStream +import java.io.InputStream + + import com.linkedin.drelephant.HadoopSecurity import com.linkedin.drelephant.analysis.ElephantFetcher import com.linkedin.drelephant.spark.SparkApplicationData import org.apache.hadoop.conf.Configuration -import org.apache.hadoop.fs.{Path, FileSystem, FileStatus} +import org.apache.hadoop.fs.{Path, FileSystem} import org.apache.hadoop.hdfs.web.WebHdfsFileSystem import org.apache.log4j.Logger import org.apache.spark.SparkConf @@ -18,6 +22,7 @@ import org.apache.spark.ui.env.EnvironmentListener import org.apache.spark.ui.exec.ExecutorsListener import org.apache.spark.ui.jobs.JobProgressListener import org.apache.spark.ui.storage.StorageListener +import org.apache.spark.io.CompressionCodec /** @@ -75,7 +80,7 @@ class SparkFSFetcher extends ElephantFetcher[SparkApplicationData] { * In short, this fetcher only works with Spark <=1.2, and we should switch to JSON endpoints with Spark's * future release. */ - val replayBus = createReplayBus(fs.getFileStatus(new Path(_logDir, appId))) + val replayBus = new ReplayListenerBus() val applicationEventListener = new ApplicationEventListener val jobProgressListener = new JobProgressListener(new SparkConf()) val environmentListener = new EnvironmentListener @@ -103,21 +108,109 @@ class SparkFSFetcher extends ElephantFetcher[SparkApplicationData] { replayBus.addListener(executorsListener) replayBus.addListener(storageListener) - replayBus.replay() + val logPath = new Path(_logDir, appId) + val logInput: InputStream = + if (isLegacyLogDirectory(logPath)) { + if (!shouldThrottle(logPath)) { + openLegacyEventLog(logPath) + } else { + null + } + } else { + val logFilePath = new Path(logPath + "_1.snappy") + if (!shouldThrottle(logFilePath)) { + EventLoggingListener.openEventLog(logFilePath, fs) + } else { + null + } + } + + if (logInput == null) { + dataCollection.throttle() + // Since the data set is empty, we need to set the appilication id, + // so that we could detect this is Spark job type + dataCollection.getGeneralData().setApplicationId(appId) + dataCollection.getConf().setProperty("spark.app.id", appId) + + logger.info("The event log of Spark application: " + appId + " is over the limit size of " + + EVENT_LOG_SIZE_LIMIT + " bytes, the parsing process gets throttled.") + } else { + logger.info("Replaying Spark logs for application: " + appId) + + replayBus.replay(logInput, logPath.toString(), false) + + logger.info("Replay completed for application: " + appId) + } dataCollection } }) } - private def createReplayBus(logDir: FileStatus): ReplayListenerBus = { - val path = logDir.getPath() - val elogInfo = EventLoggingListener.parseLoggingInfo(path, fs) - new ReplayListenerBus(elogInfo.logPaths, fs, elogInfo.compressionCodec) + /** + * Checks if the log path stores the legacy event log. (Spark <= 1.2 store an event log in a directory) + * + * @param entry The path to check + * @return true if it is legacy log path, else false + */ + private def isLegacyLogDirectory(entry: Path) : Boolean = fs.exists(entry) && fs.getFileStatus(entry).isDirectory() + + /** + * Opens a legacy log path + * + * @param dir The directory to open + * @return an InputStream + */ + private def openLegacyEventLog(dir: Path): InputStream = { + val children = fs.listStatus(dir) + var eventLogPath: Path = null + var codecName: Option[String] = None + + children.foreach { child => + child.getPath().getName() match { + case name if name.startsWith(LOG_PREFIX) => + eventLogPath = child.getPath() + case codec if codec.startsWith(COMPRESSION_CODEC_PREFIX) => + codecName = Some(codec.substring(COMPRESSION_CODEC_PREFIX.length())) + case _ => + } + } + + if (eventLogPath == null) { + throw new IllegalArgumentException(s"$dir is not a Spark application log directory.") + } + + val codec = try { + codecName.map { c => CompressionCodec.createCodec(_sparkConf, c) } + } catch { + case e: Exception => + throw new IllegalArgumentException(s"Unknown compression codec $codecName.") + } + + val in = new BufferedInputStream(fs.open(eventLogPath)) + codec.map(_.compressedInputStream(in)).getOrElse(in) + } + + /** + * Checks if the log parser should be throttled when the file is too large. + * Note: the current Spark's implementation of ReplayListenerBus will take more than 80 minutes to read a compressed + * 500 MB event log file. Allowing such reading might block the entire Dr Elephant thread pool. + * + * @param eventLogPath The event log path + * @return If the event log parsing should be throttled + */ + private def shouldThrottle(eventLogPath: Path): Boolean = { + fs.getFileStatus(eventLogPath).getLen() > EVENT_LOG_SIZE_LIMIT } + } private object SparkFSFetcher { private val logger = Logger.getLogger(SparkFSFetcher.getClass) val DEFAULT_LOG_DIR = "/system/spark-history" + val EVENT_LOG_SIZE_LIMIT = 104857600L // 100MB + + // Constants used to parse <= Spark 1.2.0 log directories. + val LOG_PREFIX = "EVENT_LOG_" + val COMPRESSION_CODEC_PREFIX = EventLoggingListener.COMPRESSION_CODEC_KEY + "_" } diff --git a/app/views/helpSparkEventLogLimit.scala.html b/app/views/helpSparkEventLogLimit.scala.html new file mode 100644 index 000000000..7d3a32486 --- /dev/null +++ b/app/views/helpSparkEventLogLimit.scala.html @@ -0,0 +1,8 @@ +

    + Spark's event log passer currently cannot handle very large event log files. It will take too long for Dr Elephant to + analyze it that might endanger the entire server. Therefore, currently we sets up a limit (100MB) for event log files, + and will by-pass the log-fetching process if the log size exceeds the limit. + + Please consult Hadoop Dev directly if you want to pass through production review with a very large Spark + job. +

    \ No newline at end of file diff --git a/build.sbt b/build.sbt index 209e7bc7d..35633f171 100644 --- a/build.sbt +++ b/build.sbt @@ -20,7 +20,7 @@ libraryDependencies ++= Seq( "org.apache.avro" % "avro" % "1.4.0", "com.linkedin.avro-schemas" % "avro-schemas-tracking" % "6.0.518", // Spark dependencies, exclude avro transitive dependencies to avoid being overriden by 1.7.x - "org.apache.spark" % "spark-core_2.10" % "1.2.0" excludeAll( + "org.apache.spark" % "spark-core_2.10" % "1.4.0" excludeAll( ExclusionRule(organization = "org.apache.avro"), ExclusionRule(organization = "org.apache.hadoop") ), diff --git a/test/com/linkedin/drelephant/spark/MockSparkApplicationData.java b/test/com/linkedin/drelephant/spark/MockSparkApplicationData.java index e296617a5..16163d3a7 100644 --- a/test/com/linkedin/drelephant/spark/MockSparkApplicationData.java +++ b/test/com/linkedin/drelephant/spark/MockSparkApplicationData.java @@ -26,6 +26,11 @@ public MockSparkApplicationData() { _sparkStorageData = new SparkStorageData(); } + @Override + public boolean isThrottled() { + return false; + } + @Override public SparkGeneralData getGeneralData() { return _sparkGeneralData; From b8bf46b875def27f44957a6d1f9d5dcef57d6c3a Mon Sep 17 00:00:00 2001 From: Fangshi Li Date: Tue, 28 Jul 2015 16:44:57 -0700 Subject: [PATCH 095/272] LIHADOOP-13018: Dr. Elephant 's Spill heuristic should be lenient to small number of tasks --- .../heuristics/MapperSpillHeuristic.java | 8 +++++++ .../heuristics/MapperSpillHeuristicTest.java | 23 +++++++++++++------ 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristic.java index b2e06104e..ebdadc4cd 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristic.java @@ -34,6 +34,10 @@ public HeuristicResult apply(MapReduceApplicationData data) { Severity severity = getSpillSeverity(ratioSpills); + // Severity is reduced if number of tasks is small + Severity taskSeverity = getNumTasksSeverity(tasks.length); + severity = Severity.min(severity, taskSeverity); + HeuristicResult result = new HeuristicResult(HEURISTIC_NAME, severity); result.addDetail("Number of tasks", Integer.toString(tasks.length)); @@ -58,4 +62,8 @@ public static Severity getSpillSeverity(double ratioSpills) { (long) (2.2 * THRESHOLD_SPILL_FACTOR), (long) (2.5 * THRESHOLD_SPILL_FACTOR), (long) (3 * THRESHOLD_SPILL_FACTOR)); } + + public static Severity getNumTasksSeverity(long numTasks) { + return Severity.getSeverityAscending(numTasks, 50, 100, 500, 1000); + } } diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristicTest.java index a2d5346dd..7709d56c5 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristicTest.java @@ -13,29 +13,38 @@ public class MapperSpillHeuristicTest extends TestCase { Heuristic heuristic = new MapperSpillHeuristic(); - private static final int numTasks = 100; public void testCritical() throws IOException { - assertEquals(Severity.CRITICAL, analyzeJob(3000, 1000)); + // Spill ratio 3.0, 1000 tasks + assertEquals(Severity.CRITICAL, analyzeJob(3000, 1000, 1000)); } public void testSevere() throws IOException { - assertEquals(Severity.SEVERE, analyzeJob(2500, 1000)); + // Spill ratio 2.5, 1000 tasks + assertEquals(Severity.SEVERE, analyzeJob(2500, 1000, 1000)); } public void testModerate() throws IOException { - assertEquals(Severity.MODERATE, analyzeJob(2300, 1000)); + // Spill ratio 2.3, 1000 tasks + assertEquals(Severity.MODERATE, analyzeJob(2300, 1000, 1000)); } public void testLow() throws IOException { - assertEquals(Severity.LOW, analyzeJob(2100, 1000)); + // Spill ratio 2.1, 1000 tasks + assertEquals(Severity.LOW, analyzeJob(2100, 1000, 1000)); } public void testNone() throws IOException { - assertEquals(Severity.NONE, analyzeJob(1000, 1000)); + // Spill ratio 1.0, 1000 tasks + assertEquals(Severity.NONE, analyzeJob(1000, 1000, 1000)); } - private Severity analyzeJob(long spilledRecords, long mapRecords) throws IOException { + public void testSmallNumTasks() throws IOException { + // Spill ratio 3.0, should be critical, but number of task is small(10), final result is NONE + assertEquals(Severity.NONE, analyzeJob(3000, 1000, 10)); + } + + private Severity analyzeJob(long spilledRecords, long mapRecords, int numTasks) throws IOException { MapReduceCounterHolder jobCounter = new MapReduceCounterHolder(); MapReduceTaskData[] mappers = new MapReduceTaskData[numTasks]; From ec463430354c4ce475194ebb820f274f47fe75c7 Mon Sep 17 00:00:00 2001 From: Fangshi Li Date: Wed, 29 Jul 2015 11:41:26 -0700 Subject: [PATCH 096/272] LIHADOOP-13094: Fix Dr. Elephant build on Jenkins after spark1.4 upgrade --- build.sbt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index 35633f171..51c135af8 100644 --- a/build.sbt +++ b/build.sbt @@ -22,7 +22,8 @@ libraryDependencies ++= Seq( // Spark dependencies, exclude avro transitive dependencies to avoid being overriden by 1.7.x "org.apache.spark" % "spark-core_2.10" % "1.4.0" excludeAll( ExclusionRule(organization = "org.apache.avro"), - ExclusionRule(organization = "org.apache.hadoop") + ExclusionRule(organization = "org.apache.hadoop"), + ExclusionRule(organization = "net.razorvine") ), // Hadoop defaultly are using guava 11.0, might raise NoSuchMethodException "com.google.guava" % "guava" % "18.0" @@ -35,7 +36,10 @@ unmanagedClasspath in Compile ++= update.value.select(configurationFilter("compi libraryDependencies ++= Seq( "com.linkedin.hadoop" % "hadoop-common" % "2.3.0.+" % "compileonly", "com.linkedin.hadoop" % "hadoop-hdfs" % "2.3.0.+" % "compileonly", - "com.linkedin.hadoop" % "hadoop-mapreduce-client-core" % "2.3.0.+" % "compileonly" + "com.linkedin.hadoop" % "hadoop-mapreduce-client-core" % "2.3.0.+" % "compileonly", + "com.linkedin.hadoop" % "hadoop-common" % "2.3.0.+" % "test", + "com.linkedin.hadoop" % "hadoop-hdfs" % "2.3.0.+" % "test", + "com.linkedin.hadoop" % "hadoop-mapreduce-client-core" % "2.3.0.+" % "test" ) val LinkedInPatterns = Patterns( From d8ab3c8faefb79a57d226991d8ff6ca38c8ff2b5 Mon Sep 17 00:00:00 2001 From: Fangshi Li Date: Wed, 29 Jul 2015 13:00:34 -0700 Subject: [PATCH 097/272] LIHADOOP-13016: Release Dr. Elephant v1.0.3 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 51c135af8..bb0ba16a4 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ import play.Project._ name := "dr-elephant" -version := "1.0.2" +version := "1.0.3" javacOptions in Compile ++= Seq("-source", "1.6", "-target", "1.6") From 4d839ca8d5eb5f9b9ee4b90c4e73b75d7dfb3586 Mon Sep 17 00:00:00 2001 From: Yitong Zhou Date: Thu, 20 Aug 2015 15:17:02 -0700 Subject: [PATCH 098/272] Adjust StageRuntime heuristics so that it's the real duration time instead of total aggregated executor time RB=549801 G=superfriends-reviewers R=mshen A=mshen --- .../linkedin/drelephant/spark/SparkJobProgressData.java | 3 +++ .../heuristics/BestPropertiesConventionHeuristic.java | 2 +- .../drelephant/spark/heuristics/StageRuntimeHeuristic.java | 6 +++--- .../apache/spark/deploy/history/SparkDataCollection.scala | 7 +++++++ .../heuristics/BestPropertiesConventionHeuristicTest.java | 2 +- 5 files changed, 15 insertions(+), 5 deletions(-) diff --git a/app/com/linkedin/drelephant/spark/SparkJobProgressData.java b/app/com/linkedin/drelephant/spark/SparkJobProgressData.java index 50a2c358a..cae9a2433 100644 --- a/app/com/linkedin/drelephant/spark/SparkJobProgressData.java +++ b/app/com/linkedin/drelephant/spark/SparkJobProgressData.java @@ -209,7 +209,10 @@ public static class StageInfo { public final Set completedIndices = new HashSet(); public int numFailedTasks; + // Total accumulated executor runtime public long executorRunTime; + // Total stage duration + public long duration; // Note, currently calculating I/O speed on stage level does not make sense // since we do not have information about specific I/O time. diff --git a/app/com/linkedin/drelephant/spark/heuristics/BestPropertiesConventionHeuristic.java b/app/com/linkedin/drelephant/spark/heuristics/BestPropertiesConventionHeuristic.java index 62c6201d6..27ca9b6be 100644 --- a/app/com/linkedin/drelephant/spark/heuristics/BestPropertiesConventionHeuristic.java +++ b/app/com/linkedin/drelephant/spark/heuristics/BestPropertiesConventionHeuristic.java @@ -31,7 +31,7 @@ public HeuristicResult apply(SparkApplicationData data) { int coreNum = sparkExecutorCores == null ? 1 : Integer.parseInt(sparkExecutorCores); Severity kryoSeverity = - binarySeverity("org.apache.spark.serializer.KryoSerializer", sparkSerializer, false, Severity.MODERATE); + binarySeverity("org.apache.spark.serializer.KryoSerializer", sparkSerializer, true, Severity.MODERATE); Severity driverMemSeverity = getDriverMemorySeverity(MemoryFormatUtils.stringToBytes(sparkDriverMemory)); Severity sortSeverity = binarySeverity("sort", sparkShuffleManager, true, Severity.MODERATE); Severity executorCoreSeverity = getCoreNumSeverity(coreNum); diff --git a/app/com/linkedin/drelephant/spark/heuristics/StageRuntimeHeuristic.java b/app/com/linkedin/drelephant/spark/heuristics/StageRuntimeHeuristic.java index bd150f972..4d600c505 100644 --- a/app/com/linkedin/drelephant/spark/heuristics/StageRuntimeHeuristic.java +++ b/app/com/linkedin/drelephant/spark/heuristics/StageRuntimeHeuristic.java @@ -44,11 +44,11 @@ public HeuristicResult apply(SparkApplicationData data) { problematicStages.add(String.format("%s (task failure rate: %1.3f)", id, stageTasksFailureRate)); } - long executorTime = info.executorRunTime; - Severity runtimeSeverity = getStageRuntimeSeverity(executorTime); + long duration = info.duration; + Severity runtimeSeverity = getStageRuntimeSeverity(duration); if (runtimeSeverity.getValue() > Severity.MODERATE.getValue()) { problematicStages - .add(String.format("%s (runtime: %s)", id, Statistics.readableTimespan(executorTime))); + .add(String.format("%s (runtime: %s)", id, Statistics.readableTimespan(duration))); } endSeverity = Severity.max(endSeverity, tasksFailureRateSeverity, runtimeSeverity); diff --git a/app/org/apache/spark/deploy/history/SparkDataCollection.scala b/app/org/apache/spark/deploy/history/SparkDataCollection.scala index dda3bc49d..ac6abd490 100644 --- a/app/org/apache/spark/deploy/history/SparkDataCollection.scala +++ b/app/org/apache/spark/deploy/history/SparkDataCollection.scala @@ -222,6 +222,13 @@ class SparkDataCollection(applicationEventListener: ApplicationEventListener, stageInfo.description = data.description.getOrElse("") stageInfo.diskBytesSpilled = data.diskBytesSpilled stageInfo.executorRunTime = data.executorRunTime + stageInfo.duration = sparkStageInfo match { + case Some(info: StageInfo) => { + val submissionTime = info.submissionTime.getOrElse(0L) + info.completionTime.getOrElse(submissionTime) - submissionTime + } + case _ => 0L + } stageInfo.inputBytes = data.inputBytes stageInfo.memoryBytesSpilled = data.memoryBytesSpilled stageInfo.numActiveTasks = data.numActiveTasks diff --git a/test/com/linkedin/drelephant/spark/heuristics/BestPropertiesConventionHeuristicTest.java b/test/com/linkedin/drelephant/spark/heuristics/BestPropertiesConventionHeuristicTest.java index eeb2e5ead..32354d8b0 100644 --- a/test/com/linkedin/drelephant/spark/heuristics/BestPropertiesConventionHeuristicTest.java +++ b/test/com/linkedin/drelephant/spark/heuristics/BestPropertiesConventionHeuristicTest.java @@ -34,7 +34,7 @@ public void testPropertiesCheck() { } public void testNullSettings() { - assertEquals(Severity.MODERATE, analyzeJob(getPropertiesAndRemove(SPARK_SERIALIZER))); + assertEquals(Severity.NONE, analyzeJob(getPropertiesAndRemove(SPARK_SERIALIZER))); assertEquals(Severity.NONE, analyzeJob(getPropertiesAndRemove(SPARK_SHUFFLE_MANAGER))); assertEquals(Severity.NONE, analyzeJob(getPropertiesAndRemove(SPARK_EXECUTOR_CORES))); } From 832033214819305961af40607ac3a3deb811dafc Mon Sep 17 00:00:00 2001 From: Min Shen Date: Thu, 20 Aug 2015 13:18:54 -0700 Subject: [PATCH 099/272] LIHADOOP-13747 Update Spark driver name to be consistent with Spark 1.4. --- app/com/linkedin/drelephant/spark/SparkExecutorData.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/com/linkedin/drelephant/spark/SparkExecutorData.java b/app/com/linkedin/drelephant/spark/SparkExecutorData.java index fe04d112d..cf5b0fca9 100644 --- a/app/com/linkedin/drelephant/spark/SparkExecutorData.java +++ b/app/com/linkedin/drelephant/spark/SparkExecutorData.java @@ -10,7 +10,7 @@ * */ public class SparkExecutorData { - public static final String EXECUTOR_DRIVER_NAME = ""; + public static final String EXECUTOR_DRIVER_NAME = "driver"; public static class ExecutorInfo { public String execId; From 14d06fcda19915476a319b27cfeff0e858b039dd Mon Sep 17 00:00:00 2001 From: Akshay Rai Date: Mon, 31 Aug 2015 02:41:57 -0700 Subject: [PATCH 100/272] LIHADOOP-13953 The start script should take arguments to enable evolutions RB=554498 BUGS=LIHADOOP-13953 G=superfriends-reviewers R=fli, rratti, annag A=fli --- app-conf/elephant.conf | 8 ++++++++ start.sh | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 app-conf/elephant.conf diff --git a/app-conf/elephant.conf b/app-conf/elephant.conf new file mode 100644 index 000000000..23e037ded --- /dev/null +++ b/app-conf/elephant.conf @@ -0,0 +1,8 @@ +port=8080 +db_url=localhost +db_name=drelephant +db_user=root +db_password= +#jvm_props="-Devolutionplugin=enabled -DapplyEvolutions.default=true" +keytab_location="/export/apps/hadoop/keytabs/dr_elephant-service.keytab" +keytab_user="elephant/eat1-magicaz01.grid.linkedin.com" diff --git a/start.sh b/start.sh index 2829741f5..9da29226a 100755 --- a/start.sh +++ b/start.sh @@ -109,7 +109,7 @@ else exit 1 fi -OPTS="-Djava.library.path=$JAVA_LIB_PATH -Dhttp.port=$port -Dkeytab.user=$keytab_user -Dkeytab.location=$keytab_location -Ddb.default.url=$db_loc -Ddb.default.user=$db_user -Ddb.default.password=$db_password" +OPTS="$jvm_props -Djava.library.path=$JAVA_LIB_PATH -Dhttp.port=$port -Dkeytab.user=$keytab_user -Dkeytab.location=$keytab_location -Ddb.default.url=$db_loc -Ddb.default.user=$db_user -Ddb.default.password=$db_password" # Start Dr. Elaphant echo "Starting Dr. Elephant ...." From a0850b0e7ac2718833e5dce291c68cde02154fe8 Mon Sep 17 00:00:00 2001 From: Akshay Rai Date: Thu, 27 Aug 2015 00:04:39 -0700 Subject: [PATCH 101/272] LIHADOOP-5177 Update documentation of Dr. Elephant RB=554506 G=superfriends-reviewers R=annag,fli,rratti,mwagner A=fli,vigankid --- README.md | 140 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 98 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 01b823921..8b8c1f476 100644 --- a/README.md +++ b/README.md @@ -1,65 +1,121 @@ -## Dr Elephant +# Dr Elephant + ### Compiling & testing locally +#### Play Setup - One time * To be able to build & run the application, download and install [Play framework 2.2.2](http://downloads.typesafe.com/play/2.2.2/play-2.2.2.zip). -* The pre-installed play command on our boxes will not work as it is configured to look at LinkedIns repos -* If this is your first time working with Dr. Elephant, take the deployed Hadoop jars and put them in the /lib directory: - scp eat1-magicgw01.grid.linkedin.com:/export/apps/hadoop/latest/hadoop-core-*.jar ./lib/. - NOTE: When building dr-elephant, this jar file should be removed or renamed. Otherwise, the hadoop-2 executable built will not work. -* To compile dr-elephant, run the script ./compile.sh. A zip file is created in the 'dist' directory. -* You can run dr-elephant on your desktop and have it point to a cluster. Do the following: - - cd dist;unzip *.zip; # and then cd to the dr-elephant release directory created. - - Copy the cluster's configuration onto a local directory on your machine. - - Copy the hadoop distribution onto a local directory (or, build hadoop locally). - - For Hadoop-2, the following setup is needed: - export HADOOP_HOME=/path/to/project/hadoop/hadoop-dist/target/hadoop-2.3.0_li-SNAPSHOT - export HADOOP_CONF_DIR=/path/to/config/etc/hadoop - export PATH=$HADOOP_HOME/bin:$PATH # because dr-elephant uses 'hadoop classpath' to load the right classes - vim $HADOOP_CONF_DIR/mapred-site.xml - # Make sure to set the job history server co-ordinates if it is not already set - # mapreduce.jobhistory.webapp.addresseat1-hcl0764.grid:19888 - - Set up and start mysql locally on your box, the default port is 3306. Create a database called 'drelephant', if it does not exist. - - You can now start dr-elephant with the following command (assuming you are running mysql locally on port 3306): - ./bin/dr-elephant -Dhttp.port=8089 -Ddb.default.url="jdbc:mysql://ssubrama-ld1.linkedin.biz:3306/drelephant?characterEncoding=UTF-8" -Devolutionplugin=enabled -DapplyEvolutions.default=true - - Note that you can add other properties with the -D option, as -Dprop.name=value - - You can debug dr-elephant using eclispse or idea by adding the argument -Djvm_args="-Xdebug -Xrunjdwp:transport=dt_socket,address=8876,server=y,suspend=y" - This will start dr-elephant and wait until you attach eclipse/idea to port 8876. You can then set breakpoints and debug interactively. +* The pre-installed play command on our boxes will not work as it is configured to look at LinkedIns repos. +* Add the Play installation directory to the system path. + +#### Hadoop Setup - One time +* Setup hadoop locally. You can find instructions to setup a single node cluster [here](http://hadoop.apache.org/docs/current/hadoop-project-dist/hadoop-common/SingleCluster.html). +* Export variable HADOOP\_HOME if you haven't already. +``` +export HADOOP\_HOME=/path/to/hadoop/home +export HADOOP\_CONF\_DIR=$HADOOP_HOME/etc/hadoop +``` + +* Add hadoop to the system path because dr-elephant uses _'hadoop classpath'_ to load the right classes. +``` +export PATH=$HADOOP_HOME/bin:$PATH +``` + +#### Mysql Setup - One time +* Set up and start mysql locally on your box. +* Create a database called 'drelephant'. Use root with no password. +``` +mysql -u root -p +mysql> create database drelephant; +``` +#### Dr. Elephant Setup +* Start Hadoop and run the history server. +* To compile dr-elephant, run the compile script. A zip file is created in the 'dist' directory. +``` +./compile.sh +``` +* Unzip the zip file in dist and change to the dr-elephant release directory created. Henceforth we will refer this as DR_RELEASE. +``` +cd dist; unzip dr-elephant\*.zip; cd dr-elephant\* +``` +* If you are running dr-elephant for the first time after creating the database, you need to enable evolutions. To do so append _-Devolutionplugin=enabled_ and _-DapplyEvolutions.default=true_ to jvm\_props in elephant.conf file. +``` +vim ./app-conf/elephant.conf +jvm\_props="... -Devolutionplugin=enabled -DapplyEvolutions.default=true" +``` +* To start dr-elephant, run the start script specifying a path to the application's configuration files. +``` +$DR\_RELEASE/bin/start.sh -appconf $DR\_RELEASE/../../app-conf +``` +* To stop dr-elephant run, +``` +$DR\_RELEASE/bin/stop.sh +``` +* The dr-elephant logs are generated in the 'dist' directory besides the dr-elephant release. +``` +less $DR\_RELEASE/../logs/elephant/dr_elephant.log +``` ### DB Schema evolutions -When the schema in the model package changes, play will need to be ran to automatically apply the evolution. +When the schema in the model package changes, run play to automatically apply the evolutions. * There is a problem with Ebean where it does not support something like @Index to generate indices for columns of interest * So what we did to work around this is to manually add indices into the sql script. * To do this, we needed to prevent the automatically generated sql to overwrite our modified sql. * The evolution sql file must be changed (by moving or removing the header "To stop Ebean DDL generation, remove this comment and start using Evolutions") to make sure it does not automatically generate new sql. * To re-create the sql file from a new schema in code: - * Backup the file at ./conf/evolutions/default/1.sql - * Remove the file - * Run play in debug mode and browse the page. This causes EBean to generate the new sql file, and automatically apply the evolution. - * Copy over the indices from the old 1.sql file - * Remove the header in the sql file so it does not get overwritten - * Browse the page again to refresh the schema to add the indices. + * Backup the file at ./conf/evolutions/default/1.sql + * Remove the file + * Run play in debug mode and browse the page. This causes EBean to generate the new sql file, and automatically apply the evolution. + * Copy over the indices from the old 1.sql file + * Remove the header in the sql file so it does not get overwritten + * Browse the page again to refresh the schema to add the indices. ### Deployment on the cluster -* SSH into the machine -* sudo as elephant -* go to /export/apps/elephant/ -* unzip the file -* cd dr-elephant*/bin -* To start: ./start.sh -* To stop: ./stop.sh +* SSH into the cluster machine. +* Switch user to _'elephant'_ +``` +sudo -iu elephant +``` +* Change directory to elephant. +``` +cd /export/apps/elephant/ +``` +* Unzip the dr-elephant release and change directory to it. +* To start dr-elephant run the start script. The start script takes an optional argument to the application's conf directory. By default it uses _'/export/apps/elephant/conf'_ +``` +./bin/start.sh +``` +* To stop dr-elephant run, +``` +./bin/stop.sh +``` * To deploy new version, be sure to kill the running process first ### Adding new heuristics * Create a new heuristic and test it. * Create a new view for the heuristic for example helpMapperSpill.scala.html -* Add the details of the heuristic in the HeuristicConf.xml file. The HeuristicConf.xml file requires the following details for each heuristic: - * heuristicname: Name of the heuristic. (e.g. Mapper Spill) - * classname: This should be the fully qualified name of the class (e.g. com.linkedin.drelephant.analysis.mapreduce.heuristics.MapperSpillHeuristic) - * viewname: This should be the fully qualified name of the view. ( e.g. views.html.helpMapperSpill ) -*Run Doctor Elephant, it should now include the new heuristics. +* Add the details of the heuristic in the HeuristicConf.xml file. + * The HeuristicConf.xml file requires the following details for each heuristic: + * **applicationtype**: The type of application analysed by the heuristic. e.g. mapreduce or spark + * **heuristicname**: Name of the heuristic. + * **classname**: Fully qualified name of the class. + * **viewname**: Fully qualified name of the view. + * **hadoopversions**: Versions of Hadoop with which the heuristic is compatible. +* A sample entry in HeuristicConf.xml would look like, +``` + + mapreduce + Job Queue Limit + com.linkedin.drelephant.mapreduce.heuristics.JobQueueLimitHeuristic + views.html.helpJobQueueLimit + + 1 + + +``` +* Run Doctor Elephant, it should now include the new heuristics. From 1f6837a2b2af134dba99a6e20e195334cfb54830 Mon Sep 17 00:00:00 2001 From: Akshay Rai Date: Tue, 8 Sep 2015 22:54:20 -0700 Subject: [PATCH 102/272] LIHADOOP-12802 Occasional Concurrent Modification Exception during Dr. Elephant start RB=563017 G=superfriends-reviewers R=rratti,fli A=rratti --- .../linkedin/drelephant/ElephantContext.java | 48 ++++++------------- 1 file changed, 15 insertions(+), 33 deletions(-) diff --git a/app/com/linkedin/drelephant/ElephantContext.java b/app/com/linkedin/drelephant/ElephantContext.java index 33b83da94..392c91ebe 100644 --- a/app/com/linkedin/drelephant/ElephantContext.java +++ b/app/com/linkedin/drelephant/ElephantContext.java @@ -16,6 +16,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; @@ -199,40 +200,21 @@ private void loadHeuristics() { new HeuristicConfigurationData(HeuristicResult.NO_DATA.getAnalysis(), null, "views.html.helpNoData", null)); } + /** + * Decides what application types can be supported. + * + * An application type is supported if all the below are true. + * 1. A Fetcher is defined in FetcherConf.xml for the application type. + * 2. At least one Heuristic is configured in HeuristicConf.xml for the application type. + * 3. At least one job type is configured in JobTypeConf.xml for the application type. + */ private void configureSupportedApplicationTypes() { Set supportedTypes = Sets.intersection(_typeToFetcher.keySet(), _typeToHeuristics.keySet()); supportedTypes = Sets.intersection(supportedTypes, _appTypeToJobTypes.keySet()); - for (ApplicationType type : _typeToFetcher.keySet()) { - if (!supportedTypes.contains(type)) { - ElephantFetcher removedFetcher = _typeToFetcher.remove(type); - logger.warn("ElephantFetcher class " + removedFetcher.getClass().getName() - + " does not have any matched heuristic rule or job type for application type " + type.getName() - + ", being ignored."); - } - } - - for (ApplicationType type : _typeToHeuristics.keySet()) { - if (!supportedTypes.contains(type)) { - List removedHeuristics = _typeToHeuristics.remove(type); - for (Heuristic removedHeuristic : removedHeuristics) { - logger.warn("Heuristic class " + removedHeuristic.getClass().getName() - + "does not have any matched fetcher or job type for application type " + type.getName() - + ", being ignored."); - } - } - } - - for (ApplicationType type : _appTypeToJobTypes.keySet()) { - if (!supportedTypes.contains(type)) { - List removedJobTypes = _appTypeToJobTypes.remove(type); - for (JobType removedJobType : removedJobTypes) { - logger.warn("JobType " + removedJobType.getName() - + " does not have any matched fetcher or heuristic for application type " + type.getName() - + ", being ignored."); - } - } - } + _typeToFetcher.keySet().retainAll(supportedTypes); + _typeToHeuristics.keySet().retainAll(supportedTypes); + _appTypeToJobTypes.keySet().retainAll(supportedTypes); logger.info("ElephantContext configured:"); for (ApplicationType type : supportedTypes) { @@ -245,9 +227,9 @@ private void configureSupportedApplicationTypes() { } List jobTypes = _appTypeToJobTypes.get(type); - logger.info("ApplicationType: " + type.getName() + ", ElephantFetcher class: " + _typeToFetcher.get(type) - + "Heuristics: [" + StringUtils.join(classes, ", ") + "], JobTypes: [" + StringUtils.join(jobTypes, ", ") - + "]."); + logger.info("Supports " + type.getName() + " application type, using " + _typeToFetcher.get(type) + + " fetcher class with Heuristics [" + StringUtils.join(classes, ", ") + "] and following JobTypes [" + + StringUtils.join(jobTypes, ", ") + "]."); } } From 475cdefa37491a6d7bbc05f68a19e1527f598725 Mon Sep 17 00:00:00 2001 From: akrai Date: Mon, 24 Aug 2015 11:43:38 +0530 Subject: [PATCH 103/272] LIHADOOP-8114 [Opensource Dr. E] Apache License and LinkedIn Copyright Notices RB=551175 G=superfriends-reviewers R=abain,ahsu,annag,csteinba,fli,mwagner,rratti A=csteinba --- .gitignore | 16 + LICENSE | 202 ++++++++ NOTICE | 435 ++++++++++++++++++ README.md | 17 +- app-conf/CounterPublisherConf.xml | 14 + app-conf/FetcherConf.xml | 15 + app-conf/HeuristicConf.xml | 14 + app-conf/JobTypeConf.xml | 15 + app-conf/elephant.config | 16 + app/Global.java | 15 + .../linkedin/drelephant/DaliMetricsAPI.java | 15 + app/com/linkedin/drelephant/DrElephant.java | 15 + .../linkedin/drelephant/ElephantContext.java | 15 + .../linkedin/drelephant/ElephantRunner.java | 15 + .../linkedin/drelephant/HadoopSecurity.java | 15 + .../drelephant/analysis/AnalyticJob.java | 15 + .../analysis/AnalyticJobGenerator.java | 15 + .../analysis/AnalyticJobGeneratorHadoop1.java | 15 + .../analysis/AnalyticJobGeneratorHadoop2.java | 15 + .../drelephant/analysis/ApplicationType.java | 15 + .../drelephant/analysis/ElephantFetcher.java | 15 + .../drelephant/analysis/HDFSContext.java | 15 + .../analysis/HadoopApplicationData.java | 15 + .../analysis/HadoopSystemContext.java | 15 + .../drelephant/analysis/Heuristic.java | 15 + .../drelephant/analysis/HeuristicResult.java | 15 + .../linkedin/drelephant/analysis/JobType.java | 15 + .../drelephant/analysis/Severity.java | 15 + .../mapreduce/MapReduceApplicationData.java | 15 + .../mapreduce/MapReduceCounterHolder.java | 15 + .../mapreduce/MapReduceFetcherHadoop1.java | 15 + .../mapreduce/MapReduceFetcherHadoop2.java | 15 + .../mapreduce/MapReduceTaskData.java | 15 + .../heuristics/GenericDataSkewHeuristic.java | 15 + .../heuristics/GenericMemoryHeuristic.java | 15 + .../heuristics/JobQueueLimitHeuristic.java | 15 + .../heuristics/MapperDataSkewHeuristic.java | 15 + .../heuristics/MapperMemoryHeuristic.java | 15 + .../heuristics/MapperSpeedHeuristic.java | 15 + .../heuristics/MapperSpillHeuristic.java | 15 + .../heuristics/MapperTimeHeuristic.java | 15 + .../heuristics/ReducerDataSkewHeuristic.java | 15 + .../heuristics/ReducerMemoryHeuristic.java | 15 + .../heuristics/ReducerTimeHeuristic.java | 15 + .../heuristics/ShuffleSortHeuristic.java | 15 + .../linkedin/drelephant/math/Statistics.java | 15 + .../drelephant/notifications/EmailThread.java | 15 + .../spark/SparkApplicationData.java | 15 + .../spark/SparkEnvironmentData.java | 15 + .../drelephant/spark/SparkExecutorData.java | 15 + .../drelephant/spark/SparkGeneralData.java | 15 + .../spark/SparkJobProgressData.java | 15 + .../drelephant/spark/SparkStorageData.java | 15 + .../BestPropertiesConventionHeuristic.java | 15 + .../heuristics/EventLogLimitHeuristic.java | 15 + .../heuristics/ExecutorLoadHeuristic.java | 15 + .../spark/heuristics/JobRuntimeHeuristic.java | 15 + .../heuristics/MemoryLimitHeuristic.java | 15 + .../heuristics/StageRuntimeHeuristic.java | 15 + .../util/HeuristicConfiguration.java | 15 + .../util/HeuristicConfigurationData.java | 15 + .../drelephant/util/InfoExtractor.java | 15 + .../linkedin/drelephant/util/JobTypeConf.java | 15 + .../drelephant/util/MemoryFormatUtils.java | 15 + app/com/linkedin/drelephant/util/Utils.java | 15 + .../drelephant/util/YarnURLUtils.java | 15 + app/controllers/Application.java | 15 + app/controllers/PaginationStats.java | 15 + app/controllers/QueryHandler.java | 15 + app/model/JobHeuristicResult.java | 15 + app/model/JobResult.java | 15 + app/model/StringResult.java | 15 + .../deploy/history/SparkDataCollection.scala | 15 + .../spark/deploy/history/SparkFSFetcher.scala | 15 + .../StorageStatusTrackingListener.scala | 15 + app/views/emailcritical.scala.html | 15 + app/views/help.scala.html | 16 + app/views/helpJobQueueLimit.scala.html | 15 + app/views/helpMapperDataSkew.scala.html | 15 + app/views/helpMapperMemory.scala.html | 15 + app/views/helpMapperSpeed.scala.html | 15 + app/views/helpMapperSpill.scala.html | 15 + app/views/helpMapperTime.scala.html | 15 + app/views/helpMemory.scala.html | 15 + app/views/helpNoData.scala.html | 15 + app/views/helpReducerDataSkew.scala.html | 15 + app/views/helpReducerMemory.scala.html | 15 + app/views/helpReducerTime.scala.html | 15 + app/views/helpShuffleSort.scala.html | 15 + app/views/helpSparkBestProperties.scala.html | 15 + app/views/helpSparkEventLogLimit.scala.html | 15 + app/views/helpSparkExecutorLoad.scala.html | 15 + app/views/helpSparkJobRuntime.scala.html | 15 + app/views/helpSparkMemoryLimit.scala.html | 15 + app/views/helpSparkStageRuntime.scala.html | 15 + app/views/index.scala.html | 15 + app/views/main.scala.html | 16 + app/views/multijob.scala.html | 15 + app/views/pagination.scala.html | 15 + app/views/relatedjob.scala.html | 15 + app/views/search.scala.html | 15 + app/views/singlejob.scala.html | 15 + app/views/tags/column.scala.html | 15 + app/views/tags/panel.scala.html | 15 + build.sbt | 16 + compile.sh | 16 + conf/application.conf | 16 + conf/evolutions/default/1.sql | 16 + conf/evolutions/default/2.sql | 16 + conf/evolutions/default/3.sql | 16 + conf/evolutions/default/4.sql | 16 + conf/evolutions/default/5.sql | 16 + conf/evolutions/default/6.sql | 16 + conf/log4j.properties | 16 + conf/routes | 16 + project/build.properties | 16 + project/plugins.sbt | 15 + start.sh | 16 + stop.sh | 16 + .../JobQueueLimitHeuristicTest.java | 15 + .../MapperDataSkewHeuristicTest.java | 15 + .../heuristics/MapperMemoryHeuristicTest.java | 15 + .../heuristics/MapperSpeedHeuristicTest.java | 15 + .../heuristics/MapperSpillHeuristicTest.java | 15 + .../heuristics/MapperTimeHeuristicTest.java | 15 + .../ReducerDataSkewHeuristicTest.java | 15 + .../ReducerMemoryHeuristicTest.java | 15 + .../heuristics/ReducerTimeHeuristicTest.java | 15 + .../heuristics/ShuffleSortHeuristicTest.java | 15 + .../spark/MockSparkApplicationData.java | 15 + .../util/MemoryFormatUtilsTest.java | 15 + .../linkedin/drelephant/util/UtilsTest.java | 15 + 132 files changed, 2604 insertions(+), 1 deletion(-) create mode 100644 LICENSE create mode 100644 NOTICE diff --git a/.gitignore b/.gitignore index 9e140102f..8d1f43f20 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,19 @@ +# +# Copyright 2015 LinkedIn Corp. +# +# 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. +# + # general *~ *.log diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..5e8a14cef --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2015 LinkedIn Corp. All rights reserved. + + 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. \ No newline at end of file diff --git a/NOTICE b/NOTICE new file mode 100644 index 000000000..7068a4426 --- /dev/null +++ b/NOTICE @@ -0,0 +1,435 @@ +Copyright 2015 LinkedIn Corp. + +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. + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/) + +======================================================================== +Common Development and Distribution License 1.0 +======================================================================== + +The following components are provided under the Common Development and Distribution License 1.0. See project link for details. + + (CDDL 1.0) Glassfish Jasper (org.mortbay.jetty:jsp-2.1:6.1.14 - http://jetty.mortbay.org/project/modules/jsp-2.1) + (CDDL 1.0) Servlet Specification 2.5 API (org.mortbay.jetty:servlet-api-2.5:6.1.14 - http://jetty.mortbay.org/project/modules/servlet-api-2.5) + (COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0) (GNU General Public Library) Streaming API for XML (javax.xml.stream:stax-api:1.0-2 - no url defined) + (COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0) JavaBeans Activation Framework (JAF) (javax.activation:activation:1.1 - http://java.sun.com/products/javabeans/jaf/index.jsp) + +======================================================================== +Common Development and Distribution License 1.1 +======================================================================== + +The following components are provided under the Common Development and Distribution License 1.1. See project link for details. + + (CDDL 1.1) (GPL2 w/ CPE) JAXB API bundle for GlassFish V3 (javax.xml.bind:jaxb-api:2.2.2 - https://jaxb.dev.java.net/) + (CDDL 1.1) (GPL2 w/ CPE) JAXB RI (com.sun.xml.bind:jaxb-impl:2.2.3-1 - http://jaxb.java.net/) + (CDDL 1.1) (GPL2 w/ CPE) jersey-core (com.sun.jersey:jersey-core:1.9 - https://jersey.java.net/jersey-core/) + (CDDL 1.1) (GPL2 w/ CPE) jersey-guice (com.sun.jersey.contribs:jersey-guice:1.9 - https://jersey.java.net/jersey-contribs/jersey-guice/) + (CDDL 1.1) (GPL2 w/ CPE) jersey-json (com.sun.jersey:jersey-json:1.9 - https://jersey.java.net/jersey-json/) + (CDDL 1.1) (GPL2 w/ CPE) jersey-server (com.sun.jersey:jersey-server:1.9 - https://jersey.java.net/jersey-server/) + + +======================================================================== +NOTICE files +======================================================================== + +The following NOTICEs pertain to software distributed with this project. + +Apache Avro +Copyright 2009-2013 The Apache Software Foundation + +Apache Commons BeanUtils +Copyright 2000-2008 The Apache Software Foundation + +Apache Commons CLI +Copyright 2001-2009 The Apache Software Foundation + +Apache Commons Codec +Copyright 2002-2011 The Apache Software Foundation + +-------------------------------------------------------------------------------- +src/test/org/apache/commons/codec/language/DoubleMetaphoneTest.java contains +test data from http://aspell.sourceforge.net/test/batch0.tab. + +Copyright (C) 2002 Kevin Atkinson (kevina@gnu.org). Verbatim copying +and distribution of this entire article is permitted in any medium, +provided this notice is preserved. +-------------------------------------------------------------------------------- + +Apache Commons Collections +Copyright 2001-2008 The Apache Software Foundation + +Apache Commons Compress +Copyright 2002-2012 The Apache Software Foundation + +Apache Commons Configuration +Copyright 2001-2008 The Apache Software Foundation + +Apache Commons Daemon +Copyright 1999-2013 The Apache Software Foundation + +Apache Jakarta Commons Digester +Copyright 2001-2006 The Apache Software Foundation + +Apache Jakarta HttpClient +Copyright 1999-2007 The Apache Software Foundation + +Apache Commons IO +Copyright 2002-2012 The Apache Software Foundation + +Apache Commons Lang +Copyright 2001-2011 The Apache Software Foundation + +Apache Commons Logging +Copyright 2003-2013 The Apache Software Foundation + +Apache Commons Math +Copyright 2001-2012 The Apache Software Foundation + +=============================================================================== + +The BracketFinder (package org.apache.commons.math3.optimization.univariate) +and PowellOptimizer (package org.apache.commons.math3.optimization.general) +classes are based on the Python code in module "optimize.py" (version 0.5) +developed by Travis E. Oliphant for the SciPy library (http://www.scipy.org/) +Copyright © 2003-2009 SciPy Developers. +=============================================================================== + +The LinearConstraint, LinearObjectiveFunction, LinearOptimizer, +RelationShip, SimplexSolver and SimplexTableau classes in package +org.apache.commons.math3.optimization.linear include software developed by +Benjamin McCann (http://www.benmccann.com) and distributed with +the following copyright: Copyright 2009 Google Inc. +=============================================================================== + +This product includes software developed by the +University of Chicago, as Operator of Argonne National +Laboratory. +The LevenbergMarquardtOptimizer class in package +org.apache.commons.math3.optimization.general includes software +translated from the lmder, lmpar and qrsolv Fortran routines +from the Minpack package +Minpack Copyright Notice (1999) University of Chicago. All rights reserved +=============================================================================== + +The GraggBulirschStoerIntegrator class in package +org.apache.commons.math3.ode.nonstiff includes software translated +from the odex Fortran routine developed by E. Hairer and G. Wanner. +Original source copyright: +Copyright (c) 2004, Ernst Hairer +=============================================================================== + +The EigenDecompositionImpl class in package +org.apache.commons.math3.linear includes software translated +from some LAPACK Fortran routines. Original source copyright: +Copyright (c) 1992-2008 The University of Tennessee. All rights reserved. +=============================================================================== + +The MersenneTwister class in package org.apache.commons.math3.random +includes software translated from the 2002-01-26 version of +the Mersenne-Twister generator written in C by Makoto Matsumoto and Takuji +Nishimura. Original source copyright: +Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura, +All rights reserved +=============================================================================== + +The LocalizedFormatsTest class in the unit tests is an adapted version of +the OrekitMessagesTest class from the orekit library distributed under the +terms of the Apache 2 licence. Original source copyright: +Copyright 2010 CS Systèmes d'Information +=============================================================================== + +The HermiteInterpolator class and its corresponding test have been imported from +the orekit library distributed under the terms of the Apache 2 licence. Original +source copyright: +Copyright 2010-2012 CS Systèmes d'Information +=============================================================================== + +The creation of the package "o.a.c.m.analysis.integration.gauss" was inspired +by an original code donated by Sébastien Brisard. +=============================================================================== + +The complete text of licenses and disclaimers associated with the the original +sources enumerated above at the time of code translation are in the LICENSE.txt +file. + +Apache Commons Net +Copyright 2001-2012 The Apache Software Foundation + +Google Guice - Core Library +Copyright 2006-2011 Google, Inc. + +Google Guice - Extensions - Servlet +Copyright 2006-2011 Google, Inc. + +This product includes software developed by Google +Snappy: http://code.google.com/p/snappy/ +New BSD License + +Apache HttpComponents HttpClient +Copyright 1999-2013 The Apache Software Foundation + +Apache HttpComponents HttpCore +Copyright 2005-2013 The Apache Software Foundation + +This project contains annotations derived from JCIP-ANNOTATIONS +Copyright (c) 2005 Brian Goetz and Tim Peierls. See http://www.jcip.net + +Apache log4j +Copyright 2007 The Apache Software Foundation + +This product includes/uses SLF4J (http://www.slf4j.org/), +Copyright (c) 2004-2008 QOS.ch + +This product includes/uses XZ for Java (http://tukaani.org/xz/java.html) +Lasse Collin and others + +This product includes/uses Guava (http://code.google.com/p/guava-libraries/) +Copyright (C) 2006 Google Inc. + +This product includes Jettison (http://jettison.codehaus.org) +Copyright 2006 Envoi Solutions LLC + +This product includes/uses xmlenc (http://xmlenc.sourceforge.net/) +Copyright 2003-2005, Ernst de Haan + +This product includes/uses javax.inject (http://code.google.com/p/atinject/) +The Apache Software License, Version 2.0 + +This product includes/uses FindBugs-jsr305 (http://findbugs.sourceforge.net/) +The Apache Software License, Version 2.0 (com.google.code.findbugs:jsr305:1.3.9) + +This product includes/uses jasper-compiler +The Apache Software License, Version 2.0 + +This product includes/uses jasper-runtime +The Apache Software License, Version 2.0 + +This product includes/uses bootstrap (http://getbootstrap.com/), +The Apache Software License, Version 2.0 + +This product includes/uses Apache Hadoop Annotations +The Apache Software License, Version 2.0 + +This product includes/uses Apache Hadoop Auth +The Apache Software License, Version 2.0 + +This product includes/uses Apache Hadoop Common +The Apache Software License, Version 2.0 + +This product includes/uses Apache Hadoop HDFS +The Apache Software License, Version 2.0 + +This product includes/uses Apache Hadoop Mapreduce Client Core +The Apache Software License, Version 2.0 + +This product includes/uses Apache Hadoop Yarn API +The Apache Software License, Version 2.0 + +This product includes/uses Apache Hadoop Yarn Common +The Apache Software License, Version 2.0 + +This product includes/uses JSch (http://www.jcraft.com/jsch/) +3-clause BSD + +This product includes/uses Protocol Buffer Java API (http://code.google.com/p/protobuf) +BSD or BSD-style License + +This product includes/uses java-xmlbuilder (http://iharder.sourceforge.net/current/java/base64/) +Robert Harder - public domain Base64 encoder/decoder + +========================================================================= +The Netty Project +========================================================================= + +Please visit the Netty web site for more information: + + * http://netty.io/ + +Copyright 2011 The Netty Project + +The Netty Project 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. + +Also, please refer to each LICENSE..txt file, which is located in +the 'license' directory of the distribution file, for the license terms of the +components that this product depends on. + +------------------------------------------------------------------------------- +This product contains the extensions to Java Collections Framework which has +been derived from the works by JSR-166 EG, Doug Lea, and Jason T. Greene: + + * LICENSE: + * license/LICENSE.jsr166y.txt (Public Domain) + * HOMEPAGE: + * http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/ + * http://viewvc.jboss.org/cgi-bin/viewvc.cgi/jbosscache/experimental/jsr166/ + +This product contains a modified version of Robert Harder's Public Domain +Base64 Encoder and Decoder, which can be obtained at: + + * LICENSE: + * license/LICENSE.base64.txt (Public Domain) + * HOMEPAGE: + * http://iharder.sourceforge.net/current/java/base64/ + +This product contains a modified version of 'JZlib', a re-implementation of +zlib in pure Java, which can be obtained at: + + * LICENSE: + * license/LICENSE.jzlib.txt (BSD Style License) + * HOMEPAGE: + * http://www.jcraft.com/jzlib/ + +This product optionally depends on 'Protocol Buffers', Google's data +interchange format, which can be obtained at: + + * LICENSE: + * license/LICENSE.protobuf.txt (New BSD License) + * HOMEPAGE: + * http://code.google.com/p/protobuf/ + +This product optionally depends on 'SLF4J', a simple logging facade for Java, +which can be obtained at: + + * LICENSE: + * license/LICENSE.slf4j.txt (MIT License) + * HOMEPAGE: + * http://www.slf4j.org/ + +This product optionally depends on 'Apache Commons Logging', a logging +framework, which can be obtained at: + + * LICENSE: + * license/LICENSE.commons-logging.txt (Apache License 2.0) + * HOMEPAGE: + * http://commons.apache.org/logging/ + +This product optionally depends on 'Apache Log4J', a logging framework, +which can be obtained at: + + * LICENSE: + * license/LICENSE.log4j.txt (Apache License 2.0) + * HOMEPAGE: + * http://logging.apache.org/log4j/ + +This product optionally depends on 'JBoss Logging', a logging framework, +which can be obtained at: + + * LICENSE: + * license/LICENSE.jboss-logging.txt (GNU LGPL 2.1) + * HOMEPAGE: + * http://anonsvn.jboss.org/repos/common/common-logging-spi/ + +This product optionally depends on 'Apache Felix', an open source OSGi +framework implementation, which can be obtained at: + + * LICENSE: + * license/LICENSE.felix.txt (Apache License 2.0) + * HOMEPAGE: + * http://felix.apache.org/ + +This product optionally depends on 'Webbit', a Java event based +WebSocket and HTTP server: + + * LICENSE: + * license/LICENSE.webbit.txt (BSD License) + * HOMEPAGE: + * https://github.com/joewalnes/webbit + +========================================================================= +# Jackson JSON processor +========================================================================= + +Jackson is a high-performance, Free/Open Source JSON processing library. +It was originally written by Tatu Saloranta (tatu.saloranta@iki.fi), and has +been in development since 2007. +It is currently developed by a community of developers, as well as supported +commercially by FasterXML.com. + +## Licensing + +Jackson core and extension components may licensed under different licenses. +To find the details that apply to this artifact see the accompanying LICENSE file. +For more information, including possible other licensing options, contact +FasterXML.com (http://fasterxml.com). + +## Credits + +A list of contributors may be found from CREDITS file, which is included +in some artifacts (usually source distributions); but is always available +from the source code management (SCM) system project uses. + +========================================================================= +== NOTICE file corresponding to section 4(d) of the Apache License, == +== Version 2.0, in this case for the distribution of jets3t. == +========================================================================= + +This product includes software developed by: + +The Apache Software Foundation (http://www.apache.org/). + +The ExoLab Project (http://www.exolab.org/) + +Sun Microsystems (http://www.sun.com/) + +Codehaus (http://castor.codehaus.org) + +Safehaus (http://jug.safehaus.org/Home) + +============================================================== + Jetty Web Container + Copyright 1995-2009 Mort Bay Consulting Pty Ltd +============================================================== + +The Jetty Web Container is Copyright Mort Bay Consulting Pty Ltd +unless otherwise noted. It is licensed under the apache 2.0 +license. + +The javax.servlet package used by Jetty is copyright +Sun Microsystems, Inc and Apache Software Foundation. It is +distributed under the Common Development and Distribution License. +You can obtain a copy of the license at +https://glassfish.dev.java.net/public/CDDLv1.0.html. + +The UnixCrypt.java code ~Implements the one way cryptography used by +Unix systems for simple password protection. Copyright 1996 Aki Yoshida, +modified April 2001 by Iris Van den Broeke, Daniel Deville. +Permission to use, copy, modify and distribute UnixCrypt +for non-commercial or commercial purposes and without fee is +granted provided that the copyright notice appears in all copies. + +The default JSP implementation is provided by the Glassfish JSP engine +from project Glassfish http://glassfish.dev.java.net. Copyright 2005 +Sun Microsystems, Inc. and portions Copyright Apache Software Foundation. + +Some portions of the code are Copyright: + 2006 Tim Vernum + 1999 Jason Gilbert. + +The jboss integration module contains some LGPL code. + +The win32 Java Service Wrapper (v3.2.3) is Copyright (c) 1999, 2006 +Tanuki Software, Inc. and 2001 Silver Egg Technology. It is +covered by an open license which is viewable at +http://svn.codehaus.org/jetty/jetty/branches/jetty-6.1/extras/win32service/LICENSE.txt \ No newline at end of file diff --git a/README.md b/README.md index 8b8c1f476..b3dcf9983 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,20 @@ -# Dr Elephant +# +# Copyright 2015 LinkedIn Corp. +# +# 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. +# +# Dr Elephant ### Compiling & testing locally diff --git a/app-conf/CounterPublisherConf.xml b/app-conf/CounterPublisherConf.xml index 0a7020c35..cdc3dfde1 100644 --- a/app-conf/CounterPublisherConf.xml +++ b/app-conf/CounterPublisherConf.xml @@ -1,5 +1,19 @@ + diff --git a/app-conf/JobTypeConf.xml b/app-conf/JobTypeConf.xml index 8a384d777..4655c8bd7 100644 --- a/app-conf/JobTypeConf.xml +++ b/app-conf/JobTypeConf.xml @@ -1,4 +1,19 @@ + + @for((flowexecurl, jobs) <- flowexecmap) { + + } +

+
+ } + } + +
diff --git a/app/views/main.scala.html b/app/views/main.scala.html index 03cb8db91..e8f1fa291 100644 --- a/app/views/main.scala.html +++ b/app/views/main.scala.html @@ -55,6 +55,7 @@
diff --git a/conf/routes b/conf/routes index cb44ca914..dfe413fe3 100644 --- a/conf/routes +++ b/conf/routes @@ -24,12 +24,14 @@ GET /dashboard/:page controllers.Application.dashboard(page: Int) GET /help controllers.Application.help() GET /email controllers.Application.testEmail() GET /search controllers.Application.search() +GET /compare controllers.Application.compare() GET /flowrelated controllers.Application.flowRelated() GET /alljobexecs controllers.Application.allJobExecs() GET /rest/job controllers.Application.restJobResult(id: String) GET /rest/jobexec controllers.Application.restJobExecResult(url: String) GET /rest/flowexec controllers.Application.restFlowExecResult(url: String) GET /rest/search controllers.Application.restSearch() +GET /rest/compare controllers.Application.restCompare() # Map static resources from the /public folder to the /assets URL path diff --git a/public/css/main.css b/public/css/main.css index a5e0abeb4..29075667f 100644 --- a/public/css/main.css +++ b/public/css/main.css @@ -66,4 +66,16 @@ a.list-group-item-danger.active:focus { .label-severe[href]:hover, .label-severe[href]:focus { background-color: #da6425; +} + +.list-group-item-alternate { + position: relative; + display: block; + padding: 10px 15px; + margin-bottom: -1px; + background-color: #fff; + border: 1px solid #ddd; +} +.list-group-item-alternate:nth-child(even){ + background-color: #f2f2f2; } \ No newline at end of file From 1ca9c89e980f17b1cec69b9d518c5865ae9b0fd4 Mon Sep 17 00:00:00 2001 From: Yitong Zhou Date: Tue, 29 Sep 2015 10:57:12 -0700 Subject: [PATCH 109/272] LIHADOOP-14722: Adjust some Spark heuristic parameters of Dr Elephant RB=577602 G=superfriends-reviewers R=fli,mshen A=mshen,fli --- .../heuristics/ExecutorLoadHeuristic.java | 35 ++++++++++++++----- .../heuristics/MemoryLimitHeuristic.java | 2 +- .../heuristics/MemoryLimitHeuristicTest.java | 11 +++--- 3 files changed, 32 insertions(+), 16 deletions(-) diff --git a/app/com/linkedin/drelephant/spark/heuristics/ExecutorLoadHeuristic.java b/app/com/linkedin/drelephant/spark/heuristics/ExecutorLoadHeuristic.java index 50000f113..6dc5487f8 100644 --- a/app/com/linkedin/drelephant/spark/heuristics/ExecutorLoadHeuristic.java +++ b/app/com/linkedin/drelephant/spark/heuristics/ExecutorLoadHeuristic.java @@ -32,6 +32,7 @@ */ public class ExecutorLoadHeuristic implements Heuristic { public static final String HEURISTIC_NAME = "Spark Executor Load Balance"; + private static final long MEMORY_OBSERVATION_THRESHOLD = MemoryFormatUtils.stringToBytes("1 MB"); private class ValueObserver { private final long[] _values; @@ -104,24 +105,30 @@ public HeuristicResult apply(SparkApplicationData data) { ValueObserver durations = new ValueObserver(numNonDriverExe); ValueObserver inputBytes = new ValueObserver(numNonDriverExe); ValueObserver outputBytes = new ValueObserver(numNonDriverExe); - ValueObserver totalTasks = new ValueObserver(numNonDriverExe); int i = 0; for (String exeId : executors) { if (!exeId.equals(EXECUTOR_DRIVER_NAME)) { SparkExecutorData.ExecutorInfo info = executorData.getExecutorInfo(exeId); - peakMems.set(i, info.memUsed); + + /* Ignore the memory variation and consider it as 0 if it is too small + * The deviation of memory usage in KB level is too fluctuating to track. + */ + if (info.memUsed < MEMORY_OBSERVATION_THRESHOLD) { + peakMems.set(i, 0L); + } else { + peakMems.set(i, info.memUsed); + } + durations.set(i, info.duration); inputBytes.set(i, info.inputBytes); outputBytes.set(i, info.outputBytes); - totalTasks.set(i, info.totalTasks); i += 1; } } - Severity severity = Severity.max(getMerticDeviationSeverity(peakMems), getMerticDeviationSeverity(durations), - getMerticDeviationSeverity(inputBytes), getMerticDeviationSeverity(outputBytes), - getMerticDeviationSeverity(totalTasks)); + Severity severity = Severity.max(getLooserMetricDeviationSeverity(peakMems), getMerticDeviationSeverity(durations), + getMerticDeviationSeverity(inputBytes), getLooserMetricDeviationSeverity(outputBytes)); HeuristicResult result = new HeuristicResult(getHeuristicName(), severity); @@ -139,15 +146,25 @@ public HeuristicResult apply(SparkApplicationData data) { .format("%s (%s~%s)", MemoryFormatUtils.bytesToString(outputBytes.getAvg()), MemoryFormatUtils.bytesToString(outputBytes.getMin()), MemoryFormatUtils.bytesToString(outputBytes.getMax()))); - result.addDetail("Average task number", - String.format("%s (%s~%s)", totalTasks.getAvg(), totalTasks.getMin(), totalTasks.getMax())); return result; } + /** + * Some metrics by nature could deviate a bit more than other metrics. This method basically allows some metrics + * to have looser severity thresholds. + * + * @param ob + * @return the corresponding severity + */ + private static Severity getLooserMetricDeviationSeverity(ValueObserver ob) { + double diffFactor = ob.getDeviationFactor(); + return Severity.getSeverityAscending(diffFactor, 0.8d, 1d, 1.2d, 1.4d); + } + private static Severity getMerticDeviationSeverity(ValueObserver ob) { double diffFactor = ob.getDeviationFactor(); - return Severity.getSeverityAscending(diffFactor, 0.2d, 0.4d, 0.6d, 0.8d); + return Severity.getSeverityAscending(diffFactor, 0.4d, 0.6d, 0.8d, 1.0d); } @Override diff --git a/app/com/linkedin/drelephant/spark/heuristics/MemoryLimitHeuristic.java b/app/com/linkedin/drelephant/spark/heuristics/MemoryLimitHeuristic.java index 92bbef27a..cd8941471 100644 --- a/app/com/linkedin/drelephant/spark/heuristics/MemoryLimitHeuristic.java +++ b/app/com/linkedin/drelephant/spark/heuristics/MemoryLimitHeuristic.java @@ -154,7 +154,7 @@ public static Severity getTotalMemorySeverity(long memory) { private static Severity getMemoryUtilizationSeverity(long peakMemory, long totalStorageMemory) { double fraction = peakMemory * 1.0 / totalStorageMemory; - if (totalStorageMemory < MemoryFormatUtils.stringToBytes("10 GB")) { + if (totalStorageMemory < MemoryFormatUtils.stringToBytes("500 GB")) { return Severity.NONE; } else { return Severity.getSeverityDescending(fraction, 0.8d, 0.6d, 0.4d, 0.2d); diff --git a/test/com/linkedin/drelephant/spark/heuristics/MemoryLimitHeuristicTest.java b/test/com/linkedin/drelephant/spark/heuristics/MemoryLimitHeuristicTest.java index db0fce48f..539521d16 100644 --- a/test/com/linkedin/drelephant/spark/heuristics/MemoryLimitHeuristicTest.java +++ b/test/com/linkedin/drelephant/spark/heuristics/MemoryLimitHeuristicTest.java @@ -41,14 +41,13 @@ public void testMemoryUtilizationRule() { // When the total memory is too low, ignore the ratio calculation assertEquals(Severity.NONE, analyzeJob(1, "1G", "1G", "0B")); // When we barely pass the safe zone - assertEquals(Severity.CRITICAL, analyzeJob(20, "1G", "1G", "0B")); + assertEquals(Severity.CRITICAL, analyzeJob(1000, "1G", "1G", "0B")); // Normal situations - assertEquals(Severity.NONE, analyzeJob(100, "1G", "1G", getPeakMemory(1.0d, 100, "1G"))); - assertEquals(Severity.LOW, analyzeJob(100, "1G", "1G", getPeakMemory(0.7d, 100, "1G"))); - assertEquals(Severity.MODERATE, analyzeJob(100, "1G", "1G", getPeakMemory(0.5d, 100, "1G"))); - assertEquals(Severity.SEVERE, analyzeJob(100, "1G", "1G", getPeakMemory(0.3d, 100, "1G"))); - assertEquals(Severity.CRITICAL, analyzeJob(100, "1G", "1G", getPeakMemory(0.1d, 100, "1G"))); + assertEquals(Severity.LOW, analyzeJob(1000, "1G", "1G", getPeakMemory(0.7d, 1000, "1G"))); + assertEquals(Severity.MODERATE, analyzeJob(1000, "1G", "1G", getPeakMemory(0.5d, 1000, "1G"))); + assertEquals(Severity.SEVERE, analyzeJob(1000, "1G", "1G", getPeakMemory(0.3d, 1000, "1G"))); + assertEquals(Severity.CRITICAL, analyzeJob(1000, "1G", "1G", getPeakMemory(0.1d, 1000, "1G"))); } public void testCombinedRules() { From a8a9a524a963fa2e6973242b90c53b4ad0376e9a Mon Sep 17 00:00:00 2001 From: Akshay Rai Date: Wed, 30 Sep 2015 03:59:59 -0700 Subject: [PATCH 110/272] LIHADOOP-14688 Adding a timestamp to Dr. Elephant search result page RB=578468 G=superfriends-reviewers R=fli,jdintruf A=fli --- app/views/compareresults.scala.html | 7 +++++-- app/views/multijob.scala.html | 8 +++++++- app/views/relatedjob.scala.html | 6 +++++- app/views/singlejob.scala.html | 3 ++- app/views/tags/formatDate.scala.html | 26 ++++++++++++++++++++++++++ public/css/main.css | 5 ++++- 6 files changed, 49 insertions(+), 6 deletions(-) create mode 100644 app/views/tags/formatDate.scala.html diff --git a/app/views/compareresults.scala.html b/app/views/compareresults.scala.html index 76989aa97..1460e316c 100644 --- a/app/views/compareresults.scala.html +++ b/app/views/compareresults.scala.html @@ -36,8 +36,11 @@

@title

-

[@result.username][@result.jobType] @result.jobId

- @result.jobName +
+

[@result.username] [@result.jobType] @result.jobId

+
+
@(tags.formatDate(result.analysisTime))
+
@result.jobName

@for(heuristicResult <- result.heuristicResults) { diff --git a/app/views/multijob.scala.html b/app/views/multijob.scala.html index e70ebf1d4..fbebd7d1f 100644 --- a/app/views/multijob.scala.html +++ b/app/views/multijob.scala.html @@ -22,7 +22,13 @@

@title

@for(result <- results) { -

[@result.username][@result.jobType] @result.jobId

@result.jobName

+

+

+

[@result.username] [@result.jobType] @result.jobId

+
+
@(tags.formatDate(result.analysisTime))
+
@result.jobName
+

@for(heuristicResult <- result.heuristicResults) { @heuristicResult.analysisName diff --git a/app/views/relatedjob.scala.html b/app/views/relatedjob.scala.html index 99b304b31..794f9d0c6 100644 --- a/app/views/relatedjob.scala.html +++ b/app/views/relatedjob.scala.html @@ -31,7 +31,11 @@

@for(result <- jobs) {

-

[@result.username][@result.jobType] @result.jobId

@result.jobName +
+

[@result.username] [@result.jobType] @result.jobId

+
+
@(tags.formatDate(result.analysisTime))
+
@result.jobName

@for(heuristicResult <- result.heuristicResults) {

-

[@result.username] [@result.jobType] @result.jobId

+

[@result.username] [@result.jobType] @result.jobId

+
@(tags.formatDate(result.analysisTime))
@result.jobName
diff --git a/app/views/tags/formatDate.scala.html b/app/views/tags/formatDate.scala.html new file mode 100644 index 000000000..4fd2052ac --- /dev/null +++ b/app/views/tags/formatDate.scala.html @@ -0,0 +1,26 @@ +@* +* Copyright 2015 LinkedIn Corp. +* +* 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. +*@ + +@(time: Long) +@{ + import java.sql.Timestamp + import java.text.DateFormat + import java.util.TimeZone + + val ts = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.LONG, Locale.ENGLISH) + ts.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles")) + ts.format(new Timestamp(time)) +} \ No newline at end of file diff --git a/public/css/main.css b/public/css/main.css index 29075667f..b77a82e08 100644 --- a/public/css/main.css +++ b/public/css/main.css @@ -78,4 +78,7 @@ a.list-group-item-danger.active:focus { } .list-group-item-alternate:nth-child(even){ background-color: #f2f2f2; -} \ No newline at end of file +} + +.left{display:inline-block} +.right{display:inline-block; float:right} \ No newline at end of file From 54f1f7ffad149f54c95098faff887b893e409857 Mon Sep 17 00:00:00 2001 From: Fangshi Li Date: Wed, 14 Oct 2015 11:47:38 -0700 Subject: [PATCH 111/272] LIHADOOP-13819: Dr. Elephant heuristic request. GC efficiency --- app-conf/HeuristicConf.xml | 22 ++++++ .../mapreduce/MapReduceCounterHolder.java | 1 + .../heuristics/GenericGCHeuristic.java | 78 +++++++++++++++++++ .../heuristics/MapperGCHeuristic.java | 23 ++++++ .../heuristics/ReducerGCHeuristic.java | 23 ++++++ app/views/helpGC.scala.html | 57 ++++++++++++++ .../heuristics/MapperGCHeuristicTest.java | 56 +++++++++++++ .../heuristics/ReducerGCHeuristicTest.java | 56 +++++++++++++ 8 files changed, 316 insertions(+) create mode 100644 app/com/linkedin/drelephant/mapreduce/heuristics/GenericGCHeuristic.java create mode 100644 app/com/linkedin/drelephant/mapreduce/heuristics/MapperGCHeuristic.java create mode 100644 app/com/linkedin/drelephant/mapreduce/heuristics/ReducerGCHeuristic.java create mode 100644 app/views/helpGC.scala.html create mode 100644 test/com/linkedin/drelephant/mapreduce/heuristics/MapperGCHeuristicTest.java create mode 100644 test/com/linkedin/drelephant/mapreduce/heuristics/ReducerGCHeuristicTest.java diff --git a/app-conf/HeuristicConf.xml b/app-conf/HeuristicConf.xml index f502ea1ce..3e4d36847 100644 --- a/app-conf/HeuristicConf.xml +++ b/app-conf/HeuristicConf.xml @@ -59,6 +59,17 @@ + + mapreduce + Mapper GC + com.linkedin.drelephant.mapreduce.heuristics.MapperGCHeuristic + views.html.helpGC + + 1 + 2 + + + mapreduce Reducer Data Skew @@ -92,6 +103,17 @@ + + mapreduce + Reducer GC + com.linkedin.drelephant.mapreduce.heuristics.ReducerGCHeuristic + views.html.helpGC + + 1 + 2 + + + mapreduce Job Queue Limit diff --git a/app/com/linkedin/drelephant/mapreduce/MapReduceCounterHolder.java b/app/com/linkedin/drelephant/mapreduce/MapReduceCounterHolder.java index 0864da1fc..1b3d4b81a 100644 --- a/app/com/linkedin/drelephant/mapreduce/MapReduceCounterHolder.java +++ b/app/com/linkedin/drelephant/mapreduce/MapReduceCounterHolder.java @@ -122,6 +122,7 @@ public static enum CounterName { SPILLED_RECORDS(GroupName.MapReduce, "SPILLED_RECORDS", "Spilled Records"), CPU_MILLISECONDS(GroupName.MapReduce, "CPU_MILLISECONDS", "CPU time spent (ms)"), + GC_MILLISECONDS(GroupName.MapReduce, "GC_TIME_MILLIS", "GC time elapsed (ms)"), COMMITTED_HEAP_BYTES(GroupName.MapReduce, "COMMITTED_HEAP_BYTES", "Total committed heap usage (bytes)"), PHYSICAL_MEMORY_BYTES(GroupName.MapReduce, "PHYSICAL_MEMORY_BYTES", "Physical memory (bytes) snapshot"), VIRTUAL_MEMORY_BYTES(GroupName.MapReduce, "VIRTUAL_MEMORY_BYTES", "Virtual memory (bytes) snapshot"); diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/GenericGCHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericGCHeuristic.java new file mode 100644 index 000000000..fb30ec44a --- /dev/null +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericGCHeuristic.java @@ -0,0 +1,78 @@ +package com.linkedin.drelephant.mapreduce.heuristics; + +import java.util.ArrayList; +import java.util.List; +import com.linkedin.drelephant.analysis.Heuristic; +import com.linkedin.drelephant.analysis.HeuristicResult; +import com.linkedin.drelephant.analysis.Severity; +import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; +import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.MapReduceTaskData; +import com.linkedin.drelephant.math.Statistics; + +public abstract class GenericGCHeuristic implements Heuristic { + private String _heuristicName; + + @Override + public String getHeuristicName() { + return _heuristicName; + } + + protected GenericGCHeuristic(String heuristicName) { + this._heuristicName = heuristicName; + } + + protected abstract MapReduceTaskData[] getTasks(MapReduceApplicationData data); + + @Override + public HeuristicResult apply(MapReduceApplicationData data) { + + MapReduceTaskData[] tasks = getTasks(data); + List gcMs = new ArrayList(); + List cpuMs = new ArrayList(); + List runtimesMs = new ArrayList(); + + for (MapReduceTaskData task : tasks) { + if (task.timed()) { + runtimesMs.add(task.getTotalRunTimeMs()); + } + gcMs.add(task.getCounters().get(MapReduceCounterHolder.CounterName.GC_MILLISECONDS)); + cpuMs.add(task.getCounters().get(MapReduceCounterHolder.CounterName.CPU_MILLISECONDS)); + } + + long avgRuntimeMs = Statistics.average(runtimesMs); + long avgCpuMs = Statistics.average(cpuMs); + long avgGcMs = Statistics.average(gcMs); + double ratio = avgCpuMs != 0 ? avgGcMs*(1.0)/avgCpuMs: 0; + + Severity severity; + if (tasks.length == 0) { + severity = Severity.NONE; + } else { + severity = getGcRatioSeverity(avgRuntimeMs, avgCpuMs, avgGcMs); + } + + HeuristicResult result = new HeuristicResult(_heuristicName, severity); + + result.addDetail("Number of tasks", Integer.toString(tasks.length)); + result.addDetail("Avg task runtime (ms)", Long.toString(avgRuntimeMs)); + result.addDetail("Avg task CPU time (ms)", Long.toString(avgCpuMs)); + result.addDetail("Avg task GC time (ms)", Long.toString(avgGcMs)); + result.addDetail("Task GC/CPU ratio", Double.toString(ratio)); + return result; + } + + private Severity getGcRatioSeverity(long runtimeMs, long cpuMs, long gcMs) { + double gcRatio = ((double)gcMs)/cpuMs; + Severity ratioSeverity = Severity.getSeverityAscending(gcRatio, 0.01, 0.02, 0.03, 0.04); + // Severity is reduced if task runtime is insignificant + Severity runtimeSeverity = getRuntimeSeverity(runtimeMs); + return Severity.min(ratioSeverity, runtimeSeverity); + } + + private Severity getRuntimeSeverity(long runtimeMs) { + return Severity.getSeverityAscending(runtimeMs, 5 * Statistics.MINUTE_IN_MS, 10 * Statistics.MINUTE_IN_MS, + 12 * Statistics.MINUTE_IN_MS, 15 * Statistics.MINUTE_IN_MS); + } + +} diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperGCHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperGCHeuristic.java new file mode 100644 index 000000000..cc7d2d125 --- /dev/null +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperGCHeuristic.java @@ -0,0 +1,23 @@ +package com.linkedin.drelephant.mapreduce.heuristics; + +import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.MapReduceTaskData; + + +public class MapperGCHeuristic extends GenericGCHeuristic { + public static final String HEURISTIC_NAME = "Mapper GC"; + + public MapperGCHeuristic() { + super(HEURISTIC_NAME); + } + + @Override + public String getHeuristicName() { + return HEURISTIC_NAME; + } + + @Override + protected MapReduceTaskData[] getTasks(MapReduceApplicationData data) { + return data.getMapperData(); + } +} diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerGCHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerGCHeuristic.java new file mode 100644 index 000000000..be8af39a4 --- /dev/null +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerGCHeuristic.java @@ -0,0 +1,23 @@ +package com.linkedin.drelephant.mapreduce.heuristics; + +import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.MapReduceTaskData; + + +public class ReducerGCHeuristic extends GenericGCHeuristic { + public static final String HEURISTIC_NAME = "Reducer GC"; + + public ReducerGCHeuristic() { + super(HEURISTIC_NAME); + } + + @Override + public String getHeuristicName() { + return HEURISTIC_NAME; + } + + @Override + protected MapReduceTaskData[] getTasks(MapReduceApplicationData data) { + return data.getReducerData(); + } +} diff --git a/app/views/helpGC.scala.html b/app/views/helpGC.scala.html new file mode 100644 index 000000000..fe486ccaa --- /dev/null +++ b/app/views/helpGC.scala.html @@ -0,0 +1,57 @@ +@* +* Copyright 2015 LinkedIn Corp. +* +* 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. +*@ +

+ This analysis gauges your job's GC efficiency. It shows the ratio between the task GC time and task CPU time. +

+
Example
+

+

+ + + + + + + + + + + + + + + + + + + + + + + +
Severity: Critical
Number of tasks2205
Avg task runtime (ms)4832
Avg task CPU time (ms)2321
Avg task CPU time (ms)55
Task GC/CPU ratio0.023423
+ +
+

+

Suggestions

+

+ If your task spends a lot time on GC, your job generally needs some code optimization. + There is no one-size-fits-all answer. We are still improving this heuristic. + We set the bar quite loose to avoid this heuristic from bothering you as much as possible. If you still get yellow/red on this one, please take care of it seriously. +

\ No newline at end of file diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperGCHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperGCHeuristicTest.java new file mode 100644 index 000000000..685356cb2 --- /dev/null +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperGCHeuristicTest.java @@ -0,0 +1,56 @@ +package com.linkedin.drelephant.mapreduce.heuristics; + +import com.linkedin.drelephant.analysis.Heuristic; +import com.linkedin.drelephant.analysis.HeuristicResult; +import com.linkedin.drelephant.analysis.Severity; +import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; +import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.MapReduceTaskData; + +import java.io.IOException; +import junit.framework.TestCase; + + +public class MapperGCHeuristicTest extends TestCase { + Heuristic _heuristic = new MapperGCHeuristic(); + private static int NUMTASKS = 100; + + public void testGCCritical() throws IOException { + assertEquals(Severity.CRITICAL, analyzeJob(1000000, 50000, 2000)); + } + + public void testGCSevere() throws IOException { + assertEquals(Severity.SEVERE, analyzeJob(1000000, 50000, 1500)); + } + + public void testGCModerate() throws IOException { + assertEquals(Severity.MODERATE, analyzeJob(1000000, 50000, 1000)); + } + + public void testGCNone() throws IOException { + assertEquals(Severity.NONE, analyzeJob(1000000, 50000, 300)); + } + + public void testShortTasksNone() throws IOException { + assertEquals(Severity.NONE, analyzeJob(100000, 50000, 2000)); + } + + + private Severity analyzeJob(long runtimeMs, long cpuMs, long gcMs) throws IOException { + MapReduceCounterHolder jobCounter = new MapReduceCounterHolder(); + MapReduceTaskData[] mappers = new MapReduceTaskData[NUMTASKS]; + + MapReduceCounterHolder counter = new MapReduceCounterHolder(); + counter.set(MapReduceCounterHolder.CounterName.CPU_MILLISECONDS, cpuMs); + counter.set(MapReduceCounterHolder.CounterName.GC_MILLISECONDS, gcMs); + + int i = 0; + for (; i < NUMTASKS; i++) { + mappers[i] = new MapReduceTaskData(counter, new long[]{runtimeMs, 0 , 0}); + } + + MapReduceApplicationData data = new MapReduceApplicationData().setCounters(jobCounter).setMapperData(mappers); + HeuristicResult result = _heuristic.apply(data); + return result.getSeverity(); + } +} diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerGCHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerGCHeuristicTest.java new file mode 100644 index 000000000..8630dc3d8 --- /dev/null +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerGCHeuristicTest.java @@ -0,0 +1,56 @@ +package com.linkedin.drelephant.mapreduce.heuristics; + +import com.linkedin.drelephant.analysis.Heuristic; +import com.linkedin.drelephant.analysis.HeuristicResult; +import com.linkedin.drelephant.analysis.Severity; +import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; +import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.MapReduceTaskData; + +import java.io.IOException; +import junit.framework.TestCase; + + +public class ReducerGCHeuristicTest extends TestCase { + Heuristic _heuristic = new ReducerGCHeuristic(); + private static int NUMTASKS = 100; + + public void testGCCritical() throws IOException { + assertEquals(Severity.CRITICAL, analyzeJob(1000000, 50000, 2000)); + } + + public void testGCSevere() throws IOException { + assertEquals(Severity.SEVERE, analyzeJob(1000000, 50000, 1500)); + } + + public void testGCModerate() throws IOException { + assertEquals(Severity.MODERATE, analyzeJob(1000000, 50000, 1000)); + } + + public void testGCNone() throws IOException { + assertEquals(Severity.NONE, analyzeJob(1000000, 50000, 300)); + } + + public void testShortTasksNone() throws IOException { + assertEquals(Severity.NONE, analyzeJob(100000, 50000, 2000)); + } + + + private Severity analyzeJob(long runtimeMs, long cpuMs, long gcMs) throws IOException { + MapReduceCounterHolder jobCounter = new MapReduceCounterHolder(); + MapReduceTaskData[] reducers = new MapReduceTaskData[NUMTASKS]; + + MapReduceCounterHolder counter = new MapReduceCounterHolder(); + counter.set(MapReduceCounterHolder.CounterName.CPU_MILLISECONDS, cpuMs); + counter.set(MapReduceCounterHolder.CounterName.GC_MILLISECONDS, gcMs); + + int i = 0; + for (; i < NUMTASKS; i++) { + reducers[i] = new MapReduceTaskData(counter, new long[]{runtimeMs, 0 , 0}); + } + + MapReduceApplicationData data = new MapReduceApplicationData().setCounters(jobCounter).setReducerData(reducers); + HeuristicResult result = _heuristic.apply(data); + return result.getSeverity(); + } +} From b35ea5d89e2587b9d4df9d304961d5cfb36f23f3 Mon Sep 17 00:00:00 2001 From: Fangshi Li Date: Thu, 3 Sep 2015 16:47:30 -0700 Subject: [PATCH 112/272] LIHADOOP-13711: Dr. Elephant should add virtual memory info in its memory heuristics --- .../heuristics/GenericMemoryHeuristic.java | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/GenericMemoryHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericMemoryHeuristic.java index e42855b7a..e112da84b 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/GenericMemoryHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericMemoryHeuristic.java @@ -50,41 +50,46 @@ public HeuristicResult apply(MapReduceApplicationData data) { long containerMem = Long.parseLong(data.getConf().getProperty(_containerMemConf)) * FileUtils.ONE_MB; MapReduceTaskData[] tasks = getTasks(data); - List taskMems = new ArrayList(); + List taskPMems = new ArrayList(); + List taskVMems = new ArrayList(); List runtimesMs = new ArrayList(); - long taskMin = Long.MAX_VALUE; - long taskMax = 0; + long taskPMin = Long.MAX_VALUE; + long taskPMax = 0; for (MapReduceTaskData task : tasks) { - long taskMem = task.getCounters().get(MapReduceCounterHolder.CounterName.PHYSICAL_MEMORY_BYTES); - taskMems.add(taskMem); - taskMin = Math.min(taskMin, taskMem); - taskMax = Math.max(taskMax, taskMem); + long taskPMem = task.getCounters().get(MapReduceCounterHolder.CounterName.PHYSICAL_MEMORY_BYTES); + long taskVMem = task.getCounters().get(MapReduceCounterHolder.CounterName.VIRTUAL_MEMORY_BYTES); + taskPMems.add(taskPMem); + taskPMin = Math.min(taskPMin, taskPMem); + taskPMax = Math.max(taskPMax, taskPMem); if (task.timed()) { runtimesMs.add(task.getTotalRunTimeMs()); } + taskVMems.add(taskVMem); } - if(taskMin == Long.MAX_VALUE) { - taskMin = 0; + if(taskPMin == Long.MAX_VALUE) { + taskPMin = 0; } - long taskAvg = Statistics.average(taskMems); + long taskPMemAvg = Statistics.average(taskPMems); + long taskVMemAvg = Statistics.average(taskVMems); long averageTimeMs = Statistics.average(runtimesMs); Severity severity; if (tasks.length == 0) { severity = Severity.NONE; } else { - severity = getTaskMemoryUtilSeverity(taskAvg, containerMem); + severity = getTaskMemoryUtilSeverity(taskPMemAvg, containerMem); } HeuristicResult result = new HeuristicResult(_heuristicName, severity); result.addDetail("Number of tasks", Integer.toString(tasks.length)); result.addDetail("Avg task runtime", Statistics.readableTimespan(averageTimeMs)); - result.addDetail("Avg Physical Memory (MB)", Long.toString(taskAvg/FileUtils.ONE_MB)); - result.addDetail("Max Physical Memory (MB)", Long.toString(taskMax/FileUtils.ONE_MB)); - result.addDetail("Min Physical Memory (MB)", Long.toString(taskMin/FileUtils.ONE_MB)); + result.addDetail("Avg Physical Memory (MB)", Long.toString(taskPMemAvg/FileUtils.ONE_MB)); + result.addDetail("Max Physical Memory (MB)", Long.toString(taskPMax/FileUtils.ONE_MB)); + result.addDetail("Min Physical Memory (MB)", Long.toString(taskPMin/FileUtils.ONE_MB)); + result.addDetail("Avg Virtual Memory (MB)", Long.toString(taskVMemAvg/FileUtils.ONE_MB)); result.addDetail("Requested Container Memory", FileUtils.byteCountToDisplaySize(containerMem)); return result; From 2aa93da2da47e2a6963a6d28989706934bce76e4 Mon Sep 17 00:00:00 2001 From: Fangshi Li Date: Wed, 14 Oct 2015 12:15:51 -0700 Subject: [PATCH 113/272] LIHADOOP-15134: Dr. Elephant should provide exception stacktrace from failed jobs Conflicts: build.sbt --- app-conf/HeuristicConf.xml | 10 + .../linkedin/drelephant/ElephantRunner.java | 5 +- .../drelephant/analysis/AnalyticJob.java | 9 +- .../analysis/AnalyticJobGeneratorHadoop2.java | 76 +++-- .../drelephant/analysis/ElephantFetcher.java | 2 +- .../mapreduce/MapReduceApplicationData.java | 22 +- .../mapreduce/MapReduceFetcherHadoop1.java | 6 +- .../mapreduce/MapReduceFetcherHadoop2.java | 307 +++++++----------- .../heuristics/ExceptionHeuristic.java | 32 ++ .../heuristics/GenericDataSkewHeuristic.java | 6 +- .../heuristics/GenericGCHeuristic.java | 4 + .../heuristics/GenericMemoryHeuristic.java | 5 + .../heuristics/MapperSpeedHeuristic.java | 4 + .../heuristics/MapperSpillHeuristic.java | 5 + .../heuristics/MapperTimeHeuristic.java | 5 + .../heuristics/ReducerTimeHeuristic.java | 5 + .../heuristics/ShuffleSortHeuristic.java | 4 + .../spark/deploy/history/SparkFSFetcher.scala | 4 +- app/views/helpException.scala.html | 9 + app/views/singlejob.scala.html | 2 +- build.sbt | 4 +- 21 files changed, 289 insertions(+), 237 deletions(-) create mode 100644 app/com/linkedin/drelephant/mapreduce/heuristics/ExceptionHeuristic.java create mode 100644 app/views/helpException.scala.html diff --git a/app-conf/HeuristicConf.xml b/app-conf/HeuristicConf.xml index 3e4d36847..c4682f969 100644 --- a/app-conf/HeuristicConf.xml +++ b/app-conf/HeuristicConf.xml @@ -114,6 +114,16 @@ + + mapreduce + Exception + com.linkedin.drelephant.mapreduce.heuristics.ExceptionHeuristic + views.html.helpException + + 2 + + + mapreduce Job Queue Limit diff --git a/app/com/linkedin/drelephant/ElephantRunner.java b/app/com/linkedin/drelephant/ElephantRunner.java index 08a1b5d4e..0f9885f41 100644 --- a/app/com/linkedin/drelephant/ElephantRunner.java +++ b/app/com/linkedin/drelephant/ElephantRunner.java @@ -40,7 +40,7 @@ public class ElephantRunner implements Runnable { private static final long WAIT_INTERVAL = 60 * 1000; - private static final int EXECUTOR_NUM = 3; + private static final int EXECUTOR_NUM = 5; private static final Logger logger = Logger.getLogger(ElephantRunner.class); private AtomicBoolean _running = new AtomicBoolean(true); private long lastRun; @@ -51,7 +51,6 @@ public class ElephantRunner implements Runnable { private void loadAnalyticJobGenerator() { JobConf configuration = new JobConf(); - if (HadoopSystemContext.isHadoop2Env()) { _analyticJobGenerator = new AnalyticJobGeneratorHadoop2(); } else if (HadoopSystemContext.isHadoop1Env()) { @@ -63,7 +62,7 @@ private void loadAnalyticJobGenerator() { try { _analyticJobGenerator.configure(configuration); } catch (Exception e) { - logger.error("Error occurred when configuring the analysis provider."); + logger.error("Error occurred when configuring the analysis provider.", e); throw new RuntimeException(e); } } diff --git a/app/com/linkedin/drelephant/analysis/AnalyticJob.java b/app/com/linkedin/drelephant/analysis/AnalyticJob.java index 2009883ce..69b3723a0 100644 --- a/app/com/linkedin/drelephant/analysis/AnalyticJob.java +++ b/app/com/linkedin/drelephant/analysis/AnalyticJob.java @@ -127,17 +127,20 @@ public AnalyticJob setTrackingUrl(String trackingUrl) { public JobResult getAnalysis() throws Exception { ElephantFetcher fetcher = ElephantContext.instance().getFetcherForApplicationType(getAppType()); - HadoopApplicationData data = fetcher.fetchData(getAppId()); - + HadoopApplicationData data = fetcher.fetchData(this); List analysisResults = new ArrayList(); if (data == null || data.isEmpty()) { + // Example: a MR job has 0 mappers and 0 reducers logger.info("No Data Received for analytic job: " + getAppId()); analysisResults.add(HeuristicResult.NO_DATA); } else { List heuristics = ElephantContext.instance().getHeuristicsForApplicationType(getAppType()); for (Heuristic heuristic : heuristics) { - analysisResults.add(heuristic.apply(data)); + HeuristicResult result = heuristic.apply(data); + if (result != null) { + analysisResults.add(result); + } } } diff --git a/app/com/linkedin/drelephant/analysis/AnalyticJobGeneratorHadoop2.java b/app/com/linkedin/drelephant/analysis/AnalyticJobGeneratorHadoop2.java index 07cde66e0..57831bcec 100644 --- a/app/com/linkedin/drelephant/analysis/AnalyticJobGeneratorHadoop2.java +++ b/app/com/linkedin/drelephant/analysis/AnalyticJobGeneratorHadoop2.java @@ -81,43 +81,26 @@ public List fetchAnalyticJobs() _currentTime = System.currentTimeMillis() - FETCH_DELAY; updateAuthToken(); - URL appsURL = - new URL(new URL("http://" + _resourceManagerAddress), String.format( - "/ws/v1/cluster/apps?finalStatus=SUCCEEDED&finishedTimeBegin=%s&finishedTimeEnd=%s", - String.valueOf(_lastTime + 1), String.valueOf(_currentTime))); - logger.info("Fetching recent finished application runs between last time: " + (_lastTime + 1) + ", and current time: " + _currentTime); - JsonNode rootNode = readJsonNode(appsURL); - JsonNode apps = rootNode.path("apps").path("app"); + URL succeededAppsURL = + new URL(new URL("http://" + _resourceManagerAddress), String.format( + "/ws/v1/cluster/apps?finalStatus=SUCCEEDED&finishedTimeBegin=%s&finishedTimeEnd=%s", + String.valueOf(_lastTime + 1), String.valueOf(_currentTime))); - for (JsonNode app : apps) { - String id = app.get("id").getValueAsText(); - String jobId = Utils.getJobIdFromApplicationId(id); + List succeededApps = readApps(succeededAppsURL); - // When called first time after launch, hit the DB and avoid duplicated analytic jobs that have been analyzed - // before. - if (_lastTime > 0 || _lastTime == 0 && JobResult.find.byId(jobId) == null && JobResult.find.byId(id) == null) { - String user = app.get("user").getValueAsText(); - String name = app.get("name").getValueAsText(); - String trackingUrl = app.get("trackingUrl").getValueAsText(); - long startTime = app.get("startedTime").getLongValue(); - long finishTime = app.get("finishedTime").getLongValue(); + appList.addAll(succeededApps); - ApplicationType type = - ElephantContext.instance().getApplicationTypeForName(app.get("applicationType").getValueAsText()); + URL failedAppsURL = + new URL(new URL("http://" + _resourceManagerAddress), String.format( + "/ws/v1/cluster/apps?finalStatus=FAILED&finishedTimeBegin=%s&finishedTimeEnd=%s", + String.valueOf(_lastTime + 1), String.valueOf(_currentTime))); - // If the application type is supported - if (type != null) { - AnalyticJob analyticJob = new AnalyticJob(); - analyticJob.setAppId(id).setAppType(type).setJobId(jobId).setUser(user).setName(name) - .setTrackingUrl(trackingUrl).setStartTime(startTime).setFinishTime(finishTime); + List failedApps = readApps(failedAppsURL); - appList.add(analyticJob); - } - } - } + appList.addAll(failedApps); // Append promises from the retry queue at the end of the list while (!_retryQueue.isEmpty()) { @@ -147,4 +130,39 @@ private JsonNode readJsonNode(URL url) HttpURLConnection conn = _authenticatedURL.openConnection(url, _token); return _objectMapper.readTree(conn.getInputStream()); } + + private List readApps(URL url) throws IOException, AuthenticationException{ + List appList = new ArrayList(); + + JsonNode rootNode = readJsonNode(url); + JsonNode apps = rootNode.path("apps").path("app"); + + for (JsonNode app : apps) { + String id = app.get("id").getValueAsText(); + String jobId = Utils.getJobIdFromApplicationId(id); + + // When called first time after launch, hit the DB and avoid duplicated analytic jobs that have been analyzed + // before. + if (_lastTime > 0 || _lastTime == 0 && JobResult.find.byId(jobId) == null && JobResult.find.byId(id) == null) { + String user = app.get("user").getValueAsText(); + String name = app.get("name").getValueAsText(); + String trackingUrl = app.get("trackingUrl") != null? app.get("trackingUrl").getValueAsText() : null; + long startTime = app.get("startedTime").getLongValue(); + long finishTime = app.get("finishedTime").getLongValue(); + + ApplicationType type = + ElephantContext.instance().getApplicationTypeForName(app.get("applicationType").getValueAsText()); + + // If the application type is supported + if (type != null) { + AnalyticJob analyticJob = new AnalyticJob(); + analyticJob.setAppId(id).setAppType(type).setJobId(jobId).setUser(user).setName(name) + .setTrackingUrl(trackingUrl).setStartTime(startTime).setFinishTime(finishTime); + + appList.add(analyticJob); + } + } + } + return appList; + } } diff --git a/app/com/linkedin/drelephant/analysis/ElephantFetcher.java b/app/com/linkedin/drelephant/analysis/ElephantFetcher.java index e9d6a6667..e999cda15 100644 --- a/app/com/linkedin/drelephant/analysis/ElephantFetcher.java +++ b/app/com/linkedin/drelephant/analysis/ElephantFetcher.java @@ -29,6 +29,6 @@ public interface ElephantFetcher { * @return the fetched data * @throws Exception */ - public T fetchData(String appId) + public T fetchData(AnalyticJob job) throws Exception; } diff --git a/app/com/linkedin/drelephant/mapreduce/MapReduceApplicationData.java b/app/com/linkedin/drelephant/mapreduce/MapReduceApplicationData.java index c87f4a782..9d6db3416 100644 --- a/app/com/linkedin/drelephant/mapreduce/MapReduceApplicationData.java +++ b/app/com/linkedin/drelephant/mapreduce/MapReduceApplicationData.java @@ -23,6 +23,8 @@ public class MapReduceApplicationData implements HadoopApplicationData { private static final ApplicationType APPLICATION_TYPE = new ApplicationType("MAPREDUCE"); + private boolean _succeeded = true; + private String _diagnosticInfo = ""; private String _appId = ""; private String _jobId = ""; private String _username = ""; @@ -36,6 +38,16 @@ public class MapReduceApplicationData implements HadoopApplicationData { private Properties _jobConf; private boolean _isRetry = false; + public MapReduceApplicationData setSucceeded(boolean succeeded) { + this._succeeded = succeeded; + return this; + } + + public MapReduceApplicationData setDiagnosticInfo(String diagnosticInfo) { + this._diagnosticInfo = diagnosticInfo; + return this; + } + public MapReduceApplicationData setRetry(boolean isRetry) { this._isRetry = isRetry; return this; @@ -125,7 +137,7 @@ public ApplicationType getApplicationType() { @Override public boolean isEmpty() { - return getMapperData().length == 0 && getReducerData().length == 0; + return _succeeded && getMapperData().length == 0 && getReducerData().length == 0; } public String getUsername() { @@ -156,6 +168,14 @@ public String getJobId() { return _jobId; } + public boolean getSucceeded() { + return _succeeded; + } + + public String getDiagnosticInfo() { + return _diagnosticInfo; + } + @Override public String toString() { return "id: " + getJobId() + ", name:" + getJobName(); diff --git a/app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop1.java b/app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop1.java index 36c818713..8c1c0379c 100644 --- a/app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop1.java +++ b/app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop1.java @@ -15,10 +15,12 @@ */ package com.linkedin.drelephant.mapreduce; +import com.linkedin.drelephant.analysis.AnalyticJob; import com.linkedin.drelephant.analysis.ElephantFetcher; import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder.CounterName; import com.linkedin.drelephant.math.Statistics; import com.linkedin.drelephant.util.Utils; + import java.io.IOException; import java.net.HttpURLConnection; import java.net.URL; @@ -31,6 +33,7 @@ import java.util.Random; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; + import org.apache.commons.io.IOUtils; import org.apache.hadoop.mapred.Counters; import org.apache.hadoop.mapred.Counters.Counter; @@ -69,8 +72,9 @@ public MapReduceFetcherHadoop1() } @Override - public MapReduceApplicationData fetchData(String id) + public MapReduceApplicationData fetchData(AnalyticJob analyticJob) throws IOException, AuthenticationException { + String id = analyticJob.getAppId(); MapReduceApplicationData jobData = new MapReduceApplicationData(); jobData.setAppId(id).setJobId(id); try { diff --git a/app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop2.java b/app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop2.java index 54b349615..36c0321e6 100644 --- a/app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop2.java +++ b/app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop2.java @@ -15,11 +15,11 @@ */ package com.linkedin.drelephant.mapreduce; -import com.google.common.collect.Lists; +import com.linkedin.drelephant.analysis.AnalyticJob; import com.linkedin.drelephant.analysis.ElephantFetcher; -import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder.CounterName; import com.linkedin.drelephant.math.Statistics; import com.linkedin.drelephant.util.Utils; + import java.io.IOException; import java.net.HttpURLConnection; import java.net.MalformedURLException; @@ -27,12 +27,12 @@ import java.net.URLConnection; import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.Properties; import java.util.Random; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; -import model.JobResult; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + import org.apache.hadoop.mapred.JobConf; import org.apache.hadoop.security.authentication.client.AuthenticatedURL; import org.apache.hadoop.security.authentication.client.AuthenticationException; @@ -44,128 +44,92 @@ public class MapReduceFetcherHadoop2 implements ElephantFetcher { private static final Logger logger = Logger.getLogger(ElephantFetcher.class); // We provide one minute job fetch delay due to the job sending lag from AM/NM to JobHistoryServer HDFS - private static final long FETCH_DELAY = 60000; - private RetryFactory _retryFactory; private URLFactory _urlFactory; private JSONFactory _jsonFactory; - private boolean _firstRun = true; - private long _lastTime = 0; - private long _currentTime = 0; + private String _jhistoryWebAddr; public MapReduceFetcherHadoop2() throws IOException { logger.info("Connecting to the job history server..."); final String jhistoryAddr = new JobConf().get("mapreduce.jobhistory.webapp.address"); _urlFactory = new URLFactory(jhistoryAddr); _jsonFactory = new JSONFactory(); - _retryFactory = new RetryFactory(); + _jhistoryWebAddr = "http://" + jhistoryAddr + "/jobhistory/job/"; logger.info("Connection success."); } - /* - * Fetch job list to analyze - * If first time, search time span from 0 to now, check database for each job - * If not first time, search time span since last fetch, also re-fetch failed jobs - * Return list on success, throw Exception on error - */ - public List fetchJobList() throws IOException, AuthenticationException { - - List jobList; - int retrySize = 0; - - // There is a lag of job data from AM/NM to JobHistoryServer HDFS, we shouldn't use the current - // time, since there might be new jobs arriving after we fetch jobs. - // We provide one minute delay to address this lag. - _currentTime = System.currentTimeMillis() - FETCH_DELAY; - URL joblistURL= _urlFactory.fetchJobListURL(_lastTime, _currentTime); - - try { - jobList = _jsonFactory.getJobData(joblistURL, _firstRun); - } finally { - ThreadContextMR2.updateAuthToken(); - } - - if (_firstRun) { - _firstRun = false; - } else { - List retryList = _retryFactory.fetchAllRetryJobs(); - // If not first time, also fetch jobs that need to retry - retrySize = retryList.size(); - jobList.addAll(retryList); - } - - logger.info("Fetcher gets total " + jobList.size() + " jobs (" + retrySize + " retry jobs) from time range [" - + _lastTime + "," + _currentTime + "]"); - _lastTime = _currentTime; - - return jobList; - } - - // Check database to see if a job is already analyzed - private boolean checkDBforJob(String jobId) { - JobResult result = JobResult.find.byId(jobId); - return (result != null); - } - - // Clear all data stored on the job object - private void clearJobData(MapReduceApplicationData jobData) { - jobData.setCounters(null).setJobConf(null).setMapperData(null).setReducerData(null); - } - - // OnJobFinish Add to retry list upon failure - public void finishJob(MapReduceApplicationData jobData, boolean success) { - if (!success) { - if (!jobData.isRetryJob()) { - jobData.setRetry(true); - } - clearJobData(jobData); - // Add to retry list - _retryFactory.addJobToRetryList(jobData); - } else { - if (jobData.isRetryJob()) { - // If it is retry job, remove it from retry map - _retryFactory.checkAndRemoveFromRetryList(jobData); - } - } - } - - // Fetch job detailed data. Return true on success @Override - public MapReduceApplicationData fetchData(String appId) throws IOException, AuthenticationException { + public MapReduceApplicationData fetchData(AnalyticJob analyticJob) throws IOException, AuthenticationException { + String appId = analyticJob.getAppId(); MapReduceApplicationData jobData = new MapReduceApplicationData(); String jobId = Utils.getJobIdFromApplicationId(appId); jobData.setAppId(appId).setJobId(jobId); + // Change job tracking url to job history page + analyticJob.setTrackingUrl(_jhistoryWebAddr + jobId); try { - // Fetch job counter - URL jobCounterURL = _urlFactory.getJobCounterURL(jobId); - MapReduceCounterHolder jobCounter = _jsonFactory.getJobCounter(jobCounterURL); // Fetch job config - URL jobConfigURL = _urlFactory.getJobConfigURL(jobId); - Properties jobConf = _jsonFactory.getProperties(jobConfigURL); + Properties jobConf = _jsonFactory.getProperties(_urlFactory.getJobConfigURL(jobId)); + jobData.setJobConf(jobConf); + + URL jobURL = _urlFactory.getJobURL(jobId); + String state = _jsonFactory.getState(jobURL); + if (state.equals("SUCCEEDED")) { + + jobData.setSucceeded(true); + + // Fetch job counter + MapReduceCounterHolder jobCounter = _jsonFactory.getJobCounter(_urlFactory.getJobCounterURL(jobId)); - // Fetch task data - URL taskListURL = _urlFactory.getTaskListURL(jobId); - List mapperList = new ArrayList(); - List reducerList = new ArrayList(); - _jsonFactory.getTaskDataAll(taskListURL, jobId, mapperList, reducerList); + // Fetch task data + URL taskListURL = _urlFactory.getTaskListURL(jobId); + List mapperList = new ArrayList(); + List reducerList = new ArrayList(); + _jsonFactory.getTaskDataAll(taskListURL, jobId, mapperList, reducerList); - MapReduceTaskData[] mapperData = mapperList.toArray(new MapReduceTaskData[mapperList.size()]); - MapReduceTaskData[] reducerData = reducerList.toArray(new MapReduceTaskData[reducerList.size()]); + MapReduceTaskData[] mapperData = mapperList.toArray(new MapReduceTaskData[mapperList.size()]); + MapReduceTaskData[] reducerData = reducerList.toArray(new MapReduceTaskData[reducerList.size()]); - jobData.setCounters(jobCounter).setMapperData(mapperData).setReducerData(reducerData).setJobConf(jobConf); + jobData.setCounters(jobCounter).setMapperData(mapperData).setReducerData(reducerData); + // TODO: publisher should not be here. refactor before open source + Utils.publishMetrics(jobData); + + } else if (state.equals("FAILED")) { + + jobData.setSucceeded(false); + String diagnosticInfo; + try { + diagnosticInfo = parseException(jobData.getJobId(), _jsonFactory.getDiagnosticInfo(jobURL)); + } catch(Exception e) { + diagnosticInfo = null; + } + jobData.setDiagnosticInfo(diagnosticInfo); + } else { + // Should not reach here + throw new RuntimeException("Job state not supported. Should be either SUCCEEDED or FAILED"); + } } finally { ThreadContextMR2.updateAuthToken(); } - Utils.publishMetrics(jobData); - return jobData; } - private String getJobDetailURL(String jobId) { - return _urlFactory.getJobDetailURLString(jobId); + private String parseException(String jobId, String diagnosticInfo) throws MalformedURLException, IOException, AuthenticationException { + Matcher m = ThreadContextMR2.getDiagnosticMatcher(diagnosticInfo); + if (m.matches()) { + if (Integer.parseInt(m.group(2)) == 0) { + // This is due to bug in hadoop 2.3 and shoufixed in 2.4 + throw new RuntimeException("Error in diagnosticInfo"); + } + String taskId = m.group(1); + System.out.println("parse succedded. " + m.group(1) + " " + m.group(2)); + return _jsonFactory.getTaskFailedStackTrace(_urlFactory.getTaskAllAttemptsURL(jobId, taskId)); + } + logger.info("Does not match regex!!"); + // Diagnostic info not present in the job. Usually due to exception during AM setup + throw new RuntimeException("No sufficient diagnostic Info"); } private URL getTaskCounterURL(String jobId, String taskId) throws MalformedURLException { @@ -178,11 +142,9 @@ private URL getTaskAttemptURL(String jobId, String taskId, String attemptId) thr private class URLFactory { - private String _root; private String _restRoot; private URLFactory(String hserverAddr) throws IOException { - _root = "http://" + hserverAddr; _restRoot = "http://" + hserverAddr + "/ws/v1/history/mapreduce/jobs"; verifyURL(_restRoot); } @@ -194,12 +156,8 @@ private void verifyURL(String url) throws IOException { return; } - private String getJobDetailURLString(String jobId) { - return _root + "/jobhistory/job/" + jobId; - } - - public URL fetchJobListURL(long startTime, long endTime) throws MalformedURLException { - return new URL(_restRoot + "?finishedTimeBegin=" + startTime + "&finishedTimeEnd=" + endTime + "&state=SUCCEEDED"); + private URL getJobURL(String jobId) throws MalformedURLException { + return new URL(_restRoot + "/" + jobId); } private URL getJobConfigURL(String jobId) throws MalformedURLException { @@ -218,52 +176,25 @@ private URL getTaskCounterURL(String jobId, String taskId) throws MalformedURLEx return new URL(_restRoot + "/" + jobId + "/tasks/" + taskId + "/counters"); } + private URL getTaskAllAttemptsURL(String jobId, String taskId) throws MalformedURLException { + return new URL(_restRoot + "/" + jobId + "/tasks/" + taskId + "/attempts"); + } + private URL getTaskAttemptURL(String jobId, String taskId, String attemptId) throws MalformedURLException { return new URL(_restRoot + "/" + jobId + "/tasks/" + taskId + "/attempts/" + attemptId); } } private class JSONFactory { - private List getJobData(URL url, boolean checkDB) throws IOException, AuthenticationException { - List jobList = new ArrayList(); - + private String getState(URL url) throws IOException, AuthenticationException { JsonNode rootNode = ThreadContextMR2.readJsonNode(url); - JsonNode jobs = rootNode.path("jobs").path("job"); - - for (JsonNode job : jobs) { - String jobId = job.get("id").getValueAsText(); - - // On first time, for every job, we check database - if (checkDB && checkDBforJob(jobId)) { - continue; - } + return rootNode.path("job").path("state").getValueAsText(); + } - // New job - /* - { - "finishTime": 1415816989533, - "id": "job_1415656299984_0014", - "mapsCompleted": 1, - "mapsTotal": 1, - "name": "word count", - "queue": "default", - "reducesCompleted": 1, - "reducesTotal": 1, - "startTime": 1415816979666, - "state": "SUCCEEDED", - "submitTime": 1415816976300, - "user": "ssubrama" - } - */ - MapReduceApplicationData jobData = new MapReduceApplicationData(); - jobData.setJobId(jobId).setUsername(job.get("user").getValueAsText()) - .setJobName(job.get("name").getValueAsText()).setUrl(getJobDetailURL(jobId)) - .setStartTime(job.get("startTime").getLongValue()) - .setFinishTime(job.get("finishTime").getLongValue()); - - jobList.add(jobData); - } - return jobList; + private String getDiagnosticInfo(URL url) throws IOException, AuthenticationException { + JsonNode rootNode = ThreadContextMR2.readJsonNode(url); + String diag = rootNode.path("job").path("diagnostics").getValueAsText(); + return diag; } private Properties getProperties(URL url) throws IOException, AuthenticationException { @@ -280,8 +211,7 @@ private Properties getProperties(URL url) throws IOException, AuthenticationExce return jobConf; } - private MapReduceCounterHolder getJobCounter(URL url) - throws IOException, AuthenticationException { + private MapReduceCounterHolder getJobCounter(URL url) throws IOException, AuthenticationException { MapReduceCounterHolder holder = new MapReduceCounterHolder(); JsonNode rootNode = ThreadContextMR2.readJsonNode(url); @@ -290,7 +220,6 @@ private MapReduceCounterHolder getJobCounter(URL url) for (JsonNode group : groups) { for (JsonNode counter : group.path("counter")) { String counterName = counter.get("name").getValueAsText(); - CounterName cn = CounterName.getCounterFromName(counterName); Long counterValue = counter.get("totalCounterValue").getLongValue(); String groupName = group.get("counterGroupName").getValueAsText(); holder.set(groupName, counterName, counterValue); @@ -312,7 +241,6 @@ private MapReduceCounterHolder getTaskCounter(URL url) throws IOException, Authe holder.set(groupName, name, value); } } - return holder; } @@ -338,8 +266,8 @@ private long[] getTaskExecTime(URL url) throws IOException, AuthenticationExcept return time; } - private void getTaskDataAll(URL url, String jobId, List mapperList, List reducerList) - throws IOException, AuthenticationException { + private void getTaskDataAll(URL url, String jobId, List mapperList, + List reducerList) throws IOException, AuthenticationException { JsonNode rootNode = ThreadContextMR2.readJsonNode(url); JsonNode tasks = rootNode.path("tasks").path("task"); @@ -368,52 +296,30 @@ private void getTaskDataAll(URL url, String jobId, List mappe } } } - } - private class RetryFactory { - private static final int DEFAULT_RETRY = 2; - private Map _retryMapInWait = new ConcurrentHashMap(); - private Map _retryMapInProgress = new ConcurrentHashMap(); - - private void addJobToRetryList(MapReduceApplicationData job) { - if (_retryMapInProgress.containsKey(job)) { - // This is old retry job - int retryLeft = _retryMapInProgress.get(job); - _retryMapInProgress.remove(job); - if (retryLeft == 0) { - // Drop job on max retries - logger.error("Drop job. Reason: reach max retry for job id=" + job.getJobId()); + private String getTaskFailedStackTrace(URL taskAllAttemptsUrl) throws IOException, AuthenticationException { + JsonNode rootNode = ThreadContextMR2.readJsonNode(taskAllAttemptsUrl); + JsonNode tasks = rootNode.path("taskAttempts").path("taskAttempt"); + for (JsonNode task : tasks) { + String state = task.get("state").getValueAsText(); + if (!state.equals("FAILED")) { + continue; + } + String stacktrace = task.get("diagnostics").getValueAsText(); + if (stacktrace.startsWith("Error:")) { + return stacktrace; } else { - _retryMapInWait.put(job, retryLeft); + // This is not a valid stacktrace. Might due to a bug in hadoop2.3 and fixed in 2.4 + throw new RuntimeException("This is not a valid stack trace."); } - } else { - // This is new retry job - _retryMapInWait.put(job, DEFAULT_RETRY); - } - } - - private List fetchAllRetryJobs() { - List retryList = new ArrayList(); - for (MapReduceApplicationData job : _retryMapInWait.keySet()) { - int retry = _retryMapInWait.get(job); - _retryMapInWait.remove(job); - retryList.add(job); - _retryMapInProgress.put(job, retry - 1); - } - return Lists.newArrayList(retryList); - } - - private void checkAndRemoveFromRetryList(MapReduceApplicationData jobData) { - if (_retryMapInProgress.containsKey(jobData)) { - _retryMapInProgress.remove(jobData); } + throw new RuntimeException("No failed task attempt in this failed task."); } } } final class ThreadContextMR2 { private static final Logger logger = Logger.getLogger(ThreadContextMR2.class); - private static final AtomicInteger THREAD_ID = new AtomicInteger(1); private static final ThreadLocal _LOCAL_THREAD_ID = new ThreadLocal() { @@ -426,19 +332,28 @@ public Integer initialValue() { private static final ThreadLocal _LOCAL_LAST_UPDATED = new ThreadLocal(); private static final ThreadLocal _LOCAL_UPDATE_INTERVAL = new ThreadLocal(); - private static final ThreadLocal _LOCAL_AUTH_TOKEN = - new ThreadLocal() { + private static final ThreadLocal _LOCAL_DIAGNOSTIC_PATTERN = new ThreadLocal() { @Override - public AuthenticatedURL.Token initialValue() { - _LOCAL_LAST_UPDATED.set(System.currentTimeMillis()); - // Random an interval for each executor to avoid update token at the same time - _LOCAL_UPDATE_INTERVAL.set(Statistics.MINUTE_IN_MS * 30 + new Random().nextLong() % (3 * Statistics.MINUTE_IN_MS)); - logger.info("Executor " + _LOCAL_THREAD_ID.get() + " update interval " + _LOCAL_UPDATE_INTERVAL.get() * 1.0 - / Statistics.MINUTE_IN_MS); - return new AuthenticatedURL.Token(); + public Pattern initialValue() { + // Example: "Task task_1443068695259_9143_m_000475 failed 1 times" + return Pattern.compile("Task[\\s\\u00A0]+(.*)[\\s\\u00A0]+failed[\\s\\u00A0]+([0-9])[\\s\\u00A0]+times[\\s\\u00A0]+"); } }; + private static final ThreadLocal _LOCAL_AUTH_TOKEN = + new ThreadLocal() { + @Override + public AuthenticatedURL.Token initialValue() { + _LOCAL_LAST_UPDATED.set(System.currentTimeMillis()); + // Random an interval for each executor to avoid update token at the same time + _LOCAL_UPDATE_INTERVAL.set(Statistics.MINUTE_IN_MS * 30 + new Random().nextLong() + % (3 * Statistics.MINUTE_IN_MS)); + logger.info("Executor " + _LOCAL_THREAD_ID.get() + " update interval " + _LOCAL_UPDATE_INTERVAL.get() * 1.0 + / Statistics.MINUTE_IN_MS); + return new AuthenticatedURL.Token(); + } + }; + private static final ThreadLocal _LOCAL_AUTH_URL = new ThreadLocal() { @Override public AuthenticatedURL initialValue() { @@ -457,6 +372,10 @@ private ThreadContextMR2() { // Empty on purpose } + public static Matcher getDiagnosticMatcher(String diagnosticInfo) { + return _LOCAL_DIAGNOSTIC_PATTERN.get().matcher(diagnosticInfo); + } + public static JsonNode readJsonNode(URL url) throws IOException, AuthenticationException { HttpURLConnection conn = _LOCAL_AUTH_URL.get().openConnection(url, _LOCAL_AUTH_TOKEN.get()); return _LOCAL_MAPPER.get().readTree(conn.getInputStream()); diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/ExceptionHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/ExceptionHeuristic.java new file mode 100644 index 000000000..2133f16b3 --- /dev/null +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/ExceptionHeuristic.java @@ -0,0 +1,32 @@ +package com.linkedin.drelephant.mapreduce.heuristics; + +import com.linkedin.drelephant.analysis.Heuristic; +import com.linkedin.drelephant.analysis.HeuristicResult; +import com.linkedin.drelephant.analysis.Severity; +import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; + +public class ExceptionHeuristic implements Heuristic { + + public static final String HEURISTIC_NAME = "Exception"; + + @Override + public String getHeuristicName() { + return HEURISTIC_NAME; + } + + @Override + public HeuristicResult apply(MapReduceApplicationData data) { + if (data.getSucceeded()) { + return null; + } + HeuristicResult result = new HeuristicResult(HEURISTIC_NAME, Severity.MODERATE); + String diagnosticInfo = data.getDiagnosticInfo(); + if (diagnosticInfo != null) { + result.addDetail(diagnosticInfo); + } else { + String msg = "Unable to find stacktrace info. Please find the real problem in the Jobhistory link above.\nException can happen either in task log or Application Master log."; + result.addDetail(msg); + } + return result; + } +} diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/GenericDataSkewHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericDataSkewHeuristic.java index 8f0ae6cb1..6ac744b78 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/GenericDataSkewHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericDataSkewHeuristic.java @@ -16,7 +16,6 @@ package com.linkedin.drelephant.mapreduce.heuristics; import com.linkedin.drelephant.analysis.HDFSContext; -import com.linkedin.drelephant.analysis.HadoopSystemContext; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; @@ -46,6 +45,11 @@ protected GenericDataSkewHeuristic(MapReduceCounterHolder.CounterName counterNam @Override public HeuristicResult apply(MapReduceApplicationData data) { + + if(!data.getSucceeded()) { + return null; + } + MapReduceTaskData[] tasks = getTasks(data); //Gather data diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/GenericGCHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericGCHeuristic.java index fb30ec44a..b22d853b7 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/GenericGCHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericGCHeuristic.java @@ -27,6 +27,10 @@ protected GenericGCHeuristic(String heuristicName) { @Override public HeuristicResult apply(MapReduceApplicationData data) { + if(!data.getSucceeded()) { + return null; + } + MapReduceTaskData[] tasks = getTasks(data); List gcMs = new ArrayList(); List cpuMs = new ArrayList(); diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/GenericMemoryHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericMemoryHeuristic.java index e112da84b..8fa310515 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/GenericMemoryHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericMemoryHeuristic.java @@ -47,6 +47,11 @@ protected GenericMemoryHeuristic(String containerMemConf, String heuristicName) @Override public HeuristicResult apply(MapReduceApplicationData data) { + + if(!data.getSucceeded()) { + return null; + } + long containerMem = Long.parseLong(data.getConf().getProperty(_containerMemConf)) * FileUtils.ONE_MB; MapReduceTaskData[] tasks = getTasks(data); diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristic.java index e6e9c460c..49839056c 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristic.java @@ -41,6 +41,10 @@ public String getHeuristicName() { @Override public HeuristicResult apply(MapReduceApplicationData data) { + if(!data.getSucceeded()) { + return null; + } + MapReduceTaskData[] tasks = data.getMapperData(); List inputByteSizes = new ArrayList(); diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristic.java index 3a43d2f02..1fc5ca457 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristic.java @@ -29,6 +29,11 @@ public class MapperSpillHeuristic implements Heuristic @Override public HeuristicResult apply(MapReduceApplicationData data) { + + if(!data.getSucceeded()) { + return null; + } + MapReduceTaskData[] tasks = data.getMapperData(); long totalSpills = 0; diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristic.java index 0b12ccbc6..097e9b42b 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristic.java @@ -39,6 +39,11 @@ public String getHeuristicName() { @Override public HeuristicResult apply(MapReduceApplicationData data) { + + if(!data.getSucceeded()) { + return null; + } + MapReduceTaskData[] tasks = data.getMapperData(); List inputBytes = new ArrayList(); diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristic.java index 37d6452fe..6dc92ee9c 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristic.java @@ -36,6 +36,11 @@ public String getHeuristicName() { @Override public HeuristicResult apply(MapReduceApplicationData data) { + + if(!data.getSucceeded()) { + return null; + } + MapReduceTaskData[] tasks = data.getReducerData(); List runTimesMs = new ArrayList(); diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristic.java index b89c2fe42..64f7baa27 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristic.java @@ -37,6 +37,10 @@ public String getHeuristicName() { @Override public HeuristicResult apply(MapReduceApplicationData data) { + if(!data.getSucceeded()) { + return null; + } + MapReduceTaskData[] tasks = data.getReducerData(); List execTimeMs = new ArrayList(); diff --git a/app/org/apache/spark/deploy/history/SparkFSFetcher.scala b/app/org/apache/spark/deploy/history/SparkFSFetcher.scala index 431550c5b..08d974543 100644 --- a/app/org/apache/spark/deploy/history/SparkFSFetcher.scala +++ b/app/org/apache/spark/deploy/history/SparkFSFetcher.scala @@ -24,6 +24,7 @@ import java.io.InputStream import com.linkedin.drelephant.HadoopSecurity +import com.linkedin.drelephant.analysis.AnalyticJob; import com.linkedin.drelephant.analysis.ElephantFetcher import com.linkedin.drelephant.spark.SparkApplicationData import org.apache.hadoop.conf.Configuration @@ -80,7 +81,8 @@ class SparkFSFetcher extends ElephantFetcher[SparkApplicationData] { } } - def fetchData(appId: String): SparkApplicationData = { + def fetchData(analyticJob: AnalyticJob): SparkApplicationData = { + val appId = analyticJob.getAppId() _security.doAs[SparkDataCollection](new PrivilegedAction[SparkDataCollection] { override def run(): SparkDataCollection = { /* Most of Spark logs will be in directory structure: /LOG_DIR/[application_id]. diff --git a/app/views/helpException.scala.html b/app/views/helpException.scala.html new file mode 100644 index 000000000..751038503 --- /dev/null +++ b/app/views/helpException.scala.html @@ -0,0 +1,9 @@ +

+ Dr. Elephant now starts to provide exception info for failed jobs.
+ The stacktrace is the exception of the failed task that caused this job to fail.
+ Some failed jobs does not have a valid stacktrace reported by Hadoop. In this case, Dr. Elephant cannot provide the stacktrace.
+ If Dr. Elephant cannot provide the exception, try to mine by yourself in its job history page.
+ For example, some real exceptions appear at the end of the Application Master log.
+ Some real exceptions appear in the task log.
+ We hope to provide your more insights over these exceptions in the future. see go/scooby +

\ No newline at end of file diff --git a/app/views/singlejob.scala.html b/app/views/singlejob.scala.html index 4e8bc822d..08186c672 100644 --- a/app/views/singlejob.scala.html +++ b/app/views/singlejob.scala.html @@ -26,7 +26,7 @@ - + @if(result.jobExecUrl != null){ diff --git a/build.sbt b/build.sbt index ef9954a24..985b0b015 100644 --- a/build.sbt +++ b/build.sbt @@ -18,7 +18,7 @@ import play.Project._ name := "dr-elephant" -version := "1.0.4" +version := "1.0.5-SNAPSHOT" javacOptions in Compile ++= Seq("-source", "1.6", "-target", "1.6") @@ -65,6 +65,6 @@ val LinkedInPatterns = Patterns( val ArtifactoryBaseUrl = "http://artifactory.corp.linkedin.com:8081/artifactory/" -resolvers += Resolver.url("LI repo repository", url(ArtifactoryBaseUrl + "repo"))(LinkedInPatterns) +resolvers += Resolver.url("LI repo repository", url(ArtifactoryBaseUrl + "release"))(LinkedInPatterns) playJavaSettings From f80f7b900e6a737ea3be04f98a27ab505d618a1c Mon Sep 17 00:00:00 2001 From: Fangshi Li Date: Wed, 14 Oct 2015 16:23:41 -0700 Subject: [PATCH 114/272] LIHADOOP-14342: Dr. Elephant's memory heuristic has a corner case to throw NumberFormatException --- .../heuristics/GenericMemoryHeuristic.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/GenericMemoryHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericMemoryHeuristic.java index 8fa310515..bee65747d 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/GenericMemoryHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericMemoryHeuristic.java @@ -31,7 +31,7 @@ public abstract class GenericMemoryHeuristic implements Heuristic { private String _containerMemConf; private String _heuristicName; - private long CONTAINER_MEMORY_DEFAULT_BYTES = 2048L * FileUtils.ONE_MB; + private final long CONTAINER_MEMORY_DEFAULT_BYTES = 2048L * FileUtils.ONE_MB; @Override public String getHeuristicName() { @@ -52,7 +52,21 @@ public HeuristicResult apply(MapReduceApplicationData data) { return null; } - long containerMem = Long.parseLong(data.getConf().getProperty(_containerMemConf)) * FileUtils.ONE_MB; + String containerSizeStr = data.getConf().getProperty(_containerMemConf); + + long containerMem; + try { + containerMem = Long.parseLong(containerSizeStr); + } catch (NumberFormatException e) { + // Some job has a string var like "${VAR}" for this config. + if(containerSizeStr.startsWith("$")) { + String realContainerConf = containerSizeStr.substring(containerSizeStr.indexOf("{")+1, containerSizeStr.indexOf("}")); + containerMem = Long.parseLong(data.getConf().getProperty(realContainerConf)); + } else { + throw e; + } + } + containerMem *= FileUtils.ONE_MB; MapReduceTaskData[] tasks = getTasks(data); List taskPMems = new ArrayList(); From ace76071bfc700eaf152941fb77e5c115caf5558 Mon Sep 17 00:00:00 2001 From: Fangshi Li Date: Wed, 14 Oct 2015 16:37:33 -0700 Subject: [PATCH 115/272] LIHADOOP-15136: Release Dr. Elephant v1.0.5 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 985b0b015..843d2bf83 100644 --- a/build.sbt +++ b/build.sbt @@ -18,7 +18,7 @@ import play.Project._ name := "dr-elephant" -version := "1.0.5-SNAPSHOT" +version := "1.0.5" javacOptions in Compile ++= Seq("-source", "1.6", "-target", "1.6") From 1d44f489bd1fe25fec11eb85c3818d2e13fde23f Mon Sep 17 00:00:00 2001 From: Akshay Rai Date: Sun, 11 Oct 2015 22:30:53 -0700 Subject: [PATCH 116/272] LIHADOOP-15080 Clean up and organize the views in Dr. Elephant G=superfriends-reviewers R=annag,fli,rratti A=fli,annag --- app-conf/HeuristicConf.xml | 32 ++--- .../linkedin/drelephant/ElephantContext.java | 2 +- app/controllers/Application.java | 54 ++++---- app/views/emailcritical.scala.html | 41 +++--- app/views/{ => help}/helpNoData.scala.html | 0 .../mapreduce}/helpException.scala.html | 0 .../{ => help/mapreduce}/helpGC.scala.html | 0 .../mapreduce}/helpJobQueueLimit.scala.html | 0 .../mapreduce}/helpMapperDataSkew.scala.html | 0 .../mapreduce}/helpMapperMemory.scala.html | 0 .../mapreduce}/helpMapperSpeed.scala.html | 0 .../mapreduce}/helpMapperSpill.scala.html | 0 .../mapreduce}/helpMapperTime.scala.html | 0 .../mapreduce}/helpMemory.scala.html | 0 .../mapreduce}/helpReducerDataSkew.scala.html | 0 .../mapreduce}/helpReducerMemory.scala.html | 0 .../mapreduce}/helpReducerTime.scala.html | 0 .../mapreduce}/helpShuffleSort.scala.html | 0 .../spark/helpBestProperties.scala.html} | 0 .../spark/helpEventLogLimit.scala.html} | 0 .../spark/helpExecutorLoad.scala.html} | 0 .../spark/helpJobRuntime.scala.html} | 0 .../spark/helpMemoryLimit.scala.html} | 0 .../spark/helpStageRuntime.scala.html} | 0 app/views/main.scala.html | 89 +++++++------ app/views/multijob.scala.html | 40 ------ .../comparePage.scala.html} | 10 ++ .../helpPage.scala.html} | 0 app/views/page/homePage.scala.html | 37 ++++++ app/views/page/searchPage.scala.html | 109 ++++++++++++++++ .../compareResults.scala.html} | 20 ++- .../flowDetails.scala.html} | 37 +++--- app/views/results/jobDetails.scala.html | 121 ++++++++++++++++++ app/views/results/searchResults.scala.html | 38 ++++++ app/views/search.scala.html | 89 ------------- app/views/singlejob.scala.html | 110 ---------------- app/views/tags/jobHeader.scala.html | 33 +++++ .../jobSummary.scala.html} | 25 ++-- app/views/{ => tags}/pagination.scala.html | 12 +- 39 files changed, 510 insertions(+), 389 deletions(-) rename app/views/{ => help}/helpNoData.scala.html (100%) rename app/views/{ => help/mapreduce}/helpException.scala.html (100%) rename app/views/{ => help/mapreduce}/helpGC.scala.html (100%) rename app/views/{ => help/mapreduce}/helpJobQueueLimit.scala.html (100%) rename app/views/{ => help/mapreduce}/helpMapperDataSkew.scala.html (100%) rename app/views/{ => help/mapreduce}/helpMapperMemory.scala.html (100%) rename app/views/{ => help/mapreduce}/helpMapperSpeed.scala.html (100%) rename app/views/{ => help/mapreduce}/helpMapperSpill.scala.html (100%) rename app/views/{ => help/mapreduce}/helpMapperTime.scala.html (100%) rename app/views/{ => help/mapreduce}/helpMemory.scala.html (100%) rename app/views/{ => help/mapreduce}/helpReducerDataSkew.scala.html (100%) rename app/views/{ => help/mapreduce}/helpReducerMemory.scala.html (100%) rename app/views/{ => help/mapreduce}/helpReducerTime.scala.html (100%) rename app/views/{ => help/mapreduce}/helpShuffleSort.scala.html (100%) rename app/views/{helpSparkBestProperties.scala.html => help/spark/helpBestProperties.scala.html} (100%) rename app/views/{helpSparkEventLogLimit.scala.html => help/spark/helpEventLogLimit.scala.html} (100%) rename app/views/{helpSparkExecutorLoad.scala.html => help/spark/helpExecutorLoad.scala.html} (100%) rename app/views/{helpSparkJobRuntime.scala.html => help/spark/helpJobRuntime.scala.html} (100%) rename app/views/{helpSparkMemoryLimit.scala.html => help/spark/helpMemoryLimit.scala.html} (100%) rename app/views/{helpSparkStageRuntime.scala.html => help/spark/helpStageRuntime.scala.html} (100%) delete mode 100644 app/views/multijob.scala.html rename app/views/{compare.scala.html => page/comparePage.scala.html} (84%) rename app/views/{help.scala.html => page/helpPage.scala.html} (100%) create mode 100644 app/views/page/homePage.scala.html create mode 100644 app/views/page/searchPage.scala.html rename app/views/{compareresults.scala.html => results/compareResults.scala.html} (71%) rename app/views/{relatedjob.scala.html => results/flowDetails.scala.html} (51%) create mode 100644 app/views/results/jobDetails.scala.html create mode 100644 app/views/results/searchResults.scala.html delete mode 100644 app/views/search.scala.html delete mode 100644 app/views/singlejob.scala.html create mode 100644 app/views/tags/jobHeader.scala.html rename app/views/{index.scala.html => tags/jobSummary.scala.html} (56%) rename app/views/{ => tags}/pagination.scala.html (90%) diff --git a/app-conf/HeuristicConf.xml b/app-conf/HeuristicConf.xml index c4682f969..6062e3ecf 100644 --- a/app-conf/HeuristicConf.xml +++ b/app-conf/HeuristicConf.xml @@ -21,7 +21,7 @@ mapreduce Mapper Data Skew com.linkedin.drelephant.mapreduce.heuristics.MapperDataSkewHeuristic - views.html.helpMapperDataSkew + views.html.help.mapreduce.helpMapperDataSkew 1 2 @@ -32,7 +32,7 @@ mapreduce Mapper Time com.linkedin.drelephant.mapreduce.heuristics.MapperTimeHeuristic - views.html.helpMapperTime + views.html.help.mapreduce.helpMapperTime 1 2 @@ -43,7 +43,7 @@ mapreduce Mapper Speed com.linkedin.drelephant.mapreduce.heuristics.MapperSpeedHeuristic - views.html.helpMapperSpeed + views.html.help.mapreduce.helpMapperSpeed 1 2 @@ -54,7 +54,7 @@ mapreduce Mapper Spill com.linkedin.drelephant.mapreduce.heuristics.MapperSpillHeuristic - views.html.helpMapperSpill + views.html.help.mapreduce.helpMapperSpill @@ -74,7 +74,7 @@ mapreduce Reducer Data Skew com.linkedin.drelephant.mapreduce.heuristics.ReducerDataSkewHeuristic - views.html.helpReducerDataSkew + views.html.help.mapreduce.helpReducerDataSkew 1 2 @@ -85,7 +85,7 @@ mapreduce Reducer Time com.linkedin.drelephant.mapreduce.heuristics.ReducerTimeHeuristic - views.html.helpReducerTime + views.html.help.mapreduce.helpReducerTime 1 2 @@ -96,7 +96,7 @@ mapreduce Shuffle & Sort com.linkedin.drelephant.mapreduce.heuristics.ShuffleSortHeuristic - views.html.helpShuffleSort + views.html.help.mapreduce.helpShuffleSort 1 2 @@ -107,7 +107,7 @@ mapreduce Reducer GC com.linkedin.drelephant.mapreduce.heuristics.ReducerGCHeuristic - views.html.helpGC + views.html.help.mapreduce.helpGC 1 2 @@ -118,7 +118,7 @@ mapreduce Exception com.linkedin.drelephant.mapreduce.heuristics.ExceptionHeuristic - views.html.helpException + views.html.help.mapreduce.helpException 2 @@ -128,7 +128,7 @@ mapreduce Job Queue Limit com.linkedin.drelephant.mapreduce.heuristics.JobQueueLimitHeuristic - views.html.helpJobQueueLimit + views.html.help.mapreduce.helpJobQueueLimit 1 @@ -138,7 +138,7 @@ spark Spark Configuration Best Practice com.linkedin.drelephant.spark.heuristics.BestPropertiesConventionHeuristic - views.html.helpSparkBestProperties + views.html.help.spark.helpBestProperties 2 @@ -148,7 +148,7 @@ spark Spark Memory Limit com.linkedin.drelephant.spark.heuristics.MemoryLimitHeuristic - views.html.helpSparkMemoryLimit + views.html.help.spark.helpMemoryLimit 2 @@ -158,7 +158,7 @@ spark Spark Stage Runtime com.linkedin.drelephant.spark.heuristics.StageRuntimeHeuristic - views.html.helpSparkStageRuntime + views.html.help.spark.helpStageRuntime 2 @@ -168,7 +168,7 @@ spark Spark Job Runtime com.linkedin.drelephant.spark.heuristics.JobRuntimeHeuristic - views.html.helpSparkJobRuntime + views.html.help.spark.helpJobRuntime 2 @@ -178,7 +178,7 @@ spark Spark Executor Load Balance com.linkedin.drelephant.spark.heuristics.ExecutorLoadHeuristic - views.html.helpSparkExecutorLoad + views.html.help.spark.helpExecutorLoad 2 @@ -188,7 +188,7 @@ spark Spark Event Log Limit com.linkedin.drelephant.spark.heuristics.EventLogLimitHeuristic - views.html.helpSparkEventLogLimit + views.html.help.spark.helpEventLogLimit 2 diff --git a/app/com/linkedin/drelephant/ElephantContext.java b/app/com/linkedin/drelephant/ElephantContext.java index c0a0427b0..fd861b23f 100644 --- a/app/com/linkedin/drelephant/ElephantContext.java +++ b/app/com/linkedin/drelephant/ElephantContext.java @@ -212,7 +212,7 @@ private void loadHeuristics() { // Bind No_DATA heuristic to its helper pages, no need to add any real configurations _heuristicsConfData.add( - new HeuristicConfigurationData(HeuristicResult.NO_DATA.getAnalysis(), null, "views.html.helpNoData", null)); + new HeuristicConfigurationData(HeuristicResult.NO_DATA.getAnalysis(), null, "views.html.help.helpNoData", null)); } /** diff --git a/app/controllers/Application.java b/app/controllers/Application.java index 89ec4c8e0..706606162 100644 --- a/app/controllers/Application.java +++ b/app/controllers/Application.java @@ -15,8 +15,13 @@ */ package controllers; +import com.avaje.ebean.ExpressionList; +import com.avaje.ebean.RawSql; +import com.avaje.ebean.RawSqlBuilder; import com.google.common.collect.Sets; import com.linkedin.drelephant.ElephantContext; +import com.linkedin.drelephant.analysis.Severity; +import com.linkedin.drelephant.util.HeuristicConfigurationData; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.text.ParseException; @@ -30,14 +35,11 @@ import java.util.List; import java.util.Map; import java.util.Set; - import model.JobHeuristicResult; import model.JobResult; - import org.apache.http.client.utils.URLEncodedUtils; import org.apache.http.message.BasicNameValuePair; import org.apache.log4j.Logger; - import play.api.Play; import play.api.templates.Html; import play.data.DynamicForm; @@ -45,21 +47,15 @@ import play.libs.Json; import play.mvc.Controller; import play.mvc.Result; -import views.html.compare; -import views.html.compareresults; import views.html.emailcritical; -import views.html.help; -import views.html.index; -import views.html.multijob; -import views.html.relatedjob; -import views.html.search; -import views.html.singlejob; - -import com.avaje.ebean.ExpressionList; -import com.avaje.ebean.RawSql; -import com.avaje.ebean.RawSqlBuilder; -import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.util.HeuristicConfigurationData; +import views.html.page.comparePage; +import views.html.page.helpPage; +import views.html.page.homePage; +import views.html.page.searchPage; +import views.html.results.compareResults; +import views.html.results.flowDetails; +import views.html.results.jobDetails; +import views.html.results.searchResults; public class Application extends Controller { @@ -110,14 +106,14 @@ public static Result search() { if (!jobId.isEmpty()) { JobResult result = JobResult.find.byId(jobId); if (result != null) { - return ok(search.render(null, singlejob.render(result))); + return ok(searchPage.render(null, jobDetails.render(result))); } else { - return ok(search.render(null, singlejob.render(null))); + return ok(searchPage.render(null, jobDetails.render(null))); } } else if (flowUrl != null && !flowUrl.isEmpty()) { List results = JobResult.find.where().eq(JobResult.TABLE.FLOW_EXEC_URL, flowUrl).findList(); Map> map = groupJobs(results, GroupBy.JOB_EXECUTION_URL); - return ok(search.render(null, relatedjob.render(flowUrl, map))); + return ok(searchPage.render(null, flowDetails.render(flowUrl, map))); } // Paginate the results @@ -141,11 +137,11 @@ public static Result search() { .setMaxRows((paginationStats.getPageBarLength() - 1) * pageLength + 1).findList(); paginationStats.setQueryString(getQueryString()); if (results.isEmpty() || currentPage > paginationStats.computePaginationBarEndIndex(results.size())) { - return ok(search.render(null, singlejob.render(null))); + return ok(searchPage.render(null, jobDetails.render(null))); } else { - return ok(search.render( + return ok(searchPage.render( paginationStats, - multijob.render( + searchResults.render( "Results", results.subList((currentPage - paginationBarStartIndex) * pageLength, Math.min(results.size(), (currentPage - paginationBarStartIndex + 1) * pageLength))))); @@ -158,7 +154,7 @@ public static Result compare() { flowExecUrl1 = (flowExecUrl1 != null) ? flowExecUrl1.trim() : null; String flowExecUrl2 = form.get(COMPARE_FLOW_URL2); flowExecUrl2 = (flowExecUrl2 != null) ? flowExecUrl2.trim() : null; - return ok(compare.render(compareresults.render("Comparison Results", compareFlows(flowExecUrl1, flowExecUrl2)))); + return ok(comparePage.render(compareResults.render("Comparison Results", compareFlows(flowExecUrl1, flowExecUrl2)))); } /** @@ -297,8 +293,8 @@ public static Result dashboard(int page) { JobResult.find.where().gt(JobResult.TABLE.ANALYSIS_TIME, now - DAY).order().desc(JobResult.TABLE.ANALYSIS_TIME) .setMaxRows(50).fetch("heuristicResults").findList(); - return ok(index.render(_numJobsAnalyzed, _numJobsSevere, _numJobsCritical, - multijob.render("Latest analysis", results))); + return ok(homePage.render(_numJobsAnalyzed, _numJobsSevere, _numJobsCritical, + searchResults.render("Latest analysis", results))); } public static Result help() { @@ -312,7 +308,7 @@ public static Result help() { title = topic; } } - return ok(help.render(title, page)); + return ok(helpPage.render(title, page)); } //create a map to cache pages. @@ -361,7 +357,7 @@ public static Result allJobExecs() { } Map> map = groupJobs(results, GroupBy.JOB_EXECUTION_URL); - return ok(search.render(null, relatedjob.render(jobUrl, map))); + return ok(searchPage.render(null, flowDetails.render(jobUrl, map))); } /** @@ -377,7 +373,7 @@ public static Result flowRelated() { } Map> map = groupJobs(results, GroupBy.JOB_EXECUTION_URL); - return ok(search.render(null, relatedjob.render(execUrl, map))); + return ok(searchPage.render(null, flowDetails.render(execUrl, map))); } public static Result restJobResult(String jobId) { diff --git a/app/views/emailcritical.scala.html b/app/views/emailcritical.scala.html index 36b030ae2..dd08518f6 100644 --- a/app/views/emailcritical.scala.html +++ b/app/views/emailcritical.scala.html @@ -13,39 +13,38 @@ * License for the specific language governing permissions and limitations under * the License. *@ + @(result: model.JobResult) - - - -
-

Dr. Elephant needs your help!

+ + + +
+

Dr. Elephant needs your help!

-

- Your Hadoop job is endangering the elephants living in our cluster. -

-

+

Your Hadoop job is endangering the elephants living in our cluster.

+

Please take a look here to figure out what chemicals is causing the elephants to go nuts. -

-

+

+

Here are the aspects of the job that we need to look at:

    - @for(heuristicResult <- result.heuristicResults) { + @for(heuristicResult <- result.heuristicResults) { @if(heuristicResult.severity == com.linkedin.drelephant.analysis.Severity.CRITICAL) { -
  • @heuristicResult.analysisName
  • +
  • @heuristicResult.analysisName
  • } - } + }
-

-

+

+

[Dr. Elephant] [Jobtracker]
@result.jobId
@result.jobName -

-

+

+

Thanks! -

-
- +

+
+ \ No newline at end of file diff --git a/app/views/helpNoData.scala.html b/app/views/help/helpNoData.scala.html similarity index 100% rename from app/views/helpNoData.scala.html rename to app/views/help/helpNoData.scala.html diff --git a/app/views/helpException.scala.html b/app/views/help/mapreduce/helpException.scala.html similarity index 100% rename from app/views/helpException.scala.html rename to app/views/help/mapreduce/helpException.scala.html diff --git a/app/views/helpGC.scala.html b/app/views/help/mapreduce/helpGC.scala.html similarity index 100% rename from app/views/helpGC.scala.html rename to app/views/help/mapreduce/helpGC.scala.html diff --git a/app/views/helpJobQueueLimit.scala.html b/app/views/help/mapreduce/helpJobQueueLimit.scala.html similarity index 100% rename from app/views/helpJobQueueLimit.scala.html rename to app/views/help/mapreduce/helpJobQueueLimit.scala.html diff --git a/app/views/helpMapperDataSkew.scala.html b/app/views/help/mapreduce/helpMapperDataSkew.scala.html similarity index 100% rename from app/views/helpMapperDataSkew.scala.html rename to app/views/help/mapreduce/helpMapperDataSkew.scala.html diff --git a/app/views/helpMapperMemory.scala.html b/app/views/help/mapreduce/helpMapperMemory.scala.html similarity index 100% rename from app/views/helpMapperMemory.scala.html rename to app/views/help/mapreduce/helpMapperMemory.scala.html diff --git a/app/views/helpMapperSpeed.scala.html b/app/views/help/mapreduce/helpMapperSpeed.scala.html similarity index 100% rename from app/views/helpMapperSpeed.scala.html rename to app/views/help/mapreduce/helpMapperSpeed.scala.html diff --git a/app/views/helpMapperSpill.scala.html b/app/views/help/mapreduce/helpMapperSpill.scala.html similarity index 100% rename from app/views/helpMapperSpill.scala.html rename to app/views/help/mapreduce/helpMapperSpill.scala.html diff --git a/app/views/helpMapperTime.scala.html b/app/views/help/mapreduce/helpMapperTime.scala.html similarity index 100% rename from app/views/helpMapperTime.scala.html rename to app/views/help/mapreduce/helpMapperTime.scala.html diff --git a/app/views/helpMemory.scala.html b/app/views/help/mapreduce/helpMemory.scala.html similarity index 100% rename from app/views/helpMemory.scala.html rename to app/views/help/mapreduce/helpMemory.scala.html diff --git a/app/views/helpReducerDataSkew.scala.html b/app/views/help/mapreduce/helpReducerDataSkew.scala.html similarity index 100% rename from app/views/helpReducerDataSkew.scala.html rename to app/views/help/mapreduce/helpReducerDataSkew.scala.html diff --git a/app/views/helpReducerMemory.scala.html b/app/views/help/mapreduce/helpReducerMemory.scala.html similarity index 100% rename from app/views/helpReducerMemory.scala.html rename to app/views/help/mapreduce/helpReducerMemory.scala.html diff --git a/app/views/helpReducerTime.scala.html b/app/views/help/mapreduce/helpReducerTime.scala.html similarity index 100% rename from app/views/helpReducerTime.scala.html rename to app/views/help/mapreduce/helpReducerTime.scala.html diff --git a/app/views/helpShuffleSort.scala.html b/app/views/help/mapreduce/helpShuffleSort.scala.html similarity index 100% rename from app/views/helpShuffleSort.scala.html rename to app/views/help/mapreduce/helpShuffleSort.scala.html diff --git a/app/views/helpSparkBestProperties.scala.html b/app/views/help/spark/helpBestProperties.scala.html similarity index 100% rename from app/views/helpSparkBestProperties.scala.html rename to app/views/help/spark/helpBestProperties.scala.html diff --git a/app/views/helpSparkEventLogLimit.scala.html b/app/views/help/spark/helpEventLogLimit.scala.html similarity index 100% rename from app/views/helpSparkEventLogLimit.scala.html rename to app/views/help/spark/helpEventLogLimit.scala.html diff --git a/app/views/helpSparkExecutorLoad.scala.html b/app/views/help/spark/helpExecutorLoad.scala.html similarity index 100% rename from app/views/helpSparkExecutorLoad.scala.html rename to app/views/help/spark/helpExecutorLoad.scala.html diff --git a/app/views/helpSparkJobRuntime.scala.html b/app/views/help/spark/helpJobRuntime.scala.html similarity index 100% rename from app/views/helpSparkJobRuntime.scala.html rename to app/views/help/spark/helpJobRuntime.scala.html diff --git a/app/views/helpSparkMemoryLimit.scala.html b/app/views/help/spark/helpMemoryLimit.scala.html similarity index 100% rename from app/views/helpSparkMemoryLimit.scala.html rename to app/views/help/spark/helpMemoryLimit.scala.html diff --git a/app/views/helpSparkStageRuntime.scala.html b/app/views/help/spark/helpStageRuntime.scala.html similarity index 100% rename from app/views/helpSparkStageRuntime.scala.html rename to app/views/help/spark/helpStageRuntime.scala.html diff --git a/app/views/main.scala.html b/app/views/main.scala.html index e8f1fa291..8df2bc20d 100644 --- a/app/views/main.scala.html +++ b/app/views/main.scala.html @@ -16,50 +16,57 @@ @(title: String, page: String)(content: Html) +@* +* The base layout of Dr. Elephant +* +* This includes the nav bar at the top and other supporting stylesheets +*@ + - - @title - - - + + @title + + + - - + + - - - + + + - - - - + + +
Jobtracker:Job History: @result.url
+ + + + + + @if(result.jobExecUrl != null){ + + + + + } + @if(result.jobUrl != null){ + + + + + } + @if(result.flowExecUrl != null){ + + + + + } + @if(result.flowUrl != null){ + + + + + } + +
Jobtracker:@result.url
Job execution:@result.jobExecUrl
Job definition:@result.jobUrl
Flow execution:@result.flowExecUrl
Flow definition:@result.flowUrl
+ @if(result.flowExecUrl != null){ + + } + @if(result.jobUrl != null){ + + } +
+ +
 
+
+ @for(heuristicResult <- result.heuristicResults) { + @heuristicResult.analysisName + } +
+

+
+ + + + } else { +
+

Results

+
+ + + } + diff --git a/app/views/results/searchResults.scala.html b/app/views/results/searchResults.scala.html new file mode 100644 index 000000000..ec15a3c8a --- /dev/null +++ b/app/views/results/searchResults.scala.html @@ -0,0 +1,38 @@ +@* +* Copyright 2015 LinkedIn Corp. +* +* 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. +*@ + +@(title: String, results: List[model.JobResult]) + +@* +* The layout of the job results. +* +* @param title The title of the search results +* @param results The search results to be displayed +*@ + +
+
+

@title

+
+
+ @for(result <- results) { + + @tags.jobSummary(result) + + } +
+
diff --git a/app/views/search.scala.html b/app/views/search.scala.html deleted file mode 100644 index 506482414..000000000 --- a/app/views/search.scala.html +++ /dev/null @@ -1,89 +0,0 @@ -@* -* Copyright 2015 LinkedIn Corp. -* -* 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. -*@ -@(paginationStats: controllers.PaginationStats)(results: Html) - -@main("Dr. Elephant - Search", "search") { - @tags.column(3) { - @tags.panel(){ Search } { -
-
- - -
-
- - -
-
- - -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
- -
- } - } - @tags.column(9) { - @results - @if(paginationStats != null) { - @pagination(paginationStats, routes.Application.search()) - } - } -} diff --git a/app/views/singlejob.scala.html b/app/views/singlejob.scala.html deleted file mode 100644 index 08186c672..000000000 --- a/app/views/singlejob.scala.html +++ /dev/null @@ -1,110 +0,0 @@ -@* -* Copyright 2015 LinkedIn Corp. -* -* 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. -*@ -@(result: model.JobResult) - -
- @if(result != null) { -
-

-

[@result.username] [@result.jobType] @result.jobId

-
@(tags.formatDate(result.analysisTime))
-
@result.jobName
-
- - - - - - - @if(result.jobExecUrl != null){ - - - - - } - @if(result.jobUrl != null){ - - - - - } - @if(result.flowExecUrl != null){ - - - - - } - @if(result.flowUrl != null){ - - - - - } - -
Job History:@result.url
Job execution:@result.jobExecUrl
Job definition:@result.jobUrl
Flow execution:@result.flowExecUrl
Flow definition:@result.flowUrl
- @if(result.flowExecUrl != null){ - - } - @if(result.jobUrl != null){ - - } -
- -
 
-
- @for(heuristicResult <- result.heuristicResults) { - @heuristicResult.analysisName - } -
-

-
- - } else { -
-

Results

-
- - } -
diff --git a/app/views/tags/jobHeader.scala.html b/app/views/tags/jobHeader.scala.html new file mode 100644 index 000000000..f6a042a3d --- /dev/null +++ b/app/views/tags/jobHeader.scala.html @@ -0,0 +1,33 @@ +@* +* Copyright 2015 LinkedIn Corp. +* +* 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. +*@ + +@(result: model.JobResult) + +@* +* The job header includes the following info, +* The user who ran the job +* The job type +* The job id +* And the analysis time +* +* @param result The job of type JobResult +*@ + +

+

[@result.username] [@result.jobType] @result.jobId

+
@(tags.formatDate(result.analysisTime))
+
@result.jobName
+

\ No newline at end of file diff --git a/app/views/index.scala.html b/app/views/tags/jobSummary.scala.html similarity index 56% rename from app/views/index.scala.html rename to app/views/tags/jobSummary.scala.html index c77789598..cd9e00568 100644 --- a/app/views/index.scala.html +++ b/app/views/tags/jobSummary.scala.html @@ -13,14 +13,19 @@ * License for the specific language governing permissions and limitations under * the License. *@ -@(numJobsAnalyzed: Int, numJobsSevere: Int, numJobsCritical: Int)(results: Html) -@main("Dr. Elephant", "dashboard") { -
-

Dr. Elephant has been busy!

-

We looked through @numJobsAnalyzed jobs today.
- About @numJobsSevere of them could use some tuning.
- About @numJobsCritical of them need some serious attention!

-
- @results -} +@(result: model.JobResult) + +@* +* Displays a brief info of the job. +* The job block includes, the job header and the heuristic info. +* +* @param result The job of type JobResult +*@ + +@tags.jobHeader(result) +

+ @for(heuristicResult <- result.heuristicResults) { + @heuristicResult.analysisName + } +

\ No newline at end of file diff --git a/app/views/pagination.scala.html b/app/views/tags/pagination.scala.html similarity index 90% rename from app/views/pagination.scala.html rename to app/views/tags/pagination.scala.html index 51201cc56..e3b1ceadb 100644 --- a/app/views/pagination.scala.html +++ b/app/views/tags/pagination.scala.html @@ -13,16 +13,24 @@ * License for the specific language governing permissions and limitations under * the License. *@ + @(paginationStats: controllers.PaginationStats, route:Call) +@* +* The layout of the pagination bar. +* +* @param paginationStats The pagination information +* @param route the search call to be made +*@ + diff --git a/app/views/results/jobHistoryResults.scala.html b/app/views/results/jobHistoryResults.scala.html new file mode 100644 index 000000000..b8febe6c6 --- /dev/null +++ b/app/views/results/jobHistoryResults.scala.html @@ -0,0 +1,109 @@ +@* +* Copyright 2015 LinkedIn Corp. +* +* 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. +*@ + +@(jobUrl: String, results: Map[String, List[model.JobResult]], maxStages: Int, flowExecTimeList: List[Long]) + +@import com.linkedin.drelephant.analysis.Severity + +@getSeverityColor(severity : Severity) = @{ + var color: String = "#5cb85c"; // LOW or NONE + + if(severity.getText().equalsIgnoreCase("CRITICAL")) { + color = "#d9534f"; + } else if(severity.getText().equalsIgnoreCase("SEVERE")) { + color = "#e4804e"; + } else if(severity.getText().equalsIgnoreCase("MODERATE")) { + color = "#f0ad4e"; + } + + color +} + +@if(results != null && results.nonEmpty) { + @tags.panel(){ Job History Results: @jobUrl} { + + + +
+ + +
+ + +
+ + +
+ + + + + + + @for(i <- 1 to maxStages) { + + } + + + + + @for((flowExecUrl, jobs) <- results) { + + + + + + + @for(i <- 1 to maxStages) { + + } + + + } + + +
Job ExecutionsStage @i
+ Loading... + + @if(i <= jobs.length) { + @for((heuristicResult, jobCount) <- jobs(i-1).heuristicResults.zipWithIndex) { + ◉ + +
+

@heuristicResult.job.jobId

+

@heuristicResult.analysisName

+ + + @for(dataArray <- heuristicResult.getDataArray()) { + + @for(cell <- dataArray) { + + } + + } + +
@cell
+
+
+ } + } +
+
+
+ } +} \ No newline at end of file diff --git a/build.sbt b/build.sbt index efd14606c..8908f56cf 100644 --- a/build.sbt +++ b/build.sbt @@ -42,7 +42,8 @@ libraryDependencies ++= Seq( ExclusionRule(organization = "net.razorvine") ), // Hadoop defaultly are using guava 11.0, might raise NoSuchMethodException - "com.google.guava" % "guava" % "18.0" + "com.google.guava" % "guava" % "18.0", + "com.google.code.gson" % "gson" % "2.2.4" ) ivyConfigurations += config("compileonly").hide diff --git a/conf/evolutions/default/7.sql b/conf/evolutions/default/7.sql index 5911b1420..b09eafa6f 100644 --- a/conf/evolutions/default/7.sql +++ b/conf/evolutions/default/7.sql @@ -14,14 +14,15 @@ # the License. # -# add/remove index on job_url +# add/remove index on flow_url and job_url # --- !Ups +create index ix_job_result_flow_url on job_result (flow_url) using HASH; create index ix_job_result_job_url on job_result (job_url) using HASH; # --- !Downs - +drop index ix_job_result_flow_url ON job_result; drop index ix_job_result_job_url ON job_result; diff --git a/conf/routes b/conf/routes index dfe413fe3..5f52445da 100644 --- a/conf/routes +++ b/conf/routes @@ -25,13 +25,16 @@ GET /help controllers.Application.help() GET /email controllers.Application.testEmail() GET /search controllers.Application.search() GET /compare controllers.Application.compare() +GET /flowhistory controllers.Application.flowHistory() +GET /jobhistory controllers.Application.jobHistory() GET /flowrelated controllers.Application.flowRelated() -GET /alljobexecs controllers.Application.allJobExecs() GET /rest/job controllers.Application.restJobResult(id: String) GET /rest/jobexec controllers.Application.restJobExecResult(url: String) GET /rest/flowexec controllers.Application.restFlowExecResult(url: String) GET /rest/search controllers.Application.restSearch() GET /rest/compare controllers.Application.restCompare() +GET /rest/flowgraphdata controllers.Application.restFlowGraphData(url: String) +GET /rest/jobgraphdata controllers.Application.restJobGraphData(url: String) # Map static resources from the /public folder to the /assets URL path diff --git a/public/css/datepicker.css b/public/assets/bootstrap-datepicker/css/datepicker.css similarity index 100% rename from public/css/datepicker.css rename to public/assets/bootstrap-datepicker/css/datepicker.css diff --git a/public/js/bootstrap-datepicker.js b/public/assets/bootstrap-datepicker/js/bootstrap-datepicker.js similarity index 100% rename from public/js/bootstrap-datepicker.js rename to public/assets/bootstrap-datepicker/js/bootstrap-datepicker.js diff --git a/public/css/bootstrap-theme.css b/public/assets/bootstrap/css/bootstrap-theme.css similarity index 100% rename from public/css/bootstrap-theme.css rename to public/assets/bootstrap/css/bootstrap-theme.css diff --git a/public/css/bootstrap-theme.css.map b/public/assets/bootstrap/css/bootstrap-theme.css.map similarity index 100% rename from public/css/bootstrap-theme.css.map rename to public/assets/bootstrap/css/bootstrap-theme.css.map diff --git a/public/css/bootstrap-theme.min.css b/public/assets/bootstrap/css/bootstrap-theme.min.css similarity index 100% rename from public/css/bootstrap-theme.min.css rename to public/assets/bootstrap/css/bootstrap-theme.min.css diff --git a/public/css/bootstrap.css b/public/assets/bootstrap/css/bootstrap.css similarity index 100% rename from public/css/bootstrap.css rename to public/assets/bootstrap/css/bootstrap.css diff --git a/public/css/bootstrap.css.map b/public/assets/bootstrap/css/bootstrap.css.map similarity index 100% rename from public/css/bootstrap.css.map rename to public/assets/bootstrap/css/bootstrap.css.map diff --git a/public/css/bootstrap.min.css b/public/assets/bootstrap/css/bootstrap.min.css similarity index 100% rename from public/css/bootstrap.min.css rename to public/assets/bootstrap/css/bootstrap.min.css diff --git a/public/fonts/glyphicons-halflings-regular.eot b/public/assets/bootstrap/fonts/glyphicons-halflings-regular.eot similarity index 100% rename from public/fonts/glyphicons-halflings-regular.eot rename to public/assets/bootstrap/fonts/glyphicons-halflings-regular.eot diff --git a/public/fonts/glyphicons-halflings-regular.svg b/public/assets/bootstrap/fonts/glyphicons-halflings-regular.svg similarity index 100% rename from public/fonts/glyphicons-halflings-regular.svg rename to public/assets/bootstrap/fonts/glyphicons-halflings-regular.svg diff --git a/public/fonts/glyphicons-halflings-regular.ttf b/public/assets/bootstrap/fonts/glyphicons-halflings-regular.ttf similarity index 100% rename from public/fonts/glyphicons-halflings-regular.ttf rename to public/assets/bootstrap/fonts/glyphicons-halflings-regular.ttf diff --git a/public/fonts/glyphicons-halflings-regular.woff b/public/assets/bootstrap/fonts/glyphicons-halflings-regular.woff similarity index 100% rename from public/fonts/glyphicons-halflings-regular.woff rename to public/assets/bootstrap/fonts/glyphicons-halflings-regular.woff diff --git a/public/js/bootstrap.js b/public/assets/bootstrap/js/bootstrap.js similarity index 100% rename from public/js/bootstrap.js rename to public/assets/bootstrap/js/bootstrap.js diff --git a/public/js/bootstrap.min.js b/public/assets/bootstrap/js/bootstrap.min.js similarity index 100% rename from public/js/bootstrap.min.js rename to public/assets/bootstrap/js/bootstrap.min.js diff --git a/public/assets/d3/d3.js b/public/assets/d3/d3.js new file mode 100644 index 000000000..e4d866432 --- /dev/null +++ b/public/assets/d3/d3.js @@ -0,0 +1,9503 @@ +!function() { + var d3 = { + version: "3.5.6" + }; + var d3_arraySlice = [].slice, d3_array = function(list) { + return d3_arraySlice.call(list); + }; + var d3_document = this.document; + function d3_documentElement(node) { + return node && (node.ownerDocument || node.document || node).documentElement; + } + function d3_window(node) { + return node && (node.ownerDocument && node.ownerDocument.defaultView || node.document && node || node.defaultView); + } + if (d3_document) { + try { + d3_array(d3_document.documentElement.childNodes)[0].nodeType; + } catch (e) { + d3_array = function(list) { + var i = list.length, array = new Array(i); + while (i--) array[i] = list[i]; + return array; + }; + } + } + if (!Date.now) Date.now = function() { + return +new Date(); + }; + if (d3_document) { + try { + d3_document.createElement("DIV").style.setProperty("opacity", 0, ""); + } catch (error) { + var d3_element_prototype = this.Element.prototype, d3_element_setAttribute = d3_element_prototype.setAttribute, d3_element_setAttributeNS = d3_element_prototype.setAttributeNS, d3_style_prototype = this.CSSStyleDeclaration.prototype, d3_style_setProperty = d3_style_prototype.setProperty; + d3_element_prototype.setAttribute = function(name, value) { + d3_element_setAttribute.call(this, name, value + ""); + }; + d3_element_prototype.setAttributeNS = function(space, local, value) { + d3_element_setAttributeNS.call(this, space, local, value + ""); + }; + d3_style_prototype.setProperty = function(name, value, priority) { + d3_style_setProperty.call(this, name, value + "", priority); + }; + } + } + d3.ascending = d3_ascending; + function d3_ascending(a, b) { + return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN; + } + d3.descending = function(a, b) { + return b < a ? -1 : b > a ? 1 : b >= a ? 0 : NaN; + }; + d3.min = function(array, f) { + var i = -1, n = array.length, a, b; + if (arguments.length === 1) { + while (++i < n) if ((b = array[i]) != null && b >= b) { + a = b; + break; + } + while (++i < n) if ((b = array[i]) != null && a > b) a = b; + } else { + while (++i < n) if ((b = f.call(array, array[i], i)) != null && b >= b) { + a = b; + break; + } + while (++i < n) if ((b = f.call(array, array[i], i)) != null && a > b) a = b; + } + return a; + }; + d3.max = function(array, f) { + var i = -1, n = array.length, a, b; + if (arguments.length === 1) { + while (++i < n) if ((b = array[i]) != null && b >= b) { + a = b; + break; + } + while (++i < n) if ((b = array[i]) != null && b > a) a = b; + } else { + while (++i < n) if ((b = f.call(array, array[i], i)) != null && b >= b) { + a = b; + break; + } + while (++i < n) if ((b = f.call(array, array[i], i)) != null && b > a) a = b; + } + return a; + }; + d3.extent = function(array, f) { + var i = -1, n = array.length, a, b, c; + if (arguments.length === 1) { + while (++i < n) if ((b = array[i]) != null && b >= b) { + a = c = b; + break; + } + while (++i < n) if ((b = array[i]) != null) { + if (a > b) a = b; + if (c < b) c = b; + } + } else { + while (++i < n) if ((b = f.call(array, array[i], i)) != null && b >= b) { + a = c = b; + break; + } + while (++i < n) if ((b = f.call(array, array[i], i)) != null) { + if (a > b) a = b; + if (c < b) c = b; + } + } + return [ a, c ]; + }; + function d3_number(x) { + return x === null ? NaN : +x; + } + function d3_numeric(x) { + return !isNaN(x); + } + d3.sum = function(array, f) { + var s = 0, n = array.length, a, i = -1; + if (arguments.length === 1) { + while (++i < n) if (d3_numeric(a = +array[i])) s += a; + } else { + while (++i < n) if (d3_numeric(a = +f.call(array, array[i], i))) s += a; + } + return s; + }; + d3.mean = function(array, f) { + var s = 0, n = array.length, a, i = -1, j = n; + if (arguments.length === 1) { + while (++i < n) if (d3_numeric(a = d3_number(array[i]))) s += a; else --j; + } else { + while (++i < n) if (d3_numeric(a = d3_number(f.call(array, array[i], i)))) s += a; else --j; + } + if (j) return s / j; + }; + d3.quantile = function(values, p) { + var H = (values.length - 1) * p + 1, h = Math.floor(H), v = +values[h - 1], e = H - h; + return e ? v + e * (values[h] - v) : v; + }; + d3.median = function(array, f) { + var numbers = [], n = array.length, a, i = -1; + if (arguments.length === 1) { + while (++i < n) if (d3_numeric(a = d3_number(array[i]))) numbers.push(a); + } else { + while (++i < n) if (d3_numeric(a = d3_number(f.call(array, array[i], i)))) numbers.push(a); + } + if (numbers.length) return d3.quantile(numbers.sort(d3_ascending), .5); + }; + d3.variance = function(array, f) { + var n = array.length, m = 0, a, d, s = 0, i = -1, j = 0; + if (arguments.length === 1) { + while (++i < n) { + if (d3_numeric(a = d3_number(array[i]))) { + d = a - m; + m += d / ++j; + s += d * (a - m); + } + } + } else { + while (++i < n) { + if (d3_numeric(a = d3_number(f.call(array, array[i], i)))) { + d = a - m; + m += d / ++j; + s += d * (a - m); + } + } + } + if (j > 1) return s / (j - 1); + }; + d3.deviation = function() { + var v = d3.variance.apply(this, arguments); + return v ? Math.sqrt(v) : v; + }; + function d3_bisector(compare) { + return { + left: function(a, x, lo, hi) { + if (arguments.length < 3) lo = 0; + if (arguments.length < 4) hi = a.length; + while (lo < hi) { + var mid = lo + hi >>> 1; + if (compare(a[mid], x) < 0) lo = mid + 1; else hi = mid; + } + return lo; + }, + right: function(a, x, lo, hi) { + if (arguments.length < 3) lo = 0; + if (arguments.length < 4) hi = a.length; + while (lo < hi) { + var mid = lo + hi >>> 1; + if (compare(a[mid], x) > 0) hi = mid; else lo = mid + 1; + } + return lo; + } + }; + } + var d3_bisect = d3_bisector(d3_ascending); + d3.bisectLeft = d3_bisect.left; + d3.bisect = d3.bisectRight = d3_bisect.right; + d3.bisector = function(f) { + return d3_bisector(f.length === 1 ? function(d, x) { + return d3_ascending(f(d), x); + } : f); + }; + d3.shuffle = function(array, i0, i1) { + if ((m = arguments.length) < 3) { + i1 = array.length; + if (m < 2) i0 = 0; + } + var m = i1 - i0, t, i; + while (m) { + i = Math.random() * m-- | 0; + t = array[m + i0], array[m + i0] = array[i + i0], array[i + i0] = t; + } + return array; + }; + d3.permute = function(array, indexes) { + var i = indexes.length, permutes = new Array(i); + while (i--) permutes[i] = array[indexes[i]]; + return permutes; + }; + d3.pairs = function(array) { + var i = 0, n = array.length - 1, p0, p1 = array[0], pairs = new Array(n < 0 ? 0 : n); + while (i < n) pairs[i] = [ p0 = p1, p1 = array[++i] ]; + return pairs; + }; + d3.zip = function() { + if (!(n = arguments.length)) return []; + for (var i = -1, m = d3.min(arguments, d3_zipLength), zips = new Array(m); ++i < m; ) { + for (var j = -1, n, zip = zips[i] = new Array(n); ++j < n; ) { + zip[j] = arguments[j][i]; + } + } + return zips; + }; + function d3_zipLength(d) { + return d.length; + } + d3.transpose = function(matrix) { + return d3.zip.apply(d3, matrix); + }; + d3.keys = function(map) { + var keys = []; + for (var key in map) keys.push(key); + return keys; + }; + d3.values = function(map) { + var values = []; + for (var key in map) values.push(map[key]); + return values; + }; + d3.entries = function(map) { + var entries = []; + for (var key in map) entries.push({ + key: key, + value: map[key] + }); + return entries; + }; + d3.merge = function(arrays) { + var n = arrays.length, m, i = -1, j = 0, merged, array; + while (++i < n) j += arrays[i].length; + merged = new Array(j); + while (--n >= 0) { + array = arrays[n]; + m = array.length; + while (--m >= 0) { + merged[--j] = array[m]; + } + } + return merged; + }; + var abs = Math.abs; + d3.range = function(start, stop, step) { + if (arguments.length < 3) { + step = 1; + if (arguments.length < 2) { + stop = start; + start = 0; + } + } + if ((stop - start) / step === Infinity) throw new Error("infinite range"); + var range = [], k = d3_range_integerScale(abs(step)), i = -1, j; + start *= k, stop *= k, step *= k; + if (step < 0) while ((j = start + step * ++i) > stop) range.push(j / k); else while ((j = start + step * ++i) < stop) range.push(j / k); + return range; + }; + function d3_range_integerScale(x) { + var k = 1; + while (x * k % 1) k *= 10; + return k; + } + function d3_class(ctor, properties) { + for (var key in properties) { + Object.defineProperty(ctor.prototype, key, { + value: properties[key], + enumerable: false + }); + } + } + d3.map = function(object, f) { + var map = new d3_Map(); + if (object instanceof d3_Map) { + object.forEach(function(key, value) { + map.set(key, value); + }); + } else if (Array.isArray(object)) { + var i = -1, n = object.length, o; + if (arguments.length === 1) while (++i < n) map.set(i, object[i]); else while (++i < n) map.set(f.call(object, o = object[i], i), o); + } else { + for (var key in object) map.set(key, object[key]); + } + return map; + }; + function d3_Map() { + this._ = Object.create(null); + } + var d3_map_proto = "__proto__", d3_map_zero = "\x00"; + d3_class(d3_Map, { + has: d3_map_has, + get: function(key) { + return this._[d3_map_escape(key)]; + }, + set: function(key, value) { + return this._[d3_map_escape(key)] = value; + }, + remove: d3_map_remove, + keys: d3_map_keys, + values: function() { + var values = []; + for (var key in this._) values.push(this._[key]); + return values; + }, + entries: function() { + var entries = []; + for (var key in this._) entries.push({ + key: d3_map_unescape(key), + value: this._[key] + }); + return entries; + }, + size: d3_map_size, + empty: d3_map_empty, + forEach: function(f) { + for (var key in this._) f.call(this, d3_map_unescape(key), this._[key]); + } + }); + function d3_map_escape(key) { + return (key += "") === d3_map_proto || key[0] === d3_map_zero ? d3_map_zero + key : key; + } + function d3_map_unescape(key) { + return (key += "")[0] === d3_map_zero ? key.slice(1) : key; + } + function d3_map_has(key) { + return d3_map_escape(key) in this._; + } + function d3_map_remove(key) { + return (key = d3_map_escape(key)) in this._ && delete this._[key]; + } + function d3_map_keys() { + var keys = []; + for (var key in this._) keys.push(d3_map_unescape(key)); + return keys; + } + function d3_map_size() { + var size = 0; + for (var key in this._) ++size; + return size; + } + function d3_map_empty() { + for (var key in this._) return false; + return true; + } + d3.nest = function() { + var nest = {}, keys = [], sortKeys = [], sortValues, rollup; + function map(mapType, array, depth) { + if (depth >= keys.length) return rollup ? rollup.call(nest, array) : sortValues ? array.sort(sortValues) : array; + var i = -1, n = array.length, key = keys[depth++], keyValue, object, setter, valuesByKey = new d3_Map(), values; + while (++i < n) { + if (values = valuesByKey.get(keyValue = key(object = array[i]))) { + values.push(object); + } else { + valuesByKey.set(keyValue, [ object ]); + } + } + if (mapType) { + object = mapType(); + setter = function(keyValue, values) { + object.set(keyValue, map(mapType, values, depth)); + }; + } else { + object = {}; + setter = function(keyValue, values) { + object[keyValue] = map(mapType, values, depth); + }; + } + valuesByKey.forEach(setter); + return object; + } + function entries(map, depth) { + if (depth >= keys.length) return map; + var array = [], sortKey = sortKeys[depth++]; + map.forEach(function(key, keyMap) { + array.push({ + key: key, + values: entries(keyMap, depth) + }); + }); + return sortKey ? array.sort(function(a, b) { + return sortKey(a.key, b.key); + }) : array; + } + nest.map = function(array, mapType) { + return map(mapType, array, 0); + }; + nest.entries = function(array) { + return entries(map(d3.map, array, 0), 0); + }; + nest.key = function(d) { + keys.push(d); + return nest; + }; + nest.sortKeys = function(order) { + sortKeys[keys.length - 1] = order; + return nest; + }; + nest.sortValues = function(order) { + sortValues = order; + return nest; + }; + nest.rollup = function(f) { + rollup = f; + return nest; + }; + return nest; + }; + d3.set = function(array) { + var set = new d3_Set(); + if (array) for (var i = 0, n = array.length; i < n; ++i) set.add(array[i]); + return set; + }; + function d3_Set() { + this._ = Object.create(null); + } + d3_class(d3_Set, { + has: d3_map_has, + add: function(key) { + this._[d3_map_escape(key += "")] = true; + return key; + }, + remove: d3_map_remove, + values: d3_map_keys, + size: d3_map_size, + empty: d3_map_empty, + forEach: function(f) { + for (var key in this._) f.call(this, d3_map_unescape(key)); + } + }); + d3.behavior = {}; + function d3_identity(d) { + return d; + } + d3.rebind = function(target, source) { + var i = 1, n = arguments.length, method; + while (++i < n) target[method = arguments[i]] = d3_rebind(target, source, source[method]); + return target; + }; + function d3_rebind(target, source, method) { + return function() { + var value = method.apply(source, arguments); + return value === source ? target : value; + }; + } + function d3_vendorSymbol(object, name) { + if (name in object) return name; + name = name.charAt(0).toUpperCase() + name.slice(1); + for (var i = 0, n = d3_vendorPrefixes.length; i < n; ++i) { + var prefixName = d3_vendorPrefixes[i] + name; + if (prefixName in object) return prefixName; + } + } + var d3_vendorPrefixes = [ "webkit", "ms", "moz", "Moz", "o", "O" ]; + function d3_noop() {} + d3.dispatch = function() { + var dispatch = new d3_dispatch(), i = -1, n = arguments.length; + while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch); + return dispatch; + }; + function d3_dispatch() {} + d3_dispatch.prototype.on = function(type, listener) { + var i = type.indexOf("."), name = ""; + if (i >= 0) { + name = type.slice(i + 1); + type = type.slice(0, i); + } + if (type) return arguments.length < 2 ? this[type].on(name) : this[type].on(name, listener); + if (arguments.length === 2) { + if (listener == null) for (type in this) { + if (this.hasOwnProperty(type)) this[type].on(name, null); + } + return this; + } + }; + function d3_dispatch_event(dispatch) { + var listeners = [], listenerByName = new d3_Map(); + function event() { + var z = listeners, i = -1, n = z.length, l; + while (++i < n) if (l = z[i].on) l.apply(this, arguments); + return dispatch; + } + event.on = function(name, listener) { + var l = listenerByName.get(name), i; + if (arguments.length < 2) return l && l.on; + if (l) { + l.on = null; + listeners = listeners.slice(0, i = listeners.indexOf(l)).concat(listeners.slice(i + 1)); + listenerByName.remove(name); + } + if (listener) listeners.push(listenerByName.set(name, { + on: listener + })); + return dispatch; + }; + return event; + } + d3.event = null; + function d3_eventPreventDefault() { + d3.event.preventDefault(); + } + function d3_eventSource() { + var e = d3.event, s; + while (s = e.sourceEvent) e = s; + return e; + } + function d3_eventDispatch(target) { + var dispatch = new d3_dispatch(), i = 0, n = arguments.length; + while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch); + dispatch.of = function(thiz, argumentz) { + return function(e1) { + try { + var e0 = e1.sourceEvent = d3.event; + e1.target = target; + d3.event = e1; + dispatch[e1.type].apply(thiz, argumentz); + } finally { + d3.event = e0; + } + }; + }; + return dispatch; + } + d3.requote = function(s) { + return s.replace(d3_requote_re, "\\$&"); + }; + var d3_requote_re = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g; + var d3_subclass = {}.__proto__ ? function(object, prototype) { + object.__proto__ = prototype; + } : function(object, prototype) { + for (var property in prototype) object[property] = prototype[property]; + }; + function d3_selection(groups) { + d3_subclass(groups, d3_selectionPrototype); + return groups; + } + var d3_select = function(s, n) { + return n.querySelector(s); + }, d3_selectAll = function(s, n) { + return n.querySelectorAll(s); + }, d3_selectMatches = function(n, s) { + var d3_selectMatcher = n.matches || n[d3_vendorSymbol(n, "matchesSelector")]; + d3_selectMatches = function(n, s) { + return d3_selectMatcher.call(n, s); + }; + return d3_selectMatches(n, s); + }; + if (typeof Sizzle === "function") { + d3_select = function(s, n) { + return Sizzle(s, n)[0] || null; + }; + d3_selectAll = Sizzle; + d3_selectMatches = Sizzle.matchesSelector; + } + d3.selection = function() { + return d3.select(d3_document.documentElement); + }; + var d3_selectionPrototype = d3.selection.prototype = []; + d3_selectionPrototype.select = function(selector) { + var subgroups = [], subgroup, subnode, group, node; + selector = d3_selection_selector(selector); + for (var j = -1, m = this.length; ++j < m; ) { + subgroups.push(subgroup = []); + subgroup.parentNode = (group = this[j]).parentNode; + for (var i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) { + subgroup.push(subnode = selector.call(node, node.__data__, i, j)); + if (subnode && "__data__" in node) subnode.__data__ = node.__data__; + } else { + subgroup.push(null); + } + } + } + return d3_selection(subgroups); + }; + function d3_selection_selector(selector) { + return typeof selector === "function" ? selector : function() { + return d3_select(selector, this); + }; + } + d3_selectionPrototype.selectAll = function(selector) { + var subgroups = [], subgroup, node; + selector = d3_selection_selectorAll(selector); + for (var j = -1, m = this.length; ++j < m; ) { + for (var group = this[j], i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) { + subgroups.push(subgroup = d3_array(selector.call(node, node.__data__, i, j))); + subgroup.parentNode = node; + } + } + } + return d3_selection(subgroups); + }; + function d3_selection_selectorAll(selector) { + return typeof selector === "function" ? selector : function() { + return d3_selectAll(selector, this); + }; + } + var d3_nsPrefix = { + svg: "http://www.w3.org/2000/svg", + xhtml: "http://www.w3.org/1999/xhtml", + xlink: "http://www.w3.org/1999/xlink", + xml: "http://www.w3.org/XML/1998/namespace", + xmlns: "http://www.w3.org/2000/xmlns/" + }; + d3.ns = { + prefix: d3_nsPrefix, + qualify: function(name) { + var i = name.indexOf(":"), prefix = name; + if (i >= 0) { + prefix = name.slice(0, i); + name = name.slice(i + 1); + } + return d3_nsPrefix.hasOwnProperty(prefix) ? { + space: d3_nsPrefix[prefix], + local: name + } : name; + } + }; + d3_selectionPrototype.attr = function(name, value) { + if (arguments.length < 2) { + if (typeof name === "string") { + var node = this.node(); + name = d3.ns.qualify(name); + return name.local ? node.getAttributeNS(name.space, name.local) : node.getAttribute(name); + } + for (value in name) this.each(d3_selection_attr(value, name[value])); + return this; + } + return this.each(d3_selection_attr(name, value)); + }; + function d3_selection_attr(name, value) { + name = d3.ns.qualify(name); + function attrNull() { + this.removeAttribute(name); + } + function attrNullNS() { + this.removeAttributeNS(name.space, name.local); + } + function attrConstant() { + this.setAttribute(name, value); + } + function attrConstantNS() { + this.setAttributeNS(name.space, name.local, value); + } + function attrFunction() { + var x = value.apply(this, arguments); + if (x == null) this.removeAttribute(name); else this.setAttribute(name, x); + } + function attrFunctionNS() { + var x = value.apply(this, arguments); + if (x == null) this.removeAttributeNS(name.space, name.local); else this.setAttributeNS(name.space, name.local, x); + } + return value == null ? name.local ? attrNullNS : attrNull : typeof value === "function" ? name.local ? attrFunctionNS : attrFunction : name.local ? attrConstantNS : attrConstant; + } + function d3_collapse(s) { + return s.trim().replace(/\s+/g, " "); + } + d3_selectionPrototype.classed = function(name, value) { + if (arguments.length < 2) { + if (typeof name === "string") { + var node = this.node(), n = (name = d3_selection_classes(name)).length, i = -1; + if (value = node.classList) { + while (++i < n) if (!value.contains(name[i])) return false; + } else { + value = node.getAttribute("class"); + while (++i < n) if (!d3_selection_classedRe(name[i]).test(value)) return false; + } + return true; + } + for (value in name) this.each(d3_selection_classed(value, name[value])); + return this; + } + return this.each(d3_selection_classed(name, value)); + }; + function d3_selection_classedRe(name) { + return new RegExp("(?:^|\\s+)" + d3.requote(name) + "(?:\\s+|$)", "g"); + } + function d3_selection_classes(name) { + return (name + "").trim().split(/^|\s+/); + } + function d3_selection_classed(name, value) { + name = d3_selection_classes(name).map(d3_selection_classedName); + var n = name.length; + function classedConstant() { + var i = -1; + while (++i < n) name[i](this, value); + } + function classedFunction() { + var i = -1, x = value.apply(this, arguments); + while (++i < n) name[i](this, x); + } + return typeof value === "function" ? classedFunction : classedConstant; + } + function d3_selection_classedName(name) { + var re = d3_selection_classedRe(name); + return function(node, value) { + if (c = node.classList) return value ? c.add(name) : c.remove(name); + var c = node.getAttribute("class") || ""; + if (value) { + re.lastIndex = 0; + if (!re.test(c)) node.setAttribute("class", d3_collapse(c + " " + name)); + } else { + node.setAttribute("class", d3_collapse(c.replace(re, " "))); + } + }; + } + d3_selectionPrototype.style = function(name, value, priority) { + var n = arguments.length; + if (n < 3) { + if (typeof name !== "string") { + if (n < 2) value = ""; + for (priority in name) this.each(d3_selection_style(priority, name[priority], value)); + return this; + } + if (n < 2) { + var node = this.node(); + return d3_window(node).getComputedStyle(node, null).getPropertyValue(name); + } + priority = ""; + } + return this.each(d3_selection_style(name, value, priority)); + }; + function d3_selection_style(name, value, priority) { + function styleNull() { + this.style.removeProperty(name); + } + function styleConstant() { + this.style.setProperty(name, value, priority); + } + function styleFunction() { + var x = value.apply(this, arguments); + if (x == null) this.style.removeProperty(name); else this.style.setProperty(name, x, priority); + } + return value == null ? styleNull : typeof value === "function" ? styleFunction : styleConstant; + } + d3_selectionPrototype.property = function(name, value) { + if (arguments.length < 2) { + if (typeof name === "string") return this.node()[name]; + for (value in name) this.each(d3_selection_property(value, name[value])); + return this; + } + return this.each(d3_selection_property(name, value)); + }; + function d3_selection_property(name, value) { + function propertyNull() { + delete this[name]; + } + function propertyConstant() { + this[name] = value; + } + function propertyFunction() { + var x = value.apply(this, arguments); + if (x == null) delete this[name]; else this[name] = x; + } + return value == null ? propertyNull : typeof value === "function" ? propertyFunction : propertyConstant; + } + d3_selectionPrototype.text = function(value) { + return arguments.length ? this.each(typeof value === "function" ? function() { + var v = value.apply(this, arguments); + this.textContent = v == null ? "" : v; + } : value == null ? function() { + this.textContent = ""; + } : function() { + this.textContent = value; + }) : this.node().textContent; + }; + d3_selectionPrototype.html = function(value) { + return arguments.length ? this.each(typeof value === "function" ? function() { + var v = value.apply(this, arguments); + this.innerHTML = v == null ? "" : v; + } : value == null ? function() { + this.innerHTML = ""; + } : function() { + this.innerHTML = value; + }) : this.node().innerHTML; + }; + d3_selectionPrototype.append = function(name) { + name = d3_selection_creator(name); + return this.select(function() { + return this.appendChild(name.apply(this, arguments)); + }); + }; + function d3_selection_creator(name) { + function create() { + var document = this.ownerDocument, namespace = this.namespaceURI; + return namespace ? document.createElementNS(namespace, name) : document.createElement(name); + } + function createNS() { + return this.ownerDocument.createElementNS(name.space, name.local); + } + return typeof name === "function" ? name : (name = d3.ns.qualify(name)).local ? createNS : create; + } + d3_selectionPrototype.insert = function(name, before) { + name = d3_selection_creator(name); + before = d3_selection_selector(before); + return this.select(function() { + return this.insertBefore(name.apply(this, arguments), before.apply(this, arguments) || null); + }); + }; + d3_selectionPrototype.remove = function() { + return this.each(d3_selectionRemove); + }; + function d3_selectionRemove() { + var parent = this.parentNode; + if (parent) parent.removeChild(this); + } + d3_selectionPrototype.data = function(value, key) { + var i = -1, n = this.length, group, node; + if (!arguments.length) { + value = new Array(n = (group = this[0]).length); + while (++i < n) { + if (node = group[i]) { + value[i] = node.__data__; + } + } + return value; + } + function bind(group, groupData) { + var i, n = group.length, m = groupData.length, n0 = Math.min(n, m), updateNodes = new Array(m), enterNodes = new Array(m), exitNodes = new Array(n), node, nodeData; + if (key) { + var nodeByKeyValue = new d3_Map(), keyValues = new Array(n), keyValue; + for (i = -1; ++i < n; ) { + if (nodeByKeyValue.has(keyValue = key.call(node = group[i], node.__data__, i))) { + exitNodes[i] = node; + } else { + nodeByKeyValue.set(keyValue, node); + } + keyValues[i] = keyValue; + } + for (i = -1; ++i < m; ) { + if (!(node = nodeByKeyValue.get(keyValue = key.call(groupData, nodeData = groupData[i], i)))) { + enterNodes[i] = d3_selection_dataNode(nodeData); + } else if (node !== true) { + updateNodes[i] = node; + node.__data__ = nodeData; + } + nodeByKeyValue.set(keyValue, true); + } + for (i = -1; ++i < n; ) { + if (nodeByKeyValue.get(keyValues[i]) !== true) { + exitNodes[i] = group[i]; + } + } + } else { + for (i = -1; ++i < n0; ) { + node = group[i]; + nodeData = groupData[i]; + if (node) { + node.__data__ = nodeData; + updateNodes[i] = node; + } else { + enterNodes[i] = d3_selection_dataNode(nodeData); + } + } + for (;i < m; ++i) { + enterNodes[i] = d3_selection_dataNode(groupData[i]); + } + for (;i < n; ++i) { + exitNodes[i] = group[i]; + } + } + enterNodes.update = updateNodes; + enterNodes.parentNode = updateNodes.parentNode = exitNodes.parentNode = group.parentNode; + enter.push(enterNodes); + update.push(updateNodes); + exit.push(exitNodes); + } + var enter = d3_selection_enter([]), update = d3_selection([]), exit = d3_selection([]); + if (typeof value === "function") { + while (++i < n) { + bind(group = this[i], value.call(group, group.parentNode.__data__, i)); + } + } else { + while (++i < n) { + bind(group = this[i], value); + } + } + update.enter = function() { + return enter; + }; + update.exit = function() { + return exit; + }; + return update; + }; + function d3_selection_dataNode(data) { + return { + __data__: data + }; + } + d3_selectionPrototype.datum = function(value) { + return arguments.length ? this.property("__data__", value) : this.property("__data__"); + }; + d3_selectionPrototype.filter = function(filter) { + var subgroups = [], subgroup, group, node; + if (typeof filter !== "function") filter = d3_selection_filter(filter); + for (var j = 0, m = this.length; j < m; j++) { + subgroups.push(subgroup = []); + subgroup.parentNode = (group = this[j]).parentNode; + for (var i = 0, n = group.length; i < n; i++) { + if ((node = group[i]) && filter.call(node, node.__data__, i, j)) { + subgroup.push(node); + } + } + } + return d3_selection(subgroups); + }; + function d3_selection_filter(selector) { + return function() { + return d3_selectMatches(this, selector); + }; + } + d3_selectionPrototype.order = function() { + for (var j = -1, m = this.length; ++j < m; ) { + for (var group = this[j], i = group.length - 1, next = group[i], node; --i >= 0; ) { + if (node = group[i]) { + if (next && next !== node.nextSibling) next.parentNode.insertBefore(node, next); + next = node; + } + } + } + return this; + }; + d3_selectionPrototype.sort = function(comparator) { + comparator = d3_selection_sortComparator.apply(this, arguments); + for (var j = -1, m = this.length; ++j < m; ) this[j].sort(comparator); + return this.order(); + }; + function d3_selection_sortComparator(comparator) { + if (!arguments.length) comparator = d3_ascending; + return function(a, b) { + return a && b ? comparator(a.__data__, b.__data__) : !a - !b; + }; + } + d3_selectionPrototype.each = function(callback) { + return d3_selection_each(this, function(node, i, j) { + callback.call(node, node.__data__, i, j); + }); + }; + function d3_selection_each(groups, callback) { + for (var j = 0, m = groups.length; j < m; j++) { + for (var group = groups[j], i = 0, n = group.length, node; i < n; i++) { + if (node = group[i]) callback(node, i, j); + } + } + return groups; + } + d3_selectionPrototype.call = function(callback) { + var args = d3_array(arguments); + callback.apply(args[0] = this, args); + return this; + }; + d3_selectionPrototype.empty = function() { + return !this.node(); + }; + d3_selectionPrototype.node = function() { + for (var j = 0, m = this.length; j < m; j++) { + for (var group = this[j], i = 0, n = group.length; i < n; i++) { + var node = group[i]; + if (node) return node; + } + } + return null; + }; + d3_selectionPrototype.size = function() { + var n = 0; + d3_selection_each(this, function() { + ++n; + }); + return n; + }; + function d3_selection_enter(selection) { + d3_subclass(selection, d3_selection_enterPrototype); + return selection; + } + var d3_selection_enterPrototype = []; + d3.selection.enter = d3_selection_enter; + d3.selection.enter.prototype = d3_selection_enterPrototype; + d3_selection_enterPrototype.append = d3_selectionPrototype.append; + d3_selection_enterPrototype.empty = d3_selectionPrototype.empty; + d3_selection_enterPrototype.node = d3_selectionPrototype.node; + d3_selection_enterPrototype.call = d3_selectionPrototype.call; + d3_selection_enterPrototype.size = d3_selectionPrototype.size; + d3_selection_enterPrototype.select = function(selector) { + var subgroups = [], subgroup, subnode, upgroup, group, node; + for (var j = -1, m = this.length; ++j < m; ) { + upgroup = (group = this[j]).update; + subgroups.push(subgroup = []); + subgroup.parentNode = group.parentNode; + for (var i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) { + subgroup.push(upgroup[i] = subnode = selector.call(group.parentNode, node.__data__, i, j)); + subnode.__data__ = node.__data__; + } else { + subgroup.push(null); + } + } + } + return d3_selection(subgroups); + }; + d3_selection_enterPrototype.insert = function(name, before) { + if (arguments.length < 2) before = d3_selection_enterInsertBefore(this); + return d3_selectionPrototype.insert.call(this, name, before); + }; + function d3_selection_enterInsertBefore(enter) { + var i0, j0; + return function(d, i, j) { + var group = enter[j].update, n = group.length, node; + if (j != j0) j0 = j, i0 = 0; + if (i >= i0) i0 = i + 1; + while (!(node = group[i0]) && ++i0 < n) ; + return node; + }; + } + d3.select = function(node) { + var group; + if (typeof node === "string") { + group = [ d3_select(node, d3_document) ]; + group.parentNode = d3_document.documentElement; + } else { + group = [ node ]; + group.parentNode = d3_documentElement(node); + } + return d3_selection([ group ]); + }; + d3.selectAll = function(nodes) { + var group; + if (typeof nodes === "string") { + group = d3_array(d3_selectAll(nodes, d3_document)); + group.parentNode = d3_document.documentElement; + } else { + group = nodes; + group.parentNode = null; + } + return d3_selection([ group ]); + }; + d3_selectionPrototype.on = function(type, listener, capture) { + var n = arguments.length; + if (n < 3) { + if (typeof type !== "string") { + if (n < 2) listener = false; + for (capture in type) this.each(d3_selection_on(capture, type[capture], listener)); + return this; + } + if (n < 2) return (n = this.node()["__on" + type]) && n._; + capture = false; + } + return this.each(d3_selection_on(type, listener, capture)); + }; + function d3_selection_on(type, listener, capture) { + var name = "__on" + type, i = type.indexOf("."), wrap = d3_selection_onListener; + if (i > 0) type = type.slice(0, i); + var filter = d3_selection_onFilters.get(type); + if (filter) type = filter, wrap = d3_selection_onFilter; + function onRemove() { + var l = this[name]; + if (l) { + this.removeEventListener(type, l, l.$); + delete this[name]; + } + } + function onAdd() { + var l = wrap(listener, d3_array(arguments)); + onRemove.call(this); + this.addEventListener(type, this[name] = l, l.$ = capture); + l._ = listener; + } + function removeAll() { + var re = new RegExp("^__on([^.]+)" + d3.requote(type) + "$"), match; + for (var name in this) { + if (match = name.match(re)) { + var l = this[name]; + this.removeEventListener(match[1], l, l.$); + delete this[name]; + } + } + } + return i ? listener ? onAdd : onRemove : listener ? d3_noop : removeAll; + } + var d3_selection_onFilters = d3.map({ + mouseenter: "mouseover", + mouseleave: "mouseout" + }); + if (d3_document) { + d3_selection_onFilters.forEach(function(k) { + if ("on" + k in d3_document) d3_selection_onFilters.remove(k); + }); + } + function d3_selection_onListener(listener, argumentz) { + return function(e) { + var o = d3.event; + d3.event = e; + argumentz[0] = this.__data__; + try { + listener.apply(this, argumentz); + } finally { + d3.event = o; + } + }; + } + function d3_selection_onFilter(listener, argumentz) { + var l = d3_selection_onListener(listener, argumentz); + return function(e) { + var target = this, related = e.relatedTarget; + if (!related || related !== target && !(related.compareDocumentPosition(target) & 8)) { + l.call(target, e); + } + }; + } + var d3_event_dragSelect, d3_event_dragId = 0; + function d3_event_dragSuppress(node) { + var name = ".dragsuppress-" + ++d3_event_dragId, click = "click" + name, w = d3.select(d3_window(node)).on("touchmove" + name, d3_eventPreventDefault).on("dragstart" + name, d3_eventPreventDefault).on("selectstart" + name, d3_eventPreventDefault); + if (d3_event_dragSelect == null) { + d3_event_dragSelect = "onselectstart" in node ? false : d3_vendorSymbol(node.style, "userSelect"); + } + if (d3_event_dragSelect) { + var style = d3_documentElement(node).style, select = style[d3_event_dragSelect]; + style[d3_event_dragSelect] = "none"; + } + return function(suppressClick) { + w.on(name, null); + if (d3_event_dragSelect) style[d3_event_dragSelect] = select; + if (suppressClick) { + var off = function() { + w.on(click, null); + }; + w.on(click, function() { + d3_eventPreventDefault(); + off(); + }, true); + setTimeout(off, 0); + } + }; + } + d3.mouse = function(container) { + return d3_mousePoint(container, d3_eventSource()); + }; + var d3_mouse_bug44083 = this.navigator && /WebKit/.test(this.navigator.userAgent) ? -1 : 0; + function d3_mousePoint(container, e) { + if (e.changedTouches) e = e.changedTouches[0]; + var svg = container.ownerSVGElement || container; + if (svg.createSVGPoint) { + var point = svg.createSVGPoint(); + if (d3_mouse_bug44083 < 0) { + var window = d3_window(container); + if (window.scrollX || window.scrollY) { + svg = d3.select("body").append("svg").style({ + position: "absolute", + top: 0, + left: 0, + margin: 0, + padding: 0, + border: "none" + }, "important"); + var ctm = svg[0][0].getScreenCTM(); + d3_mouse_bug44083 = !(ctm.f || ctm.e); + svg.remove(); + } + } + if (d3_mouse_bug44083) point.x = e.pageX, point.y = e.pageY; else point.x = e.clientX, + point.y = e.clientY; + point = point.matrixTransform(container.getScreenCTM().inverse()); + return [ point.x, point.y ]; + } + var rect = container.getBoundingClientRect(); + return [ e.clientX - rect.left - container.clientLeft, e.clientY - rect.top - container.clientTop ]; + } + d3.touch = function(container, touches, identifier) { + if (arguments.length < 3) identifier = touches, touches = d3_eventSource().changedTouches; + if (touches) for (var i = 0, n = touches.length, touch; i < n; ++i) { + if ((touch = touches[i]).identifier === identifier) { + return d3_mousePoint(container, touch); + } + } + }; + d3.behavior.drag = function() { + var event = d3_eventDispatch(drag, "drag", "dragstart", "dragend"), origin = null, mousedown = dragstart(d3_noop, d3.mouse, d3_window, "mousemove", "mouseup"), touchstart = dragstart(d3_behavior_dragTouchId, d3.touch, d3_identity, "touchmove", "touchend"); + function drag() { + this.on("mousedown.drag", mousedown).on("touchstart.drag", touchstart); + } + function dragstart(id, position, subject, move, end) { + return function() { + var that = this, target = d3.event.target, parent = that.parentNode, dispatch = event.of(that, arguments), dragged = 0, dragId = id(), dragName = ".drag" + (dragId == null ? "" : "-" + dragId), dragOffset, dragSubject = d3.select(subject(target)).on(move + dragName, moved).on(end + dragName, ended), dragRestore = d3_event_dragSuppress(target), position0 = position(parent, dragId); + if (origin) { + dragOffset = origin.apply(that, arguments); + dragOffset = [ dragOffset.x - position0[0], dragOffset.y - position0[1] ]; + } else { + dragOffset = [ 0, 0 ]; + } + dispatch({ + type: "dragstart" + }); + function moved() { + var position1 = position(parent, dragId), dx, dy; + if (!position1) return; + dx = position1[0] - position0[0]; + dy = position1[1] - position0[1]; + dragged |= dx | dy; + position0 = position1; + dispatch({ + type: "drag", + x: position1[0] + dragOffset[0], + y: position1[1] + dragOffset[1], + dx: dx, + dy: dy + }); + } + function ended() { + if (!position(parent, dragId)) return; + dragSubject.on(move + dragName, null).on(end + dragName, null); + dragRestore(dragged && d3.event.target === target); + dispatch({ + type: "dragend" + }); + } + }; + } + drag.origin = function(x) { + if (!arguments.length) return origin; + origin = x; + return drag; + }; + return d3.rebind(drag, event, "on"); + }; + function d3_behavior_dragTouchId() { + return d3.event.changedTouches[0].identifier; + } + d3.touches = function(container, touches) { + if (arguments.length < 2) touches = d3_eventSource().touches; + return touches ? d3_array(touches).map(function(touch) { + var point = d3_mousePoint(container, touch); + point.identifier = touch.identifier; + return point; + }) : []; + }; + var ε = 1e-6, ε2 = ε * ε, π = Math.PI, τ = 2 * π, τε = τ - ε, halfπ = π / 2, d3_radians = π / 180, d3_degrees = 180 / π; + function d3_sgn(x) { + return x > 0 ? 1 : x < 0 ? -1 : 0; + } + function d3_cross2d(a, b, c) { + return (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]); + } + function d3_acos(x) { + return x > 1 ? 0 : x < -1 ? π : Math.acos(x); + } + function d3_asin(x) { + return x > 1 ? halfπ : x < -1 ? -halfπ : Math.asin(x); + } + function d3_sinh(x) { + return ((x = Math.exp(x)) - 1 / x) / 2; + } + function d3_cosh(x) { + return ((x = Math.exp(x)) + 1 / x) / 2; + } + function d3_tanh(x) { + return ((x = Math.exp(2 * x)) - 1) / (x + 1); + } + function d3_haversin(x) { + return (x = Math.sin(x / 2)) * x; + } + var ρ = Math.SQRT2, ρ2 = 2, ρ4 = 4; + d3.interpolateZoom = function(p0, p1) { + var ux0 = p0[0], uy0 = p0[1], w0 = p0[2], ux1 = p1[0], uy1 = p1[1], w1 = p1[2]; + var dx = ux1 - ux0, dy = uy1 - uy0, d2 = dx * dx + dy * dy, d1 = Math.sqrt(d2), b0 = (w1 * w1 - w0 * w0 + ρ4 * d2) / (2 * w0 * ρ2 * d1), b1 = (w1 * w1 - w0 * w0 - ρ4 * d2) / (2 * w1 * ρ2 * d1), r0 = Math.log(Math.sqrt(b0 * b0 + 1) - b0), r1 = Math.log(Math.sqrt(b1 * b1 + 1) - b1), dr = r1 - r0, S = (dr || Math.log(w1 / w0)) / ρ; + function interpolate(t) { + var s = t * S; + if (dr) { + var coshr0 = d3_cosh(r0), u = w0 / (ρ2 * d1) * (coshr0 * d3_tanh(ρ * s + r0) - d3_sinh(r0)); + return [ ux0 + u * dx, uy0 + u * dy, w0 * coshr0 / d3_cosh(ρ * s + r0) ]; + } + return [ ux0 + t * dx, uy0 + t * dy, w0 * Math.exp(ρ * s) ]; + } + interpolate.duration = S * 1e3; + return interpolate; + }; + d3.behavior.zoom = function() { + var view = { + x: 0, + y: 0, + k: 1 + }, translate0, center0, center, size = [ 960, 500 ], scaleExtent = d3_behavior_zoomInfinity, duration = 250, zooming = 0, mousedown = "mousedown.zoom", mousemove = "mousemove.zoom", mouseup = "mouseup.zoom", mousewheelTimer, touchstart = "touchstart.zoom", touchtime, event = d3_eventDispatch(zoom, "zoomstart", "zoom", "zoomend"), x0, x1, y0, y1; + if (!d3_behavior_zoomWheel) { + d3_behavior_zoomWheel = "onwheel" in d3_document ? (d3_behavior_zoomDelta = function() { + return -d3.event.deltaY * (d3.event.deltaMode ? 120 : 1); + }, "wheel") : "onmousewheel" in d3_document ? (d3_behavior_zoomDelta = function() { + return d3.event.wheelDelta; + }, "mousewheel") : (d3_behavior_zoomDelta = function() { + return -d3.event.detail; + }, "MozMousePixelScroll"); + } + function zoom(g) { + g.on(mousedown, mousedowned).on(d3_behavior_zoomWheel + ".zoom", mousewheeled).on("dblclick.zoom", dblclicked).on(touchstart, touchstarted); + } + zoom.event = function(g) { + g.each(function() { + var dispatch = event.of(this, arguments), view1 = view; + if (d3_transitionInheritId) { + d3.select(this).transition().each("start.zoom", function() { + view = this.__chart__ || { + x: 0, + y: 0, + k: 1 + }; + zoomstarted(dispatch); + }).tween("zoom:zoom", function() { + var dx = size[0], dy = size[1], cx = center0 ? center0[0] : dx / 2, cy = center0 ? center0[1] : dy / 2, i = d3.interpolateZoom([ (cx - view.x) / view.k, (cy - view.y) / view.k, dx / view.k ], [ (cx - view1.x) / view1.k, (cy - view1.y) / view1.k, dx / view1.k ]); + return function(t) { + var l = i(t), k = dx / l[2]; + this.__chart__ = view = { + x: cx - l[0] * k, + y: cy - l[1] * k, + k: k + }; + zoomed(dispatch); + }; + }).each("interrupt.zoom", function() { + zoomended(dispatch); + }).each("end.zoom", function() { + zoomended(dispatch); + }); + } else { + this.__chart__ = view; + zoomstarted(dispatch); + zoomed(dispatch); + zoomended(dispatch); + } + }); + }; + zoom.translate = function(_) { + if (!arguments.length) return [ view.x, view.y ]; + view = { + x: +_[0], + y: +_[1], + k: view.k + }; + rescale(); + return zoom; + }; + zoom.scale = function(_) { + if (!arguments.length) return view.k; + view = { + x: view.x, + y: view.y, + k: +_ + }; + rescale(); + return zoom; + }; + zoom.scaleExtent = function(_) { + if (!arguments.length) return scaleExtent; + scaleExtent = _ == null ? d3_behavior_zoomInfinity : [ +_[0], +_[1] ]; + return zoom; + }; + zoom.center = function(_) { + if (!arguments.length) return center; + center = _ && [ +_[0], +_[1] ]; + return zoom; + }; + zoom.size = function(_) { + if (!arguments.length) return size; + size = _ && [ +_[0], +_[1] ]; + return zoom; + }; + zoom.duration = function(_) { + if (!arguments.length) return duration; + duration = +_; + return zoom; + }; + zoom.x = function(z) { + if (!arguments.length) return x1; + x1 = z; + x0 = z.copy(); + view = { + x: 0, + y: 0, + k: 1 + }; + return zoom; + }; + zoom.y = function(z) { + if (!arguments.length) return y1; + y1 = z; + y0 = z.copy(); + view = { + x: 0, + y: 0, + k: 1 + }; + return zoom; + }; + function location(p) { + return [ (p[0] - view.x) / view.k, (p[1] - view.y) / view.k ]; + } + function point(l) { + return [ l[0] * view.k + view.x, l[1] * view.k + view.y ]; + } + function scaleTo(s) { + view.k = Math.max(scaleExtent[0], Math.min(scaleExtent[1], s)); + } + function translateTo(p, l) { + l = point(l); + view.x += p[0] - l[0]; + view.y += p[1] - l[1]; + } + function zoomTo(that, p, l, k) { + that.__chart__ = { + x: view.x, + y: view.y, + k: view.k + }; + scaleTo(Math.pow(2, k)); + translateTo(center0 = p, l); + that = d3.select(that); + if (duration > 0) that = that.transition().duration(duration); + that.call(zoom.event); + } + function rescale() { + if (x1) x1.domain(x0.range().map(function(x) { + return (x - view.x) / view.k; + }).map(x0.invert)); + if (y1) y1.domain(y0.range().map(function(y) { + return (y - view.y) / view.k; + }).map(y0.invert)); + } + function zoomstarted(dispatch) { + if (!zooming++) dispatch({ + type: "zoomstart" + }); + } + function zoomed(dispatch) { + rescale(); + dispatch({ + type: "zoom", + scale: view.k, + translate: [ view.x, view.y ] + }); + } + function zoomended(dispatch) { + if (!--zooming) dispatch({ + type: "zoomend" + }), center0 = null; + } + function mousedowned() { + var that = this, target = d3.event.target, dispatch = event.of(that, arguments), dragged = 0, subject = d3.select(d3_window(that)).on(mousemove, moved).on(mouseup, ended), location0 = location(d3.mouse(that)), dragRestore = d3_event_dragSuppress(that); + d3_selection_interrupt.call(that); + zoomstarted(dispatch); + function moved() { + dragged = 1; + translateTo(d3.mouse(that), location0); + zoomed(dispatch); + } + function ended() { + subject.on(mousemove, null).on(mouseup, null); + dragRestore(dragged && d3.event.target === target); + zoomended(dispatch); + } + } + function touchstarted() { + var that = this, dispatch = event.of(that, arguments), locations0 = {}, distance0 = 0, scale0, zoomName = ".zoom-" + d3.event.changedTouches[0].identifier, touchmove = "touchmove" + zoomName, touchend = "touchend" + zoomName, targets = [], subject = d3.select(that), dragRestore = d3_event_dragSuppress(that); + started(); + zoomstarted(dispatch); + subject.on(mousedown, null).on(touchstart, started); + function relocate() { + var touches = d3.touches(that); + scale0 = view.k; + touches.forEach(function(t) { + if (t.identifier in locations0) locations0[t.identifier] = location(t); + }); + return touches; + } + function started() { + var target = d3.event.target; + d3.select(target).on(touchmove, moved).on(touchend, ended); + targets.push(target); + var changed = d3.event.changedTouches; + for (var i = 0, n = changed.length; i < n; ++i) { + locations0[changed[i].identifier] = null; + } + var touches = relocate(), now = Date.now(); + if (touches.length === 1) { + if (now - touchtime < 500) { + var p = touches[0]; + zoomTo(that, p, locations0[p.identifier], Math.floor(Math.log(view.k) / Math.LN2) + 1); + d3_eventPreventDefault(); + } + touchtime = now; + } else if (touches.length > 1) { + var p = touches[0], q = touches[1], dx = p[0] - q[0], dy = p[1] - q[1]; + distance0 = dx * dx + dy * dy; + } + } + function moved() { + var touches = d3.touches(that), p0, l0, p1, l1; + d3_selection_interrupt.call(that); + for (var i = 0, n = touches.length; i < n; ++i, l1 = null) { + p1 = touches[i]; + if (l1 = locations0[p1.identifier]) { + if (l0) break; + p0 = p1, l0 = l1; + } + } + if (l1) { + var distance1 = (distance1 = p1[0] - p0[0]) * distance1 + (distance1 = p1[1] - p0[1]) * distance1, scale1 = distance0 && Math.sqrt(distance1 / distance0); + p0 = [ (p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2 ]; + l0 = [ (l0[0] + l1[0]) / 2, (l0[1] + l1[1]) / 2 ]; + scaleTo(scale1 * scale0); + } + touchtime = null; + translateTo(p0, l0); + zoomed(dispatch); + } + function ended() { + if (d3.event.touches.length) { + var changed = d3.event.changedTouches; + for (var i = 0, n = changed.length; i < n; ++i) { + delete locations0[changed[i].identifier]; + } + for (var identifier in locations0) { + return void relocate(); + } + } + d3.selectAll(targets).on(zoomName, null); + subject.on(mousedown, mousedowned).on(touchstart, touchstarted); + dragRestore(); + zoomended(dispatch); + } + } + function mousewheeled() { + var dispatch = event.of(this, arguments); + if (mousewheelTimer) clearTimeout(mousewheelTimer); else d3_selection_interrupt.call(this), + translate0 = location(center0 = center || d3.mouse(this)), zoomstarted(dispatch); + mousewheelTimer = setTimeout(function() { + mousewheelTimer = null; + zoomended(dispatch); + }, 50); + d3_eventPreventDefault(); + scaleTo(Math.pow(2, d3_behavior_zoomDelta() * .002) * view.k); + translateTo(center0, translate0); + zoomed(dispatch); + } + function dblclicked() { + var p = d3.mouse(this), k = Math.log(view.k) / Math.LN2; + zoomTo(this, p, location(p), d3.event.shiftKey ? Math.ceil(k) - 1 : Math.floor(k) + 1); + } + return d3.rebind(zoom, event, "on"); + }; + var d3_behavior_zoomInfinity = [ 0, Infinity ], d3_behavior_zoomDelta, d3_behavior_zoomWheel; + d3.color = d3_color; + function d3_color() {} + d3_color.prototype.toString = function() { + return this.rgb() + ""; + }; + d3.hsl = d3_hsl; + function d3_hsl(h, s, l) { + return this instanceof d3_hsl ? void (this.h = +h, this.s = +s, this.l = +l) : arguments.length < 2 ? h instanceof d3_hsl ? new d3_hsl(h.h, h.s, h.l) : d3_rgb_parse("" + h, d3_rgb_hsl, d3_hsl) : new d3_hsl(h, s, l); + } + var d3_hslPrototype = d3_hsl.prototype = new d3_color(); + d3_hslPrototype.brighter = function(k) { + k = Math.pow(.7, arguments.length ? k : 1); + return new d3_hsl(this.h, this.s, this.l / k); + }; + d3_hslPrototype.darker = function(k) { + k = Math.pow(.7, arguments.length ? k : 1); + return new d3_hsl(this.h, this.s, k * this.l); + }; + d3_hslPrototype.rgb = function() { + return d3_hsl_rgb(this.h, this.s, this.l); + }; + function d3_hsl_rgb(h, s, l) { + var m1, m2; + h = isNaN(h) ? 0 : (h %= 360) < 0 ? h + 360 : h; + s = isNaN(s) ? 0 : s < 0 ? 0 : s > 1 ? 1 : s; + l = l < 0 ? 0 : l > 1 ? 1 : l; + m2 = l <= .5 ? l * (1 + s) : l + s - l * s; + m1 = 2 * l - m2; + function v(h) { + if (h > 360) h -= 360; else if (h < 0) h += 360; + if (h < 60) return m1 + (m2 - m1) * h / 60; + if (h < 180) return m2; + if (h < 240) return m1 + (m2 - m1) * (240 - h) / 60; + return m1; + } + function vv(h) { + return Math.round(v(h) * 255); + } + return new d3_rgb(vv(h + 120), vv(h), vv(h - 120)); + } + d3.hcl = d3_hcl; + function d3_hcl(h, c, l) { + return this instanceof d3_hcl ? void (this.h = +h, this.c = +c, this.l = +l) : arguments.length < 2 ? h instanceof d3_hcl ? new d3_hcl(h.h, h.c, h.l) : h instanceof d3_lab ? d3_lab_hcl(h.l, h.a, h.b) : d3_lab_hcl((h = d3_rgb_lab((h = d3.rgb(h)).r, h.g, h.b)).l, h.a, h.b) : new d3_hcl(h, c, l); + } + var d3_hclPrototype = d3_hcl.prototype = new d3_color(); + d3_hclPrototype.brighter = function(k) { + return new d3_hcl(this.h, this.c, Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1))); + }; + d3_hclPrototype.darker = function(k) { + return new d3_hcl(this.h, this.c, Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1))); + }; + d3_hclPrototype.rgb = function() { + return d3_hcl_lab(this.h, this.c, this.l).rgb(); + }; + function d3_hcl_lab(h, c, l) { + if (isNaN(h)) h = 0; + if (isNaN(c)) c = 0; + return new d3_lab(l, Math.cos(h *= d3_radians) * c, Math.sin(h) * c); + } + d3.lab = d3_lab; + function d3_lab(l, a, b) { + return this instanceof d3_lab ? void (this.l = +l, this.a = +a, this.b = +b) : arguments.length < 2 ? l instanceof d3_lab ? new d3_lab(l.l, l.a, l.b) : l instanceof d3_hcl ? d3_hcl_lab(l.h, l.c, l.l) : d3_rgb_lab((l = d3_rgb(l)).r, l.g, l.b) : new d3_lab(l, a, b); + } + var d3_lab_K = 18; + var d3_lab_X = .95047, d3_lab_Y = 1, d3_lab_Z = 1.08883; + var d3_labPrototype = d3_lab.prototype = new d3_color(); + d3_labPrototype.brighter = function(k) { + return new d3_lab(Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1)), this.a, this.b); + }; + d3_labPrototype.darker = function(k) { + return new d3_lab(Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1)), this.a, this.b); + }; + d3_labPrototype.rgb = function() { + return d3_lab_rgb(this.l, this.a, this.b); + }; + function d3_lab_rgb(l, a, b) { + var y = (l + 16) / 116, x = y + a / 500, z = y - b / 200; + x = d3_lab_xyz(x) * d3_lab_X; + y = d3_lab_xyz(y) * d3_lab_Y; + z = d3_lab_xyz(z) * d3_lab_Z; + return new d3_rgb(d3_xyz_rgb(3.2404542 * x - 1.5371385 * y - .4985314 * z), d3_xyz_rgb(-.969266 * x + 1.8760108 * y + .041556 * z), d3_xyz_rgb(.0556434 * x - .2040259 * y + 1.0572252 * z)); + } + function d3_lab_hcl(l, a, b) { + return l > 0 ? new d3_hcl(Math.atan2(b, a) * d3_degrees, Math.sqrt(a * a + b * b), l) : new d3_hcl(NaN, NaN, l); + } + function d3_lab_xyz(x) { + return x > .206893034 ? x * x * x : (x - 4 / 29) / 7.787037; + } + function d3_xyz_lab(x) { + return x > .008856 ? Math.pow(x, 1 / 3) : 7.787037 * x + 4 / 29; + } + function d3_xyz_rgb(r) { + return Math.round(255 * (r <= .00304 ? 12.92 * r : 1.055 * Math.pow(r, 1 / 2.4) - .055)); + } + d3.rgb = d3_rgb; + function d3_rgb(r, g, b) { + return this instanceof d3_rgb ? void (this.r = ~~r, this.g = ~~g, this.b = ~~b) : arguments.length < 2 ? r instanceof d3_rgb ? new d3_rgb(r.r, r.g, r.b) : d3_rgb_parse("" + r, d3_rgb, d3_hsl_rgb) : new d3_rgb(r, g, b); + } + function d3_rgbNumber(value) { + return new d3_rgb(value >> 16, value >> 8 & 255, value & 255); + } + function d3_rgbString(value) { + return d3_rgbNumber(value) + ""; + } + var d3_rgbPrototype = d3_rgb.prototype = new d3_color(); + d3_rgbPrototype.brighter = function(k) { + k = Math.pow(.7, arguments.length ? k : 1); + var r = this.r, g = this.g, b = this.b, i = 30; + if (!r && !g && !b) return new d3_rgb(i, i, i); + if (r && r < i) r = i; + if (g && g < i) g = i; + if (b && b < i) b = i; + return new d3_rgb(Math.min(255, r / k), Math.min(255, g / k), Math.min(255, b / k)); + }; + d3_rgbPrototype.darker = function(k) { + k = Math.pow(.7, arguments.length ? k : 1); + return new d3_rgb(k * this.r, k * this.g, k * this.b); + }; + d3_rgbPrototype.hsl = function() { + return d3_rgb_hsl(this.r, this.g, this.b); + }; + d3_rgbPrototype.toString = function() { + return "#" + d3_rgb_hex(this.r) + d3_rgb_hex(this.g) + d3_rgb_hex(this.b); + }; + function d3_rgb_hex(v) { + return v < 16 ? "0" + Math.max(0, v).toString(16) : Math.min(255, v).toString(16); + } + function d3_rgb_parse(format, rgb, hsl) { + var r = 0, g = 0, b = 0, m1, m2, color; + m1 = /([a-z]+)\((.*)\)/.exec(format = format.toLowerCase()); + if (m1) { + m2 = m1[2].split(","); + switch (m1[1]) { + case "hsl": + { + return hsl(parseFloat(m2[0]), parseFloat(m2[1]) / 100, parseFloat(m2[2]) / 100); + } + + case "rgb": + { + return rgb(d3_rgb_parseNumber(m2[0]), d3_rgb_parseNumber(m2[1]), d3_rgb_parseNumber(m2[2])); + } + } + } + if (color = d3_rgb_names.get(format)) { + return rgb(color.r, color.g, color.b); + } + if (format != null && format.charAt(0) === "#" && !isNaN(color = parseInt(format.slice(1), 16))) { + if (format.length === 4) { + r = (color & 3840) >> 4; + r = r >> 4 | r; + g = color & 240; + g = g >> 4 | g; + b = color & 15; + b = b << 4 | b; + } else if (format.length === 7) { + r = (color & 16711680) >> 16; + g = (color & 65280) >> 8; + b = color & 255; + } + } + return rgb(r, g, b); + } + function d3_rgb_hsl(r, g, b) { + var min = Math.min(r /= 255, g /= 255, b /= 255), max = Math.max(r, g, b), d = max - min, h, s, l = (max + min) / 2; + if (d) { + s = l < .5 ? d / (max + min) : d / (2 - max - min); + if (r == max) h = (g - b) / d + (g < b ? 6 : 0); else if (g == max) h = (b - r) / d + 2; else h = (r - g) / d + 4; + h *= 60; + } else { + h = NaN; + s = l > 0 && l < 1 ? 0 : h; + } + return new d3_hsl(h, s, l); + } + function d3_rgb_lab(r, g, b) { + r = d3_rgb_xyz(r); + g = d3_rgb_xyz(g); + b = d3_rgb_xyz(b); + var x = d3_xyz_lab((.4124564 * r + .3575761 * g + .1804375 * b) / d3_lab_X), y = d3_xyz_lab((.2126729 * r + .7151522 * g + .072175 * b) / d3_lab_Y), z = d3_xyz_lab((.0193339 * r + .119192 * g + .9503041 * b) / d3_lab_Z); + return d3_lab(116 * y - 16, 500 * (x - y), 200 * (y - z)); + } + function d3_rgb_xyz(r) { + return (r /= 255) <= .04045 ? r / 12.92 : Math.pow((r + .055) / 1.055, 2.4); + } + function d3_rgb_parseNumber(c) { + var f = parseFloat(c); + return c.charAt(c.length - 1) === "%" ? Math.round(f * 2.55) : f; + } + var d3_rgb_names = d3.map({ + aliceblue: 15792383, + antiquewhite: 16444375, + aqua: 65535, + aquamarine: 8388564, + azure: 15794175, + beige: 16119260, + bisque: 16770244, + black: 0, + blanchedalmond: 16772045, + blue: 255, + blueviolet: 9055202, + brown: 10824234, + burlywood: 14596231, + cadetblue: 6266528, + chartreuse: 8388352, + chocolate: 13789470, + coral: 16744272, + cornflowerblue: 6591981, + cornsilk: 16775388, + crimson: 14423100, + cyan: 65535, + darkblue: 139, + darkcyan: 35723, + darkgoldenrod: 12092939, + darkgray: 11119017, + darkgreen: 25600, + darkgrey: 11119017, + darkkhaki: 12433259, + darkmagenta: 9109643, + darkolivegreen: 5597999, + darkorange: 16747520, + darkorchid: 10040012, + darkred: 9109504, + darksalmon: 15308410, + darkseagreen: 9419919, + darkslateblue: 4734347, + darkslategray: 3100495, + darkslategrey: 3100495, + darkturquoise: 52945, + darkviolet: 9699539, + deeppink: 16716947, + deepskyblue: 49151, + dimgray: 6908265, + dimgrey: 6908265, + dodgerblue: 2003199, + firebrick: 11674146, + floralwhite: 16775920, + forestgreen: 2263842, + fuchsia: 16711935, + gainsboro: 14474460, + ghostwhite: 16316671, + gold: 16766720, + goldenrod: 14329120, + gray: 8421504, + green: 32768, + greenyellow: 11403055, + grey: 8421504, + honeydew: 15794160, + hotpink: 16738740, + indianred: 13458524, + indigo: 4915330, + ivory: 16777200, + khaki: 15787660, + lavender: 15132410, + lavenderblush: 16773365, + lawngreen: 8190976, + lemonchiffon: 16775885, + lightblue: 11393254, + lightcoral: 15761536, + lightcyan: 14745599, + lightgoldenrodyellow: 16448210, + lightgray: 13882323, + lightgreen: 9498256, + lightgrey: 13882323, + lightpink: 16758465, + lightsalmon: 16752762, + lightseagreen: 2142890, + lightskyblue: 8900346, + lightslategray: 7833753, + lightslategrey: 7833753, + lightsteelblue: 11584734, + lightyellow: 16777184, + lime: 65280, + limegreen: 3329330, + linen: 16445670, + magenta: 16711935, + maroon: 8388608, + mediumaquamarine: 6737322, + mediumblue: 205, + mediumorchid: 12211667, + mediumpurple: 9662683, + mediumseagreen: 3978097, + mediumslateblue: 8087790, + mediumspringgreen: 64154, + mediumturquoise: 4772300, + mediumvioletred: 13047173, + midnightblue: 1644912, + mintcream: 16121850, + mistyrose: 16770273, + moccasin: 16770229, + navajowhite: 16768685, + navy: 128, + oldlace: 16643558, + olive: 8421376, + olivedrab: 7048739, + orange: 16753920, + orangered: 16729344, + orchid: 14315734, + palegoldenrod: 15657130, + palegreen: 10025880, + paleturquoise: 11529966, + palevioletred: 14381203, + papayawhip: 16773077, + peachpuff: 16767673, + peru: 13468991, + pink: 16761035, + plum: 14524637, + powderblue: 11591910, + purple: 8388736, + rebeccapurple: 6697881, + red: 16711680, + rosybrown: 12357519, + royalblue: 4286945, + saddlebrown: 9127187, + salmon: 16416882, + sandybrown: 16032864, + seagreen: 3050327, + seashell: 16774638, + sienna: 10506797, + silver: 12632256, + skyblue: 8900331, + slateblue: 6970061, + slategray: 7372944, + slategrey: 7372944, + snow: 16775930, + springgreen: 65407, + steelblue: 4620980, + tan: 13808780, + teal: 32896, + thistle: 14204888, + tomato: 16737095, + turquoise: 4251856, + violet: 15631086, + wheat: 16113331, + white: 16777215, + whitesmoke: 16119285, + yellow: 16776960, + yellowgreen: 10145074 + }); + d3_rgb_names.forEach(function(key, value) { + d3_rgb_names.set(key, d3_rgbNumber(value)); + }); + function d3_functor(v) { + return typeof v === "function" ? v : function() { + return v; + }; + } + d3.functor = d3_functor; + d3.xhr = d3_xhrType(d3_identity); + function d3_xhrType(response) { + return function(url, mimeType, callback) { + if (arguments.length === 2 && typeof mimeType === "function") callback = mimeType, + mimeType = null; + return d3_xhr(url, mimeType, response, callback); + }; + } + function d3_xhr(url, mimeType, response, callback) { + var xhr = {}, dispatch = d3.dispatch("beforesend", "progress", "load", "error"), headers = {}, request = new XMLHttpRequest(), responseType = null; + if (this.XDomainRequest && !("withCredentials" in request) && /^(http(s)?:)?\/\//.test(url)) request = new XDomainRequest(); + "onload" in request ? request.onload = request.onerror = respond : request.onreadystatechange = function() { + request.readyState > 3 && respond(); + }; + function respond() { + var status = request.status, result; + if (!status && d3_xhrHasResponse(request) || status >= 200 && status < 300 || status === 304) { + try { + result = response.call(xhr, request); + } catch (e) { + dispatch.error.call(xhr, e); + return; + } + dispatch.load.call(xhr, result); + } else { + dispatch.error.call(xhr, request); + } + } + request.onprogress = function(event) { + var o = d3.event; + d3.event = event; + try { + dispatch.progress.call(xhr, request); + } finally { + d3.event = o; + } + }; + xhr.header = function(name, value) { + name = (name + "").toLowerCase(); + if (arguments.length < 2) return headers[name]; + if (value == null) delete headers[name]; else headers[name] = value + ""; + return xhr; + }; + xhr.mimeType = function(value) { + if (!arguments.length) return mimeType; + mimeType = value == null ? null : value + ""; + return xhr; + }; + xhr.responseType = function(value) { + if (!arguments.length) return responseType; + responseType = value; + return xhr; + }; + xhr.response = function(value) { + response = value; + return xhr; + }; + [ "get", "post" ].forEach(function(method) { + xhr[method] = function() { + return xhr.send.apply(xhr, [ method ].concat(d3_array(arguments))); + }; + }); + xhr.send = function(method, data, callback) { + if (arguments.length === 2 && typeof data === "function") callback = data, data = null; + request.open(method, url, true); + if (mimeType != null && !("accept" in headers)) headers["accept"] = mimeType + ",*/*"; + if (request.setRequestHeader) for (var name in headers) request.setRequestHeader(name, headers[name]); + if (mimeType != null && request.overrideMimeType) request.overrideMimeType(mimeType); + if (responseType != null) request.responseType = responseType; + if (callback != null) xhr.on("error", callback).on("load", function(request) { + callback(null, request); + }); + dispatch.beforesend.call(xhr, request); + request.send(data == null ? null : data); + return xhr; + }; + xhr.abort = function() { + request.abort(); + return xhr; + }; + d3.rebind(xhr, dispatch, "on"); + return callback == null ? xhr : xhr.get(d3_xhr_fixCallback(callback)); + } + function d3_xhr_fixCallback(callback) { + return callback.length === 1 ? function(error, request) { + callback(error == null ? request : null); + } : callback; + } + function d3_xhrHasResponse(request) { + var type = request.responseType; + return type && type !== "text" ? request.response : request.responseText; + } + d3.dsv = function(delimiter, mimeType) { + var reFormat = new RegExp('["' + delimiter + "\n]"), delimiterCode = delimiter.charCodeAt(0); + function dsv(url, row, callback) { + if (arguments.length < 3) callback = row, row = null; + var xhr = d3_xhr(url, mimeType, row == null ? response : typedResponse(row), callback); + xhr.row = function(_) { + return arguments.length ? xhr.response((row = _) == null ? response : typedResponse(_)) : row; + }; + return xhr; + } + function response(request) { + return dsv.parse(request.responseText); + } + function typedResponse(f) { + return function(request) { + return dsv.parse(request.responseText, f); + }; + } + dsv.parse = function(text, f) { + var o; + return dsv.parseRows(text, function(row, i) { + if (o) return o(row, i - 1); + var a = new Function("d", "return {" + row.map(function(name, i) { + return JSON.stringify(name) + ": d[" + i + "]"; + }).join(",") + "}"); + o = f ? function(row, i) { + return f(a(row), i); + } : a; + }); + }; + dsv.parseRows = function(text, f) { + var EOL = {}, EOF = {}, rows = [], N = text.length, I = 0, n = 0, t, eol; + function token() { + if (I >= N) return EOF; + if (eol) return eol = false, EOL; + var j = I; + if (text.charCodeAt(j) === 34) { + var i = j; + while (i++ < N) { + if (text.charCodeAt(i) === 34) { + if (text.charCodeAt(i + 1) !== 34) break; + ++i; + } + } + I = i + 2; + var c = text.charCodeAt(i + 1); + if (c === 13) { + eol = true; + if (text.charCodeAt(i + 2) === 10) ++I; + } else if (c === 10) { + eol = true; + } + return text.slice(j + 1, i).replace(/""/g, '"'); + } + while (I < N) { + var c = text.charCodeAt(I++), k = 1; + if (c === 10) eol = true; else if (c === 13) { + eol = true; + if (text.charCodeAt(I) === 10) ++I, ++k; + } else if (c !== delimiterCode) continue; + return text.slice(j, I - k); + } + return text.slice(j); + } + while ((t = token()) !== EOF) { + var a = []; + while (t !== EOL && t !== EOF) { + a.push(t); + t = token(); + } + if (f && (a = f(a, n++)) == null) continue; + rows.push(a); + } + return rows; + }; + dsv.format = function(rows) { + if (Array.isArray(rows[0])) return dsv.formatRows(rows); + var fieldSet = new d3_Set(), fields = []; + rows.forEach(function(row) { + for (var field in row) { + if (!fieldSet.has(field)) { + fields.push(fieldSet.add(field)); + } + } + }); + return [ fields.map(formatValue).join(delimiter) ].concat(rows.map(function(row) { + return fields.map(function(field) { + return formatValue(row[field]); + }).join(delimiter); + })).join("\n"); + }; + dsv.formatRows = function(rows) { + return rows.map(formatRow).join("\n"); + }; + function formatRow(row) { + return row.map(formatValue).join(delimiter); + } + function formatValue(text) { + return reFormat.test(text) ? '"' + text.replace(/\"/g, '""') + '"' : text; + } + return dsv; + }; + d3.csv = d3.dsv(",", "text/csv"); + d3.tsv = d3.dsv(" ", "text/tab-separated-values"); + var d3_timer_queueHead, d3_timer_queueTail, d3_timer_interval, d3_timer_timeout, d3_timer_active, d3_timer_frame = this[d3_vendorSymbol(this, "requestAnimationFrame")] || function(callback) { + setTimeout(callback, 17); + }; + d3.timer = function(callback, delay, then) { + var n = arguments.length; + if (n < 2) delay = 0; + if (n < 3) then = Date.now(); + var time = then + delay, timer = { + c: callback, + t: time, + f: false, + n: null + }; + if (d3_timer_queueTail) d3_timer_queueTail.n = timer; else d3_timer_queueHead = timer; + d3_timer_queueTail = timer; + if (!d3_timer_interval) { + d3_timer_timeout = clearTimeout(d3_timer_timeout); + d3_timer_interval = 1; + d3_timer_frame(d3_timer_step); + } + }; + function d3_timer_step() { + var now = d3_timer_mark(), delay = d3_timer_sweep() - now; + if (delay > 24) { + if (isFinite(delay)) { + clearTimeout(d3_timer_timeout); + d3_timer_timeout = setTimeout(d3_timer_step, delay); + } + d3_timer_interval = 0; + } else { + d3_timer_interval = 1; + d3_timer_frame(d3_timer_step); + } + } + d3.timer.flush = function() { + d3_timer_mark(); + d3_timer_sweep(); + }; + function d3_timer_mark() { + var now = Date.now(); + d3_timer_active = d3_timer_queueHead; + while (d3_timer_active) { + if (now >= d3_timer_active.t) d3_timer_active.f = d3_timer_active.c(now - d3_timer_active.t); + d3_timer_active = d3_timer_active.n; + } + return now; + } + function d3_timer_sweep() { + var t0, t1 = d3_timer_queueHead, time = Infinity; + while (t1) { + if (t1.f) { + t1 = t0 ? t0.n = t1.n : d3_timer_queueHead = t1.n; + } else { + if (t1.t < time) time = t1.t; + t1 = (t0 = t1).n; + } + } + d3_timer_queueTail = t0; + return time; + } + function d3_format_precision(x, p) { + return p - (x ? Math.ceil(Math.log(x) / Math.LN10) : 1); + } + d3.round = function(x, n) { + return n ? Math.round(x * (n = Math.pow(10, n))) / n : Math.round(x); + }; + var d3_formatPrefixes = [ "y", "z", "a", "f", "p", "n", "µ", "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y" ].map(d3_formatPrefix); + d3.formatPrefix = function(value, precision) { + var i = 0; + if (value) { + if (value < 0) value *= -1; + if (precision) value = d3.round(value, d3_format_precision(value, precision)); + i = 1 + Math.floor(1e-12 + Math.log(value) / Math.LN10); + i = Math.max(-24, Math.min(24, Math.floor((i - 1) / 3) * 3)); + } + return d3_formatPrefixes[8 + i / 3]; + }; + function d3_formatPrefix(d, i) { + var k = Math.pow(10, abs(8 - i) * 3); + return { + scale: i > 8 ? function(d) { + return d / k; + } : function(d) { + return d * k; + }, + symbol: d + }; + } + function d3_locale_numberFormat(locale) { + var locale_decimal = locale.decimal, locale_thousands = locale.thousands, locale_grouping = locale.grouping, locale_currency = locale.currency, formatGroup = locale_grouping && locale_thousands ? function(value, width) { + var i = value.length, t = [], j = 0, g = locale_grouping[0], length = 0; + while (i > 0 && g > 0) { + if (length + g + 1 > width) g = Math.max(1, width - length); + t.push(value.substring(i -= g, i + g)); + if ((length += g + 1) > width) break; + g = locale_grouping[j = (j + 1) % locale_grouping.length]; + } + return t.reverse().join(locale_thousands); + } : d3_identity; + return function(specifier) { + var match = d3_format_re.exec(specifier), fill = match[1] || " ", align = match[2] || ">", sign = match[3] || "-", symbol = match[4] || "", zfill = match[5], width = +match[6], comma = match[7], precision = match[8], type = match[9], scale = 1, prefix = "", suffix = "", integer = false, exponent = true; + if (precision) precision = +precision.substring(1); + if (zfill || fill === "0" && align === "=") { + zfill = fill = "0"; + align = "="; + } + switch (type) { + case "n": + comma = true; + type = "g"; + break; + + case "%": + scale = 100; + suffix = "%"; + type = "f"; + break; + + case "p": + scale = 100; + suffix = "%"; + type = "r"; + break; + + case "b": + case "o": + case "x": + case "X": + if (symbol === "#") prefix = "0" + type.toLowerCase(); + + case "c": + exponent = false; + + case "d": + integer = true; + precision = 0; + break; + + case "s": + scale = -1; + type = "r"; + break; + } + if (symbol === "$") prefix = locale_currency[0], suffix = locale_currency[1]; + if (type == "r" && !precision) type = "g"; + if (precision != null) { + if (type == "g") precision = Math.max(1, Math.min(21, precision)); else if (type == "e" || type == "f") precision = Math.max(0, Math.min(20, precision)); + } + type = d3_format_types.get(type) || d3_format_typeDefault; + var zcomma = zfill && comma; + return function(value) { + var fullSuffix = suffix; + if (integer && value % 1) return ""; + var negative = value < 0 || value === 0 && 1 / value < 0 ? (value = -value, "-") : sign === "-" ? "" : sign; + if (scale < 0) { + var unit = d3.formatPrefix(value, precision); + value = unit.scale(value); + fullSuffix = unit.symbol + suffix; + } else { + value *= scale; + } + value = type(value, precision); + var i = value.lastIndexOf("."), before, after; + if (i < 0) { + var j = exponent ? value.lastIndexOf("e") : -1; + if (j < 0) before = value, after = ""; else before = value.substring(0, j), after = value.substring(j); + } else { + before = value.substring(0, i); + after = locale_decimal + value.substring(i + 1); + } + if (!zfill && comma) before = formatGroup(before, Infinity); + var length = prefix.length + before.length + after.length + (zcomma ? 0 : negative.length), padding = length < width ? new Array(length = width - length + 1).join(fill) : ""; + if (zcomma) before = formatGroup(padding + before, padding.length ? width - after.length : Infinity); + negative += prefix; + value = before + after; + return (align === "<" ? negative + value + padding : align === ">" ? padding + negative + value : align === "^" ? padding.substring(0, length >>= 1) + negative + value + padding.substring(length) : negative + (zcomma ? value : padding + value)) + fullSuffix; + }; + }; + } + var d3_format_re = /(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i; + var d3_format_types = d3.map({ + b: function(x) { + return x.toString(2); + }, + c: function(x) { + return String.fromCharCode(x); + }, + o: function(x) { + return x.toString(8); + }, + x: function(x) { + return x.toString(16); + }, + X: function(x) { + return x.toString(16).toUpperCase(); + }, + g: function(x, p) { + return x.toPrecision(p); + }, + e: function(x, p) { + return x.toExponential(p); + }, + f: function(x, p) { + return x.toFixed(p); + }, + r: function(x, p) { + return (x = d3.round(x, d3_format_precision(x, p))).toFixed(Math.max(0, Math.min(20, d3_format_precision(x * (1 + 1e-15), p)))); + } + }); + function d3_format_typeDefault(x) { + return x + ""; + } + var d3_time = d3.time = {}, d3_date = Date; + function d3_date_utc() { + this._ = new Date(arguments.length > 1 ? Date.UTC.apply(this, arguments) : arguments[0]); + } + d3_date_utc.prototype = { + getDate: function() { + return this._.getUTCDate(); + }, + getDay: function() { + return this._.getUTCDay(); + }, + getFullYear: function() { + return this._.getUTCFullYear(); + }, + getHours: function() { + return this._.getUTCHours(); + }, + getMilliseconds: function() { + return this._.getUTCMilliseconds(); + }, + getMinutes: function() { + return this._.getUTCMinutes(); + }, + getMonth: function() { + return this._.getUTCMonth(); + }, + getSeconds: function() { + return this._.getUTCSeconds(); + }, + getTime: function() { + return this._.getTime(); + }, + getTimezoneOffset: function() { + return 0; + }, + valueOf: function() { + return this._.valueOf(); + }, + setDate: function() { + d3_time_prototype.setUTCDate.apply(this._, arguments); + }, + setDay: function() { + d3_time_prototype.setUTCDay.apply(this._, arguments); + }, + setFullYear: function() { + d3_time_prototype.setUTCFullYear.apply(this._, arguments); + }, + setHours: function() { + d3_time_prototype.setUTCHours.apply(this._, arguments); + }, + setMilliseconds: function() { + d3_time_prototype.setUTCMilliseconds.apply(this._, arguments); + }, + setMinutes: function() { + d3_time_prototype.setUTCMinutes.apply(this._, arguments); + }, + setMonth: function() { + d3_time_prototype.setUTCMonth.apply(this._, arguments); + }, + setSeconds: function() { + d3_time_prototype.setUTCSeconds.apply(this._, arguments); + }, + setTime: function() { + d3_time_prototype.setTime.apply(this._, arguments); + } + }; + var d3_time_prototype = Date.prototype; + function d3_time_interval(local, step, number) { + function round(date) { + var d0 = local(date), d1 = offset(d0, 1); + return date - d0 < d1 - date ? d0 : d1; + } + function ceil(date) { + step(date = local(new d3_date(date - 1)), 1); + return date; + } + function offset(date, k) { + step(date = new d3_date(+date), k); + return date; + } + function range(t0, t1, dt) { + var time = ceil(t0), times = []; + if (dt > 1) { + while (time < t1) { + if (!(number(time) % dt)) times.push(new Date(+time)); + step(time, 1); + } + } else { + while (time < t1) times.push(new Date(+time)), step(time, 1); + } + return times; + } + function range_utc(t0, t1, dt) { + try { + d3_date = d3_date_utc; + var utc = new d3_date_utc(); + utc._ = t0; + return range(utc, t1, dt); + } finally { + d3_date = Date; + } + } + local.floor = local; + local.round = round; + local.ceil = ceil; + local.offset = offset; + local.range = range; + var utc = local.utc = d3_time_interval_utc(local); + utc.floor = utc; + utc.round = d3_time_interval_utc(round); + utc.ceil = d3_time_interval_utc(ceil); + utc.offset = d3_time_interval_utc(offset); + utc.range = range_utc; + return local; + } + function d3_time_interval_utc(method) { + return function(date, k) { + try { + d3_date = d3_date_utc; + var utc = new d3_date_utc(); + utc._ = date; + return method(utc, k)._; + } finally { + d3_date = Date; + } + }; + } + d3_time.year = d3_time_interval(function(date) { + date = d3_time.day(date); + date.setMonth(0, 1); + return date; + }, function(date, offset) { + date.setFullYear(date.getFullYear() + offset); + }, function(date) { + return date.getFullYear(); + }); + d3_time.years = d3_time.year.range; + d3_time.years.utc = d3_time.year.utc.range; + d3_time.day = d3_time_interval(function(date) { + var day = new d3_date(2e3, 0); + day.setFullYear(date.getFullYear(), date.getMonth(), date.getDate()); + return day; + }, function(date, offset) { + date.setDate(date.getDate() + offset); + }, function(date) { + return date.getDate() - 1; + }); + d3_time.days = d3_time.day.range; + d3_time.days.utc = d3_time.day.utc.range; + d3_time.dayOfYear = function(date) { + var year = d3_time.year(date); + return Math.floor((date - year - (date.getTimezoneOffset() - year.getTimezoneOffset()) * 6e4) / 864e5); + }; + [ "sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday" ].forEach(function(day, i) { + i = 7 - i; + var interval = d3_time[day] = d3_time_interval(function(date) { + (date = d3_time.day(date)).setDate(date.getDate() - (date.getDay() + i) % 7); + return date; + }, function(date, offset) { + date.setDate(date.getDate() + Math.floor(offset) * 7); + }, function(date) { + var day = d3_time.year(date).getDay(); + return Math.floor((d3_time.dayOfYear(date) + (day + i) % 7) / 7) - (day !== i); + }); + d3_time[day + "s"] = interval.range; + d3_time[day + "s"].utc = interval.utc.range; + d3_time[day + "OfYear"] = function(date) { + var day = d3_time.year(date).getDay(); + return Math.floor((d3_time.dayOfYear(date) + (day + i) % 7) / 7); + }; + }); + d3_time.week = d3_time.sunday; + d3_time.weeks = d3_time.sunday.range; + d3_time.weeks.utc = d3_time.sunday.utc.range; + d3_time.weekOfYear = d3_time.sundayOfYear; + function d3_locale_timeFormat(locale) { + var locale_dateTime = locale.dateTime, locale_date = locale.date, locale_time = locale.time, locale_periods = locale.periods, locale_days = locale.days, locale_shortDays = locale.shortDays, locale_months = locale.months, locale_shortMonths = locale.shortMonths; + function d3_time_format(template) { + var n = template.length; + function format(date) { + var string = [], i = -1, j = 0, c, p, f; + while (++i < n) { + if (template.charCodeAt(i) === 37) { + string.push(template.slice(j, i)); + if ((p = d3_time_formatPads[c = template.charAt(++i)]) != null) c = template.charAt(++i); + if (f = d3_time_formats[c]) c = f(date, p == null ? c === "e" ? " " : "0" : p); + string.push(c); + j = i + 1; + } + } + string.push(template.slice(j, i)); + return string.join(""); + } + format.parse = function(string) { + var d = { + y: 1900, + m: 0, + d: 1, + H: 0, + M: 0, + S: 0, + L: 0, + Z: null + }, i = d3_time_parse(d, template, string, 0); + if (i != string.length) return null; + if ("p" in d) d.H = d.H % 12 + d.p * 12; + var localZ = d.Z != null && d3_date !== d3_date_utc, date = new (localZ ? d3_date_utc : d3_date)(); + if ("j" in d) date.setFullYear(d.y, 0, d.j); else if ("w" in d && ("W" in d || "U" in d)) { + date.setFullYear(d.y, 0, 1); + date.setFullYear(d.y, 0, "W" in d ? (d.w + 6) % 7 + d.W * 7 - (date.getDay() + 5) % 7 : d.w + d.U * 7 - (date.getDay() + 6) % 7); + } else date.setFullYear(d.y, d.m, d.d); + date.setHours(d.H + (d.Z / 100 | 0), d.M + d.Z % 100, d.S, d.L); + return localZ ? date._ : date; + }; + format.toString = function() { + return template; + }; + return format; + } + function d3_time_parse(date, template, string, j) { + var c, p, t, i = 0, n = template.length, m = string.length; + while (i < n) { + if (j >= m) return -1; + c = template.charCodeAt(i++); + if (c === 37) { + t = template.charAt(i++); + p = d3_time_parsers[t in d3_time_formatPads ? template.charAt(i++) : t]; + if (!p || (j = p(date, string, j)) < 0) return -1; + } else if (c != string.charCodeAt(j++)) { + return -1; + } + } + return j; + } + d3_time_format.utc = function(template) { + var local = d3_time_format(template); + function format(date) { + try { + d3_date = d3_date_utc; + var utc = new d3_date(); + utc._ = date; + return local(utc); + } finally { + d3_date = Date; + } + } + format.parse = function(string) { + try { + d3_date = d3_date_utc; + var date = local.parse(string); + return date && date._; + } finally { + d3_date = Date; + } + }; + format.toString = local.toString; + return format; + }; + d3_time_format.multi = d3_time_format.utc.multi = d3_time_formatMulti; + var d3_time_periodLookup = d3.map(), d3_time_dayRe = d3_time_formatRe(locale_days), d3_time_dayLookup = d3_time_formatLookup(locale_days), d3_time_dayAbbrevRe = d3_time_formatRe(locale_shortDays), d3_time_dayAbbrevLookup = d3_time_formatLookup(locale_shortDays), d3_time_monthRe = d3_time_formatRe(locale_months), d3_time_monthLookup = d3_time_formatLookup(locale_months), d3_time_monthAbbrevRe = d3_time_formatRe(locale_shortMonths), d3_time_monthAbbrevLookup = d3_time_formatLookup(locale_shortMonths); + locale_periods.forEach(function(p, i) { + d3_time_periodLookup.set(p.toLowerCase(), i); + }); + var d3_time_formats = { + a: function(d) { + return locale_shortDays[d.getDay()]; + }, + A: function(d) { + return locale_days[d.getDay()]; + }, + b: function(d) { + return locale_shortMonths[d.getMonth()]; + }, + B: function(d) { + return locale_months[d.getMonth()]; + }, + c: d3_time_format(locale_dateTime), + d: function(d, p) { + return d3_time_formatPad(d.getDate(), p, 2); + }, + e: function(d, p) { + return d3_time_formatPad(d.getDate(), p, 2); + }, + H: function(d, p) { + return d3_time_formatPad(d.getHours(), p, 2); + }, + I: function(d, p) { + return d3_time_formatPad(d.getHours() % 12 || 12, p, 2); + }, + j: function(d, p) { + return d3_time_formatPad(1 + d3_time.dayOfYear(d), p, 3); + }, + L: function(d, p) { + return d3_time_formatPad(d.getMilliseconds(), p, 3); + }, + m: function(d, p) { + return d3_time_formatPad(d.getMonth() + 1, p, 2); + }, + M: function(d, p) { + return d3_time_formatPad(d.getMinutes(), p, 2); + }, + p: function(d) { + return locale_periods[+(d.getHours() >= 12)]; + }, + S: function(d, p) { + return d3_time_formatPad(d.getSeconds(), p, 2); + }, + U: function(d, p) { + return d3_time_formatPad(d3_time.sundayOfYear(d), p, 2); + }, + w: function(d) { + return d.getDay(); + }, + W: function(d, p) { + return d3_time_formatPad(d3_time.mondayOfYear(d), p, 2); + }, + x: d3_time_format(locale_date), + X: d3_time_format(locale_time), + y: function(d, p) { + return d3_time_formatPad(d.getFullYear() % 100, p, 2); + }, + Y: function(d, p) { + return d3_time_formatPad(d.getFullYear() % 1e4, p, 4); + }, + Z: d3_time_zone, + "%": function() { + return "%"; + } + }; + var d3_time_parsers = { + a: d3_time_parseWeekdayAbbrev, + A: d3_time_parseWeekday, + b: d3_time_parseMonthAbbrev, + B: d3_time_parseMonth, + c: d3_time_parseLocaleFull, + d: d3_time_parseDay, + e: d3_time_parseDay, + H: d3_time_parseHour24, + I: d3_time_parseHour24, + j: d3_time_parseDayOfYear, + L: d3_time_parseMilliseconds, + m: d3_time_parseMonthNumber, + M: d3_time_parseMinutes, + p: d3_time_parseAmPm, + S: d3_time_parseSeconds, + U: d3_time_parseWeekNumberSunday, + w: d3_time_parseWeekdayNumber, + W: d3_time_parseWeekNumberMonday, + x: d3_time_parseLocaleDate, + X: d3_time_parseLocaleTime, + y: d3_time_parseYear, + Y: d3_time_parseFullYear, + Z: d3_time_parseZone, + "%": d3_time_parseLiteralPercent + }; + function d3_time_parseWeekdayAbbrev(date, string, i) { + d3_time_dayAbbrevRe.lastIndex = 0; + var n = d3_time_dayAbbrevRe.exec(string.slice(i)); + return n ? (date.w = d3_time_dayAbbrevLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; + } + function d3_time_parseWeekday(date, string, i) { + d3_time_dayRe.lastIndex = 0; + var n = d3_time_dayRe.exec(string.slice(i)); + return n ? (date.w = d3_time_dayLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; + } + function d3_time_parseMonthAbbrev(date, string, i) { + d3_time_monthAbbrevRe.lastIndex = 0; + var n = d3_time_monthAbbrevRe.exec(string.slice(i)); + return n ? (date.m = d3_time_monthAbbrevLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; + } + function d3_time_parseMonth(date, string, i) { + d3_time_monthRe.lastIndex = 0; + var n = d3_time_monthRe.exec(string.slice(i)); + return n ? (date.m = d3_time_monthLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; + } + function d3_time_parseLocaleFull(date, string, i) { + return d3_time_parse(date, d3_time_formats.c.toString(), string, i); + } + function d3_time_parseLocaleDate(date, string, i) { + return d3_time_parse(date, d3_time_formats.x.toString(), string, i); + } + function d3_time_parseLocaleTime(date, string, i) { + return d3_time_parse(date, d3_time_formats.X.toString(), string, i); + } + function d3_time_parseAmPm(date, string, i) { + var n = d3_time_periodLookup.get(string.slice(i, i += 2).toLowerCase()); + return n == null ? -1 : (date.p = n, i); + } + return d3_time_format; + } + var d3_time_formatPads = { + "-": "", + _: " ", + "0": "0" + }, d3_time_numberRe = /^\s*\d+/, d3_time_percentRe = /^%/; + function d3_time_formatPad(value, fill, width) { + var sign = value < 0 ? "-" : "", string = (sign ? -value : value) + "", length = string.length; + return sign + (length < width ? new Array(width - length + 1).join(fill) + string : string); + } + function d3_time_formatRe(names) { + return new RegExp("^(?:" + names.map(d3.requote).join("|") + ")", "i"); + } + function d3_time_formatLookup(names) { + var map = new d3_Map(), i = -1, n = names.length; + while (++i < n) map.set(names[i].toLowerCase(), i); + return map; + } + function d3_time_parseWeekdayNumber(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 1)); + return n ? (date.w = +n[0], i + n[0].length) : -1; + } + function d3_time_parseWeekNumberSunday(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i)); + return n ? (date.U = +n[0], i + n[0].length) : -1; + } + function d3_time_parseWeekNumberMonday(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i)); + return n ? (date.W = +n[0], i + n[0].length) : -1; + } + function d3_time_parseFullYear(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 4)); + return n ? (date.y = +n[0], i + n[0].length) : -1; + } + function d3_time_parseYear(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 2)); + return n ? (date.y = d3_time_expandYear(+n[0]), i + n[0].length) : -1; + } + function d3_time_parseZone(date, string, i) { + return /^[+-]\d{4}$/.test(string = string.slice(i, i + 5)) ? (date.Z = -string, + i + 5) : -1; + } + function d3_time_expandYear(d) { + return d + (d > 68 ? 1900 : 2e3); + } + function d3_time_parseMonthNumber(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 2)); + return n ? (date.m = n[0] - 1, i + n[0].length) : -1; + } + function d3_time_parseDay(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 2)); + return n ? (date.d = +n[0], i + n[0].length) : -1; + } + function d3_time_parseDayOfYear(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 3)); + return n ? (date.j = +n[0], i + n[0].length) : -1; + } + function d3_time_parseHour24(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 2)); + return n ? (date.H = +n[0], i + n[0].length) : -1; + } + function d3_time_parseMinutes(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 2)); + return n ? (date.M = +n[0], i + n[0].length) : -1; + } + function d3_time_parseSeconds(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 2)); + return n ? (date.S = +n[0], i + n[0].length) : -1; + } + function d3_time_parseMilliseconds(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 3)); + return n ? (date.L = +n[0], i + n[0].length) : -1; + } + function d3_time_zone(d) { + var z = d.getTimezoneOffset(), zs = z > 0 ? "-" : "+", zh = abs(z) / 60 | 0, zm = abs(z) % 60; + return zs + d3_time_formatPad(zh, "0", 2) + d3_time_formatPad(zm, "0", 2); + } + function d3_time_parseLiteralPercent(date, string, i) { + d3_time_percentRe.lastIndex = 0; + var n = d3_time_percentRe.exec(string.slice(i, i + 1)); + return n ? i + n[0].length : -1; + } + function d3_time_formatMulti(formats) { + var n = formats.length, i = -1; + while (++i < n) formats[i][0] = this(formats[i][0]); + return function(date) { + var i = 0, f = formats[i]; + while (!f[1](date)) f = formats[++i]; + return f[0](date); + }; + } + d3.locale = function(locale) { + return { + numberFormat: d3_locale_numberFormat(locale), + timeFormat: d3_locale_timeFormat(locale) + }; + }; + var d3_locale_enUS = d3.locale({ + decimal: ".", + thousands: ",", + grouping: [ 3 ], + currency: [ "$", "" ], + dateTime: "%a %b %e %X %Y", + date: "%m/%d/%Y", + time: "%H:%M:%S", + periods: [ "AM", "PM" ], + days: [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ], + shortDays: [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ], + months: [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ], + shortMonths: [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ] + }); + d3.format = d3_locale_enUS.numberFormat; + d3.geo = {}; + function d3_adder() {} + d3_adder.prototype = { + s: 0, + t: 0, + add: function(y) { + d3_adderSum(y, this.t, d3_adderTemp); + d3_adderSum(d3_adderTemp.s, this.s, this); + if (this.s) this.t += d3_adderTemp.t; else this.s = d3_adderTemp.t; + }, + reset: function() { + this.s = this.t = 0; + }, + valueOf: function() { + return this.s; + } + }; + var d3_adderTemp = new d3_adder(); + function d3_adderSum(a, b, o) { + var x = o.s = a + b, bv = x - a, av = x - bv; + o.t = a - av + (b - bv); + } + d3.geo.stream = function(object, listener) { + if (object && d3_geo_streamObjectType.hasOwnProperty(object.type)) { + d3_geo_streamObjectType[object.type](object, listener); + } else { + d3_geo_streamGeometry(object, listener); + } + }; + function d3_geo_streamGeometry(geometry, listener) { + if (geometry && d3_geo_streamGeometryType.hasOwnProperty(geometry.type)) { + d3_geo_streamGeometryType[geometry.type](geometry, listener); + } + } + var d3_geo_streamObjectType = { + Feature: function(feature, listener) { + d3_geo_streamGeometry(feature.geometry, listener); + }, + FeatureCollection: function(object, listener) { + var features = object.features, i = -1, n = features.length; + while (++i < n) d3_geo_streamGeometry(features[i].geometry, listener); + } + }; + var d3_geo_streamGeometryType = { + Sphere: function(object, listener) { + listener.sphere(); + }, + Point: function(object, listener) { + object = object.coordinates; + listener.point(object[0], object[1], object[2]); + }, + MultiPoint: function(object, listener) { + var coordinates = object.coordinates, i = -1, n = coordinates.length; + while (++i < n) object = coordinates[i], listener.point(object[0], object[1], object[2]); + }, + LineString: function(object, listener) { + d3_geo_streamLine(object.coordinates, listener, 0); + }, + MultiLineString: function(object, listener) { + var coordinates = object.coordinates, i = -1, n = coordinates.length; + while (++i < n) d3_geo_streamLine(coordinates[i], listener, 0); + }, + Polygon: function(object, listener) { + d3_geo_streamPolygon(object.coordinates, listener); + }, + MultiPolygon: function(object, listener) { + var coordinates = object.coordinates, i = -1, n = coordinates.length; + while (++i < n) d3_geo_streamPolygon(coordinates[i], listener); + }, + GeometryCollection: function(object, listener) { + var geometries = object.geometries, i = -1, n = geometries.length; + while (++i < n) d3_geo_streamGeometry(geometries[i], listener); + } + }; + function d3_geo_streamLine(coordinates, listener, closed) { + var i = -1, n = coordinates.length - closed, coordinate; + listener.lineStart(); + while (++i < n) coordinate = coordinates[i], listener.point(coordinate[0], coordinate[1], coordinate[2]); + listener.lineEnd(); + } + function d3_geo_streamPolygon(coordinates, listener) { + var i = -1, n = coordinates.length; + listener.polygonStart(); + while (++i < n) d3_geo_streamLine(coordinates[i], listener, 1); + listener.polygonEnd(); + } + d3.geo.area = function(object) { + d3_geo_areaSum = 0; + d3.geo.stream(object, d3_geo_area); + return d3_geo_areaSum; + }; + var d3_geo_areaSum, d3_geo_areaRingSum = new d3_adder(); + var d3_geo_area = { + sphere: function() { + d3_geo_areaSum += 4 * π; + }, + point: d3_noop, + lineStart: d3_noop, + lineEnd: d3_noop, + polygonStart: function() { + d3_geo_areaRingSum.reset(); + d3_geo_area.lineStart = d3_geo_areaRingStart; + }, + polygonEnd: function() { + var area = 2 * d3_geo_areaRingSum; + d3_geo_areaSum += area < 0 ? 4 * π + area : area; + d3_geo_area.lineStart = d3_geo_area.lineEnd = d3_geo_area.point = d3_noop; + } + }; + function d3_geo_areaRingStart() { + var λ00, φ00, λ0, cosφ0, sinφ0; + d3_geo_area.point = function(λ, φ) { + d3_geo_area.point = nextPoint; + λ0 = (λ00 = λ) * d3_radians, cosφ0 = Math.cos(φ = (φ00 = φ) * d3_radians / 2 + π / 4), + sinφ0 = Math.sin(φ); + }; + function nextPoint(λ, φ) { + λ *= d3_radians; + φ = φ * d3_radians / 2 + π / 4; + var dλ = λ - λ0, sdλ = dλ >= 0 ? 1 : -1, adλ = sdλ * dλ, cosφ = Math.cos(φ), sinφ = Math.sin(φ), k = sinφ0 * sinφ, u = cosφ0 * cosφ + k * Math.cos(adλ), v = k * sdλ * Math.sin(adλ); + d3_geo_areaRingSum.add(Math.atan2(v, u)); + λ0 = λ, cosφ0 = cosφ, sinφ0 = sinφ; + } + d3_geo_area.lineEnd = function() { + nextPoint(λ00, φ00); + }; + } + function d3_geo_cartesian(spherical) { + var λ = spherical[0], φ = spherical[1], cosφ = Math.cos(φ); + return [ cosφ * Math.cos(λ), cosφ * Math.sin(λ), Math.sin(φ) ]; + } + function d3_geo_cartesianDot(a, b) { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; + } + function d3_geo_cartesianCross(a, b) { + return [ a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0] ]; + } + function d3_geo_cartesianAdd(a, b) { + a[0] += b[0]; + a[1] += b[1]; + a[2] += b[2]; + } + function d3_geo_cartesianScale(vector, k) { + return [ vector[0] * k, vector[1] * k, vector[2] * k ]; + } + function d3_geo_cartesianNormalize(d) { + var l = Math.sqrt(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]); + d[0] /= l; + d[1] /= l; + d[2] /= l; + } + function d3_geo_spherical(cartesian) { + return [ Math.atan2(cartesian[1], cartesian[0]), d3_asin(cartesian[2]) ]; + } + function d3_geo_sphericalEqual(a, b) { + return abs(a[0] - b[0]) < ε && abs(a[1] - b[1]) < ε; + } + d3.geo.bounds = function() { + var λ0, φ0, λ1, φ1, λ_, λ__, φ__, p0, dλSum, ranges, range; + var bound = { + point: point, + lineStart: lineStart, + lineEnd: lineEnd, + polygonStart: function() { + bound.point = ringPoint; + bound.lineStart = ringStart; + bound.lineEnd = ringEnd; + dλSum = 0; + d3_geo_area.polygonStart(); + }, + polygonEnd: function() { + d3_geo_area.polygonEnd(); + bound.point = point; + bound.lineStart = lineStart; + bound.lineEnd = lineEnd; + if (d3_geo_areaRingSum < 0) λ0 = -(λ1 = 180), φ0 = -(φ1 = 90); else if (dλSum > ε) φ1 = 90; else if (dλSum < -ε) φ0 = -90; + range[0] = λ0, range[1] = λ1; + } + }; + function point(λ, φ) { + ranges.push(range = [ λ0 = λ, λ1 = λ ]); + if (φ < φ0) φ0 = φ; + if (φ > φ1) φ1 = φ; + } + function linePoint(λ, φ) { + var p = d3_geo_cartesian([ λ * d3_radians, φ * d3_radians ]); + if (p0) { + var normal = d3_geo_cartesianCross(p0, p), equatorial = [ normal[1], -normal[0], 0 ], inflection = d3_geo_cartesianCross(equatorial, normal); + d3_geo_cartesianNormalize(inflection); + inflection = d3_geo_spherical(inflection); + var dλ = λ - λ_, s = dλ > 0 ? 1 : -1, λi = inflection[0] * d3_degrees * s, antimeridian = abs(dλ) > 180; + if (antimeridian ^ (s * λ_ < λi && λi < s * λ)) { + var φi = inflection[1] * d3_degrees; + if (φi > φ1) φ1 = φi; + } else if (λi = (λi + 360) % 360 - 180, antimeridian ^ (s * λ_ < λi && λi < s * λ)) { + var φi = -inflection[1] * d3_degrees; + if (φi < φ0) φ0 = φi; + } else { + if (φ < φ0) φ0 = φ; + if (φ > φ1) φ1 = φ; + } + if (antimeridian) { + if (λ < λ_) { + if (angle(λ0, λ) > angle(λ0, λ1)) λ1 = λ; + } else { + if (angle(λ, λ1) > angle(λ0, λ1)) λ0 = λ; + } + } else { + if (λ1 >= λ0) { + if (λ < λ0) λ0 = λ; + if (λ > λ1) λ1 = λ; + } else { + if (λ > λ_) { + if (angle(λ0, λ) > angle(λ0, λ1)) λ1 = λ; + } else { + if (angle(λ, λ1) > angle(λ0, λ1)) λ0 = λ; + } + } + } + } else { + point(λ, φ); + } + p0 = p, λ_ = λ; + } + function lineStart() { + bound.point = linePoint; + } + function lineEnd() { + range[0] = λ0, range[1] = λ1; + bound.point = point; + p0 = null; + } + function ringPoint(λ, φ) { + if (p0) { + var dλ = λ - λ_; + dλSum += abs(dλ) > 180 ? dλ + (dλ > 0 ? 360 : -360) : dλ; + } else λ__ = λ, φ__ = φ; + d3_geo_area.point(λ, φ); + linePoint(λ, φ); + } + function ringStart() { + d3_geo_area.lineStart(); + } + function ringEnd() { + ringPoint(λ__, φ__); + d3_geo_area.lineEnd(); + if (abs(dλSum) > ε) λ0 = -(λ1 = 180); + range[0] = λ0, range[1] = λ1; + p0 = null; + } + function angle(λ0, λ1) { + return (λ1 -= λ0) < 0 ? λ1 + 360 : λ1; + } + function compareRanges(a, b) { + return a[0] - b[0]; + } + function withinRange(x, range) { + return range[0] <= range[1] ? range[0] <= x && x <= range[1] : x < range[0] || range[1] < x; + } + return function(feature) { + φ1 = λ1 = -(λ0 = φ0 = Infinity); + ranges = []; + d3.geo.stream(feature, bound); + var n = ranges.length; + if (n) { + ranges.sort(compareRanges); + for (var i = 1, a = ranges[0], b, merged = [ a ]; i < n; ++i) { + b = ranges[i]; + if (withinRange(b[0], a) || withinRange(b[1], a)) { + if (angle(a[0], b[1]) > angle(a[0], a[1])) a[1] = b[1]; + if (angle(b[0], a[1]) > angle(a[0], a[1])) a[0] = b[0]; + } else { + merged.push(a = b); + } + } + var best = -Infinity, dλ; + for (var n = merged.length - 1, i = 0, a = merged[n], b; i <= n; a = b, ++i) { + b = merged[i]; + if ((dλ = angle(a[1], b[0])) > best) best = dλ, λ0 = b[0], λ1 = a[1]; + } + } + ranges = range = null; + return λ0 === Infinity || φ0 === Infinity ? [ [ NaN, NaN ], [ NaN, NaN ] ] : [ [ λ0, φ0 ], [ λ1, φ1 ] ]; + }; + }(); + d3.geo.centroid = function(object) { + d3_geo_centroidW0 = d3_geo_centroidW1 = d3_geo_centroidX0 = d3_geo_centroidY0 = d3_geo_centroidZ0 = d3_geo_centroidX1 = d3_geo_centroidY1 = d3_geo_centroidZ1 = d3_geo_centroidX2 = d3_geo_centroidY2 = d3_geo_centroidZ2 = 0; + d3.geo.stream(object, d3_geo_centroid); + var x = d3_geo_centroidX2, y = d3_geo_centroidY2, z = d3_geo_centroidZ2, m = x * x + y * y + z * z; + if (m < ε2) { + x = d3_geo_centroidX1, y = d3_geo_centroidY1, z = d3_geo_centroidZ1; + if (d3_geo_centroidW1 < ε) x = d3_geo_centroidX0, y = d3_geo_centroidY0, z = d3_geo_centroidZ0; + m = x * x + y * y + z * z; + if (m < ε2) return [ NaN, NaN ]; + } + return [ Math.atan2(y, x) * d3_degrees, d3_asin(z / Math.sqrt(m)) * d3_degrees ]; + }; + var d3_geo_centroidW0, d3_geo_centroidW1, d3_geo_centroidX0, d3_geo_centroidY0, d3_geo_centroidZ0, d3_geo_centroidX1, d3_geo_centroidY1, d3_geo_centroidZ1, d3_geo_centroidX2, d3_geo_centroidY2, d3_geo_centroidZ2; + var d3_geo_centroid = { + sphere: d3_noop, + point: d3_geo_centroidPoint, + lineStart: d3_geo_centroidLineStart, + lineEnd: d3_geo_centroidLineEnd, + polygonStart: function() { + d3_geo_centroid.lineStart = d3_geo_centroidRingStart; + }, + polygonEnd: function() { + d3_geo_centroid.lineStart = d3_geo_centroidLineStart; + } + }; + function d3_geo_centroidPoint(λ, φ) { + λ *= d3_radians; + var cosφ = Math.cos(φ *= d3_radians); + d3_geo_centroidPointXYZ(cosφ * Math.cos(λ), cosφ * Math.sin(λ), Math.sin(φ)); + } + function d3_geo_centroidPointXYZ(x, y, z) { + ++d3_geo_centroidW0; + d3_geo_centroidX0 += (x - d3_geo_centroidX0) / d3_geo_centroidW0; + d3_geo_centroidY0 += (y - d3_geo_centroidY0) / d3_geo_centroidW0; + d3_geo_centroidZ0 += (z - d3_geo_centroidZ0) / d3_geo_centroidW0; + } + function d3_geo_centroidLineStart() { + var x0, y0, z0; + d3_geo_centroid.point = function(λ, φ) { + λ *= d3_radians; + var cosφ = Math.cos(φ *= d3_radians); + x0 = cosφ * Math.cos(λ); + y0 = cosφ * Math.sin(λ); + z0 = Math.sin(φ); + d3_geo_centroid.point = nextPoint; + d3_geo_centroidPointXYZ(x0, y0, z0); + }; + function nextPoint(λ, φ) { + λ *= d3_radians; + var cosφ = Math.cos(φ *= d3_radians), x = cosφ * Math.cos(λ), y = cosφ * Math.sin(λ), z = Math.sin(φ), w = Math.atan2(Math.sqrt((w = y0 * z - z0 * y) * w + (w = z0 * x - x0 * z) * w + (w = x0 * y - y0 * x) * w), x0 * x + y0 * y + z0 * z); + d3_geo_centroidW1 += w; + d3_geo_centroidX1 += w * (x0 + (x0 = x)); + d3_geo_centroidY1 += w * (y0 + (y0 = y)); + d3_geo_centroidZ1 += w * (z0 + (z0 = z)); + d3_geo_centroidPointXYZ(x0, y0, z0); + } + } + function d3_geo_centroidLineEnd() { + d3_geo_centroid.point = d3_geo_centroidPoint; + } + function d3_geo_centroidRingStart() { + var λ00, φ00, x0, y0, z0; + d3_geo_centroid.point = function(λ, φ) { + λ00 = λ, φ00 = φ; + d3_geo_centroid.point = nextPoint; + λ *= d3_radians; + var cosφ = Math.cos(φ *= d3_radians); + x0 = cosφ * Math.cos(λ); + y0 = cosφ * Math.sin(λ); + z0 = Math.sin(φ); + d3_geo_centroidPointXYZ(x0, y0, z0); + }; + d3_geo_centroid.lineEnd = function() { + nextPoint(λ00, φ00); + d3_geo_centroid.lineEnd = d3_geo_centroidLineEnd; + d3_geo_centroid.point = d3_geo_centroidPoint; + }; + function nextPoint(λ, φ) { + λ *= d3_radians; + var cosφ = Math.cos(φ *= d3_radians), x = cosφ * Math.cos(λ), y = cosφ * Math.sin(λ), z = Math.sin(φ), cx = y0 * z - z0 * y, cy = z0 * x - x0 * z, cz = x0 * y - y0 * x, m = Math.sqrt(cx * cx + cy * cy + cz * cz), u = x0 * x + y0 * y + z0 * z, v = m && -d3_acos(u) / m, w = Math.atan2(m, u); + d3_geo_centroidX2 += v * cx; + d3_geo_centroidY2 += v * cy; + d3_geo_centroidZ2 += v * cz; + d3_geo_centroidW1 += w; + d3_geo_centroidX1 += w * (x0 + (x0 = x)); + d3_geo_centroidY1 += w * (y0 + (y0 = y)); + d3_geo_centroidZ1 += w * (z0 + (z0 = z)); + d3_geo_centroidPointXYZ(x0, y0, z0); + } + } + function d3_geo_compose(a, b) { + function compose(x, y) { + return x = a(x, y), b(x[0], x[1]); + } + if (a.invert && b.invert) compose.invert = function(x, y) { + return x = b.invert(x, y), x && a.invert(x[0], x[1]); + }; + return compose; + } + function d3_true() { + return true; + } + function d3_geo_clipPolygon(segments, compare, clipStartInside, interpolate, listener) { + var subject = [], clip = []; + segments.forEach(function(segment) { + if ((n = segment.length - 1) <= 0) return; + var n, p0 = segment[0], p1 = segment[n]; + if (d3_geo_sphericalEqual(p0, p1)) { + listener.lineStart(); + for (var i = 0; i < n; ++i) listener.point((p0 = segment[i])[0], p0[1]); + listener.lineEnd(); + return; + } + var a = new d3_geo_clipPolygonIntersection(p0, segment, null, true), b = new d3_geo_clipPolygonIntersection(p0, null, a, false); + a.o = b; + subject.push(a); + clip.push(b); + a = new d3_geo_clipPolygonIntersection(p1, segment, null, false); + b = new d3_geo_clipPolygonIntersection(p1, null, a, true); + a.o = b; + subject.push(a); + clip.push(b); + }); + clip.sort(compare); + d3_geo_clipPolygonLinkCircular(subject); + d3_geo_clipPolygonLinkCircular(clip); + if (!subject.length) return; + for (var i = 0, entry = clipStartInside, n = clip.length; i < n; ++i) { + clip[i].e = entry = !entry; + } + var start = subject[0], points, point; + while (1) { + var current = start, isSubject = true; + while (current.v) if ((current = current.n) === start) return; + points = current.z; + listener.lineStart(); + do { + current.v = current.o.v = true; + if (current.e) { + if (isSubject) { + for (var i = 0, n = points.length; i < n; ++i) listener.point((point = points[i])[0], point[1]); + } else { + interpolate(current.x, current.n.x, 1, listener); + } + current = current.n; + } else { + if (isSubject) { + points = current.p.z; + for (var i = points.length - 1; i >= 0; --i) listener.point((point = points[i])[0], point[1]); + } else { + interpolate(current.x, current.p.x, -1, listener); + } + current = current.p; + } + current = current.o; + points = current.z; + isSubject = !isSubject; + } while (!current.v); + listener.lineEnd(); + } + } + function d3_geo_clipPolygonLinkCircular(array) { + if (!(n = array.length)) return; + var n, i = 0, a = array[0], b; + while (++i < n) { + a.n = b = array[i]; + b.p = a; + a = b; + } + a.n = b = array[0]; + b.p = a; + } + function d3_geo_clipPolygonIntersection(point, points, other, entry) { + this.x = point; + this.z = points; + this.o = other; + this.e = entry; + this.v = false; + this.n = this.p = null; + } + function d3_geo_clip(pointVisible, clipLine, interpolate, clipStart) { + return function(rotate, listener) { + var line = clipLine(listener), rotatedClipStart = rotate.invert(clipStart[0], clipStart[1]); + var clip = { + point: point, + lineStart: lineStart, + lineEnd: lineEnd, + polygonStart: function() { + clip.point = pointRing; + clip.lineStart = ringStart; + clip.lineEnd = ringEnd; + segments = []; + polygon = []; + }, + polygonEnd: function() { + clip.point = point; + clip.lineStart = lineStart; + clip.lineEnd = lineEnd; + segments = d3.merge(segments); + var clipStartInside = d3_geo_pointInPolygon(rotatedClipStart, polygon); + if (segments.length) { + if (!polygonStarted) listener.polygonStart(), polygonStarted = true; + d3_geo_clipPolygon(segments, d3_geo_clipSort, clipStartInside, interpolate, listener); + } else if (clipStartInside) { + if (!polygonStarted) listener.polygonStart(), polygonStarted = true; + listener.lineStart(); + interpolate(null, null, 1, listener); + listener.lineEnd(); + } + if (polygonStarted) listener.polygonEnd(), polygonStarted = false; + segments = polygon = null; + }, + sphere: function() { + listener.polygonStart(); + listener.lineStart(); + interpolate(null, null, 1, listener); + listener.lineEnd(); + listener.polygonEnd(); + } + }; + function point(λ, φ) { + var point = rotate(λ, φ); + if (pointVisible(λ = point[0], φ = point[1])) listener.point(λ, φ); + } + function pointLine(λ, φ) { + var point = rotate(λ, φ); + line.point(point[0], point[1]); + } + function lineStart() { + clip.point = pointLine; + line.lineStart(); + } + function lineEnd() { + clip.point = point; + line.lineEnd(); + } + var segments; + var buffer = d3_geo_clipBufferListener(), ringListener = clipLine(buffer), polygonStarted = false, polygon, ring; + function pointRing(λ, φ) { + ring.push([ λ, φ ]); + var point = rotate(λ, φ); + ringListener.point(point[0], point[1]); + } + function ringStart() { + ringListener.lineStart(); + ring = []; + } + function ringEnd() { + pointRing(ring[0][0], ring[0][1]); + ringListener.lineEnd(); + var clean = ringListener.clean(), ringSegments = buffer.buffer(), segment, n = ringSegments.length; + ring.pop(); + polygon.push(ring); + ring = null; + if (!n) return; + if (clean & 1) { + segment = ringSegments[0]; + var n = segment.length - 1, i = -1, point; + if (n > 0) { + if (!polygonStarted) listener.polygonStart(), polygonStarted = true; + listener.lineStart(); + while (++i < n) listener.point((point = segment[i])[0], point[1]); + listener.lineEnd(); + } + return; + } + if (n > 1 && clean & 2) ringSegments.push(ringSegments.pop().concat(ringSegments.shift())); + segments.push(ringSegments.filter(d3_geo_clipSegmentLength1)); + } + return clip; + }; + } + function d3_geo_clipSegmentLength1(segment) { + return segment.length > 1; + } + function d3_geo_clipBufferListener() { + var lines = [], line; + return { + lineStart: function() { + lines.push(line = []); + }, + point: function(λ, φ) { + line.push([ λ, φ ]); + }, + lineEnd: d3_noop, + buffer: function() { + var buffer = lines; + lines = []; + line = null; + return buffer; + }, + rejoin: function() { + if (lines.length > 1) lines.push(lines.pop().concat(lines.shift())); + } + }; + } + function d3_geo_clipSort(a, b) { + return ((a = a.x)[0] < 0 ? a[1] - halfπ - ε : halfπ - a[1]) - ((b = b.x)[0] < 0 ? b[1] - halfπ - ε : halfπ - b[1]); + } + var d3_geo_clipAntimeridian = d3_geo_clip(d3_true, d3_geo_clipAntimeridianLine, d3_geo_clipAntimeridianInterpolate, [ -π, -π / 2 ]); + function d3_geo_clipAntimeridianLine(listener) { + var λ0 = NaN, φ0 = NaN, sλ0 = NaN, clean; + return { + lineStart: function() { + listener.lineStart(); + clean = 1; + }, + point: function(λ1, φ1) { + var sλ1 = λ1 > 0 ? π : -π, dλ = abs(λ1 - λ0); + if (abs(dλ - π) < ε) { + listener.point(λ0, φ0 = (φ0 + φ1) / 2 > 0 ? halfπ : -halfπ); + listener.point(sλ0, φ0); + listener.lineEnd(); + listener.lineStart(); + listener.point(sλ1, φ0); + listener.point(λ1, φ0); + clean = 0; + } else if (sλ0 !== sλ1 && dλ >= π) { + if (abs(λ0 - sλ0) < ε) λ0 -= sλ0 * ε; + if (abs(λ1 - sλ1) < ε) λ1 -= sλ1 * ε; + φ0 = d3_geo_clipAntimeridianIntersect(λ0, φ0, λ1, φ1); + listener.point(sλ0, φ0); + listener.lineEnd(); + listener.lineStart(); + listener.point(sλ1, φ0); + clean = 0; + } + listener.point(λ0 = λ1, φ0 = φ1); + sλ0 = sλ1; + }, + lineEnd: function() { + listener.lineEnd(); + λ0 = φ0 = NaN; + }, + clean: function() { + return 2 - clean; + } + }; + } + function d3_geo_clipAntimeridianIntersect(λ0, φ0, λ1, φ1) { + var cosφ0, cosφ1, sinλ0_λ1 = Math.sin(λ0 - λ1); + return abs(sinλ0_λ1) > ε ? Math.atan((Math.sin(φ0) * (cosφ1 = Math.cos(φ1)) * Math.sin(λ1) - Math.sin(φ1) * (cosφ0 = Math.cos(φ0)) * Math.sin(λ0)) / (cosφ0 * cosφ1 * sinλ0_λ1)) : (φ0 + φ1) / 2; + } + function d3_geo_clipAntimeridianInterpolate(from, to, direction, listener) { + var φ; + if (from == null) { + φ = direction * halfπ; + listener.point(-π, φ); + listener.point(0, φ); + listener.point(π, φ); + listener.point(π, 0); + listener.point(π, -φ); + listener.point(0, -φ); + listener.point(-π, -φ); + listener.point(-π, 0); + listener.point(-π, φ); + } else if (abs(from[0] - to[0]) > ε) { + var s = from[0] < to[0] ? π : -π; + φ = direction * s / 2; + listener.point(-s, φ); + listener.point(0, φ); + listener.point(s, φ); + } else { + listener.point(to[0], to[1]); + } + } + function d3_geo_pointInPolygon(point, polygon) { + var meridian = point[0], parallel = point[1], meridianNormal = [ Math.sin(meridian), -Math.cos(meridian), 0 ], polarAngle = 0, winding = 0; + d3_geo_areaRingSum.reset(); + for (var i = 0, n = polygon.length; i < n; ++i) { + var ring = polygon[i], m = ring.length; + if (!m) continue; + var point0 = ring[0], λ0 = point0[0], φ0 = point0[1] / 2 + π / 4, sinφ0 = Math.sin(φ0), cosφ0 = Math.cos(φ0), j = 1; + while (true) { + if (j === m) j = 0; + point = ring[j]; + var λ = point[0], φ = point[1] / 2 + π / 4, sinφ = Math.sin(φ), cosφ = Math.cos(φ), dλ = λ - λ0, sdλ = dλ >= 0 ? 1 : -1, adλ = sdλ * dλ, antimeridian = adλ > π, k = sinφ0 * sinφ; + d3_geo_areaRingSum.add(Math.atan2(k * sdλ * Math.sin(adλ), cosφ0 * cosφ + k * Math.cos(adλ))); + polarAngle += antimeridian ? dλ + sdλ * τ : dλ; + if (antimeridian ^ λ0 >= meridian ^ λ >= meridian) { + var arc = d3_geo_cartesianCross(d3_geo_cartesian(point0), d3_geo_cartesian(point)); + d3_geo_cartesianNormalize(arc); + var intersection = d3_geo_cartesianCross(meridianNormal, arc); + d3_geo_cartesianNormalize(intersection); + var φarc = (antimeridian ^ dλ >= 0 ? -1 : 1) * d3_asin(intersection[2]); + if (parallel > φarc || parallel === φarc && (arc[0] || arc[1])) { + winding += antimeridian ^ dλ >= 0 ? 1 : -1; + } + } + if (!j++) break; + λ0 = λ, sinφ0 = sinφ, cosφ0 = cosφ, point0 = point; + } + } + return (polarAngle < -ε || polarAngle < ε && d3_geo_areaRingSum < 0) ^ winding & 1; + } + function d3_geo_clipCircle(radius) { + var cr = Math.cos(radius), smallRadius = cr > 0, notHemisphere = abs(cr) > ε, interpolate = d3_geo_circleInterpolate(radius, 6 * d3_radians); + return d3_geo_clip(visible, clipLine, interpolate, smallRadius ? [ 0, -radius ] : [ -π, radius - π ]); + function visible(λ, φ) { + return Math.cos(λ) * Math.cos(φ) > cr; + } + function clipLine(listener) { + var point0, c0, v0, v00, clean; + return { + lineStart: function() { + v00 = v0 = false; + clean = 1; + }, + point: function(λ, φ) { + var point1 = [ λ, φ ], point2, v = visible(λ, φ), c = smallRadius ? v ? 0 : code(λ, φ) : v ? code(λ + (λ < 0 ? π : -π), φ) : 0; + if (!point0 && (v00 = v0 = v)) listener.lineStart(); + if (v !== v0) { + point2 = intersect(point0, point1); + if (d3_geo_sphericalEqual(point0, point2) || d3_geo_sphericalEqual(point1, point2)) { + point1[0] += ε; + point1[1] += ε; + v = visible(point1[0], point1[1]); + } + } + if (v !== v0) { + clean = 0; + if (v) { + listener.lineStart(); + point2 = intersect(point1, point0); + listener.point(point2[0], point2[1]); + } else { + point2 = intersect(point0, point1); + listener.point(point2[0], point2[1]); + listener.lineEnd(); + } + point0 = point2; + } else if (notHemisphere && point0 && smallRadius ^ v) { + var t; + if (!(c & c0) && (t = intersect(point1, point0, true))) { + clean = 0; + if (smallRadius) { + listener.lineStart(); + listener.point(t[0][0], t[0][1]); + listener.point(t[1][0], t[1][1]); + listener.lineEnd(); + } else { + listener.point(t[1][0], t[1][1]); + listener.lineEnd(); + listener.lineStart(); + listener.point(t[0][0], t[0][1]); + } + } + } + if (v && (!point0 || !d3_geo_sphericalEqual(point0, point1))) { + listener.point(point1[0], point1[1]); + } + point0 = point1, v0 = v, c0 = c; + }, + lineEnd: function() { + if (v0) listener.lineEnd(); + point0 = null; + }, + clean: function() { + return clean | (v00 && v0) << 1; + } + }; + } + function intersect(a, b, two) { + var pa = d3_geo_cartesian(a), pb = d3_geo_cartesian(b); + var n1 = [ 1, 0, 0 ], n2 = d3_geo_cartesianCross(pa, pb), n2n2 = d3_geo_cartesianDot(n2, n2), n1n2 = n2[0], determinant = n2n2 - n1n2 * n1n2; + if (!determinant) return !two && a; + var c1 = cr * n2n2 / determinant, c2 = -cr * n1n2 / determinant, n1xn2 = d3_geo_cartesianCross(n1, n2), A = d3_geo_cartesianScale(n1, c1), B = d3_geo_cartesianScale(n2, c2); + d3_geo_cartesianAdd(A, B); + var u = n1xn2, w = d3_geo_cartesianDot(A, u), uu = d3_geo_cartesianDot(u, u), t2 = w * w - uu * (d3_geo_cartesianDot(A, A) - 1); + if (t2 < 0) return; + var t = Math.sqrt(t2), q = d3_geo_cartesianScale(u, (-w - t) / uu); + d3_geo_cartesianAdd(q, A); + q = d3_geo_spherical(q); + if (!two) return q; + var λ0 = a[0], λ1 = b[0], φ0 = a[1], φ1 = b[1], z; + if (λ1 < λ0) z = λ0, λ0 = λ1, λ1 = z; + var δλ = λ1 - λ0, polar = abs(δλ - π) < ε, meridian = polar || δλ < ε; + if (!polar && φ1 < φ0) z = φ0, φ0 = φ1, φ1 = z; + if (meridian ? polar ? φ0 + φ1 > 0 ^ q[1] < (abs(q[0] - λ0) < ε ? φ0 : φ1) : φ0 <= q[1] && q[1] <= φ1 : δλ > π ^ (λ0 <= q[0] && q[0] <= λ1)) { + var q1 = d3_geo_cartesianScale(u, (-w + t) / uu); + d3_geo_cartesianAdd(q1, A); + return [ q, d3_geo_spherical(q1) ]; + } + } + function code(λ, φ) { + var r = smallRadius ? radius : π - radius, code = 0; + if (λ < -r) code |= 1; else if (λ > r) code |= 2; + if (φ < -r) code |= 4; else if (φ > r) code |= 8; + return code; + } + } + function d3_geom_clipLine(x0, y0, x1, y1) { + return function(line) { + var a = line.a, b = line.b, ax = a.x, ay = a.y, bx = b.x, by = b.y, t0 = 0, t1 = 1, dx = bx - ax, dy = by - ay, r; + r = x0 - ax; + if (!dx && r > 0) return; + r /= dx; + if (dx < 0) { + if (r < t0) return; + if (r < t1) t1 = r; + } else if (dx > 0) { + if (r > t1) return; + if (r > t0) t0 = r; + } + r = x1 - ax; + if (!dx && r < 0) return; + r /= dx; + if (dx < 0) { + if (r > t1) return; + if (r > t0) t0 = r; + } else if (dx > 0) { + if (r < t0) return; + if (r < t1) t1 = r; + } + r = y0 - ay; + if (!dy && r > 0) return; + r /= dy; + if (dy < 0) { + if (r < t0) return; + if (r < t1) t1 = r; + } else if (dy > 0) { + if (r > t1) return; + if (r > t0) t0 = r; + } + r = y1 - ay; + if (!dy && r < 0) return; + r /= dy; + if (dy < 0) { + if (r > t1) return; + if (r > t0) t0 = r; + } else if (dy > 0) { + if (r < t0) return; + if (r < t1) t1 = r; + } + if (t0 > 0) line.a = { + x: ax + t0 * dx, + y: ay + t0 * dy + }; + if (t1 < 1) line.b = { + x: ax + t1 * dx, + y: ay + t1 * dy + }; + return line; + }; + } + var d3_geo_clipExtentMAX = 1e9; + d3.geo.clipExtent = function() { + var x0, y0, x1, y1, stream, clip, clipExtent = { + stream: function(output) { + if (stream) stream.valid = false; + stream = clip(output); + stream.valid = true; + return stream; + }, + extent: function(_) { + if (!arguments.length) return [ [ x0, y0 ], [ x1, y1 ] ]; + clip = d3_geo_clipExtent(x0 = +_[0][0], y0 = +_[0][1], x1 = +_[1][0], y1 = +_[1][1]); + if (stream) stream.valid = false, stream = null; + return clipExtent; + } + }; + return clipExtent.extent([ [ 0, 0 ], [ 960, 500 ] ]); + }; + function d3_geo_clipExtent(x0, y0, x1, y1) { + return function(listener) { + var listener_ = listener, bufferListener = d3_geo_clipBufferListener(), clipLine = d3_geom_clipLine(x0, y0, x1, y1), segments, polygon, ring; + var clip = { + point: point, + lineStart: lineStart, + lineEnd: lineEnd, + polygonStart: function() { + listener = bufferListener; + segments = []; + polygon = []; + clean = true; + }, + polygonEnd: function() { + listener = listener_; + segments = d3.merge(segments); + var clipStartInside = insidePolygon([ x0, y1 ]), inside = clean && clipStartInside, visible = segments.length; + if (inside || visible) { + listener.polygonStart(); + if (inside) { + listener.lineStart(); + interpolate(null, null, 1, listener); + listener.lineEnd(); + } + if (visible) { + d3_geo_clipPolygon(segments, compare, clipStartInside, interpolate, listener); + } + listener.polygonEnd(); + } + segments = polygon = ring = null; + } + }; + function insidePolygon(p) { + var wn = 0, n = polygon.length, y = p[1]; + for (var i = 0; i < n; ++i) { + for (var j = 1, v = polygon[i], m = v.length, a = v[0], b; j < m; ++j) { + b = v[j]; + if (a[1] <= y) { + if (b[1] > y && d3_cross2d(a, b, p) > 0) ++wn; + } else { + if (b[1] <= y && d3_cross2d(a, b, p) < 0) --wn; + } + a = b; + } + } + return wn !== 0; + } + function interpolate(from, to, direction, listener) { + var a = 0, a1 = 0; + if (from == null || (a = corner(from, direction)) !== (a1 = corner(to, direction)) || comparePoints(from, to) < 0 ^ direction > 0) { + do { + listener.point(a === 0 || a === 3 ? x0 : x1, a > 1 ? y1 : y0); + } while ((a = (a + direction + 4) % 4) !== a1); + } else { + listener.point(to[0], to[1]); + } + } + function pointVisible(x, y) { + return x0 <= x && x <= x1 && y0 <= y && y <= y1; + } + function point(x, y) { + if (pointVisible(x, y)) listener.point(x, y); + } + var x__, y__, v__, x_, y_, v_, first, clean; + function lineStart() { + clip.point = linePoint; + if (polygon) polygon.push(ring = []); + first = true; + v_ = false; + x_ = y_ = NaN; + } + function lineEnd() { + if (segments) { + linePoint(x__, y__); + if (v__ && v_) bufferListener.rejoin(); + segments.push(bufferListener.buffer()); + } + clip.point = point; + if (v_) listener.lineEnd(); + } + function linePoint(x, y) { + x = Math.max(-d3_geo_clipExtentMAX, Math.min(d3_geo_clipExtentMAX, x)); + y = Math.max(-d3_geo_clipExtentMAX, Math.min(d3_geo_clipExtentMAX, y)); + var v = pointVisible(x, y); + if (polygon) ring.push([ x, y ]); + if (first) { + x__ = x, y__ = y, v__ = v; + first = false; + if (v) { + listener.lineStart(); + listener.point(x, y); + } + } else { + if (v && v_) listener.point(x, y); else { + var l = { + a: { + x: x_, + y: y_ + }, + b: { + x: x, + y: y + } + }; + if (clipLine(l)) { + if (!v_) { + listener.lineStart(); + listener.point(l.a.x, l.a.y); + } + listener.point(l.b.x, l.b.y); + if (!v) listener.lineEnd(); + clean = false; + } else if (v) { + listener.lineStart(); + listener.point(x, y); + clean = false; + } + } + } + x_ = x, y_ = y, v_ = v; + } + return clip; + }; + function corner(p, direction) { + return abs(p[0] - x0) < ε ? direction > 0 ? 0 : 3 : abs(p[0] - x1) < ε ? direction > 0 ? 2 : 1 : abs(p[1] - y0) < ε ? direction > 0 ? 1 : 0 : direction > 0 ? 3 : 2; + } + function compare(a, b) { + return comparePoints(a.x, b.x); + } + function comparePoints(a, b) { + var ca = corner(a, 1), cb = corner(b, 1); + return ca !== cb ? ca - cb : ca === 0 ? b[1] - a[1] : ca === 1 ? a[0] - b[0] : ca === 2 ? a[1] - b[1] : b[0] - a[0]; + } + } + function d3_geo_conic(projectAt) { + var φ0 = 0, φ1 = π / 3, m = d3_geo_projectionMutator(projectAt), p = m(φ0, φ1); + p.parallels = function(_) { + if (!arguments.length) return [ φ0 / π * 180, φ1 / π * 180 ]; + return m(φ0 = _[0] * π / 180, φ1 = _[1] * π / 180); + }; + return p; + } + function d3_geo_conicEqualArea(φ0, φ1) { + var sinφ0 = Math.sin(φ0), n = (sinφ0 + Math.sin(φ1)) / 2, C = 1 + sinφ0 * (2 * n - sinφ0), ρ0 = Math.sqrt(C) / n; + function forward(λ, φ) { + var ρ = Math.sqrt(C - 2 * n * Math.sin(φ)) / n; + return [ ρ * Math.sin(λ *= n), ρ0 - ρ * Math.cos(λ) ]; + } + forward.invert = function(x, y) { + var ρ0_y = ρ0 - y; + return [ Math.atan2(x, ρ0_y) / n, d3_asin((C - (x * x + ρ0_y * ρ0_y) * n * n) / (2 * n)) ]; + }; + return forward; + } + (d3.geo.conicEqualArea = function() { + return d3_geo_conic(d3_geo_conicEqualArea); + }).raw = d3_geo_conicEqualArea; + d3.geo.albers = function() { + return d3.geo.conicEqualArea().rotate([ 96, 0 ]).center([ -.6, 38.7 ]).parallels([ 29.5, 45.5 ]).scale(1070); + }; + d3.geo.albersUsa = function() { + var lower48 = d3.geo.albers(); + var alaska = d3.geo.conicEqualArea().rotate([ 154, 0 ]).center([ -2, 58.5 ]).parallels([ 55, 65 ]); + var hawaii = d3.geo.conicEqualArea().rotate([ 157, 0 ]).center([ -3, 19.9 ]).parallels([ 8, 18 ]); + var point, pointStream = { + point: function(x, y) { + point = [ x, y ]; + } + }, lower48Point, alaskaPoint, hawaiiPoint; + function albersUsa(coordinates) { + var x = coordinates[0], y = coordinates[1]; + point = null; + (lower48Point(x, y), point) || (alaskaPoint(x, y), point) || hawaiiPoint(x, y); + return point; + } + albersUsa.invert = function(coordinates) { + var k = lower48.scale(), t = lower48.translate(), x = (coordinates[0] - t[0]) / k, y = (coordinates[1] - t[1]) / k; + return (y >= .12 && y < .234 && x >= -.425 && x < -.214 ? alaska : y >= .166 && y < .234 && x >= -.214 && x < -.115 ? hawaii : lower48).invert(coordinates); + }; + albersUsa.stream = function(stream) { + var lower48Stream = lower48.stream(stream), alaskaStream = alaska.stream(stream), hawaiiStream = hawaii.stream(stream); + return { + point: function(x, y) { + lower48Stream.point(x, y); + alaskaStream.point(x, y); + hawaiiStream.point(x, y); + }, + sphere: function() { + lower48Stream.sphere(); + alaskaStream.sphere(); + hawaiiStream.sphere(); + }, + lineStart: function() { + lower48Stream.lineStart(); + alaskaStream.lineStart(); + hawaiiStream.lineStart(); + }, + lineEnd: function() { + lower48Stream.lineEnd(); + alaskaStream.lineEnd(); + hawaiiStream.lineEnd(); + }, + polygonStart: function() { + lower48Stream.polygonStart(); + alaskaStream.polygonStart(); + hawaiiStream.polygonStart(); + }, + polygonEnd: function() { + lower48Stream.polygonEnd(); + alaskaStream.polygonEnd(); + hawaiiStream.polygonEnd(); + } + }; + }; + albersUsa.precision = function(_) { + if (!arguments.length) return lower48.precision(); + lower48.precision(_); + alaska.precision(_); + hawaii.precision(_); + return albersUsa; + }; + albersUsa.scale = function(_) { + if (!arguments.length) return lower48.scale(); + lower48.scale(_); + alaska.scale(_ * .35); + hawaii.scale(_); + return albersUsa.translate(lower48.translate()); + }; + albersUsa.translate = function(_) { + if (!arguments.length) return lower48.translate(); + var k = lower48.scale(), x = +_[0], y = +_[1]; + lower48Point = lower48.translate(_).clipExtent([ [ x - .455 * k, y - .238 * k ], [ x + .455 * k, y + .238 * k ] ]).stream(pointStream).point; + alaskaPoint = alaska.translate([ x - .307 * k, y + .201 * k ]).clipExtent([ [ x - .425 * k + ε, y + .12 * k + ε ], [ x - .214 * k - ε, y + .234 * k - ε ] ]).stream(pointStream).point; + hawaiiPoint = hawaii.translate([ x - .205 * k, y + .212 * k ]).clipExtent([ [ x - .214 * k + ε, y + .166 * k + ε ], [ x - .115 * k - ε, y + .234 * k - ε ] ]).stream(pointStream).point; + return albersUsa; + }; + return albersUsa.scale(1070); + }; + var d3_geo_pathAreaSum, d3_geo_pathAreaPolygon, d3_geo_pathArea = { + point: d3_noop, + lineStart: d3_noop, + lineEnd: d3_noop, + polygonStart: function() { + d3_geo_pathAreaPolygon = 0; + d3_geo_pathArea.lineStart = d3_geo_pathAreaRingStart; + }, + polygonEnd: function() { + d3_geo_pathArea.lineStart = d3_geo_pathArea.lineEnd = d3_geo_pathArea.point = d3_noop; + d3_geo_pathAreaSum += abs(d3_geo_pathAreaPolygon / 2); + } + }; + function d3_geo_pathAreaRingStart() { + var x00, y00, x0, y0; + d3_geo_pathArea.point = function(x, y) { + d3_geo_pathArea.point = nextPoint; + x00 = x0 = x, y00 = y0 = y; + }; + function nextPoint(x, y) { + d3_geo_pathAreaPolygon += y0 * x - x0 * y; + x0 = x, y0 = y; + } + d3_geo_pathArea.lineEnd = function() { + nextPoint(x00, y00); + }; + } + var d3_geo_pathBoundsX0, d3_geo_pathBoundsY0, d3_geo_pathBoundsX1, d3_geo_pathBoundsY1; + var d3_geo_pathBounds = { + point: d3_geo_pathBoundsPoint, + lineStart: d3_noop, + lineEnd: d3_noop, + polygonStart: d3_noop, + polygonEnd: d3_noop + }; + function d3_geo_pathBoundsPoint(x, y) { + if (x < d3_geo_pathBoundsX0) d3_geo_pathBoundsX0 = x; + if (x > d3_geo_pathBoundsX1) d3_geo_pathBoundsX1 = x; + if (y < d3_geo_pathBoundsY0) d3_geo_pathBoundsY0 = y; + if (y > d3_geo_pathBoundsY1) d3_geo_pathBoundsY1 = y; + } + function d3_geo_pathBuffer() { + var pointCircle = d3_geo_pathBufferCircle(4.5), buffer = []; + var stream = { + point: point, + lineStart: function() { + stream.point = pointLineStart; + }, + lineEnd: lineEnd, + polygonStart: function() { + stream.lineEnd = lineEndPolygon; + }, + polygonEnd: function() { + stream.lineEnd = lineEnd; + stream.point = point; + }, + pointRadius: function(_) { + pointCircle = d3_geo_pathBufferCircle(_); + return stream; + }, + result: function() { + if (buffer.length) { + var result = buffer.join(""); + buffer = []; + return result; + } + } + }; + function point(x, y) { + buffer.push("M", x, ",", y, pointCircle); + } + function pointLineStart(x, y) { + buffer.push("M", x, ",", y); + stream.point = pointLine; + } + function pointLine(x, y) { + buffer.push("L", x, ",", y); + } + function lineEnd() { + stream.point = point; + } + function lineEndPolygon() { + buffer.push("Z"); + } + return stream; + } + function d3_geo_pathBufferCircle(radius) { + return "m0," + radius + "a" + radius + "," + radius + " 0 1,1 0," + -2 * radius + "a" + radius + "," + radius + " 0 1,1 0," + 2 * radius + "z"; + } + var d3_geo_pathCentroid = { + point: d3_geo_pathCentroidPoint, + lineStart: d3_geo_pathCentroidLineStart, + lineEnd: d3_geo_pathCentroidLineEnd, + polygonStart: function() { + d3_geo_pathCentroid.lineStart = d3_geo_pathCentroidRingStart; + }, + polygonEnd: function() { + d3_geo_pathCentroid.point = d3_geo_pathCentroidPoint; + d3_geo_pathCentroid.lineStart = d3_geo_pathCentroidLineStart; + d3_geo_pathCentroid.lineEnd = d3_geo_pathCentroidLineEnd; + } + }; + function d3_geo_pathCentroidPoint(x, y) { + d3_geo_centroidX0 += x; + d3_geo_centroidY0 += y; + ++d3_geo_centroidZ0; + } + function d3_geo_pathCentroidLineStart() { + var x0, y0; + d3_geo_pathCentroid.point = function(x, y) { + d3_geo_pathCentroid.point = nextPoint; + d3_geo_pathCentroidPoint(x0 = x, y0 = y); + }; + function nextPoint(x, y) { + var dx = x - x0, dy = y - y0, z = Math.sqrt(dx * dx + dy * dy); + d3_geo_centroidX1 += z * (x0 + x) / 2; + d3_geo_centroidY1 += z * (y0 + y) / 2; + d3_geo_centroidZ1 += z; + d3_geo_pathCentroidPoint(x0 = x, y0 = y); + } + } + function d3_geo_pathCentroidLineEnd() { + d3_geo_pathCentroid.point = d3_geo_pathCentroidPoint; + } + function d3_geo_pathCentroidRingStart() { + var x00, y00, x0, y0; + d3_geo_pathCentroid.point = function(x, y) { + d3_geo_pathCentroid.point = nextPoint; + d3_geo_pathCentroidPoint(x00 = x0 = x, y00 = y0 = y); + }; + function nextPoint(x, y) { + var dx = x - x0, dy = y - y0, z = Math.sqrt(dx * dx + dy * dy); + d3_geo_centroidX1 += z * (x0 + x) / 2; + d3_geo_centroidY1 += z * (y0 + y) / 2; + d3_geo_centroidZ1 += z; + z = y0 * x - x0 * y; + d3_geo_centroidX2 += z * (x0 + x); + d3_geo_centroidY2 += z * (y0 + y); + d3_geo_centroidZ2 += z * 3; + d3_geo_pathCentroidPoint(x0 = x, y0 = y); + } + d3_geo_pathCentroid.lineEnd = function() { + nextPoint(x00, y00); + }; + } + function d3_geo_pathContext(context) { + var pointRadius = 4.5; + var stream = { + point: point, + lineStart: function() { + stream.point = pointLineStart; + }, + lineEnd: lineEnd, + polygonStart: function() { + stream.lineEnd = lineEndPolygon; + }, + polygonEnd: function() { + stream.lineEnd = lineEnd; + stream.point = point; + }, + pointRadius: function(_) { + pointRadius = _; + return stream; + }, + result: d3_noop + }; + function point(x, y) { + context.moveTo(x + pointRadius, y); + context.arc(x, y, pointRadius, 0, τ); + } + function pointLineStart(x, y) { + context.moveTo(x, y); + stream.point = pointLine; + } + function pointLine(x, y) { + context.lineTo(x, y); + } + function lineEnd() { + stream.point = point; + } + function lineEndPolygon() { + context.closePath(); + } + return stream; + } + function d3_geo_resample(project) { + var δ2 = .5, cosMinDistance = Math.cos(30 * d3_radians), maxDepth = 16; + function resample(stream) { + return (maxDepth ? resampleRecursive : resampleNone)(stream); + } + function resampleNone(stream) { + return d3_geo_transformPoint(stream, function(x, y) { + x = project(x, y); + stream.point(x[0], x[1]); + }); + } + function resampleRecursive(stream) { + var λ00, φ00, x00, y00, a00, b00, c00, λ0, x0, y0, a0, b0, c0; + var resample = { + point: point, + lineStart: lineStart, + lineEnd: lineEnd, + polygonStart: function() { + stream.polygonStart(); + resample.lineStart = ringStart; + }, + polygonEnd: function() { + stream.polygonEnd(); + resample.lineStart = lineStart; + } + }; + function point(x, y) { + x = project(x, y); + stream.point(x[0], x[1]); + } + function lineStart() { + x0 = NaN; + resample.point = linePoint; + stream.lineStart(); + } + function linePoint(λ, φ) { + var c = d3_geo_cartesian([ λ, φ ]), p = project(λ, φ); + resampleLineTo(x0, y0, λ0, a0, b0, c0, x0 = p[0], y0 = p[1], λ0 = λ, a0 = c[0], b0 = c[1], c0 = c[2], maxDepth, stream); + stream.point(x0, y0); + } + function lineEnd() { + resample.point = point; + stream.lineEnd(); + } + function ringStart() { + lineStart(); + resample.point = ringPoint; + resample.lineEnd = ringEnd; + } + function ringPoint(λ, φ) { + linePoint(λ00 = λ, φ00 = φ), x00 = x0, y00 = y0, a00 = a0, b00 = b0, c00 = c0; + resample.point = linePoint; + } + function ringEnd() { + resampleLineTo(x0, y0, λ0, a0, b0, c0, x00, y00, λ00, a00, b00, c00, maxDepth, stream); + resample.lineEnd = lineEnd; + lineEnd(); + } + return resample; + } + function resampleLineTo(x0, y0, λ0, a0, b0, c0, x1, y1, λ1, a1, b1, c1, depth, stream) { + var dx = x1 - x0, dy = y1 - y0, d2 = dx * dx + dy * dy; + if (d2 > 4 * δ2 && depth--) { + var a = a0 + a1, b = b0 + b1, c = c0 + c1, m = Math.sqrt(a * a + b * b + c * c), φ2 = Math.asin(c /= m), λ2 = abs(abs(c) - 1) < ε || abs(λ0 - λ1) < ε ? (λ0 + λ1) / 2 : Math.atan2(b, a), p = project(λ2, φ2), x2 = p[0], y2 = p[1], dx2 = x2 - x0, dy2 = y2 - y0, dz = dy * dx2 - dx * dy2; + if (dz * dz / d2 > δ2 || abs((dx * dx2 + dy * dy2) / d2 - .5) > .3 || a0 * a1 + b0 * b1 + c0 * c1 < cosMinDistance) { + resampleLineTo(x0, y0, λ0, a0, b0, c0, x2, y2, λ2, a /= m, b /= m, c, depth, stream); + stream.point(x2, y2); + resampleLineTo(x2, y2, λ2, a, b, c, x1, y1, λ1, a1, b1, c1, depth, stream); + } + } + } + resample.precision = function(_) { + if (!arguments.length) return Math.sqrt(δ2); + maxDepth = (δ2 = _ * _) > 0 && 16; + return resample; + }; + return resample; + } + d3.geo.path = function() { + var pointRadius = 4.5, projection, context, projectStream, contextStream, cacheStream; + function path(object) { + if (object) { + if (typeof pointRadius === "function") contextStream.pointRadius(+pointRadius.apply(this, arguments)); + if (!cacheStream || !cacheStream.valid) cacheStream = projectStream(contextStream); + d3.geo.stream(object, cacheStream); + } + return contextStream.result(); + } + path.area = function(object) { + d3_geo_pathAreaSum = 0; + d3.geo.stream(object, projectStream(d3_geo_pathArea)); + return d3_geo_pathAreaSum; + }; + path.centroid = function(object) { + d3_geo_centroidX0 = d3_geo_centroidY0 = d3_geo_centroidZ0 = d3_geo_centroidX1 = d3_geo_centroidY1 = d3_geo_centroidZ1 = d3_geo_centroidX2 = d3_geo_centroidY2 = d3_geo_centroidZ2 = 0; + d3.geo.stream(object, projectStream(d3_geo_pathCentroid)); + return d3_geo_centroidZ2 ? [ d3_geo_centroidX2 / d3_geo_centroidZ2, d3_geo_centroidY2 / d3_geo_centroidZ2 ] : d3_geo_centroidZ1 ? [ d3_geo_centroidX1 / d3_geo_centroidZ1, d3_geo_centroidY1 / d3_geo_centroidZ1 ] : d3_geo_centroidZ0 ? [ d3_geo_centroidX0 / d3_geo_centroidZ0, d3_geo_centroidY0 / d3_geo_centroidZ0 ] : [ NaN, NaN ]; + }; + path.bounds = function(object) { + d3_geo_pathBoundsX1 = d3_geo_pathBoundsY1 = -(d3_geo_pathBoundsX0 = d3_geo_pathBoundsY0 = Infinity); + d3.geo.stream(object, projectStream(d3_geo_pathBounds)); + return [ [ d3_geo_pathBoundsX0, d3_geo_pathBoundsY0 ], [ d3_geo_pathBoundsX1, d3_geo_pathBoundsY1 ] ]; + }; + path.projection = function(_) { + if (!arguments.length) return projection; + projectStream = (projection = _) ? _.stream || d3_geo_pathProjectStream(_) : d3_identity; + return reset(); + }; + path.context = function(_) { + if (!arguments.length) return context; + contextStream = (context = _) == null ? new d3_geo_pathBuffer() : new d3_geo_pathContext(_); + if (typeof pointRadius !== "function") contextStream.pointRadius(pointRadius); + return reset(); + }; + path.pointRadius = function(_) { + if (!arguments.length) return pointRadius; + pointRadius = typeof _ === "function" ? _ : (contextStream.pointRadius(+_), +_); + return path; + }; + function reset() { + cacheStream = null; + return path; + } + return path.projection(d3.geo.albersUsa()).context(null); + }; + function d3_geo_pathProjectStream(project) { + var resample = d3_geo_resample(function(x, y) { + return project([ x * d3_degrees, y * d3_degrees ]); + }); + return function(stream) { + return d3_geo_projectionRadians(resample(stream)); + }; + } + d3.geo.transform = function(methods) { + return { + stream: function(stream) { + var transform = new d3_geo_transform(stream); + for (var k in methods) transform[k] = methods[k]; + return transform; + } + }; + }; + function d3_geo_transform(stream) { + this.stream = stream; + } + d3_geo_transform.prototype = { + point: function(x, y) { + this.stream.point(x, y); + }, + sphere: function() { + this.stream.sphere(); + }, + lineStart: function() { + this.stream.lineStart(); + }, + lineEnd: function() { + this.stream.lineEnd(); + }, + polygonStart: function() { + this.stream.polygonStart(); + }, + polygonEnd: function() { + this.stream.polygonEnd(); + } + }; + function d3_geo_transformPoint(stream, point) { + return { + point: point, + sphere: function() { + stream.sphere(); + }, + lineStart: function() { + stream.lineStart(); + }, + lineEnd: function() { + stream.lineEnd(); + }, + polygonStart: function() { + stream.polygonStart(); + }, + polygonEnd: function() { + stream.polygonEnd(); + } + }; + } + d3.geo.projection = d3_geo_projection; + d3.geo.projectionMutator = d3_geo_projectionMutator; + function d3_geo_projection(project) { + return d3_geo_projectionMutator(function() { + return project; + })(); + } + function d3_geo_projectionMutator(projectAt) { + var project, rotate, projectRotate, projectResample = d3_geo_resample(function(x, y) { + x = project(x, y); + return [ x[0] * k + δx, δy - x[1] * k ]; + }), k = 150, x = 480, y = 250, λ = 0, φ = 0, δλ = 0, δφ = 0, δγ = 0, δx, δy, preclip = d3_geo_clipAntimeridian, postclip = d3_identity, clipAngle = null, clipExtent = null, stream; + function projection(point) { + point = projectRotate(point[0] * d3_radians, point[1] * d3_radians); + return [ point[0] * k + δx, δy - point[1] * k ]; + } + function invert(point) { + point = projectRotate.invert((point[0] - δx) / k, (δy - point[1]) / k); + return point && [ point[0] * d3_degrees, point[1] * d3_degrees ]; + } + projection.stream = function(output) { + if (stream) stream.valid = false; + stream = d3_geo_projectionRadians(preclip(rotate, projectResample(postclip(output)))); + stream.valid = true; + return stream; + }; + projection.clipAngle = function(_) { + if (!arguments.length) return clipAngle; + preclip = _ == null ? (clipAngle = _, d3_geo_clipAntimeridian) : d3_geo_clipCircle((clipAngle = +_) * d3_radians); + return invalidate(); + }; + projection.clipExtent = function(_) { + if (!arguments.length) return clipExtent; + clipExtent = _; + postclip = _ ? d3_geo_clipExtent(_[0][0], _[0][1], _[1][0], _[1][1]) : d3_identity; + return invalidate(); + }; + projection.scale = function(_) { + if (!arguments.length) return k; + k = +_; + return reset(); + }; + projection.translate = function(_) { + if (!arguments.length) return [ x, y ]; + x = +_[0]; + y = +_[1]; + return reset(); + }; + projection.center = function(_) { + if (!arguments.length) return [ λ * d3_degrees, φ * d3_degrees ]; + λ = _[0] % 360 * d3_radians; + φ = _[1] % 360 * d3_radians; + return reset(); + }; + projection.rotate = function(_) { + if (!arguments.length) return [ δλ * d3_degrees, δφ * d3_degrees, δγ * d3_degrees ]; + δλ = _[0] % 360 * d3_radians; + δφ = _[1] % 360 * d3_radians; + δγ = _.length > 2 ? _[2] % 360 * d3_radians : 0; + return reset(); + }; + d3.rebind(projection, projectResample, "precision"); + function reset() { + projectRotate = d3_geo_compose(rotate = d3_geo_rotation(δλ, δφ, δγ), project); + var center = project(λ, φ); + δx = x - center[0] * k; + δy = y + center[1] * k; + return invalidate(); + } + function invalidate() { + if (stream) stream.valid = false, stream = null; + return projection; + } + return function() { + project = projectAt.apply(this, arguments); + projection.invert = project.invert && invert; + return reset(); + }; + } + function d3_geo_projectionRadians(stream) { + return d3_geo_transformPoint(stream, function(x, y) { + stream.point(x * d3_radians, y * d3_radians); + }); + } + function d3_geo_equirectangular(λ, φ) { + return [ λ, φ ]; + } + (d3.geo.equirectangular = function() { + return d3_geo_projection(d3_geo_equirectangular); + }).raw = d3_geo_equirectangular.invert = d3_geo_equirectangular; + d3.geo.rotation = function(rotate) { + rotate = d3_geo_rotation(rotate[0] % 360 * d3_radians, rotate[1] * d3_radians, rotate.length > 2 ? rotate[2] * d3_radians : 0); + function forward(coordinates) { + coordinates = rotate(coordinates[0] * d3_radians, coordinates[1] * d3_radians); + return coordinates[0] *= d3_degrees, coordinates[1] *= d3_degrees, coordinates; + } + forward.invert = function(coordinates) { + coordinates = rotate.invert(coordinates[0] * d3_radians, coordinates[1] * d3_radians); + return coordinates[0] *= d3_degrees, coordinates[1] *= d3_degrees, coordinates; + }; + return forward; + }; + function d3_geo_identityRotation(λ, φ) { + return [ λ > π ? λ - τ : λ < -π ? λ + τ : λ, φ ]; + } + d3_geo_identityRotation.invert = d3_geo_equirectangular; + function d3_geo_rotation(δλ, δφ, δγ) { + return δλ ? δφ || δγ ? d3_geo_compose(d3_geo_rotationλ(δλ), d3_geo_rotationφγ(δφ, δγ)) : d3_geo_rotationλ(δλ) : δφ || δγ ? d3_geo_rotationφγ(δφ, δγ) : d3_geo_identityRotation; + } + function d3_geo_forwardRotationλ(δλ) { + return function(λ, φ) { + return λ += δλ, [ λ > π ? λ - τ : λ < -π ? λ + τ : λ, φ ]; + }; + } + function d3_geo_rotationλ(δλ) { + var rotation = d3_geo_forwardRotationλ(δλ); + rotation.invert = d3_geo_forwardRotationλ(-δλ); + return rotation; + } + function d3_geo_rotationφγ(δφ, δγ) { + var cosδφ = Math.cos(δφ), sinδφ = Math.sin(δφ), cosδγ = Math.cos(δγ), sinδγ = Math.sin(δγ); + function rotation(λ, φ) { + var cosφ = Math.cos(φ), x = Math.cos(λ) * cosφ, y = Math.sin(λ) * cosφ, z = Math.sin(φ), k = z * cosδφ + x * sinδφ; + return [ Math.atan2(y * cosδγ - k * sinδγ, x * cosδφ - z * sinδφ), d3_asin(k * cosδγ + y * sinδγ) ]; + } + rotation.invert = function(λ, φ) { + var cosφ = Math.cos(φ), x = Math.cos(λ) * cosφ, y = Math.sin(λ) * cosφ, z = Math.sin(φ), k = z * cosδγ - y * sinδγ; + return [ Math.atan2(y * cosδγ + z * sinδγ, x * cosδφ + k * sinδφ), d3_asin(k * cosδφ - x * sinδφ) ]; + }; + return rotation; + } + d3.geo.circle = function() { + var origin = [ 0, 0 ], angle, precision = 6, interpolate; + function circle() { + var center = typeof origin === "function" ? origin.apply(this, arguments) : origin, rotate = d3_geo_rotation(-center[0] * d3_radians, -center[1] * d3_radians, 0).invert, ring = []; + interpolate(null, null, 1, { + point: function(x, y) { + ring.push(x = rotate(x, y)); + x[0] *= d3_degrees, x[1] *= d3_degrees; + } + }); + return { + type: "Polygon", + coordinates: [ ring ] + }; + } + circle.origin = function(x) { + if (!arguments.length) return origin; + origin = x; + return circle; + }; + circle.angle = function(x) { + if (!arguments.length) return angle; + interpolate = d3_geo_circleInterpolate((angle = +x) * d3_radians, precision * d3_radians); + return circle; + }; + circle.precision = function(_) { + if (!arguments.length) return precision; + interpolate = d3_geo_circleInterpolate(angle * d3_radians, (precision = +_) * d3_radians); + return circle; + }; + return circle.angle(90); + }; + function d3_geo_circleInterpolate(radius, precision) { + var cr = Math.cos(radius), sr = Math.sin(radius); + return function(from, to, direction, listener) { + var step = direction * precision; + if (from != null) { + from = d3_geo_circleAngle(cr, from); + to = d3_geo_circleAngle(cr, to); + if (direction > 0 ? from < to : from > to) from += direction * τ; + } else { + from = radius + direction * τ; + to = radius - .5 * step; + } + for (var point, t = from; direction > 0 ? t > to : t < to; t -= step) { + listener.point((point = d3_geo_spherical([ cr, -sr * Math.cos(t), -sr * Math.sin(t) ]))[0], point[1]); + } + }; + } + function d3_geo_circleAngle(cr, point) { + var a = d3_geo_cartesian(point); + a[0] -= cr; + d3_geo_cartesianNormalize(a); + var angle = d3_acos(-a[1]); + return ((-a[2] < 0 ? -angle : angle) + 2 * Math.PI - ε) % (2 * Math.PI); + } + d3.geo.distance = function(a, b) { + var Δλ = (b[0] - a[0]) * d3_radians, φ0 = a[1] * d3_radians, φ1 = b[1] * d3_radians, sinΔλ = Math.sin(Δλ), cosΔλ = Math.cos(Δλ), sinφ0 = Math.sin(φ0), cosφ0 = Math.cos(φ0), sinφ1 = Math.sin(φ1), cosφ1 = Math.cos(φ1), t; + return Math.atan2(Math.sqrt((t = cosφ1 * sinΔλ) * t + (t = cosφ0 * sinφ1 - sinφ0 * cosφ1 * cosΔλ) * t), sinφ0 * sinφ1 + cosφ0 * cosφ1 * cosΔλ); + }; + d3.geo.graticule = function() { + var x1, x0, X1, X0, y1, y0, Y1, Y0, dx = 10, dy = dx, DX = 90, DY = 360, x, y, X, Y, precision = 2.5; + function graticule() { + return { + type: "MultiLineString", + coordinates: lines() + }; + } + function lines() { + return d3.range(Math.ceil(X0 / DX) * DX, X1, DX).map(X).concat(d3.range(Math.ceil(Y0 / DY) * DY, Y1, DY).map(Y)).concat(d3.range(Math.ceil(x0 / dx) * dx, x1, dx).filter(function(x) { + return abs(x % DX) > ε; + }).map(x)).concat(d3.range(Math.ceil(y0 / dy) * dy, y1, dy).filter(function(y) { + return abs(y % DY) > ε; + }).map(y)); + } + graticule.lines = function() { + return lines().map(function(coordinates) { + return { + type: "LineString", + coordinates: coordinates + }; + }); + }; + graticule.outline = function() { + return { + type: "Polygon", + coordinates: [ X(X0).concat(Y(Y1).slice(1), X(X1).reverse().slice(1), Y(Y0).reverse().slice(1)) ] + }; + }; + graticule.extent = function(_) { + if (!arguments.length) return graticule.minorExtent(); + return graticule.majorExtent(_).minorExtent(_); + }; + graticule.majorExtent = function(_) { + if (!arguments.length) return [ [ X0, Y0 ], [ X1, Y1 ] ]; + X0 = +_[0][0], X1 = +_[1][0]; + Y0 = +_[0][1], Y1 = +_[1][1]; + if (X0 > X1) _ = X0, X0 = X1, X1 = _; + if (Y0 > Y1) _ = Y0, Y0 = Y1, Y1 = _; + return graticule.precision(precision); + }; + graticule.minorExtent = function(_) { + if (!arguments.length) return [ [ x0, y0 ], [ x1, y1 ] ]; + x0 = +_[0][0], x1 = +_[1][0]; + y0 = +_[0][1], y1 = +_[1][1]; + if (x0 > x1) _ = x0, x0 = x1, x1 = _; + if (y0 > y1) _ = y0, y0 = y1, y1 = _; + return graticule.precision(precision); + }; + graticule.step = function(_) { + if (!arguments.length) return graticule.minorStep(); + return graticule.majorStep(_).minorStep(_); + }; + graticule.majorStep = function(_) { + if (!arguments.length) return [ DX, DY ]; + DX = +_[0], DY = +_[1]; + return graticule; + }; + graticule.minorStep = function(_) { + if (!arguments.length) return [ dx, dy ]; + dx = +_[0], dy = +_[1]; + return graticule; + }; + graticule.precision = function(_) { + if (!arguments.length) return precision; + precision = +_; + x = d3_geo_graticuleX(y0, y1, 90); + y = d3_geo_graticuleY(x0, x1, precision); + X = d3_geo_graticuleX(Y0, Y1, 90); + Y = d3_geo_graticuleY(X0, X1, precision); + return graticule; + }; + return graticule.majorExtent([ [ -180, -90 + ε ], [ 180, 90 - ε ] ]).minorExtent([ [ -180, -80 - ε ], [ 180, 80 + ε ] ]); + }; + function d3_geo_graticuleX(y0, y1, dy) { + var y = d3.range(y0, y1 - ε, dy).concat(y1); + return function(x) { + return y.map(function(y) { + return [ x, y ]; + }); + }; + } + function d3_geo_graticuleY(x0, x1, dx) { + var x = d3.range(x0, x1 - ε, dx).concat(x1); + return function(y) { + return x.map(function(x) { + return [ x, y ]; + }); + }; + } + function d3_source(d) { + return d.source; + } + function d3_target(d) { + return d.target; + } + d3.geo.greatArc = function() { + var source = d3_source, source_, target = d3_target, target_; + function greatArc() { + return { + type: "LineString", + coordinates: [ source_ || source.apply(this, arguments), target_ || target.apply(this, arguments) ] + }; + } + greatArc.distance = function() { + return d3.geo.distance(source_ || source.apply(this, arguments), target_ || target.apply(this, arguments)); + }; + greatArc.source = function(_) { + if (!arguments.length) return source; + source = _, source_ = typeof _ === "function" ? null : _; + return greatArc; + }; + greatArc.target = function(_) { + if (!arguments.length) return target; + target = _, target_ = typeof _ === "function" ? null : _; + return greatArc; + }; + greatArc.precision = function() { + return arguments.length ? greatArc : 0; + }; + return greatArc; + }; + d3.geo.interpolate = function(source, target) { + return d3_geo_interpolate(source[0] * d3_radians, source[1] * d3_radians, target[0] * d3_radians, target[1] * d3_radians); + }; + function d3_geo_interpolate(x0, y0, x1, y1) { + var cy0 = Math.cos(y0), sy0 = Math.sin(y0), cy1 = Math.cos(y1), sy1 = Math.sin(y1), kx0 = cy0 * Math.cos(x0), ky0 = cy0 * Math.sin(x0), kx1 = cy1 * Math.cos(x1), ky1 = cy1 * Math.sin(x1), d = 2 * Math.asin(Math.sqrt(d3_haversin(y1 - y0) + cy0 * cy1 * d3_haversin(x1 - x0))), k = 1 / Math.sin(d); + var interpolate = d ? function(t) { + var B = Math.sin(t *= d) * k, A = Math.sin(d - t) * k, x = A * kx0 + B * kx1, y = A * ky0 + B * ky1, z = A * sy0 + B * sy1; + return [ Math.atan2(y, x) * d3_degrees, Math.atan2(z, Math.sqrt(x * x + y * y)) * d3_degrees ]; + } : function() { + return [ x0 * d3_degrees, y0 * d3_degrees ]; + }; + interpolate.distance = d; + return interpolate; + } + d3.geo.length = function(object) { + d3_geo_lengthSum = 0; + d3.geo.stream(object, d3_geo_length); + return d3_geo_lengthSum; + }; + var d3_geo_lengthSum; + var d3_geo_length = { + sphere: d3_noop, + point: d3_noop, + lineStart: d3_geo_lengthLineStart, + lineEnd: d3_noop, + polygonStart: d3_noop, + polygonEnd: d3_noop + }; + function d3_geo_lengthLineStart() { + var λ0, sinφ0, cosφ0; + d3_geo_length.point = function(λ, φ) { + λ0 = λ * d3_radians, sinφ0 = Math.sin(φ *= d3_radians), cosφ0 = Math.cos(φ); + d3_geo_length.point = nextPoint; + }; + d3_geo_length.lineEnd = function() { + d3_geo_length.point = d3_geo_length.lineEnd = d3_noop; + }; + function nextPoint(λ, φ) { + var sinφ = Math.sin(φ *= d3_radians), cosφ = Math.cos(φ), t = abs((λ *= d3_radians) - λ0), cosΔλ = Math.cos(t); + d3_geo_lengthSum += Math.atan2(Math.sqrt((t = cosφ * Math.sin(t)) * t + (t = cosφ0 * sinφ - sinφ0 * cosφ * cosΔλ) * t), sinφ0 * sinφ + cosφ0 * cosφ * cosΔλ); + λ0 = λ, sinφ0 = sinφ, cosφ0 = cosφ; + } + } + function d3_geo_azimuthal(scale, angle) { + function azimuthal(λ, φ) { + var cosλ = Math.cos(λ), cosφ = Math.cos(φ), k = scale(cosλ * cosφ); + return [ k * cosφ * Math.sin(λ), k * Math.sin(φ) ]; + } + azimuthal.invert = function(x, y) { + var ρ = Math.sqrt(x * x + y * y), c = angle(ρ), sinc = Math.sin(c), cosc = Math.cos(c); + return [ Math.atan2(x * sinc, ρ * cosc), Math.asin(ρ && y * sinc / ρ) ]; + }; + return azimuthal; + } + var d3_geo_azimuthalEqualArea = d3_geo_azimuthal(function(cosλcosφ) { + return Math.sqrt(2 / (1 + cosλcosφ)); + }, function(ρ) { + return 2 * Math.asin(ρ / 2); + }); + (d3.geo.azimuthalEqualArea = function() { + return d3_geo_projection(d3_geo_azimuthalEqualArea); + }).raw = d3_geo_azimuthalEqualArea; + var d3_geo_azimuthalEquidistant = d3_geo_azimuthal(function(cosλcosφ) { + var c = Math.acos(cosλcosφ); + return c && c / Math.sin(c); + }, d3_identity); + (d3.geo.azimuthalEquidistant = function() { + return d3_geo_projection(d3_geo_azimuthalEquidistant); + }).raw = d3_geo_azimuthalEquidistant; + function d3_geo_conicConformal(φ0, φ1) { + var cosφ0 = Math.cos(φ0), t = function(φ) { + return Math.tan(π / 4 + φ / 2); + }, n = φ0 === φ1 ? Math.sin(φ0) : Math.log(cosφ0 / Math.cos(φ1)) / Math.log(t(φ1) / t(φ0)), F = cosφ0 * Math.pow(t(φ0), n) / n; + if (!n) return d3_geo_mercator; + function forward(λ, φ) { + if (F > 0) { + if (φ < -halfπ + ε) φ = -halfπ + ε; + } else { + if (φ > halfπ - ε) φ = halfπ - ε; + } + var ρ = F / Math.pow(t(φ), n); + return [ ρ * Math.sin(n * λ), F - ρ * Math.cos(n * λ) ]; + } + forward.invert = function(x, y) { + var ρ0_y = F - y, ρ = d3_sgn(n) * Math.sqrt(x * x + ρ0_y * ρ0_y); + return [ Math.atan2(x, ρ0_y) / n, 2 * Math.atan(Math.pow(F / ρ, 1 / n)) - halfπ ]; + }; + return forward; + } + (d3.geo.conicConformal = function() { + return d3_geo_conic(d3_geo_conicConformal); + }).raw = d3_geo_conicConformal; + function d3_geo_conicEquidistant(φ0, φ1) { + var cosφ0 = Math.cos(φ0), n = φ0 === φ1 ? Math.sin(φ0) : (cosφ0 - Math.cos(φ1)) / (φ1 - φ0), G = cosφ0 / n + φ0; + if (abs(n) < ε) return d3_geo_equirectangular; + function forward(λ, φ) { + var ρ = G - φ; + return [ ρ * Math.sin(n * λ), G - ρ * Math.cos(n * λ) ]; + } + forward.invert = function(x, y) { + var ρ0_y = G - y; + return [ Math.atan2(x, ρ0_y) / n, G - d3_sgn(n) * Math.sqrt(x * x + ρ0_y * ρ0_y) ]; + }; + return forward; + } + (d3.geo.conicEquidistant = function() { + return d3_geo_conic(d3_geo_conicEquidistant); + }).raw = d3_geo_conicEquidistant; + var d3_geo_gnomonic = d3_geo_azimuthal(function(cosλcosφ) { + return 1 / cosλcosφ; + }, Math.atan); + (d3.geo.gnomonic = function() { + return d3_geo_projection(d3_geo_gnomonic); + }).raw = d3_geo_gnomonic; + function d3_geo_mercator(λ, φ) { + return [ λ, Math.log(Math.tan(π / 4 + φ / 2)) ]; + } + d3_geo_mercator.invert = function(x, y) { + return [ x, 2 * Math.atan(Math.exp(y)) - halfπ ]; + }; + function d3_geo_mercatorProjection(project) { + var m = d3_geo_projection(project), scale = m.scale, translate = m.translate, clipExtent = m.clipExtent, clipAuto; + m.scale = function() { + var v = scale.apply(m, arguments); + return v === m ? clipAuto ? m.clipExtent(null) : m : v; + }; + m.translate = function() { + var v = translate.apply(m, arguments); + return v === m ? clipAuto ? m.clipExtent(null) : m : v; + }; + m.clipExtent = function(_) { + var v = clipExtent.apply(m, arguments); + if (v === m) { + if (clipAuto = _ == null) { + var k = π * scale(), t = translate(); + clipExtent([ [ t[0] - k, t[1] - k ], [ t[0] + k, t[1] + k ] ]); + } + } else if (clipAuto) { + v = null; + } + return v; + }; + return m.clipExtent(null); + } + (d3.geo.mercator = function() { + return d3_geo_mercatorProjection(d3_geo_mercator); + }).raw = d3_geo_mercator; + var d3_geo_orthographic = d3_geo_azimuthal(function() { + return 1; + }, Math.asin); + (d3.geo.orthographic = function() { + return d3_geo_projection(d3_geo_orthographic); + }).raw = d3_geo_orthographic; + var d3_geo_stereographic = d3_geo_azimuthal(function(cosλcosφ) { + return 1 / (1 + cosλcosφ); + }, function(ρ) { + return 2 * Math.atan(ρ); + }); + (d3.geo.stereographic = function() { + return d3_geo_projection(d3_geo_stereographic); + }).raw = d3_geo_stereographic; + function d3_geo_transverseMercator(λ, φ) { + return [ Math.log(Math.tan(π / 4 + φ / 2)), -λ ]; + } + d3_geo_transverseMercator.invert = function(x, y) { + return [ -y, 2 * Math.atan(Math.exp(x)) - halfπ ]; + }; + (d3.geo.transverseMercator = function() { + var projection = d3_geo_mercatorProjection(d3_geo_transverseMercator), center = projection.center, rotate = projection.rotate; + projection.center = function(_) { + return _ ? center([ -_[1], _[0] ]) : (_ = center(), [ _[1], -_[0] ]); + }; + projection.rotate = function(_) { + return _ ? rotate([ _[0], _[1], _.length > 2 ? _[2] + 90 : 90 ]) : (_ = rotate(), + [ _[0], _[1], _[2] - 90 ]); + }; + return rotate([ 0, 0, 90 ]); + }).raw = d3_geo_transverseMercator; + d3.geom = {}; + function d3_geom_pointX(d) { + return d[0]; + } + function d3_geom_pointY(d) { + return d[1]; + } + d3.geom.hull = function(vertices) { + var x = d3_geom_pointX, y = d3_geom_pointY; + if (arguments.length) return hull(vertices); + function hull(data) { + if (data.length < 3) return []; + var fx = d3_functor(x), fy = d3_functor(y), i, n = data.length, points = [], flippedPoints = []; + for (i = 0; i < n; i++) { + points.push([ +fx.call(this, data[i], i), +fy.call(this, data[i], i), i ]); + } + points.sort(d3_geom_hullOrder); + for (i = 0; i < n; i++) flippedPoints.push([ points[i][0], -points[i][1] ]); + var upper = d3_geom_hullUpper(points), lower = d3_geom_hullUpper(flippedPoints); + var skipLeft = lower[0] === upper[0], skipRight = lower[lower.length - 1] === upper[upper.length - 1], polygon = []; + for (i = upper.length - 1; i >= 0; --i) polygon.push(data[points[upper[i]][2]]); + for (i = +skipLeft; i < lower.length - skipRight; ++i) polygon.push(data[points[lower[i]][2]]); + return polygon; + } + hull.x = function(_) { + return arguments.length ? (x = _, hull) : x; + }; + hull.y = function(_) { + return arguments.length ? (y = _, hull) : y; + }; + return hull; + }; + function d3_geom_hullUpper(points) { + var n = points.length, hull = [ 0, 1 ], hs = 2; + for (var i = 2; i < n; i++) { + while (hs > 1 && d3_cross2d(points[hull[hs - 2]], points[hull[hs - 1]], points[i]) <= 0) --hs; + hull[hs++] = i; + } + return hull.slice(0, hs); + } + function d3_geom_hullOrder(a, b) { + return a[0] - b[0] || a[1] - b[1]; + } + d3.geom.polygon = function(coordinates) { + d3_subclass(coordinates, d3_geom_polygonPrototype); + return coordinates; + }; + var d3_geom_polygonPrototype = d3.geom.polygon.prototype = []; + d3_geom_polygonPrototype.area = function() { + var i = -1, n = this.length, a, b = this[n - 1], area = 0; + while (++i < n) { + a = b; + b = this[i]; + area += a[1] * b[0] - a[0] * b[1]; + } + return area * .5; + }; + d3_geom_polygonPrototype.centroid = function(k) { + var i = -1, n = this.length, x = 0, y = 0, a, b = this[n - 1], c; + if (!arguments.length) k = -1 / (6 * this.area()); + while (++i < n) { + a = b; + b = this[i]; + c = a[0] * b[1] - b[0] * a[1]; + x += (a[0] + b[0]) * c; + y += (a[1] + b[1]) * c; + } + return [ x * k, y * k ]; + }; + d3_geom_polygonPrototype.clip = function(subject) { + var input, closed = d3_geom_polygonClosed(subject), i = -1, n = this.length - d3_geom_polygonClosed(this), j, m, a = this[n - 1], b, c, d; + while (++i < n) { + input = subject.slice(); + subject.length = 0; + b = this[i]; + c = input[(m = input.length - closed) - 1]; + j = -1; + while (++j < m) { + d = input[j]; + if (d3_geom_polygonInside(d, a, b)) { + if (!d3_geom_polygonInside(c, a, b)) { + subject.push(d3_geom_polygonIntersect(c, d, a, b)); + } + subject.push(d); + } else if (d3_geom_polygonInside(c, a, b)) { + subject.push(d3_geom_polygonIntersect(c, d, a, b)); + } + c = d; + } + if (closed) subject.push(subject[0]); + a = b; + } + return subject; + }; + function d3_geom_polygonInside(p, a, b) { + return (b[0] - a[0]) * (p[1] - a[1]) < (b[1] - a[1]) * (p[0] - a[0]); + } + function d3_geom_polygonIntersect(c, d, a, b) { + var x1 = c[0], x3 = a[0], x21 = d[0] - x1, x43 = b[0] - x3, y1 = c[1], y3 = a[1], y21 = d[1] - y1, y43 = b[1] - y3, ua = (x43 * (y1 - y3) - y43 * (x1 - x3)) / (y43 * x21 - x43 * y21); + return [ x1 + ua * x21, y1 + ua * y21 ]; + } + function d3_geom_polygonClosed(coordinates) { + var a = coordinates[0], b = coordinates[coordinates.length - 1]; + return !(a[0] - b[0] || a[1] - b[1]); + } + var d3_geom_voronoiEdges, d3_geom_voronoiCells, d3_geom_voronoiBeaches, d3_geom_voronoiBeachPool = [], d3_geom_voronoiFirstCircle, d3_geom_voronoiCircles, d3_geom_voronoiCirclePool = []; + function d3_geom_voronoiBeach() { + d3_geom_voronoiRedBlackNode(this); + this.edge = this.site = this.circle = null; + } + function d3_geom_voronoiCreateBeach(site) { + var beach = d3_geom_voronoiBeachPool.pop() || new d3_geom_voronoiBeach(); + beach.site = site; + return beach; + } + function d3_geom_voronoiDetachBeach(beach) { + d3_geom_voronoiDetachCircle(beach); + d3_geom_voronoiBeaches.remove(beach); + d3_geom_voronoiBeachPool.push(beach); + d3_geom_voronoiRedBlackNode(beach); + } + function d3_geom_voronoiRemoveBeach(beach) { + var circle = beach.circle, x = circle.x, y = circle.cy, vertex = { + x: x, + y: y + }, previous = beach.P, next = beach.N, disappearing = [ beach ]; + d3_geom_voronoiDetachBeach(beach); + var lArc = previous; + while (lArc.circle && abs(x - lArc.circle.x) < ε && abs(y - lArc.circle.cy) < ε) { + previous = lArc.P; + disappearing.unshift(lArc); + d3_geom_voronoiDetachBeach(lArc); + lArc = previous; + } + disappearing.unshift(lArc); + d3_geom_voronoiDetachCircle(lArc); + var rArc = next; + while (rArc.circle && abs(x - rArc.circle.x) < ε && abs(y - rArc.circle.cy) < ε) { + next = rArc.N; + disappearing.push(rArc); + d3_geom_voronoiDetachBeach(rArc); + rArc = next; + } + disappearing.push(rArc); + d3_geom_voronoiDetachCircle(rArc); + var nArcs = disappearing.length, iArc; + for (iArc = 1; iArc < nArcs; ++iArc) { + rArc = disappearing[iArc]; + lArc = disappearing[iArc - 1]; + d3_geom_voronoiSetEdgeEnd(rArc.edge, lArc.site, rArc.site, vertex); + } + lArc = disappearing[0]; + rArc = disappearing[nArcs - 1]; + rArc.edge = d3_geom_voronoiCreateEdge(lArc.site, rArc.site, null, vertex); + d3_geom_voronoiAttachCircle(lArc); + d3_geom_voronoiAttachCircle(rArc); + } + function d3_geom_voronoiAddBeach(site) { + var x = site.x, directrix = site.y, lArc, rArc, dxl, dxr, node = d3_geom_voronoiBeaches._; + while (node) { + dxl = d3_geom_voronoiLeftBreakPoint(node, directrix) - x; + if (dxl > ε) node = node.L; else { + dxr = x - d3_geom_voronoiRightBreakPoint(node, directrix); + if (dxr > ε) { + if (!node.R) { + lArc = node; + break; + } + node = node.R; + } else { + if (dxl > -ε) { + lArc = node.P; + rArc = node; + } else if (dxr > -ε) { + lArc = node; + rArc = node.N; + } else { + lArc = rArc = node; + } + break; + } + } + } + var newArc = d3_geom_voronoiCreateBeach(site); + d3_geom_voronoiBeaches.insert(lArc, newArc); + if (!lArc && !rArc) return; + if (lArc === rArc) { + d3_geom_voronoiDetachCircle(lArc); + rArc = d3_geom_voronoiCreateBeach(lArc.site); + d3_geom_voronoiBeaches.insert(newArc, rArc); + newArc.edge = rArc.edge = d3_geom_voronoiCreateEdge(lArc.site, newArc.site); + d3_geom_voronoiAttachCircle(lArc); + d3_geom_voronoiAttachCircle(rArc); + return; + } + if (!rArc) { + newArc.edge = d3_geom_voronoiCreateEdge(lArc.site, newArc.site); + return; + } + d3_geom_voronoiDetachCircle(lArc); + d3_geom_voronoiDetachCircle(rArc); + var lSite = lArc.site, ax = lSite.x, ay = lSite.y, bx = site.x - ax, by = site.y - ay, rSite = rArc.site, cx = rSite.x - ax, cy = rSite.y - ay, d = 2 * (bx * cy - by * cx), hb = bx * bx + by * by, hc = cx * cx + cy * cy, vertex = { + x: (cy * hb - by * hc) / d + ax, + y: (bx * hc - cx * hb) / d + ay + }; + d3_geom_voronoiSetEdgeEnd(rArc.edge, lSite, rSite, vertex); + newArc.edge = d3_geom_voronoiCreateEdge(lSite, site, null, vertex); + rArc.edge = d3_geom_voronoiCreateEdge(site, rSite, null, vertex); + d3_geom_voronoiAttachCircle(lArc); + d3_geom_voronoiAttachCircle(rArc); + } + function d3_geom_voronoiLeftBreakPoint(arc, directrix) { + var site = arc.site, rfocx = site.x, rfocy = site.y, pby2 = rfocy - directrix; + if (!pby2) return rfocx; + var lArc = arc.P; + if (!lArc) return -Infinity; + site = lArc.site; + var lfocx = site.x, lfocy = site.y, plby2 = lfocy - directrix; + if (!plby2) return lfocx; + var hl = lfocx - rfocx, aby2 = 1 / pby2 - 1 / plby2, b = hl / plby2; + if (aby2) return (-b + Math.sqrt(b * b - 2 * aby2 * (hl * hl / (-2 * plby2) - lfocy + plby2 / 2 + rfocy - pby2 / 2))) / aby2 + rfocx; + return (rfocx + lfocx) / 2; + } + function d3_geom_voronoiRightBreakPoint(arc, directrix) { + var rArc = arc.N; + if (rArc) return d3_geom_voronoiLeftBreakPoint(rArc, directrix); + var site = arc.site; + return site.y === directrix ? site.x : Infinity; + } + function d3_geom_voronoiCell(site) { + this.site = site; + this.edges = []; + } + d3_geom_voronoiCell.prototype.prepare = function() { + var halfEdges = this.edges, iHalfEdge = halfEdges.length, edge; + while (iHalfEdge--) { + edge = halfEdges[iHalfEdge].edge; + if (!edge.b || !edge.a) halfEdges.splice(iHalfEdge, 1); + } + halfEdges.sort(d3_geom_voronoiHalfEdgeOrder); + return halfEdges.length; + }; + function d3_geom_voronoiCloseCells(extent) { + var x0 = extent[0][0], x1 = extent[1][0], y0 = extent[0][1], y1 = extent[1][1], x2, y2, x3, y3, cells = d3_geom_voronoiCells, iCell = cells.length, cell, iHalfEdge, halfEdges, nHalfEdges, start, end; + while (iCell--) { + cell = cells[iCell]; + if (!cell || !cell.prepare()) continue; + halfEdges = cell.edges; + nHalfEdges = halfEdges.length; + iHalfEdge = 0; + while (iHalfEdge < nHalfEdges) { + end = halfEdges[iHalfEdge].end(), x3 = end.x, y3 = end.y; + start = halfEdges[++iHalfEdge % nHalfEdges].start(), x2 = start.x, y2 = start.y; + if (abs(x3 - x2) > ε || abs(y3 - y2) > ε) { + halfEdges.splice(iHalfEdge, 0, new d3_geom_voronoiHalfEdge(d3_geom_voronoiCreateBorderEdge(cell.site, end, abs(x3 - x0) < ε && y1 - y3 > ε ? { + x: x0, + y: abs(x2 - x0) < ε ? y2 : y1 + } : abs(y3 - y1) < ε && x1 - x3 > ε ? { + x: abs(y2 - y1) < ε ? x2 : x1, + y: y1 + } : abs(x3 - x1) < ε && y3 - y0 > ε ? { + x: x1, + y: abs(x2 - x1) < ε ? y2 : y0 + } : abs(y3 - y0) < ε && x3 - x0 > ε ? { + x: abs(y2 - y0) < ε ? x2 : x0, + y: y0 + } : null), cell.site, null)); + ++nHalfEdges; + } + } + } + } + function d3_geom_voronoiHalfEdgeOrder(a, b) { + return b.angle - a.angle; + } + function d3_geom_voronoiCircle() { + d3_geom_voronoiRedBlackNode(this); + this.x = this.y = this.arc = this.site = this.cy = null; + } + function d3_geom_voronoiAttachCircle(arc) { + var lArc = arc.P, rArc = arc.N; + if (!lArc || !rArc) return; + var lSite = lArc.site, cSite = arc.site, rSite = rArc.site; + if (lSite === rSite) return; + var bx = cSite.x, by = cSite.y, ax = lSite.x - bx, ay = lSite.y - by, cx = rSite.x - bx, cy = rSite.y - by; + var d = 2 * (ax * cy - ay * cx); + if (d >= -ε2) return; + var ha = ax * ax + ay * ay, hc = cx * cx + cy * cy, x = (cy * ha - ay * hc) / d, y = (ax * hc - cx * ha) / d, cy = y + by; + var circle = d3_geom_voronoiCirclePool.pop() || new d3_geom_voronoiCircle(); + circle.arc = arc; + circle.site = cSite; + circle.x = x + bx; + circle.y = cy + Math.sqrt(x * x + y * y); + circle.cy = cy; + arc.circle = circle; + var before = null, node = d3_geom_voronoiCircles._; + while (node) { + if (circle.y < node.y || circle.y === node.y && circle.x <= node.x) { + if (node.L) node = node.L; else { + before = node.P; + break; + } + } else { + if (node.R) node = node.R; else { + before = node; + break; + } + } + } + d3_geom_voronoiCircles.insert(before, circle); + if (!before) d3_geom_voronoiFirstCircle = circle; + } + function d3_geom_voronoiDetachCircle(arc) { + var circle = arc.circle; + if (circle) { + if (!circle.P) d3_geom_voronoiFirstCircle = circle.N; + d3_geom_voronoiCircles.remove(circle); + d3_geom_voronoiCirclePool.push(circle); + d3_geom_voronoiRedBlackNode(circle); + arc.circle = null; + } + } + function d3_geom_voronoiClipEdges(extent) { + var edges = d3_geom_voronoiEdges, clip = d3_geom_clipLine(extent[0][0], extent[0][1], extent[1][0], extent[1][1]), i = edges.length, e; + while (i--) { + e = edges[i]; + if (!d3_geom_voronoiConnectEdge(e, extent) || !clip(e) || abs(e.a.x - e.b.x) < ε && abs(e.a.y - e.b.y) < ε) { + e.a = e.b = null; + edges.splice(i, 1); + } + } + } + function d3_geom_voronoiConnectEdge(edge, extent) { + var vb = edge.b; + if (vb) return true; + var va = edge.a, x0 = extent[0][0], x1 = extent[1][0], y0 = extent[0][1], y1 = extent[1][1], lSite = edge.l, rSite = edge.r, lx = lSite.x, ly = lSite.y, rx = rSite.x, ry = rSite.y, fx = (lx + rx) / 2, fy = (ly + ry) / 2, fm, fb; + if (ry === ly) { + if (fx < x0 || fx >= x1) return; + if (lx > rx) { + if (!va) va = { + x: fx, + y: y0 + }; else if (va.y >= y1) return; + vb = { + x: fx, + y: y1 + }; + } else { + if (!va) va = { + x: fx, + y: y1 + }; else if (va.y < y0) return; + vb = { + x: fx, + y: y0 + }; + } + } else { + fm = (lx - rx) / (ry - ly); + fb = fy - fm * fx; + if (fm < -1 || fm > 1) { + if (lx > rx) { + if (!va) va = { + x: (y0 - fb) / fm, + y: y0 + }; else if (va.y >= y1) return; + vb = { + x: (y1 - fb) / fm, + y: y1 + }; + } else { + if (!va) va = { + x: (y1 - fb) / fm, + y: y1 + }; else if (va.y < y0) return; + vb = { + x: (y0 - fb) / fm, + y: y0 + }; + } + } else { + if (ly < ry) { + if (!va) va = { + x: x0, + y: fm * x0 + fb + }; else if (va.x >= x1) return; + vb = { + x: x1, + y: fm * x1 + fb + }; + } else { + if (!va) va = { + x: x1, + y: fm * x1 + fb + }; else if (va.x < x0) return; + vb = { + x: x0, + y: fm * x0 + fb + }; + } + } + } + edge.a = va; + edge.b = vb; + return true; + } + function d3_geom_voronoiEdge(lSite, rSite) { + this.l = lSite; + this.r = rSite; + this.a = this.b = null; + } + function d3_geom_voronoiCreateEdge(lSite, rSite, va, vb) { + var edge = new d3_geom_voronoiEdge(lSite, rSite); + d3_geom_voronoiEdges.push(edge); + if (va) d3_geom_voronoiSetEdgeEnd(edge, lSite, rSite, va); + if (vb) d3_geom_voronoiSetEdgeEnd(edge, rSite, lSite, vb); + d3_geom_voronoiCells[lSite.i].edges.push(new d3_geom_voronoiHalfEdge(edge, lSite, rSite)); + d3_geom_voronoiCells[rSite.i].edges.push(new d3_geom_voronoiHalfEdge(edge, rSite, lSite)); + return edge; + } + function d3_geom_voronoiCreateBorderEdge(lSite, va, vb) { + var edge = new d3_geom_voronoiEdge(lSite, null); + edge.a = va; + edge.b = vb; + d3_geom_voronoiEdges.push(edge); + return edge; + } + function d3_geom_voronoiSetEdgeEnd(edge, lSite, rSite, vertex) { + if (!edge.a && !edge.b) { + edge.a = vertex; + edge.l = lSite; + edge.r = rSite; + } else if (edge.l === rSite) { + edge.b = vertex; + } else { + edge.a = vertex; + } + } + function d3_geom_voronoiHalfEdge(edge, lSite, rSite) { + var va = edge.a, vb = edge.b; + this.edge = edge; + this.site = lSite; + this.angle = rSite ? Math.atan2(rSite.y - lSite.y, rSite.x - lSite.x) : edge.l === lSite ? Math.atan2(vb.x - va.x, va.y - vb.y) : Math.atan2(va.x - vb.x, vb.y - va.y); + } + d3_geom_voronoiHalfEdge.prototype = { + start: function() { + return this.edge.l === this.site ? this.edge.a : this.edge.b; + }, + end: function() { + return this.edge.l === this.site ? this.edge.b : this.edge.a; + } + }; + function d3_geom_voronoiRedBlackTree() { + this._ = null; + } + function d3_geom_voronoiRedBlackNode(node) { + node.U = node.C = node.L = node.R = node.P = node.N = null; + } + d3_geom_voronoiRedBlackTree.prototype = { + insert: function(after, node) { + var parent, grandpa, uncle; + if (after) { + node.P = after; + node.N = after.N; + if (after.N) after.N.P = node; + after.N = node; + if (after.R) { + after = after.R; + while (after.L) after = after.L; + after.L = node; + } else { + after.R = node; + } + parent = after; + } else if (this._) { + after = d3_geom_voronoiRedBlackFirst(this._); + node.P = null; + node.N = after; + after.P = after.L = node; + parent = after; + } else { + node.P = node.N = null; + this._ = node; + parent = null; + } + node.L = node.R = null; + node.U = parent; + node.C = true; + after = node; + while (parent && parent.C) { + grandpa = parent.U; + if (parent === grandpa.L) { + uncle = grandpa.R; + if (uncle && uncle.C) { + parent.C = uncle.C = false; + grandpa.C = true; + after = grandpa; + } else { + if (after === parent.R) { + d3_geom_voronoiRedBlackRotateLeft(this, parent); + after = parent; + parent = after.U; + } + parent.C = false; + grandpa.C = true; + d3_geom_voronoiRedBlackRotateRight(this, grandpa); + } + } else { + uncle = grandpa.L; + if (uncle && uncle.C) { + parent.C = uncle.C = false; + grandpa.C = true; + after = grandpa; + } else { + if (after === parent.L) { + d3_geom_voronoiRedBlackRotateRight(this, parent); + after = parent; + parent = after.U; + } + parent.C = false; + grandpa.C = true; + d3_geom_voronoiRedBlackRotateLeft(this, grandpa); + } + } + parent = after.U; + } + this._.C = false; + }, + remove: function(node) { + if (node.N) node.N.P = node.P; + if (node.P) node.P.N = node.N; + node.N = node.P = null; + var parent = node.U, sibling, left = node.L, right = node.R, next, red; + if (!left) next = right; else if (!right) next = left; else next = d3_geom_voronoiRedBlackFirst(right); + if (parent) { + if (parent.L === node) parent.L = next; else parent.R = next; + } else { + this._ = next; + } + if (left && right) { + red = next.C; + next.C = node.C; + next.L = left; + left.U = next; + if (next !== right) { + parent = next.U; + next.U = node.U; + node = next.R; + parent.L = node; + next.R = right; + right.U = next; + } else { + next.U = parent; + parent = next; + node = next.R; + } + } else { + red = node.C; + node = next; + } + if (node) node.U = parent; + if (red) return; + if (node && node.C) { + node.C = false; + return; + } + do { + if (node === this._) break; + if (node === parent.L) { + sibling = parent.R; + if (sibling.C) { + sibling.C = false; + parent.C = true; + d3_geom_voronoiRedBlackRotateLeft(this, parent); + sibling = parent.R; + } + if (sibling.L && sibling.L.C || sibling.R && sibling.R.C) { + if (!sibling.R || !sibling.R.C) { + sibling.L.C = false; + sibling.C = true; + d3_geom_voronoiRedBlackRotateRight(this, sibling); + sibling = parent.R; + } + sibling.C = parent.C; + parent.C = sibling.R.C = false; + d3_geom_voronoiRedBlackRotateLeft(this, parent); + node = this._; + break; + } + } else { + sibling = parent.L; + if (sibling.C) { + sibling.C = false; + parent.C = true; + d3_geom_voronoiRedBlackRotateRight(this, parent); + sibling = parent.L; + } + if (sibling.L && sibling.L.C || sibling.R && sibling.R.C) { + if (!sibling.L || !sibling.L.C) { + sibling.R.C = false; + sibling.C = true; + d3_geom_voronoiRedBlackRotateLeft(this, sibling); + sibling = parent.L; + } + sibling.C = parent.C; + parent.C = sibling.L.C = false; + d3_geom_voronoiRedBlackRotateRight(this, parent); + node = this._; + break; + } + } + sibling.C = true; + node = parent; + parent = parent.U; + } while (!node.C); + if (node) node.C = false; + } + }; + function d3_geom_voronoiRedBlackRotateLeft(tree, node) { + var p = node, q = node.R, parent = p.U; + if (parent) { + if (parent.L === p) parent.L = q; else parent.R = q; + } else { + tree._ = q; + } + q.U = parent; + p.U = q; + p.R = q.L; + if (p.R) p.R.U = p; + q.L = p; + } + function d3_geom_voronoiRedBlackRotateRight(tree, node) { + var p = node, q = node.L, parent = p.U; + if (parent) { + if (parent.L === p) parent.L = q; else parent.R = q; + } else { + tree._ = q; + } + q.U = parent; + p.U = q; + p.L = q.R; + if (p.L) p.L.U = p; + q.R = p; + } + function d3_geom_voronoiRedBlackFirst(node) { + while (node.L) node = node.L; + return node; + } + function d3_geom_voronoi(sites, bbox) { + var site = sites.sort(d3_geom_voronoiVertexOrder).pop(), x0, y0, circle; + d3_geom_voronoiEdges = []; + d3_geom_voronoiCells = new Array(sites.length); + d3_geom_voronoiBeaches = new d3_geom_voronoiRedBlackTree(); + d3_geom_voronoiCircles = new d3_geom_voronoiRedBlackTree(); + while (true) { + circle = d3_geom_voronoiFirstCircle; + if (site && (!circle || site.y < circle.y || site.y === circle.y && site.x < circle.x)) { + if (site.x !== x0 || site.y !== y0) { + d3_geom_voronoiCells[site.i] = new d3_geom_voronoiCell(site); + d3_geom_voronoiAddBeach(site); + x0 = site.x, y0 = site.y; + } + site = sites.pop(); + } else if (circle) { + d3_geom_voronoiRemoveBeach(circle.arc); + } else { + break; + } + } + if (bbox) d3_geom_voronoiClipEdges(bbox), d3_geom_voronoiCloseCells(bbox); + var diagram = { + cells: d3_geom_voronoiCells, + edges: d3_geom_voronoiEdges + }; + d3_geom_voronoiBeaches = d3_geom_voronoiCircles = d3_geom_voronoiEdges = d3_geom_voronoiCells = null; + return diagram; + } + function d3_geom_voronoiVertexOrder(a, b) { + return b.y - a.y || b.x - a.x; + } + d3.geom.voronoi = function(points) { + var x = d3_geom_pointX, y = d3_geom_pointY, fx = x, fy = y, clipExtent = d3_geom_voronoiClipExtent; + if (points) return voronoi(points); + function voronoi(data) { + var polygons = new Array(data.length), x0 = clipExtent[0][0], y0 = clipExtent[0][1], x1 = clipExtent[1][0], y1 = clipExtent[1][1]; + d3_geom_voronoi(sites(data), clipExtent).cells.forEach(function(cell, i) { + var edges = cell.edges, site = cell.site, polygon = polygons[i] = edges.length ? edges.map(function(e) { + var s = e.start(); + return [ s.x, s.y ]; + }) : site.x >= x0 && site.x <= x1 && site.y >= y0 && site.y <= y1 ? [ [ x0, y1 ], [ x1, y1 ], [ x1, y0 ], [ x0, y0 ] ] : []; + polygon.point = data[i]; + }); + return polygons; + } + function sites(data) { + return data.map(function(d, i) { + return { + x: Math.round(fx(d, i) / ε) * ε, + y: Math.round(fy(d, i) / ε) * ε, + i: i + }; + }); + } + voronoi.links = function(data) { + return d3_geom_voronoi(sites(data)).edges.filter(function(edge) { + return edge.l && edge.r; + }).map(function(edge) { + return { + source: data[edge.l.i], + target: data[edge.r.i] + }; + }); + }; + voronoi.triangles = function(data) { + var triangles = []; + d3_geom_voronoi(sites(data)).cells.forEach(function(cell, i) { + var site = cell.site, edges = cell.edges.sort(d3_geom_voronoiHalfEdgeOrder), j = -1, m = edges.length, e0, s0, e1 = edges[m - 1].edge, s1 = e1.l === site ? e1.r : e1.l; + while (++j < m) { + e0 = e1; + s0 = s1; + e1 = edges[j].edge; + s1 = e1.l === site ? e1.r : e1.l; + if (i < s0.i && i < s1.i && d3_geom_voronoiTriangleArea(site, s0, s1) < 0) { + triangles.push([ data[i], data[s0.i], data[s1.i] ]); + } + } + }); + return triangles; + }; + voronoi.x = function(_) { + return arguments.length ? (fx = d3_functor(x = _), voronoi) : x; + }; + voronoi.y = function(_) { + return arguments.length ? (fy = d3_functor(y = _), voronoi) : y; + }; + voronoi.clipExtent = function(_) { + if (!arguments.length) return clipExtent === d3_geom_voronoiClipExtent ? null : clipExtent; + clipExtent = _ == null ? d3_geom_voronoiClipExtent : _; + return voronoi; + }; + voronoi.size = function(_) { + if (!arguments.length) return clipExtent === d3_geom_voronoiClipExtent ? null : clipExtent && clipExtent[1]; + return voronoi.clipExtent(_ && [ [ 0, 0 ], _ ]); + }; + return voronoi; + }; + var d3_geom_voronoiClipExtent = [ [ -1e6, -1e6 ], [ 1e6, 1e6 ] ]; + function d3_geom_voronoiTriangleArea(a, b, c) { + return (a.x - c.x) * (b.y - a.y) - (a.x - b.x) * (c.y - a.y); + } + d3.geom.delaunay = function(vertices) { + return d3.geom.voronoi().triangles(vertices); + }; + d3.geom.quadtree = function(points, x1, y1, x2, y2) { + var x = d3_geom_pointX, y = d3_geom_pointY, compat; + if (compat = arguments.length) { + x = d3_geom_quadtreeCompatX; + y = d3_geom_quadtreeCompatY; + if (compat === 3) { + y2 = y1; + x2 = x1; + y1 = x1 = 0; + } + return quadtree(points); + } + function quadtree(data) { + var d, fx = d3_functor(x), fy = d3_functor(y), xs, ys, i, n, x1_, y1_, x2_, y2_; + if (x1 != null) { + x1_ = x1, y1_ = y1, x2_ = x2, y2_ = y2; + } else { + x2_ = y2_ = -(x1_ = y1_ = Infinity); + xs = [], ys = []; + n = data.length; + if (compat) for (i = 0; i < n; ++i) { + d = data[i]; + if (d.x < x1_) x1_ = d.x; + if (d.y < y1_) y1_ = d.y; + if (d.x > x2_) x2_ = d.x; + if (d.y > y2_) y2_ = d.y; + xs.push(d.x); + ys.push(d.y); + } else for (i = 0; i < n; ++i) { + var x_ = +fx(d = data[i], i), y_ = +fy(d, i); + if (x_ < x1_) x1_ = x_; + if (y_ < y1_) y1_ = y_; + if (x_ > x2_) x2_ = x_; + if (y_ > y2_) y2_ = y_; + xs.push(x_); + ys.push(y_); + } + } + var dx = x2_ - x1_, dy = y2_ - y1_; + if (dx > dy) y2_ = y1_ + dx; else x2_ = x1_ + dy; + function insert(n, d, x, y, x1, y1, x2, y2) { + if (isNaN(x) || isNaN(y)) return; + if (n.leaf) { + var nx = n.x, ny = n.y; + if (nx != null) { + if (abs(nx - x) + abs(ny - y) < .01) { + insertChild(n, d, x, y, x1, y1, x2, y2); + } else { + var nPoint = n.point; + n.x = n.y = n.point = null; + insertChild(n, nPoint, nx, ny, x1, y1, x2, y2); + insertChild(n, d, x, y, x1, y1, x2, y2); + } + } else { + n.x = x, n.y = y, n.point = d; + } + } else { + insertChild(n, d, x, y, x1, y1, x2, y2); + } + } + function insertChild(n, d, x, y, x1, y1, x2, y2) { + var xm = (x1 + x2) * .5, ym = (y1 + y2) * .5, right = x >= xm, below = y >= ym, i = below << 1 | right; + n.leaf = false; + n = n.nodes[i] || (n.nodes[i] = d3_geom_quadtreeNode()); + if (right) x1 = xm; else x2 = xm; + if (below) y1 = ym; else y2 = ym; + insert(n, d, x, y, x1, y1, x2, y2); + } + var root = d3_geom_quadtreeNode(); + root.add = function(d) { + insert(root, d, +fx(d, ++i), +fy(d, i), x1_, y1_, x2_, y2_); + }; + root.visit = function(f) { + d3_geom_quadtreeVisit(f, root, x1_, y1_, x2_, y2_); + }; + root.find = function(point) { + return d3_geom_quadtreeFind(root, point[0], point[1], x1_, y1_, x2_, y2_); + }; + i = -1; + if (x1 == null) { + while (++i < n) { + insert(root, data[i], xs[i], ys[i], x1_, y1_, x2_, y2_); + } + --i; + } else data.forEach(root.add); + xs = ys = data = d = null; + return root; + } + quadtree.x = function(_) { + return arguments.length ? (x = _, quadtree) : x; + }; + quadtree.y = function(_) { + return arguments.length ? (y = _, quadtree) : y; + }; + quadtree.extent = function(_) { + if (!arguments.length) return x1 == null ? null : [ [ x1, y1 ], [ x2, y2 ] ]; + if (_ == null) x1 = y1 = x2 = y2 = null; else x1 = +_[0][0], y1 = +_[0][1], x2 = +_[1][0], + y2 = +_[1][1]; + return quadtree; + }; + quadtree.size = function(_) { + if (!arguments.length) return x1 == null ? null : [ x2 - x1, y2 - y1 ]; + if (_ == null) x1 = y1 = x2 = y2 = null; else x1 = y1 = 0, x2 = +_[0], y2 = +_[1]; + return quadtree; + }; + return quadtree; + }; + function d3_geom_quadtreeCompatX(d) { + return d.x; + } + function d3_geom_quadtreeCompatY(d) { + return d.y; + } + function d3_geom_quadtreeNode() { + return { + leaf: true, + nodes: [], + point: null, + x: null, + y: null + }; + } + function d3_geom_quadtreeVisit(f, node, x1, y1, x2, y2) { + if (!f(node, x1, y1, x2, y2)) { + var sx = (x1 + x2) * .5, sy = (y1 + y2) * .5, children = node.nodes; + if (children[0]) d3_geom_quadtreeVisit(f, children[0], x1, y1, sx, sy); + if (children[1]) d3_geom_quadtreeVisit(f, children[1], sx, y1, x2, sy); + if (children[2]) d3_geom_quadtreeVisit(f, children[2], x1, sy, sx, y2); + if (children[3]) d3_geom_quadtreeVisit(f, children[3], sx, sy, x2, y2); + } + } + function d3_geom_quadtreeFind(root, x, y, x0, y0, x3, y3) { + var minDistance2 = Infinity, closestPoint; + (function find(node, x1, y1, x2, y2) { + if (x1 > x3 || y1 > y3 || x2 < x0 || y2 < y0) return; + if (point = node.point) { + var point, dx = x - node.x, dy = y - node.y, distance2 = dx * dx + dy * dy; + if (distance2 < minDistance2) { + var distance = Math.sqrt(minDistance2 = distance2); + x0 = x - distance, y0 = y - distance; + x3 = x + distance, y3 = y + distance; + closestPoint = point; + } + } + var children = node.nodes, xm = (x1 + x2) * .5, ym = (y1 + y2) * .5, right = x >= xm, below = y >= ym; + for (var i = below << 1 | right, j = i + 4; i < j; ++i) { + if (node = children[i & 3]) switch (i & 3) { + case 0: + find(node, x1, y1, xm, ym); + break; + + case 1: + find(node, xm, y1, x2, ym); + break; + + case 2: + find(node, x1, ym, xm, y2); + break; + + case 3: + find(node, xm, ym, x2, y2); + break; + } + } + })(root, x0, y0, x3, y3); + return closestPoint; + } + d3.interpolateRgb = d3_interpolateRgb; + function d3_interpolateRgb(a, b) { + a = d3.rgb(a); + b = d3.rgb(b); + var ar = a.r, ag = a.g, ab = a.b, br = b.r - ar, bg = b.g - ag, bb = b.b - ab; + return function(t) { + return "#" + d3_rgb_hex(Math.round(ar + br * t)) + d3_rgb_hex(Math.round(ag + bg * t)) + d3_rgb_hex(Math.round(ab + bb * t)); + }; + } + d3.interpolateObject = d3_interpolateObject; + function d3_interpolateObject(a, b) { + var i = {}, c = {}, k; + for (k in a) { + if (k in b) { + i[k] = d3_interpolate(a[k], b[k]); + } else { + c[k] = a[k]; + } + } + for (k in b) { + if (!(k in a)) { + c[k] = b[k]; + } + } + return function(t) { + for (k in i) c[k] = i[k](t); + return c; + }; + } + d3.interpolateNumber = d3_interpolateNumber; + function d3_interpolateNumber(a, b) { + a = +a, b = +b; + return function(t) { + return a * (1 - t) + b * t; + }; + } + d3.interpolateString = d3_interpolateString; + function d3_interpolateString(a, b) { + var bi = d3_interpolate_numberA.lastIndex = d3_interpolate_numberB.lastIndex = 0, am, bm, bs, i = -1, s = [], q = []; + a = a + "", b = b + ""; + while ((am = d3_interpolate_numberA.exec(a)) && (bm = d3_interpolate_numberB.exec(b))) { + if ((bs = bm.index) > bi) { + bs = b.slice(bi, bs); + if (s[i]) s[i] += bs; else s[++i] = bs; + } + if ((am = am[0]) === (bm = bm[0])) { + if (s[i]) s[i] += bm; else s[++i] = bm; + } else { + s[++i] = null; + q.push({ + i: i, + x: d3_interpolateNumber(am, bm) + }); + } + bi = d3_interpolate_numberB.lastIndex; + } + if (bi < b.length) { + bs = b.slice(bi); + if (s[i]) s[i] += bs; else s[++i] = bs; + } + return s.length < 2 ? q[0] ? (b = q[0].x, function(t) { + return b(t) + ""; + }) : function() { + return b; + } : (b = q.length, function(t) { + for (var i = 0, o; i < b; ++i) s[(o = q[i]).i] = o.x(t); + return s.join(""); + }); + } + var d3_interpolate_numberA = /[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g, d3_interpolate_numberB = new RegExp(d3_interpolate_numberA.source, "g"); + d3.interpolate = d3_interpolate; + function d3_interpolate(a, b) { + var i = d3.interpolators.length, f; + while (--i >= 0 && !(f = d3.interpolators[i](a, b))) ; + return f; + } + d3.interpolators = [ function(a, b) { + var t = typeof b; + return (t === "string" ? d3_rgb_names.has(b.toLowerCase()) || /^(#|rgb\(|hsl\()/i.test(b) ? d3_interpolateRgb : d3_interpolateString : b instanceof d3_color ? d3_interpolateRgb : Array.isArray(b) ? d3_interpolateArray : t === "object" && isNaN(b) ? d3_interpolateObject : d3_interpolateNumber)(a, b); + } ]; + d3.interpolateArray = d3_interpolateArray; + function d3_interpolateArray(a, b) { + var x = [], c = [], na = a.length, nb = b.length, n0 = Math.min(a.length, b.length), i; + for (i = 0; i < n0; ++i) x.push(d3_interpolate(a[i], b[i])); + for (;i < na; ++i) c[i] = a[i]; + for (;i < nb; ++i) c[i] = b[i]; + return function(t) { + for (i = 0; i < n0; ++i) c[i] = x[i](t); + return c; + }; + } + var d3_ease_default = function() { + return d3_identity; + }; + var d3_ease = d3.map({ + linear: d3_ease_default, + poly: d3_ease_poly, + quad: function() { + return d3_ease_quad; + }, + cubic: function() { + return d3_ease_cubic; + }, + sin: function() { + return d3_ease_sin; + }, + exp: function() { + return d3_ease_exp; + }, + circle: function() { + return d3_ease_circle; + }, + elastic: d3_ease_elastic, + back: d3_ease_back, + bounce: function() { + return d3_ease_bounce; + } + }); + var d3_ease_mode = d3.map({ + "in": d3_identity, + out: d3_ease_reverse, + "in-out": d3_ease_reflect, + "out-in": function(f) { + return d3_ease_reflect(d3_ease_reverse(f)); + } + }); + d3.ease = function(name) { + var i = name.indexOf("-"), t = i >= 0 ? name.slice(0, i) : name, m = i >= 0 ? name.slice(i + 1) : "in"; + t = d3_ease.get(t) || d3_ease_default; + m = d3_ease_mode.get(m) || d3_identity; + return d3_ease_clamp(m(t.apply(null, d3_arraySlice.call(arguments, 1)))); + }; + function d3_ease_clamp(f) { + return function(t) { + return t <= 0 ? 0 : t >= 1 ? 1 : f(t); + }; + } + function d3_ease_reverse(f) { + return function(t) { + return 1 - f(1 - t); + }; + } + function d3_ease_reflect(f) { + return function(t) { + return .5 * (t < .5 ? f(2 * t) : 2 - f(2 - 2 * t)); + }; + } + function d3_ease_quad(t) { + return t * t; + } + function d3_ease_cubic(t) { + return t * t * t; + } + function d3_ease_cubicInOut(t) { + if (t <= 0) return 0; + if (t >= 1) return 1; + var t2 = t * t, t3 = t2 * t; + return 4 * (t < .5 ? t3 : 3 * (t - t2) + t3 - .75); + } + function d3_ease_poly(e) { + return function(t) { + return Math.pow(t, e); + }; + } + function d3_ease_sin(t) { + return 1 - Math.cos(t * halfπ); + } + function d3_ease_exp(t) { + return Math.pow(2, 10 * (t - 1)); + } + function d3_ease_circle(t) { + return 1 - Math.sqrt(1 - t * t); + } + function d3_ease_elastic(a, p) { + var s; + if (arguments.length < 2) p = .45; + if (arguments.length) s = p / τ * Math.asin(1 / a); else a = 1, s = p / 4; + return function(t) { + return 1 + a * Math.pow(2, -10 * t) * Math.sin((t - s) * τ / p); + }; + } + function d3_ease_back(s) { + if (!s) s = 1.70158; + return function(t) { + return t * t * ((s + 1) * t - s); + }; + } + function d3_ease_bounce(t) { + return t < 1 / 2.75 ? 7.5625 * t * t : t < 2 / 2.75 ? 7.5625 * (t -= 1.5 / 2.75) * t + .75 : t < 2.5 / 2.75 ? 7.5625 * (t -= 2.25 / 2.75) * t + .9375 : 7.5625 * (t -= 2.625 / 2.75) * t + .984375; + } + d3.interpolateHcl = d3_interpolateHcl; + function d3_interpolateHcl(a, b) { + a = d3.hcl(a); + b = d3.hcl(b); + var ah = a.h, ac = a.c, al = a.l, bh = b.h - ah, bc = b.c - ac, bl = b.l - al; + if (isNaN(bc)) bc = 0, ac = isNaN(ac) ? b.c : ac; + if (isNaN(bh)) bh = 0, ah = isNaN(ah) ? b.h : ah; else if (bh > 180) bh -= 360; else if (bh < -180) bh += 360; + return function(t) { + return d3_hcl_lab(ah + bh * t, ac + bc * t, al + bl * t) + ""; + }; + } + d3.interpolateHsl = d3_interpolateHsl; + function d3_interpolateHsl(a, b) { + a = d3.hsl(a); + b = d3.hsl(b); + var ah = a.h, as = a.s, al = a.l, bh = b.h - ah, bs = b.s - as, bl = b.l - al; + if (isNaN(bs)) bs = 0, as = isNaN(as) ? b.s : as; + if (isNaN(bh)) bh = 0, ah = isNaN(ah) ? b.h : ah; else if (bh > 180) bh -= 360; else if (bh < -180) bh += 360; + return function(t) { + return d3_hsl_rgb(ah + bh * t, as + bs * t, al + bl * t) + ""; + }; + } + d3.interpolateLab = d3_interpolateLab; + function d3_interpolateLab(a, b) { + a = d3.lab(a); + b = d3.lab(b); + var al = a.l, aa = a.a, ab = a.b, bl = b.l - al, ba = b.a - aa, bb = b.b - ab; + return function(t) { + return d3_lab_rgb(al + bl * t, aa + ba * t, ab + bb * t) + ""; + }; + } + d3.interpolateRound = d3_interpolateRound; + function d3_interpolateRound(a, b) { + b -= a; + return function(t) { + return Math.round(a + b * t); + }; + } + d3.transform = function(string) { + var g = d3_document.createElementNS(d3.ns.prefix.svg, "g"); + return (d3.transform = function(string) { + if (string != null) { + g.setAttribute("transform", string); + var t = g.transform.baseVal.consolidate(); + } + return new d3_transform(t ? t.matrix : d3_transformIdentity); + })(string); + }; + function d3_transform(m) { + var r0 = [ m.a, m.b ], r1 = [ m.c, m.d ], kx = d3_transformNormalize(r0), kz = d3_transformDot(r0, r1), ky = d3_transformNormalize(d3_transformCombine(r1, r0, -kz)) || 0; + if (r0[0] * r1[1] < r1[0] * r0[1]) { + r0[0] *= -1; + r0[1] *= -1; + kx *= -1; + kz *= -1; + } + this.rotate = (kx ? Math.atan2(r0[1], r0[0]) : Math.atan2(-r1[0], r1[1])) * d3_degrees; + this.translate = [ m.e, m.f ]; + this.scale = [ kx, ky ]; + this.skew = ky ? Math.atan2(kz, ky) * d3_degrees : 0; + } + d3_transform.prototype.toString = function() { + return "translate(" + this.translate + ")rotate(" + this.rotate + ")skewX(" + this.skew + ")scale(" + this.scale + ")"; + }; + function d3_transformDot(a, b) { + return a[0] * b[0] + a[1] * b[1]; + } + function d3_transformNormalize(a) { + var k = Math.sqrt(d3_transformDot(a, a)); + if (k) { + a[0] /= k; + a[1] /= k; + } + return k; + } + function d3_transformCombine(a, b, k) { + a[0] += k * b[0]; + a[1] += k * b[1]; + return a; + } + var d3_transformIdentity = { + a: 1, + b: 0, + c: 0, + d: 1, + e: 0, + f: 0 + }; + d3.interpolateTransform = d3_interpolateTransform; + function d3_interpolateTransform(a, b) { + var s = [], q = [], n, A = d3.transform(a), B = d3.transform(b), ta = A.translate, tb = B.translate, ra = A.rotate, rb = B.rotate, wa = A.skew, wb = B.skew, ka = A.scale, kb = B.scale; + if (ta[0] != tb[0] || ta[1] != tb[1]) { + s.push("translate(", null, ",", null, ")"); + q.push({ + i: 1, + x: d3_interpolateNumber(ta[0], tb[0]) + }, { + i: 3, + x: d3_interpolateNumber(ta[1], tb[1]) + }); + } else if (tb[0] || tb[1]) { + s.push("translate(" + tb + ")"); + } else { + s.push(""); + } + if (ra != rb) { + if (ra - rb > 180) rb += 360; else if (rb - ra > 180) ra += 360; + q.push({ + i: s.push(s.pop() + "rotate(", null, ")") - 2, + x: d3_interpolateNumber(ra, rb) + }); + } else if (rb) { + s.push(s.pop() + "rotate(" + rb + ")"); + } + if (wa != wb) { + q.push({ + i: s.push(s.pop() + "skewX(", null, ")") - 2, + x: d3_interpolateNumber(wa, wb) + }); + } else if (wb) { + s.push(s.pop() + "skewX(" + wb + ")"); + } + if (ka[0] != kb[0] || ka[1] != kb[1]) { + n = s.push(s.pop() + "scale(", null, ",", null, ")"); + q.push({ + i: n - 4, + x: d3_interpolateNumber(ka[0], kb[0]) + }, { + i: n - 2, + x: d3_interpolateNumber(ka[1], kb[1]) + }); + } else if (kb[0] != 1 || kb[1] != 1) { + s.push(s.pop() + "scale(" + kb + ")"); + } + n = q.length; + return function(t) { + var i = -1, o; + while (++i < n) s[(o = q[i]).i] = o.x(t); + return s.join(""); + }; + } + function d3_uninterpolateNumber(a, b) { + b = (b -= a = +a) || 1 / b; + return function(x) { + return (x - a) / b; + }; + } + function d3_uninterpolateClamp(a, b) { + b = (b -= a = +a) || 1 / b; + return function(x) { + return Math.max(0, Math.min(1, (x - a) / b)); + }; + } + d3.layout = {}; + d3.layout.bundle = function() { + return function(links) { + var paths = [], i = -1, n = links.length; + while (++i < n) paths.push(d3_layout_bundlePath(links[i])); + return paths; + }; + }; + function d3_layout_bundlePath(link) { + var start = link.source, end = link.target, lca = d3_layout_bundleLeastCommonAncestor(start, end), points = [ start ]; + while (start !== lca) { + start = start.parent; + points.push(start); + } + var k = points.length; + while (end !== lca) { + points.splice(k, 0, end); + end = end.parent; + } + return points; + } + function d3_layout_bundleAncestors(node) { + var ancestors = [], parent = node.parent; + while (parent != null) { + ancestors.push(node); + node = parent; + parent = parent.parent; + } + ancestors.push(node); + return ancestors; + } + function d3_layout_bundleLeastCommonAncestor(a, b) { + if (a === b) return a; + var aNodes = d3_layout_bundleAncestors(a), bNodes = d3_layout_bundleAncestors(b), aNode = aNodes.pop(), bNode = bNodes.pop(), sharedNode = null; + while (aNode === bNode) { + sharedNode = aNode; + aNode = aNodes.pop(); + bNode = bNodes.pop(); + } + return sharedNode; + } + d3.layout.chord = function() { + var chord = {}, chords, groups, matrix, n, padding = 0, sortGroups, sortSubgroups, sortChords; + function relayout() { + var subgroups = {}, groupSums = [], groupIndex = d3.range(n), subgroupIndex = [], k, x, x0, i, j; + chords = []; + groups = []; + k = 0, i = -1; + while (++i < n) { + x = 0, j = -1; + while (++j < n) { + x += matrix[i][j]; + } + groupSums.push(x); + subgroupIndex.push(d3.range(n)); + k += x; + } + if (sortGroups) { + groupIndex.sort(function(a, b) { + return sortGroups(groupSums[a], groupSums[b]); + }); + } + if (sortSubgroups) { + subgroupIndex.forEach(function(d, i) { + d.sort(function(a, b) { + return sortSubgroups(matrix[i][a], matrix[i][b]); + }); + }); + } + k = (τ - padding * n) / k; + x = 0, i = -1; + while (++i < n) { + x0 = x, j = -1; + while (++j < n) { + var di = groupIndex[i], dj = subgroupIndex[di][j], v = matrix[di][dj], a0 = x, a1 = x += v * k; + subgroups[di + "-" + dj] = { + index: di, + subindex: dj, + startAngle: a0, + endAngle: a1, + value: v + }; + } + groups[di] = { + index: di, + startAngle: x0, + endAngle: x, + value: (x - x0) / k + }; + x += padding; + } + i = -1; + while (++i < n) { + j = i - 1; + while (++j < n) { + var source = subgroups[i + "-" + j], target = subgroups[j + "-" + i]; + if (source.value || target.value) { + chords.push(source.value < target.value ? { + source: target, + target: source + } : { + source: source, + target: target + }); + } + } + } + if (sortChords) resort(); + } + function resort() { + chords.sort(function(a, b) { + return sortChords((a.source.value + a.target.value) / 2, (b.source.value + b.target.value) / 2); + }); + } + chord.matrix = function(x) { + if (!arguments.length) return matrix; + n = (matrix = x) && matrix.length; + chords = groups = null; + return chord; + }; + chord.padding = function(x) { + if (!arguments.length) return padding; + padding = x; + chords = groups = null; + return chord; + }; + chord.sortGroups = function(x) { + if (!arguments.length) return sortGroups; + sortGroups = x; + chords = groups = null; + return chord; + }; + chord.sortSubgroups = function(x) { + if (!arguments.length) return sortSubgroups; + sortSubgroups = x; + chords = null; + return chord; + }; + chord.sortChords = function(x) { + if (!arguments.length) return sortChords; + sortChords = x; + if (chords) resort(); + return chord; + }; + chord.chords = function() { + if (!chords) relayout(); + return chords; + }; + chord.groups = function() { + if (!groups) relayout(); + return groups; + }; + return chord; + }; + d3.layout.force = function() { + var force = {}, event = d3.dispatch("start", "tick", "end"), size = [ 1, 1 ], drag, alpha, friction = .9, linkDistance = d3_layout_forceLinkDistance, linkStrength = d3_layout_forceLinkStrength, charge = -30, chargeDistance2 = d3_layout_forceChargeDistance2, gravity = .1, theta2 = .64, nodes = [], links = [], distances, strengths, charges; + function repulse(node) { + return function(quad, x1, _, x2) { + if (quad.point !== node) { + var dx = quad.cx - node.x, dy = quad.cy - node.y, dw = x2 - x1, dn = dx * dx + dy * dy; + if (dw * dw / theta2 < dn) { + if (dn < chargeDistance2) { + var k = quad.charge / dn; + node.px -= dx * k; + node.py -= dy * k; + } + return true; + } + if (quad.point && dn && dn < chargeDistance2) { + var k = quad.pointCharge / dn; + node.px -= dx * k; + node.py -= dy * k; + } + } + return !quad.charge; + }; + } + force.tick = function() { + if ((alpha *= .99) < .005) { + event.end({ + type: "end", + alpha: alpha = 0 + }); + return true; + } + var n = nodes.length, m = links.length, q, i, o, s, t, l, k, x, y; + for (i = 0; i < m; ++i) { + o = links[i]; + s = o.source; + t = o.target; + x = t.x - s.x; + y = t.y - s.y; + if (l = x * x + y * y) { + l = alpha * strengths[i] * ((l = Math.sqrt(l)) - distances[i]) / l; + x *= l; + y *= l; + t.x -= x * (k = s.weight / (t.weight + s.weight)); + t.y -= y * k; + s.x += x * (k = 1 - k); + s.y += y * k; + } + } + if (k = alpha * gravity) { + x = size[0] / 2; + y = size[1] / 2; + i = -1; + if (k) while (++i < n) { + o = nodes[i]; + o.x += (x - o.x) * k; + o.y += (y - o.y) * k; + } + } + if (charge) { + d3_layout_forceAccumulate(q = d3.geom.quadtree(nodes), alpha, charges); + i = -1; + while (++i < n) { + if (!(o = nodes[i]).fixed) { + q.visit(repulse(o)); + } + } + } + i = -1; + while (++i < n) { + o = nodes[i]; + if (o.fixed) { + o.x = o.px; + o.y = o.py; + } else { + o.x -= (o.px - (o.px = o.x)) * friction; + o.y -= (o.py - (o.py = o.y)) * friction; + } + } + event.tick({ + type: "tick", + alpha: alpha + }); + }; + force.nodes = function(x) { + if (!arguments.length) return nodes; + nodes = x; + return force; + }; + force.links = function(x) { + if (!arguments.length) return links; + links = x; + return force; + }; + force.size = function(x) { + if (!arguments.length) return size; + size = x; + return force; + }; + force.linkDistance = function(x) { + if (!arguments.length) return linkDistance; + linkDistance = typeof x === "function" ? x : +x; + return force; + }; + force.distance = force.linkDistance; + force.linkStrength = function(x) { + if (!arguments.length) return linkStrength; + linkStrength = typeof x === "function" ? x : +x; + return force; + }; + force.friction = function(x) { + if (!arguments.length) return friction; + friction = +x; + return force; + }; + force.charge = function(x) { + if (!arguments.length) return charge; + charge = typeof x === "function" ? x : +x; + return force; + }; + force.chargeDistance = function(x) { + if (!arguments.length) return Math.sqrt(chargeDistance2); + chargeDistance2 = x * x; + return force; + }; + force.gravity = function(x) { + if (!arguments.length) return gravity; + gravity = +x; + return force; + }; + force.theta = function(x) { + if (!arguments.length) return Math.sqrt(theta2); + theta2 = x * x; + return force; + }; + force.alpha = function(x) { + if (!arguments.length) return alpha; + x = +x; + if (alpha) { + if (x > 0) alpha = x; else alpha = 0; + } else if (x > 0) { + event.start({ + type: "start", + alpha: alpha = x + }); + d3.timer(force.tick); + } + return force; + }; + force.start = function() { + var i, n = nodes.length, m = links.length, w = size[0], h = size[1], neighbors, o; + for (i = 0; i < n; ++i) { + (o = nodes[i]).index = i; + o.weight = 0; + } + for (i = 0; i < m; ++i) { + o = links[i]; + if (typeof o.source == "number") o.source = nodes[o.source]; + if (typeof o.target == "number") o.target = nodes[o.target]; + ++o.source.weight; + ++o.target.weight; + } + for (i = 0; i < n; ++i) { + o = nodes[i]; + if (isNaN(o.x)) o.x = position("x", w); + if (isNaN(o.y)) o.y = position("y", h); + if (isNaN(o.px)) o.px = o.x; + if (isNaN(o.py)) o.py = o.y; + } + distances = []; + if (typeof linkDistance === "function") for (i = 0; i < m; ++i) distances[i] = +linkDistance.call(this, links[i], i); else for (i = 0; i < m; ++i) distances[i] = linkDistance; + strengths = []; + if (typeof linkStrength === "function") for (i = 0; i < m; ++i) strengths[i] = +linkStrength.call(this, links[i], i); else for (i = 0; i < m; ++i) strengths[i] = linkStrength; + charges = []; + if (typeof charge === "function") for (i = 0; i < n; ++i) charges[i] = +charge.call(this, nodes[i], i); else for (i = 0; i < n; ++i) charges[i] = charge; + function position(dimension, size) { + if (!neighbors) { + neighbors = new Array(n); + for (j = 0; j < n; ++j) { + neighbors[j] = []; + } + for (j = 0; j < m; ++j) { + var o = links[j]; + neighbors[o.source.index].push(o.target); + neighbors[o.target.index].push(o.source); + } + } + var candidates = neighbors[i], j = -1, l = candidates.length, x; + while (++j < l) if (!isNaN(x = candidates[j][dimension])) return x; + return Math.random() * size; + } + return force.resume(); + }; + force.resume = function() { + return force.alpha(.1); + }; + force.stop = function() { + return force.alpha(0); + }; + force.drag = function() { + if (!drag) drag = d3.behavior.drag().origin(d3_identity).on("dragstart.force", d3_layout_forceDragstart).on("drag.force", dragmove).on("dragend.force", d3_layout_forceDragend); + if (!arguments.length) return drag; + this.on("mouseover.force", d3_layout_forceMouseover).on("mouseout.force", d3_layout_forceMouseout).call(drag); + }; + function dragmove(d) { + d.px = d3.event.x, d.py = d3.event.y; + force.resume(); + } + return d3.rebind(force, event, "on"); + }; + function d3_layout_forceDragstart(d) { + d.fixed |= 2; + } + function d3_layout_forceDragend(d) { + d.fixed &= ~6; + } + function d3_layout_forceMouseover(d) { + d.fixed |= 4; + d.px = d.x, d.py = d.y; + } + function d3_layout_forceMouseout(d) { + d.fixed &= ~4; + } + function d3_layout_forceAccumulate(quad, alpha, charges) { + var cx = 0, cy = 0; + quad.charge = 0; + if (!quad.leaf) { + var nodes = quad.nodes, n = nodes.length, i = -1, c; + while (++i < n) { + c = nodes[i]; + if (c == null) continue; + d3_layout_forceAccumulate(c, alpha, charges); + quad.charge += c.charge; + cx += c.charge * c.cx; + cy += c.charge * c.cy; + } + } + if (quad.point) { + if (!quad.leaf) { + quad.point.x += Math.random() - .5; + quad.point.y += Math.random() - .5; + } + var k = alpha * charges[quad.point.index]; + quad.charge += quad.pointCharge = k; + cx += k * quad.point.x; + cy += k * quad.point.y; + } + quad.cx = cx / quad.charge; + quad.cy = cy / quad.charge; + } + var d3_layout_forceLinkDistance = 20, d3_layout_forceLinkStrength = 1, d3_layout_forceChargeDistance2 = Infinity; + d3.layout.hierarchy = function() { + var sort = d3_layout_hierarchySort, children = d3_layout_hierarchyChildren, value = d3_layout_hierarchyValue; + function hierarchy(root) { + var stack = [ root ], nodes = [], node; + root.depth = 0; + while ((node = stack.pop()) != null) { + nodes.push(node); + if ((childs = children.call(hierarchy, node, node.depth)) && (n = childs.length)) { + var n, childs, child; + while (--n >= 0) { + stack.push(child = childs[n]); + child.parent = node; + child.depth = node.depth + 1; + } + if (value) node.value = 0; + node.children = childs; + } else { + if (value) node.value = +value.call(hierarchy, node, node.depth) || 0; + delete node.children; + } + } + d3_layout_hierarchyVisitAfter(root, function(node) { + var childs, parent; + if (sort && (childs = node.children)) childs.sort(sort); + if (value && (parent = node.parent)) parent.value += node.value; + }); + return nodes; + } + hierarchy.sort = function(x) { + if (!arguments.length) return sort; + sort = x; + return hierarchy; + }; + hierarchy.children = function(x) { + if (!arguments.length) return children; + children = x; + return hierarchy; + }; + hierarchy.value = function(x) { + if (!arguments.length) return value; + value = x; + return hierarchy; + }; + hierarchy.revalue = function(root) { + if (value) { + d3_layout_hierarchyVisitBefore(root, function(node) { + if (node.children) node.value = 0; + }); + d3_layout_hierarchyVisitAfter(root, function(node) { + var parent; + if (!node.children) node.value = +value.call(hierarchy, node, node.depth) || 0; + if (parent = node.parent) parent.value += node.value; + }); + } + return root; + }; + return hierarchy; + }; + function d3_layout_hierarchyRebind(object, hierarchy) { + d3.rebind(object, hierarchy, "sort", "children", "value"); + object.nodes = object; + object.links = d3_layout_hierarchyLinks; + return object; + } + function d3_layout_hierarchyVisitBefore(node, callback) { + var nodes = [ node ]; + while ((node = nodes.pop()) != null) { + callback(node); + if ((children = node.children) && (n = children.length)) { + var n, children; + while (--n >= 0) nodes.push(children[n]); + } + } + } + function d3_layout_hierarchyVisitAfter(node, callback) { + var nodes = [ node ], nodes2 = []; + while ((node = nodes.pop()) != null) { + nodes2.push(node); + if ((children = node.children) && (n = children.length)) { + var i = -1, n, children; + while (++i < n) nodes.push(children[i]); + } + } + while ((node = nodes2.pop()) != null) { + callback(node); + } + } + function d3_layout_hierarchyChildren(d) { + return d.children; + } + function d3_layout_hierarchyValue(d) { + return d.value; + } + function d3_layout_hierarchySort(a, b) { + return b.value - a.value; + } + function d3_layout_hierarchyLinks(nodes) { + return d3.merge(nodes.map(function(parent) { + return (parent.children || []).map(function(child) { + return { + source: parent, + target: child + }; + }); + })); + } + d3.layout.partition = function() { + var hierarchy = d3.layout.hierarchy(), size = [ 1, 1 ]; + function position(node, x, dx, dy) { + var children = node.children; + node.x = x; + node.y = node.depth * dy; + node.dx = dx; + node.dy = dy; + if (children && (n = children.length)) { + var i = -1, n, c, d; + dx = node.value ? dx / node.value : 0; + while (++i < n) { + position(c = children[i], x, d = c.value * dx, dy); + x += d; + } + } + } + function depth(node) { + var children = node.children, d = 0; + if (children && (n = children.length)) { + var i = -1, n; + while (++i < n) d = Math.max(d, depth(children[i])); + } + return 1 + d; + } + function partition(d, i) { + var nodes = hierarchy.call(this, d, i); + position(nodes[0], 0, size[0], size[1] / depth(nodes[0])); + return nodes; + } + partition.size = function(x) { + if (!arguments.length) return size; + size = x; + return partition; + }; + return d3_layout_hierarchyRebind(partition, hierarchy); + }; + d3.layout.pie = function() { + var value = Number, sort = d3_layout_pieSortByValue, startAngle = 0, endAngle = τ, padAngle = 0; + function pie(data) { + var n = data.length, values = data.map(function(d, i) { + return +value.call(pie, d, i); + }), a = +(typeof startAngle === "function" ? startAngle.apply(this, arguments) : startAngle), da = (typeof endAngle === "function" ? endAngle.apply(this, arguments) : endAngle) - a, p = Math.min(Math.abs(da) / n, +(typeof padAngle === "function" ? padAngle.apply(this, arguments) : padAngle)), pa = p * (da < 0 ? -1 : 1), k = (da - n * pa) / d3.sum(values), index = d3.range(n), arcs = [], v; + if (sort != null) index.sort(sort === d3_layout_pieSortByValue ? function(i, j) { + return values[j] - values[i]; + } : function(i, j) { + return sort(data[i], data[j]); + }); + index.forEach(function(i) { + arcs[i] = { + data: data[i], + value: v = values[i], + startAngle: a, + endAngle: a += v * k + pa, + padAngle: p + }; + }); + return arcs; + } + pie.value = function(_) { + if (!arguments.length) return value; + value = _; + return pie; + }; + pie.sort = function(_) { + if (!arguments.length) return sort; + sort = _; + return pie; + }; + pie.startAngle = function(_) { + if (!arguments.length) return startAngle; + startAngle = _; + return pie; + }; + pie.endAngle = function(_) { + if (!arguments.length) return endAngle; + endAngle = _; + return pie; + }; + pie.padAngle = function(_) { + if (!arguments.length) return padAngle; + padAngle = _; + return pie; + }; + return pie; + }; + var d3_layout_pieSortByValue = {}; + d3.layout.stack = function() { + var values = d3_identity, order = d3_layout_stackOrderDefault, offset = d3_layout_stackOffsetZero, out = d3_layout_stackOut, x = d3_layout_stackX, y = d3_layout_stackY; + function stack(data, index) { + if (!(n = data.length)) return data; + var series = data.map(function(d, i) { + return values.call(stack, d, i); + }); + var points = series.map(function(d) { + return d.map(function(v, i) { + return [ x.call(stack, v, i), y.call(stack, v, i) ]; + }); + }); + var orders = order.call(stack, points, index); + series = d3.permute(series, orders); + points = d3.permute(points, orders); + var offsets = offset.call(stack, points, index); + var m = series[0].length, n, i, j, o; + for (j = 0; j < m; ++j) { + out.call(stack, series[0][j], o = offsets[j], points[0][j][1]); + for (i = 1; i < n; ++i) { + out.call(stack, series[i][j], o += points[i - 1][j][1], points[i][j][1]); + } + } + return data; + } + stack.values = function(x) { + if (!arguments.length) return values; + values = x; + return stack; + }; + stack.order = function(x) { + if (!arguments.length) return order; + order = typeof x === "function" ? x : d3_layout_stackOrders.get(x) || d3_layout_stackOrderDefault; + return stack; + }; + stack.offset = function(x) { + if (!arguments.length) return offset; + offset = typeof x === "function" ? x : d3_layout_stackOffsets.get(x) || d3_layout_stackOffsetZero; + return stack; + }; + stack.x = function(z) { + if (!arguments.length) return x; + x = z; + return stack; + }; + stack.y = function(z) { + if (!arguments.length) return y; + y = z; + return stack; + }; + stack.out = function(z) { + if (!arguments.length) return out; + out = z; + return stack; + }; + return stack; + }; + function d3_layout_stackX(d) { + return d.x; + } + function d3_layout_stackY(d) { + return d.y; + } + function d3_layout_stackOut(d, y0, y) { + d.y0 = y0; + d.y = y; + } + var d3_layout_stackOrders = d3.map({ + "inside-out": function(data) { + var n = data.length, i, j, max = data.map(d3_layout_stackMaxIndex), sums = data.map(d3_layout_stackReduceSum), index = d3.range(n).sort(function(a, b) { + return max[a] - max[b]; + }), top = 0, bottom = 0, tops = [], bottoms = []; + for (i = 0; i < n; ++i) { + j = index[i]; + if (top < bottom) { + top += sums[j]; + tops.push(j); + } else { + bottom += sums[j]; + bottoms.push(j); + } + } + return bottoms.reverse().concat(tops); + }, + reverse: function(data) { + return d3.range(data.length).reverse(); + }, + "default": d3_layout_stackOrderDefault + }); + var d3_layout_stackOffsets = d3.map({ + silhouette: function(data) { + var n = data.length, m = data[0].length, sums = [], max = 0, i, j, o, y0 = []; + for (j = 0; j < m; ++j) { + for (i = 0, o = 0; i < n; i++) o += data[i][j][1]; + if (o > max) max = o; + sums.push(o); + } + for (j = 0; j < m; ++j) { + y0[j] = (max - sums[j]) / 2; + } + return y0; + }, + wiggle: function(data) { + var n = data.length, x = data[0], m = x.length, i, j, k, s1, s2, s3, dx, o, o0, y0 = []; + y0[0] = o = o0 = 0; + for (j = 1; j < m; ++j) { + for (i = 0, s1 = 0; i < n; ++i) s1 += data[i][j][1]; + for (i = 0, s2 = 0, dx = x[j][0] - x[j - 1][0]; i < n; ++i) { + for (k = 0, s3 = (data[i][j][1] - data[i][j - 1][1]) / (2 * dx); k < i; ++k) { + s3 += (data[k][j][1] - data[k][j - 1][1]) / dx; + } + s2 += s3 * data[i][j][1]; + } + y0[j] = o -= s1 ? s2 / s1 * dx : 0; + if (o < o0) o0 = o; + } + for (j = 0; j < m; ++j) y0[j] -= o0; + return y0; + }, + expand: function(data) { + var n = data.length, m = data[0].length, k = 1 / n, i, j, o, y0 = []; + for (j = 0; j < m; ++j) { + for (i = 0, o = 0; i < n; i++) o += data[i][j][1]; + if (o) for (i = 0; i < n; i++) data[i][j][1] /= o; else for (i = 0; i < n; i++) data[i][j][1] = k; + } + for (j = 0; j < m; ++j) y0[j] = 0; + return y0; + }, + zero: d3_layout_stackOffsetZero + }); + function d3_layout_stackOrderDefault(data) { + return d3.range(data.length); + } + function d3_layout_stackOffsetZero(data) { + var j = -1, m = data[0].length, y0 = []; + while (++j < m) y0[j] = 0; + return y0; + } + function d3_layout_stackMaxIndex(array) { + var i = 1, j = 0, v = array[0][1], k, n = array.length; + for (;i < n; ++i) { + if ((k = array[i][1]) > v) { + j = i; + v = k; + } + } + return j; + } + function d3_layout_stackReduceSum(d) { + return d.reduce(d3_layout_stackSum, 0); + } + function d3_layout_stackSum(p, d) { + return p + d[1]; + } + d3.layout.histogram = function() { + var frequency = true, valuer = Number, ranger = d3_layout_histogramRange, binner = d3_layout_histogramBinSturges; + function histogram(data, i) { + var bins = [], values = data.map(valuer, this), range = ranger.call(this, values, i), thresholds = binner.call(this, range, values, i), bin, i = -1, n = values.length, m = thresholds.length - 1, k = frequency ? 1 : 1 / n, x; + while (++i < m) { + bin = bins[i] = []; + bin.dx = thresholds[i + 1] - (bin.x = thresholds[i]); + bin.y = 0; + } + if (m > 0) { + i = -1; + while (++i < n) { + x = values[i]; + if (x >= range[0] && x <= range[1]) { + bin = bins[d3.bisect(thresholds, x, 1, m) - 1]; + bin.y += k; + bin.push(data[i]); + } + } + } + return bins; + } + histogram.value = function(x) { + if (!arguments.length) return valuer; + valuer = x; + return histogram; + }; + histogram.range = function(x) { + if (!arguments.length) return ranger; + ranger = d3_functor(x); + return histogram; + }; + histogram.bins = function(x) { + if (!arguments.length) return binner; + binner = typeof x === "number" ? function(range) { + return d3_layout_histogramBinFixed(range, x); + } : d3_functor(x); + return histogram; + }; + histogram.frequency = function(x) { + if (!arguments.length) return frequency; + frequency = !!x; + return histogram; + }; + return histogram; + }; + function d3_layout_histogramBinSturges(range, values) { + return d3_layout_histogramBinFixed(range, Math.ceil(Math.log(values.length) / Math.LN2 + 1)); + } + function d3_layout_histogramBinFixed(range, n) { + var x = -1, b = +range[0], m = (range[1] - b) / n, f = []; + while (++x <= n) f[x] = m * x + b; + return f; + } + function d3_layout_histogramRange(values) { + return [ d3.min(values), d3.max(values) ]; + } + d3.layout.pack = function() { + var hierarchy = d3.layout.hierarchy().sort(d3_layout_packSort), padding = 0, size = [ 1, 1 ], radius; + function pack(d, i) { + var nodes = hierarchy.call(this, d, i), root = nodes[0], w = size[0], h = size[1], r = radius == null ? Math.sqrt : typeof radius === "function" ? radius : function() { + return radius; + }; + root.x = root.y = 0; + d3_layout_hierarchyVisitAfter(root, function(d) { + d.r = +r(d.value); + }); + d3_layout_hierarchyVisitAfter(root, d3_layout_packSiblings); + if (padding) { + var dr = padding * (radius ? 1 : Math.max(2 * root.r / w, 2 * root.r / h)) / 2; + d3_layout_hierarchyVisitAfter(root, function(d) { + d.r += dr; + }); + d3_layout_hierarchyVisitAfter(root, d3_layout_packSiblings); + d3_layout_hierarchyVisitAfter(root, function(d) { + d.r -= dr; + }); + } + d3_layout_packTransform(root, w / 2, h / 2, radius ? 1 : 1 / Math.max(2 * root.r / w, 2 * root.r / h)); + return nodes; + } + pack.size = function(_) { + if (!arguments.length) return size; + size = _; + return pack; + }; + pack.radius = function(_) { + if (!arguments.length) return radius; + radius = _ == null || typeof _ === "function" ? _ : +_; + return pack; + }; + pack.padding = function(_) { + if (!arguments.length) return padding; + padding = +_; + return pack; + }; + return d3_layout_hierarchyRebind(pack, hierarchy); + }; + function d3_layout_packSort(a, b) { + return a.value - b.value; + } + function d3_layout_packInsert(a, b) { + var c = a._pack_next; + a._pack_next = b; + b._pack_prev = a; + b._pack_next = c; + c._pack_prev = b; + } + function d3_layout_packSplice(a, b) { + a._pack_next = b; + b._pack_prev = a; + } + function d3_layout_packIntersects(a, b) { + var dx = b.x - a.x, dy = b.y - a.y, dr = a.r + b.r; + return .999 * dr * dr > dx * dx + dy * dy; + } + function d3_layout_packSiblings(node) { + if (!(nodes = node.children) || !(n = nodes.length)) return; + var nodes, xMin = Infinity, xMax = -Infinity, yMin = Infinity, yMax = -Infinity, a, b, c, i, j, k, n; + function bound(node) { + xMin = Math.min(node.x - node.r, xMin); + xMax = Math.max(node.x + node.r, xMax); + yMin = Math.min(node.y - node.r, yMin); + yMax = Math.max(node.y + node.r, yMax); + } + nodes.forEach(d3_layout_packLink); + a = nodes[0]; + a.x = -a.r; + a.y = 0; + bound(a); + if (n > 1) { + b = nodes[1]; + b.x = b.r; + b.y = 0; + bound(b); + if (n > 2) { + c = nodes[2]; + d3_layout_packPlace(a, b, c); + bound(c); + d3_layout_packInsert(a, c); + a._pack_prev = c; + d3_layout_packInsert(c, b); + b = a._pack_next; + for (i = 3; i < n; i++) { + d3_layout_packPlace(a, b, c = nodes[i]); + var isect = 0, s1 = 1, s2 = 1; + for (j = b._pack_next; j !== b; j = j._pack_next, s1++) { + if (d3_layout_packIntersects(j, c)) { + isect = 1; + break; + } + } + if (isect == 1) { + for (k = a._pack_prev; k !== j._pack_prev; k = k._pack_prev, s2++) { + if (d3_layout_packIntersects(k, c)) { + break; + } + } + } + if (isect) { + if (s1 < s2 || s1 == s2 && b.r < a.r) d3_layout_packSplice(a, b = j); else d3_layout_packSplice(a = k, b); + i--; + } else { + d3_layout_packInsert(a, c); + b = c; + bound(c); + } + } + } + } + var cx = (xMin + xMax) / 2, cy = (yMin + yMax) / 2, cr = 0; + for (i = 0; i < n; i++) { + c = nodes[i]; + c.x -= cx; + c.y -= cy; + cr = Math.max(cr, c.r + Math.sqrt(c.x * c.x + c.y * c.y)); + } + node.r = cr; + nodes.forEach(d3_layout_packUnlink); + } + function d3_layout_packLink(node) { + node._pack_next = node._pack_prev = node; + } + function d3_layout_packUnlink(node) { + delete node._pack_next; + delete node._pack_prev; + } + function d3_layout_packTransform(node, x, y, k) { + var children = node.children; + node.x = x += k * node.x; + node.y = y += k * node.y; + node.r *= k; + if (children) { + var i = -1, n = children.length; + while (++i < n) d3_layout_packTransform(children[i], x, y, k); + } + } + function d3_layout_packPlace(a, b, c) { + var db = a.r + c.r, dx = b.x - a.x, dy = b.y - a.y; + if (db && (dx || dy)) { + var da = b.r + c.r, dc = dx * dx + dy * dy; + da *= da; + db *= db; + var x = .5 + (db - da) / (2 * dc), y = Math.sqrt(Math.max(0, 2 * da * (db + dc) - (db -= dc) * db - da * da)) / (2 * dc); + c.x = a.x + x * dx + y * dy; + c.y = a.y + x * dy - y * dx; + } else { + c.x = a.x + db; + c.y = a.y; + } + } + d3.layout.tree = function() { + var hierarchy = d3.layout.hierarchy().sort(null).value(null), separation = d3_layout_treeSeparation, size = [ 1, 1 ], nodeSize = null; + function tree(d, i) { + var nodes = hierarchy.call(this, d, i), root0 = nodes[0], root1 = wrapTree(root0); + d3_layout_hierarchyVisitAfter(root1, firstWalk), root1.parent.m = -root1.z; + d3_layout_hierarchyVisitBefore(root1, secondWalk); + if (nodeSize) d3_layout_hierarchyVisitBefore(root0, sizeNode); else { + var left = root0, right = root0, bottom = root0; + d3_layout_hierarchyVisitBefore(root0, function(node) { + if (node.x < left.x) left = node; + if (node.x > right.x) right = node; + if (node.depth > bottom.depth) bottom = node; + }); + var tx = separation(left, right) / 2 - left.x, kx = size[0] / (right.x + separation(right, left) / 2 + tx), ky = size[1] / (bottom.depth || 1); + d3_layout_hierarchyVisitBefore(root0, function(node) { + node.x = (node.x + tx) * kx; + node.y = node.depth * ky; + }); + } + return nodes; + } + function wrapTree(root0) { + var root1 = { + A: null, + children: [ root0 ] + }, queue = [ root1 ], node1; + while ((node1 = queue.pop()) != null) { + for (var children = node1.children, child, i = 0, n = children.length; i < n; ++i) { + queue.push((children[i] = child = { + _: children[i], + parent: node1, + children: (child = children[i].children) && child.slice() || [], + A: null, + a: null, + z: 0, + m: 0, + c: 0, + s: 0, + t: null, + i: i + }).a = child); + } + } + return root1.children[0]; + } + function firstWalk(v) { + var children = v.children, siblings = v.parent.children, w = v.i ? siblings[v.i - 1] : null; + if (children.length) { + d3_layout_treeShift(v); + var midpoint = (children[0].z + children[children.length - 1].z) / 2; + if (w) { + v.z = w.z + separation(v._, w._); + v.m = v.z - midpoint; + } else { + v.z = midpoint; + } + } else if (w) { + v.z = w.z + separation(v._, w._); + } + v.parent.A = apportion(v, w, v.parent.A || siblings[0]); + } + function secondWalk(v) { + v._.x = v.z + v.parent.m; + v.m += v.parent.m; + } + function apportion(v, w, ancestor) { + if (w) { + var vip = v, vop = v, vim = w, vom = vip.parent.children[0], sip = vip.m, sop = vop.m, sim = vim.m, som = vom.m, shift; + while (vim = d3_layout_treeRight(vim), vip = d3_layout_treeLeft(vip), vim && vip) { + vom = d3_layout_treeLeft(vom); + vop = d3_layout_treeRight(vop); + vop.a = v; + shift = vim.z + sim - vip.z - sip + separation(vim._, vip._); + if (shift > 0) { + d3_layout_treeMove(d3_layout_treeAncestor(vim, v, ancestor), v, shift); + sip += shift; + sop += shift; + } + sim += vim.m; + sip += vip.m; + som += vom.m; + sop += vop.m; + } + if (vim && !d3_layout_treeRight(vop)) { + vop.t = vim; + vop.m += sim - sop; + } + if (vip && !d3_layout_treeLeft(vom)) { + vom.t = vip; + vom.m += sip - som; + ancestor = v; + } + } + return ancestor; + } + function sizeNode(node) { + node.x *= size[0]; + node.y = node.depth * size[1]; + } + tree.separation = function(x) { + if (!arguments.length) return separation; + separation = x; + return tree; + }; + tree.size = function(x) { + if (!arguments.length) return nodeSize ? null : size; + nodeSize = (size = x) == null ? sizeNode : null; + return tree; + }; + tree.nodeSize = function(x) { + if (!arguments.length) return nodeSize ? size : null; + nodeSize = (size = x) == null ? null : sizeNode; + return tree; + }; + return d3_layout_hierarchyRebind(tree, hierarchy); + }; + function d3_layout_treeSeparation(a, b) { + return a.parent == b.parent ? 1 : 2; + } + function d3_layout_treeLeft(v) { + var children = v.children; + return children.length ? children[0] : v.t; + } + function d3_layout_treeRight(v) { + var children = v.children, n; + return (n = children.length) ? children[n - 1] : v.t; + } + function d3_layout_treeMove(wm, wp, shift) { + var change = shift / (wp.i - wm.i); + wp.c -= change; + wp.s += shift; + wm.c += change; + wp.z += shift; + wp.m += shift; + } + function d3_layout_treeShift(v) { + var shift = 0, change = 0, children = v.children, i = children.length, w; + while (--i >= 0) { + w = children[i]; + w.z += shift; + w.m += shift; + shift += w.s + (change += w.c); + } + } + function d3_layout_treeAncestor(vim, v, ancestor) { + return vim.a.parent === v.parent ? vim.a : ancestor; + } + d3.layout.cluster = function() { + var hierarchy = d3.layout.hierarchy().sort(null).value(null), separation = d3_layout_treeSeparation, size = [ 1, 1 ], nodeSize = false; + function cluster(d, i) { + var nodes = hierarchy.call(this, d, i), root = nodes[0], previousNode, x = 0; + d3_layout_hierarchyVisitAfter(root, function(node) { + var children = node.children; + if (children && children.length) { + node.x = d3_layout_clusterX(children); + node.y = d3_layout_clusterY(children); + } else { + node.x = previousNode ? x += separation(node, previousNode) : 0; + node.y = 0; + previousNode = node; + } + }); + var left = d3_layout_clusterLeft(root), right = d3_layout_clusterRight(root), x0 = left.x - separation(left, right) / 2, x1 = right.x + separation(right, left) / 2; + d3_layout_hierarchyVisitAfter(root, nodeSize ? function(node) { + node.x = (node.x - root.x) * size[0]; + node.y = (root.y - node.y) * size[1]; + } : function(node) { + node.x = (node.x - x0) / (x1 - x0) * size[0]; + node.y = (1 - (root.y ? node.y / root.y : 1)) * size[1]; + }); + return nodes; + } + cluster.separation = function(x) { + if (!arguments.length) return separation; + separation = x; + return cluster; + }; + cluster.size = function(x) { + if (!arguments.length) return nodeSize ? null : size; + nodeSize = (size = x) == null; + return cluster; + }; + cluster.nodeSize = function(x) { + if (!arguments.length) return nodeSize ? size : null; + nodeSize = (size = x) != null; + return cluster; + }; + return d3_layout_hierarchyRebind(cluster, hierarchy); + }; + function d3_layout_clusterY(children) { + return 1 + d3.max(children, function(child) { + return child.y; + }); + } + function d3_layout_clusterX(children) { + return children.reduce(function(x, child) { + return x + child.x; + }, 0) / children.length; + } + function d3_layout_clusterLeft(node) { + var children = node.children; + return children && children.length ? d3_layout_clusterLeft(children[0]) : node; + } + function d3_layout_clusterRight(node) { + var children = node.children, n; + return children && (n = children.length) ? d3_layout_clusterRight(children[n - 1]) : node; + } + d3.layout.treemap = function() { + var hierarchy = d3.layout.hierarchy(), round = Math.round, size = [ 1, 1 ], padding = null, pad = d3_layout_treemapPadNull, sticky = false, stickies, mode = "squarify", ratio = .5 * (1 + Math.sqrt(5)); + function scale(children, k) { + var i = -1, n = children.length, child, area; + while (++i < n) { + area = (child = children[i]).value * (k < 0 ? 0 : k); + child.area = isNaN(area) || area <= 0 ? 0 : area; + } + } + function squarify(node) { + var children = node.children; + if (children && children.length) { + var rect = pad(node), row = [], remaining = children.slice(), child, best = Infinity, score, u = mode === "slice" ? rect.dx : mode === "dice" ? rect.dy : mode === "slice-dice" ? node.depth & 1 ? rect.dy : rect.dx : Math.min(rect.dx, rect.dy), n; + scale(remaining, rect.dx * rect.dy / node.value); + row.area = 0; + while ((n = remaining.length) > 0) { + row.push(child = remaining[n - 1]); + row.area += child.area; + if (mode !== "squarify" || (score = worst(row, u)) <= best) { + remaining.pop(); + best = score; + } else { + row.area -= row.pop().area; + position(row, u, rect, false); + u = Math.min(rect.dx, rect.dy); + row.length = row.area = 0; + best = Infinity; + } + } + if (row.length) { + position(row, u, rect, true); + row.length = row.area = 0; + } + children.forEach(squarify); + } + } + function stickify(node) { + var children = node.children; + if (children && children.length) { + var rect = pad(node), remaining = children.slice(), child, row = []; + scale(remaining, rect.dx * rect.dy / node.value); + row.area = 0; + while (child = remaining.pop()) { + row.push(child); + row.area += child.area; + if (child.z != null) { + position(row, child.z ? rect.dx : rect.dy, rect, !remaining.length); + row.length = row.area = 0; + } + } + children.forEach(stickify); + } + } + function worst(row, u) { + var s = row.area, r, rmax = 0, rmin = Infinity, i = -1, n = row.length; + while (++i < n) { + if (!(r = row[i].area)) continue; + if (r < rmin) rmin = r; + if (r > rmax) rmax = r; + } + s *= s; + u *= u; + return s ? Math.max(u * rmax * ratio / s, s / (u * rmin * ratio)) : Infinity; + } + function position(row, u, rect, flush) { + var i = -1, n = row.length, x = rect.x, y = rect.y, v = u ? round(row.area / u) : 0, o; + if (u == rect.dx) { + if (flush || v > rect.dy) v = rect.dy; + while (++i < n) { + o = row[i]; + o.x = x; + o.y = y; + o.dy = v; + x += o.dx = Math.min(rect.x + rect.dx - x, v ? round(o.area / v) : 0); + } + o.z = true; + o.dx += rect.x + rect.dx - x; + rect.y += v; + rect.dy -= v; + } else { + if (flush || v > rect.dx) v = rect.dx; + while (++i < n) { + o = row[i]; + o.x = x; + o.y = y; + o.dx = v; + y += o.dy = Math.min(rect.y + rect.dy - y, v ? round(o.area / v) : 0); + } + o.z = false; + o.dy += rect.y + rect.dy - y; + rect.x += v; + rect.dx -= v; + } + } + function treemap(d) { + var nodes = stickies || hierarchy(d), root = nodes[0]; + root.x = 0; + root.y = 0; + root.dx = size[0]; + root.dy = size[1]; + if (stickies) hierarchy.revalue(root); + scale([ root ], root.dx * root.dy / root.value); + (stickies ? stickify : squarify)(root); + if (sticky) stickies = nodes; + return nodes; + } + treemap.size = function(x) { + if (!arguments.length) return size; + size = x; + return treemap; + }; + treemap.padding = function(x) { + if (!arguments.length) return padding; + function padFunction(node) { + var p = x.call(treemap, node, node.depth); + return p == null ? d3_layout_treemapPadNull(node) : d3_layout_treemapPad(node, typeof p === "number" ? [ p, p, p, p ] : p); + } + function padConstant(node) { + return d3_layout_treemapPad(node, x); + } + var type; + pad = (padding = x) == null ? d3_layout_treemapPadNull : (type = typeof x) === "function" ? padFunction : type === "number" ? (x = [ x, x, x, x ], + padConstant) : padConstant; + return treemap; + }; + treemap.round = function(x) { + if (!arguments.length) return round != Number; + round = x ? Math.round : Number; + return treemap; + }; + treemap.sticky = function(x) { + if (!arguments.length) return sticky; + sticky = x; + stickies = null; + return treemap; + }; + treemap.ratio = function(x) { + if (!arguments.length) return ratio; + ratio = x; + return treemap; + }; + treemap.mode = function(x) { + if (!arguments.length) return mode; + mode = x + ""; + return treemap; + }; + return d3_layout_hierarchyRebind(treemap, hierarchy); + }; + function d3_layout_treemapPadNull(node) { + return { + x: node.x, + y: node.y, + dx: node.dx, + dy: node.dy + }; + } + function d3_layout_treemapPad(node, padding) { + var x = node.x + padding[3], y = node.y + padding[0], dx = node.dx - padding[1] - padding[3], dy = node.dy - padding[0] - padding[2]; + if (dx < 0) { + x += dx / 2; + dx = 0; + } + if (dy < 0) { + y += dy / 2; + dy = 0; + } + return { + x: x, + y: y, + dx: dx, + dy: dy + }; + } + d3.random = { + normal: function(µ, σ) { + var n = arguments.length; + if (n < 2) σ = 1; + if (n < 1) µ = 0; + return function() { + var x, y, r; + do { + x = Math.random() * 2 - 1; + y = Math.random() * 2 - 1; + r = x * x + y * y; + } while (!r || r > 1); + return µ + σ * x * Math.sqrt(-2 * Math.log(r) / r); + }; + }, + logNormal: function() { + var random = d3.random.normal.apply(d3, arguments); + return function() { + return Math.exp(random()); + }; + }, + bates: function(m) { + var random = d3.random.irwinHall(m); + return function() { + return random() / m; + }; + }, + irwinHall: function(m) { + return function() { + for (var s = 0, j = 0; j < m; j++) s += Math.random(); + return s; + }; + } + }; + d3.scale = {}; + function d3_scaleExtent(domain) { + var start = domain[0], stop = domain[domain.length - 1]; + return start < stop ? [ start, stop ] : [ stop, start ]; + } + function d3_scaleRange(scale) { + return scale.rangeExtent ? scale.rangeExtent() : d3_scaleExtent(scale.range()); + } + function d3_scale_bilinear(domain, range, uninterpolate, interpolate) { + var u = uninterpolate(domain[0], domain[1]), i = interpolate(range[0], range[1]); + return function(x) { + return i(u(x)); + }; + } + function d3_scale_nice(domain, nice) { + var i0 = 0, i1 = domain.length - 1, x0 = domain[i0], x1 = domain[i1], dx; + if (x1 < x0) { + dx = i0, i0 = i1, i1 = dx; + dx = x0, x0 = x1, x1 = dx; + } + domain[i0] = nice.floor(x0); + domain[i1] = nice.ceil(x1); + return domain; + } + function d3_scale_niceStep(step) { + return step ? { + floor: function(x) { + return Math.floor(x / step) * step; + }, + ceil: function(x) { + return Math.ceil(x / step) * step; + } + } : d3_scale_niceIdentity; + } + var d3_scale_niceIdentity = { + floor: d3_identity, + ceil: d3_identity + }; + function d3_scale_polylinear(domain, range, uninterpolate, interpolate) { + var u = [], i = [], j = 0, k = Math.min(domain.length, range.length) - 1; + if (domain[k] < domain[0]) { + domain = domain.slice().reverse(); + range = range.slice().reverse(); + } + while (++j <= k) { + u.push(uninterpolate(domain[j - 1], domain[j])); + i.push(interpolate(range[j - 1], range[j])); + } + return function(x) { + var j = d3.bisect(domain, x, 1, k) - 1; + return i[j](u[j](x)); + }; + } + d3.scale.linear = function() { + return d3_scale_linear([ 0, 1 ], [ 0, 1 ], d3_interpolate, false); + }; + function d3_scale_linear(domain, range, interpolate, clamp) { + var output, input; + function rescale() { + var linear = Math.min(domain.length, range.length) > 2 ? d3_scale_polylinear : d3_scale_bilinear, uninterpolate = clamp ? d3_uninterpolateClamp : d3_uninterpolateNumber; + output = linear(domain, range, uninterpolate, interpolate); + input = linear(range, domain, uninterpolate, d3_interpolate); + return scale; + } + function scale(x) { + return output(x); + } + scale.invert = function(y) { + return input(y); + }; + scale.domain = function(x) { + if (!arguments.length) return domain; + domain = x.map(Number); + return rescale(); + }; + scale.range = function(x) { + if (!arguments.length) return range; + range = x; + return rescale(); + }; + scale.rangeRound = function(x) { + return scale.range(x).interpolate(d3_interpolateRound); + }; + scale.clamp = function(x) { + if (!arguments.length) return clamp; + clamp = x; + return rescale(); + }; + scale.interpolate = function(x) { + if (!arguments.length) return interpolate; + interpolate = x; + return rescale(); + }; + scale.ticks = function(m) { + return d3_scale_linearTicks(domain, m); + }; + scale.tickFormat = function(m, format) { + return d3_scale_linearTickFormat(domain, m, format); + }; + scale.nice = function(m) { + d3_scale_linearNice(domain, m); + return rescale(); + }; + scale.copy = function() { + return d3_scale_linear(domain, range, interpolate, clamp); + }; + return rescale(); + } + function d3_scale_linearRebind(scale, linear) { + return d3.rebind(scale, linear, "range", "rangeRound", "interpolate", "clamp"); + } + function d3_scale_linearNice(domain, m) { + return d3_scale_nice(domain, d3_scale_niceStep(d3_scale_linearTickRange(domain, m)[2])); + } + function d3_scale_linearTickRange(domain, m) { + if (m == null) m = 10; + var extent = d3_scaleExtent(domain), span = extent[1] - extent[0], step = Math.pow(10, Math.floor(Math.log(span / m) / Math.LN10)), err = m / span * step; + if (err <= .15) step *= 10; else if (err <= .35) step *= 5; else if (err <= .75) step *= 2; + extent[0] = Math.ceil(extent[0] / step) * step; + extent[1] = Math.floor(extent[1] / step) * step + step * .5; + extent[2] = step; + return extent; + } + function d3_scale_linearTicks(domain, m) { + return d3.range.apply(d3, d3_scale_linearTickRange(domain, m)); + } + function d3_scale_linearTickFormat(domain, m, format) { + var range = d3_scale_linearTickRange(domain, m); + if (format) { + var match = d3_format_re.exec(format); + match.shift(); + if (match[8] === "s") { + var prefix = d3.formatPrefix(Math.max(abs(range[0]), abs(range[1]))); + if (!match[7]) match[7] = "." + d3_scale_linearPrecision(prefix.scale(range[2])); + match[8] = "f"; + format = d3.format(match.join("")); + return function(d) { + return format(prefix.scale(d)) + prefix.symbol; + }; + } + if (!match[7]) match[7] = "." + d3_scale_linearFormatPrecision(match[8], range); + format = match.join(""); + } else { + format = ",." + d3_scale_linearPrecision(range[2]) + "f"; + } + return d3.format(format); + } + var d3_scale_linearFormatSignificant = { + s: 1, + g: 1, + p: 1, + r: 1, + e: 1 + }; + function d3_scale_linearPrecision(value) { + return -Math.floor(Math.log(value) / Math.LN10 + .01); + } + function d3_scale_linearFormatPrecision(type, range) { + var p = d3_scale_linearPrecision(range[2]); + return type in d3_scale_linearFormatSignificant ? Math.abs(p - d3_scale_linearPrecision(Math.max(abs(range[0]), abs(range[1])))) + +(type !== "e") : p - (type === "%") * 2; + } + d3.scale.log = function() { + return d3_scale_log(d3.scale.linear().domain([ 0, 1 ]), 10, true, [ 1, 10 ]); + }; + function d3_scale_log(linear, base, positive, domain) { + function log(x) { + return (positive ? Math.log(x < 0 ? 0 : x) : -Math.log(x > 0 ? 0 : -x)) / Math.log(base); + } + function pow(x) { + return positive ? Math.pow(base, x) : -Math.pow(base, -x); + } + function scale(x) { + return linear(log(x)); + } + scale.invert = function(x) { + return pow(linear.invert(x)); + }; + scale.domain = function(x) { + if (!arguments.length) return domain; + positive = x[0] >= 0; + linear.domain((domain = x.map(Number)).map(log)); + return scale; + }; + scale.base = function(_) { + if (!arguments.length) return base; + base = +_; + linear.domain(domain.map(log)); + return scale; + }; + scale.nice = function() { + var niced = d3_scale_nice(domain.map(log), positive ? Math : d3_scale_logNiceNegative); + linear.domain(niced); + domain = niced.map(pow); + return scale; + }; + scale.ticks = function() { + var extent = d3_scaleExtent(domain), ticks = [], u = extent[0], v = extent[1], i = Math.floor(log(u)), j = Math.ceil(log(v)), n = base % 1 ? 2 : base; + if (isFinite(j - i)) { + if (positive) { + for (;i < j; i++) for (var k = 1; k < n; k++) ticks.push(pow(i) * k); + ticks.push(pow(i)); + } else { + ticks.push(pow(i)); + for (;i++ < j; ) for (var k = n - 1; k > 0; k--) ticks.push(pow(i) * k); + } + for (i = 0; ticks[i] < u; i++) {} + for (j = ticks.length; ticks[j - 1] > v; j--) {} + ticks = ticks.slice(i, j); + } + return ticks; + }; + scale.tickFormat = function(n, format) { + if (!arguments.length) return d3_scale_logFormat; + if (arguments.length < 2) format = d3_scale_logFormat; else if (typeof format !== "function") format = d3.format(format); + var k = Math.max(.1, n / scale.ticks().length), f = positive ? (e = 1e-12, Math.ceil) : (e = -1e-12, + Math.floor), e; + return function(d) { + return d / pow(f(log(d) + e)) <= k ? format(d) : ""; + }; + }; + scale.copy = function() { + return d3_scale_log(linear.copy(), base, positive, domain); + }; + return d3_scale_linearRebind(scale, linear); + } + var d3_scale_logFormat = d3.format(".0e"), d3_scale_logNiceNegative = { + floor: function(x) { + return -Math.ceil(-x); + }, + ceil: function(x) { + return -Math.floor(-x); + } + }; + d3.scale.pow = function() { + return d3_scale_pow(d3.scale.linear(), 1, [ 0, 1 ]); + }; + function d3_scale_pow(linear, exponent, domain) { + var powp = d3_scale_powPow(exponent), powb = d3_scale_powPow(1 / exponent); + function scale(x) { + return linear(powp(x)); + } + scale.invert = function(x) { + return powb(linear.invert(x)); + }; + scale.domain = function(x) { + if (!arguments.length) return domain; + linear.domain((domain = x.map(Number)).map(powp)); + return scale; + }; + scale.ticks = function(m) { + return d3_scale_linearTicks(domain, m); + }; + scale.tickFormat = function(m, format) { + return d3_scale_linearTickFormat(domain, m, format); + }; + scale.nice = function(m) { + return scale.domain(d3_scale_linearNice(domain, m)); + }; + scale.exponent = function(x) { + if (!arguments.length) return exponent; + powp = d3_scale_powPow(exponent = x); + powb = d3_scale_powPow(1 / exponent); + linear.domain(domain.map(powp)); + return scale; + }; + scale.copy = function() { + return d3_scale_pow(linear.copy(), exponent, domain); + }; + return d3_scale_linearRebind(scale, linear); + } + function d3_scale_powPow(e) { + return function(x) { + return x < 0 ? -Math.pow(-x, e) : Math.pow(x, e); + }; + } + d3.scale.sqrt = function() { + return d3.scale.pow().exponent(.5); + }; + d3.scale.ordinal = function() { + return d3_scale_ordinal([], { + t: "range", + a: [ [] ] + }); + }; + function d3_scale_ordinal(domain, ranger) { + var index, range, rangeBand; + function scale(x) { + return range[((index.get(x) || (ranger.t === "range" ? index.set(x, domain.push(x)) : NaN)) - 1) % range.length]; + } + function steps(start, step) { + return d3.range(domain.length).map(function(i) { + return start + step * i; + }); + } + scale.domain = function(x) { + if (!arguments.length) return domain; + domain = []; + index = new d3_Map(); + var i = -1, n = x.length, xi; + while (++i < n) if (!index.has(xi = x[i])) index.set(xi, domain.push(xi)); + return scale[ranger.t].apply(scale, ranger.a); + }; + scale.range = function(x) { + if (!arguments.length) return range; + range = x; + rangeBand = 0; + ranger = { + t: "range", + a: arguments + }; + return scale; + }; + scale.rangePoints = function(x, padding) { + if (arguments.length < 2) padding = 0; + var start = x[0], stop = x[1], step = domain.length < 2 ? (start = (start + stop) / 2, + 0) : (stop - start) / (domain.length - 1 + padding); + range = steps(start + step * padding / 2, step); + rangeBand = 0; + ranger = { + t: "rangePoints", + a: arguments + }; + return scale; + }; + scale.rangeRoundPoints = function(x, padding) { + if (arguments.length < 2) padding = 0; + var start = x[0], stop = x[1], step = domain.length < 2 ? (start = stop = Math.round((start + stop) / 2), + 0) : (stop - start) / (domain.length - 1 + padding) | 0; + range = steps(start + Math.round(step * padding / 2 + (stop - start - (domain.length - 1 + padding) * step) / 2), step); + rangeBand = 0; + ranger = { + t: "rangeRoundPoints", + a: arguments + }; + return scale; + }; + scale.rangeBands = function(x, padding, outerPadding) { + if (arguments.length < 2) padding = 0; + if (arguments.length < 3) outerPadding = padding; + var reverse = x[1] < x[0], start = x[reverse - 0], stop = x[1 - reverse], step = (stop - start) / (domain.length - padding + 2 * outerPadding); + range = steps(start + step * outerPadding, step); + if (reverse) range.reverse(); + rangeBand = step * (1 - padding); + ranger = { + t: "rangeBands", + a: arguments + }; + return scale; + }; + scale.rangeRoundBands = function(x, padding, outerPadding) { + if (arguments.length < 2) padding = 0; + if (arguments.length < 3) outerPadding = padding; + var reverse = x[1] < x[0], start = x[reverse - 0], stop = x[1 - reverse], step = Math.floor((stop - start) / (domain.length - padding + 2 * outerPadding)); + range = steps(start + Math.round((stop - start - (domain.length - padding) * step) / 2), step); + if (reverse) range.reverse(); + rangeBand = Math.round(step * (1 - padding)); + ranger = { + t: "rangeRoundBands", + a: arguments + }; + return scale; + }; + scale.rangeBand = function() { + return rangeBand; + }; + scale.rangeExtent = function() { + return d3_scaleExtent(ranger.a[0]); + }; + scale.copy = function() { + return d3_scale_ordinal(domain, ranger); + }; + return scale.domain(domain); + } + d3.scale.category10 = function() { + return d3.scale.ordinal().range(d3_category10); + }; + d3.scale.category20 = function() { + return d3.scale.ordinal().range(d3_category20); + }; + d3.scale.category20b = function() { + return d3.scale.ordinal().range(d3_category20b); + }; + d3.scale.category20c = function() { + return d3.scale.ordinal().range(d3_category20c); + }; + var d3_category10 = [ 2062260, 16744206, 2924588, 14034728, 9725885, 9197131, 14907330, 8355711, 12369186, 1556175 ].map(d3_rgbString); + var d3_category20 = [ 2062260, 11454440, 16744206, 16759672, 2924588, 10018698, 14034728, 16750742, 9725885, 12955861, 9197131, 12885140, 14907330, 16234194, 8355711, 13092807, 12369186, 14408589, 1556175, 10410725 ].map(d3_rgbString); + var d3_category20b = [ 3750777, 5395619, 7040719, 10264286, 6519097, 9216594, 11915115, 13556636, 9202993, 12426809, 15186514, 15190932, 8666169, 11356490, 14049643, 15177372, 8077683, 10834324, 13528509, 14589654 ].map(d3_rgbString); + var d3_category20c = [ 3244733, 7057110, 10406625, 13032431, 15095053, 16616764, 16625259, 16634018, 3253076, 7652470, 10607003, 13101504, 7695281, 10394312, 12369372, 14342891, 6513507, 9868950, 12434877, 14277081 ].map(d3_rgbString); + d3.scale.quantile = function() { + return d3_scale_quantile([], []); + }; + function d3_scale_quantile(domain, range) { + var thresholds; + function rescale() { + var k = 0, q = range.length; + thresholds = []; + while (++k < q) thresholds[k - 1] = d3.quantile(domain, k / q); + return scale; + } + function scale(x) { + if (!isNaN(x = +x)) return range[d3.bisect(thresholds, x)]; + } + scale.domain = function(x) { + if (!arguments.length) return domain; + domain = x.map(d3_number).filter(d3_numeric).sort(d3_ascending); + return rescale(); + }; + scale.range = function(x) { + if (!arguments.length) return range; + range = x; + return rescale(); + }; + scale.quantiles = function() { + return thresholds; + }; + scale.invertExtent = function(y) { + y = range.indexOf(y); + return y < 0 ? [ NaN, NaN ] : [ y > 0 ? thresholds[y - 1] : domain[0], y < thresholds.length ? thresholds[y] : domain[domain.length - 1] ]; + }; + scale.copy = function() { + return d3_scale_quantile(domain, range); + }; + return rescale(); + } + d3.scale.quantize = function() { + return d3_scale_quantize(0, 1, [ 0, 1 ]); + }; + function d3_scale_quantize(x0, x1, range) { + var kx, i; + function scale(x) { + return range[Math.max(0, Math.min(i, Math.floor(kx * (x - x0))))]; + } + function rescale() { + kx = range.length / (x1 - x0); + i = range.length - 1; + return scale; + } + scale.domain = function(x) { + if (!arguments.length) return [ x0, x1 ]; + x0 = +x[0]; + x1 = +x[x.length - 1]; + return rescale(); + }; + scale.range = function(x) { + if (!arguments.length) return range; + range = x; + return rescale(); + }; + scale.invertExtent = function(y) { + y = range.indexOf(y); + y = y < 0 ? NaN : y / kx + x0; + return [ y, y + 1 / kx ]; + }; + scale.copy = function() { + return d3_scale_quantize(x0, x1, range); + }; + return rescale(); + } + d3.scale.threshold = function() { + return d3_scale_threshold([ .5 ], [ 0, 1 ]); + }; + function d3_scale_threshold(domain, range) { + function scale(x) { + if (x <= x) return range[d3.bisect(domain, x)]; + } + scale.domain = function(_) { + if (!arguments.length) return domain; + domain = _; + return scale; + }; + scale.range = function(_) { + if (!arguments.length) return range; + range = _; + return scale; + }; + scale.invertExtent = function(y) { + y = range.indexOf(y); + return [ domain[y - 1], domain[y] ]; + }; + scale.copy = function() { + return d3_scale_threshold(domain, range); + }; + return scale; + } + d3.scale.identity = function() { + return d3_scale_identity([ 0, 1 ]); + }; + function d3_scale_identity(domain) { + function identity(x) { + return +x; + } + identity.invert = identity; + identity.domain = identity.range = function(x) { + if (!arguments.length) return domain; + domain = x.map(identity); + return identity; + }; + identity.ticks = function(m) { + return d3_scale_linearTicks(domain, m); + }; + identity.tickFormat = function(m, format) { + return d3_scale_linearTickFormat(domain, m, format); + }; + identity.copy = function() { + return d3_scale_identity(domain); + }; + return identity; + } + d3.svg = {}; + function d3_zero() { + return 0; + } + d3.svg.arc = function() { + var innerRadius = d3_svg_arcInnerRadius, outerRadius = d3_svg_arcOuterRadius, cornerRadius = d3_zero, padRadius = d3_svg_arcAuto, startAngle = d3_svg_arcStartAngle, endAngle = d3_svg_arcEndAngle, padAngle = d3_svg_arcPadAngle; + function arc() { + var r0 = Math.max(0, +innerRadius.apply(this, arguments)), r1 = Math.max(0, +outerRadius.apply(this, arguments)), a0 = startAngle.apply(this, arguments) - halfπ, a1 = endAngle.apply(this, arguments) - halfπ, da = Math.abs(a1 - a0), cw = a0 > a1 ? 0 : 1; + if (r1 < r0) rc = r1, r1 = r0, r0 = rc; + if (da >= τε) return circleSegment(r1, cw) + (r0 ? circleSegment(r0, 1 - cw) : "") + "Z"; + var rc, cr, rp, ap, p0 = 0, p1 = 0, x0, y0, x1, y1, x2, y2, x3, y3, path = []; + if (ap = (+padAngle.apply(this, arguments) || 0) / 2) { + rp = padRadius === d3_svg_arcAuto ? Math.sqrt(r0 * r0 + r1 * r1) : +padRadius.apply(this, arguments); + if (!cw) p1 *= -1; + if (r1) p1 = d3_asin(rp / r1 * Math.sin(ap)); + if (r0) p0 = d3_asin(rp / r0 * Math.sin(ap)); + } + if (r1) { + x0 = r1 * Math.cos(a0 + p1); + y0 = r1 * Math.sin(a0 + p1); + x1 = r1 * Math.cos(a1 - p1); + y1 = r1 * Math.sin(a1 - p1); + var l1 = Math.abs(a1 - a0 - 2 * p1) <= π ? 0 : 1; + if (p1 && d3_svg_arcSweep(x0, y0, x1, y1) === cw ^ l1) { + var h1 = (a0 + a1) / 2; + x0 = r1 * Math.cos(h1); + y0 = r1 * Math.sin(h1); + x1 = y1 = null; + } + } else { + x0 = y0 = 0; + } + if (r0) { + x2 = r0 * Math.cos(a1 - p0); + y2 = r0 * Math.sin(a1 - p0); + x3 = r0 * Math.cos(a0 + p0); + y3 = r0 * Math.sin(a0 + p0); + var l0 = Math.abs(a0 - a1 + 2 * p0) <= π ? 0 : 1; + if (p0 && d3_svg_arcSweep(x2, y2, x3, y3) === 1 - cw ^ l0) { + var h0 = (a0 + a1) / 2; + x2 = r0 * Math.cos(h0); + y2 = r0 * Math.sin(h0); + x3 = y3 = null; + } + } else { + x2 = y2 = 0; + } + if ((rc = Math.min(Math.abs(r1 - r0) / 2, +cornerRadius.apply(this, arguments))) > .001) { + cr = r0 < r1 ^ cw ? 0 : 1; + var oc = x3 == null ? [ x2, y2 ] : x1 == null ? [ x0, y0 ] : d3_geom_polygonIntersect([ x0, y0 ], [ x3, y3 ], [ x1, y1 ], [ x2, y2 ]), ax = x0 - oc[0], ay = y0 - oc[1], bx = x1 - oc[0], by = y1 - oc[1], kc = 1 / Math.sin(Math.acos((ax * bx + ay * by) / (Math.sqrt(ax * ax + ay * ay) * Math.sqrt(bx * bx + by * by))) / 2), lc = Math.sqrt(oc[0] * oc[0] + oc[1] * oc[1]); + if (x1 != null) { + var rc1 = Math.min(rc, (r1 - lc) / (kc + 1)), t30 = d3_svg_arcCornerTangents(x3 == null ? [ x2, y2 ] : [ x3, y3 ], [ x0, y0 ], r1, rc1, cw), t12 = d3_svg_arcCornerTangents([ x1, y1 ], [ x2, y2 ], r1, rc1, cw); + if (rc === rc1) { + path.push("M", t30[0], "A", rc1, ",", rc1, " 0 0,", cr, " ", t30[1], "A", r1, ",", r1, " 0 ", 1 - cw ^ d3_svg_arcSweep(t30[1][0], t30[1][1], t12[1][0], t12[1][1]), ",", cw, " ", t12[1], "A", rc1, ",", rc1, " 0 0,", cr, " ", t12[0]); + } else { + path.push("M", t30[0], "A", rc1, ",", rc1, " 0 1,", cr, " ", t12[0]); + } + } else { + path.push("M", x0, ",", y0); + } + if (x3 != null) { + var rc0 = Math.min(rc, (r0 - lc) / (kc - 1)), t03 = d3_svg_arcCornerTangents([ x0, y0 ], [ x3, y3 ], r0, -rc0, cw), t21 = d3_svg_arcCornerTangents([ x2, y2 ], x1 == null ? [ x0, y0 ] : [ x1, y1 ], r0, -rc0, cw); + if (rc === rc0) { + path.push("L", t21[0], "A", rc0, ",", rc0, " 0 0,", cr, " ", t21[1], "A", r0, ",", r0, " 0 ", cw ^ d3_svg_arcSweep(t21[1][0], t21[1][1], t03[1][0], t03[1][1]), ",", 1 - cw, " ", t03[1], "A", rc0, ",", rc0, " 0 0,", cr, " ", t03[0]); + } else { + path.push("L", t21[0], "A", rc0, ",", rc0, " 0 0,", cr, " ", t03[0]); + } + } else { + path.push("L", x2, ",", y2); + } + } else { + path.push("M", x0, ",", y0); + if (x1 != null) path.push("A", r1, ",", r1, " 0 ", l1, ",", cw, " ", x1, ",", y1); + path.push("L", x2, ",", y2); + if (x3 != null) path.push("A", r0, ",", r0, " 0 ", l0, ",", 1 - cw, " ", x3, ",", y3); + } + path.push("Z"); + return path.join(""); + } + function circleSegment(r1, cw) { + return "M0," + r1 + "A" + r1 + "," + r1 + " 0 1," + cw + " 0," + -r1 + "A" + r1 + "," + r1 + " 0 1," + cw + " 0," + r1; + } + arc.innerRadius = function(v) { + if (!arguments.length) return innerRadius; + innerRadius = d3_functor(v); + return arc; + }; + arc.outerRadius = function(v) { + if (!arguments.length) return outerRadius; + outerRadius = d3_functor(v); + return arc; + }; + arc.cornerRadius = function(v) { + if (!arguments.length) return cornerRadius; + cornerRadius = d3_functor(v); + return arc; + }; + arc.padRadius = function(v) { + if (!arguments.length) return padRadius; + padRadius = v == d3_svg_arcAuto ? d3_svg_arcAuto : d3_functor(v); + return arc; + }; + arc.startAngle = function(v) { + if (!arguments.length) return startAngle; + startAngle = d3_functor(v); + return arc; + }; + arc.endAngle = function(v) { + if (!arguments.length) return endAngle; + endAngle = d3_functor(v); + return arc; + }; + arc.padAngle = function(v) { + if (!arguments.length) return padAngle; + padAngle = d3_functor(v); + return arc; + }; + arc.centroid = function() { + var r = (+innerRadius.apply(this, arguments) + +outerRadius.apply(this, arguments)) / 2, a = (+startAngle.apply(this, arguments) + +endAngle.apply(this, arguments)) / 2 - halfπ; + return [ Math.cos(a) * r, Math.sin(a) * r ]; + }; + return arc; + }; + var d3_svg_arcAuto = "auto"; + function d3_svg_arcInnerRadius(d) { + return d.innerRadius; + } + function d3_svg_arcOuterRadius(d) { + return d.outerRadius; + } + function d3_svg_arcStartAngle(d) { + return d.startAngle; + } + function d3_svg_arcEndAngle(d) { + return d.endAngle; + } + function d3_svg_arcPadAngle(d) { + return d && d.padAngle; + } + function d3_svg_arcSweep(x0, y0, x1, y1) { + return (x0 - x1) * y0 - (y0 - y1) * x0 > 0 ? 0 : 1; + } + function d3_svg_arcCornerTangents(p0, p1, r1, rc, cw) { + var x01 = p0[0] - p1[0], y01 = p0[1] - p1[1], lo = (cw ? rc : -rc) / Math.sqrt(x01 * x01 + y01 * y01), ox = lo * y01, oy = -lo * x01, x1 = p0[0] + ox, y1 = p0[1] + oy, x2 = p1[0] + ox, y2 = p1[1] + oy, x3 = (x1 + x2) / 2, y3 = (y1 + y2) / 2, dx = x2 - x1, dy = y2 - y1, d2 = dx * dx + dy * dy, r = r1 - rc, D = x1 * y2 - x2 * y1, d = (dy < 0 ? -1 : 1) * Math.sqrt(r * r * d2 - D * D), cx0 = (D * dy - dx * d) / d2, cy0 = (-D * dx - dy * d) / d2, cx1 = (D * dy + dx * d) / d2, cy1 = (-D * dx + dy * d) / d2, dx0 = cx0 - x3, dy0 = cy0 - y3, dx1 = cx1 - x3, dy1 = cy1 - y3; + if (dx0 * dx0 + dy0 * dy0 > dx1 * dx1 + dy1 * dy1) cx0 = cx1, cy0 = cy1; + return [ [ cx0 - ox, cy0 - oy ], [ cx0 * r1 / r, cy0 * r1 / r ] ]; + } + function d3_svg_line(projection) { + var x = d3_geom_pointX, y = d3_geom_pointY, defined = d3_true, interpolate = d3_svg_lineLinear, interpolateKey = interpolate.key, tension = .7; + function line(data) { + var segments = [], points = [], i = -1, n = data.length, d, fx = d3_functor(x), fy = d3_functor(y); + function segment() { + segments.push("M", interpolate(projection(points), tension)); + } + while (++i < n) { + if (defined.call(this, d = data[i], i)) { + points.push([ +fx.call(this, d, i), +fy.call(this, d, i) ]); + } else if (points.length) { + segment(); + points = []; + } + } + if (points.length) segment(); + return segments.length ? segments.join("") : null; + } + line.x = function(_) { + if (!arguments.length) return x; + x = _; + return line; + }; + line.y = function(_) { + if (!arguments.length) return y; + y = _; + return line; + }; + line.defined = function(_) { + if (!arguments.length) return defined; + defined = _; + return line; + }; + line.interpolate = function(_) { + if (!arguments.length) return interpolateKey; + if (typeof _ === "function") interpolateKey = interpolate = _; else interpolateKey = (interpolate = d3_svg_lineInterpolators.get(_) || d3_svg_lineLinear).key; + return line; + }; + line.tension = function(_) { + if (!arguments.length) return tension; + tension = _; + return line; + }; + return line; + } + d3.svg.line = function() { + return d3_svg_line(d3_identity); + }; + var d3_svg_lineInterpolators = d3.map({ + linear: d3_svg_lineLinear, + "linear-closed": d3_svg_lineLinearClosed, + step: d3_svg_lineStep, + "step-before": d3_svg_lineStepBefore, + "step-after": d3_svg_lineStepAfter, + basis: d3_svg_lineBasis, + "basis-open": d3_svg_lineBasisOpen, + "basis-closed": d3_svg_lineBasisClosed, + bundle: d3_svg_lineBundle, + cardinal: d3_svg_lineCardinal, + "cardinal-open": d3_svg_lineCardinalOpen, + "cardinal-closed": d3_svg_lineCardinalClosed, + monotone: d3_svg_lineMonotone + }); + d3_svg_lineInterpolators.forEach(function(key, value) { + value.key = key; + value.closed = /-closed$/.test(key); + }); + function d3_svg_lineLinear(points) { + return points.join("L"); + } + function d3_svg_lineLinearClosed(points) { + return d3_svg_lineLinear(points) + "Z"; + } + function d3_svg_lineStep(points) { + var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ]; + while (++i < n) path.push("H", (p[0] + (p = points[i])[0]) / 2, "V", p[1]); + if (n > 1) path.push("H", p[0]); + return path.join(""); + } + function d3_svg_lineStepBefore(points) { + var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ]; + while (++i < n) path.push("V", (p = points[i])[1], "H", p[0]); + return path.join(""); + } + function d3_svg_lineStepAfter(points) { + var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ]; + while (++i < n) path.push("H", (p = points[i])[0], "V", p[1]); + return path.join(""); + } + function d3_svg_lineCardinalOpen(points, tension) { + return points.length < 4 ? d3_svg_lineLinear(points) : points[1] + d3_svg_lineHermite(points.slice(1, -1), d3_svg_lineCardinalTangents(points, tension)); + } + function d3_svg_lineCardinalClosed(points, tension) { + return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite((points.push(points[0]), + points), d3_svg_lineCardinalTangents([ points[points.length - 2] ].concat(points, [ points[1] ]), tension)); + } + function d3_svg_lineCardinal(points, tension) { + return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite(points, d3_svg_lineCardinalTangents(points, tension)); + } + function d3_svg_lineHermite(points, tangents) { + if (tangents.length < 1 || points.length != tangents.length && points.length != tangents.length + 2) { + return d3_svg_lineLinear(points); + } + var quad = points.length != tangents.length, path = "", p0 = points[0], p = points[1], t0 = tangents[0], t = t0, pi = 1; + if (quad) { + path += "Q" + (p[0] - t0[0] * 2 / 3) + "," + (p[1] - t0[1] * 2 / 3) + "," + p[0] + "," + p[1]; + p0 = points[1]; + pi = 2; + } + if (tangents.length > 1) { + t = tangents[1]; + p = points[pi]; + pi++; + path += "C" + (p0[0] + t0[0]) + "," + (p0[1] + t0[1]) + "," + (p[0] - t[0]) + "," + (p[1] - t[1]) + "," + p[0] + "," + p[1]; + for (var i = 2; i < tangents.length; i++, pi++) { + p = points[pi]; + t = tangents[i]; + path += "S" + (p[0] - t[0]) + "," + (p[1] - t[1]) + "," + p[0] + "," + p[1]; + } + } + if (quad) { + var lp = points[pi]; + path += "Q" + (p[0] + t[0] * 2 / 3) + "," + (p[1] + t[1] * 2 / 3) + "," + lp[0] + "," + lp[1]; + } + return path; + } + function d3_svg_lineCardinalTangents(points, tension) { + var tangents = [], a = (1 - tension) / 2, p0, p1 = points[0], p2 = points[1], i = 1, n = points.length; + while (++i < n) { + p0 = p1; + p1 = p2; + p2 = points[i]; + tangents.push([ a * (p2[0] - p0[0]), a * (p2[1] - p0[1]) ]); + } + return tangents; + } + function d3_svg_lineBasis(points) { + if (points.length < 3) return d3_svg_lineLinear(points); + var i = 1, n = points.length, pi = points[0], x0 = pi[0], y0 = pi[1], px = [ x0, x0, x0, (pi = points[1])[0] ], py = [ y0, y0, y0, pi[1] ], path = [ x0, ",", y0, "L", d3_svg_lineDot4(d3_svg_lineBasisBezier3, px), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, py) ]; + points.push(points[n - 1]); + while (++i <= n) { + pi = points[i]; + px.shift(); + px.push(pi[0]); + py.shift(); + py.push(pi[1]); + d3_svg_lineBasisBezier(path, px, py); + } + points.pop(); + path.push("L", pi); + return path.join(""); + } + function d3_svg_lineBasisOpen(points) { + if (points.length < 4) return d3_svg_lineLinear(points); + var path = [], i = -1, n = points.length, pi, px = [ 0 ], py = [ 0 ]; + while (++i < 3) { + pi = points[i]; + px.push(pi[0]); + py.push(pi[1]); + } + path.push(d3_svg_lineDot4(d3_svg_lineBasisBezier3, px) + "," + d3_svg_lineDot4(d3_svg_lineBasisBezier3, py)); + --i; + while (++i < n) { + pi = points[i]; + px.shift(); + px.push(pi[0]); + py.shift(); + py.push(pi[1]); + d3_svg_lineBasisBezier(path, px, py); + } + return path.join(""); + } + function d3_svg_lineBasisClosed(points) { + var path, i = -1, n = points.length, m = n + 4, pi, px = [], py = []; + while (++i < 4) { + pi = points[i % n]; + px.push(pi[0]); + py.push(pi[1]); + } + path = [ d3_svg_lineDot4(d3_svg_lineBasisBezier3, px), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, py) ]; + --i; + while (++i < m) { + pi = points[i % n]; + px.shift(); + px.push(pi[0]); + py.shift(); + py.push(pi[1]); + d3_svg_lineBasisBezier(path, px, py); + } + return path.join(""); + } + function d3_svg_lineBundle(points, tension) { + var n = points.length - 1; + if (n) { + var x0 = points[0][0], y0 = points[0][1], dx = points[n][0] - x0, dy = points[n][1] - y0, i = -1, p, t; + while (++i <= n) { + p = points[i]; + t = i / n; + p[0] = tension * p[0] + (1 - tension) * (x0 + t * dx); + p[1] = tension * p[1] + (1 - tension) * (y0 + t * dy); + } + } + return d3_svg_lineBasis(points); + } + function d3_svg_lineDot4(a, b) { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3]; + } + var d3_svg_lineBasisBezier1 = [ 0, 2 / 3, 1 / 3, 0 ], d3_svg_lineBasisBezier2 = [ 0, 1 / 3, 2 / 3, 0 ], d3_svg_lineBasisBezier3 = [ 0, 1 / 6, 2 / 3, 1 / 6 ]; + function d3_svg_lineBasisBezier(path, x, y) { + path.push("C", d3_svg_lineDot4(d3_svg_lineBasisBezier1, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier1, y), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, y), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, y)); + } + function d3_svg_lineSlope(p0, p1) { + return (p1[1] - p0[1]) / (p1[0] - p0[0]); + } + function d3_svg_lineFiniteDifferences(points) { + var i = 0, j = points.length - 1, m = [], p0 = points[0], p1 = points[1], d = m[0] = d3_svg_lineSlope(p0, p1); + while (++i < j) { + m[i] = (d + (d = d3_svg_lineSlope(p0 = p1, p1 = points[i + 1]))) / 2; + } + m[i] = d; + return m; + } + function d3_svg_lineMonotoneTangents(points) { + var tangents = [], d, a, b, s, m = d3_svg_lineFiniteDifferences(points), i = -1, j = points.length - 1; + while (++i < j) { + d = d3_svg_lineSlope(points[i], points[i + 1]); + if (abs(d) < ε) { + m[i] = m[i + 1] = 0; + } else { + a = m[i] / d; + b = m[i + 1] / d; + s = a * a + b * b; + if (s > 9) { + s = d * 3 / Math.sqrt(s); + m[i] = s * a; + m[i + 1] = s * b; + } + } + } + i = -1; + while (++i <= j) { + s = (points[Math.min(j, i + 1)][0] - points[Math.max(0, i - 1)][0]) / (6 * (1 + m[i] * m[i])); + tangents.push([ s || 0, m[i] * s || 0 ]); + } + return tangents; + } + function d3_svg_lineMonotone(points) { + return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite(points, d3_svg_lineMonotoneTangents(points)); + } + d3.svg.line.radial = function() { + var line = d3_svg_line(d3_svg_lineRadial); + line.radius = line.x, delete line.x; + line.angle = line.y, delete line.y; + return line; + }; + function d3_svg_lineRadial(points) { + var point, i = -1, n = points.length, r, a; + while (++i < n) { + point = points[i]; + r = point[0]; + a = point[1] - halfπ; + point[0] = r * Math.cos(a); + point[1] = r * Math.sin(a); + } + return points; + } + function d3_svg_area(projection) { + var x0 = d3_geom_pointX, x1 = d3_geom_pointX, y0 = 0, y1 = d3_geom_pointY, defined = d3_true, interpolate = d3_svg_lineLinear, interpolateKey = interpolate.key, interpolateReverse = interpolate, L = "L", tension = .7; + function area(data) { + var segments = [], points0 = [], points1 = [], i = -1, n = data.length, d, fx0 = d3_functor(x0), fy0 = d3_functor(y0), fx1 = x0 === x1 ? function() { + return x; + } : d3_functor(x1), fy1 = y0 === y1 ? function() { + return y; + } : d3_functor(y1), x, y; + function segment() { + segments.push("M", interpolate(projection(points1), tension), L, interpolateReverse(projection(points0.reverse()), tension), "Z"); + } + while (++i < n) { + if (defined.call(this, d = data[i], i)) { + points0.push([ x = +fx0.call(this, d, i), y = +fy0.call(this, d, i) ]); + points1.push([ +fx1.call(this, d, i), +fy1.call(this, d, i) ]); + } else if (points0.length) { + segment(); + points0 = []; + points1 = []; + } + } + if (points0.length) segment(); + return segments.length ? segments.join("") : null; + } + area.x = function(_) { + if (!arguments.length) return x1; + x0 = x1 = _; + return area; + }; + area.x0 = function(_) { + if (!arguments.length) return x0; + x0 = _; + return area; + }; + area.x1 = function(_) { + if (!arguments.length) return x1; + x1 = _; + return area; + }; + area.y = function(_) { + if (!arguments.length) return y1; + y0 = y1 = _; + return area; + }; + area.y0 = function(_) { + if (!arguments.length) return y0; + y0 = _; + return area; + }; + area.y1 = function(_) { + if (!arguments.length) return y1; + y1 = _; + return area; + }; + area.defined = function(_) { + if (!arguments.length) return defined; + defined = _; + return area; + }; + area.interpolate = function(_) { + if (!arguments.length) return interpolateKey; + if (typeof _ === "function") interpolateKey = interpolate = _; else interpolateKey = (interpolate = d3_svg_lineInterpolators.get(_) || d3_svg_lineLinear).key; + interpolateReverse = interpolate.reverse || interpolate; + L = interpolate.closed ? "M" : "L"; + return area; + }; + area.tension = function(_) { + if (!arguments.length) return tension; + tension = _; + return area; + }; + return area; + } + d3_svg_lineStepBefore.reverse = d3_svg_lineStepAfter; + d3_svg_lineStepAfter.reverse = d3_svg_lineStepBefore; + d3.svg.area = function() { + return d3_svg_area(d3_identity); + }; + d3.svg.area.radial = function() { + var area = d3_svg_area(d3_svg_lineRadial); + area.radius = area.x, delete area.x; + area.innerRadius = area.x0, delete area.x0; + area.outerRadius = area.x1, delete area.x1; + area.angle = area.y, delete area.y; + area.startAngle = area.y0, delete area.y0; + area.endAngle = area.y1, delete area.y1; + return area; + }; + d3.svg.chord = function() { + var source = d3_source, target = d3_target, radius = d3_svg_chordRadius, startAngle = d3_svg_arcStartAngle, endAngle = d3_svg_arcEndAngle; + function chord(d, i) { + var s = subgroup(this, source, d, i), t = subgroup(this, target, d, i); + return "M" + s.p0 + arc(s.r, s.p1, s.a1 - s.a0) + (equals(s, t) ? curve(s.r, s.p1, s.r, s.p0) : curve(s.r, s.p1, t.r, t.p0) + arc(t.r, t.p1, t.a1 - t.a0) + curve(t.r, t.p1, s.r, s.p0)) + "Z"; + } + function subgroup(self, f, d, i) { + var subgroup = f.call(self, d, i), r = radius.call(self, subgroup, i), a0 = startAngle.call(self, subgroup, i) - halfπ, a1 = endAngle.call(self, subgroup, i) - halfπ; + return { + r: r, + a0: a0, + a1: a1, + p0: [ r * Math.cos(a0), r * Math.sin(a0) ], + p1: [ r * Math.cos(a1), r * Math.sin(a1) ] + }; + } + function equals(a, b) { + return a.a0 == b.a0 && a.a1 == b.a1; + } + function arc(r, p, a) { + return "A" + r + "," + r + " 0 " + +(a > π) + ",1 " + p; + } + function curve(r0, p0, r1, p1) { + return "Q 0,0 " + p1; + } + chord.radius = function(v) { + if (!arguments.length) return radius; + radius = d3_functor(v); + return chord; + }; + chord.source = function(v) { + if (!arguments.length) return source; + source = d3_functor(v); + return chord; + }; + chord.target = function(v) { + if (!arguments.length) return target; + target = d3_functor(v); + return chord; + }; + chord.startAngle = function(v) { + if (!arguments.length) return startAngle; + startAngle = d3_functor(v); + return chord; + }; + chord.endAngle = function(v) { + if (!arguments.length) return endAngle; + endAngle = d3_functor(v); + return chord; + }; + return chord; + }; + function d3_svg_chordRadius(d) { + return d.radius; + } + d3.svg.diagonal = function() { + var source = d3_source, target = d3_target, projection = d3_svg_diagonalProjection; + function diagonal(d, i) { + var p0 = source.call(this, d, i), p3 = target.call(this, d, i), m = (p0.y + p3.y) / 2, p = [ p0, { + x: p0.x, + y: m + }, { + x: p3.x, + y: m + }, p3 ]; + p = p.map(projection); + return "M" + p[0] + "C" + p[1] + " " + p[2] + " " + p[3]; + } + diagonal.source = function(x) { + if (!arguments.length) return source; + source = d3_functor(x); + return diagonal; + }; + diagonal.target = function(x) { + if (!arguments.length) return target; + target = d3_functor(x); + return diagonal; + }; + diagonal.projection = function(x) { + if (!arguments.length) return projection; + projection = x; + return diagonal; + }; + return diagonal; + }; + function d3_svg_diagonalProjection(d) { + return [ d.x, d.y ]; + } + d3.svg.diagonal.radial = function() { + var diagonal = d3.svg.diagonal(), projection = d3_svg_diagonalProjection, projection_ = diagonal.projection; + diagonal.projection = function(x) { + return arguments.length ? projection_(d3_svg_diagonalRadialProjection(projection = x)) : projection; + }; + return diagonal; + }; + function d3_svg_diagonalRadialProjection(projection) { + return function() { + var d = projection.apply(this, arguments), r = d[0], a = d[1] - halfπ; + return [ r * Math.cos(a), r * Math.sin(a) ]; + }; + } + d3.svg.symbol = function() { + var type = d3_svg_symbolType, size = d3_svg_symbolSize; + function symbol(d, i) { + return (d3_svg_symbols.get(type.call(this, d, i)) || d3_svg_symbolCircle)(size.call(this, d, i)); + } + symbol.type = function(x) { + if (!arguments.length) return type; + type = d3_functor(x); + return symbol; + }; + symbol.size = function(x) { + if (!arguments.length) return size; + size = d3_functor(x); + return symbol; + }; + return symbol; + }; + function d3_svg_symbolSize() { + return 64; + } + function d3_svg_symbolType() { + return "circle"; + } + function d3_svg_symbolCircle(size) { + var r = Math.sqrt(size / π); + return "M0," + r + "A" + r + "," + r + " 0 1,1 0," + -r + "A" + r + "," + r + " 0 1,1 0," + r + "Z"; + } + var d3_svg_symbols = d3.map({ + circle: d3_svg_symbolCircle, + cross: function(size) { + var r = Math.sqrt(size / 5) / 2; + return "M" + -3 * r + "," + -r + "H" + -r + "V" + -3 * r + "H" + r + "V" + -r + "H" + 3 * r + "V" + r + "H" + r + "V" + 3 * r + "H" + -r + "V" + r + "H" + -3 * r + "Z"; + }, + diamond: function(size) { + var ry = Math.sqrt(size / (2 * d3_svg_symbolTan30)), rx = ry * d3_svg_symbolTan30; + return "M0," + -ry + "L" + rx + ",0" + " 0," + ry + " " + -rx + ",0" + "Z"; + }, + square: function(size) { + var r = Math.sqrt(size) / 2; + return "M" + -r + "," + -r + "L" + r + "," + -r + " " + r + "," + r + " " + -r + "," + r + "Z"; + }, + "triangle-down": function(size) { + var rx = Math.sqrt(size / d3_svg_symbolSqrt3), ry = rx * d3_svg_symbolSqrt3 / 2; + return "M0," + ry + "L" + rx + "," + -ry + " " + -rx + "," + -ry + "Z"; + }, + "triangle-up": function(size) { + var rx = Math.sqrt(size / d3_svg_symbolSqrt3), ry = rx * d3_svg_symbolSqrt3 / 2; + return "M0," + -ry + "L" + rx + "," + ry + " " + -rx + "," + ry + "Z"; + } + }); + d3.svg.symbolTypes = d3_svg_symbols.keys(); + var d3_svg_symbolSqrt3 = Math.sqrt(3), d3_svg_symbolTan30 = Math.tan(30 * d3_radians); + d3_selectionPrototype.transition = function(name) { + var id = d3_transitionInheritId || ++d3_transitionId, ns = d3_transitionNamespace(name), subgroups = [], subgroup, node, transition = d3_transitionInherit || { + time: Date.now(), + ease: d3_ease_cubicInOut, + delay: 0, + duration: 250 + }; + for (var j = -1, m = this.length; ++j < m; ) { + subgroups.push(subgroup = []); + for (var group = this[j], i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) d3_transitionNode(node, i, ns, id, transition); + subgroup.push(node); + } + } + return d3_transition(subgroups, ns, id); + }; + d3_selectionPrototype.interrupt = function(name) { + return this.each(name == null ? d3_selection_interrupt : d3_selection_interruptNS(d3_transitionNamespace(name))); + }; + var d3_selection_interrupt = d3_selection_interruptNS(d3_transitionNamespace()); + function d3_selection_interruptNS(ns) { + return function() { + var lock, active; + if ((lock = this[ns]) && (active = lock[lock.active])) { + if (--lock.count) delete lock[lock.active]; else delete this[ns]; + lock.active += .5; + active.event && active.event.interrupt.call(this, this.__data__, active.index); + } + }; + } + function d3_transition(groups, ns, id) { + d3_subclass(groups, d3_transitionPrototype); + groups.namespace = ns; + groups.id = id; + return groups; + } + var d3_transitionPrototype = [], d3_transitionId = 0, d3_transitionInheritId, d3_transitionInherit; + d3_transitionPrototype.call = d3_selectionPrototype.call; + d3_transitionPrototype.empty = d3_selectionPrototype.empty; + d3_transitionPrototype.node = d3_selectionPrototype.node; + d3_transitionPrototype.size = d3_selectionPrototype.size; + d3.transition = function(selection, name) { + return selection && selection.transition ? d3_transitionInheritId ? selection.transition(name) : selection : d3.selection().transition(selection); + }; + d3.transition.prototype = d3_transitionPrototype; + d3_transitionPrototype.select = function(selector) { + var id = this.id, ns = this.namespace, subgroups = [], subgroup, subnode, node; + selector = d3_selection_selector(selector); + for (var j = -1, m = this.length; ++j < m; ) { + subgroups.push(subgroup = []); + for (var group = this[j], i = -1, n = group.length; ++i < n; ) { + if ((node = group[i]) && (subnode = selector.call(node, node.__data__, i, j))) { + if ("__data__" in node) subnode.__data__ = node.__data__; + d3_transitionNode(subnode, i, ns, id, node[ns][id]); + subgroup.push(subnode); + } else { + subgroup.push(null); + } + } + } + return d3_transition(subgroups, ns, id); + }; + d3_transitionPrototype.selectAll = function(selector) { + var id = this.id, ns = this.namespace, subgroups = [], subgroup, subnodes, node, subnode, transition; + selector = d3_selection_selectorAll(selector); + for (var j = -1, m = this.length; ++j < m; ) { + for (var group = this[j], i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) { + transition = node[ns][id]; + subnodes = selector.call(node, node.__data__, i, j); + subgroups.push(subgroup = []); + for (var k = -1, o = subnodes.length; ++k < o; ) { + if (subnode = subnodes[k]) d3_transitionNode(subnode, k, ns, id, transition); + subgroup.push(subnode); + } + } + } + } + return d3_transition(subgroups, ns, id); + }; + d3_transitionPrototype.filter = function(filter) { + var subgroups = [], subgroup, group, node; + if (typeof filter !== "function") filter = d3_selection_filter(filter); + for (var j = 0, m = this.length; j < m; j++) { + subgroups.push(subgroup = []); + for (var group = this[j], i = 0, n = group.length; i < n; i++) { + if ((node = group[i]) && filter.call(node, node.__data__, i, j)) { + subgroup.push(node); + } + } + } + return d3_transition(subgroups, this.namespace, this.id); + }; + d3_transitionPrototype.tween = function(name, tween) { + var id = this.id, ns = this.namespace; + if (arguments.length < 2) return this.node()[ns][id].tween.get(name); + return d3_selection_each(this, tween == null ? function(node) { + node[ns][id].tween.remove(name); + } : function(node) { + node[ns][id].tween.set(name, tween); + }); + }; + function d3_transition_tween(groups, name, value, tween) { + var id = groups.id, ns = groups.namespace; + return d3_selection_each(groups, typeof value === "function" ? function(node, i, j) { + node[ns][id].tween.set(name, tween(value.call(node, node.__data__, i, j))); + } : (value = tween(value), function(node) { + node[ns][id].tween.set(name, value); + })); + } + d3_transitionPrototype.attr = function(nameNS, value) { + if (arguments.length < 2) { + for (value in nameNS) this.attr(value, nameNS[value]); + return this; + } + var interpolate = nameNS == "transform" ? d3_interpolateTransform : d3_interpolate, name = d3.ns.qualify(nameNS); + function attrNull() { + this.removeAttribute(name); + } + function attrNullNS() { + this.removeAttributeNS(name.space, name.local); + } + function attrTween(b) { + return b == null ? attrNull : (b += "", function() { + var a = this.getAttribute(name), i; + return a !== b && (i = interpolate(a, b), function(t) { + this.setAttribute(name, i(t)); + }); + }); + } + function attrTweenNS(b) { + return b == null ? attrNullNS : (b += "", function() { + var a = this.getAttributeNS(name.space, name.local), i; + return a !== b && (i = interpolate(a, b), function(t) { + this.setAttributeNS(name.space, name.local, i(t)); + }); + }); + } + return d3_transition_tween(this, "attr." + nameNS, value, name.local ? attrTweenNS : attrTween); + }; + d3_transitionPrototype.attrTween = function(nameNS, tween) { + var name = d3.ns.qualify(nameNS); + function attrTween(d, i) { + var f = tween.call(this, d, i, this.getAttribute(name)); + return f && function(t) { + this.setAttribute(name, f(t)); + }; + } + function attrTweenNS(d, i) { + var f = tween.call(this, d, i, this.getAttributeNS(name.space, name.local)); + return f && function(t) { + this.setAttributeNS(name.space, name.local, f(t)); + }; + } + return this.tween("attr." + nameNS, name.local ? attrTweenNS : attrTween); + }; + d3_transitionPrototype.style = function(name, value, priority) { + var n = arguments.length; + if (n < 3) { + if (typeof name !== "string") { + if (n < 2) value = ""; + for (priority in name) this.style(priority, name[priority], value); + return this; + } + priority = ""; + } + function styleNull() { + this.style.removeProperty(name); + } + function styleString(b) { + return b == null ? styleNull : (b += "", function() { + var a = d3_window(this).getComputedStyle(this, null).getPropertyValue(name), i; + return a !== b && (i = d3_interpolate(a, b), function(t) { + this.style.setProperty(name, i(t), priority); + }); + }); + } + return d3_transition_tween(this, "style." + name, value, styleString); + }; + d3_transitionPrototype.styleTween = function(name, tween, priority) { + if (arguments.length < 3) priority = ""; + function styleTween(d, i) { + var f = tween.call(this, d, i, d3_window(this).getComputedStyle(this, null).getPropertyValue(name)); + return f && function(t) { + this.style.setProperty(name, f(t), priority); + }; + } + return this.tween("style." + name, styleTween); + }; + d3_transitionPrototype.text = function(value) { + return d3_transition_tween(this, "text", value, d3_transition_text); + }; + function d3_transition_text(b) { + if (b == null) b = ""; + return function() { + this.textContent = b; + }; + } + d3_transitionPrototype.remove = function() { + var ns = this.namespace; + return this.each("end.transition", function() { + var p; + if (this[ns].count < 2 && (p = this.parentNode)) p.removeChild(this); + }); + }; + d3_transitionPrototype.ease = function(value) { + var id = this.id, ns = this.namespace; + if (arguments.length < 1) return this.node()[ns][id].ease; + if (typeof value !== "function") value = d3.ease.apply(d3, arguments); + return d3_selection_each(this, function(node) { + node[ns][id].ease = value; + }); + }; + d3_transitionPrototype.delay = function(value) { + var id = this.id, ns = this.namespace; + if (arguments.length < 1) return this.node()[ns][id].delay; + return d3_selection_each(this, typeof value === "function" ? function(node, i, j) { + node[ns][id].delay = +value.call(node, node.__data__, i, j); + } : (value = +value, function(node) { + node[ns][id].delay = value; + })); + }; + d3_transitionPrototype.duration = function(value) { + var id = this.id, ns = this.namespace; + if (arguments.length < 1) return this.node()[ns][id].duration; + return d3_selection_each(this, typeof value === "function" ? function(node, i, j) { + node[ns][id].duration = Math.max(1, value.call(node, node.__data__, i, j)); + } : (value = Math.max(1, value), function(node) { + node[ns][id].duration = value; + })); + }; + d3_transitionPrototype.each = function(type, listener) { + var id = this.id, ns = this.namespace; + if (arguments.length < 2) { + var inherit = d3_transitionInherit, inheritId = d3_transitionInheritId; + try { + d3_transitionInheritId = id; + d3_selection_each(this, function(node, i, j) { + d3_transitionInherit = node[ns][id]; + type.call(node, node.__data__, i, j); + }); + } finally { + d3_transitionInherit = inherit; + d3_transitionInheritId = inheritId; + } + } else { + d3_selection_each(this, function(node) { + var transition = node[ns][id]; + (transition.event || (transition.event = d3.dispatch("start", "end", "interrupt"))).on(type, listener); + }); + } + return this; + }; + d3_transitionPrototype.transition = function() { + var id0 = this.id, id1 = ++d3_transitionId, ns = this.namespace, subgroups = [], subgroup, group, node, transition; + for (var j = 0, m = this.length; j < m; j++) { + subgroups.push(subgroup = []); + for (var group = this[j], i = 0, n = group.length; i < n; i++) { + if (node = group[i]) { + transition = node[ns][id0]; + d3_transitionNode(node, i, ns, id1, { + time: transition.time, + ease: transition.ease, + delay: transition.delay + transition.duration, + duration: transition.duration + }); + } + subgroup.push(node); + } + } + return d3_transition(subgroups, ns, id1); + }; + function d3_transitionNamespace(name) { + return name == null ? "__transition__" : "__transition_" + name + "__"; + } + function d3_transitionNode(node, i, ns, id, inherit) { + var lock = node[ns] || (node[ns] = { + active: 0, + count: 0 + }), transition = lock[id]; + if (!transition) { + var time = inherit.time; + transition = lock[id] = { + tween: new d3_Map(), + time: time, + delay: inherit.delay, + duration: inherit.duration, + ease: inherit.ease, + index: i + }; + inherit = null; + ++lock.count; + d3.timer(function(elapsed) { + var delay = transition.delay, duration, ease, timer = d3_timer_active, tweened = []; + timer.t = delay + time; + if (delay <= elapsed) return start(elapsed - delay); + timer.c = start; + function start(elapsed) { + if (lock.active > id) return stop(); + var active = lock[lock.active]; + if (active) { + --lock.count; + delete lock[lock.active]; + active.event && active.event.interrupt.call(node, node.__data__, active.index); + } + lock.active = id; + transition.event && transition.event.start.call(node, node.__data__, i); + transition.tween.forEach(function(key, value) { + if (value = value.call(node, node.__data__, i)) { + tweened.push(value); + } + }); + ease = transition.ease; + duration = transition.duration; + d3.timer(function() { + timer.c = tick(elapsed || 1) ? d3_true : tick; + return 1; + }, 0, time); + } + function tick(elapsed) { + if (lock.active !== id) return 1; + var t = elapsed / duration, e = ease(t), n = tweened.length; + while (n > 0) { + tweened[--n].call(node, e); + } + if (t >= 1) { + transition.event && transition.event.end.call(node, node.__data__, i); + return stop(); + } + } + function stop() { + if (--lock.count) delete lock[id]; else delete node[ns]; + return 1; + } + }, 0, time); + } + } + d3.svg.axis = function() { + var scale = d3.scale.linear(), orient = d3_svg_axisDefaultOrient, innerTickSize = 6, outerTickSize = 6, tickPadding = 3, tickArguments_ = [ 10 ], tickValues = null, tickFormat_; + function axis(g) { + g.each(function() { + var g = d3.select(this); + var scale0 = this.__chart__ || scale, scale1 = this.__chart__ = scale.copy(); + var ticks = tickValues == null ? scale1.ticks ? scale1.ticks.apply(scale1, tickArguments_) : scale1.domain() : tickValues, tickFormat = tickFormat_ == null ? scale1.tickFormat ? scale1.tickFormat.apply(scale1, tickArguments_) : d3_identity : tickFormat_, tick = g.selectAll(".tick").data(ticks, scale1), tickEnter = tick.enter().insert("g", ".domain").attr("class", "tick").style("opacity", ε), tickExit = d3.transition(tick.exit()).style("opacity", ε).remove(), tickUpdate = d3.transition(tick.order()).style("opacity", 1), tickSpacing = Math.max(innerTickSize, 0) + tickPadding, tickTransform; + var range = d3_scaleRange(scale1), path = g.selectAll(".domain").data([ 0 ]), pathUpdate = (path.enter().append("path").attr("class", "domain"), + d3.transition(path)); + tickEnter.append("line"); + tickEnter.append("text"); + var lineEnter = tickEnter.select("line"), lineUpdate = tickUpdate.select("line"), text = tick.select("text").text(tickFormat), textEnter = tickEnter.select("text"), textUpdate = tickUpdate.select("text"), sign = orient === "top" || orient === "left" ? -1 : 1, x1, x2, y1, y2; + if (orient === "bottom" || orient === "top") { + tickTransform = d3_svg_axisX, x1 = "x", y1 = "y", x2 = "x2", y2 = "y2"; + text.attr("dy", sign < 0 ? "0em" : ".71em").style("text-anchor", "middle"); + pathUpdate.attr("d", "M" + range[0] + "," + sign * outerTickSize + "V0H" + range[1] + "V" + sign * outerTickSize); + } else { + tickTransform = d3_svg_axisY, x1 = "y", y1 = "x", x2 = "y2", y2 = "x2"; + text.attr("dy", ".32em").style("text-anchor", sign < 0 ? "end" : "start"); + pathUpdate.attr("d", "M" + sign * outerTickSize + "," + range[0] + "H0V" + range[1] + "H" + sign * outerTickSize); + } + lineEnter.attr(y2, sign * innerTickSize); + textEnter.attr(y1, sign * tickSpacing); + lineUpdate.attr(x2, 0).attr(y2, sign * innerTickSize); + textUpdate.attr(x1, 0).attr(y1, sign * tickSpacing); + if (scale1.rangeBand) { + var x = scale1, dx = x.rangeBand() / 2; + scale0 = scale1 = function(d) { + return x(d) + dx; + }; + } else if (scale0.rangeBand) { + scale0 = scale1; + } else { + tickExit.call(tickTransform, scale1, scale0); + } + tickEnter.call(tickTransform, scale0, scale1); + tickUpdate.call(tickTransform, scale1, scale1); + }); + } + axis.scale = function(x) { + if (!arguments.length) return scale; + scale = x; + return axis; + }; + axis.orient = function(x) { + if (!arguments.length) return orient; + orient = x in d3_svg_axisOrients ? x + "" : d3_svg_axisDefaultOrient; + return axis; + }; + axis.ticks = function() { + if (!arguments.length) return tickArguments_; + tickArguments_ = arguments; + return axis; + }; + axis.tickValues = function(x) { + if (!arguments.length) return tickValues; + tickValues = x; + return axis; + }; + axis.tickFormat = function(x) { + if (!arguments.length) return tickFormat_; + tickFormat_ = x; + return axis; + }; + axis.tickSize = function(x) { + var n = arguments.length; + if (!n) return innerTickSize; + innerTickSize = +x; + outerTickSize = +arguments[n - 1]; + return axis; + }; + axis.innerTickSize = function(x) { + if (!arguments.length) return innerTickSize; + innerTickSize = +x; + return axis; + }; + axis.outerTickSize = function(x) { + if (!arguments.length) return outerTickSize; + outerTickSize = +x; + return axis; + }; + axis.tickPadding = function(x) { + if (!arguments.length) return tickPadding; + tickPadding = +x; + return axis; + }; + axis.tickSubdivide = function() { + return arguments.length && axis; + }; + return axis; + }; + var d3_svg_axisDefaultOrient = "bottom", d3_svg_axisOrients = { + top: 1, + right: 1, + bottom: 1, + left: 1 + }; + function d3_svg_axisX(selection, x0, x1) { + selection.attr("transform", function(d) { + var v0 = x0(d); + return "translate(" + (isFinite(v0) ? v0 : x1(d)) + ",0)"; + }); + } + function d3_svg_axisY(selection, y0, y1) { + selection.attr("transform", function(d) { + var v0 = y0(d); + return "translate(0," + (isFinite(v0) ? v0 : y1(d)) + ")"; + }); + } + d3.svg.brush = function() { + var event = d3_eventDispatch(brush, "brushstart", "brush", "brushend"), x = null, y = null, xExtent = [ 0, 0 ], yExtent = [ 0, 0 ], xExtentDomain, yExtentDomain, xClamp = true, yClamp = true, resizes = d3_svg_brushResizes[0]; + function brush(g) { + g.each(function() { + var g = d3.select(this).style("pointer-events", "all").style("-webkit-tap-highlight-color", "rgba(0,0,0,0)").on("mousedown.brush", brushstart).on("touchstart.brush", brushstart); + var background = g.selectAll(".background").data([ 0 ]); + background.enter().append("rect").attr("class", "background").style("visibility", "hidden").style("cursor", "crosshair"); + g.selectAll(".extent").data([ 0 ]).enter().append("rect").attr("class", "extent").style("cursor", "move"); + var resize = g.selectAll(".resize").data(resizes, d3_identity); + resize.exit().remove(); + resize.enter().append("g").attr("class", function(d) { + return "resize " + d; + }).style("cursor", function(d) { + return d3_svg_brushCursor[d]; + }).append("rect").attr("x", function(d) { + return /[ew]$/.test(d) ? -3 : null; + }).attr("y", function(d) { + return /^[ns]/.test(d) ? -3 : null; + }).attr("width", 6).attr("height", 6).style("visibility", "hidden"); + resize.style("display", brush.empty() ? "none" : null); + var gUpdate = d3.transition(g), backgroundUpdate = d3.transition(background), range; + if (x) { + range = d3_scaleRange(x); + backgroundUpdate.attr("x", range[0]).attr("width", range[1] - range[0]); + redrawX(gUpdate); + } + if (y) { + range = d3_scaleRange(y); + backgroundUpdate.attr("y", range[0]).attr("height", range[1] - range[0]); + redrawY(gUpdate); + } + redraw(gUpdate); + }); + } + brush.event = function(g) { + g.each(function() { + var event_ = event.of(this, arguments), extent1 = { + x: xExtent, + y: yExtent, + i: xExtentDomain, + j: yExtentDomain + }, extent0 = this.__chart__ || extent1; + this.__chart__ = extent1; + if (d3_transitionInheritId) { + d3.select(this).transition().each("start.brush", function() { + xExtentDomain = extent0.i; + yExtentDomain = extent0.j; + xExtent = extent0.x; + yExtent = extent0.y; + event_({ + type: "brushstart" + }); + }).tween("brush:brush", function() { + var xi = d3_interpolateArray(xExtent, extent1.x), yi = d3_interpolateArray(yExtent, extent1.y); + xExtentDomain = yExtentDomain = null; + return function(t) { + xExtent = extent1.x = xi(t); + yExtent = extent1.y = yi(t); + event_({ + type: "brush", + mode: "resize" + }); + }; + }).each("end.brush", function() { + xExtentDomain = extent1.i; + yExtentDomain = extent1.j; + event_({ + type: "brush", + mode: "resize" + }); + event_({ + type: "brushend" + }); + }); + } else { + event_({ + type: "brushstart" + }); + event_({ + type: "brush", + mode: "resize" + }); + event_({ + type: "brushend" + }); + } + }); + }; + function redraw(g) { + g.selectAll(".resize").attr("transform", function(d) { + return "translate(" + xExtent[+/e$/.test(d)] + "," + yExtent[+/^s/.test(d)] + ")"; + }); + } + function redrawX(g) { + g.select(".extent").attr("x", xExtent[0]); + g.selectAll(".extent,.n>rect,.s>rect").attr("width", xExtent[1] - xExtent[0]); + } + function redrawY(g) { + g.select(".extent").attr("y", yExtent[0]); + g.selectAll(".extent,.e>rect,.w>rect").attr("height", yExtent[1] - yExtent[0]); + } + function brushstart() { + var target = this, eventTarget = d3.select(d3.event.target), event_ = event.of(target, arguments), g = d3.select(target), resizing = eventTarget.datum(), resizingX = !/^(n|s)$/.test(resizing) && x, resizingY = !/^(e|w)$/.test(resizing) && y, dragging = eventTarget.classed("extent"), dragRestore = d3_event_dragSuppress(target), center, origin = d3.mouse(target), offset; + var w = d3.select(d3_window(target)).on("keydown.brush", keydown).on("keyup.brush", keyup); + if (d3.event.changedTouches) { + w.on("touchmove.brush", brushmove).on("touchend.brush", brushend); + } else { + w.on("mousemove.brush", brushmove).on("mouseup.brush", brushend); + } + g.interrupt().selectAll("*").interrupt(); + if (dragging) { + origin[0] = xExtent[0] - origin[0]; + origin[1] = yExtent[0] - origin[1]; + } else if (resizing) { + var ex = +/w$/.test(resizing), ey = +/^n/.test(resizing); + offset = [ xExtent[1 - ex] - origin[0], yExtent[1 - ey] - origin[1] ]; + origin[0] = xExtent[ex]; + origin[1] = yExtent[ey]; + } else if (d3.event.altKey) center = origin.slice(); + g.style("pointer-events", "none").selectAll(".resize").style("display", null); + d3.select("body").style("cursor", eventTarget.style("cursor")); + event_({ + type: "brushstart" + }); + brushmove(); + function keydown() { + if (d3.event.keyCode == 32) { + if (!dragging) { + center = null; + origin[0] -= xExtent[1]; + origin[1] -= yExtent[1]; + dragging = 2; + } + d3_eventPreventDefault(); + } + } + function keyup() { + if (d3.event.keyCode == 32 && dragging == 2) { + origin[0] += xExtent[1]; + origin[1] += yExtent[1]; + dragging = 0; + d3_eventPreventDefault(); + } + } + function brushmove() { + var point = d3.mouse(target), moved = false; + if (offset) { + point[0] += offset[0]; + point[1] += offset[1]; + } + if (!dragging) { + if (d3.event.altKey) { + if (!center) center = [ (xExtent[0] + xExtent[1]) / 2, (yExtent[0] + yExtent[1]) / 2 ]; + origin[0] = xExtent[+(point[0] < center[0])]; + origin[1] = yExtent[+(point[1] < center[1])]; + } else center = null; + } + if (resizingX && move1(point, x, 0)) { + redrawX(g); + moved = true; + } + if (resizingY && move1(point, y, 1)) { + redrawY(g); + moved = true; + } + if (moved) { + redraw(g); + event_({ + type: "brush", + mode: dragging ? "move" : "resize" + }); + } + } + function move1(point, scale, i) { + var range = d3_scaleRange(scale), r0 = range[0], r1 = range[1], position = origin[i], extent = i ? yExtent : xExtent, size = extent[1] - extent[0], min, max; + if (dragging) { + r0 -= position; + r1 -= size + position; + } + min = (i ? yClamp : xClamp) ? Math.max(r0, Math.min(r1, point[i])) : point[i]; + if (dragging) { + max = (min += position) + size; + } else { + if (center) position = Math.max(r0, Math.min(r1, 2 * center[i] - min)); + if (position < min) { + max = min; + min = position; + } else { + max = position; + } + } + if (extent[0] != min || extent[1] != max) { + if (i) yExtentDomain = null; else xExtentDomain = null; + extent[0] = min; + extent[1] = max; + return true; + } + } + function brushend() { + brushmove(); + g.style("pointer-events", "all").selectAll(".resize").style("display", brush.empty() ? "none" : null); + d3.select("body").style("cursor", null); + w.on("mousemove.brush", null).on("mouseup.brush", null).on("touchmove.brush", null).on("touchend.brush", null).on("keydown.brush", null).on("keyup.brush", null); + dragRestore(); + event_({ + type: "brushend" + }); + } + } + brush.x = function(z) { + if (!arguments.length) return x; + x = z; + resizes = d3_svg_brushResizes[!x << 1 | !y]; + return brush; + }; + brush.y = function(z) { + if (!arguments.length) return y; + y = z; + resizes = d3_svg_brushResizes[!x << 1 | !y]; + return brush; + }; + brush.clamp = function(z) { + if (!arguments.length) return x && y ? [ xClamp, yClamp ] : x ? xClamp : y ? yClamp : null; + if (x && y) xClamp = !!z[0], yClamp = !!z[1]; else if (x) xClamp = !!z; else if (y) yClamp = !!z; + return brush; + }; + brush.extent = function(z) { + var x0, x1, y0, y1, t; + if (!arguments.length) { + if (x) { + if (xExtentDomain) { + x0 = xExtentDomain[0], x1 = xExtentDomain[1]; + } else { + x0 = xExtent[0], x1 = xExtent[1]; + if (x.invert) x0 = x.invert(x0), x1 = x.invert(x1); + if (x1 < x0) t = x0, x0 = x1, x1 = t; + } + } + if (y) { + if (yExtentDomain) { + y0 = yExtentDomain[0], y1 = yExtentDomain[1]; + } else { + y0 = yExtent[0], y1 = yExtent[1]; + if (y.invert) y0 = y.invert(y0), y1 = y.invert(y1); + if (y1 < y0) t = y0, y0 = y1, y1 = t; + } + } + return x && y ? [ [ x0, y0 ], [ x1, y1 ] ] : x ? [ x0, x1 ] : y && [ y0, y1 ]; + } + if (x) { + x0 = z[0], x1 = z[1]; + if (y) x0 = x0[0], x1 = x1[0]; + xExtentDomain = [ x0, x1 ]; + if (x.invert) x0 = x(x0), x1 = x(x1); + if (x1 < x0) t = x0, x0 = x1, x1 = t; + if (x0 != xExtent[0] || x1 != xExtent[1]) xExtent = [ x0, x1 ]; + } + if (y) { + y0 = z[0], y1 = z[1]; + if (x) y0 = y0[1], y1 = y1[1]; + yExtentDomain = [ y0, y1 ]; + if (y.invert) y0 = y(y0), y1 = y(y1); + if (y1 < y0) t = y0, y0 = y1, y1 = t; + if (y0 != yExtent[0] || y1 != yExtent[1]) yExtent = [ y0, y1 ]; + } + return brush; + }; + brush.clear = function() { + if (!brush.empty()) { + xExtent = [ 0, 0 ], yExtent = [ 0, 0 ]; + xExtentDomain = yExtentDomain = null; + } + return brush; + }; + brush.empty = function() { + return !!x && xExtent[0] == xExtent[1] || !!y && yExtent[0] == yExtent[1]; + }; + return d3.rebind(brush, event, "on"); + }; + var d3_svg_brushCursor = { + n: "ns-resize", + e: "ew-resize", + s: "ns-resize", + w: "ew-resize", + nw: "nwse-resize", + ne: "nesw-resize", + se: "nwse-resize", + sw: "nesw-resize" + }; + var d3_svg_brushResizes = [ [ "n", "e", "s", "w", "nw", "ne", "se", "sw" ], [ "e", "w" ], [ "n", "s" ], [] ]; + var d3_time_format = d3_time.format = d3_locale_enUS.timeFormat; + var d3_time_formatUtc = d3_time_format.utc; + var d3_time_formatIso = d3_time_formatUtc("%Y-%m-%dT%H:%M:%S.%LZ"); + d3_time_format.iso = Date.prototype.toISOString && +new Date("2000-01-01T00:00:00.000Z") ? d3_time_formatIsoNative : d3_time_formatIso; + function d3_time_formatIsoNative(date) { + return date.toISOString(); + } + d3_time_formatIsoNative.parse = function(string) { + var date = new Date(string); + return isNaN(date) ? null : date; + }; + d3_time_formatIsoNative.toString = d3_time_formatIso.toString; + d3_time.second = d3_time_interval(function(date) { + return new d3_date(Math.floor(date / 1e3) * 1e3); + }, function(date, offset) { + date.setTime(date.getTime() + Math.floor(offset) * 1e3); + }, function(date) { + return date.getSeconds(); + }); + d3_time.seconds = d3_time.second.range; + d3_time.seconds.utc = d3_time.second.utc.range; + d3_time.minute = d3_time_interval(function(date) { + return new d3_date(Math.floor(date / 6e4) * 6e4); + }, function(date, offset) { + date.setTime(date.getTime() + Math.floor(offset) * 6e4); + }, function(date) { + return date.getMinutes(); + }); + d3_time.minutes = d3_time.minute.range; + d3_time.minutes.utc = d3_time.minute.utc.range; + d3_time.hour = d3_time_interval(function(date) { + var timezone = date.getTimezoneOffset() / 60; + return new d3_date((Math.floor(date / 36e5 - timezone) + timezone) * 36e5); + }, function(date, offset) { + date.setTime(date.getTime() + Math.floor(offset) * 36e5); + }, function(date) { + return date.getHours(); + }); + d3_time.hours = d3_time.hour.range; + d3_time.hours.utc = d3_time.hour.utc.range; + d3_time.month = d3_time_interval(function(date) { + date = d3_time.day(date); + date.setDate(1); + return date; + }, function(date, offset) { + date.setMonth(date.getMonth() + offset); + }, function(date) { + return date.getMonth(); + }); + d3_time.months = d3_time.month.range; + d3_time.months.utc = d3_time.month.utc.range; + function d3_time_scale(linear, methods, format) { + function scale(x) { + return linear(x); + } + scale.invert = function(x) { + return d3_time_scaleDate(linear.invert(x)); + }; + scale.domain = function(x) { + if (!arguments.length) return linear.domain().map(d3_time_scaleDate); + linear.domain(x); + return scale; + }; + function tickMethod(extent, count) { + var span = extent[1] - extent[0], target = span / count, i = d3.bisect(d3_time_scaleSteps, target); + return i == d3_time_scaleSteps.length ? [ methods.year, d3_scale_linearTickRange(extent.map(function(d) { + return d / 31536e6; + }), count)[2] ] : !i ? [ d3_time_scaleMilliseconds, d3_scale_linearTickRange(extent, count)[2] ] : methods[target / d3_time_scaleSteps[i - 1] < d3_time_scaleSteps[i] / target ? i - 1 : i]; + } + scale.nice = function(interval, skip) { + var domain = scale.domain(), extent = d3_scaleExtent(domain), method = interval == null ? tickMethod(extent, 10) : typeof interval === "number" && tickMethod(extent, interval); + if (method) interval = method[0], skip = method[1]; + function skipped(date) { + return !isNaN(date) && !interval.range(date, d3_time_scaleDate(+date + 1), skip).length; + } + return scale.domain(d3_scale_nice(domain, skip > 1 ? { + floor: function(date) { + while (skipped(date = interval.floor(date))) date = d3_time_scaleDate(date - 1); + return date; + }, + ceil: function(date) { + while (skipped(date = interval.ceil(date))) date = d3_time_scaleDate(+date + 1); + return date; + } + } : interval)); + }; + scale.ticks = function(interval, skip) { + var extent = d3_scaleExtent(scale.domain()), method = interval == null ? tickMethod(extent, 10) : typeof interval === "number" ? tickMethod(extent, interval) : !interval.range && [ { + range: interval + }, skip ]; + if (method) interval = method[0], skip = method[1]; + return interval.range(extent[0], d3_time_scaleDate(+extent[1] + 1), skip < 1 ? 1 : skip); + }; + scale.tickFormat = function() { + return format; + }; + scale.copy = function() { + return d3_time_scale(linear.copy(), methods, format); + }; + return d3_scale_linearRebind(scale, linear); + } + function d3_time_scaleDate(t) { + return new Date(t); + } + var d3_time_scaleSteps = [ 1e3, 5e3, 15e3, 3e4, 6e4, 3e5, 9e5, 18e5, 36e5, 108e5, 216e5, 432e5, 864e5, 1728e5, 6048e5, 2592e6, 7776e6, 31536e6 ]; + var d3_time_scaleLocalMethods = [ [ d3_time.second, 1 ], [ d3_time.second, 5 ], [ d3_time.second, 15 ], [ d3_time.second, 30 ], [ d3_time.minute, 1 ], [ d3_time.minute, 5 ], [ d3_time.minute, 15 ], [ d3_time.minute, 30 ], [ d3_time.hour, 1 ], [ d3_time.hour, 3 ], [ d3_time.hour, 6 ], [ d3_time.hour, 12 ], [ d3_time.day, 1 ], [ d3_time.day, 2 ], [ d3_time.week, 1 ], [ d3_time.month, 1 ], [ d3_time.month, 3 ], [ d3_time.year, 1 ] ]; + var d3_time_scaleLocalFormat = d3_time_format.multi([ [ ".%L", function(d) { + return d.getMilliseconds(); + } ], [ ":%S", function(d) { + return d.getSeconds(); + } ], [ "%I:%M", function(d) { + return d.getMinutes(); + } ], [ "%I %p", function(d) { + return d.getHours(); + } ], [ "%a %d", function(d) { + return d.getDay() && d.getDate() != 1; + } ], [ "%b %d", function(d) { + return d.getDate() != 1; + } ], [ "%B", function(d) { + return d.getMonth(); + } ], [ "%Y", d3_true ] ]); + var d3_time_scaleMilliseconds = { + range: function(start, stop, step) { + return d3.range(Math.ceil(start / step) * step, +stop, step).map(d3_time_scaleDate); + }, + floor: d3_identity, + ceil: d3_identity + }; + d3_time_scaleLocalMethods.year = d3_time.year; + d3_time.scale = function() { + return d3_time_scale(d3.scale.linear(), d3_time_scaleLocalMethods, d3_time_scaleLocalFormat); + }; + var d3_time_scaleUtcMethods = d3_time_scaleLocalMethods.map(function(m) { + return [ m[0].utc, m[1] ]; + }); + var d3_time_scaleUtcFormat = d3_time_formatUtc.multi([ [ ".%L", function(d) { + return d.getUTCMilliseconds(); + } ], [ ":%S", function(d) { + return d.getUTCSeconds(); + } ], [ "%I:%M", function(d) { + return d.getUTCMinutes(); + } ], [ "%I %p", function(d) { + return d.getUTCHours(); + } ], [ "%a %d", function(d) { + return d.getUTCDay() && d.getUTCDate() != 1; + } ], [ "%b %d", function(d) { + return d.getUTCDate() != 1; + } ], [ "%B", function(d) { + return d.getUTCMonth(); + } ], [ "%Y", d3_true ] ]); + d3_time_scaleUtcMethods.year = d3_time.year.utc; + d3_time.scale.utc = function() { + return d3_time_scale(d3.scale.linear(), d3_time_scaleUtcMethods, d3_time_scaleUtcFormat); + }; + d3.text = d3_xhrType(function(request) { + return request.responseText; + }); + d3.json = function(url, callback) { + return d3_xhr(url, "application/json", d3_json, callback); + }; + function d3_json(request) { + return JSON.parse(request.responseText); + } + d3.html = function(url, callback) { + return d3_xhr(url, "text/html", d3_html, callback); + }; + function d3_html(request) { + var range = d3_document.createRange(); + range.selectNode(d3_document.body); + return range.createContextualFragment(request.responseText); + } + d3.xml = d3_xhrType(function(request) { + return request.responseXML; + }); + if (typeof define === "function" && define.amd) define(d3); else if (typeof module === "object" && module.exports) module.exports = d3; + this.d3 = d3; +}(); \ No newline at end of file diff --git a/public/assets/d3/d3.min.js b/public/assets/d3/d3.min.js new file mode 100644 index 000000000..1984d1723 --- /dev/null +++ b/public/assets/d3/d3.min.js @@ -0,0 +1,5 @@ +!function(){function n(n){return n&&(n.ownerDocument||n.document||n).documentElement}function t(n){return n&&(n.ownerDocument&&n.ownerDocument.defaultView||n.document&&n||n.defaultView)}function e(n,t){return t>n?-1:n>t?1:n>=t?0:0/0}function r(n){return null===n?0/0:+n}function u(n){return!isNaN(n)}function i(n){return{left:function(t,e,r,u){for(arguments.length<3&&(r=0),arguments.length<4&&(u=t.length);u>r;){var i=r+u>>>1;n(t[i],e)<0?r=i+1:u=i}return r},right:function(t,e,r,u){for(arguments.length<3&&(r=0),arguments.length<4&&(u=t.length);u>r;){var i=r+u>>>1;n(t[i],e)>0?u=i:r=i+1}return r}}}function o(n){return n.length}function a(n){for(var t=1;n*t%1;)t*=10;return t}function c(n,t){for(var e in t)Object.defineProperty(n.prototype,e,{value:t[e],enumerable:!1})}function l(){this._=Object.create(null)}function s(n){return(n+="")===pa||n[0]===va?va+n:n}function f(n){return(n+="")[0]===va?n.slice(1):n}function h(n){return s(n)in this._}function g(n){return(n=s(n))in this._&&delete this._[n]}function p(){var n=[];for(var t in this._)n.push(f(t));return n}function v(){var n=0;for(var t in this._)++n;return n}function d(){for(var n in this._)return!1;return!0}function m(){this._=Object.create(null)}function y(n){return n}function M(n,t,e){return function(){var r=e.apply(t,arguments);return r===t?n:r}}function x(n,t){if(t in n)return t;t=t.charAt(0).toUpperCase()+t.slice(1);for(var e=0,r=da.length;r>e;++e){var u=da[e]+t;if(u in n)return u}}function b(){}function _(){}function w(n){function t(){for(var t,r=e,u=-1,i=r.length;++ue;e++)for(var u,i=n[e],o=0,a=i.length;a>o;o++)(u=i[o])&&t(u,o,e);return n}function Z(n){return ya(n,Sa),n}function V(n){var t,e;return function(r,u,i){var o,a=n[i].update,c=a.length;for(i!=e&&(e=i,t=0),u>=t&&(t=u+1);!(o=a[t])&&++t0&&(n=n.slice(0,a));var l=ka.get(n);return l&&(n=l,c=B),a?t?u:r:t?b:i}function $(n,t){return function(e){var r=ta.event;ta.event=e,t[0]=this.__data__;try{n.apply(this,t)}finally{ta.event=r}}}function B(n,t){var e=$(n,t);return function(n){var t=this,r=n.relatedTarget;r&&(r===t||8&r.compareDocumentPosition(t))||e.call(t,n)}}function W(e){var r=".dragsuppress-"+ ++Aa,u="click"+r,i=ta.select(t(e)).on("touchmove"+r,S).on("dragstart"+r,S).on("selectstart"+r,S);if(null==Ea&&(Ea="onselectstart"in e?!1:x(e.style,"userSelect")),Ea){var o=n(e).style,a=o[Ea];o[Ea]="none"}return function(n){if(i.on(r,null),Ea&&(o[Ea]=a),n){var t=function(){i.on(u,null)};i.on(u,function(){S(),t()},!0),setTimeout(t,0)}}}function J(n,e){e.changedTouches&&(e=e.changedTouches[0]);var r=n.ownerSVGElement||n;if(r.createSVGPoint){var u=r.createSVGPoint();if(0>Na){var i=t(n);if(i.scrollX||i.scrollY){r=ta.select("body").append("svg").style({position:"absolute",top:0,left:0,margin:0,padding:0,border:"none"},"important");var o=r[0][0].getScreenCTM();Na=!(o.f||o.e),r.remove()}}return Na?(u.x=e.pageX,u.y=e.pageY):(u.x=e.clientX,u.y=e.clientY),u=u.matrixTransform(n.getScreenCTM().inverse()),[u.x,u.y]}var a=n.getBoundingClientRect();return[e.clientX-a.left-n.clientLeft,e.clientY-a.top-n.clientTop]}function G(){return ta.event.changedTouches[0].identifier}function K(n){return n>0?1:0>n?-1:0}function Q(n,t,e){return(t[0]-n[0])*(e[1]-n[1])-(t[1]-n[1])*(e[0]-n[0])}function nt(n){return n>1?0:-1>n?qa:Math.acos(n)}function tt(n){return n>1?Ra:-1>n?-Ra:Math.asin(n)}function et(n){return((n=Math.exp(n))-1/n)/2}function rt(n){return((n=Math.exp(n))+1/n)/2}function ut(n){return((n=Math.exp(2*n))-1)/(n+1)}function it(n){return(n=Math.sin(n/2))*n}function ot(){}function at(n,t,e){return this instanceof at?(this.h=+n,this.s=+t,void(this.l=+e)):arguments.length<2?n instanceof at?new at(n.h,n.s,n.l):bt(""+n,_t,at):new at(n,t,e)}function ct(n,t,e){function r(n){return n>360?n-=360:0>n&&(n+=360),60>n?i+(o-i)*n/60:180>n?o:240>n?i+(o-i)*(240-n)/60:i}function u(n){return Math.round(255*r(n))}var i,o;return n=isNaN(n)?0:(n%=360)<0?n+360:n,t=isNaN(t)?0:0>t?0:t>1?1:t,e=0>e?0:e>1?1:e,o=.5>=e?e*(1+t):e+t-e*t,i=2*e-o,new mt(u(n+120),u(n),u(n-120))}function lt(n,t,e){return this instanceof lt?(this.h=+n,this.c=+t,void(this.l=+e)):arguments.length<2?n instanceof lt?new lt(n.h,n.c,n.l):n instanceof ft?gt(n.l,n.a,n.b):gt((n=wt((n=ta.rgb(n)).r,n.g,n.b)).l,n.a,n.b):new lt(n,t,e)}function st(n,t,e){return isNaN(n)&&(n=0),isNaN(t)&&(t=0),new ft(e,Math.cos(n*=Da)*t,Math.sin(n)*t)}function ft(n,t,e){return this instanceof ft?(this.l=+n,this.a=+t,void(this.b=+e)):arguments.length<2?n instanceof ft?new ft(n.l,n.a,n.b):n instanceof lt?st(n.h,n.c,n.l):wt((n=mt(n)).r,n.g,n.b):new ft(n,t,e)}function ht(n,t,e){var r=(n+16)/116,u=r+t/500,i=r-e/200;return u=pt(u)*Xa,r=pt(r)*$a,i=pt(i)*Ba,new mt(dt(3.2404542*u-1.5371385*r-.4985314*i),dt(-.969266*u+1.8760108*r+.041556*i),dt(.0556434*u-.2040259*r+1.0572252*i))}function gt(n,t,e){return n>0?new lt(Math.atan2(e,t)*Pa,Math.sqrt(t*t+e*e),n):new lt(0/0,0/0,n)}function pt(n){return n>.206893034?n*n*n:(n-4/29)/7.787037}function vt(n){return n>.008856?Math.pow(n,1/3):7.787037*n+4/29}function dt(n){return Math.round(255*(.00304>=n?12.92*n:1.055*Math.pow(n,1/2.4)-.055))}function mt(n,t,e){return this instanceof mt?(this.r=~~n,this.g=~~t,void(this.b=~~e)):arguments.length<2?n instanceof mt?new mt(n.r,n.g,n.b):bt(""+n,mt,ct):new mt(n,t,e)}function yt(n){return new mt(n>>16,n>>8&255,255&n)}function Mt(n){return yt(n)+""}function xt(n){return 16>n?"0"+Math.max(0,n).toString(16):Math.min(255,n).toString(16)}function bt(n,t,e){var r,u,i,o=0,a=0,c=0;if(r=/([a-z]+)\((.*)\)/.exec(n=n.toLowerCase()))switch(u=r[2].split(","),r[1]){case"hsl":return e(parseFloat(u[0]),parseFloat(u[1])/100,parseFloat(u[2])/100);case"rgb":return t(kt(u[0]),kt(u[1]),kt(u[2]))}return(i=Ga.get(n))?t(i.r,i.g,i.b):(null==n||"#"!==n.charAt(0)||isNaN(i=parseInt(n.slice(1),16))||(4===n.length?(o=(3840&i)>>4,o=o>>4|o,a=240&i,a=a>>4|a,c=15&i,c=c<<4|c):7===n.length&&(o=(16711680&i)>>16,a=(65280&i)>>8,c=255&i)),t(o,a,c))}function _t(n,t,e){var r,u,i=Math.min(n/=255,t/=255,e/=255),o=Math.max(n,t,e),a=o-i,c=(o+i)/2;return a?(u=.5>c?a/(o+i):a/(2-o-i),r=n==o?(t-e)/a+(e>t?6:0):t==o?(e-n)/a+2:(n-t)/a+4,r*=60):(r=0/0,u=c>0&&1>c?0:r),new at(r,u,c)}function wt(n,t,e){n=St(n),t=St(t),e=St(e);var r=vt((.4124564*n+.3575761*t+.1804375*e)/Xa),u=vt((.2126729*n+.7151522*t+.072175*e)/$a),i=vt((.0193339*n+.119192*t+.9503041*e)/Ba);return ft(116*u-16,500*(r-u),200*(u-i))}function St(n){return(n/=255)<=.04045?n/12.92:Math.pow((n+.055)/1.055,2.4)}function kt(n){var t=parseFloat(n);return"%"===n.charAt(n.length-1)?Math.round(2.55*t):t}function Et(n){return"function"==typeof n?n:function(){return n}}function At(n){return function(t,e,r){return 2===arguments.length&&"function"==typeof e&&(r=e,e=null),Nt(t,e,n,r)}}function Nt(n,t,e,r){function u(){var n,t=c.status;if(!t&&zt(c)||t>=200&&300>t||304===t){try{n=e.call(i,c)}catch(r){return void o.error.call(i,r)}o.load.call(i,n)}else o.error.call(i,c)}var i={},o=ta.dispatch("beforesend","progress","load","error"),a={},c=new XMLHttpRequest,l=null;return!this.XDomainRequest||"withCredentials"in c||!/^(http(s)?:)?\/\//.test(n)||(c=new XDomainRequest),"onload"in c?c.onload=c.onerror=u:c.onreadystatechange=function(){c.readyState>3&&u()},c.onprogress=function(n){var t=ta.event;ta.event=n;try{o.progress.call(i,c)}finally{ta.event=t}},i.header=function(n,t){return n=(n+"").toLowerCase(),arguments.length<2?a[n]:(null==t?delete a[n]:a[n]=t+"",i)},i.mimeType=function(n){return arguments.length?(t=null==n?null:n+"",i):t},i.responseType=function(n){return arguments.length?(l=n,i):l},i.response=function(n){return e=n,i},["get","post"].forEach(function(n){i[n]=function(){return i.send.apply(i,[n].concat(ra(arguments)))}}),i.send=function(e,r,u){if(2===arguments.length&&"function"==typeof r&&(u=r,r=null),c.open(e,n,!0),null==t||"accept"in a||(a.accept=t+",*/*"),c.setRequestHeader)for(var s in a)c.setRequestHeader(s,a[s]);return null!=t&&c.overrideMimeType&&c.overrideMimeType(t),null!=l&&(c.responseType=l),null!=u&&i.on("error",u).on("load",function(n){u(null,n)}),o.beforesend.call(i,c),c.send(null==r?null:r),i},i.abort=function(){return c.abort(),i},ta.rebind(i,o,"on"),null==r?i:i.get(Ct(r))}function Ct(n){return 1===n.length?function(t,e){n(null==t?e:null)}:n}function zt(n){var t=n.responseType;return t&&"text"!==t?n.response:n.responseText}function qt(){var n=Lt(),t=Tt()-n;t>24?(isFinite(t)&&(clearTimeout(tc),tc=setTimeout(qt,t)),nc=0):(nc=1,rc(qt))}function Lt(){var n=Date.now();for(ec=Ka;ec;)n>=ec.t&&(ec.f=ec.c(n-ec.t)),ec=ec.n;return n}function Tt(){for(var n,t=Ka,e=1/0;t;)t.f?t=n?n.n=t.n:Ka=t.n:(t.t8?function(n){return n/e}:function(n){return n*e},symbol:n}}function Pt(n){var t=n.decimal,e=n.thousands,r=n.grouping,u=n.currency,i=r&&e?function(n,t){for(var u=n.length,i=[],o=0,a=r[0],c=0;u>0&&a>0&&(c+a+1>t&&(a=Math.max(1,t-c)),i.push(n.substring(u-=a,u+a)),!((c+=a+1)>t));)a=r[o=(o+1)%r.length];return i.reverse().join(e)}:y;return function(n){var e=ic.exec(n),r=e[1]||" ",o=e[2]||">",a=e[3]||"-",c=e[4]||"",l=e[5],s=+e[6],f=e[7],h=e[8],g=e[9],p=1,v="",d="",m=!1,y=!0;switch(h&&(h=+h.substring(1)),(l||"0"===r&&"="===o)&&(l=r="0",o="="),g){case"n":f=!0,g="g";break;case"%":p=100,d="%",g="f";break;case"p":p=100,d="%",g="r";break;case"b":case"o":case"x":case"X":"#"===c&&(v="0"+g.toLowerCase());case"c":y=!1;case"d":m=!0,h=0;break;case"s":p=-1,g="r"}"$"===c&&(v=u[0],d=u[1]),"r"!=g||h||(g="g"),null!=h&&("g"==g?h=Math.max(1,Math.min(21,h)):("e"==g||"f"==g)&&(h=Math.max(0,Math.min(20,h)))),g=oc.get(g)||Ut;var M=l&&f;return function(n){var e=d;if(m&&n%1)return"";var u=0>n||0===n&&0>1/n?(n=-n,"-"):"-"===a?"":a;if(0>p){var c=ta.formatPrefix(n,h);n=c.scale(n),e=c.symbol+d}else n*=p;n=g(n,h);var x,b,_=n.lastIndexOf(".");if(0>_){var w=y?n.lastIndexOf("e"):-1;0>w?(x=n,b=""):(x=n.substring(0,w),b=n.substring(w))}else x=n.substring(0,_),b=t+n.substring(_+1);!l&&f&&(x=i(x,1/0));var S=v.length+x.length+b.length+(M?0:u.length),k=s>S?new Array(S=s-S+1).join(r):"";return M&&(x=i(k+x,k.length?s-b.length:1/0)),u+=v,n=x+b,("<"===o?u+n+k:">"===o?k+u+n:"^"===o?k.substring(0,S>>=1)+u+n+k.substring(S):u+(M?n:k+n))+e}}}function Ut(n){return n+""}function jt(){this._=new Date(arguments.length>1?Date.UTC.apply(this,arguments):arguments[0])}function Ft(n,t,e){function r(t){var e=n(t),r=i(e,1);return r-t>t-e?e:r}function u(e){return t(e=n(new cc(e-1)),1),e}function i(n,e){return t(n=new cc(+n),e),n}function o(n,r,i){var o=u(n),a=[];if(i>1)for(;r>o;)e(o)%i||a.push(new Date(+o)),t(o,1);else for(;r>o;)a.push(new Date(+o)),t(o,1);return a}function a(n,t,e){try{cc=jt;var r=new jt;return r._=n,o(r,t,e)}finally{cc=Date}}n.floor=n,n.round=r,n.ceil=u,n.offset=i,n.range=o;var c=n.utc=Ht(n);return c.floor=c,c.round=Ht(r),c.ceil=Ht(u),c.offset=Ht(i),c.range=a,n}function Ht(n){return function(t,e){try{cc=jt;var r=new jt;return r._=t,n(r,e)._}finally{cc=Date}}}function Ot(n){function t(n){function t(t){for(var e,u,i,o=[],a=-1,c=0;++aa;){if(r>=l)return-1;if(u=t.charCodeAt(a++),37===u){if(o=t.charAt(a++),i=C[o in sc?t.charAt(a++):o],!i||(r=i(n,e,r))<0)return-1}else if(u!=e.charCodeAt(r++))return-1}return r}function r(n,t,e){_.lastIndex=0;var r=_.exec(t.slice(e));return r?(n.w=w.get(r[0].toLowerCase()),e+r[0].length):-1}function u(n,t,e){x.lastIndex=0;var r=x.exec(t.slice(e));return r?(n.w=b.get(r[0].toLowerCase()),e+r[0].length):-1}function i(n,t,e){E.lastIndex=0;var r=E.exec(t.slice(e));return r?(n.m=A.get(r[0].toLowerCase()),e+r[0].length):-1}function o(n,t,e){S.lastIndex=0;var r=S.exec(t.slice(e));return r?(n.m=k.get(r[0].toLowerCase()),e+r[0].length):-1}function a(n,t,r){return e(n,N.c.toString(),t,r)}function c(n,t,r){return e(n,N.x.toString(),t,r)}function l(n,t,r){return e(n,N.X.toString(),t,r)}function s(n,t,e){var r=M.get(t.slice(e,e+=2).toLowerCase());return null==r?-1:(n.p=r,e)}var f=n.dateTime,h=n.date,g=n.time,p=n.periods,v=n.days,d=n.shortDays,m=n.months,y=n.shortMonths;t.utc=function(n){function e(n){try{cc=jt;var t=new cc;return t._=n,r(t)}finally{cc=Date}}var r=t(n);return e.parse=function(n){try{cc=jt;var t=r.parse(n);return t&&t._}finally{cc=Date}},e.toString=r.toString,e},t.multi=t.utc.multi=ae;var M=ta.map(),x=Yt(v),b=Zt(v),_=Yt(d),w=Zt(d),S=Yt(m),k=Zt(m),E=Yt(y),A=Zt(y);p.forEach(function(n,t){M.set(n.toLowerCase(),t)});var N={a:function(n){return d[n.getDay()]},A:function(n){return v[n.getDay()]},b:function(n){return y[n.getMonth()]},B:function(n){return m[n.getMonth()]},c:t(f),d:function(n,t){return It(n.getDate(),t,2)},e:function(n,t){return It(n.getDate(),t,2)},H:function(n,t){return It(n.getHours(),t,2)},I:function(n,t){return It(n.getHours()%12||12,t,2)},j:function(n,t){return It(1+ac.dayOfYear(n),t,3)},L:function(n,t){return It(n.getMilliseconds(),t,3)},m:function(n,t){return It(n.getMonth()+1,t,2)},M:function(n,t){return It(n.getMinutes(),t,2)},p:function(n){return p[+(n.getHours()>=12)]},S:function(n,t){return It(n.getSeconds(),t,2)},U:function(n,t){return It(ac.sundayOfYear(n),t,2)},w:function(n){return n.getDay()},W:function(n,t){return It(ac.mondayOfYear(n),t,2)},x:t(h),X:t(g),y:function(n,t){return It(n.getFullYear()%100,t,2)},Y:function(n,t){return It(n.getFullYear()%1e4,t,4)},Z:ie,"%":function(){return"%"}},C={a:r,A:u,b:i,B:o,c:a,d:Qt,e:Qt,H:te,I:te,j:ne,L:ue,m:Kt,M:ee,p:s,S:re,U:Xt,w:Vt,W:$t,x:c,X:l,y:Wt,Y:Bt,Z:Jt,"%":oe};return t}function It(n,t,e){var r=0>n?"-":"",u=(r?-n:n)+"",i=u.length;return r+(e>i?new Array(e-i+1).join(t)+u:u)}function Yt(n){return new RegExp("^(?:"+n.map(ta.requote).join("|")+")","i")}function Zt(n){for(var t=new l,e=-1,r=n.length;++e68?1900:2e3)}function Kt(n,t,e){fc.lastIndex=0;var r=fc.exec(t.slice(e,e+2));return r?(n.m=r[0]-1,e+r[0].length):-1}function Qt(n,t,e){fc.lastIndex=0;var r=fc.exec(t.slice(e,e+2));return r?(n.d=+r[0],e+r[0].length):-1}function ne(n,t,e){fc.lastIndex=0;var r=fc.exec(t.slice(e,e+3));return r?(n.j=+r[0],e+r[0].length):-1}function te(n,t,e){fc.lastIndex=0;var r=fc.exec(t.slice(e,e+2));return r?(n.H=+r[0],e+r[0].length):-1}function ee(n,t,e){fc.lastIndex=0;var r=fc.exec(t.slice(e,e+2));return r?(n.M=+r[0],e+r[0].length):-1}function re(n,t,e){fc.lastIndex=0;var r=fc.exec(t.slice(e,e+2));return r?(n.S=+r[0],e+r[0].length):-1}function ue(n,t,e){fc.lastIndex=0;var r=fc.exec(t.slice(e,e+3));return r?(n.L=+r[0],e+r[0].length):-1}function ie(n){var t=n.getTimezoneOffset(),e=t>0?"-":"+",r=ga(t)/60|0,u=ga(t)%60;return e+It(r,"0",2)+It(u,"0",2)}function oe(n,t,e){hc.lastIndex=0;var r=hc.exec(t.slice(e,e+1));return r?e+r[0].length:-1}function ae(n){for(var t=n.length,e=-1;++e=0?1:-1,a=o*e,c=Math.cos(t),l=Math.sin(t),s=i*l,f=u*c+s*Math.cos(a),h=s*o*Math.sin(a);yc.add(Math.atan2(h,f)),r=n,u=c,i=l}var t,e,r,u,i;Mc.point=function(o,a){Mc.point=n,r=(t=o)*Da,u=Math.cos(a=(e=a)*Da/2+qa/4),i=Math.sin(a)},Mc.lineEnd=function(){n(t,e)}}function pe(n){var t=n[0],e=n[1],r=Math.cos(e);return[r*Math.cos(t),r*Math.sin(t),Math.sin(e)]}function ve(n,t){return n[0]*t[0]+n[1]*t[1]+n[2]*t[2]}function de(n,t){return[n[1]*t[2]-n[2]*t[1],n[2]*t[0]-n[0]*t[2],n[0]*t[1]-n[1]*t[0]]}function me(n,t){n[0]+=t[0],n[1]+=t[1],n[2]+=t[2]}function ye(n,t){return[n[0]*t,n[1]*t,n[2]*t]}function Me(n){var t=Math.sqrt(n[0]*n[0]+n[1]*n[1]+n[2]*n[2]);n[0]/=t,n[1]/=t,n[2]/=t}function xe(n){return[Math.atan2(n[1],n[0]),tt(n[2])]}function be(n,t){return ga(n[0]-t[0])a;++a)u.point((e=n[a])[0],e[1]);return void u.lineEnd()}var c=new qe(e,n,null,!0),l=new qe(e,null,c,!1);c.o=l,i.push(c),o.push(l),c=new qe(r,n,null,!1),l=new qe(r,null,c,!0),c.o=l,i.push(c),o.push(l)}}),o.sort(t),ze(i),ze(o),i.length){for(var a=0,c=e,l=o.length;l>a;++a)o[a].e=c=!c;for(var s,f,h=i[0];;){for(var g=h,p=!0;g.v;)if((g=g.n)===h)return;s=g.z,u.lineStart();do{if(g.v=g.o.v=!0,g.e){if(p)for(var a=0,l=s.length;l>a;++a)u.point((f=s[a])[0],f[1]);else r(g.x,g.n.x,1,u);g=g.n}else{if(p){s=g.p.z;for(var a=s.length-1;a>=0;--a)u.point((f=s[a])[0],f[1])}else r(g.x,g.p.x,-1,u);g=g.p}g=g.o,s=g.z,p=!p}while(!g.v);u.lineEnd()}}}function ze(n){if(t=n.length){for(var t,e,r=0,u=n[0];++r0){for(b||(i.polygonStart(),b=!0),i.lineStart();++o1&&2&t&&e.push(e.pop().concat(e.shift())),g.push(e.filter(Te))}var g,p,v,d=t(i),m=u.invert(r[0],r[1]),y={point:o,lineStart:c,lineEnd:l,polygonStart:function(){y.point=s,y.lineStart=f,y.lineEnd=h,g=[],p=[]},polygonEnd:function(){y.point=o,y.lineStart=c,y.lineEnd=l,g=ta.merge(g);var n=Fe(m,p);g.length?(b||(i.polygonStart(),b=!0),Ce(g,De,n,e,i)):n&&(b||(i.polygonStart(),b=!0),i.lineStart(),e(null,null,1,i),i.lineEnd()),b&&(i.polygonEnd(),b=!1),g=p=null},sphere:function(){i.polygonStart(),i.lineStart(),e(null,null,1,i),i.lineEnd(),i.polygonEnd()}},M=Re(),x=t(M),b=!1;return y}}function Te(n){return n.length>1}function Re(){var n,t=[];return{lineStart:function(){t.push(n=[])},point:function(t,e){n.push([t,e])},lineEnd:b,buffer:function(){var e=t;return t=[],n=null,e},rejoin:function(){t.length>1&&t.push(t.pop().concat(t.shift()))}}}function De(n,t){return((n=n.x)[0]<0?n[1]-Ra-Ca:Ra-n[1])-((t=t.x)[0]<0?t[1]-Ra-Ca:Ra-t[1])}function Pe(n){var t,e=0/0,r=0/0,u=0/0;return{lineStart:function(){n.lineStart(),t=1},point:function(i,o){var a=i>0?qa:-qa,c=ga(i-e);ga(c-qa)0?Ra:-Ra),n.point(u,r),n.lineEnd(),n.lineStart(),n.point(a,r),n.point(i,r),t=0):u!==a&&c>=qa&&(ga(e-u)Ca?Math.atan((Math.sin(t)*(i=Math.cos(r))*Math.sin(e)-Math.sin(r)*(u=Math.cos(t))*Math.sin(n))/(u*i*o)):(t+r)/2}function je(n,t,e,r){var u;if(null==n)u=e*Ra,r.point(-qa,u),r.point(0,u),r.point(qa,u),r.point(qa,0),r.point(qa,-u),r.point(0,-u),r.point(-qa,-u),r.point(-qa,0),r.point(-qa,u);else if(ga(n[0]-t[0])>Ca){var i=n[0]a;++a){var l=t[a],s=l.length;if(s)for(var f=l[0],h=f[0],g=f[1]/2+qa/4,p=Math.sin(g),v=Math.cos(g),d=1;;){d===s&&(d=0),n=l[d];var m=n[0],y=n[1]/2+qa/4,M=Math.sin(y),x=Math.cos(y),b=m-h,_=b>=0?1:-1,w=_*b,S=w>qa,k=p*M;if(yc.add(Math.atan2(k*_*Math.sin(w),v*x+k*Math.cos(w))),i+=S?b+_*La:b,S^h>=e^m>=e){var E=de(pe(f),pe(n));Me(E);var A=de(u,E);Me(A);var N=(S^b>=0?-1:1)*tt(A[2]);(r>N||r===N&&(E[0]||E[1]))&&(o+=S^b>=0?1:-1)}if(!d++)break;h=m,p=M,v=x,f=n}}return(-Ca>i||Ca>i&&0>yc)^1&o}function He(n){function t(n,t){return Math.cos(n)*Math.cos(t)>i}function e(n){var e,i,c,l,s;return{lineStart:function(){l=c=!1,s=1},point:function(f,h){var g,p=[f,h],v=t(f,h),d=o?v?0:u(f,h):v?u(f+(0>f?qa:-qa),h):0;if(!e&&(l=c=v)&&n.lineStart(),v!==c&&(g=r(e,p),(be(e,g)||be(p,g))&&(p[0]+=Ca,p[1]+=Ca,v=t(p[0],p[1]))),v!==c)s=0,v?(n.lineStart(),g=r(p,e),n.point(g[0],g[1])):(g=r(e,p),n.point(g[0],g[1]),n.lineEnd()),e=g;else if(a&&e&&o^v){var m;d&i||!(m=r(p,e,!0))||(s=0,o?(n.lineStart(),n.point(m[0][0],m[0][1]),n.point(m[1][0],m[1][1]),n.lineEnd()):(n.point(m[1][0],m[1][1]),n.lineEnd(),n.lineStart(),n.point(m[0][0],m[0][1])))}!v||e&&be(e,p)||n.point(p[0],p[1]),e=p,c=v,i=d},lineEnd:function(){c&&n.lineEnd(),e=null},clean:function(){return s|(l&&c)<<1}}}function r(n,t,e){var r=pe(n),u=pe(t),o=[1,0,0],a=de(r,u),c=ve(a,a),l=a[0],s=c-l*l;if(!s)return!e&&n;var f=i*c/s,h=-i*l/s,g=de(o,a),p=ye(o,f),v=ye(a,h);me(p,v);var d=g,m=ve(p,d),y=ve(d,d),M=m*m-y*(ve(p,p)-1);if(!(0>M)){var x=Math.sqrt(M),b=ye(d,(-m-x)/y);if(me(b,p),b=xe(b),!e)return b;var _,w=n[0],S=t[0],k=n[1],E=t[1];w>S&&(_=w,w=S,S=_);var A=S-w,N=ga(A-qa)A;if(!N&&k>E&&(_=k,k=E,E=_),C?N?k+E>0^b[1]<(ga(b[0]-w)qa^(w<=b[0]&&b[0]<=S)){var z=ye(d,(-m+x)/y);return me(z,p),[b,xe(z)]}}}function u(t,e){var r=o?n:qa-n,u=0;return-r>t?u|=1:t>r&&(u|=2),-r>e?u|=4:e>r&&(u|=8),u}var i=Math.cos(n),o=i>0,a=ga(i)>Ca,c=gr(n,6*Da);return Le(t,e,c,o?[0,-n]:[-qa,n-qa])}function Oe(n,t,e,r){return function(u){var i,o=u.a,a=u.b,c=o.x,l=o.y,s=a.x,f=a.y,h=0,g=1,p=s-c,v=f-l;if(i=n-c,p||!(i>0)){if(i/=p,0>p){if(h>i)return;g>i&&(g=i)}else if(p>0){if(i>g)return;i>h&&(h=i)}if(i=e-c,p||!(0>i)){if(i/=p,0>p){if(i>g)return;i>h&&(h=i)}else if(p>0){if(h>i)return;g>i&&(g=i)}if(i=t-l,v||!(i>0)){if(i/=v,0>v){if(h>i)return;g>i&&(g=i)}else if(v>0){if(i>g)return;i>h&&(h=i)}if(i=r-l,v||!(0>i)){if(i/=v,0>v){if(i>g)return;i>h&&(h=i)}else if(v>0){if(h>i)return;g>i&&(g=i)}return h>0&&(u.a={x:c+h*p,y:l+h*v}),1>g&&(u.b={x:c+g*p,y:l+g*v}),u}}}}}}function Ie(n,t,e,r){function u(r,u){return ga(r[0]-n)0?0:3:ga(r[0]-e)0?2:1:ga(r[1]-t)0?1:0:u>0?3:2}function i(n,t){return o(n.x,t.x)}function o(n,t){var e=u(n,1),r=u(t,1);return e!==r?e-r:0===e?t[1]-n[1]:1===e?n[0]-t[0]:2===e?n[1]-t[1]:t[0]-n[0]}return function(a){function c(n){for(var t=0,e=d.length,r=n[1],u=0;e>u;++u)for(var i,o=1,a=d[u],c=a.length,l=a[0];c>o;++o)i=a[o],l[1]<=r?i[1]>r&&Q(l,i,n)>0&&++t:i[1]<=r&&Q(l,i,n)<0&&--t,l=i;return 0!==t}function l(i,a,c,l){var s=0,f=0;if(null==i||(s=u(i,c))!==(f=u(a,c))||o(i,a)<0^c>0){do l.point(0===s||3===s?n:e,s>1?r:t);while((s=(s+c+4)%4)!==f)}else l.point(a[0],a[1])}function s(u,i){return u>=n&&e>=u&&i>=t&&r>=i}function f(n,t){s(n,t)&&a.point(n,t)}function h(){C.point=p,d&&d.push(m=[]),S=!0,w=!1,b=_=0/0}function g(){v&&(p(y,M),x&&w&&A.rejoin(),v.push(A.buffer())),C.point=f,w&&a.lineEnd()}function p(n,t){n=Math.max(-Tc,Math.min(Tc,n)),t=Math.max(-Tc,Math.min(Tc,t));var e=s(n,t);if(d&&m.push([n,t]),S)y=n,M=t,x=e,S=!1,e&&(a.lineStart(),a.point(n,t));else if(e&&w)a.point(n,t);else{var r={a:{x:b,y:_},b:{x:n,y:t}};N(r)?(w||(a.lineStart(),a.point(r.a.x,r.a.y)),a.point(r.b.x,r.b.y),e||a.lineEnd(),k=!1):e&&(a.lineStart(),a.point(n,t),k=!1)}b=n,_=t,w=e}var v,d,m,y,M,x,b,_,w,S,k,E=a,A=Re(),N=Oe(n,t,e,r),C={point:f,lineStart:h,lineEnd:g,polygonStart:function(){a=A,v=[],d=[],k=!0},polygonEnd:function(){a=E,v=ta.merge(v);var t=c([n,r]),e=k&&t,u=v.length;(e||u)&&(a.polygonStart(),e&&(a.lineStart(),l(null,null,1,a),a.lineEnd()),u&&Ce(v,i,t,l,a),a.polygonEnd()),v=d=m=null}};return C}}function Ye(n){var t=0,e=qa/3,r=ir(n),u=r(t,e);return u.parallels=function(n){return arguments.length?r(t=n[0]*qa/180,e=n[1]*qa/180):[t/qa*180,e/qa*180]},u}function Ze(n,t){function e(n,t){var e=Math.sqrt(i-2*u*Math.sin(t))/u;return[e*Math.sin(n*=u),o-e*Math.cos(n)]}var r=Math.sin(n),u=(r+Math.sin(t))/2,i=1+r*(2*u-r),o=Math.sqrt(i)/u;return e.invert=function(n,t){var e=o-t;return[Math.atan2(n,e)/u,tt((i-(n*n+e*e)*u*u)/(2*u))]},e}function Ve(){function n(n,t){Dc+=u*n-r*t,r=n,u=t}var t,e,r,u;Hc.point=function(i,o){Hc.point=n,t=r=i,e=u=o},Hc.lineEnd=function(){n(t,e)}}function Xe(n,t){Pc>n&&(Pc=n),n>jc&&(jc=n),Uc>t&&(Uc=t),t>Fc&&(Fc=t)}function $e(){function n(n,t){o.push("M",n,",",t,i)}function t(n,t){o.push("M",n,",",t),a.point=e}function e(n,t){o.push("L",n,",",t)}function r(){a.point=n}function u(){o.push("Z")}var i=Be(4.5),o=[],a={point:n,lineStart:function(){a.point=t},lineEnd:r,polygonStart:function(){a.lineEnd=u},polygonEnd:function(){a.lineEnd=r,a.point=n},pointRadius:function(n){return i=Be(n),a},result:function(){if(o.length){var n=o.join("");return o=[],n}}};return a}function Be(n){return"m0,"+n+"a"+n+","+n+" 0 1,1 0,"+-2*n+"a"+n+","+n+" 0 1,1 0,"+2*n+"z"}function We(n,t){_c+=n,wc+=t,++Sc}function Je(){function n(n,r){var u=n-t,i=r-e,o=Math.sqrt(u*u+i*i);kc+=o*(t+n)/2,Ec+=o*(e+r)/2,Ac+=o,We(t=n,e=r)}var t,e;Ic.point=function(r,u){Ic.point=n,We(t=r,e=u)}}function Ge(){Ic.point=We}function Ke(){function n(n,t){var e=n-r,i=t-u,o=Math.sqrt(e*e+i*i);kc+=o*(r+n)/2,Ec+=o*(u+t)/2,Ac+=o,o=u*n-r*t,Nc+=o*(r+n),Cc+=o*(u+t),zc+=3*o,We(r=n,u=t)}var t,e,r,u;Ic.point=function(i,o){Ic.point=n,We(t=r=i,e=u=o)},Ic.lineEnd=function(){n(t,e)}}function Qe(n){function t(t,e){n.moveTo(t+o,e),n.arc(t,e,o,0,La)}function e(t,e){n.moveTo(t,e),a.point=r}function r(t,e){n.lineTo(t,e)}function u(){a.point=t}function i(){n.closePath()}var o=4.5,a={point:t,lineStart:function(){a.point=e},lineEnd:u,polygonStart:function(){a.lineEnd=i},polygonEnd:function(){a.lineEnd=u,a.point=t},pointRadius:function(n){return o=n,a},result:b};return a}function nr(n){function t(n){return(a?r:e)(n)}function e(t){return rr(t,function(e,r){e=n(e,r),t.point(e[0],e[1])})}function r(t){function e(e,r){e=n(e,r),t.point(e[0],e[1])}function r(){M=0/0,S.point=i,t.lineStart()}function i(e,r){var i=pe([e,r]),o=n(e,r);u(M,x,y,b,_,w,M=o[0],x=o[1],y=e,b=i[0],_=i[1],w=i[2],a,t),t.point(M,x)}function o(){S.point=e,t.lineEnd()}function c(){r(),S.point=l,S.lineEnd=s}function l(n,t){i(f=n,h=t),g=M,p=x,v=b,d=_,m=w,S.point=i}function s(){u(M,x,y,b,_,w,g,p,f,v,d,m,a,t),S.lineEnd=o,o()}var f,h,g,p,v,d,m,y,M,x,b,_,w,S={point:e,lineStart:r,lineEnd:o,polygonStart:function(){t.polygonStart(),S.lineStart=c +},polygonEnd:function(){t.polygonEnd(),S.lineStart=r}};return S}function u(t,e,r,a,c,l,s,f,h,g,p,v,d,m){var y=s-t,M=f-e,x=y*y+M*M;if(x>4*i&&d--){var b=a+g,_=c+p,w=l+v,S=Math.sqrt(b*b+_*_+w*w),k=Math.asin(w/=S),E=ga(ga(w)-1)i||ga((y*z+M*q)/x-.5)>.3||o>a*g+c*p+l*v)&&(u(t,e,r,a,c,l,N,C,E,b/=S,_/=S,w,d,m),m.point(N,C),u(N,C,E,b,_,w,s,f,h,g,p,v,d,m))}}var i=.5,o=Math.cos(30*Da),a=16;return t.precision=function(n){return arguments.length?(a=(i=n*n)>0&&16,t):Math.sqrt(i)},t}function tr(n){var t=nr(function(t,e){return n([t*Pa,e*Pa])});return function(n){return or(t(n))}}function er(n){this.stream=n}function rr(n,t){return{point:t,sphere:function(){n.sphere()},lineStart:function(){n.lineStart()},lineEnd:function(){n.lineEnd()},polygonStart:function(){n.polygonStart()},polygonEnd:function(){n.polygonEnd()}}}function ur(n){return ir(function(){return n})()}function ir(n){function t(n){return n=a(n[0]*Da,n[1]*Da),[n[0]*h+c,l-n[1]*h]}function e(n){return n=a.invert((n[0]-c)/h,(l-n[1])/h),n&&[n[0]*Pa,n[1]*Pa]}function r(){a=Ae(o=lr(m,M,x),i);var n=i(v,d);return c=g-n[0]*h,l=p+n[1]*h,u()}function u(){return s&&(s.valid=!1,s=null),t}var i,o,a,c,l,s,f=nr(function(n,t){return n=i(n,t),[n[0]*h+c,l-n[1]*h]}),h=150,g=480,p=250,v=0,d=0,m=0,M=0,x=0,b=Lc,_=y,w=null,S=null;return t.stream=function(n){return s&&(s.valid=!1),s=or(b(o,f(_(n)))),s.valid=!0,s},t.clipAngle=function(n){return arguments.length?(b=null==n?(w=n,Lc):He((w=+n)*Da),u()):w},t.clipExtent=function(n){return arguments.length?(S=n,_=n?Ie(n[0][0],n[0][1],n[1][0],n[1][1]):y,u()):S},t.scale=function(n){return arguments.length?(h=+n,r()):h},t.translate=function(n){return arguments.length?(g=+n[0],p=+n[1],r()):[g,p]},t.center=function(n){return arguments.length?(v=n[0]%360*Da,d=n[1]%360*Da,r()):[v*Pa,d*Pa]},t.rotate=function(n){return arguments.length?(m=n[0]%360*Da,M=n[1]%360*Da,x=n.length>2?n[2]%360*Da:0,r()):[m*Pa,M*Pa,x*Pa]},ta.rebind(t,f,"precision"),function(){return i=n.apply(this,arguments),t.invert=i.invert&&e,r()}}function or(n){return rr(n,function(t,e){n.point(t*Da,e*Da)})}function ar(n,t){return[n,t]}function cr(n,t){return[n>qa?n-La:-qa>n?n+La:n,t]}function lr(n,t,e){return n?t||e?Ae(fr(n),hr(t,e)):fr(n):t||e?hr(t,e):cr}function sr(n){return function(t,e){return t+=n,[t>qa?t-La:-qa>t?t+La:t,e]}}function fr(n){var t=sr(n);return t.invert=sr(-n),t}function hr(n,t){function e(n,t){var e=Math.cos(t),a=Math.cos(n)*e,c=Math.sin(n)*e,l=Math.sin(t),s=l*r+a*u;return[Math.atan2(c*i-s*o,a*r-l*u),tt(s*i+c*o)]}var r=Math.cos(n),u=Math.sin(n),i=Math.cos(t),o=Math.sin(t);return e.invert=function(n,t){var e=Math.cos(t),a=Math.cos(n)*e,c=Math.sin(n)*e,l=Math.sin(t),s=l*i-c*o;return[Math.atan2(c*i+l*o,a*r+s*u),tt(s*r-a*u)]},e}function gr(n,t){var e=Math.cos(n),r=Math.sin(n);return function(u,i,o,a){var c=o*t;null!=u?(u=pr(e,u),i=pr(e,i),(o>0?i>u:u>i)&&(u+=o*La)):(u=n+o*La,i=n-.5*c);for(var l,s=u;o>0?s>i:i>s;s-=c)a.point((l=xe([e,-r*Math.cos(s),-r*Math.sin(s)]))[0],l[1])}}function pr(n,t){var e=pe(t);e[0]-=n,Me(e);var r=nt(-e[1]);return((-e[2]<0?-r:r)+2*Math.PI-Ca)%(2*Math.PI)}function vr(n,t,e){var r=ta.range(n,t-Ca,e).concat(t);return function(n){return r.map(function(t){return[n,t]})}}function dr(n,t,e){var r=ta.range(n,t-Ca,e).concat(t);return function(n){return r.map(function(t){return[t,n]})}}function mr(n){return n.source}function yr(n){return n.target}function Mr(n,t,e,r){var u=Math.cos(t),i=Math.sin(t),o=Math.cos(r),a=Math.sin(r),c=u*Math.cos(n),l=u*Math.sin(n),s=o*Math.cos(e),f=o*Math.sin(e),h=2*Math.asin(Math.sqrt(it(r-t)+u*o*it(e-n))),g=1/Math.sin(h),p=h?function(n){var t=Math.sin(n*=h)*g,e=Math.sin(h-n)*g,r=e*c+t*s,u=e*l+t*f,o=e*i+t*a;return[Math.atan2(u,r)*Pa,Math.atan2(o,Math.sqrt(r*r+u*u))*Pa]}:function(){return[n*Pa,t*Pa]};return p.distance=h,p}function xr(){function n(n,u){var i=Math.sin(u*=Da),o=Math.cos(u),a=ga((n*=Da)-t),c=Math.cos(a);Yc+=Math.atan2(Math.sqrt((a=o*Math.sin(a))*a+(a=r*i-e*o*c)*a),e*i+r*o*c),t=n,e=i,r=o}var t,e,r;Zc.point=function(u,i){t=u*Da,e=Math.sin(i*=Da),r=Math.cos(i),Zc.point=n},Zc.lineEnd=function(){Zc.point=Zc.lineEnd=b}}function br(n,t){function e(t,e){var r=Math.cos(t),u=Math.cos(e),i=n(r*u);return[i*u*Math.sin(t),i*Math.sin(e)]}return e.invert=function(n,e){var r=Math.sqrt(n*n+e*e),u=t(r),i=Math.sin(u),o=Math.cos(u);return[Math.atan2(n*i,r*o),Math.asin(r&&e*i/r)]},e}function _r(n,t){function e(n,t){o>0?-Ra+Ca>t&&(t=-Ra+Ca):t>Ra-Ca&&(t=Ra-Ca);var e=o/Math.pow(u(t),i);return[e*Math.sin(i*n),o-e*Math.cos(i*n)]}var r=Math.cos(n),u=function(n){return Math.tan(qa/4+n/2)},i=n===t?Math.sin(n):Math.log(r/Math.cos(t))/Math.log(u(t)/u(n)),o=r*Math.pow(u(n),i)/i;return i?(e.invert=function(n,t){var e=o-t,r=K(i)*Math.sqrt(n*n+e*e);return[Math.atan2(n,e)/i,2*Math.atan(Math.pow(o/r,1/i))-Ra]},e):Sr}function wr(n,t){function e(n,t){var e=i-t;return[e*Math.sin(u*n),i-e*Math.cos(u*n)]}var r=Math.cos(n),u=n===t?Math.sin(n):(r-Math.cos(t))/(t-n),i=r/u+n;return ga(u)u;u++){for(;r>1&&Q(n[e[r-2]],n[e[r-1]],n[u])<=0;)--r;e[r++]=u}return e.slice(0,r)}function zr(n,t){return n[0]-t[0]||n[1]-t[1]}function qr(n,t,e){return(e[0]-t[0])*(n[1]-t[1])<(e[1]-t[1])*(n[0]-t[0])}function Lr(n,t,e,r){var u=n[0],i=e[0],o=t[0]-u,a=r[0]-i,c=n[1],l=e[1],s=t[1]-c,f=r[1]-l,h=(a*(c-l)-f*(u-i))/(f*o-a*s);return[u+h*o,c+h*s]}function Tr(n){var t=n[0],e=n[n.length-1];return!(t[0]-e[0]||t[1]-e[1])}function Rr(){tu(this),this.edge=this.site=this.circle=null}function Dr(n){var t=el.pop()||new Rr;return t.site=n,t}function Pr(n){Xr(n),Qc.remove(n),el.push(n),tu(n)}function Ur(n){var t=n.circle,e=t.x,r=t.cy,u={x:e,y:r},i=n.P,o=n.N,a=[n];Pr(n);for(var c=i;c.circle&&ga(e-c.circle.x)s;++s)l=a[s],c=a[s-1],Kr(l.edge,c.site,l.site,u);c=a[0],l=a[f-1],l.edge=Jr(c.site,l.site,null,u),Vr(c),Vr(l)}function jr(n){for(var t,e,r,u,i=n.x,o=n.y,a=Qc._;a;)if(r=Fr(a,o)-i,r>Ca)a=a.L;else{if(u=i-Hr(a,o),!(u>Ca)){r>-Ca?(t=a.P,e=a):u>-Ca?(t=a,e=a.N):t=e=a;break}if(!a.R){t=a;break}a=a.R}var c=Dr(n);if(Qc.insert(t,c),t||e){if(t===e)return Xr(t),e=Dr(t.site),Qc.insert(c,e),c.edge=e.edge=Jr(t.site,c.site),Vr(t),void Vr(e);if(!e)return void(c.edge=Jr(t.site,c.site));Xr(t),Xr(e);var l=t.site,s=l.x,f=l.y,h=n.x-s,g=n.y-f,p=e.site,v=p.x-s,d=p.y-f,m=2*(h*d-g*v),y=h*h+g*g,M=v*v+d*d,x={x:(d*y-g*M)/m+s,y:(h*M-v*y)/m+f};Kr(e.edge,l,p,x),c.edge=Jr(l,n,null,x),e.edge=Jr(n,p,null,x),Vr(t),Vr(e)}}function Fr(n,t){var e=n.site,r=e.x,u=e.y,i=u-t;if(!i)return r;var o=n.P;if(!o)return-1/0;e=o.site;var a=e.x,c=e.y,l=c-t;if(!l)return a;var s=a-r,f=1/i-1/l,h=s/l;return f?(-h+Math.sqrt(h*h-2*f*(s*s/(-2*l)-c+l/2+u-i/2)))/f+r:(r+a)/2}function Hr(n,t){var e=n.N;if(e)return Fr(e,t);var r=n.site;return r.y===t?r.x:1/0}function Or(n){this.site=n,this.edges=[]}function Ir(n){for(var t,e,r,u,i,o,a,c,l,s,f=n[0][0],h=n[1][0],g=n[0][1],p=n[1][1],v=Kc,d=v.length;d--;)if(i=v[d],i&&i.prepare())for(a=i.edges,c=a.length,o=0;c>o;)s=a[o].end(),r=s.x,u=s.y,l=a[++o%c].start(),t=l.x,e=l.y,(ga(r-t)>Ca||ga(u-e)>Ca)&&(a.splice(o,0,new Qr(Gr(i.site,s,ga(r-f)Ca?{x:f,y:ga(t-f)Ca?{x:ga(e-p)Ca?{x:h,y:ga(t-h)Ca?{x:ga(e-g)=-za)){var g=c*c+l*l,p=s*s+f*f,v=(f*g-l*p)/h,d=(c*p-s*g)/h,f=d+a,m=rl.pop()||new Zr;m.arc=n,m.site=u,m.x=v+o,m.y=f+Math.sqrt(v*v+d*d),m.cy=f,n.circle=m;for(var y=null,M=tl._;M;)if(m.yd||d>=a)return;if(h>p){if(i){if(i.y>=l)return}else i={x:d,y:c};e={x:d,y:l}}else{if(i){if(i.yr||r>1)if(h>p){if(i){if(i.y>=l)return}else i={x:(c-u)/r,y:c};e={x:(l-u)/r,y:l}}else{if(i){if(i.yg){if(i){if(i.x>=a)return}else i={x:o,y:r*o+u};e={x:a,y:r*a+u}}else{if(i){if(i.xi||f>o||r>h||u>g)){if(p=n.point){var p,v=t-n.x,d=e-n.y,m=v*v+d*d;if(c>m){var y=Math.sqrt(c=m);r=t-y,u=e-y,i=t+y,o=e+y,a=p}}for(var M=n.nodes,x=.5*(s+h),b=.5*(f+g),_=t>=x,w=e>=b,S=w<<1|_,k=S+4;k>S;++S)if(n=M[3&S])switch(3&S){case 0:l(n,s,f,x,b);break;case 1:l(n,x,f,h,b);break;case 2:l(n,s,b,x,g);break;case 3:l(n,x,b,h,g)}}}(n,r,u,i,o),a}function gu(n,t){n=ta.rgb(n),t=ta.rgb(t);var e=n.r,r=n.g,u=n.b,i=t.r-e,o=t.g-r,a=t.b-u;return function(n){return"#"+xt(Math.round(e+i*n))+xt(Math.round(r+o*n))+xt(Math.round(u+a*n))}}function pu(n,t){var e,r={},u={};for(e in n)e in t?r[e]=mu(n[e],t[e]):u[e]=n[e];for(e in t)e in n||(u[e]=t[e]);return function(n){for(e in r)u[e]=r[e](n);return u}}function vu(n,t){return n=+n,t=+t,function(e){return n*(1-e)+t*e}}function du(n,t){var e,r,u,i=il.lastIndex=ol.lastIndex=0,o=-1,a=[],c=[];for(n+="",t+="";(e=il.exec(n))&&(r=ol.exec(t));)(u=r.index)>i&&(u=t.slice(i,u),a[o]?a[o]+=u:a[++o]=u),(e=e[0])===(r=r[0])?a[o]?a[o]+=r:a[++o]=r:(a[++o]=null,c.push({i:o,x:vu(e,r)})),i=ol.lastIndex;return ir;++r)a[(e=c[r]).i]=e.x(n);return a.join("")})}function mu(n,t){for(var e,r=ta.interpolators.length;--r>=0&&!(e=ta.interpolators[r](n,t)););return e}function yu(n,t){var e,r=[],u=[],i=n.length,o=t.length,a=Math.min(n.length,t.length);for(e=0;a>e;++e)r.push(mu(n[e],t[e]));for(;i>e;++e)u[e]=n[e];for(;o>e;++e)u[e]=t[e];return function(n){for(e=0;a>e;++e)u[e]=r[e](n);return u}}function Mu(n){return function(t){return 0>=t?0:t>=1?1:n(t)}}function xu(n){return function(t){return 1-n(1-t)}}function bu(n){return function(t){return.5*(.5>t?n(2*t):2-n(2-2*t))}}function _u(n){return n*n}function wu(n){return n*n*n}function Su(n){if(0>=n)return 0;if(n>=1)return 1;var t=n*n,e=t*n;return 4*(.5>n?e:3*(n-t)+e-.75)}function ku(n){return function(t){return Math.pow(t,n)}}function Eu(n){return 1-Math.cos(n*Ra)}function Au(n){return Math.pow(2,10*(n-1))}function Nu(n){return 1-Math.sqrt(1-n*n)}function Cu(n,t){var e;return arguments.length<2&&(t=.45),arguments.length?e=t/La*Math.asin(1/n):(n=1,e=t/4),function(r){return 1+n*Math.pow(2,-10*r)*Math.sin((r-e)*La/t)}}function zu(n){return n||(n=1.70158),function(t){return t*t*((n+1)*t-n)}}function qu(n){return 1/2.75>n?7.5625*n*n:2/2.75>n?7.5625*(n-=1.5/2.75)*n+.75:2.5/2.75>n?7.5625*(n-=2.25/2.75)*n+.9375:7.5625*(n-=2.625/2.75)*n+.984375}function Lu(n,t){n=ta.hcl(n),t=ta.hcl(t);var e=n.h,r=n.c,u=n.l,i=t.h-e,o=t.c-r,a=t.l-u;return isNaN(o)&&(o=0,r=isNaN(r)?t.c:r),isNaN(i)?(i=0,e=isNaN(e)?t.h:e):i>180?i-=360:-180>i&&(i+=360),function(n){return st(e+i*n,r+o*n,u+a*n)+""}}function Tu(n,t){n=ta.hsl(n),t=ta.hsl(t);var e=n.h,r=n.s,u=n.l,i=t.h-e,o=t.s-r,a=t.l-u;return isNaN(o)&&(o=0,r=isNaN(r)?t.s:r),isNaN(i)?(i=0,e=isNaN(e)?t.h:e):i>180?i-=360:-180>i&&(i+=360),function(n){return ct(e+i*n,r+o*n,u+a*n)+""}}function Ru(n,t){n=ta.lab(n),t=ta.lab(t);var e=n.l,r=n.a,u=n.b,i=t.l-e,o=t.a-r,a=t.b-u;return function(n){return ht(e+i*n,r+o*n,u+a*n)+""}}function Du(n,t){return t-=n,function(e){return Math.round(n+t*e)}}function Pu(n){var t=[n.a,n.b],e=[n.c,n.d],r=ju(t),u=Uu(t,e),i=ju(Fu(e,t,-u))||0;t[0]*e[1]180?s+=360:s-l>180&&(l+=360),u.push({i:r.push(r.pop()+"rotate(",null,")")-2,x:vu(l,s)})):s&&r.push(r.pop()+"rotate("+s+")"),f!=h?u.push({i:r.push(r.pop()+"skewX(",null,")")-2,x:vu(f,h)}):h&&r.push(r.pop()+"skewX("+h+")"),g[0]!=p[0]||g[1]!=p[1]?(e=r.push(r.pop()+"scale(",null,",",null,")"),u.push({i:e-4,x:vu(g[0],p[0])},{i:e-2,x:vu(g[1],p[1])})):(1!=p[0]||1!=p[1])&&r.push(r.pop()+"scale("+p+")"),e=u.length,function(n){for(var t,i=-1;++i=0;)e.push(u[r])}function Qu(n,t){for(var e=[n],r=[];null!=(n=e.pop());)if(r.push(n),(i=n.children)&&(u=i.length))for(var u,i,o=-1;++oe;++e)(t=n[e][1])>u&&(r=e,u=t);return r}function si(n){return n.reduce(fi,0)}function fi(n,t){return n+t[1]}function hi(n,t){return gi(n,Math.ceil(Math.log(t.length)/Math.LN2+1))}function gi(n,t){for(var e=-1,r=+n[0],u=(n[1]-r)/t,i=[];++e<=t;)i[e]=u*e+r;return i}function pi(n){return[ta.min(n),ta.max(n)]}function vi(n,t){return n.value-t.value}function di(n,t){var e=n._pack_next;n._pack_next=t,t._pack_prev=n,t._pack_next=e,e._pack_prev=t}function mi(n,t){n._pack_next=t,t._pack_prev=n}function yi(n,t){var e=t.x-n.x,r=t.y-n.y,u=n.r+t.r;return.999*u*u>e*e+r*r}function Mi(n){function t(n){s=Math.min(n.x-n.r,s),f=Math.max(n.x+n.r,f),h=Math.min(n.y-n.r,h),g=Math.max(n.y+n.r,g)}if((e=n.children)&&(l=e.length)){var e,r,u,i,o,a,c,l,s=1/0,f=-1/0,h=1/0,g=-1/0;if(e.forEach(xi),r=e[0],r.x=-r.r,r.y=0,t(r),l>1&&(u=e[1],u.x=u.r,u.y=0,t(u),l>2))for(i=e[2],wi(r,u,i),t(i),di(r,i),r._pack_prev=i,di(i,u),u=r._pack_next,o=3;l>o;o++){wi(r,u,i=e[o]);var p=0,v=1,d=1;for(a=u._pack_next;a!==u;a=a._pack_next,v++)if(yi(a,i)){p=1;break}if(1==p)for(c=r._pack_prev;c!==a._pack_prev&&!yi(c,i);c=c._pack_prev,d++);p?(d>v||v==d&&u.ro;o++)i=e[o],i.x-=m,i.y-=y,M=Math.max(M,i.r+Math.sqrt(i.x*i.x+i.y*i.y));n.r=M,e.forEach(bi)}}function xi(n){n._pack_next=n._pack_prev=n}function bi(n){delete n._pack_next,delete n._pack_prev}function _i(n,t,e,r){var u=n.children;if(n.x=t+=r*n.x,n.y=e+=r*n.y,n.r*=r,u)for(var i=-1,o=u.length;++i=0;)t=u[i],t.z+=e,t.m+=e,e+=t.s+(r+=t.c)}function Ci(n,t,e){return n.a.parent===t.parent?n.a:e}function zi(n){return 1+ta.max(n,function(n){return n.y})}function qi(n){return n.reduce(function(n,t){return n+t.x},0)/n.length}function Li(n){var t=n.children;return t&&t.length?Li(t[0]):n}function Ti(n){var t,e=n.children;return e&&(t=e.length)?Ti(e[t-1]):n}function Ri(n){return{x:n.x,y:n.y,dx:n.dx,dy:n.dy}}function Di(n,t){var e=n.x+t[3],r=n.y+t[0],u=n.dx-t[1]-t[3],i=n.dy-t[0]-t[2];return 0>u&&(e+=u/2,u=0),0>i&&(r+=i/2,i=0),{x:e,y:r,dx:u,dy:i}}function Pi(n){var t=n[0],e=n[n.length-1];return e>t?[t,e]:[e,t]}function Ui(n){return n.rangeExtent?n.rangeExtent():Pi(n.range())}function ji(n,t,e,r){var u=e(n[0],n[1]),i=r(t[0],t[1]);return function(n){return i(u(n))}}function Fi(n,t){var e,r=0,u=n.length-1,i=n[r],o=n[u];return i>o&&(e=r,r=u,u=e,e=i,i=o,o=e),n[r]=t.floor(i),n[u]=t.ceil(o),n}function Hi(n){return n?{floor:function(t){return Math.floor(t/n)*n},ceil:function(t){return Math.ceil(t/n)*n}}:ml}function Oi(n,t,e,r){var u=[],i=[],o=0,a=Math.min(n.length,t.length)-1;for(n[a]2?Oi:ji,c=r?Iu:Ou;return o=u(n,t,c,e),a=u(t,n,c,mu),i}function i(n){return o(n)}var o,a;return i.invert=function(n){return a(n)},i.domain=function(t){return arguments.length?(n=t.map(Number),u()):n},i.range=function(n){return arguments.length?(t=n,u()):t},i.rangeRound=function(n){return i.range(n).interpolate(Du)},i.clamp=function(n){return arguments.length?(r=n,u()):r},i.interpolate=function(n){return arguments.length?(e=n,u()):e},i.ticks=function(t){return Xi(n,t)},i.tickFormat=function(t,e){return $i(n,t,e)},i.nice=function(t){return Zi(n,t),u()},i.copy=function(){return Ii(n,t,e,r)},u()}function Yi(n,t){return ta.rebind(n,t,"range","rangeRound","interpolate","clamp")}function Zi(n,t){return Fi(n,Hi(Vi(n,t)[2]))}function Vi(n,t){null==t&&(t=10);var e=Pi(n),r=e[1]-e[0],u=Math.pow(10,Math.floor(Math.log(r/t)/Math.LN10)),i=t/r*u;return.15>=i?u*=10:.35>=i?u*=5:.75>=i&&(u*=2),e[0]=Math.ceil(e[0]/u)*u,e[1]=Math.floor(e[1]/u)*u+.5*u,e[2]=u,e}function Xi(n,t){return ta.range.apply(ta,Vi(n,t))}function $i(n,t,e){var r=Vi(n,t);if(e){var u=ic.exec(e);if(u.shift(),"s"===u[8]){var i=ta.formatPrefix(Math.max(ga(r[0]),ga(r[1])));return u[7]||(u[7]="."+Bi(i.scale(r[2]))),u[8]="f",e=ta.format(u.join("")),function(n){return e(i.scale(n))+i.symbol}}u[7]||(u[7]="."+Wi(u[8],r)),e=u.join("")}else e=",."+Bi(r[2])+"f";return ta.format(e)}function Bi(n){return-Math.floor(Math.log(n)/Math.LN10+.01)}function Wi(n,t){var e=Bi(t[2]);return n in yl?Math.abs(e-Bi(Math.max(ga(t[0]),ga(t[1]))))+ +("e"!==n):e-2*("%"===n)}function Ji(n,t,e,r){function u(n){return(e?Math.log(0>n?0:n):-Math.log(n>0?0:-n))/Math.log(t)}function i(n){return e?Math.pow(t,n):-Math.pow(t,-n)}function o(t){return n(u(t))}return o.invert=function(t){return i(n.invert(t))},o.domain=function(t){return arguments.length?(e=t[0]>=0,n.domain((r=t.map(Number)).map(u)),o):r},o.base=function(e){return arguments.length?(t=+e,n.domain(r.map(u)),o):t},o.nice=function(){var t=Fi(r.map(u),e?Math:xl);return n.domain(t),r=t.map(i),o},o.ticks=function(){var n=Pi(r),o=[],a=n[0],c=n[1],l=Math.floor(u(a)),s=Math.ceil(u(c)),f=t%1?2:t;if(isFinite(s-l)){if(e){for(;s>l;l++)for(var h=1;f>h;h++)o.push(i(l)*h);o.push(i(l))}else for(o.push(i(l));l++0;h--)o.push(i(l)*h);for(l=0;o[l]c;s--);o=o.slice(l,s)}return o},o.tickFormat=function(n,t){if(!arguments.length)return Ml;arguments.length<2?t=Ml:"function"!=typeof t&&(t=ta.format(t));var r,a=Math.max(.1,n/o.ticks().length),c=e?(r=1e-12,Math.ceil):(r=-1e-12,Math.floor);return function(n){return n/i(c(u(n)+r))<=a?t(n):""}},o.copy=function(){return Ji(n.copy(),t,e,r)},Yi(o,n)}function Gi(n,t,e){function r(t){return n(u(t))}var u=Ki(t),i=Ki(1/t);return r.invert=function(t){return i(n.invert(t))},r.domain=function(t){return arguments.length?(n.domain((e=t.map(Number)).map(u)),r):e},r.ticks=function(n){return Xi(e,n)},r.tickFormat=function(n,t){return $i(e,n,t)},r.nice=function(n){return r.domain(Zi(e,n))},r.exponent=function(o){return arguments.length?(u=Ki(t=o),i=Ki(1/t),n.domain(e.map(u)),r):t},r.copy=function(){return Gi(n.copy(),t,e)},Yi(r,n)}function Ki(n){return function(t){return 0>t?-Math.pow(-t,n):Math.pow(t,n)}}function Qi(n,t){function e(e){return i[((u.get(e)||("range"===t.t?u.set(e,n.push(e)):0/0))-1)%i.length]}function r(t,e){return ta.range(n.length).map(function(n){return t+e*n})}var u,i,o;return e.domain=function(r){if(!arguments.length)return n;n=[],u=new l;for(var i,o=-1,a=r.length;++oe?[0/0,0/0]:[e>0?a[e-1]:n[0],et?0/0:t/i+n,[t,t+1/i]},r.copy=function(){return to(n,t,e)},u()}function eo(n,t){function e(e){return e>=e?t[ta.bisect(n,e)]:void 0}return e.domain=function(t){return arguments.length?(n=t,e):n},e.range=function(n){return arguments.length?(t=n,e):t},e.invertExtent=function(e){return e=t.indexOf(e),[n[e-1],n[e]]},e.copy=function(){return eo(n,t)},e}function ro(n){function t(n){return+n}return t.invert=t,t.domain=t.range=function(e){return arguments.length?(n=e.map(t),t):n},t.ticks=function(t){return Xi(n,t)},t.tickFormat=function(t,e){return $i(n,t,e)},t.copy=function(){return ro(n)},t}function uo(){return 0}function io(n){return n.innerRadius}function oo(n){return n.outerRadius}function ao(n){return n.startAngle}function co(n){return n.endAngle}function lo(n){return n&&n.padAngle}function so(n,t,e,r){return(n-e)*t-(t-r)*n>0?0:1}function fo(n,t,e,r,u){var i=n[0]-t[0],o=n[1]-t[1],a=(u?r:-r)/Math.sqrt(i*i+o*o),c=a*o,l=-a*i,s=n[0]+c,f=n[1]+l,h=t[0]+c,g=t[1]+l,p=(s+h)/2,v=(f+g)/2,d=h-s,m=g-f,y=d*d+m*m,M=e-r,x=s*g-h*f,b=(0>m?-1:1)*Math.sqrt(M*M*y-x*x),_=(x*m-d*b)/y,w=(-x*d-m*b)/y,S=(x*m+d*b)/y,k=(-x*d+m*b)/y,E=_-p,A=w-v,N=S-p,C=k-v;return E*E+A*A>N*N+C*C&&(_=S,w=k),[[_-c,w-l],[_*e/M,w*e/M]]}function ho(n){function t(t){function o(){l.push("M",i(n(s),a))}for(var c,l=[],s=[],f=-1,h=t.length,g=Et(e),p=Et(r);++f1&&u.push("H",r[0]),u.join("")}function mo(n){for(var t=0,e=n.length,r=n[0],u=[r[0],",",r[1]];++t1){a=t[1],i=n[c],c++,r+="C"+(u[0]+o[0])+","+(u[1]+o[1])+","+(i[0]-a[0])+","+(i[1]-a[1])+","+i[0]+","+i[1];for(var l=2;l9&&(u=3*t/Math.sqrt(u),o[a]=u*e,o[a+1]=u*r));for(a=-1;++a<=c;)u=(n[Math.min(c,a+1)][0]-n[Math.max(0,a-1)][0])/(6*(1+o[a]*o[a])),i.push([u||0,o[a]*u||0]);return i}function To(n){return n.length<3?go(n):n[0]+_o(n,Lo(n))}function Ro(n){for(var t,e,r,u=-1,i=n.length;++ur)return s();var u=i[i.active];u&&(--i.count,delete i[i.active],u.event&&u.event.interrupt.call(n,n.__data__,u.index)),i.active=r,o.event&&o.event.start.call(n,n.__data__,t),o.tween.forEach(function(e,r){(r=r.call(n,n.__data__,t))&&v.push(r)}),h=o.ease,f=o.duration,ta.timer(function(){return p.c=l(e||1)?Ne:l,1},0,a)}function l(e){if(i.active!==r)return 1;for(var u=e/f,a=h(u),c=v.length;c>0;)v[--c].call(n,a);return u>=1?(o.event&&o.event.end.call(n,n.__data__,t),s()):void 0}function s(){return--i.count?delete i[r]:delete n[e],1}var f,h,g=o.delay,p=ec,v=[];return p.t=g+a,u>=g?c(u-g):void(p.c=c)},0,a)}}function Bo(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate("+(isFinite(r)?r:e(n))+",0)"})}function Wo(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate(0,"+(isFinite(r)?r:e(n))+")"})}function Jo(n){return n.toISOString()}function Go(n,t,e){function r(t){return n(t)}function u(n,e){var r=n[1]-n[0],u=r/e,i=ta.bisect(Vl,u);return i==Vl.length?[t.year,Vi(n.map(function(n){return n/31536e6}),e)[2]]:i?t[u/Vl[i-1]1?{floor:function(t){for(;e(t=n.floor(t));)t=Ko(t-1);return t},ceil:function(t){for(;e(t=n.ceil(t));)t=Ko(+t+1);return t}}:n))},r.ticks=function(n,t){var e=Pi(r.domain()),i=null==n?u(e,10):"number"==typeof n?u(e,n):!n.range&&[{range:n},t];return i&&(n=i[0],t=i[1]),n.range(e[0],Ko(+e[1]+1),1>t?1:t)},r.tickFormat=function(){return e},r.copy=function(){return Go(n.copy(),t,e)},Yi(r,n)}function Ko(n){return new Date(n)}function Qo(n){return JSON.parse(n.responseText)}function na(n){var t=ua.createRange();return t.selectNode(ua.body),t.createContextualFragment(n.responseText)}var ta={version:"3.5.6"},ea=[].slice,ra=function(n){return ea.call(n)},ua=this.document;if(ua)try{ra(ua.documentElement.childNodes)[0].nodeType}catch(ia){ra=function(n){for(var t=n.length,e=new Array(t);t--;)e[t]=n[t];return e}}if(Date.now||(Date.now=function(){return+new Date}),ua)try{ua.createElement("DIV").style.setProperty("opacity",0,"")}catch(oa){var aa=this.Element.prototype,ca=aa.setAttribute,la=aa.setAttributeNS,sa=this.CSSStyleDeclaration.prototype,fa=sa.setProperty;aa.setAttribute=function(n,t){ca.call(this,n,t+"")},aa.setAttributeNS=function(n,t,e){la.call(this,n,t,e+"")},sa.setProperty=function(n,t,e){fa.call(this,n,t+"",e)}}ta.ascending=e,ta.descending=function(n,t){return n>t?-1:t>n?1:t>=n?0:0/0},ta.min=function(n,t){var e,r,u=-1,i=n.length;if(1===arguments.length){for(;++u=r){e=r;break}for(;++ur&&(e=r)}else{for(;++u=r){e=r;break}for(;++ur&&(e=r)}return e},ta.max=function(n,t){var e,r,u=-1,i=n.length;if(1===arguments.length){for(;++u=r){e=r;break}for(;++ue&&(e=r)}else{for(;++u=r){e=r;break}for(;++ue&&(e=r)}return e},ta.extent=function(n,t){var e,r,u,i=-1,o=n.length;if(1===arguments.length){for(;++i=r){e=u=r;break}for(;++ir&&(e=r),r>u&&(u=r))}else{for(;++i=r){e=u=r;break}for(;++ir&&(e=r),r>u&&(u=r))}return[e,u]},ta.sum=function(n,t){var e,r=0,i=n.length,o=-1;if(1===arguments.length)for(;++o1?c/(s-1):void 0},ta.deviation=function(){var n=ta.variance.apply(this,arguments);return n?Math.sqrt(n):n};var ha=i(e);ta.bisectLeft=ha.left,ta.bisect=ta.bisectRight=ha.right,ta.bisector=function(n){return i(1===n.length?function(t,r){return e(n(t),r)}:n)},ta.shuffle=function(n,t,e){(i=arguments.length)<3&&(e=n.length,2>i&&(t=0));for(var r,u,i=e-t;i;)u=Math.random()*i--|0,r=n[i+t],n[i+t]=n[u+t],n[u+t]=r;return n},ta.permute=function(n,t){for(var e=t.length,r=new Array(e);e--;)r[e]=n[t[e]];return r},ta.pairs=function(n){for(var t,e=0,r=n.length-1,u=n[0],i=new Array(0>r?0:r);r>e;)i[e]=[t=u,u=n[++e]];return i},ta.zip=function(){if(!(r=arguments.length))return[];for(var n=-1,t=ta.min(arguments,o),e=new Array(t);++n=0;)for(r=n[u],t=r.length;--t>=0;)e[--o]=r[t];return e};var ga=Math.abs;ta.range=function(n,t,e){if(arguments.length<3&&(e=1,arguments.length<2&&(t=n,n=0)),(t-n)/e===1/0)throw new Error("infinite range");var r,u=[],i=a(ga(e)),o=-1;if(n*=i,t*=i,e*=i,0>e)for(;(r=n+e*++o)>t;)u.push(r/i);else for(;(r=n+e*++o)=i.length)return r?r.call(u,o):e?o.sort(e):o;for(var c,s,f,h,g=-1,p=o.length,v=i[a++],d=new l;++g=i.length)return n;var r=[],u=o[e++];return n.forEach(function(n,u){r.push({key:n,values:t(u,e)})}),u?r.sort(function(n,t){return u(n.key,t.key)}):r}var e,r,u={},i=[],o=[];return u.map=function(t,e){return n(e,t,0)},u.entries=function(e){return t(n(ta.map,e,0),0)},u.key=function(n){return i.push(n),u},u.sortKeys=function(n){return o[i.length-1]=n,u},u.sortValues=function(n){return e=n,u},u.rollup=function(n){return r=n,u},u},ta.set=function(n){var t=new m;if(n)for(var e=0,r=n.length;r>e;++e)t.add(n[e]);return t},c(m,{has:h,add:function(n){return this._[s(n+="")]=!0,n},remove:g,values:p,size:v,empty:d,forEach:function(n){for(var t in this._)n.call(this,f(t))}}),ta.behavior={},ta.rebind=function(n,t){for(var e,r=1,u=arguments.length;++r=0&&(r=n.slice(e+1),n=n.slice(0,e)),n)return arguments.length<2?this[n].on(r):this[n].on(r,t);if(2===arguments.length){if(null==t)for(n in this)this.hasOwnProperty(n)&&this[n].on(r,null);return this}},ta.event=null,ta.requote=function(n){return n.replace(ma,"\\$&")};var ma=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g,ya={}.__proto__?function(n,t){n.__proto__=t}:function(n,t){for(var e in t)n[e]=t[e]},Ma=function(n,t){return t.querySelector(n)},xa=function(n,t){return t.querySelectorAll(n)},ba=function(n,t){var e=n.matches||n[x(n,"matchesSelector")];return(ba=function(n,t){return e.call(n,t)})(n,t)};"function"==typeof Sizzle&&(Ma=function(n,t){return Sizzle(n,t)[0]||null},xa=Sizzle,ba=Sizzle.matchesSelector),ta.selection=function(){return ta.select(ua.documentElement)};var _a=ta.selection.prototype=[];_a.select=function(n){var t,e,r,u,i=[];n=N(n);for(var o=-1,a=this.length;++o=0&&(e=n.slice(0,t),n=n.slice(t+1)),wa.hasOwnProperty(e)?{space:wa[e],local:n}:n}},_a.attr=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node();return n=ta.ns.qualify(n),n.local?e.getAttributeNS(n.space,n.local):e.getAttribute(n)}for(t in n)this.each(z(t,n[t]));return this}return this.each(z(n,t))},_a.classed=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node(),r=(n=T(n)).length,u=-1;if(t=e.classList){for(;++uu){if("string"!=typeof n){2>u&&(e="");for(r in n)this.each(P(r,n[r],e));return this}if(2>u){var i=this.node();return t(i).getComputedStyle(i,null).getPropertyValue(n)}r=""}return this.each(P(n,e,r))},_a.property=function(n,t){if(arguments.length<2){if("string"==typeof n)return this.node()[n];for(t in n)this.each(U(t,n[t]));return this}return this.each(U(n,t))},_a.text=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.textContent=null==t?"":t}:null==n?function(){this.textContent=""}:function(){this.textContent=n}):this.node().textContent},_a.html=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.innerHTML=null==t?"":t}:null==n?function(){this.innerHTML=""}:function(){this.innerHTML=n}):this.node().innerHTML},_a.append=function(n){return n=j(n),this.select(function(){return this.appendChild(n.apply(this,arguments))})},_a.insert=function(n,t){return n=j(n),t=N(t),this.select(function(){return this.insertBefore(n.apply(this,arguments),t.apply(this,arguments)||null)})},_a.remove=function(){return this.each(F)},_a.data=function(n,t){function e(n,e){var r,u,i,o=n.length,f=e.length,h=Math.min(o,f),g=new Array(f),p=new Array(f),v=new Array(o);if(t){var d,m=new l,y=new Array(o);for(r=-1;++rr;++r)p[r]=H(e[r]);for(;o>r;++r)v[r]=n[r]}p.update=g,p.parentNode=g.parentNode=v.parentNode=n.parentNode,a.push(p),c.push(g),s.push(v)}var r,u,i=-1,o=this.length;if(!arguments.length){for(n=new Array(o=(r=this[0]).length);++ii;i++){u.push(t=[]),t.parentNode=(e=this[i]).parentNode;for(var a=0,c=e.length;c>a;a++)(r=e[a])&&n.call(r,r.__data__,a,i)&&t.push(r)}return A(u)},_a.order=function(){for(var n=-1,t=this.length;++n=0;)(e=r[u])&&(i&&i!==e.nextSibling&&i.parentNode.insertBefore(e,i),i=e);return this},_a.sort=function(n){n=I.apply(this,arguments);for(var t=-1,e=this.length;++tn;n++)for(var e=this[n],r=0,u=e.length;u>r;r++){var i=e[r];if(i)return i}return null},_a.size=function(){var n=0;return Y(this,function(){++n}),n};var Sa=[];ta.selection.enter=Z,ta.selection.enter.prototype=Sa,Sa.append=_a.append,Sa.empty=_a.empty,Sa.node=_a.node,Sa.call=_a.call,Sa.size=_a.size,Sa.select=function(n){for(var t,e,r,u,i,o=[],a=-1,c=this.length;++ar){if("string"!=typeof n){2>r&&(t=!1);for(e in n)this.each(X(e,n[e],t));return this}if(2>r)return(r=this.node()["__on"+n])&&r._;e=!1}return this.each(X(n,t,e))};var ka=ta.map({mouseenter:"mouseover",mouseleave:"mouseout"});ua&&ka.forEach(function(n){"on"+n in ua&&ka.remove(n)});var Ea,Aa=0;ta.mouse=function(n){return J(n,k())};var Na=this.navigator&&/WebKit/.test(this.navigator.userAgent)?-1:0;ta.touch=function(n,t,e){if(arguments.length<3&&(e=t,t=k().changedTouches),t)for(var r,u=0,i=t.length;i>u;++u)if((r=t[u]).identifier===e)return J(n,r)},ta.behavior.drag=function(){function n(){this.on("mousedown.drag",i).on("touchstart.drag",o)}function e(n,t,e,i,o){return function(){function a(){var n,e,r=t(h,v);r&&(n=r[0]-M[0],e=r[1]-M[1],p|=n|e,M=r,g({type:"drag",x:r[0]+l[0],y:r[1]+l[1],dx:n,dy:e}))}function c(){t(h,v)&&(m.on(i+d,null).on(o+d,null),y(p&&ta.event.target===f),g({type:"dragend"}))}var l,s=this,f=ta.event.target,h=s.parentNode,g=r.of(s,arguments),p=0,v=n(),d=".drag"+(null==v?"":"-"+v),m=ta.select(e(f)).on(i+d,a).on(o+d,c),y=W(f),M=t(h,v);u?(l=u.apply(s,arguments),l=[l.x-M[0],l.y-M[1]]):l=[0,0],g({type:"dragstart"})}}var r=E(n,"drag","dragstart","dragend"),u=null,i=e(b,ta.mouse,t,"mousemove","mouseup"),o=e(G,ta.touch,y,"touchmove","touchend");return n.origin=function(t){return arguments.length?(u=t,n):u},ta.rebind(n,r,"on")},ta.touches=function(n,t){return arguments.length<2&&(t=k().touches),t?ra(t).map(function(t){var e=J(n,t);return e.identifier=t.identifier,e}):[]};var Ca=1e-6,za=Ca*Ca,qa=Math.PI,La=2*qa,Ta=La-Ca,Ra=qa/2,Da=qa/180,Pa=180/qa,Ua=Math.SQRT2,ja=2,Fa=4;ta.interpolateZoom=function(n,t){function e(n){var t=n*y;if(m){var e=rt(v),o=i/(ja*h)*(e*ut(Ua*t+v)-et(v));return[r+o*l,u+o*s,i*e/rt(Ua*t+v)]}return[r+n*l,u+n*s,i*Math.exp(Ua*t)]}var r=n[0],u=n[1],i=n[2],o=t[0],a=t[1],c=t[2],l=o-r,s=a-u,f=l*l+s*s,h=Math.sqrt(f),g=(c*c-i*i+Fa*f)/(2*i*ja*h),p=(c*c-i*i-Fa*f)/(2*c*ja*h),v=Math.log(Math.sqrt(g*g+1)-g),d=Math.log(Math.sqrt(p*p+1)-p),m=d-v,y=(m||Math.log(c/i))/Ua;return e.duration=1e3*y,e},ta.behavior.zoom=function(){function n(n){n.on(q,f).on(Oa+".zoom",g).on("dblclick.zoom",p).on(R,h)}function e(n){return[(n[0]-k.x)/k.k,(n[1]-k.y)/k.k]}function r(n){return[n[0]*k.k+k.x,n[1]*k.k+k.y]}function u(n){k.k=Math.max(N[0],Math.min(N[1],n))}function i(n,t){t=r(t),k.x+=n[0]-t[0],k.y+=n[1]-t[1]}function o(t,e,r,o){t.__chart__={x:k.x,y:k.y,k:k.k},u(Math.pow(2,o)),i(d=e,r),t=ta.select(t),C>0&&(t=t.transition().duration(C)),t.call(n.event)}function a(){b&&b.domain(x.range().map(function(n){return(n-k.x)/k.k}).map(x.invert)),w&&w.domain(_.range().map(function(n){return(n-k.y)/k.k}).map(_.invert))}function c(n){z++||n({type:"zoomstart"})}function l(n){a(),n({type:"zoom",scale:k.k,translate:[k.x,k.y]})}function s(n){--z||(n({type:"zoomend"}),d=null)}function f(){function n(){f=1,i(ta.mouse(u),g),l(a)}function r(){h.on(L,null).on(T,null),p(f&&ta.event.target===o),s(a)}var u=this,o=ta.event.target,a=D.of(u,arguments),f=0,h=ta.select(t(u)).on(L,n).on(T,r),g=e(ta.mouse(u)),p=W(u);Dl.call(u),c(a)}function h(){function n(){var n=ta.touches(p);return g=k.k,n.forEach(function(n){n.identifier in d&&(d[n.identifier]=e(n))}),n}function t(){var t=ta.event.target;ta.select(t).on(x,r).on(b,a),_.push(t);for(var e=ta.event.changedTouches,u=0,i=e.length;i>u;++u)d[e[u].identifier]=null;var c=n(),l=Date.now();if(1===c.length){if(500>l-M){var s=c[0];o(p,s,d[s.identifier],Math.floor(Math.log(k.k)/Math.LN2)+1),S()}M=l}else if(c.length>1){var s=c[0],f=c[1],h=s[0]-f[0],g=s[1]-f[1];m=h*h+g*g}}function r(){var n,t,e,r,o=ta.touches(p);Dl.call(p);for(var a=0,c=o.length;c>a;++a,r=null)if(e=o[a],r=d[e.identifier]){if(t)break;n=e,t=r}if(r){var s=(s=e[0]-n[0])*s+(s=e[1]-n[1])*s,f=m&&Math.sqrt(s/m);n=[(n[0]+e[0])/2,(n[1]+e[1])/2],t=[(t[0]+r[0])/2,(t[1]+r[1])/2],u(f*g)}M=null,i(n,t),l(v)}function a(){if(ta.event.touches.length){for(var t=ta.event.changedTouches,e=0,r=t.length;r>e;++e)delete d[t[e].identifier];for(var u in d)return void n()}ta.selectAll(_).on(y,null),w.on(q,f).on(R,h),E(),s(v)}var g,p=this,v=D.of(p,arguments),d={},m=0,y=".zoom-"+ta.event.changedTouches[0].identifier,x="touchmove"+y,b="touchend"+y,_=[],w=ta.select(p),E=W(p);t(),c(v),w.on(q,null).on(R,t)}function g(){var n=D.of(this,arguments);y?clearTimeout(y):(Dl.call(this),v=e(d=m||ta.mouse(this)),c(n)),y=setTimeout(function(){y=null,s(n)},50),S(),u(Math.pow(2,.002*Ha())*k.k),i(d,v),l(n)}function p(){var n=ta.mouse(this),t=Math.log(k.k)/Math.LN2;o(this,n,e(n),ta.event.shiftKey?Math.ceil(t)-1:Math.floor(t)+1)}var v,d,m,y,M,x,b,_,w,k={x:0,y:0,k:1},A=[960,500],N=Ia,C=250,z=0,q="mousedown.zoom",L="mousemove.zoom",T="mouseup.zoom",R="touchstart.zoom",D=E(n,"zoomstart","zoom","zoomend");return Oa||(Oa="onwheel"in ua?(Ha=function(){return-ta.event.deltaY*(ta.event.deltaMode?120:1)},"wheel"):"onmousewheel"in ua?(Ha=function(){return ta.event.wheelDelta},"mousewheel"):(Ha=function(){return-ta.event.detail},"MozMousePixelScroll")),n.event=function(n){n.each(function(){var n=D.of(this,arguments),t=k;Tl?ta.select(this).transition().each("start.zoom",function(){k=this.__chart__||{x:0,y:0,k:1},c(n)}).tween("zoom:zoom",function(){var e=A[0],r=A[1],u=d?d[0]:e/2,i=d?d[1]:r/2,o=ta.interpolateZoom([(u-k.x)/k.k,(i-k.y)/k.k,e/k.k],[(u-t.x)/t.k,(i-t.y)/t.k,e/t.k]);return function(t){var r=o(t),a=e/r[2];this.__chart__=k={x:u-r[0]*a,y:i-r[1]*a,k:a},l(n)}}).each("interrupt.zoom",function(){s(n)}).each("end.zoom",function(){s(n)}):(this.__chart__=k,c(n),l(n),s(n))})},n.translate=function(t){return arguments.length?(k={x:+t[0],y:+t[1],k:k.k},a(),n):[k.x,k.y]},n.scale=function(t){return arguments.length?(k={x:k.x,y:k.y,k:+t},a(),n):k.k},n.scaleExtent=function(t){return arguments.length?(N=null==t?Ia:[+t[0],+t[1]],n):N},n.center=function(t){return arguments.length?(m=t&&[+t[0],+t[1]],n):m},n.size=function(t){return arguments.length?(A=t&&[+t[0],+t[1]],n):A},n.duration=function(t){return arguments.length?(C=+t,n):C},n.x=function(t){return arguments.length?(b=t,x=t.copy(),k={x:0,y:0,k:1},n):b},n.y=function(t){return arguments.length?(w=t,_=t.copy(),k={x:0,y:0,k:1},n):w},ta.rebind(n,D,"on")};var Ha,Oa,Ia=[0,1/0];ta.color=ot,ot.prototype.toString=function(){return this.rgb()+""},ta.hsl=at;var Ya=at.prototype=new ot;Ya.brighter=function(n){return n=Math.pow(.7,arguments.length?n:1),new at(this.h,this.s,this.l/n)},Ya.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new at(this.h,this.s,n*this.l)},Ya.rgb=function(){return ct(this.h,this.s,this.l)},ta.hcl=lt;var Za=lt.prototype=new ot;Za.brighter=function(n){return new lt(this.h,this.c,Math.min(100,this.l+Va*(arguments.length?n:1)))},Za.darker=function(n){return new lt(this.h,this.c,Math.max(0,this.l-Va*(arguments.length?n:1)))},Za.rgb=function(){return st(this.h,this.c,this.l).rgb()},ta.lab=ft;var Va=18,Xa=.95047,$a=1,Ba=1.08883,Wa=ft.prototype=new ot;Wa.brighter=function(n){return new ft(Math.min(100,this.l+Va*(arguments.length?n:1)),this.a,this.b)},Wa.darker=function(n){return new ft(Math.max(0,this.l-Va*(arguments.length?n:1)),this.a,this.b)},Wa.rgb=function(){return ht(this.l,this.a,this.b)},ta.rgb=mt;var Ja=mt.prototype=new ot;Ja.brighter=function(n){n=Math.pow(.7,arguments.length?n:1);var t=this.r,e=this.g,r=this.b,u=30;return t||e||r?(t&&u>t&&(t=u),e&&u>e&&(e=u),r&&u>r&&(r=u),new mt(Math.min(255,t/n),Math.min(255,e/n),Math.min(255,r/n))):new mt(u,u,u)},Ja.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new mt(n*this.r,n*this.g,n*this.b)},Ja.hsl=function(){return _t(this.r,this.g,this.b)},Ja.toString=function(){return"#"+xt(this.r)+xt(this.g)+xt(this.b)};var Ga=ta.map({aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074});Ga.forEach(function(n,t){Ga.set(n,yt(t))}),ta.functor=Et,ta.xhr=At(y),ta.dsv=function(n,t){function e(n,e,i){arguments.length<3&&(i=e,e=null);var o=Nt(n,t,null==e?r:u(e),i);return o.row=function(n){return arguments.length?o.response(null==(e=n)?r:u(n)):e},o}function r(n){return e.parse(n.responseText)}function u(n){return function(t){return e.parse(t.responseText,n)}}function i(t){return t.map(o).join(n)}function o(n){return a.test(n)?'"'+n.replace(/\"/g,'""')+'"':n}var a=new RegExp('["'+n+"\n]"),c=n.charCodeAt(0);return e.parse=function(n,t){var r;return e.parseRows(n,function(n,e){if(r)return r(n,e-1);var u=new Function("d","return {"+n.map(function(n,t){return JSON.stringify(n)+": d["+t+"]"}).join(",")+"}");r=t?function(n,e){return t(u(n),e)}:u})},e.parseRows=function(n,t){function e(){if(s>=l)return o;if(u)return u=!1,i;var t=s;if(34===n.charCodeAt(t)){for(var e=t;e++s;){var r=n.charCodeAt(s++),a=1;if(10===r)u=!0;else if(13===r)u=!0,10===n.charCodeAt(s)&&(++s,++a);else if(r!==c)continue;return n.slice(t,s-a)}return n.slice(t)}for(var r,u,i={},o={},a=[],l=n.length,s=0,f=0;(r=e())!==o;){for(var h=[];r!==i&&r!==o;)h.push(r),r=e();t&&null==(h=t(h,f++))||a.push(h)}return a},e.format=function(t){if(Array.isArray(t[0]))return e.formatRows(t);var r=new m,u=[];return t.forEach(function(n){for(var t in n)r.has(t)||u.push(r.add(t))}),[u.map(o).join(n)].concat(t.map(function(t){return u.map(function(n){return o(t[n])}).join(n)})).join("\n")},e.formatRows=function(n){return n.map(i).join("\n")},e},ta.csv=ta.dsv(",","text/csv"),ta.tsv=ta.dsv(" ","text/tab-separated-values");var Ka,Qa,nc,tc,ec,rc=this[x(this,"requestAnimationFrame")]||function(n){setTimeout(n,17)};ta.timer=function(n,t,e){var r=arguments.length;2>r&&(t=0),3>r&&(e=Date.now());var u=e+t,i={c:n,t:u,f:!1,n:null};Qa?Qa.n=i:Ka=i,Qa=i,nc||(tc=clearTimeout(tc),nc=1,rc(qt))},ta.timer.flush=function(){Lt(),Tt()},ta.round=function(n,t){return t?Math.round(n*(t=Math.pow(10,t)))/t:Math.round(n)};var uc=["y","z","a","f","p","n","\xb5","m","","k","M","G","T","P","E","Z","Y"].map(Dt);ta.formatPrefix=function(n,t){var e=0;return n&&(0>n&&(n*=-1),t&&(n=ta.round(n,Rt(n,t))),e=1+Math.floor(1e-12+Math.log(n)/Math.LN10),e=Math.max(-24,Math.min(24,3*Math.floor((e-1)/3)))),uc[8+e/3]};var ic=/(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i,oc=ta.map({b:function(n){return n.toString(2)},c:function(n){return String.fromCharCode(n)},o:function(n){return n.toString(8)},x:function(n){return n.toString(16)},X:function(n){return n.toString(16).toUpperCase()},g:function(n,t){return n.toPrecision(t)},e:function(n,t){return n.toExponential(t)},f:function(n,t){return n.toFixed(t)},r:function(n,t){return(n=ta.round(n,Rt(n,t))).toFixed(Math.max(0,Math.min(20,Rt(n*(1+1e-15),t))))}}),ac=ta.time={},cc=Date;jt.prototype={getDate:function(){return this._.getUTCDate()},getDay:function(){return this._.getUTCDay()},getFullYear:function(){return this._.getUTCFullYear()},getHours:function(){return this._.getUTCHours()},getMilliseconds:function(){return this._.getUTCMilliseconds()},getMinutes:function(){return this._.getUTCMinutes()},getMonth:function(){return this._.getUTCMonth()},getSeconds:function(){return this._.getUTCSeconds()},getTime:function(){return this._.getTime()},getTimezoneOffset:function(){return 0},valueOf:function(){return this._.valueOf()},setDate:function(){lc.setUTCDate.apply(this._,arguments)},setDay:function(){lc.setUTCDay.apply(this._,arguments)},setFullYear:function(){lc.setUTCFullYear.apply(this._,arguments)},setHours:function(){lc.setUTCHours.apply(this._,arguments)},setMilliseconds:function(){lc.setUTCMilliseconds.apply(this._,arguments)},setMinutes:function(){lc.setUTCMinutes.apply(this._,arguments)},setMonth:function(){lc.setUTCMonth.apply(this._,arguments)},setSeconds:function(){lc.setUTCSeconds.apply(this._,arguments)},setTime:function(){lc.setTime.apply(this._,arguments)}};var lc=Date.prototype;ac.year=Ft(function(n){return n=ac.day(n),n.setMonth(0,1),n},function(n,t){n.setFullYear(n.getFullYear()+t)},function(n){return n.getFullYear()}),ac.years=ac.year.range,ac.years.utc=ac.year.utc.range,ac.day=Ft(function(n){var t=new cc(2e3,0);return t.setFullYear(n.getFullYear(),n.getMonth(),n.getDate()),t},function(n,t){n.setDate(n.getDate()+t)},function(n){return n.getDate()-1}),ac.days=ac.day.range,ac.days.utc=ac.day.utc.range,ac.dayOfYear=function(n){var t=ac.year(n);return Math.floor((n-t-6e4*(n.getTimezoneOffset()-t.getTimezoneOffset()))/864e5)},["sunday","monday","tuesday","wednesday","thursday","friday","saturday"].forEach(function(n,t){t=7-t;var e=ac[n]=Ft(function(n){return(n=ac.day(n)).setDate(n.getDate()-(n.getDay()+t)%7),n},function(n,t){n.setDate(n.getDate()+7*Math.floor(t))},function(n){var e=ac.year(n).getDay();return Math.floor((ac.dayOfYear(n)+(e+t)%7)/7)-(e!==t)});ac[n+"s"]=e.range,ac[n+"s"].utc=e.utc.range,ac[n+"OfYear"]=function(n){var e=ac.year(n).getDay();return Math.floor((ac.dayOfYear(n)+(e+t)%7)/7)}}),ac.week=ac.sunday,ac.weeks=ac.sunday.range,ac.weeks.utc=ac.sunday.utc.range,ac.weekOfYear=ac.sundayOfYear;var sc={"-":"",_:" ",0:"0"},fc=/^\s*\d+/,hc=/^%/;ta.locale=function(n){return{numberFormat:Pt(n),timeFormat:Ot(n)}};var gc=ta.locale({decimal:".",thousands:",",grouping:[3],currency:["$",""],dateTime:"%a %b %e %X %Y",date:"%m/%d/%Y",time:"%H:%M:%S",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});ta.format=gc.numberFormat,ta.geo={},ce.prototype={s:0,t:0,add:function(n){le(n,this.t,pc),le(pc.s,this.s,this),this.s?this.t+=pc.t:this.s=pc.t +},reset:function(){this.s=this.t=0},valueOf:function(){return this.s}};var pc=new ce;ta.geo.stream=function(n,t){n&&vc.hasOwnProperty(n.type)?vc[n.type](n,t):se(n,t)};var vc={Feature:function(n,t){se(n.geometry,t)},FeatureCollection:function(n,t){for(var e=n.features,r=-1,u=e.length;++rn?4*qa+n:n,Mc.lineStart=Mc.lineEnd=Mc.point=b}};ta.geo.bounds=function(){function n(n,t){M.push(x=[s=n,h=n]),f>t&&(f=t),t>g&&(g=t)}function t(t,e){var r=pe([t*Da,e*Da]);if(m){var u=de(m,r),i=[u[1],-u[0],0],o=de(i,u);Me(o),o=xe(o);var c=t-p,l=c>0?1:-1,v=o[0]*Pa*l,d=ga(c)>180;if(d^(v>l*p&&l*t>v)){var y=o[1]*Pa;y>g&&(g=y)}else if(v=(v+360)%360-180,d^(v>l*p&&l*t>v)){var y=-o[1]*Pa;f>y&&(f=y)}else f>e&&(f=e),e>g&&(g=e);d?p>t?a(s,t)>a(s,h)&&(h=t):a(t,h)>a(s,h)&&(s=t):h>=s?(s>t&&(s=t),t>h&&(h=t)):t>p?a(s,t)>a(s,h)&&(h=t):a(t,h)>a(s,h)&&(s=t)}else n(t,e);m=r,p=t}function e(){b.point=t}function r(){x[0]=s,x[1]=h,b.point=n,m=null}function u(n,e){if(m){var r=n-p;y+=ga(r)>180?r+(r>0?360:-360):r}else v=n,d=e;Mc.point(n,e),t(n,e)}function i(){Mc.lineStart()}function o(){u(v,d),Mc.lineEnd(),ga(y)>Ca&&(s=-(h=180)),x[0]=s,x[1]=h,m=null}function a(n,t){return(t-=n)<0?t+360:t}function c(n,t){return n[0]-t[0]}function l(n,t){return t[0]<=t[1]?t[0]<=n&&n<=t[1]:nyc?(s=-(h=180),f=-(g=90)):y>Ca?g=90:-Ca>y&&(f=-90),x[0]=s,x[1]=h}};return function(n){g=h=-(s=f=1/0),M=[],ta.geo.stream(n,b);var t=M.length;if(t){M.sort(c);for(var e,r=1,u=M[0],i=[u];t>r;++r)e=M[r],l(e[0],u)||l(e[1],u)?(a(u[0],e[1])>a(u[0],u[1])&&(u[1]=e[1]),a(e[0],u[1])>a(u[0],u[1])&&(u[0]=e[0])):i.push(u=e);for(var o,e,p=-1/0,t=i.length-1,r=0,u=i[t];t>=r;u=e,++r)e=i[r],(o=a(u[1],e[0]))>p&&(p=o,s=e[0],h=u[1])}return M=x=null,1/0===s||1/0===f?[[0/0,0/0],[0/0,0/0]]:[[s,f],[h,g]]}}(),ta.geo.centroid=function(n){xc=bc=_c=wc=Sc=kc=Ec=Ac=Nc=Cc=zc=0,ta.geo.stream(n,qc);var t=Nc,e=Cc,r=zc,u=t*t+e*e+r*r;return za>u&&(t=kc,e=Ec,r=Ac,Ca>bc&&(t=_c,e=wc,r=Sc),u=t*t+e*e+r*r,za>u)?[0/0,0/0]:[Math.atan2(e,t)*Pa,tt(r/Math.sqrt(u))*Pa]};var xc,bc,_c,wc,Sc,kc,Ec,Ac,Nc,Cc,zc,qc={sphere:b,point:_e,lineStart:Se,lineEnd:ke,polygonStart:function(){qc.lineStart=Ee},polygonEnd:function(){qc.lineStart=Se}},Lc=Le(Ne,Pe,je,[-qa,-qa/2]),Tc=1e9;ta.geo.clipExtent=function(){var n,t,e,r,u,i,o={stream:function(n){return u&&(u.valid=!1),u=i(n),u.valid=!0,u},extent:function(a){return arguments.length?(i=Ie(n=+a[0][0],t=+a[0][1],e=+a[1][0],r=+a[1][1]),u&&(u.valid=!1,u=null),o):[[n,t],[e,r]]}};return o.extent([[0,0],[960,500]])},(ta.geo.conicEqualArea=function(){return Ye(Ze)}).raw=Ze,ta.geo.albers=function(){return ta.geo.conicEqualArea().rotate([96,0]).center([-.6,38.7]).parallels([29.5,45.5]).scale(1070)},ta.geo.albersUsa=function(){function n(n){var i=n[0],o=n[1];return t=null,e(i,o),t||(r(i,o),t)||u(i,o),t}var t,e,r,u,i=ta.geo.albers(),o=ta.geo.conicEqualArea().rotate([154,0]).center([-2,58.5]).parallels([55,65]),a=ta.geo.conicEqualArea().rotate([157,0]).center([-3,19.9]).parallels([8,18]),c={point:function(n,e){t=[n,e]}};return n.invert=function(n){var t=i.scale(),e=i.translate(),r=(n[0]-e[0])/t,u=(n[1]-e[1])/t;return(u>=.12&&.234>u&&r>=-.425&&-.214>r?o:u>=.166&&.234>u&&r>=-.214&&-.115>r?a:i).invert(n)},n.stream=function(n){var t=i.stream(n),e=o.stream(n),r=a.stream(n);return{point:function(n,u){t.point(n,u),e.point(n,u),r.point(n,u)},sphere:function(){t.sphere(),e.sphere(),r.sphere()},lineStart:function(){t.lineStart(),e.lineStart(),r.lineStart()},lineEnd:function(){t.lineEnd(),e.lineEnd(),r.lineEnd()},polygonStart:function(){t.polygonStart(),e.polygonStart(),r.polygonStart()},polygonEnd:function(){t.polygonEnd(),e.polygonEnd(),r.polygonEnd()}}},n.precision=function(t){return arguments.length?(i.precision(t),o.precision(t),a.precision(t),n):i.precision()},n.scale=function(t){return arguments.length?(i.scale(t),o.scale(.35*t),a.scale(t),n.translate(i.translate())):i.scale()},n.translate=function(t){if(!arguments.length)return i.translate();var l=i.scale(),s=+t[0],f=+t[1];return e=i.translate(t).clipExtent([[s-.455*l,f-.238*l],[s+.455*l,f+.238*l]]).stream(c).point,r=o.translate([s-.307*l,f+.201*l]).clipExtent([[s-.425*l+Ca,f+.12*l+Ca],[s-.214*l-Ca,f+.234*l-Ca]]).stream(c).point,u=a.translate([s-.205*l,f+.212*l]).clipExtent([[s-.214*l+Ca,f+.166*l+Ca],[s-.115*l-Ca,f+.234*l-Ca]]).stream(c).point,n},n.scale(1070)};var Rc,Dc,Pc,Uc,jc,Fc,Hc={point:b,lineStart:b,lineEnd:b,polygonStart:function(){Dc=0,Hc.lineStart=Ve},polygonEnd:function(){Hc.lineStart=Hc.lineEnd=Hc.point=b,Rc+=ga(Dc/2)}},Oc={point:Xe,lineStart:b,lineEnd:b,polygonStart:b,polygonEnd:b},Ic={point:We,lineStart:Je,lineEnd:Ge,polygonStart:function(){Ic.lineStart=Ke},polygonEnd:function(){Ic.point=We,Ic.lineStart=Je,Ic.lineEnd=Ge}};ta.geo.path=function(){function n(n){return n&&("function"==typeof a&&i.pointRadius(+a.apply(this,arguments)),o&&o.valid||(o=u(i)),ta.geo.stream(n,o)),i.result()}function t(){return o=null,n}var e,r,u,i,o,a=4.5;return n.area=function(n){return Rc=0,ta.geo.stream(n,u(Hc)),Rc},n.centroid=function(n){return _c=wc=Sc=kc=Ec=Ac=Nc=Cc=zc=0,ta.geo.stream(n,u(Ic)),zc?[Nc/zc,Cc/zc]:Ac?[kc/Ac,Ec/Ac]:Sc?[_c/Sc,wc/Sc]:[0/0,0/0]},n.bounds=function(n){return jc=Fc=-(Pc=Uc=1/0),ta.geo.stream(n,u(Oc)),[[Pc,Uc],[jc,Fc]]},n.projection=function(n){return arguments.length?(u=(e=n)?n.stream||tr(n):y,t()):e},n.context=function(n){return arguments.length?(i=null==(r=n)?new $e:new Qe(n),"function"!=typeof a&&i.pointRadius(a),t()):r},n.pointRadius=function(t){return arguments.length?(a="function"==typeof t?t:(i.pointRadius(+t),+t),n):a},n.projection(ta.geo.albersUsa()).context(null)},ta.geo.transform=function(n){return{stream:function(t){var e=new er(t);for(var r in n)e[r]=n[r];return e}}},er.prototype={point:function(n,t){this.stream.point(n,t)},sphere:function(){this.stream.sphere()},lineStart:function(){this.stream.lineStart()},lineEnd:function(){this.stream.lineEnd()},polygonStart:function(){this.stream.polygonStart()},polygonEnd:function(){this.stream.polygonEnd()}},ta.geo.projection=ur,ta.geo.projectionMutator=ir,(ta.geo.equirectangular=function(){return ur(ar)}).raw=ar.invert=ar,ta.geo.rotation=function(n){function t(t){return t=n(t[0]*Da,t[1]*Da),t[0]*=Pa,t[1]*=Pa,t}return n=lr(n[0]%360*Da,n[1]*Da,n.length>2?n[2]*Da:0),t.invert=function(t){return t=n.invert(t[0]*Da,t[1]*Da),t[0]*=Pa,t[1]*=Pa,t},t},cr.invert=ar,ta.geo.circle=function(){function n(){var n="function"==typeof r?r.apply(this,arguments):r,t=lr(-n[0]*Da,-n[1]*Da,0).invert,u=[];return e(null,null,1,{point:function(n,e){u.push(n=t(n,e)),n[0]*=Pa,n[1]*=Pa}}),{type:"Polygon",coordinates:[u]}}var t,e,r=[0,0],u=6;return n.origin=function(t){return arguments.length?(r=t,n):r},n.angle=function(r){return arguments.length?(e=gr((t=+r)*Da,u*Da),n):t},n.precision=function(r){return arguments.length?(e=gr(t*Da,(u=+r)*Da),n):u},n.angle(90)},ta.geo.distance=function(n,t){var e,r=(t[0]-n[0])*Da,u=n[1]*Da,i=t[1]*Da,o=Math.sin(r),a=Math.cos(r),c=Math.sin(u),l=Math.cos(u),s=Math.sin(i),f=Math.cos(i);return Math.atan2(Math.sqrt((e=f*o)*e+(e=l*s-c*f*a)*e),c*s+l*f*a)},ta.geo.graticule=function(){function n(){return{type:"MultiLineString",coordinates:t()}}function t(){return ta.range(Math.ceil(i/d)*d,u,d).map(h).concat(ta.range(Math.ceil(l/m)*m,c,m).map(g)).concat(ta.range(Math.ceil(r/p)*p,e,p).filter(function(n){return ga(n%d)>Ca}).map(s)).concat(ta.range(Math.ceil(a/v)*v,o,v).filter(function(n){return ga(n%m)>Ca}).map(f))}var e,r,u,i,o,a,c,l,s,f,h,g,p=10,v=p,d=90,m=360,y=2.5;return n.lines=function(){return t().map(function(n){return{type:"LineString",coordinates:n}})},n.outline=function(){return{type:"Polygon",coordinates:[h(i).concat(g(c).slice(1),h(u).reverse().slice(1),g(l).reverse().slice(1))]}},n.extent=function(t){return arguments.length?n.majorExtent(t).minorExtent(t):n.minorExtent()},n.majorExtent=function(t){return arguments.length?(i=+t[0][0],u=+t[1][0],l=+t[0][1],c=+t[1][1],i>u&&(t=i,i=u,u=t),l>c&&(t=l,l=c,c=t),n.precision(y)):[[i,l],[u,c]]},n.minorExtent=function(t){return arguments.length?(r=+t[0][0],e=+t[1][0],a=+t[0][1],o=+t[1][1],r>e&&(t=r,r=e,e=t),a>o&&(t=a,a=o,o=t),n.precision(y)):[[r,a],[e,o]]},n.step=function(t){return arguments.length?n.majorStep(t).minorStep(t):n.minorStep()},n.majorStep=function(t){return arguments.length?(d=+t[0],m=+t[1],n):[d,m]},n.minorStep=function(t){return arguments.length?(p=+t[0],v=+t[1],n):[p,v]},n.precision=function(t){return arguments.length?(y=+t,s=vr(a,o,90),f=dr(r,e,y),h=vr(l,c,90),g=dr(i,u,y),n):y},n.majorExtent([[-180,-90+Ca],[180,90-Ca]]).minorExtent([[-180,-80-Ca],[180,80+Ca]])},ta.geo.greatArc=function(){function n(){return{type:"LineString",coordinates:[t||r.apply(this,arguments),e||u.apply(this,arguments)]}}var t,e,r=mr,u=yr;return n.distance=function(){return ta.geo.distance(t||r.apply(this,arguments),e||u.apply(this,arguments))},n.source=function(e){return arguments.length?(r=e,t="function"==typeof e?null:e,n):r},n.target=function(t){return arguments.length?(u=t,e="function"==typeof t?null:t,n):u},n.precision=function(){return arguments.length?n:0},n},ta.geo.interpolate=function(n,t){return Mr(n[0]*Da,n[1]*Da,t[0]*Da,t[1]*Da)},ta.geo.length=function(n){return Yc=0,ta.geo.stream(n,Zc),Yc};var Yc,Zc={sphere:b,point:b,lineStart:xr,lineEnd:b,polygonStart:b,polygonEnd:b},Vc=br(function(n){return Math.sqrt(2/(1+n))},function(n){return 2*Math.asin(n/2)});(ta.geo.azimuthalEqualArea=function(){return ur(Vc)}).raw=Vc;var Xc=br(function(n){var t=Math.acos(n);return t&&t/Math.sin(t)},y);(ta.geo.azimuthalEquidistant=function(){return ur(Xc)}).raw=Xc,(ta.geo.conicConformal=function(){return Ye(_r)}).raw=_r,(ta.geo.conicEquidistant=function(){return Ye(wr)}).raw=wr;var $c=br(function(n){return 1/n},Math.atan);(ta.geo.gnomonic=function(){return ur($c)}).raw=$c,Sr.invert=function(n,t){return[n,2*Math.atan(Math.exp(t))-Ra]},(ta.geo.mercator=function(){return kr(Sr)}).raw=Sr;var Bc=br(function(){return 1},Math.asin);(ta.geo.orthographic=function(){return ur(Bc)}).raw=Bc;var Wc=br(function(n){return 1/(1+n)},function(n){return 2*Math.atan(n)});(ta.geo.stereographic=function(){return ur(Wc)}).raw=Wc,Er.invert=function(n,t){return[-t,2*Math.atan(Math.exp(n))-Ra]},(ta.geo.transverseMercator=function(){var n=kr(Er),t=n.center,e=n.rotate;return n.center=function(n){return n?t([-n[1],n[0]]):(n=t(),[n[1],-n[0]])},n.rotate=function(n){return n?e([n[0],n[1],n.length>2?n[2]+90:90]):(n=e(),[n[0],n[1],n[2]-90])},e([0,0,90])}).raw=Er,ta.geom={},ta.geom.hull=function(n){function t(n){if(n.length<3)return[];var t,u=Et(e),i=Et(r),o=n.length,a=[],c=[];for(t=0;o>t;t++)a.push([+u.call(this,n[t],t),+i.call(this,n[t],t),t]);for(a.sort(zr),t=0;o>t;t++)c.push([a[t][0],-a[t][1]]);var l=Cr(a),s=Cr(c),f=s[0]===l[0],h=s[s.length-1]===l[l.length-1],g=[];for(t=l.length-1;t>=0;--t)g.push(n[a[l[t]][2]]);for(t=+f;t=r&&l.x<=i&&l.y>=u&&l.y<=o?[[r,o],[i,o],[i,u],[r,u]]:[];s.point=n[a]}),t}function e(n){return n.map(function(n,t){return{x:Math.round(i(n,t)/Ca)*Ca,y:Math.round(o(n,t)/Ca)*Ca,i:t}})}var r=Ar,u=Nr,i=r,o=u,a=ul;return n?t(n):(t.links=function(n){return iu(e(n)).edges.filter(function(n){return n.l&&n.r}).map(function(t){return{source:n[t.l.i],target:n[t.r.i]}})},t.triangles=function(n){var t=[];return iu(e(n)).cells.forEach(function(e,r){for(var u,i,o=e.site,a=e.edges.sort(Yr),c=-1,l=a.length,s=a[l-1].edge,f=s.l===o?s.r:s.l;++c=l,h=r>=s,g=h<<1|f;n.leaf=!1,n=n.nodes[g]||(n.nodes[g]=su()),f?u=l:a=l,h?o=s:c=s,i(n,t,e,r,u,o,a,c)}var s,f,h,g,p,v,d,m,y,M=Et(a),x=Et(c);if(null!=t)v=t,d=e,m=r,y=u;else if(m=y=-(v=d=1/0),f=[],h=[],p=n.length,o)for(g=0;p>g;++g)s=n[g],s.xm&&(m=s.x),s.y>y&&(y=s.y),f.push(s.x),h.push(s.y);else for(g=0;p>g;++g){var b=+M(s=n[g],g),_=+x(s,g);v>b&&(v=b),d>_&&(d=_),b>m&&(m=b),_>y&&(y=_),f.push(b),h.push(_)}var w=m-v,S=y-d;w>S?y=d+w:m=v+S;var k=su();if(k.add=function(n){i(k,n,+M(n,++g),+x(n,g),v,d,m,y)},k.visit=function(n){fu(n,k,v,d,m,y)},k.find=function(n){return hu(k,n[0],n[1],v,d,m,y)},g=-1,null==t){for(;++g=0?n.slice(0,t):n,r=t>=0?n.slice(t+1):"in";return e=cl.get(e)||al,r=ll.get(r)||y,Mu(r(e.apply(null,ea.call(arguments,1))))},ta.interpolateHcl=Lu,ta.interpolateHsl=Tu,ta.interpolateLab=Ru,ta.interpolateRound=Du,ta.transform=function(n){var t=ua.createElementNS(ta.ns.prefix.svg,"g");return(ta.transform=function(n){if(null!=n){t.setAttribute("transform",n);var e=t.transform.baseVal.consolidate()}return new Pu(e?e.matrix:sl)})(n)},Pu.prototype.toString=function(){return"translate("+this.translate+")rotate("+this.rotate+")skewX("+this.skew+")scale("+this.scale+")"};var sl={a:1,b:0,c:0,d:1,e:0,f:0};ta.interpolateTransform=Hu,ta.layout={},ta.layout.bundle=function(){return function(n){for(var t=[],e=-1,r=n.length;++ea*a/d){if(p>c){var l=t.charge/c;n.px-=i*l,n.py-=o*l}return!0}if(t.point&&c&&p>c){var l=t.pointCharge/c;n.px-=i*l,n.py-=o*l}}return!t.charge}}function t(n){n.px=ta.event.x,n.py=ta.event.y,a.resume()}var e,r,u,i,o,a={},c=ta.dispatch("start","tick","end"),l=[1,1],s=.9,f=fl,h=hl,g=-30,p=gl,v=.1,d=.64,m=[],M=[];return a.tick=function(){if((r*=.99)<.005)return c.end({type:"end",alpha:r=0}),!0;var t,e,a,f,h,p,d,y,x,b=m.length,_=M.length;for(e=0;_>e;++e)a=M[e],f=a.source,h=a.target,y=h.x-f.x,x=h.y-f.y,(p=y*y+x*x)&&(p=r*i[e]*((p=Math.sqrt(p))-u[e])/p,y*=p,x*=p,h.x-=y*(d=f.weight/(h.weight+f.weight)),h.y-=x*d,f.x+=y*(d=1-d),f.y+=x*d);if((d=r*v)&&(y=l[0]/2,x=l[1]/2,e=-1,d))for(;++e0?n:0:n>0&&(c.start({type:"start",alpha:r=n}),ta.timer(a.tick)),a):r},a.start=function(){function n(n,r){if(!e){for(e=new Array(c),a=0;c>a;++a)e[a]=[];for(a=0;s>a;++a){var u=M[a];e[u.source.index].push(u.target),e[u.target.index].push(u.source)}}for(var i,o=e[t],a=-1,l=o.length;++at;++t)(r=m[t]).index=t,r.weight=0;for(t=0;s>t;++t)r=M[t],"number"==typeof r.source&&(r.source=m[r.source]),"number"==typeof r.target&&(r.target=m[r.target]),++r.source.weight,++r.target.weight;for(t=0;c>t;++t)r=m[t],isNaN(r.x)&&(r.x=n("x",p)),isNaN(r.y)&&(r.y=n("y",v)),isNaN(r.px)&&(r.px=r.x),isNaN(r.py)&&(r.py=r.y);if(u=[],"function"==typeof f)for(t=0;s>t;++t)u[t]=+f.call(this,M[t],t);else for(t=0;s>t;++t)u[t]=f;if(i=[],"function"==typeof h)for(t=0;s>t;++t)i[t]=+h.call(this,M[t],t);else for(t=0;s>t;++t)i[t]=h;if(o=[],"function"==typeof g)for(t=0;c>t;++t)o[t]=+g.call(this,m[t],t);else for(t=0;c>t;++t)o[t]=g;return a.resume()},a.resume=function(){return a.alpha(.1)},a.stop=function(){return a.alpha(0)},a.drag=function(){return e||(e=ta.behavior.drag().origin(y).on("dragstart.force",Xu).on("drag.force",t).on("dragend.force",$u)),arguments.length?void this.on("mouseover.force",Bu).on("mouseout.force",Wu).call(e):e},ta.rebind(a,c,"on")};var fl=20,hl=1,gl=1/0;ta.layout.hierarchy=function(){function n(u){var i,o=[u],a=[];for(u.depth=0;null!=(i=o.pop());)if(a.push(i),(l=e.call(n,i,i.depth))&&(c=l.length)){for(var c,l,s;--c>=0;)o.push(s=l[c]),s.parent=i,s.depth=i.depth+1;r&&(i.value=0),i.children=l}else r&&(i.value=+r.call(n,i,i.depth)||0),delete i.children;return Qu(u,function(n){var e,u;t&&(e=n.children)&&e.sort(t),r&&(u=n.parent)&&(u.value+=n.value)}),a}var t=ei,e=ni,r=ti;return n.sort=function(e){return arguments.length?(t=e,n):t},n.children=function(t){return arguments.length?(e=t,n):e},n.value=function(t){return arguments.length?(r=t,n):r},n.revalue=function(t){return r&&(Ku(t,function(n){n.children&&(n.value=0)}),Qu(t,function(t){var e;t.children||(t.value=+r.call(n,t,t.depth)||0),(e=t.parent)&&(e.value+=t.value)})),t},n},ta.layout.partition=function(){function n(t,e,r,u){var i=t.children;if(t.x=e,t.y=t.depth*u,t.dx=r,t.dy=u,i&&(o=i.length)){var o,a,c,l=-1;for(r=t.value?r/t.value:0;++lf?-1:1),p=(f-c*g)/ta.sum(l),v=ta.range(c),d=[];return null!=e&&v.sort(e===pl?function(n,t){return l[t]-l[n]}:function(n,t){return e(o[n],o[t])}),v.forEach(function(n){d[n]={data:o[n],value:a=l[n],startAngle:s,endAngle:s+=a*p+g,padAngle:h}}),d}var t=Number,e=pl,r=0,u=La,i=0;return n.value=function(e){return arguments.length?(t=e,n):t},n.sort=function(t){return arguments.length?(e=t,n):e},n.startAngle=function(t){return arguments.length?(r=t,n):r},n.endAngle=function(t){return arguments.length?(u=t,n):u},n.padAngle=function(t){return arguments.length?(i=t,n):i},n};var pl={};ta.layout.stack=function(){function n(a,c){if(!(h=a.length))return a;var l=a.map(function(e,r){return t.call(n,e,r)}),s=l.map(function(t){return t.map(function(t,e){return[i.call(n,t,e),o.call(n,t,e)]})}),f=e.call(n,s,c);l=ta.permute(l,f),s=ta.permute(s,f);var h,g,p,v,d=r.call(n,s,c),m=l[0].length;for(p=0;m>p;++p)for(u.call(n,l[0][p],v=d[p],s[0][p][1]),g=1;h>g;++g)u.call(n,l[g][p],v+=s[g-1][p][1],s[g][p][1]);return a}var t=y,e=ai,r=ci,u=oi,i=ui,o=ii;return n.values=function(e){return arguments.length?(t=e,n):t},n.order=function(t){return arguments.length?(e="function"==typeof t?t:vl.get(t)||ai,n):e},n.offset=function(t){return arguments.length?(r="function"==typeof t?t:dl.get(t)||ci,n):r},n.x=function(t){return arguments.length?(i=t,n):i},n.y=function(t){return arguments.length?(o=t,n):o},n.out=function(t){return arguments.length?(u=t,n):u},n};var vl=ta.map({"inside-out":function(n){var t,e,r=n.length,u=n.map(li),i=n.map(si),o=ta.range(r).sort(function(n,t){return u[n]-u[t]}),a=0,c=0,l=[],s=[];for(t=0;r>t;++t)e=o[t],c>a?(a+=i[e],l.push(e)):(c+=i[e],s.push(e));return s.reverse().concat(l)},reverse:function(n){return ta.range(n.length).reverse()},"default":ai}),dl=ta.map({silhouette:function(n){var t,e,r,u=n.length,i=n[0].length,o=[],a=0,c=[];for(e=0;i>e;++e){for(t=0,r=0;u>t;t++)r+=n[t][e][1];r>a&&(a=r),o.push(r)}for(e=0;i>e;++e)c[e]=(a-o[e])/2;return c},wiggle:function(n){var t,e,r,u,i,o,a,c,l,s=n.length,f=n[0],h=f.length,g=[];for(g[0]=c=l=0,e=1;h>e;++e){for(t=0,u=0;s>t;++t)u+=n[t][e][1];for(t=0,i=0,a=f[e][0]-f[e-1][0];s>t;++t){for(r=0,o=(n[t][e][1]-n[t][e-1][1])/(2*a);t>r;++r)o+=(n[r][e][1]-n[r][e-1][1])/a;i+=o*n[t][e][1]}g[e]=c-=u?i/u*a:0,l>c&&(l=c)}for(e=0;h>e;++e)g[e]-=l;return g},expand:function(n){var t,e,r,u=n.length,i=n[0].length,o=1/u,a=[];for(e=0;i>e;++e){for(t=0,r=0;u>t;t++)r+=n[t][e][1];if(r)for(t=0;u>t;t++)n[t][e][1]/=r;else for(t=0;u>t;t++)n[t][e][1]=o}for(e=0;i>e;++e)a[e]=0;return a},zero:ci});ta.layout.histogram=function(){function n(n,i){for(var o,a,c=[],l=n.map(e,this),s=r.call(this,l,i),f=u.call(this,s,l,i),i=-1,h=l.length,g=f.length-1,p=t?1:1/h;++i0)for(i=-1;++i=s[0]&&a<=s[1]&&(o=c[ta.bisect(f,a,1,g)-1],o.y+=p,o.push(n[i]));return c}var t=!0,e=Number,r=pi,u=hi;return n.value=function(t){return arguments.length?(e=t,n):e},n.range=function(t){return arguments.length?(r=Et(t),n):r},n.bins=function(t){return arguments.length?(u="number"==typeof t?function(n){return gi(n,t)}:Et(t),n):u},n.frequency=function(e){return arguments.length?(t=!!e,n):t},n},ta.layout.pack=function(){function n(n,i){var o=e.call(this,n,i),a=o[0],c=u[0],l=u[1],s=null==t?Math.sqrt:"function"==typeof t?t:function(){return t};if(a.x=a.y=0,Qu(a,function(n){n.r=+s(n.value)}),Qu(a,Mi),r){var f=r*(t?1:Math.max(2*a.r/c,2*a.r/l))/2;Qu(a,function(n){n.r+=f}),Qu(a,Mi),Qu(a,function(n){n.r-=f})}return _i(a,c/2,l/2,t?1:1/Math.max(2*a.r/c,2*a.r/l)),o}var t,e=ta.layout.hierarchy().sort(vi),r=0,u=[1,1];return n.size=function(t){return arguments.length?(u=t,n):u},n.radius=function(e){return arguments.length?(t=null==e||"function"==typeof e?e:+e,n):t},n.padding=function(t){return arguments.length?(r=+t,n):r},Gu(n,e)},ta.layout.tree=function(){function n(n,u){var s=o.call(this,n,u),f=s[0],h=t(f);if(Qu(h,e),h.parent.m=-h.z,Ku(h,r),l)Ku(f,i);else{var g=f,p=f,v=f;Ku(f,function(n){n.xp.x&&(p=n),n.depth>v.depth&&(v=n)});var d=a(g,p)/2-g.x,m=c[0]/(p.x+a(p,g)/2+d),y=c[1]/(v.depth||1);Ku(f,function(n){n.x=(n.x+d)*m,n.y=n.depth*y})}return s}function t(n){for(var t,e={A:null,children:[n]},r=[e];null!=(t=r.pop());)for(var u,i=t.children,o=0,a=i.length;a>o;++o)r.push((i[o]=u={_:i[o],parent:t,children:(u=i[o].children)&&u.slice()||[],A:null,a:null,z:0,m:0,c:0,s:0,t:null,i:o}).a=u);return e.children[0]}function e(n){var t=n.children,e=n.parent.children,r=n.i?e[n.i-1]:null;if(t.length){Ni(n);var i=(t[0].z+t[t.length-1].z)/2;r?(n.z=r.z+a(n._,r._),n.m=n.z-i):n.z=i}else r&&(n.z=r.z+a(n._,r._));n.parent.A=u(n,r,n.parent.A||e[0])}function r(n){n._.x=n.z+n.parent.m,n.m+=n.parent.m}function u(n,t,e){if(t){for(var r,u=n,i=n,o=t,c=u.parent.children[0],l=u.m,s=i.m,f=o.m,h=c.m;o=Ei(o),u=ki(u),o&&u;)c=ki(c),i=Ei(i),i.a=n,r=o.z+f-u.z-l+a(o._,u._),r>0&&(Ai(Ci(o,n,e),n,r),l+=r,s+=r),f+=o.m,l+=u.m,h+=c.m,s+=i.m;o&&!Ei(i)&&(i.t=o,i.m+=f-s),u&&!ki(c)&&(c.t=u,c.m+=l-h,e=n)}return e}function i(n){n.x*=c[0],n.y=n.depth*c[1]}var o=ta.layout.hierarchy().sort(null).value(null),a=Si,c=[1,1],l=null;return n.separation=function(t){return arguments.length?(a=t,n):a},n.size=function(t){return arguments.length?(l=null==(c=t)?i:null,n):l?null:c},n.nodeSize=function(t){return arguments.length?(l=null==(c=t)?null:i,n):l?c:null},Gu(n,o)},ta.layout.cluster=function(){function n(n,i){var o,a=t.call(this,n,i),c=a[0],l=0;Qu(c,function(n){var t=n.children;t&&t.length?(n.x=qi(t),n.y=zi(t)):(n.x=o?l+=e(n,o):0,n.y=0,o=n)});var s=Li(c),f=Ti(c),h=s.x-e(s,f)/2,g=f.x+e(f,s)/2;return Qu(c,u?function(n){n.x=(n.x-c.x)*r[0],n.y=(c.y-n.y)*r[1]}:function(n){n.x=(n.x-h)/(g-h)*r[0],n.y=(1-(c.y?n.y/c.y:1))*r[1]}),a}var t=ta.layout.hierarchy().sort(null).value(null),e=Si,r=[1,1],u=!1;return n.separation=function(t){return arguments.length?(e=t,n):e},n.size=function(t){return arguments.length?(u=null==(r=t),n):u?null:r},n.nodeSize=function(t){return arguments.length?(u=null!=(r=t),n):u?r:null},Gu(n,t)},ta.layout.treemap=function(){function n(n,t){for(var e,r,u=-1,i=n.length;++ut?0:t),e.area=isNaN(r)||0>=r?0:r}function t(e){var i=e.children;if(i&&i.length){var o,a,c,l=f(e),s=[],h=i.slice(),p=1/0,v="slice"===g?l.dx:"dice"===g?l.dy:"slice-dice"===g?1&e.depth?l.dy:l.dx:Math.min(l.dx,l.dy);for(n(h,l.dx*l.dy/e.value),s.area=0;(c=h.length)>0;)s.push(o=h[c-1]),s.area+=o.area,"squarify"!==g||(a=r(s,v))<=p?(h.pop(),p=a):(s.area-=s.pop().area,u(s,v,l,!1),v=Math.min(l.dx,l.dy),s.length=s.area=0,p=1/0);s.length&&(u(s,v,l,!0),s.length=s.area=0),i.forEach(t)}}function e(t){var r=t.children;if(r&&r.length){var i,o=f(t),a=r.slice(),c=[];for(n(a,o.dx*o.dy/t.value),c.area=0;i=a.pop();)c.push(i),c.area+=i.area,null!=i.z&&(u(c,i.z?o.dx:o.dy,o,!a.length),c.length=c.area=0);r.forEach(e)}}function r(n,t){for(var e,r=n.area,u=0,i=1/0,o=-1,a=n.length;++oe&&(i=e),e>u&&(u=e));return r*=r,t*=t,r?Math.max(t*u*p/r,r/(t*i*p)):1/0}function u(n,t,e,r){var u,i=-1,o=n.length,a=e.x,l=e.y,s=t?c(n.area/t):0;if(t==e.dx){for((r||s>e.dy)&&(s=e.dy);++ie.dx)&&(s=e.dx);++ie&&(t=1),1>e&&(n=0),function(){var e,r,u;do e=2*Math.random()-1,r=2*Math.random()-1,u=e*e+r*r;while(!u||u>1);return n+t*e*Math.sqrt(-2*Math.log(u)/u)}},logNormal:function(){var n=ta.random.normal.apply(ta,arguments);return function(){return Math.exp(n())}},bates:function(n){var t=ta.random.irwinHall(n);return function(){return t()/n}},irwinHall:function(n){return function(){for(var t=0,e=0;n>e;e++)t+=Math.random();return t}}},ta.scale={};var ml={floor:y,ceil:y};ta.scale.linear=function(){return Ii([0,1],[0,1],mu,!1)};var yl={s:1,g:1,p:1,r:1,e:1};ta.scale.log=function(){return Ji(ta.scale.linear().domain([0,1]),10,!0,[1,10])};var Ml=ta.format(".0e"),xl={floor:function(n){return-Math.ceil(-n)},ceil:function(n){return-Math.floor(-n)}};ta.scale.pow=function(){return Gi(ta.scale.linear(),1,[0,1])},ta.scale.sqrt=function(){return ta.scale.pow().exponent(.5)},ta.scale.ordinal=function(){return Qi([],{t:"range",a:[[]]})},ta.scale.category10=function(){return ta.scale.ordinal().range(bl)},ta.scale.category20=function(){return ta.scale.ordinal().range(_l)},ta.scale.category20b=function(){return ta.scale.ordinal().range(wl)},ta.scale.category20c=function(){return ta.scale.ordinal().range(Sl)};var bl=[2062260,16744206,2924588,14034728,9725885,9197131,14907330,8355711,12369186,1556175].map(Mt),_l=[2062260,11454440,16744206,16759672,2924588,10018698,14034728,16750742,9725885,12955861,9197131,12885140,14907330,16234194,8355711,13092807,12369186,14408589,1556175,10410725].map(Mt),wl=[3750777,5395619,7040719,10264286,6519097,9216594,11915115,13556636,9202993,12426809,15186514,15190932,8666169,11356490,14049643,15177372,8077683,10834324,13528509,14589654].map(Mt),Sl=[3244733,7057110,10406625,13032431,15095053,16616764,16625259,16634018,3253076,7652470,10607003,13101504,7695281,10394312,12369372,14342891,6513507,9868950,12434877,14277081].map(Mt);ta.scale.quantile=function(){return no([],[])},ta.scale.quantize=function(){return to(0,1,[0,1])},ta.scale.threshold=function(){return eo([.5],[0,1])},ta.scale.identity=function(){return ro([0,1])},ta.svg={},ta.svg.arc=function(){function n(){var n=Math.max(0,+e.apply(this,arguments)),l=Math.max(0,+r.apply(this,arguments)),s=o.apply(this,arguments)-Ra,f=a.apply(this,arguments)-Ra,h=Math.abs(f-s),g=s>f?0:1;if(n>l&&(p=l,l=n,n=p),h>=Ta)return t(l,g)+(n?t(n,1-g):"")+"Z";var p,v,d,m,y,M,x,b,_,w,S,k,E=0,A=0,N=[];if((m=(+c.apply(this,arguments)||0)/2)&&(d=i===kl?Math.sqrt(n*n+l*l):+i.apply(this,arguments),g||(A*=-1),l&&(A=tt(d/l*Math.sin(m))),n&&(E=tt(d/n*Math.sin(m)))),l){y=l*Math.cos(s+A),M=l*Math.sin(s+A),x=l*Math.cos(f-A),b=l*Math.sin(f-A);var C=Math.abs(f-s-2*A)<=qa?0:1;if(A&&so(y,M,x,b)===g^C){var z=(s+f)/2;y=l*Math.cos(z),M=l*Math.sin(z),x=b=null}}else y=M=0;if(n){_=n*Math.cos(f-E),w=n*Math.sin(f-E),S=n*Math.cos(s+E),k=n*Math.sin(s+E);var q=Math.abs(s-f+2*E)<=qa?0:1;if(E&&so(_,w,S,k)===1-g^q){var L=(s+f)/2;_=n*Math.cos(L),w=n*Math.sin(L),S=k=null}}else _=w=0;if((p=Math.min(Math.abs(l-n)/2,+u.apply(this,arguments)))>.001){v=l>n^g?0:1;var T=null==S?[_,w]:null==x?[y,M]:Lr([y,M],[S,k],[x,b],[_,w]),R=y-T[0],D=M-T[1],P=x-T[0],U=b-T[1],j=1/Math.sin(Math.acos((R*P+D*U)/(Math.sqrt(R*R+D*D)*Math.sqrt(P*P+U*U)))/2),F=Math.sqrt(T[0]*T[0]+T[1]*T[1]);if(null!=x){var H=Math.min(p,(l-F)/(j+1)),O=fo(null==S?[_,w]:[S,k],[y,M],l,H,g),I=fo([x,b],[_,w],l,H,g);p===H?N.push("M",O[0],"A",H,",",H," 0 0,",v," ",O[1],"A",l,",",l," 0 ",1-g^so(O[1][0],O[1][1],I[1][0],I[1][1]),",",g," ",I[1],"A",H,",",H," 0 0,",v," ",I[0]):N.push("M",O[0],"A",H,",",H," 0 1,",v," ",I[0])}else N.push("M",y,",",M);if(null!=S){var Y=Math.min(p,(n-F)/(j-1)),Z=fo([y,M],[S,k],n,-Y,g),V=fo([_,w],null==x?[y,M]:[x,b],n,-Y,g);p===Y?N.push("L",V[0],"A",Y,",",Y," 0 0,",v," ",V[1],"A",n,",",n," 0 ",g^so(V[1][0],V[1][1],Z[1][0],Z[1][1]),",",1-g," ",Z[1],"A",Y,",",Y," 0 0,",v," ",Z[0]):N.push("L",V[0],"A",Y,",",Y," 0 0,",v," ",Z[0])}else N.push("L",_,",",w)}else N.push("M",y,",",M),null!=x&&N.push("A",l,",",l," 0 ",C,",",g," ",x,",",b),N.push("L",_,",",w),null!=S&&N.push("A",n,",",n," 0 ",q,",",1-g," ",S,",",k);return N.push("Z"),N.join("")}function t(n,t){return"M0,"+n+"A"+n+","+n+" 0 1,"+t+" 0,"+-n+"A"+n+","+n+" 0 1,"+t+" 0,"+n}var e=io,r=oo,u=uo,i=kl,o=ao,a=co,c=lo;return n.innerRadius=function(t){return arguments.length?(e=Et(t),n):e},n.outerRadius=function(t){return arguments.length?(r=Et(t),n):r},n.cornerRadius=function(t){return arguments.length?(u=Et(t),n):u},n.padRadius=function(t){return arguments.length?(i=t==kl?kl:Et(t),n):i},n.startAngle=function(t){return arguments.length?(o=Et(t),n):o},n.endAngle=function(t){return arguments.length?(a=Et(t),n):a},n.padAngle=function(t){return arguments.length?(c=Et(t),n):c},n.centroid=function(){var n=(+e.apply(this,arguments)+ +r.apply(this,arguments))/2,t=(+o.apply(this,arguments)+ +a.apply(this,arguments))/2-Ra;return[Math.cos(t)*n,Math.sin(t)*n]},n};var kl="auto";ta.svg.line=function(){return ho(y)};var El=ta.map({linear:go,"linear-closed":po,step:vo,"step-before":mo,"step-after":yo,basis:So,"basis-open":ko,"basis-closed":Eo,bundle:Ao,cardinal:bo,"cardinal-open":Mo,"cardinal-closed":xo,monotone:To});El.forEach(function(n,t){t.key=n,t.closed=/-closed$/.test(n)});var Al=[0,2/3,1/3,0],Nl=[0,1/3,2/3,0],Cl=[0,1/6,2/3,1/6];ta.svg.line.radial=function(){var n=ho(Ro);return n.radius=n.x,delete n.x,n.angle=n.y,delete n.y,n},mo.reverse=yo,yo.reverse=mo,ta.svg.area=function(){return Do(y)},ta.svg.area.radial=function(){var n=Do(Ro);return n.radius=n.x,delete n.x,n.innerRadius=n.x0,delete n.x0,n.outerRadius=n.x1,delete n.x1,n.angle=n.y,delete n.y,n.startAngle=n.y0,delete n.y0,n.endAngle=n.y1,delete n.y1,n},ta.svg.chord=function(){function n(n,a){var c=t(this,i,n,a),l=t(this,o,n,a);return"M"+c.p0+r(c.r,c.p1,c.a1-c.a0)+(e(c,l)?u(c.r,c.p1,c.r,c.p0):u(c.r,c.p1,l.r,l.p0)+r(l.r,l.p1,l.a1-l.a0)+u(l.r,l.p1,c.r,c.p0))+"Z"}function t(n,t,e,r){var u=t.call(n,e,r),i=a.call(n,u,r),o=c.call(n,u,r)-Ra,s=l.call(n,u,r)-Ra;return{r:i,a0:o,a1:s,p0:[i*Math.cos(o),i*Math.sin(o)],p1:[i*Math.cos(s),i*Math.sin(s)]}}function e(n,t){return n.a0==t.a0&&n.a1==t.a1}function r(n,t,e){return"A"+n+","+n+" 0 "+ +(e>qa)+",1 "+t}function u(n,t,e,r){return"Q 0,0 "+r}var i=mr,o=yr,a=Po,c=ao,l=co;return n.radius=function(t){return arguments.length?(a=Et(t),n):a},n.source=function(t){return arguments.length?(i=Et(t),n):i},n.target=function(t){return arguments.length?(o=Et(t),n):o},n.startAngle=function(t){return arguments.length?(c=Et(t),n):c},n.endAngle=function(t){return arguments.length?(l=Et(t),n):l},n},ta.svg.diagonal=function(){function n(n,u){var i=t.call(this,n,u),o=e.call(this,n,u),a=(i.y+o.y)/2,c=[i,{x:i.x,y:a},{x:o.x,y:a},o];return c=c.map(r),"M"+c[0]+"C"+c[1]+" "+c[2]+" "+c[3]}var t=mr,e=yr,r=Uo;return n.source=function(e){return arguments.length?(t=Et(e),n):t},n.target=function(t){return arguments.length?(e=Et(t),n):e},n.projection=function(t){return arguments.length?(r=t,n):r},n},ta.svg.diagonal.radial=function(){var n=ta.svg.diagonal(),t=Uo,e=n.projection;return n.projection=function(n){return arguments.length?e(jo(t=n)):t},n},ta.svg.symbol=function(){function n(n,r){return(zl.get(t.call(this,n,r))||Oo)(e.call(this,n,r))}var t=Ho,e=Fo;return n.type=function(e){return arguments.length?(t=Et(e),n):t},n.size=function(t){return arguments.length?(e=Et(t),n):e},n};var zl=ta.map({circle:Oo,cross:function(n){var t=Math.sqrt(n/5)/2;return"M"+-3*t+","+-t+"H"+-t+"V"+-3*t+"H"+t+"V"+-t+"H"+3*t+"V"+t+"H"+t+"V"+3*t+"H"+-t+"V"+t+"H"+-3*t+"Z"},diamond:function(n){var t=Math.sqrt(n/(2*Ll)),e=t*Ll;return"M0,"+-t+"L"+e+",0 0,"+t+" "+-e+",0Z"},square:function(n){var t=Math.sqrt(n)/2;return"M"+-t+","+-t+"L"+t+","+-t+" "+t+","+t+" "+-t+","+t+"Z"},"triangle-down":function(n){var t=Math.sqrt(n/ql),e=t*ql/2;return"M0,"+e+"L"+t+","+-e+" "+-t+","+-e+"Z"},"triangle-up":function(n){var t=Math.sqrt(n/ql),e=t*ql/2;return"M0,"+-e+"L"+t+","+e+" "+-t+","+e+"Z"}});ta.svg.symbolTypes=zl.keys();var ql=Math.sqrt(3),Ll=Math.tan(30*Da);_a.transition=function(n){for(var t,e,r=Tl||++Ul,u=Xo(n),i=[],o=Rl||{time:Date.now(),ease:Su,delay:0,duration:250},a=-1,c=this.length;++ai;i++){u.push(t=[]);for(var e=this[i],a=0,c=e.length;c>a;a++)(r=e[a])&&n.call(r,r.__data__,a,i)&&t.push(r)}return Yo(u,this.namespace,this.id)},Pl.tween=function(n,t){var e=this.id,r=this.namespace;return arguments.length<2?this.node()[r][e].tween.get(n):Y(this,null==t?function(t){t[r][e].tween.remove(n)}:function(u){u[r][e].tween.set(n,t)})},Pl.attr=function(n,t){function e(){this.removeAttribute(a)}function r(){this.removeAttributeNS(a.space,a.local)}function u(n){return null==n?e:(n+="",function(){var t,e=this.getAttribute(a);return e!==n&&(t=o(e,n),function(n){this.setAttribute(a,t(n))})})}function i(n){return null==n?r:(n+="",function(){var t,e=this.getAttributeNS(a.space,a.local);return e!==n&&(t=o(e,n),function(n){this.setAttributeNS(a.space,a.local,t(n))})})}if(arguments.length<2){for(t in n)this.attr(t,n[t]);return this}var o="transform"==n?Hu:mu,a=ta.ns.qualify(n);return Zo(this,"attr."+n,t,a.local?i:u)},Pl.attrTween=function(n,t){function e(n,e){var r=t.call(this,n,e,this.getAttribute(u));return r&&function(n){this.setAttribute(u,r(n))}}function r(n,e){var r=t.call(this,n,e,this.getAttributeNS(u.space,u.local));return r&&function(n){this.setAttributeNS(u.space,u.local,r(n))}}var u=ta.ns.qualify(n);return this.tween("attr."+n,u.local?r:e)},Pl.style=function(n,e,r){function u(){this.style.removeProperty(n)}function i(e){return null==e?u:(e+="",function(){var u,i=t(this).getComputedStyle(this,null).getPropertyValue(n);return i!==e&&(u=mu(i,e),function(t){this.style.setProperty(n,u(t),r)})})}var o=arguments.length;if(3>o){if("string"!=typeof n){2>o&&(e="");for(r in n)this.style(r,n[r],e);return this}r=""}return Zo(this,"style."+n,e,i)},Pl.styleTween=function(n,e,r){function u(u,i){var o=e.call(this,u,i,t(this).getComputedStyle(this,null).getPropertyValue(n));return o&&function(t){this.style.setProperty(n,o(t),r)}}return arguments.length<3&&(r=""),this.tween("style."+n,u)},Pl.text=function(n){return Zo(this,"text",n,Vo)},Pl.remove=function(){var n=this.namespace;return this.each("end.transition",function(){var t;this[n].count<2&&(t=this.parentNode)&&t.removeChild(this)})},Pl.ease=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].ease:("function"!=typeof n&&(n=ta.ease.apply(ta,arguments)),Y(this,function(r){r[e][t].ease=n}))},Pl.delay=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].delay:Y(this,"function"==typeof n?function(r,u,i){r[e][t].delay=+n.call(r,r.__data__,u,i)}:(n=+n,function(r){r[e][t].delay=n}))},Pl.duration=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].duration:Y(this,"function"==typeof n?function(r,u,i){r[e][t].duration=Math.max(1,n.call(r,r.__data__,u,i))}:(n=Math.max(1,n),function(r){r[e][t].duration=n}))},Pl.each=function(n,t){var e=this.id,r=this.namespace;if(arguments.length<2){var u=Rl,i=Tl;try{Tl=e,Y(this,function(t,u,i){Rl=t[r][e],n.call(t,t.__data__,u,i)})}finally{Rl=u,Tl=i}}else Y(this,function(u){var i=u[r][e];(i.event||(i.event=ta.dispatch("start","end","interrupt"))).on(n,t)});return this},Pl.transition=function(){for(var n,t,e,r,u=this.id,i=++Ul,o=this.namespace,a=[],c=0,l=this.length;l>c;c++){a.push(n=[]);for(var t=this[c],s=0,f=t.length;f>s;s++)(e=t[s])&&(r=e[o][u],$o(e,s,o,i,{time:r.time,ease:r.ease,delay:r.delay+r.duration,duration:r.duration})),n.push(e)}return Yo(a,o,i)},ta.svg.axis=function(){function n(n){n.each(function(){var n,l=ta.select(this),s=this.__chart__||e,f=this.__chart__=e.copy(),h=null==c?f.ticks?f.ticks.apply(f,a):f.domain():c,g=null==t?f.tickFormat?f.tickFormat.apply(f,a):y:t,p=l.selectAll(".tick").data(h,f),v=p.enter().insert("g",".domain").attr("class","tick").style("opacity",Ca),d=ta.transition(p.exit()).style("opacity",Ca).remove(),m=ta.transition(p.order()).style("opacity",1),M=Math.max(u,0)+o,x=Ui(f),b=l.selectAll(".domain").data([0]),_=(b.enter().append("path").attr("class","domain"),ta.transition(b));v.append("line"),v.append("text");var w,S,k,E,A=v.select("line"),N=m.select("line"),C=p.select("text").text(g),z=v.select("text"),q=m.select("text"),L="top"===r||"left"===r?-1:1;if("bottom"===r||"top"===r?(n=Bo,w="x",k="y",S="x2",E="y2",C.attr("dy",0>L?"0em":".71em").style("text-anchor","middle"),_.attr("d","M"+x[0]+","+L*i+"V0H"+x[1]+"V"+L*i)):(n=Wo,w="y",k="x",S="y2",E="x2",C.attr("dy",".32em").style("text-anchor",0>L?"end":"start"),_.attr("d","M"+L*i+","+x[0]+"H0V"+x[1]+"H"+L*i)),A.attr(E,L*u),z.attr(k,L*M),N.attr(S,0).attr(E,L*u),q.attr(w,0).attr(k,L*M),f.rangeBand){var T=f,R=T.rangeBand()/2;s=f=function(n){return T(n)+R}}else s.rangeBand?s=f:d.call(n,f,s);v.call(n,s,f),m.call(n,f,f)})}var t,e=ta.scale.linear(),r=jl,u=6,i=6,o=3,a=[10],c=null;return n.scale=function(t){return arguments.length?(e=t,n):e},n.orient=function(t){return arguments.length?(r=t in Fl?t+"":jl,n):r},n.ticks=function(){return arguments.length?(a=arguments,n):a},n.tickValues=function(t){return arguments.length?(c=t,n):c},n.tickFormat=function(e){return arguments.length?(t=e,n):t},n.tickSize=function(t){var e=arguments.length;return e?(u=+t,i=+arguments[e-1],n):u},n.innerTickSize=function(t){return arguments.length?(u=+t,n):u},n.outerTickSize=function(t){return arguments.length?(i=+t,n):i},n.tickPadding=function(t){return arguments.length?(o=+t,n):o},n.tickSubdivide=function(){return arguments.length&&n},n};var jl="bottom",Fl={top:1,right:1,bottom:1,left:1};ta.svg.brush=function(){function n(t){t.each(function(){var t=ta.select(this).style("pointer-events","all").style("-webkit-tap-highlight-color","rgba(0,0,0,0)").on("mousedown.brush",i).on("touchstart.brush",i),o=t.selectAll(".background").data([0]);o.enter().append("rect").attr("class","background").style("visibility","hidden").style("cursor","crosshair"),t.selectAll(".extent").data([0]).enter().append("rect").attr("class","extent").style("cursor","move");var a=t.selectAll(".resize").data(v,y);a.exit().remove(),a.enter().append("g").attr("class",function(n){return"resize "+n}).style("cursor",function(n){return Hl[n]}).append("rect").attr("x",function(n){return/[ew]$/.test(n)?-3:null}).attr("y",function(n){return/^[ns]/.test(n)?-3:null}).attr("width",6).attr("height",6).style("visibility","hidden"),a.style("display",n.empty()?"none":null);var c,f=ta.transition(t),h=ta.transition(o);l&&(c=Ui(l),h.attr("x",c[0]).attr("width",c[1]-c[0]),r(f)),s&&(c=Ui(s),h.attr("y",c[0]).attr("height",c[1]-c[0]),u(f)),e(f)})}function e(n){n.selectAll(".resize").attr("transform",function(n){return"translate("+f[+/e$/.test(n)]+","+h[+/^s/.test(n)]+")"})}function r(n){n.select(".extent").attr("x",f[0]),n.selectAll(".extent,.n>rect,.s>rect").attr("width",f[1]-f[0])}function u(n){n.select(".extent").attr("y",h[0]),n.selectAll(".extent,.e>rect,.w>rect").attr("height",h[1]-h[0])}function i(){function i(){32==ta.event.keyCode&&(C||(M=null,q[0]-=f[1],q[1]-=h[1],C=2),S())}function v(){32==ta.event.keyCode&&2==C&&(q[0]+=f[1],q[1]+=h[1],C=0,S())}function d(){var n=ta.mouse(b),t=!1;x&&(n[0]+=x[0],n[1]+=x[1]),C||(ta.event.altKey?(M||(M=[(f[0]+f[1])/2,(h[0]+h[1])/2]),q[0]=f[+(n[0]s?(u=r,r=s):u=s),v[0]!=r||v[1]!=u?(e?a=null:o=null,v[0]=r,v[1]=u,!0):void 0}function y(){d(),k.style("pointer-events","all").selectAll(".resize").style("display",n.empty()?"none":null),ta.select("body").style("cursor",null),L.on("mousemove.brush",null).on("mouseup.brush",null).on("touchmove.brush",null).on("touchend.brush",null).on("keydown.brush",null).on("keyup.brush",null),z(),w({type:"brushend"})}var M,x,b=this,_=ta.select(ta.event.target),w=c.of(b,arguments),k=ta.select(b),E=_.datum(),A=!/^(n|s)$/.test(E)&&l,N=!/^(e|w)$/.test(E)&&s,C=_.classed("extent"),z=W(b),q=ta.mouse(b),L=ta.select(t(b)).on("keydown.brush",i).on("keyup.brush",v);if(ta.event.changedTouches?L.on("touchmove.brush",d).on("touchend.brush",y):L.on("mousemove.brush",d).on("mouseup.brush",y),k.interrupt().selectAll("*").interrupt(),C)q[0]=f[0]-q[0],q[1]=h[0]-q[1];else if(E){var T=+/w$/.test(E),R=+/^n/.test(E);x=[f[1-T]-q[0],h[1-R]-q[1]],q[0]=f[T],q[1]=h[R]}else ta.event.altKey&&(M=q.slice());k.style("pointer-events","none").selectAll(".resize").style("display",null),ta.select("body").style("cursor",_.style("cursor")),w({type:"brushstart"}),d()}var o,a,c=E(n,"brushstart","brush","brushend"),l=null,s=null,f=[0,0],h=[0,0],g=!0,p=!0,v=Ol[0];return n.event=function(n){n.each(function(){var n=c.of(this,arguments),t={x:f,y:h,i:o,j:a},e=this.__chart__||t;this.__chart__=t,Tl?ta.select(this).transition().each("start.brush",function(){o=e.i,a=e.j,f=e.x,h=e.y,n({type:"brushstart"})}).tween("brush:brush",function(){var e=yu(f,t.x),r=yu(h,t.y);return o=a=null,function(u){f=t.x=e(u),h=t.y=r(u),n({type:"brush",mode:"resize"})}}).each("end.brush",function(){o=t.i,a=t.j,n({type:"brush",mode:"resize"}),n({type:"brushend"})}):(n({type:"brushstart"}),n({type:"brush",mode:"resize"}),n({type:"brushend"}))})},n.x=function(t){return arguments.length?(l=t,v=Ol[!l<<1|!s],n):l},n.y=function(t){return arguments.length?(s=t,v=Ol[!l<<1|!s],n):s},n.clamp=function(t){return arguments.length?(l&&s?(g=!!t[0],p=!!t[1]):l?g=!!t:s&&(p=!!t),n):l&&s?[g,p]:l?g:s?p:null},n.extent=function(t){var e,r,u,i,c;return arguments.length?(l&&(e=t[0],r=t[1],s&&(e=e[0],r=r[0]),o=[e,r],l.invert&&(e=l(e),r=l(r)),e>r&&(c=e,e=r,r=c),(e!=f[0]||r!=f[1])&&(f=[e,r])),s&&(u=t[0],i=t[1],l&&(u=u[1],i=i[1]),a=[u,i],s.invert&&(u=s(u),i=s(i)),u>i&&(c=u,u=i,i=c),(u!=h[0]||i!=h[1])&&(h=[u,i])),n):(l&&(o?(e=o[0],r=o[1]):(e=f[0],r=f[1],l.invert&&(e=l.invert(e),r=l.invert(r)),e>r&&(c=e,e=r,r=c))),s&&(a?(u=a[0],i=a[1]):(u=h[0],i=h[1],s.invert&&(u=s.invert(u),i=s.invert(i)),u>i&&(c=u,u=i,i=c))),l&&s?[[e,u],[r,i]]:l?[e,r]:s&&[u,i])},n.clear=function(){return n.empty()||(f=[0,0],h=[0,0],o=a=null),n},n.empty=function(){return!!l&&f[0]==f[1]||!!s&&h[0]==h[1]},ta.rebind(n,c,"on")};var Hl={n:"ns-resize",e:"ew-resize",s:"ns-resize",w:"ew-resize",nw:"nwse-resize",ne:"nesw-resize",se:"nwse-resize",sw:"nesw-resize"},Ol=[["n","e","s","w","nw","ne","se","sw"],["e","w"],["n","s"],[]],Il=ac.format=gc.timeFormat,Yl=Il.utc,Zl=Yl("%Y-%m-%dT%H:%M:%S.%LZ");Il.iso=Date.prototype.toISOString&&+new Date("2000-01-01T00:00:00.000Z")?Jo:Zl,Jo.parse=function(n){var t=new Date(n);return isNaN(t)?null:t},Jo.toString=Zl.toString,ac.second=Ft(function(n){return new cc(1e3*Math.floor(n/1e3))},function(n,t){n.setTime(n.getTime()+1e3*Math.floor(t))},function(n){return n.getSeconds()}),ac.seconds=ac.second.range,ac.seconds.utc=ac.second.utc.range,ac.minute=Ft(function(n){return new cc(6e4*Math.floor(n/6e4))},function(n,t){n.setTime(n.getTime()+6e4*Math.floor(t))},function(n){return n.getMinutes()}),ac.minutes=ac.minute.range,ac.minutes.utc=ac.minute.utc.range,ac.hour=Ft(function(n){var t=n.getTimezoneOffset()/60;return new cc(36e5*(Math.floor(n/36e5-t)+t))},function(n,t){n.setTime(n.getTime()+36e5*Math.floor(t))},function(n){return n.getHours()}),ac.hours=ac.hour.range,ac.hours.utc=ac.hour.utc.range,ac.month=Ft(function(n){return n=ac.day(n),n.setDate(1),n},function(n,t){n.setMonth(n.getMonth()+t)},function(n){return n.getMonth()}),ac.months=ac.month.range,ac.months.utc=ac.month.utc.range;var Vl=[1e3,5e3,15e3,3e4,6e4,3e5,9e5,18e5,36e5,108e5,216e5,432e5,864e5,1728e5,6048e5,2592e6,7776e6,31536e6],Xl=[[ac.second,1],[ac.second,5],[ac.second,15],[ac.second,30],[ac.minute,1],[ac.minute,5],[ac.minute,15],[ac.minute,30],[ac.hour,1],[ac.hour,3],[ac.hour,6],[ac.hour,12],[ac.day,1],[ac.day,2],[ac.week,1],[ac.month,1],[ac.month,3],[ac.year,1]],$l=Il.multi([[".%L",function(n){return n.getMilliseconds()}],[":%S",function(n){return n.getSeconds()}],["%I:%M",function(n){return n.getMinutes()}],["%I %p",function(n){return n.getHours()}],["%a %d",function(n){return n.getDay()&&1!=n.getDate()}],["%b %d",function(n){return 1!=n.getDate()}],["%B",function(n){return n.getMonth()}],["%Y",Ne]]),Bl={range:function(n,t,e){return ta.range(Math.ceil(n/e)*e,+t,e).map(Ko)},floor:y,ceil:y};Xl.year=ac.year,ac.scale=function(){return Go(ta.scale.linear(),Xl,$l)};var Wl=Xl.map(function(n){return[n[0].utc,n[1]]}),Jl=Yl.multi([[".%L",function(n){return n.getUTCMilliseconds()}],[":%S",function(n){return n.getUTCSeconds()}],["%I:%M",function(n){return n.getUTCMinutes()}],["%I %p",function(n){return n.getUTCHours()}],["%a %d",function(n){return n.getUTCDay()&&1!=n.getUTCDate()}],["%b %d",function(n){return 1!=n.getUTCDate()}],["%B",function(n){return n.getUTCMonth()}],["%Y",Ne]]);Wl.year=ac.year.utc,ac.scale.utc=function(){return Go(ta.scale.linear(),Wl,Jl)},ta.text=At(function(n){return n.responseText}),ta.json=function(n,t){return Nt(n,"application/json",Qo,t)},ta.html=function(n,t){return Nt(n,"text/html",na,t)},ta.xml=At(function(n){return n.responseXML}),"function"==typeof define&&define.amd?define(ta):"object"==typeof module&&module.exports&&(module.exports=ta),this.d3=ta}(); \ No newline at end of file diff --git a/public/js/jquery-1.9.0.min.js b/public/assets/jquery/jquery-1.9.0.min.js similarity index 100% rename from public/js/jquery-1.9.0.min.js rename to public/assets/jquery/jquery-1.9.0.min.js diff --git a/public/js/jquery.deserialize.min.js b/public/assets/jquery/jquery.deserialize.min.js similarity index 100% rename from public/js/jquery.deserialize.min.js rename to public/assets/jquery/jquery.deserialize.min.js diff --git a/public/css/main.css b/public/css/main.css index b77a82e08..665a38038 100644 --- a/public/css/main.css +++ b/public/css/main.css @@ -1,3 +1,19 @@ +/* + * Copyright 2015 LinkedIn Corp. + * + * 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. + */ + .left-table { display:table; } @@ -81,4 +97,57 @@ a.list-group-item-danger.active:focus { } .left{display:inline-block} -.right{display:inline-block; float:right} \ No newline at end of file +.right{display:inline-block; float:right} + +/* Graph axis */ +.axis path, .axis line { + fill: none; + stroke: #777; + shape-rendering: crispEdges; +} +.y.axis path { + display:none; +} +.tick { + stroke-dasharray: 5, 5, 1, 5; +} + +/* Override bootstrap. Horizontal scrollable history table */ +.table-responsive { + overflow-x: auto +} +.table-responsive > .table > thead > tr > th { + text-align:center; +} +.table-responsive > .table > tbody > tr > td { + text-align:left; +} +.table-responsive > .table > thead > tr > th, +.table-responsive > .table > tbody > tr > td { + overflow: hidden; + width: 120px; + padding-right:0px; +} +.table-responsive > .table > thead > tr > th > a{ + text-decoration:none; +} + +/* Tooltips for history table */ +.hasTooltip { + text-decoration:none; + position:relative; +} +.hasTooltip div { + display:none; + position:fixed; + background-color:white; + border-radius:5px; + color:black; + border:2px solid gray; + padding:5px; + overflow:hidden; +} +.hasTooltip:hover div { + display:block; + z-index:1; +} \ No newline at end of file diff --git a/public/images/loading.gif b/public/images/loading.gif new file mode 100644 index 0000000000000000000000000000000000000000..3c2f7c058836808f7f1f5f9a4bdf37d4e5f9284a GIT binary patch literal 3208 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 ; i--) { + for (var j = 0; j < data[i].jobscores.length; j++) { + var jobDefUrl = data[i].jobscores[j]["jobdefurl"]; + if (jobDefList.indexOf(jobDefUrl) == -1) { + jobDefList.push(jobDefUrl); + } + } + } + + plotter(data, jobDefList); + }); + + loadTableTooltips(); +}); + +/** + * Example tooltip content: + * + * Execution 1 + * Flow score = 163672 + * Top poor jobs: + * Job 25 45% + * Job 16 20% + * job 14 10% + * + */ +function getGraphTooltipContent(record, jobDefList) { + + var content = ["" + record.flowtime + ""]; + content.push("Flow Score = " + record.score); + if (record.score != 0) { + var jobLimit = 3; + content.push("Top poor jobs:"); + + var scoreList = []; + for (var i = 0; i < record.jobscores.length; i++) { + var scoreWidth = record.jobscores[i]["jobscore"] * 100 / record.score; + scoreList.push([scoreWidth, i]); + } + + scoreList.sort(function(left, right) { + return left[0] > right[0] ? -1 : 1; + }); + + // Traverse ordered list + var jobRow = ""; + for (var jobIndex = 0; jobIndex < scoreList.length; jobIndex++) { + + var width = scoreList[jobIndex][0]; + var index = scoreList[jobIndex][1]; + + // Skip after jobLimit jobs are captured or when width is 0. + if (jobIndex >= jobLimit || width == 0) { + break; + } + + // cell 1 + var jobDefUrl = record.jobscores[index]['jobdefurl']; + var jobLink = "/jobhistory?historyjoburl=" + encodeURIComponent(jobDefUrl); + var jobRef = "Job " + (jobDefList.indexOf(jobDefUrl) + 1) + ""; + var cell1 = "" + jobRef + ""; + + // cell 2 + var jobScoreRect = "
" + +width.toFixed(2) + "\%
"; + var cell2 = "" + jobScoreRect + ""; + + jobRow = jobRow + "" + cell1 + cell2 + ""; + } + + var jobTable = "" + jobRow + "
"; + content.push(jobTable); + } + return content; +} \ No newline at end of file diff --git a/public/js/graphutility.js b/public/js/graphutility.js new file mode 100644 index 000000000..a9454fe2f --- /dev/null +++ b/public/js/graphutility.js @@ -0,0 +1,238 @@ +/* + * Copyright 2015 LinkedIn Corp. + * + * 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. + */ + +/* Show loading sign during ajax call */ +$(document).ajaxStart(function() { + $("#loading-indicator").show(); +}); + +$(document).ajaxStop(function() { + $("#loading-indicator").hide(); +}); + +/* Plot the performance graph for the data */ +function plotter(graphData, jobDefList) { + + graphData.forEach(function(d) { d.flowtime = new Date(d.flowtime); }); + + var graphContainer = d3.select("#visualisation"); + + /////////// DEFINE THE GRAPH ATTRIBUTES ///////////// + + // Define the Margins for the GRAPH Dimensions + var MARGINS = {top: 50, right: 50, bottom: 100, left: 100}, + WIDTH = graphContainer.style("width").replace("px", ""), + HEIGHT = graphContainer.style("height").replace("px", ""), + GRAPH_WIDTH = WIDTH - MARGINS.left - MARGINS.right, + GRAPH_HEIGHT = HEIGHT - MARGINS.top - MARGINS.bottom; + + // Set the domain of x + var millisDay = 86400000; // Offset to the domain. Also makes a single execution to be at the center. + var xRange = d3.time.scale().range([MARGINS.left, MARGINS.left + GRAPH_WIDTH]) + .domain([ + d3.min(graphData, function (d) { return Math.min(d.flowtime) - millisDay/2}), + d3.max(graphData, function (d) { return Math.max(d.flowtime) + millisDay/2}) + ]); + + // Set the domain of y + var yRange = d3.scale.linear().range([MARGINS.top + GRAPH_HEIGHT, MARGINS.top]) + .domain([0, d3.max(graphData, function (d) { return d.score + d.score/5; })]) + .nice(5); // Ensures a nice round value at the end of y axis + + // The graph function + var lineFunc = d3.svg.line() + .x(function (d) { return xRange(d.flowtime); }) + .y(function (d) { return yRange(d.score); }) + .interpolate('linear'); + + /* + var customTimeFormat = d3.time.format.multi([ + [".%L", function(d) { return d.getMilliseconds(); }], + [":%S", function(d) { return d.getSeconds(); }], + ["%I:%M", function(d) { return d.getMinutes(); }], + ["%I %p", function(d) { return d.getHours(); }], + ["%a %d", function(d) { return d.getDay() && d.getDate() != 1; }], + ["%b %d", function(d) { return d.getDate() != 1; }], + ["%B", function(d) { return d.getMonth(); }], + ["%Y", function() { return true; }] + ]); + */ + + var customTimeFormat = d3.time.format("%Y-%b-%d"); + + // x-axis definition + var xAxis = d3.svg.axis() + .scale(xRange) + .tickSize(3) + .orient("bottom") + .ticks(9) + .tickFormat(customTimeFormat); + + // y-axis definition + var yAxis = d3.svg.axis() + .scale(yRange) + .tickSize(-1 * (GRAPH_WIDTH)) // Adds horizontal lines in the graph + .ticks(5) // Set 5 levels (5 horizontal lines) + .orient("left"); + + /////////// ADD CONTENTS TO THE GRAPH CONTAINER ///////////// + + // Add the x-axis + graphContainer.append("svg:g") + .attr("class", "x axis") + .attr("transform", "translate(0," + (HEIGHT - MARGINS.bottom) + ")") + .call(xAxis); + + // Add the y-axis + graphContainer.append("svg:g") + .attr("class", "y axis") + .attr("transform", "translate(" + (MARGINS.left) + ", 0)") + .call(yAxis); + + // Add label for the x axis + graphContainer.append("svg:text") + .style("text-anchor", "middle") + .attr("transform", "translate(" + ((WIDTH - MARGINS.right + MARGINS.left)/2) + ", " + (HEIGHT - (MARGINS.bottom/2)) + ")") + .text("Executions"); + + // Add label for the y axis + graphContainer.append("svg:text") + .style("text-anchor", "middle") + .attr("transform", "translate(" + (MARGINS.left/3) + ", " + (HEIGHT/2) + ")rotate(-90)") + .text("Performance Score"); + + // Add the graph function + graphContainer.append("svg:path") + .attr("d", lineFunc(graphData)) + .attr("stroke", "black") + .attr("stroke-width", 2) + .attr("fill", "none"); + + // Add the small bubble dots on the graph line + graphContainer.append("svg:g") + .selectAll("scatter-dots") + .data(graphData) + .enter().append("svg:circle") + .style({stroke: 'white', fill: 'green'}) + .attr("cx", function (d) { return xRange(d.flowtime); } ) + .attr("cy", function (d) { return yRange(d.score); } ) + .attr("r", 5); + + + /////////// THE TOOLTIPS FOR THE GRAPH ///////////// + + // Add a transparent rectangle on top of the graph area to compute x-value mouse over + graphContainer.append("svg:rect") + .attr("class", "overlay") + .attr("width", GRAPH_WIDTH) + .attr("height", GRAPH_HEIGHT) + .attr("transform", "translate(" + (MARGINS.left) + ", " + (MARGINS.top) + ")") + .attr("opacity", 0) + .on("mouseover", function() { tooltip.style("display", null); }) // Reset tooltip display (default value) + .on("mousemove", mousemove); // Compute position and show the tooltip + + // The tooltip container (Top of the stack) + var tooltip = graphContainer.append("svg:g"); + + // Add the highlight bubble + var highlightCircleRad = 10; + tooltip.append("svg:circle") + .attr("stroke", "white") + .attr("fill", "green") + .attr("r", highlightCircleRad) + .style("display", "none"); + + // Add the tooltip + var tooltipWidth = 300; + tooltip.append("foreignObject") + .attr("width", tooltipWidth + "px") + .append("xhtml:body") + .style("background", "rgba(255, 255, 255, 0.9)") + .style("text-align", "left") + .style("border-radius", "5px") + .style("padding", "5px") + .style("border", "2px solid gray"); + + var bisectExec = d3.bisector(function(d) { return d.flowtime; }).left; + + function mousemove(d) { + + // Compute tooltip to be shown depending on mouse position + var record; + if (graphData.length == 1) { + record = graphData[0]; + } else { + var xValueMouse = xRange.invert(MARGINS.left + d3.mouse(this)[0]), + index = bisectExec(graphData, xValueMouse, 1), + dleft = graphData[index - 1], + dright = graphData[index]; + record = xValueMouse - dleft.flowtime > dright.flowtime - xValueMouse ? dright : dleft; + } + + // Add content to tooltip + tooltip.select("body").html(getGraphTooltipContent(record, jobDefList).join("
")); + + // Set position of highlighted circle + tooltip.select("circle").style("display", "inline").attr("transform", "translate(" + xRange(record.flowtime) + "," + + yRange(record.score) + ")"); + + // Set position of tooltip + tooltip.select("foreignObject").attr("height", tooltip.select("body").style("height")); + tooltip.select("foreignObject").attr("transform", "translate(" + (xRange(record.flowtime) - (tooltipWidth/2)) + "," + + (yRange(record.score) - tooltip.select("body").style("height").replace("px", "") - 10) + ")"); + + } +} + +/* Return the query parameters */ +function queryString() { + + var query_string = {}; + var query = window.location.search.substring(1); // Returns the query parameters excluding ? + var vars = query.split("&"); + + for (var i = 0; i < vars.length; i++) { + var pair = vars[i].split("="); + if (typeof query_string[pair[0]] === "undefined") { + query_string[pair[0]] = pair[1]; + } + } + return query_string; +} + +/* Update tooltip position on mouse-move over table */ +function loadTableTooltips() { + + var tooltipDiv = document.querySelectorAll('.hasTooltip div'); + window.onmousemove = function (e) { + var x = e.clientX, + y = e.clientY; + + for (var i = 0; i < tooltipDiv.length; i++) { + tooltipDiv[i].style.top = (y - tooltipDiv[i].offsetHeight - 10)+ "px"; + tooltipDiv[i].style.left = (x + 10) + "px"; + } + }; +} + +/* Update execution table with time in user timezone */ +function updateExecTimezone(data) { + var parse = d3.time.format("%b %d, %Y %I:%M %p"); + var time = document.querySelectorAll('.exectime'); + for (var i = time.length - 1; i >= 0; i--) { + time[i].innerHTML = parse(new Date(data[time.length - 1 - i].flowtime)); + } +} \ No newline at end of file diff --git a/public/js/jobhistoryform.js b/public/js/jobhistoryform.js new file mode 100644 index 000000000..1330261d0 --- /dev/null +++ b/public/js/jobhistoryform.js @@ -0,0 +1,82 @@ +/* + * Copyright 2015 LinkedIn Corp. + * + * 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. + */ + +$(document).ready(function(){ + + /* Plot graph for data obtained from ajax call */ + $.getJSON('/rest/jobgraphdata?url=' + queryString()['historyjoburl'], function(data) { + updateExecTimezone(data); + plotter(data, []); + }); + + loadTableTooltips(); +}); + +/** + * Example tooltip content: + * + * Execution 1 + * Job score = 163672 + * Top poor stages: + * Stage 1 65% + * Stage 26 25% + * Stage 12 11% + */ +function getGraphTooltipContent(record, jobDefList) { + + var content = ["" + record.flowtime + ""]; + content.push("Job Score = " + record.score); + if (record.score != 0) { + var jobLimit = 3; + content.push("Top poor stages:"); + + var scoreList = []; + for (var i = 0; i < record.stagescores.length; i++) { + var scoreWidth = record.stagescores[i]["stagescore"] * 100 / record.score; + scoreList.push([scoreWidth, i]); + } + + scoreList.sort(function(left, right) { + return left[0] > right[0] ? -1 : 1; + }); + + var stageRows = ""; + for (var stageIndex = 0; stageIndex < scoreList.length; stageIndex++) { + + var width = scoreList[stageIndex][0]; + var index = scoreList[stageIndex][1]; + + // Skip after jobLimit jobs are captured or when width becomes 0. + if (stageIndex >= jobLimit || width == 0) { + break; + } + + // cell 1 + var cell1 = "Stage " + (index + 1) + ""; + + // cell 2 + var stageScoreRect = "
" + +width.toFixed(2) + + "\%
"; + var cell2 = "" + stageScoreRect + ""; + + stageRows = stageRows + "" + cell1 + cell2 + ""; + } + + var jobTable = "" + stageRows + "
"; + content.push(jobTable); + } + return content; +} diff --git a/public/js/searchform.js b/public/js/searchform.js index 638272a37..9d8af0f0e 100644 --- a/public/js/searchform.js +++ b/public/js/searchform.js @@ -1,3 +1,19 @@ +/* + * Copyright 2015 LinkedIn Corp. + * + * 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. + */ + $(document).ready(function(){ var form = $("#search-form"); From 4471ae984ce899da5a07918b4733908b9ca66f7c Mon Sep 17 00:00:00 2001 From: Akshay Rai Date: Sun, 20 Dec 2015 21:33:23 +0530 Subject: [PATCH 122/272] LIHADOOP-16102 Discuss and remove Hadoop 1 support from Dr. Elephant RB=634544 G=superfriends-reviewrs R=annag,fli,rratti,viramach A=rratti --- app-conf/FetcherConf.xml | 12 +- app-conf/HeuristicConf.xml | 67 +-- .../linkedin/drelephant/ElephantContext.java | 60 +- .../linkedin/drelephant/ElephantRunner.java | 5 +- .../analysis/AnalyticJobGeneratorHadoop1.java | 146 ----- .../mapreduce/MapReduceFetcherHadoop1.java | 554 ------------------ .../util/HeuristicConfiguration.java | 26 +- app/com/linkedin/drelephant/util/Utils.java | 22 - .../help/mapreduce/helpMapperTime.scala.html | 3 +- .../help/mapreduce/helpShuffleSort.scala.html | 2 +- start.sh | 3 +- .../linkedin/drelephant/util/UtilsTest.java | 24 - 12 files changed, 34 insertions(+), 890 deletions(-) delete mode 100644 app/com/linkedin/drelephant/analysis/AnalyticJobGeneratorHadoop1.java delete mode 100644 app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop1.java diff --git a/app-conf/FetcherConf.xml b/app-conf/FetcherConf.xml index d33ce3b2e..3e5bdb738 100644 --- a/app-conf/FetcherConf.xml +++ b/app-conf/FetcherConf.xml @@ -23,28 +23,18 @@ # Choose the application type that this fetcher is for mapreduce - # Choose the hadoop version, could be a version string '1.x.x', '2.x', '1 or etc. Only the major version number - # would be used. - 1 # Specify the implementation class - com.linkedin.drelephant.mapreduce.MapReduceFetcherHadoop1 + com.linkedin.drelephant.mapreduce.MapReduceFetcherHadoop2 --> mapreduce - 1 - com.linkedin.drelephant.mapreduce.MapReduceFetcherHadoop1 - - - mapreduce - 2 com.linkedin.drelephant.mapreduce.MapReduceFetcherHadoop2 spark - 2 org.apache.spark.deploy.history.SparkFSFetcher diff --git a/app-conf/HeuristicConf.xml b/app-conf/HeuristicConf.xml index 0a5b853a6..3d2dca46c 100644 --- a/app-conf/HeuristicConf.xml +++ b/app-conf/HeuristicConf.xml @@ -22,10 +22,6 @@ Mapper Data Skew com.linkedin.drelephant.mapreduce.heuristics.MapperDataSkewHeuristic views.html.help.mapreduce.helpMapperDataSkew - - 1 - 2 -
@@ -33,10 +29,6 @@ Mapper Time com.linkedin.drelephant.mapreduce.heuristics.MapperTimeHeuristic views.html.help.mapreduce.helpMapperTime - - 1 - 2 - @@ -44,30 +36,22 @@ Mapper Speed com.linkedin.drelephant.mapreduce.heuristics.MapperSpeedHeuristic views.html.help.mapreduce.helpMapperSpeed - - 1 - 2 - + mapreduce Mapper GC com.linkedin.drelephant.mapreduce.heuristics.MapperGCHeuristic views.html.help.mapreduce.helpGC - - 1 - 2 - @@ -75,10 +59,6 @@ Reducer Data Skew com.linkedin.drelephant.mapreduce.heuristics.ReducerDataSkewHeuristic views.html.help.mapreduce.helpReducerDataSkew - - 1 - 2 - @@ -86,10 +66,6 @@ Reducer Time com.linkedin.drelephant.mapreduce.heuristics.ReducerTimeHeuristic views.html.help.mapreduce.helpReducerTime - - 1 - 2 - @@ -97,10 +73,6 @@ Shuffle & Sort com.linkedin.drelephant.mapreduce.heuristics.ShuffleSortHeuristic views.html.help.mapreduce.helpShuffleSort - - 1 - 2 - @@ -108,10 +80,6 @@ Reducer GC com.linkedin.drelephant.mapreduce.heuristics.ReducerGCHeuristic views.html.help.mapreduce.helpGC - - 1 - 2 - @@ -119,19 +87,6 @@ Exception com.linkedin.drelephant.mapreduce.heuristics.ExceptionHeuristic views.html.help.mapreduce.helpException - - 2 - - - - - mapreduce - Job Queue Limit - com.linkedin.drelephant.mapreduce.heuristics.JobQueueLimitHeuristic - views.html.help.mapreduce.helpJobQueueLimit - - 1 - @@ -139,9 +94,6 @@ Spark Configuration Best Practice com.linkedin.drelephant.spark.heuristics.BestPropertiesConventionHeuristic views.html.help.spark.helpBestProperties - - 2 - @@ -149,9 +101,6 @@ Spark Memory Limit com.linkedin.drelephant.spark.heuristics.MemoryLimitHeuristic views.html.help.spark.helpMemoryLimit - - 2 - @@ -159,9 +108,6 @@ Spark Stage Runtime com.linkedin.drelephant.spark.heuristics.StageRuntimeHeuristic views.html.help.spark.helpStageRuntime - - 2 - @@ -169,9 +115,6 @@ Spark Job Runtime com.linkedin.drelephant.spark.heuristics.JobRuntimeHeuristic views.html.help.spark.helpJobRuntime - - 2 - @@ -179,9 +122,6 @@ Spark Executor Load Balance com.linkedin.drelephant.spark.heuristics.ExecutorLoadHeuristic views.html.help.spark.helpExecutorLoad - - 2 - @@ -189,9 +129,6 @@ Spark Event Log Limit com.linkedin.drelephant.spark.heuristics.EventLogLimitHeuristic views.html.help.spark.helpEventLogLimit - - 2 - diff --git a/app/com/linkedin/drelephant/ElephantContext.java b/app/com/linkedin/drelephant/ElephantContext.java index fd861b23f..8a890462c 100644 --- a/app/com/linkedin/drelephant/ElephantContext.java +++ b/app/com/linkedin/drelephant/ElephantContext.java @@ -20,7 +20,6 @@ import com.linkedin.drelephant.analysis.ApplicationType; import com.linkedin.drelephant.analysis.ElephantFetcher; import com.linkedin.drelephant.analysis.HadoopApplicationData; -import com.linkedin.drelephant.analysis.HadoopSystemContext; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.JobType; @@ -31,7 +30,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; @@ -55,7 +53,6 @@ public class ElephantContext { private static final Logger logger = Logger.getLogger(ElephantContext.class); private static ElephantContext INSTANCE; - private static final String HADOOP_VERSION_XML_FIELD = "hadoopversion"; private static final String CLASS_NAME_XML_FIELD = "classname"; private static final String APPLICATION_TYPE_XML_FIELD = "applicationtype"; @@ -121,11 +118,6 @@ private void loadFetchers() { n++; Element fetcherNode = (Element) node; - Node hadoopVersionNode = fetcherNode.getElementsByTagName(HADOOP_VERSION_XML_FIELD).item(0); - if (hadoopVersionNode == null) { - throw new RuntimeException("No hadoopversion tag presented in fetcher #" + n); - } - Node applicationTypeNode = fetcherNode.getElementsByTagName(APPLICATION_TYPE_XML_FIELD).item(0); if (applicationTypeNode == null) { throw new RuntimeException("No applicationtype tag presented in fetcher #" + n); @@ -136,39 +128,33 @@ private void loadFetchers() { throw new RuntimeException("No classname tag presented in fetcher #" + n); } - String hadoopVersion = hadoopVersionNode.getTextContent().toLowerCase().trim(); - int hadoopMajorVersion = Utils.getMajorVersionFromString(hadoopVersion); - if (HadoopSystemContext.matchCurrentHadoopVersion(hadoopMajorVersion)) { - String typeName = applicationTypeNode.getTextContent(); - if (getApplicationTypeForName(typeName) == null) { - ApplicationType type = new ApplicationType(typeName); - - String className = classNameNode.getTextContent(); - try { - Class fetcherClass = Play.current().classloader().loadClass(className); - Object instance = fetcherClass.newInstance(); - if (!(instance instanceof ElephantFetcher)) { - throw new IllegalArgumentException( - "Class " + fetcherClass.getName() + " is not an implementation of " + ElephantFetcher.class - .getName()); - } - _typeToFetcher.put(type, (ElephantFetcher) fetcherClass.newInstance()); - } catch (ClassNotFoundException e) { - throw new RuntimeException("Class" + className + " not found for fetcher #" + n, e); - } catch (InstantiationException e) { - throw new RuntimeException("Could not instantiate class " + className, e); - } catch (IllegalAccessException e) { - throw new RuntimeException("Could not access constructor for class " + className, e); + String typeName = applicationTypeNode.getTextContent(); + if (getApplicationTypeForName(typeName) == null) { + ApplicationType type = new ApplicationType(typeName); + + String className = classNameNode.getTextContent(); + try { + Class fetcherClass = Play.current().classloader().loadClass(className); + Object instance = fetcherClass.newInstance(); + if (!(instance instanceof ElephantFetcher)) { + throw new IllegalArgumentException( + "Class " + fetcherClass.getName() + " is not an implementation of " + ElephantFetcher.class + .getName()); } - } else { - throw new RuntimeException( - "Given a hadoop version and an application type, there could only be one fetcher. Fetcher #" + n - + " is duplicated with the previous fetchers."); + _typeToFetcher.put(type, (ElephantFetcher) fetcherClass.newInstance()); + } catch (ClassNotFoundException e) { + throw new RuntimeException("Class" + className + " not found for fetcher #" + n, e); + } catch (InstantiationException e) { + throw new RuntimeException("Could not instantiate class " + className, e); + } catch (IllegalAccessException e) { + throw new RuntimeException("Could not access constructor for class " + className, e); } } else { - logger.info("Skipping fetcher #" + n + ", because its hadoop version [" + hadoopVersion - + "] does not match our current Hadoop version."); + throw new RuntimeException( + "Given a hadoop version and an application type, there could only be one fetcher. Fetcher #" + n + + " is duplicated with the previous fetchers."); } + } } } diff --git a/app/com/linkedin/drelephant/ElephantRunner.java b/app/com/linkedin/drelephant/ElephantRunner.java index 0f9885f41..4409348be 100644 --- a/app/com/linkedin/drelephant/ElephantRunner.java +++ b/app/com/linkedin/drelephant/ElephantRunner.java @@ -17,7 +17,6 @@ import com.linkedin.drelephant.analysis.AnalyticJob; import com.linkedin.drelephant.analysis.AnalyticJobGenerator; -import com.linkedin.drelephant.analysis.AnalyticJobGeneratorHadoop1; import com.linkedin.drelephant.analysis.HDFSContext; import com.linkedin.drelephant.analysis.HadoopSystemContext; import com.linkedin.drelephant.analysis.AnalyticJobGeneratorHadoop2; @@ -53,10 +52,8 @@ private void loadAnalyticJobGenerator() { JobConf configuration = new JobConf(); if (HadoopSystemContext.isHadoop2Env()) { _analyticJobGenerator = new AnalyticJobGeneratorHadoop2(); - } else if (HadoopSystemContext.isHadoop1Env()) { - _analyticJobGenerator = new AnalyticJobGeneratorHadoop1(); } else { - throw new RuntimeException("Unsupported Hadoop major version detected. It is neither 1.x nor 2.x."); + throw new RuntimeException("Unsupported Hadoop major version detected. It is not 2.x."); } try { diff --git a/app/com/linkedin/drelephant/analysis/AnalyticJobGeneratorHadoop1.java b/app/com/linkedin/drelephant/analysis/AnalyticJobGeneratorHadoop1.java deleted file mode 100644 index 61c6d4735..000000000 --- a/app/com/linkedin/drelephant/analysis/AnalyticJobGeneratorHadoop1.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright 2015 LinkedIn Corp. - * - * 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.linkedin.drelephant.analysis; - -import com.linkedin.drelephant.ElephantContext; -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Queue; -import java.util.Set; -import java.util.concurrent.ConcurrentLinkedQueue; -import model.JobResult; -import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.mapred.JobClient; -import org.apache.hadoop.mapred.JobConf; -import org.apache.hadoop.mapred.JobStatus; -import org.apache.hadoop.mapred.RunningJob; -import org.apache.hadoop.security.authentication.client.AuthenticationException; -import org.apache.log4j.Logger; - - -/** - * This class provides a AnalyticJob list to be fetched/analyzed in Hadoop 1 environment. - * - */ -public class AnalyticJobGeneratorHadoop1 implements AnalyticJobGenerator { - private static final Logger logger = Logger.getLogger(AnalyticJobGeneratorHadoop1.class); - private JobClient _jobClient; - - private boolean _firstRun = true; - private Set _previousJobs; - - private final Queue _retryQueue = new ConcurrentLinkedQueue(); - private static final String ONLY_SUPPORTED_TYPE_NAME = "MAPREDUCE"; - private ApplicationType _onlySupportedType; - - @Override - public void configure(Configuration configuration) - throws IOException { - _jobClient = new JobClient(new JobConf(configuration)); - - _onlySupportedType = ElephantContext.instance().getApplicationTypeForName(ONLY_SUPPORTED_TYPE_NAME); - if (_onlySupportedType == null) { - throw new RuntimeException("Cannot configure the analysis provider, " + ONLY_SUPPORTED_TYPE_NAME - + " application type is not supported."); - } - } - - @Override - public List fetchAnalyticJobs() - throws IOException, AuthenticationException { - JobStatus[] result = _jobClient.getAllJobs(); - - if (result == null) { - throw new IOException("Error fetching joblist from jobtracker."); - } - - // Get all completed jobs from jobtracker - Set successJobs = filterSuccessfulJobs(result); - - // Filter out newly completed jobs - Set todoJobs = filterPreviousJobs(successJobs); - - // Add newly completed jobs to return list - List jobList = new ArrayList(); - for (String jobId : todoJobs) { - RunningJob job = _jobClient.getJob(jobId); - JobStatus jobStatus = job.getJobStatus(); - - AnalyticJob analyticJob = new AnalyticJob(); - long startTime = jobStatus.getStartTime(); - - analyticJob.setAppId(jobId).setAppType(_onlySupportedType).setJobId(jobId).setUser(jobStatus.getUsername()) - .setName(job.getJobName()).setTrackingUrl(job.getTrackingURL()).setStartTime(startTime); - // Note: - // In Hadoop-1 the getFinishTime() call is not there. Unfortunately, calling this method does not - // result in a compile error, neither a MethodNotFound exception at run time. The program just hangs. - // Since we don't have metrics (the only consumer of finish time as of now) in Hadoop-1, set the finish time - // to be the same as start time. - analyticJob.setFinishTime(startTime); - - jobList.add(analyticJob); - } - // Append promises from the retry queue at the end of the list - while (!_retryQueue.isEmpty()) { - jobList.add(_retryQueue.poll()); - } - - return jobList; - } - - @Override - public void addIntoRetries(AnalyticJob promise) { - _retryQueue.add(promise); - } - - // Return a set of succeeded job ids - private Set filterSuccessfulJobs(JobStatus[] jobs) { - HashSet successJobs = new HashSet(); - for (JobStatus job : jobs) { - if (job.getRunState() == JobStatus.SUCCEEDED && job.isJobComplete()) { - successJobs.add(job.getJobID().toString()); - } - } - return successJobs; - } - - private Set filterPreviousJobs(Set jobs) { - // On first run, check each job against DB - if (_firstRun) { - logger.info("Checking database ...... "); - Set newJobs = new HashSet(); - for (String jobId : jobs) { - JobResult prevResult = JobResult.find.byId(jobId); - if (prevResult == null) { - // Job not found, add to new jobs list - newJobs.add(jobId); - } - } - _previousJobs = jobs; - _firstRun = false; - logger.info("Database check completed"); - return newJobs; - } else { - Set tempPrevJobs = new HashSet(jobs); - // Leave only the newly completed jobs - jobs.removeAll(_previousJobs); - _previousJobs = tempPrevJobs; - return jobs; - } - } -} diff --git a/app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop1.java b/app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop1.java deleted file mode 100644 index 8c1c0379c..000000000 --- a/app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop1.java +++ /dev/null @@ -1,554 +0,0 @@ -/* - * Copyright 2015 LinkedIn Corp. - * - * 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.linkedin.drelephant.mapreduce; - -import com.linkedin.drelephant.analysis.AnalyticJob; -import com.linkedin.drelephant.analysis.ElephantFetcher; -import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder.CounterName; -import com.linkedin.drelephant.math.Statistics; -import com.linkedin.drelephant.util.Utils; - -import java.io.IOException; -import java.net.HttpURLConnection; -import java.net.URL; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.Random; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicInteger; - -import org.apache.commons.io.IOUtils; -import org.apache.hadoop.mapred.Counters; -import org.apache.hadoop.mapred.Counters.Counter; -import org.apache.hadoop.mapred.JobClient; -import org.apache.hadoop.mapred.JobConf; -import org.apache.hadoop.mapred.JobID; -import org.apache.hadoop.mapred.JobStatus; -import org.apache.hadoop.mapred.RunningJob; -import org.apache.hadoop.mapred.TIPStatus; -import org.apache.hadoop.mapred.TaskReport; -import org.apache.hadoop.security.authentication.client.AuthenticatedURL; -import org.apache.hadoop.security.authentication.client.AuthenticationException; -import org.apache.log4j.Logger; -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.jsoup.select.Elements; - - -public class MapReduceFetcherHadoop1 implements ElephantFetcher { - private static final Logger logger = Logger.getLogger(ElephantFetcher.class); - private static final int SHUFFLE_SORT_MAX_SAMPLE_SIZE = 50; - private static final int DEFAULT_RETRY = 2; - private JobConf _conf; - - private Map _failedJobsInWait = - new ConcurrentHashMap(); - private Map _failedJobsInProgress = - new ConcurrentHashMap(); - private String _jobtrackerHttpRoot; - - public MapReduceFetcherHadoop1() - throws IOException { - _conf = new JobConf(); - _jobtrackerHttpRoot = "http://" + _conf.get("mapred.job.tracker.http.address") + "/"; - } - - @Override - public MapReduceApplicationData fetchData(AnalyticJob analyticJob) - throws IOException, AuthenticationException { - String id = analyticJob.getAppId(); - MapReduceApplicationData jobData = new MapReduceApplicationData(); - jobData.setAppId(id).setJobId(id); - try { - JobID jobId = JobID.forName(jobData.getJobId()); - JobClient jobClient = ThreadContextMR1.getClient(); - TaskReport[] mapperTasks = null; - TaskReport[] reducerTasks = null; - RunningJob job = jobClient.getJob(jobId); - - if (job != null) { - mapperTasks = jobClient.getMapTaskReports(jobId); - reducerTasks = jobClient.getReduceTaskReports(jobId); - } - - if (job == null || (mapperTasks.length == 0 && reducerTasks.length == 0)) { - if (checkRetiredAndFetchJobData(jobData)) { - // This is a retired job - logger.info(jobData.getJobId() + " retired. Fetch data from web UI."); - return jobData; - } - // This is a no data job - } - - JobStatus status = job.getJobStatus(); - String username = status.getUsername(); - long startTime = status.getStartTime(); - // ALERT: - // In Hadoop-1 the getFinishTime() call is not there. Unfortunately, calling this method does not - // result in a compile error, neither a MethodNotFound exception at run time. The program just hangs. - // Since we don't have metrics (the only consumer of finish time as of now) in Hadoop-1, set the finish time - // to be the same as start time. - long finishTime = startTime; - String jobUrl = job.getTrackingURL(); - String jobName = job.getJobName(); - jobData.setUsername(username).setStartTime(startTime).setFinishTime(finishTime).setUrl(jobUrl) - .setJobName(jobName); - - // Fetch job counter - MapReduceCounterHolder counterHolder = fetchCounter(job.getCounters()); - - int sampleSize = SHUFFLE_SORT_MAX_SAMPLE_SIZE; - - // Fetch mapper task data - List mapperList = new ArrayList(); - Statistics.shuffleArraySample(mapperTasks, sampleSize); - for (int i = 0; i < mapperTasks.length; i++) { - MapReduceTaskData mapper = fetchTaskData(jobUrl, mapperTasks[i], true, (i < sampleSize)); - if (mapper != null) { - mapperList.add(mapper); - } - } - MapReduceTaskData[] mappers = mapperList.toArray(new MapReduceTaskData[mapperList.size()]); - - // Fetch reducer task data - List reducerList = new ArrayList(); - Statistics.shuffleArraySample(reducerTasks, sampleSize); - for (int i = 0; i < reducerTasks.length; i++) { - MapReduceTaskData reducer = fetchTaskData(jobUrl, reducerTasks[i], false, (i < sampleSize)); - if (reducer != null) { - reducerList.add(reducer); - } - } - MapReduceTaskData[] reducers = reducerList.toArray(new MapReduceTaskData[reducerList.size()]); - - // Fetch job config - Properties jobConf = fetchJobConf(job); - jobData.setCounters(counterHolder).setMapperData(mappers).setReducerData(reducers).setJobConf(jobConf); - } finally { - ThreadContextMR1.updateAuthToken(); - } - - Utils.publishMetrics(jobData); - - return jobData; - } - - private Properties fetchJobConf(RunningJob job) - throws IOException, AuthenticationException { - String jobconfUrl = getJobconfUrl(job); - if (jobconfUrl == null) { - return new Properties(); - } - return fetchJobConfFromURL(jobconfUrl); - } - - private Properties fetchJobConfFromURL(String jobconfUrl) - throws IOException, AuthenticationException { - - //Parse job config from config page - Properties properties = new Properties(); - Document doc = ThreadContextMR1.fetchHtmlDoc(jobconfUrl); - Elements rows = doc.select("table").select("tr"); - for (Element row : rows) { - Elements cells = row.select("> td"); - if (cells.size() == 2) { - String key = cells.get(0).text().trim(); - String value = cells.get(1).text().trim(); - properties.put(key, value); - } - } - return properties; - } - - private MapReduceTaskData fetchTaskData(String jobDetailUrl, TaskReport task, boolean isMapper, boolean sampled) - throws IOException, AuthenticationException { - - if (task.getCurrentStatus() != TIPStatus.COMPLETE) { - // this is a failed task. Discard it. - return null; - } - - MapReduceCounterHolder taskCounter = fetchCounter(task.getCounters()); - - // This is not a sampled task, return task data with only counters - if (!sampled) { - return new MapReduceTaskData(taskCounter); - } - - // This is a sampled task, fetch task time from task detail page - String taskDetailsUrl = getTaskDetailsPageUrl(jobDetailUrl, task.getTaskID().toString()); - long[] time = fetchTaskDetails(taskDetailsUrl, isMapper); - return new MapReduceTaskData(taskCounter, time); - } - - private String getJobconfUrl(RunningJob job) { - String jobDetails = job.getTrackingURL(); - String root = jobDetails.substring(0, jobDetails.indexOf("jobdetails.jsp")); - return root + "jobconf.jsp?jobid=" + job.getID().toString(); - } - - private String getTaskDetailsPageUrl(String jobDetails, String taskId) { - String root = jobDetails.substring(0, jobDetails.indexOf("jobdetails.jsp")); - return root + "taskdetails.jsp?tipid=" + taskId.toString(); - } - - // Fetch task time from task detail page - private long[] fetchTaskDetails(String taskDetailUrl, boolean isMapper) - throws IOException, AuthenticationException { - Document doc = ThreadContextMR1.fetchHtmlDoc(taskDetailUrl); - Elements rows = doc.select("table").select("tr"); - long[] time = null; - for (Element row : rows) { - try { - time = tryExtractDetailFromRow(row, isMapper); - if (time != null) { - return time; - } - } catch (Exception e) { - throw new IOException("Error in fetch task data from task detail page. TASK URL=" + taskDetailUrl, e); - } - } - throw new IOException("No valid time data found from task detail page. TASK URL=" + taskDetailUrl); - } - - //This method return time array if successfully extracts data from table row () - //Return [totalMs,shuffleMs,sortMs] for reducers, [totalMs,0,0] for mappers - private long[] tryExtractDetailFromRow(Element row, boolean isMapper) - throws ParseException { - Elements cells = row.select("> td"); - - // For rows() in reducer task page with other than 12 cols(),or 10 cols in mapper page, - // they are not rows that contains time data - if ((!isMapper && cells.size() != 12) || (isMapper && cells.size() != 10)) { - return null; - } - - boolean succeeded = cells.get(2).html().trim().equals("SUCCEEDED"); - if (succeeded) { - if (!isMapper) { - // reducer task. Get total/shuffle/sort time from time span. Html text format: [abs time(time span)] - long shuffleTimeMs = parseTimeInMs(cells.get(5).html().trim()); - long sortTimeMs = parseTimeInMs(cells.get(6).html().trim()); - long totalTimeMs = parseTimeInMs(cells.get(7).html().trim()); - return new long[]{totalTimeMs, shuffleTimeMs, sortTimeMs}; - } else { - // Mapper task. Get total time from time span - long totalTimeMs = parseTimeInMs(cells.get(5).html().trim()); - return new long[]{totalTimeMs, 0, 0}; - } - } - // This is a failed task attempt. - return null; - } - - private MapReduceCounterHolder fetchCounter(Counters counters) { - MapReduceCounterHolder holder = new MapReduceCounterHolder(); - for (Counters.Group group : counters) { - for (Counter ctr : group) { - CounterName cn = CounterName.getCounterFromName(ctr.getName()); - if (cn != null) { - holder.set(cn.getGroupName(), cn.getName(), ctr.getValue()); - } - } - } - return holder; - } - - private void addJobToRetryList(MapReduceApplicationData job) { - if (_failedJobsInProgress.containsKey(job)) { - // This is old retry job - int retryLeft = _failedJobsInProgress.get(job); - _failedJobsInProgress.remove(job); - if (retryLeft == 0) { - // Drop job on max retries - logger.error("Drop job. Reason: reach max retry for job id=" + job.getJobId()); - } else { - // Put this job back to failed job waiting list to be fetched again - _failedJobsInWait.put(job, retryLeft); - } - } else { - // This is new job to retry, add to wait list - _failedJobsInWait.put(job, DEFAULT_RETRY); - } - } - - // Return false if this is a real nodata received job ( 0 mapper & 0 reducer ) rather than a retired job - private boolean checkRetiredAndFetchJobData(MapReduceApplicationData jobData) - throws IOException, AuthenticationException { - String jobUrl = _jobtrackerHttpRoot + "jobdetails.jsp?jobid=" + jobData.getJobId(); - Document doc = ThreadContextMR1.fetchHtmlDoc(jobUrl); - Elements hrefs = doc.select("a"); - String mapperUrl = null; - String reducerUrl = null; - String confUrl = null; - - // Parse all hrefs in job main page to find job conf page and mapper/reducer main page - for (Element href : hrefs) { - String hrefStr = href.attr("href"); - if (hrefStr.contains("jobconf_history.jsp")) { - confUrl = _jobtrackerHttpRoot + hrefStr; - } - if (hrefStr.contains("taskType=MAP&status=SUCCESS")) { - mapperUrl = _jobtrackerHttpRoot + hrefStr; - } - if (hrefStr.contains("taskType=REDUCE&status=SUCCESS")) { - reducerUrl = _jobtrackerHttpRoot + hrefStr; - } - } - - if (mapperUrl == null || reducerUrl == null || confUrl == null) { - // This is an real no data received job - return false; - } - - MapReduceCounterHolder jobCounter = fetchJobCounterForRetiredJob(doc); - Properties jobConf = fetchJobConfFromURL(confUrl); - MapReduceTaskData[] mapperData = fetchAllTaskDataForRetiredJob(mapperUrl, true); - MapReduceTaskData[] reducerData = fetchAllTaskDataForRetiredJob(reducerUrl, false); - - long startTime = fetchStartTimeForRetiredJob(doc, jobUrl); - // We can scrape the job tracker page to get the finish time, but in order to be consistent with the - // non-retired jobs, we set the finish time to be same as start time here. - long finishTime = startTime; - String username = jobConf.getProperty("user.name"); - String jobName = jobConf.getProperty("mapred.job.name"); - jobData.setUsername(username).setStartTime(startTime).setFinishTime(finishTime).setUrl(jobUrl).setJobName(jobName); - jobData.setCounters(jobCounter).setJobConf(jobConf).setMapperData(mapperData).setReducerData(reducerData); - return true; - } - - // Fetch job counter from job's main page - private MapReduceCounterHolder fetchJobCounterForRetiredJob(Document doc) - throws IOException, AuthenticationException { - MapReduceCounterHolder holder = new MapReduceCounterHolder(); - Elements rows = doc.select("table").select("tr"); - for (Element row : rows) { - Elements cells = row.select("> td"); - if (cells.size() == 5) { - String countername = cells.get(1).text().trim(); - CounterName cn = CounterName.getCounterFromDisplayName(countername); - if (cn != null) { - long value = Long.parseLong(cells.get(4).text().trim().replace(",", "")); - holder.set(cn.getGroupName(), cn.getName(), value); - } - } else if (cells.size() == 4) { - String countername = cells.get(0).text().trim(); - CounterName cn = CounterName.getCounterFromDisplayName(countername); - if (cn != null) { - long value = Long.parseLong(cells.get(3).text().trim().replace(",", "")); - holder.set(cn.getGroupName(), cn.getName(), value); - } - } - } - return holder; - } - - // Fetch task counter from task's counter page - public MapReduceCounterHolder fetchTaskCounterForRetiredJob(String taskCounterUrl) - throws IOException, AuthenticationException { - MapReduceCounterHolder holder = new MapReduceCounterHolder(); - Document doc = ThreadContextMR1.fetchHtmlDoc(taskCounterUrl); - Elements rows = doc.select("table").select("tr"); - for (Element row : rows) { - Elements cells = row.select("> td"); - if (cells.size() == 3) { - String countername = cells.get(1).text().trim(); - CounterName cn = CounterName.getCounterFromDisplayName(countername); - if (cn != null) { - long value = Long.parseLong(cells.get(2).text().trim().replace(",", "")); - holder.set(cn.getGroupName(), cn.getName(), value); - } - } - } - return holder; - } - - // We fetch the start time of job's Setup task as the job's start time, shown in job's main page - private long fetchStartTimeForRetiredJob(Document doc, String jobUrl) - throws IOException { - Elements rows = doc.select("table").select("tr"); - for (Element row : rows) { - Elements cells = row.select("> td"); - if (cells.size() == 7 && cells.get(0).text().trim().equals("Setup")) { - SimpleDateFormat dateFormat = new SimpleDateFormat("d-MMM-yyyy HH:mm:ss"); - try { - long time = dateFormat.parse(cells.get(5).text().trim()).getTime(); - return time; - } catch (ParseException e) { - throw new IOException("Error in fetching start time data from job page in URL : " + jobUrl); - } - } - } - throw new IOException("Unable to fetch start time data from job page in URL : " + jobUrl); - } - - private MapReduceTaskData[] fetchAllTaskDataForRetiredJob(String taskIndexPage, boolean isMapper) - throws IOException, AuthenticationException { - List taskList = new ArrayList(); - Document doc = ThreadContextMR1.fetchHtmlDoc(taskIndexPage); - Elements hrefs = doc.select("a"); - for (Element href : hrefs) { - String hrefStr = href.attr("href"); - if (hrefStr.contains("taskdetailshistory.jsp?")) { - String taskUrl = _jobtrackerHttpRoot + hrefStr; - MapReduceTaskData tdata = fetchTaskDataForRetiredJob(taskUrl, isMapper); - taskList.add(tdata); - } - } - return taskList.toArray(new MapReduceTaskData[taskList.size()]); - } - - private MapReduceTaskData fetchTaskDataForRetiredJob(String taskDetailUrl, boolean isMapper) - throws IOException, AuthenticationException { - Document doc = ThreadContextMR1.fetchHtmlDoc(taskDetailUrl); - Elements rows = doc.select("table").select("tr"); - MapReduceTaskData taskData = null; - for (Element row : rows) { - try { - taskData = tryExtractTaskDataForRetiredJob(row, isMapper); - if (taskData != null) { - return taskData; - } - } catch (Exception e) { - throw new IOException("Error in fetch task data from task detail page. TASK URL=" + taskDetailUrl, e); - } - } - throw new IOException("No valid time data found from task detail page. TASK URL=" + taskDetailUrl); - } - - private MapReduceTaskData tryExtractTaskDataForRetiredJob(Element row, boolean isMapper) - throws IOException, ParseException, AuthenticationException { - Elements cells = row.select("> td"); - if (isMapper && cells.size() == 7) { - String c = cells.get(6).text().trim(); - if (c.matches("[^0][0-9]+")) { - long totalTimeMs = parseTimeInMs(cells.get(2).html().trim()); - long[] time = new long[]{totalTimeMs, 0, 0}; - String counterUrl = _jobtrackerHttpRoot + cells.get(6).select("a").attr("href"); - MapReduceCounterHolder ctrholder = fetchTaskCounterForRetiredJob(counterUrl); - return new MapReduceTaskData(ctrholder, time); - } - } else if (!isMapper && cells.size() == 9) { - String c = cells.get(8).text().trim(); - if (c.matches("[^0][0-9]+")) { - long shuffleTimeMs = parseTimeInMs(cells.get(2).html().trim()); - long sortTimeMs = parseTimeInMs(cells.get(3).html().trim()); - long totalTimeMs = parseTimeInMs(cells.get(4).html().trim()); - long[] time = new long[]{totalTimeMs, shuffleTimeMs, sortTimeMs}; - String counterUrl = _jobtrackerHttpRoot + cells.get(8).select("a").attr("href"); - MapReduceCounterHolder ctrholder = fetchTaskCounterForRetiredJob(counterUrl); - return new MapReduceTaskData(ctrholder, time); - } - } - return null; - } - - // Return time in ms from input date "Xhrs,Xmins,Xsecs" - private long parseTimeInMs(String rawDateStr) { - int hour = 0; - int min = 0; - int sec = 0; - String dateStr = rawDateStr.substring(rawDateStr.indexOf('(') + 1, rawDateStr.indexOf(')')); - String[] parts = dateStr.split(","); - for (String part : parts) { - if (part.endsWith("hrs")) { - hour = Integer.parseInt(part.substring(0, part.indexOf('h')).trim()); - } else if (part.endsWith("mins")) { - min = Integer.parseInt(part.substring(0, part.indexOf('m')).trim()); - } else if (part.endsWith("sec")) { - sec = Integer.parseInt(part.substring(0, part.indexOf('s')).trim()); - } - } - return hour * Statistics.HOUR_IN_MS + min * Statistics.MINUTE_IN_MS + sec * Statistics.SECOND_IN_MS; - } -} - -final class ThreadContextMR1 { - private static final AtomicInteger THREAD_ID = new AtomicInteger(1); - - private static final Logger logger = Logger.getLogger(ThreadContextMR1.class); - private static final ThreadLocal _LOCAL_THREAD_ID = new ThreadLocal() { - @Override - protected Integer initialValue() { - return THREAD_ID.getAndIncrement(); - } - }; - - private static final ThreadLocal _LOCAL_JOB_CLIENT = new ThreadLocal() { - @Override - protected JobClient initialValue() { - JobClient client = null; - try { - client = new JobClient(new JobConf()); - } catch (IOException e) { - throw new RuntimeException("Exception occurred while initializing the job client.", e); - } - - return client; - } - }; - - private static final ThreadLocal _LOCAL_LAST_UPDATED = new ThreadLocal(); - private static final ThreadLocal _LOCAL_UPDATE_INTERVAL = new ThreadLocal(); - private static final ThreadLocal _LOCAL_AUTH_TOKEN = - new ThreadLocal() { - @Override - protected AuthenticatedURL.Token initialValue() { - _LOCAL_LAST_UPDATED.set(System.currentTimeMillis()); - _LOCAL_UPDATE_INTERVAL - .set(Statistics.MINUTE_IN_MS * 30 + new Random().nextLong() % (3 * Statistics.MINUTE_IN_MS)); - logger.info("Executor " + _LOCAL_THREAD_ID.get() + " update interval " - + _LOCAL_UPDATE_INTERVAL.get() * 1.0 / Statistics.MINUTE_IN_MS); - return new AuthenticatedURL.Token(); - } - }; - - private static final ThreadLocal _LOCAL_AUTH_URL = new ThreadLocal() { - @Override - protected AuthenticatedURL initialValue() { - return new AuthenticatedURL(); - } - }; - - private ThreadContextMR1() { - } - - public static JobClient getClient() { - return _LOCAL_JOB_CLIENT.get(); - } - - public static Document fetchHtmlDoc(String url) - throws IOException, AuthenticationException { - HttpURLConnection conn = _LOCAL_AUTH_URL.get().openConnection(new URL(url), _LOCAL_AUTH_TOKEN.get()); - Document doc = Jsoup.parse(IOUtils.toString(conn.getInputStream())); - return doc; - } - - public static void updateAuthToken() { - long curTime = System.currentTimeMillis(); - if (curTime - _LOCAL_LAST_UPDATED.get() > _LOCAL_UPDATE_INTERVAL.get()) { - logger.info("Executor " + _LOCAL_THREAD_ID.get() + " updates its AuthenticatedToken."); - _LOCAL_AUTH_TOKEN.set(new AuthenticatedURL.Token()); - _LOCAL_AUTH_URL.set(new AuthenticatedURL()); - _LOCAL_LAST_UPDATED.set(curTime); - } - } -} diff --git a/app/com/linkedin/drelephant/util/HeuristicConfiguration.java b/app/com/linkedin/drelephant/util/HeuristicConfiguration.java index 32845ff67..bbbab6bd7 100644 --- a/app/com/linkedin/drelephant/util/HeuristicConfiguration.java +++ b/app/com/linkedin/drelephant/util/HeuristicConfiguration.java @@ -16,7 +16,6 @@ package com.linkedin.drelephant.util; import com.linkedin.drelephant.analysis.ApplicationType; -import com.linkedin.drelephant.analysis.HadoopSystemContext; import java.util.ArrayList; import java.util.List; @@ -79,13 +78,6 @@ private void parseHeuristicConfiguration(Element configuration) { throw new RuntimeException("Empty tag 'viewname' in heuristic " + n + " classname " + className); } - Node versionsNode = heuristicNode.getElementsByTagName("hadoopversions").item(0); - if (versionsNode == null || versionsNode.getNodeType() != Node.ELEMENT_NODE) { - throw new RuntimeException( - "No tag or invalid tag 'hadoopversions' in heuristic " + n + " classname " + className); - } - NodeList versionList = ((Element) versionsNode).getElementsByTagName("version"); - Node appTypeNode = heuristicNode.getElementsByTagName("applicationtype").item(0); if (appTypeNode == null) { throw new RuntimeException( @@ -99,20 +91,10 @@ private void parseHeuristicConfiguration(Element configuration) { } ApplicationType appType = new ApplicationType(appTypeStr); - for (int j = 0; j < versionList.getLength(); j++) { - String version = versionList.item(j).getTextContent(); - int majorVersion = Utils.getMajorVersionFromString(version); - if (HadoopSystemContext.matchCurrentHadoopVersion(majorVersion)) { - HeuristicConfigurationData heuristicData = - new HeuristicConfigurationData(heuristicName, className, viewName, appType); - _heuristicsConfDataList.add(heuristicData); - break; - } else { - logger.warn( - "Ignoring Heuristic: " + heuristicName + " className: " + className + "because it's Hadoop version: " - + majorVersion + " does not match the current Hadoop version."); - } - } + HeuristicConfigurationData heuristicData = new HeuristicConfigurationData(heuristicName, className, viewName, + appType); + _heuristicsConfDataList.add(heuristicData); + } } } diff --git a/app/com/linkedin/drelephant/util/Utils.java b/app/com/linkedin/drelephant/util/Utils.java index 1e7960d7c..c04cfc09b 100644 --- a/app/com/linkedin/drelephant/util/Utils.java +++ b/app/com/linkedin/drelephant/util/Utils.java @@ -19,8 +19,6 @@ import com.linkedin.drelephant.ElephantContext; import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; -import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; @@ -153,26 +151,6 @@ public static Document loadXMLDoc(String filePath) { return document; } - /** - * Given a version string e.g.: 1.2.3, return the corresponding major version number - * - * @param versionString the version string - * @return the major version number - */ - public static int getMajorVersionFromString(String versionString) { - if (versionString == null) { - throw new IllegalArgumentException("Cannot get major version from null."); - } - - Matcher matcher = VERSION_PATTERN.matcher(versionString); - if (!matcher.matches() || matcher.groupCount() < 1) { - throw new IllegalArgumentException( - "Version string [" + versionString + "] does not match pattern + [" + VERSION_PATTERN + "]."); - } - - return Integer.parseInt(matcher.group(1)); - } - /** * Parse a java option string in the format of "-Dfoo=bar -Dfoo2=bar ..." into a {optionName -> optionValue} map. * diff --git a/app/views/help/mapreduce/helpMapperTime.scala.html b/app/views/help/mapreduce/helpMapperTime.scala.html index f48eea3fa..e94b379bc 100644 --- a/app/views/help/mapreduce/helpMapperTime.scala.html +++ b/app/views/help/mapreduce/helpMapperTime.scala.html @@ -59,8 +59,7 @@

Suggestions

You should tune mapper split size to reduce number of mappers and let each mapper process larger data
The parameter for changing split size is:
    -
  • mapred.max/min.split.size (Hadoop 1)
  • -
  • mapreduce.input.fileinputformat.split.minsize/maxsize (Hadoop 2)
  • +
  • mapreduce.input.fileinputformat.split.minsize/maxsize
  • pig.maxCombinedSplitSize (Pig Only)
Examples on how to set them: diff --git a/app/views/help/mapreduce/helpShuffleSort.scala.html b/app/views/help/mapreduce/helpShuffleSort.scala.html index a9377b41b..82097563b 100644 --- a/app/views/help/mapreduce/helpShuffleSort.scala.html +++ b/app/views/help/mapreduce/helpShuffleSort.scala.html @@ -51,7 +51,7 @@

Shuffle & Sort

Suggestions

- If your shuffle time is high but sort is low, you likely need to tune slowstart by setting "mapred.reduce.slowstart.completed.maps"(hadoop1) or "mapreduce.job.reduce.slowstart.completedmaps"(hadoop2) to 0.90+ up to 1.0
+ If your shuffle time is high but sort is low, you likely need to tune slowstart by setting "mapreduce.job.reduce.slowstart.completedmaps" to 0.90+ up to 1.0
Warning: Setting this parameter close to 1.0 will decrease shuffle time, but may increase this job's total runtime

If you want to set it to 0.95 on Nertz:
diff --git a/start.sh b/start.sh index c841f9b7b..f23050a80 100755 --- a/start.sh +++ b/start.sh @@ -114,8 +114,7 @@ fi HADOOP_VERSION=$(hadoop version | awk '{if (NR == 1) {print $2;}}') if [[ $HADOOP_VERSION == 1* ]]; then - JAVA_LIB_PATH=$HADOOP_HOME"/lib/native/Linux-amd64-64" - echo "This is hadoop1.x grid. Add Java library path: "$JAVA_LIB_PATH + echo "This is hadoop1.x grid. Switch to hadoop2 if you want to use Dr. Elephant" elif [[ $HADOOP_VERSION == 2* ]]; then JAVA_LIB_PATH=$HADOOP_HOME"/lib/native" diff --git a/test/com/linkedin/drelephant/util/UtilsTest.java b/test/com/linkedin/drelephant/util/UtilsTest.java index b98f8bffd..07c1b724a 100644 --- a/test/com/linkedin/drelephant/util/UtilsTest.java +++ b/test/com/linkedin/drelephant/util/UtilsTest.java @@ -35,28 +35,4 @@ public void testParseJavaOptions() { assertEquals("bar2", options2.get("foo2")); assertEquals("bar3", options2.get("foo3")); } - - public void testGetMajorVersionFromString() { - assertEquals(1, Utils.getMajorVersionFromString("1")); - assertEquals(2, Utils.getMajorVersionFromString("2.1")); - assertEquals(2, Utils.getMajorVersionFromString("2.1.3")); - assertEquals(2, Utils.getMajorVersionFromString("2.1.3-LI0")); - - // Negative cases - testGetMajorVersionFromStringNegative(null); - testGetMajorVersionFromStringNegative(".1"); - testGetMajorVersionFromStringNegative("-LI0"); - testGetMajorVersionFromStringNegative("a"); - testGetMajorVersionFromStringNegative("1.a"); - } - - private void testGetMajorVersionFromStringNegative(String input){ - // Expecting IllegalArgumentException - try { - Utils.getMajorVersionFromString(input); - fail(); - } catch (IllegalArgumentException e) { - // The exception is expected, do nothing - } - } } From ce4abb4b61e3538eacaf42df727e4eb597019392 Mon Sep 17 00:00:00 2001 From: Akshay Rai Date: Mon, 4 Jan 2016 12:18:58 +0530 Subject: [PATCH 123/272] LIHADOOP-9004 Support Hadoop 2 properties in Dr. Elephant RB=635540 G=superfriends-reviewers R=annag,fli,rratti,viramach A=annag,fli --- app/views/help/mapreduce/helpGC.scala.html | 4 +- .../help/mapreduce/helpMapperSpeed.scala.html | 12 +++--- .../help/mapreduce/helpMapperSpill.scala.html | 8 +++- .../help/mapreduce/helpMapperTime.scala.html | 41 +++++++++---------- .../help/mapreduce/helpReducerTime.scala.html | 12 +++--- 5 files changed, 42 insertions(+), 35 deletions(-) diff --git a/app/views/help/mapreduce/helpGC.scala.html b/app/views/help/mapreduce/helpGC.scala.html index fe486ccaa..4320b0f83 100644 --- a/app/views/help/mapreduce/helpGC.scala.html +++ b/app/views/help/mapreduce/helpGC.scala.html @@ -37,7 +37,7 @@

Mapper/Reducer GC

2321 - Avg task CPU time (ms) + Avg task GC time (ms) 55 @@ -54,4 +54,4 @@

Suggestions

If your task spends a lot time on GC, your job generally needs some code optimization. There is no one-size-fits-all answer. We are still improving this heuristic. We set the bar quite loose to avoid this heuristic from bothering you as much as possible. If you still get yellow/red on this one, please take care of it seriously. -

\ No newline at end of file +

diff --git a/app/views/help/mapreduce/helpMapperSpeed.scala.html b/app/views/help/mapreduce/helpMapperSpeed.scala.html index 6e93d798c..c8fd5841d 100644 --- a/app/views/help/mapreduce/helpMapperSpeed.scala.html +++ b/app/views/help/mapreduce/helpMapperSpeed.scala.html @@ -18,7 +18,8 @@ This should allow you to determine if your mapper is CPU-bound or if your mapper is outputting huge amounts of data.

- This result of the analysis shows problems with mappers with significant slow speeds for the amount of data it needs to read. + This result of the analysis shows problems with mappers with significant slow speeds for the amount of data it needs + to read.

Example

@@ -53,9 +54,10 @@

Suggestions

If your mappers are CPU bound (Average task speed ~KB/s), then your mappers are performing significant CPU work, and you should consider optimizing your mapper code or check for inefficiencies in code. Alternatively, in rare - cases, it may help to reduce the maximum size of input that each mapper can process.
+ cases, it may help to reduce the size of input that each mapper can process.

- The maximum map split size is controlled by the "mapred.max.split.size" parameter. By decreasing this value - below dfs.block.size, you can reduce the maximum input size for each mapper, thereby increase the number of mappers - in your job.
+ The maximum map split size is controlled by the FileInputFormat.SPLIT_MAXSIZE + ("mapreduce.input.fileinputformat.split.maxsize") parameter. By + decreasing this value below dfs.block.size, you can reduce the input size for each mapper, thereby increase the + number of mappers in your job.

diff --git a/app/views/help/mapreduce/helpMapperSpill.scala.html b/app/views/help/mapreduce/helpMapperSpill.scala.html index 457200d42..6256c684d 100644 --- a/app/views/help/mapreduce/helpMapperSpill.scala.html +++ b/app/views/help/mapreduce/helpMapperSpill.scala.html @@ -14,7 +14,13 @@ * the License. *@

- This heuristic gauges your mapper performance in a disk I/0 perspective. Mapper spill ratio (spilled records/output records) is a critical indicator to your mapper performance: if the ratio is close to 2, it means each record is spilled to disk twice(once when in-memory sort buffer is almost full, once when merging spilled splits). This usually happens when your mappers have large amount of outputs. If the ratio is higher than 2, we suggest you try our recommendation below. Having large disk I/O wasted on sorting output records could seriously affect your mapper speed. To make it run faster, try our tentative recommendation. We newly enabled this heuristic and want you to test it!
+ This heuristic gauges your mapper performance in a disk I/0 perspective. Mapper spill ratio (spilled records/output + records) is a critical indicator to your mapper performance: if the ratio is close to 2, it means each record is + spilled to disk twice(once when in-memory sort buffer is almost full, once when merging spilled splits). This + usually happens when your mappers have large amount of outputs. If the ratio is higher than 2, we suggest you try + our recommendation below. Having large disk I/O wasted on sorting output records could seriously affect your mapper + speed. To make it run faster, try our tentative recommendation. We newly enabled this heuristic and want you to test + it!

diff --git a/app/views/help/mapreduce/helpMapperTime.scala.html b/app/views/help/mapreduce/helpMapperTime.scala.html index e94b379bc..2ba8a6ee2 100644 --- a/app/views/help/mapreduce/helpMapperTime.scala.html +++ b/app/views/help/mapreduce/helpMapperTime.scala.html @@ -22,9 +22,9 @@

Mapper time too short

This usually happens when the Hadoop job has:

    -
  • A large number of mappers
  • -
  • Short mapper avg runtime
  • -
  • Small file size
  • +
  • A large number of mappers
  • +
  • Short mapper avg runtime
  • +
  • Small file size

Example
@@ -54,36 +54,36 @@

Mapper Input Size

Suggestions

- -
You should tune mapper split size to reduce number of mappers and let each mapper process larger data
- The parameter for changing split size is:
+ The parameters for changing split size are:

  • mapreduce.input.fileinputformat.split.minsize/maxsize
  • pig.maxCombinedSplitSize (Pig Only)
Examples on how to set them:
    -
  • HadoopJava: conf.setLong("mapred.min.split.size",XXXXX)
  • -
  • Pig: set mapred.min.split.size XXXXX
  • -
  • Hive: set mapred.min.split.size=XXXXX
  • +
  • HadoopJava: conf.setLong("mapreduce.input.fileinputformat.split.minsize", XXXXX)
  • +
  • Pig: set mapreduce.input.fileinputformat.split.minsize XXXXX
  • +
  • Hive: set mapreduce.input.fileinputformat.split.minsize=XXXXX
- The split size is controlled by formula max(minSplitSize, min(maxSplitSize, blockSize)). By default, blockSize=512MB and minSplit < blockSize < maxSplit.
+ The split size is controlled by formula max(minSplitSize, min(maxSplitSize, blockSize)). By default, + blockSize=512MB and minSplit < blockSize < maxSplit.
You should always refer to this formula.
- In the case above, try increase min split size and let each mapper process larger data.
- [Note] By default HadoopJava will not combine small files, so each mapper cannot process more than one file, and changing split size won't help. If that is your case, you should either try CombineFileInputFormat or use Pig/Hive. + In the case above, try increasing min split size and let each mapper process larger data.
+ [Note] By default HadoopJava will not combine small files, so each mapper cannot process more than + one file, and changing split size won't help. If that is your case, you should either try CombineFileInputFormat or + use Pig/Hive.
See Hadoop Tuning Tips for further information.
-

Large files/Unsplittable files

This usually happens when the Hadoop job has:

    -
  • A small number of mappers
  • -
  • Long mapper avg runtime
  • -
  • Large file size (a few GB's)
  • +
  • A small number of mappers
  • +
  • Long mapper avg runtime
  • +
  • Large file size (a few GB's)

Example
@@ -113,12 +113,11 @@

Mapper Input Size

Suggestions

- The split size is too large. You should tune mapper split size to increase number of mappers and let each mapper process less data.
+ The split size is too large. You should tune mapper split size to increase number of mappers and let each mapper + process less data.

- The input split size is controlled by formula max(minSplitSize, min(maxSplitSize, blockSize)).
- See the previous section for further details.
- + The input split size is controlled by formula max(minSplitSize, min(maxSplitSize, blockSize)). See the + previous section for further details.
In the case above, since mapper input size >> block size and you want to increase mappers, you should decrease min split size close to BlockSize(512MB).
- See Hadoop Tuning Tips for further information.

diff --git a/app/views/help/mapreduce/helpReducerTime.scala.html b/app/views/help/mapreduce/helpReducerTime.scala.html index 24df9dc49..1776950c4 100644 --- a/app/views/help/mapreduce/helpReducerTime.scala.html +++ b/app/views/help/mapreduce/helpReducerTime.scala.html @@ -22,8 +22,8 @@

Too many reducers

This happens when the Hadoop job has:

    -
  • A large number of reducers
  • -
  • Short reducer runtime
  • +
  • A large number of reducers
  • +
  • Short reducer runtime

Example
@@ -51,8 +51,8 @@

Too few reducers

This happens when the Hadoop job has:

    -
  • A small number of reducers
  • -
  • Long reducer runtime
  • +
  • A small number of reducers
  • +
  • Long reducer runtime

Example
@@ -82,8 +82,8 @@

Suggestions


For Hadoop/Java jobs: Use "jobConf.setNumReduceTasks(NUMBER_OF_REDUCERS);"
For Apache-Pig jobs: Use PARALLEL [num] clause on the operator which caused this job (Though this will probably be hard for people to understand without Lipstick)
- For Apache-Hive jobs: Use "set mapred.reduce.tasks=NUMBER_OF_REDUCERS"
- For Azkaban flows, add jvm.args=-Dmapred.reduce.tasks=NUMBER_OF_REDUCERS to your job properties
+ For Apache-Hive jobs: Use "set mapreduce.job.reduces=NUMBER_OF_REDUCERS"
+ For Azkaban flows, add jvm.args=-Dmapreduce.job.reduces=NUMBER_OF_REDUCERS to your job properties

Generally, Dr. Elephant(and Hadoop team) advises the ideal task time to be 5-10 minutes.
See Hadoop Tuning Tips for further information. From ba92a862652c0bc202060a3c119e6e6d29c60f02 Mon Sep 17 00:00:00 2001 From: Akshay Rai Date: Wed, 6 Jan 2016 15:35:20 +0530 Subject: [PATCH 124/272] LIHADOOP-16687 Update Dr. Elephant to use the new Hadoop API RB=636803 G=superfriends-reviewers R=annag,fli,rratti,viramach A=rratti --- .../linkedin/drelephant/ElephantRunner.java | 4 ++-- .../linkedin/drelephant/HadoopSecurity.java | 3 +-- .../analysis/HadoopSystemContext.java | 20 ++----------------- .../mapreduce/MapReduceFetcherHadoop2.java | 4 ++-- build.sbt | 4 +--- 5 files changed, 8 insertions(+), 27 deletions(-) diff --git a/app/com/linkedin/drelephant/ElephantRunner.java b/app/com/linkedin/drelephant/ElephantRunner.java index 4409348be..319ffe852 100644 --- a/app/com/linkedin/drelephant/ElephantRunner.java +++ b/app/com/linkedin/drelephant/ElephantRunner.java @@ -33,7 +33,7 @@ import model.JobResult; import org.apache.commons.lang.exception.ExceptionUtils; -import org.apache.hadoop.mapred.JobConf; +import org.apache.hadoop.conf.Configuration; import org.apache.log4j.Logger; @@ -49,7 +49,7 @@ public class ElephantRunner implements Runnable { private AnalyticJobGenerator _analyticJobGenerator; private void loadAnalyticJobGenerator() { - JobConf configuration = new JobConf(); + Configuration configuration = new Configuration(); if (HadoopSystemContext.isHadoop2Env()) { _analyticJobGenerator = new AnalyticJobGeneratorHadoop2(); } else { diff --git a/app/com/linkedin/drelephant/HadoopSecurity.java b/app/com/linkedin/drelephant/HadoopSecurity.java index 61700c08f..744b82831 100644 --- a/app/com/linkedin/drelephant/HadoopSecurity.java +++ b/app/com/linkedin/drelephant/HadoopSecurity.java @@ -16,7 +16,6 @@ package com.linkedin.drelephant; import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.mapred.JobConf; import org.apache.hadoop.security.UserGroupInformation; import org.apache.log4j.Logger; @@ -36,7 +35,7 @@ public class HadoopSecurity { private boolean _securityEnabled = false; public HadoopSecurity() throws IOException { - Configuration conf = new JobConf(); + Configuration conf = new Configuration(); UserGroupInformation.setConfiguration(conf); _securityEnabled = UserGroupInformation.isSecurityEnabled(); if (_securityEnabled) { diff --git a/app/com/linkedin/drelephant/analysis/HadoopSystemContext.java b/app/com/linkedin/drelephant/analysis/HadoopSystemContext.java index 1e40cc16c..5ed366cd9 100644 --- a/app/com/linkedin/drelephant/analysis/HadoopSystemContext.java +++ b/app/com/linkedin/drelephant/analysis/HadoopSystemContext.java @@ -16,28 +16,12 @@ package com.linkedin.drelephant.analysis; import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.mapred.JobConf; public final class HadoopSystemContext { private static final String MAPREDUCE_FRAMEWORK_NAME_PROP = "mapreduce.framework.name"; private static final String MAPRED_JOB_TRACKER_PROP = "mapred.job.tracker.http.address"; private static final String YARN = "yarn"; - private static final String CLASSIC = "classic"; - - /** - * Detect if the current Hadoop environment is 1.x - * - * @return true if it is Hadoop 1 env, else false - */ - public static boolean isHadoop1Env() { - Configuration hadoopConf = new JobConf(); - String hadoopVersion = hadoopConf.get(MAPREDUCE_FRAMEWORK_NAME_PROP); - if (hadoopVersion == null) { - return hadoopConf.get(MAPRED_JOB_TRACKER_PROP) != null; - } - return hadoopVersion.equals(CLASSIC); - } /** * Detect if the current Hadoop environment is 2.x @@ -45,7 +29,7 @@ public static boolean isHadoop1Env() { * @return true if it is Hadoop 2 env, else false */ public static boolean isHadoop2Env() { - Configuration hadoopConf = new JobConf(); + Configuration hadoopConf = new Configuration(); String hadoopVersion = hadoopConf.get(MAPREDUCE_FRAMEWORK_NAME_PROP); return hadoopVersion != null && hadoopVersion.equals(YARN); } @@ -57,6 +41,6 @@ public static boolean isHadoop2Env() { * @return true if we have a major version match else false */ public static boolean matchCurrentHadoopVersion(int majorVersion) { - return majorVersion == 2 && isHadoop2Env() || majorVersion == 1 && isHadoop1Env(); + return majorVersion == 2 && isHadoop2Env(); } } diff --git a/app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop2.java b/app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop2.java index 36c0321e6..981106ce2 100644 --- a/app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop2.java +++ b/app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop2.java @@ -33,7 +33,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.apache.hadoop.mapred.JobConf; +import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.security.authentication.client.AuthenticatedURL; import org.apache.hadoop.security.authentication.client.AuthenticationException; import org.apache.log4j.Logger; @@ -51,7 +51,7 @@ public class MapReduceFetcherHadoop2 implements ElephantFetcher Date: Mon, 21 Dec 2015 18:02:18 +0530 Subject: [PATCH 125/272] LIHADOOP-16576 Make severity threshold values configurable for the heuristics. RB=635070 G=superfriends-reviewers R=annag,fli,rratti,shanm,viramach A=fli --- README.md | 27 +++-- app-conf/FetcherConf.xml | 5 + app-conf/HeuristicConf.xml | 112 ++++++++++++++++-- .../linkedin/drelephant/ElephantContext.java | 98 +++++++-------- .../mapreduce/MapReduceFetcherHadoop2.java | 6 +- .../heuristics/ExceptionHeuristic.java | 7 ++ .../heuristics/GenericDataSkewHeuristic.java | 80 +++++++++++-- .../heuristics/GenericGCHeuristic.java | 57 ++++++++- .../heuristics/GenericMemoryHeuristic.java | 58 ++++++++- .../heuristics/JobQueueLimitHeuristic.java | 7 ++ .../heuristics/MapperDataSkewHeuristic.java | 5 +- .../heuristics/MapperGCHeuristic.java | 5 +- .../heuristics/MapperMemoryHeuristic.java | 5 +- .../heuristics/MapperSpeedHeuristic.java | 63 +++++++++- .../heuristics/MapperSpillHeuristic.java | 61 +++++++++- .../heuristics/MapperTimeHeuristic.java | 72 ++++++++++- .../heuristics/ReducerDataSkewHeuristic.java | 5 +- .../heuristics/ReducerGCHeuristic.java | 5 +- .../heuristics/ReducerMemoryHeuristic.java | 5 +- .../heuristics/ReducerTimeHeuristic.java | 72 ++++++++++- .../heuristics/ShuffleSortHeuristic.java | 58 ++++++++- .../linkedin/drelephant/math/Statistics.java | 4 - .../BestPropertiesConventionHeuristic.java | 59 ++++++++- .../heuristics/EventLogLimitHeuristic.java | 6 + .../heuristics/ExecutorLoadHeuristic.java | 59 ++++++++- .../spark/heuristics/JobRuntimeHeuristic.java | 56 ++++++++- .../heuristics/MemoryLimitHeuristic.java | 57 ++++++++- .../heuristics/StageRuntimeHeuristic.java | 77 ++++++++++-- .../drelephant/util/FetcherConfiguration.java | 110 +++++++++++++++++ .../util/FetcherConfigurationData.java | 47 ++++++++ .../util/HeuristicConfiguration.java | 19 ++- .../util/HeuristicConfigurationData.java | 10 +- app/com/linkedin/drelephant/util/Utils.java | 39 ++++++ .../spark/deploy/history/SparkFSFetcher.scala | 28 +++-- .../JobQueueLimitHeuristicTest.java | 9 +- .../MapperDataSkewHeuristicTest.java | 10 +- .../heuristics/MapperGCHeuristicTest.java | 11 +- .../heuristics/MapperMemoryHeuristicTest.java | 11 +- .../heuristics/MapperSpeedHeuristicTest.java | 10 +- .../heuristics/MapperSpillHeuristicTest.java | 11 +- .../heuristics/MapperTimeHeuristicTest.java | 10 +- .../ReducerDataSkewHeuristicTest.java | 11 +- .../heuristics/ReducerGCHeuristicTest.java | 10 +- .../ReducerMemoryHeuristicTest.java | 10 +- .../heuristics/ReducerTimeHeuristicTest.java | 11 +- .../heuristics/ShuffleSortHeuristicTest.java | 11 +- ...BestPropertiesConventionHeuristicTest.java | 11 +- .../heuristics/MemoryLimitHeuristicTest.java | 9 +- .../linkedin/drelephant/util/UtilsTest.java | 26 ++++ 49 files changed, 1361 insertions(+), 194 deletions(-) create mode 100644 app/com/linkedin/drelephant/util/FetcherConfiguration.java create mode 100644 app/com/linkedin/drelephant/util/FetcherConfigurationData.java diff --git a/README.md b/README.md index 0725ebb3c..e9ffcd502 100644 --- a/README.md +++ b/README.md @@ -121,16 +121,29 @@ cd /export/apps/elephant/ * **classname**: Fully qualified name of the class. * **viewname**: Fully qualified name of the view. * **hadoopversions**: Versions of Hadoop with which the heuristic is compatible. + * Optionally, if you wish to override the threshold values of the severities used in the Heuristic and use custom + threshold limits, you can specify them in the HeuristicConf.xml between params tag. See examples below. * A sample entry in HeuristicConf.xml would look like, ``` - mapreduce - Job Queue Limit - com.linkedin.drelephant.mapreduce.heuristics.JobQueueLimitHeuristic - views.html.helpJobQueueLimit - - 1 - + mapreduce + Job Queue Limit + com.linkedin.drelephant.mapreduce.heuristics.JobQueueLimitHeuristic + views.html.helpJobQueueLimit + +``` +* A sample entry showing how to override/configure severity thresholds would look like, +``` + + mapreduce + Mapper Data Skew + com.linkedin.drelephant.mapreduce.heuristics.MapperDataSkewHeuristic + views.html.help.mapreduce.helpMapperDataSkew + + 10, 50, 100, 200 + 2, 4, 8, 16 + 1/8, 1/4, 1/2, 1 + ``` * Run Doctor Elephant, it should now include the new heuristics. diff --git a/app-conf/FetcherConf.xml b/app-conf/FetcherConf.xml index 3e5bdb738..690dc0f50 100644 --- a/app-conf/FetcherConf.xml +++ b/app-conf/FetcherConf.xml @@ -36,5 +36,10 @@ spark org.apache.spark.deploy.history.SparkFSFetcher + diff --git a/app-conf/HeuristicConf.xml b/app-conf/HeuristicConf.xml index 3d2dca46c..915370576 100644 --- a/app-conf/HeuristicConf.xml +++ b/app-conf/HeuristicConf.xml @@ -14,14 +14,32 @@ License for the specific language governing permissions and limitations under the License. --> - + + + + mapreduce Mapper Data Skew com.linkedin.drelephant.mapreduce.heuristics.MapperDataSkewHeuristic views.html.help.mapreduce.helpMapperDataSkew + + + + + mapreduce + Mapper GC + com.linkedin.drelephant.mapreduce.heuristics.MapperGCHeuristic + views.html.help.mapreduce.helpGC + @@ -29,6 +47,11 @@ Mapper Time com.linkedin.drelephant.mapreduce.heuristics.MapperTimeHeuristic views.html.help.mapreduce.helpMapperTime + @@ -36,22 +59,32 @@ Mapper Speed com.linkedin.drelephant.mapreduce.heuristics.MapperSpeedHeuristic views.html.help.mapreduce.helpMapperSpeed + -
- --> mapreduce - Mapper GC - com.linkedin.drelephant.mapreduce.heuristics.MapperGCHeuristic - views.html.help.mapreduce.helpGC + Mapper Memory + com.linkedin.drelephant.mapreduce.heuristics.MapperMemoryHeuristic + views.html.help.mapreduce.helpMapperMemory + @@ -59,6 +92,22 @@ Reducer Data Skew com.linkedin.drelephant.mapreduce.heuristics.ReducerDataSkewHeuristic views.html.help.mapreduce.helpReducerDataSkew + + + + + mapreduce + Reducer GC + com.linkedin.drelephant.mapreduce.heuristics.ReducerGCHeuristic + views.html.help.mapreduce.helpGC + @@ -66,20 +115,33 @@ Reducer Time com.linkedin.drelephant.mapreduce.heuristics.ReducerTimeHeuristic views.html.help.mapreduce.helpReducerTime + mapreduce - Shuffle & Sort - com.linkedin.drelephant.mapreduce.heuristics.ShuffleSortHeuristic - views.html.help.mapreduce.helpShuffleSort + Reducer Memory + com.linkedin.drelephant.mapreduce.heuristics.ReducerMemoryHeuristic + views.html.help.mapreduce.helpReducerMemory + mapreduce - Reducer GC - com.linkedin.drelephant.mapreduce.heuristics.ReducerGCHeuristic - views.html.help.mapreduce.helpGC + Shuffle & Sort + com.linkedin.drelephant.mapreduce.heuristics.ShuffleSortHeuristic + views.html.help.mapreduce.helpShuffleSort + @@ -89,11 +151,18 @@ views.html.help.mapreduce.helpException + + + spark Spark Configuration Best Practice com.linkedin.drelephant.spark.heuristics.BestPropertiesConventionHeuristic views.html.help.spark.helpBestProperties + @@ -101,6 +170,10 @@ Spark Memory Limit com.linkedin.drelephant.spark.heuristics.MemoryLimitHeuristic views.html.help.spark.helpMemoryLimit + @@ -108,6 +181,11 @@ Spark Stage Runtime com.linkedin.drelephant.spark.heuristics.StageRuntimeHeuristic views.html.help.spark.helpStageRuntime + @@ -115,6 +193,10 @@ Spark Job Runtime com.linkedin.drelephant.spark.heuristics.JobRuntimeHeuristic views.html.help.spark.helpJobRuntime + @@ -122,6 +204,10 @@ Spark Executor Load Balance com.linkedin.drelephant.spark.heuristics.ExecutorLoadHeuristic views.html.help.spark.helpExecutorLoad + diff --git a/app/com/linkedin/drelephant/ElephantContext.java b/app/com/linkedin/drelephant/ElephantContext.java index 8a890462c..c5251be7e 100644 --- a/app/com/linkedin/drelephant/ElephantContext.java +++ b/app/com/linkedin/drelephant/ElephantContext.java @@ -23,10 +23,13 @@ import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.JobType; +import com.linkedin.drelephant.util.FetcherConfiguration; +import com.linkedin.drelephant.util.FetcherConfigurationData; import com.linkedin.drelephant.util.HeuristicConfiguration; import com.linkedin.drelephant.util.HeuristicConfigurationData; import com.linkedin.drelephant.util.JobTypeConf; import com.linkedin.drelephant.util.Utils; +import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -37,9 +40,6 @@ import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; import play.api.Play; @@ -53,9 +53,6 @@ public class ElephantContext { private static final Logger logger = Logger.getLogger(ElephantContext.class); private static ElephantContext INSTANCE; - private static final String CLASS_NAME_XML_FIELD = "classname"; - private static final String APPLICATION_TYPE_XML_FIELD = "applicationtype"; - private static final String FETCHERS_CONF = "FetcherConf.xml"; private static final String HEURISTICS_CONF = "HeuristicConf.xml"; private static final String JOB_TYPES_CONF = "JobTypeConf.xml"; @@ -63,6 +60,7 @@ public class ElephantContext { private final Map> _heuristicGroupedNames = new HashMap>(); private List _heuristicsConfData; + private List _fetchersConfData; private final Map _nameToType = new HashMap(); private final Map> _typeToHeuristics = @@ -106,74 +104,59 @@ private void loadConfiguration() { configureSupportedApplicationTypes(); } + /** + * Load all the fetchers configured in FetcherConf.xml + */ private void loadFetchers() { - Document document = Utils.loadXMLDoc(FETCHERS_CONF); - NodeList nodes = document.getDocumentElement().getChildNodes(); - for (int i = 0; i < nodes.getLength(); i++) { - Node node = nodes.item(i); - int n = 0; - if (node.getNodeType() == Node.ELEMENT_NODE) { - n++; - Element fetcherNode = (Element) node; - - Node applicationTypeNode = fetcherNode.getElementsByTagName(APPLICATION_TYPE_XML_FIELD).item(0); - if (applicationTypeNode == null) { - throw new RuntimeException("No applicationtype tag presented in fetcher #" + n); - } - - Node classNameNode = fetcherNode.getElementsByTagName(CLASS_NAME_XML_FIELD).item(0); - if (classNameNode == null) { - throw new RuntimeException("No classname tag presented in fetcher #" + n); + _fetchersConfData = new FetcherConfiguration(document.getDocumentElement()).getFetchersConfigurationData(); + for (FetcherConfigurationData data : _fetchersConfData) { + try { + Class fetcherClass = Play.current().classloader().loadClass(data.getClassName()); + Object instance = fetcherClass.getConstructor(FetcherConfigurationData.class).newInstance(data); + if (!(instance instanceof ElephantFetcher)) { + throw new IllegalArgumentException( + "Class " + fetcherClass.getName() + " is not an implementation of " + ElephantFetcher.class.getName()); } - String typeName = applicationTypeNode.getTextContent(); - if (getApplicationTypeForName(typeName) == null) { - ApplicationType type = new ApplicationType(typeName); - - String className = classNameNode.getTextContent(); - try { - Class fetcherClass = Play.current().classloader().loadClass(className); - Object instance = fetcherClass.newInstance(); - if (!(instance instanceof ElephantFetcher)) { - throw new IllegalArgumentException( - "Class " + fetcherClass.getName() + " is not an implementation of " + ElephantFetcher.class - .getName()); - } - _typeToFetcher.put(type, (ElephantFetcher) fetcherClass.newInstance()); - } catch (ClassNotFoundException e) { - throw new RuntimeException("Class" + className + " not found for fetcher #" + n, e); - } catch (InstantiationException e) { - throw new RuntimeException("Could not instantiate class " + className, e); - } catch (IllegalAccessException e) { - throw new RuntimeException("Could not access constructor for class " + className, e); - } - } else { - throw new RuntimeException( - "Given a hadoop version and an application type, there could only be one fetcher. Fetcher #" + n - + " is duplicated with the previous fetchers."); + ApplicationType type = data.getAppType(); + if (_typeToFetcher.get(type) == null) { + _typeToFetcher.put(type, (ElephantFetcher) instance); } + logger.info("Load Fetcher : " + data.getClassName()); + } catch (ClassNotFoundException e) { + throw new RuntimeException("Could not find class " + data.getClassName(), e); + } catch (InstantiationException e) { + throw new RuntimeException("Could not instantiate class " + data.getClassName(), e); + } catch (IllegalAccessException e) { + throw new RuntimeException("Could not access constructor for class" + data.getClassName(), e); + } catch (RuntimeException e) { + throw new RuntimeException(data.getClassName() + " is not a valid Fetcher class.", e); + } catch (InvocationTargetException e) { + throw new RuntimeException("Could not invoke class " + data.getClassName(), e); + } catch (NoSuchMethodException e) { + throw new RuntimeException("Could not find constructor for class " + data.getClassName(), e); } } } + /** + * Load all the heuristics configured in HeuristicConf.xml + */ private void loadHeuristics() { - Document document = Utils.loadXMLDoc(HEURISTICS_CONF); _heuristicsConfData = new HeuristicConfiguration(document.getDocumentElement()).getHeuristicsConfigurationData(); - for (HeuristicConfigurationData data : _heuristicsConfData) { try { Class heuristicClass = Play.current().classloader().loadClass(data.getClassName()); - Object instance = heuristicClass.newInstance(); + Object instance = heuristicClass.getConstructor(HeuristicConfigurationData.class).newInstance(data); if (!(instance instanceof Heuristic)) { throw new IllegalArgumentException( "Class " + heuristicClass.getName() + " is not an implementation of " + Heuristic.class.getName()); } - Heuristic heuristicInstance = (Heuristic) heuristicClass.newInstance(); ApplicationType type = data.getAppType(); List heuristics = _typeToHeuristics.get(type); @@ -181,7 +164,7 @@ private void loadHeuristics() { heuristics = new ArrayList(); _typeToHeuristics.put(type, heuristics); } - heuristics.add(heuristicInstance); + heuristics.add((Heuristic) instance); logger.info("Load Heuristic : " + data.getClassName()); } catch (ClassNotFoundException e) { @@ -191,14 +174,19 @@ private void loadHeuristics() { } catch (IllegalAccessException e) { throw new RuntimeException("Could not access constructor for class" + data.getClassName(), e); } catch (RuntimeException e) { - //More descriptive on other runtime exception such as ClassCastException + // More descriptive on other runtime exception such as ClassCastException throw new RuntimeException(data.getClassName() + " is not a valid Heuristic class.", e); + } catch (InvocationTargetException e) { + throw new RuntimeException("Could not invoke class " + data.getClassName(), e); + } catch (NoSuchMethodException e) { + throw new RuntimeException("Could not find constructor for class " + data.getClassName(), e); } } // Bind No_DATA heuristic to its helper pages, no need to add any real configurations _heuristicsConfData.add( - new HeuristicConfigurationData(HeuristicResult.NO_DATA.getAnalysis(), null, "views.html.help.helpNoData", null)); + new HeuristicConfigurationData(HeuristicResult.NO_DATA.getAnalysis(), null, "views.html.help.helpNoData", null, + null)); } /** diff --git a/app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop2.java b/app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop2.java index 981106ce2..8a859e097 100644 --- a/app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop2.java +++ b/app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop2.java @@ -18,6 +18,7 @@ import com.linkedin.drelephant.analysis.AnalyticJob; import com.linkedin.drelephant.analysis.ElephantFetcher; import com.linkedin.drelephant.math.Statistics; +import com.linkedin.drelephant.util.FetcherConfigurationData; import com.linkedin.drelephant.util.Utils; import java.io.IOException; @@ -48,8 +49,11 @@ public class MapReduceFetcherHadoop2 implements ElephantFetcher { public static final String HEURISTIC_NAME = "Exception"; + private HeuristicConfigurationData _heuristicConfData; + + public ExceptionHeuristic(HeuristicConfigurationData heuristicConfData) { + this._heuristicConfData = heuristicConfData; + } @Override public String getHeuristicName() { diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/GenericDataSkewHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericDataSkewHeuristic.java index 6ac744b78..51957850e 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/GenericDataSkewHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericDataSkewHeuristic.java @@ -24,21 +24,78 @@ import com.linkedin.drelephant.mapreduce.MapReduceTaskData; import com.linkedin.drelephant.math.Statistics; +import com.linkedin.drelephant.util.HeuristicConfigurationData; +import com.linkedin.drelephant.util.Utils; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; import org.apache.commons.io.FileUtils; +import org.apache.log4j.Logger; public abstract class GenericDataSkewHeuristic implements Heuristic { + private static final Logger logger = Logger.getLogger(GenericDataSkewHeuristic.class); + + // Severity Parameters + private static final String NUM_TASKS_SEVERITY = "num_tasks_severity"; + private static final String DEVIATION_SEVERITY = "deviation_severity"; + private static final String FILES_SEVERITY = "files_severity"; + + // Default value of parameters + private double[] numTasksLimits = {10, 50, 100, 200}; // Number of map or reduce tasks + private double[] deviationLimits = {2, 4, 8, 16}; // Deviation in i/p bytes btw 2 groups + private double[] filesLimits = {1d/8, 1d/4, 1d/2, 1d}; // Fraction of HDFS Block Size + private MapReduceCounterHolder.CounterName _counterName; private String _heuristicName; + private HeuristicConfigurationData _heuristicConfData; - @Override - public String getHeuristicName() { - return _heuristicName; + private void loadParameters() { + Map paramMap = _heuristicConfData.getParamMap(); + + if(paramMap.get(NUM_TASKS_SEVERITY) != null) { + double[] confNumTasksThreshold = Utils.getParam(paramMap.get(NUM_TASKS_SEVERITY), numTasksLimits.length); + if (confNumTasksThreshold != null) { + numTasksLimits = confNumTasksThreshold; + } + } + logger.info(_heuristicName + " will use " + NUM_TASKS_SEVERITY + " with the following threshold settings: " + + Arrays.toString(numTasksLimits)); + + if(paramMap.get(DEVIATION_SEVERITY) != null) { + double[] confDeviationThreshold = Utils.getParam(paramMap.get(DEVIATION_SEVERITY), deviationLimits.length); + if (confDeviationThreshold != null) { + deviationLimits = confDeviationThreshold; + } + } + logger.info(_heuristicName + " will use " + DEVIATION_SEVERITY + " with the following threshold settings: " + + Arrays.toString(deviationLimits)); + + if(paramMap.get(FILES_SEVERITY) != null) { + double[] confFilesThreshold = Utils.getParam(paramMap.get(FILES_SEVERITY), filesLimits.length); + if (confFilesThreshold != null) { + filesLimits = confFilesThreshold; + } + } + logger.info(_heuristicName + " will use " + FILES_SEVERITY + " with the following threshold settings: " + + Arrays.toString(filesLimits)); + for (int i = 0; i < filesLimits.length; i++) { + filesLimits[i] = filesLimits[i] * HDFSContext.HDFS_BLOCK_SIZE; + } } - protected GenericDataSkewHeuristic(MapReduceCounterHolder.CounterName counterName, String heuristicName) { + protected GenericDataSkewHeuristic(MapReduceCounterHolder.CounterName counterName, String heuristicName, + HeuristicConfigurationData heuristicConfData) { this._counterName = counterName; this._heuristicName = heuristicName; + this._heuristicConfData = heuristicConfData; + + loadParameters(); + } + + @Override + public String getHeuristicName() { + return _heuristicName; } protected abstract MapReduceTaskData[] getTasks(MapReduceApplicationData data); @@ -74,7 +131,8 @@ public HeuristicResult apply(MapReduceApplicationData data) { severity = Severity.min(severity, getFilesSeverity(avg2)); //This reduces severity if number of tasks is insignificant - severity = Severity.min(severity, Statistics.getNumTasksSeverity(groups[0].length)); + severity = Severity.min(severity, Severity.getSeverityAscending( + groups[0].length, numTasksLimits[0], numTasksLimits[1], numTasksLimits[2], numTasksLimits[3])); HeuristicResult result = new HeuristicResult(_heuristicName, severity); @@ -85,16 +143,18 @@ public HeuristicResult apply(MapReduceApplicationData data) { return result; } - public static Severity getDeviationSeverity(long averageMin, long averageDiff) { + private Severity getDeviationSeverity(long averageMin, long averageDiff) { if (averageMin <= 0) { averageMin = 1; } long value = averageDiff / averageMin; - return Severity.getSeverityAscending(value, 2, 4, 8, 16); + return Severity.getSeverityAscending( + value, deviationLimits[0], deviationLimits[1], deviationLimits[2], deviationLimits[3]); } - public static Severity getFilesSeverity(long value) { - return Severity.getSeverityAscending(value, HDFSContext.HDFS_BLOCK_SIZE / 8, HDFSContext.HDFS_BLOCK_SIZE / 4, - HDFSContext.HDFS_BLOCK_SIZE / 2, HDFSContext.HDFS_BLOCK_SIZE); + private Severity getFilesSeverity(long value) { + return Severity.getSeverityAscending( + value, filesLimits[0], filesLimits[1], filesLimits[2], filesLimits[3]); } + } diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/GenericGCHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericGCHeuristic.java index b22d853b7..c3bcc42bd 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/GenericGCHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericGCHeuristic.java @@ -1,6 +1,10 @@ package com.linkedin.drelephant.mapreduce.heuristics; +import com.linkedin.drelephant.util.HeuristicConfigurationData; +import com.linkedin.drelephant.util.Utils; import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; import java.util.List; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; @@ -9,17 +13,59 @@ import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; import com.linkedin.drelephant.mapreduce.MapReduceTaskData; import com.linkedin.drelephant.math.Statistics; +import java.util.Map; +import org.apache.log4j.Logger; + public abstract class GenericGCHeuristic implements Heuristic { + private static final Logger logger = Logger.getLogger(GenericGCHeuristic.class); + + // Severity Parameters + private static final String GC_RATIO_SEVERITY = "gc_ratio_severity"; + private static final String RUNTIME_SEVERITY = "runtime_severity_in_min"; + + // Default value of parameters + private double[] gcRatioLimits = {0.01d, 0.02d, 0.03d, 0.04d}; // Garbage Collection Time / CPU Time + private double[] runtimeLimits = {5, 10, 12, 15}; // Task Runtime in milli sec + private String _heuristicName; + private HeuristicConfigurationData _heuristicConfData; @Override public String getHeuristicName() { return _heuristicName; } - protected GenericGCHeuristic(String heuristicName) { + private void loadParameters() { + Map paramMap = _heuristicConfData.getParamMap(); + + if(paramMap.get(GC_RATIO_SEVERITY) != null) { + double[] confGcRatioThreshold = Utils.getParam(paramMap.get(GC_RATIO_SEVERITY), gcRatioLimits.length); + if (confGcRatioThreshold != null) { + gcRatioLimits = confGcRatioThreshold; + } + } + logger.info(_heuristicName + " will use " + GC_RATIO_SEVERITY + " with the following threshold settings: " + + Arrays.toString(gcRatioLimits)); + + if(paramMap.get(RUNTIME_SEVERITY) != null) { + double[] confRuntimeThreshold = Utils.getParam(paramMap.get(RUNTIME_SEVERITY), runtimeLimits.length); + if (confRuntimeThreshold != null) { + runtimeLimits = confRuntimeThreshold; + } + } + logger.info(_heuristicName + " will use " + RUNTIME_SEVERITY + " with the following threshold settings: " + + Arrays.toString(runtimeLimits)); + for (int i = 0; i < runtimeLimits.length; i++) { + runtimeLimits[i] = runtimeLimits[i] * Statistics.MINUTE_IN_MS; + } + } + + protected GenericGCHeuristic(String heuristicName, HeuristicConfigurationData heuristicConfData) { this._heuristicName = heuristicName; + this._heuristicConfData = heuristicConfData; + + loadParameters(); } protected abstract MapReduceTaskData[] getTasks(MapReduceApplicationData data); @@ -68,15 +114,18 @@ public HeuristicResult apply(MapReduceApplicationData data) { private Severity getGcRatioSeverity(long runtimeMs, long cpuMs, long gcMs) { double gcRatio = ((double)gcMs)/cpuMs; - Severity ratioSeverity = Severity.getSeverityAscending(gcRatio, 0.01, 0.02, 0.03, 0.04); + Severity ratioSeverity = Severity.getSeverityAscending( + gcRatio, gcRatioLimits[0], gcRatioLimits[1], gcRatioLimits[2], gcRatioLimits[3]); + // Severity is reduced if task runtime is insignificant Severity runtimeSeverity = getRuntimeSeverity(runtimeMs); + return Severity.min(ratioSeverity, runtimeSeverity); } private Severity getRuntimeSeverity(long runtimeMs) { - return Severity.getSeverityAscending(runtimeMs, 5 * Statistics.MINUTE_IN_MS, 10 * Statistics.MINUTE_IN_MS, - 12 * Statistics.MINUTE_IN_MS, 15 * Statistics.MINUTE_IN_MS); + return Severity.getSeverityAscending( + runtimeMs, runtimeLimits[0], runtimeLimits[1], runtimeLimits[2], runtimeLimits[3]); } } diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/GenericMemoryHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericMemoryHeuristic.java index bee65747d..3d2f2c621 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/GenericMemoryHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericMemoryHeuristic.java @@ -15,7 +15,11 @@ */ package com.linkedin.drelephant.mapreduce.heuristics; +import com.linkedin.drelephant.util.HeuristicConfigurationData; +import com.linkedin.drelephant.util.Utils; import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; import java.util.List; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; @@ -25,22 +29,64 @@ import com.linkedin.drelephant.mapreduce.MapReduceTaskData; import com.linkedin.drelephant.math.Statistics; +import java.util.Map; import org.apache.commons.io.FileUtils; +import org.apache.log4j.Logger; public abstract class GenericMemoryHeuristic implements Heuristic { + private static final Logger logger = Logger.getLogger(GenericMemoryHeuristic.class); + private static final long CONTAINER_MEMORY_DEFAULT_BYTES = 2048L * FileUtils.ONE_MB; + + // Severity Parameters + private static final String MEM_RATIO_SEVERITY = "memory_ratio_severity"; + private static final String CONTAINER_MEM_SEVERITY = "container_memory_severity"; + + // Default value of parameters + private double[] memRatioLimits = {0.6d, 0.5d, 0.4d, 0.3d}; // Avg Physical Mem of Tasks / Container Mem + private double[] memoryLimits = {1.1d, 1.5d, 2.0d, 2.5d}; // Container Memory Severity Limits + private String _containerMemConf; private String _heuristicName; - private final long CONTAINER_MEMORY_DEFAULT_BYTES = 2048L * FileUtils.ONE_MB; + private HeuristicConfigurationData _heuristicConfData; @Override public String getHeuristicName() { return _heuristicName; } - protected GenericMemoryHeuristic(String containerMemConf, String heuristicName) { + private void loadParameters() { + Map paramMap = _heuristicConfData.getParamMap(); + + if(paramMap.get(MEM_RATIO_SEVERITY) != null) { + double[] confMemRatioLimits = Utils.getParam(paramMap.get(MEM_RATIO_SEVERITY), memRatioLimits.length); + if (confMemRatioLimits != null) { + memRatioLimits = confMemRatioLimits; + } + } + logger.info(_heuristicName + " will use " + MEM_RATIO_SEVERITY + " with the following threshold settings: " + + Arrays.toString(memRatioLimits)); + + if(paramMap.get(CONTAINER_MEM_SEVERITY) != null) { + double[] confMemoryLimits = Utils.getParam(paramMap.get(CONTAINER_MEM_SEVERITY), memoryLimits.length); + if (confMemoryLimits != null) { + memoryLimits = confMemoryLimits; + } + } + logger.info(_heuristicName + " will use " + CONTAINER_MEM_SEVERITY + " with the following threshold settings: " + + Arrays.toString(memoryLimits)); + for (int i = 0; i < memoryLimits.length; i++) { + memoryLimits[i] = memoryLimits[i] * CONTAINER_MEMORY_DEFAULT_BYTES; + } + } + + protected GenericMemoryHeuristic(String containerMemConf, String heuristicName, + HeuristicConfigurationData heuristicConfData) { this._heuristicName = heuristicName; this._containerMemConf = containerMemConf; + this._heuristicConfData = heuristicConfData; + + loadParameters(); } protected abstract MapReduceTaskData[] getTasks(MapReduceApplicationData data); @@ -123,12 +169,14 @@ private Severity getTaskMemoryUtilSeverity(long taskMemAvg, long taskMemMax) { return Severity.min(sevRatio, sevMax); } + private Severity getContainerMemorySeverity(long taskMemMax) { - return Severity.getSeverityAscending(taskMemMax, CONTAINER_MEMORY_DEFAULT_BYTES * 1.1, - CONTAINER_MEMORY_DEFAULT_BYTES * 1.5, CONTAINER_MEMORY_DEFAULT_BYTES * 2, CONTAINER_MEMORY_DEFAULT_BYTES * 2.5); + return Severity.getSeverityAscending( + taskMemMax, memoryLimits[0], memoryLimits[1], memoryLimits[2], memoryLimits[3]); } private Severity getMemoryRatioSeverity(double ratio) { - return Severity.getSeverityDescending(ratio, 0.6, 0.5, 0.4, 0.3); + return Severity.getSeverityDescending( + ratio, memRatioLimits[0], memRatioLimits[1], memRatioLimits[2], memRatioLimits[3]); } } diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/JobQueueLimitHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/JobQueueLimitHeuristic.java index c5bce78dc..4ee221b69 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/JobQueueLimitHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/JobQueueLimitHeuristic.java @@ -17,6 +17,7 @@ import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; import com.linkedin.drelephant.mapreduce.MapReduceTaskData; +import com.linkedin.drelephant.util.HeuristicConfigurationData; import java.util.Properties; import java.util.concurrent.TimeUnit; @@ -28,11 +29,17 @@ public class JobQueueLimitHeuristic implements Heuristic { public static final String HEURISTIC_NAME = "Queue Time Limit"; + private HeuristicConfigurationData _heuristicConfData; + @Override public String getHeuristicName() { return HEURISTIC_NAME; } + protected JobQueueLimitHeuristic(HeuristicConfigurationData heuristicConfData) { + this._heuristicConfData = heuristicConfData; + } + @Override public HeuristicResult apply(MapReduceApplicationData data) { HeuristicResult result = new HeuristicResult(HEURISTIC_NAME, Severity.NONE); diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperDataSkewHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperDataSkewHeuristic.java index c44dfd211..c03c5b0a8 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperDataSkewHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperDataSkewHeuristic.java @@ -18,13 +18,14 @@ import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; import com.linkedin.drelephant.mapreduce.MapReduceTaskData; +import com.linkedin.drelephant.util.HeuristicConfigurationData; public class MapperDataSkewHeuristic extends GenericDataSkewHeuristic { public static final String HEURISTIC_NAME = "Mapper Data Skew"; - public MapperDataSkewHeuristic() { - super(MapReduceCounterHolder.CounterName.HDFS_BYTES_READ, HEURISTIC_NAME); + public MapperDataSkewHeuristic(HeuristicConfigurationData heuristicConfData) { + super(MapReduceCounterHolder.CounterName.HDFS_BYTES_READ, HEURISTIC_NAME, heuristicConfData); } @Override diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperGCHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperGCHeuristic.java index cc7d2d125..9e8472973 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperGCHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperGCHeuristic.java @@ -2,13 +2,14 @@ import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; import com.linkedin.drelephant.mapreduce.MapReduceTaskData; +import com.linkedin.drelephant.util.HeuristicConfigurationData; public class MapperGCHeuristic extends GenericGCHeuristic { public static final String HEURISTIC_NAME = "Mapper GC"; - public MapperGCHeuristic() { - super(HEURISTIC_NAME); + public MapperGCHeuristic(HeuristicConfigurationData heuristicConfData) { + super(HEURISTIC_NAME, heuristicConfData); } @Override diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperMemoryHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperMemoryHeuristic.java index 0c3096e27..5a8018a91 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperMemoryHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperMemoryHeuristic.java @@ -17,14 +17,15 @@ import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; import com.linkedin.drelephant.mapreduce.MapReduceTaskData; +import com.linkedin.drelephant.util.HeuristicConfigurationData; public class MapperMemoryHeuristic extends GenericMemoryHeuristic { public static final String HEURISTIC_NAME = "Mapper Memory"; public static final String MAPPER_MEMORY_CONF = "mapreduce.map.memory.mb"; - public MapperMemoryHeuristic() { - super(MAPPER_MEMORY_CONF, HEURISTIC_NAME); + public MapperMemoryHeuristic(HeuristicConfigurationData _heuristicConfData) { + super(MAPPER_MEMORY_CONF, HEURISTIC_NAME, _heuristicConfData); } @Override diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristic.java index 49839056c..f0ed6125f 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristic.java @@ -18,7 +18,11 @@ import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; +import com.linkedin.drelephant.util.HeuristicConfigurationData; +import com.linkedin.drelephant.util.Utils; import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; import java.util.List; import com.linkedin.drelephant.analysis.HDFSContext; @@ -28,11 +32,58 @@ import com.linkedin.drelephant.mapreduce.MapReduceTaskData; import com.linkedin.drelephant.math.Statistics; +import java.util.Map; import org.apache.commons.io.FileUtils; +import org.apache.log4j.Logger; + public class MapperSpeedHeuristic implements Heuristic { + private static final Logger logger = Logger.getLogger(MapperSpeedHeuristic.class); public static final String HEURISTIC_NAME = "Mapper Speed"; + // Severity parameters. + private static final String DISK_SPEED_SEVERITY = "disk_speed_severity"; + private static final String RUNTIME_SEVERITY = "runtime_severity_in_min"; + + // Default value of parameters + private double[] diskSpeedLimits = {1d/2, 1d/4, 1d/8, 1d/32}; // Fraction of HDFS block size + private double[] runtimeLimits = {5, 10, 15, 30}; // The Map task runtime in milli sec + + private HeuristicConfigurationData _heuristicConfData; + + private void loadParameters() { + Map paramMap = _heuristicConfData.getParamMap(); + + if(paramMap.get(DISK_SPEED_SEVERITY) != null) { + double[] confDiskSpeedThreshold = Utils.getParam(paramMap.get(DISK_SPEED_SEVERITY), diskSpeedLimits.length); + if (confDiskSpeedThreshold != null) { + diskSpeedLimits = confDiskSpeedThreshold; + } + } + logger.info(HEURISTIC_NAME + " will use " + DISK_SPEED_SEVERITY + " with the following threshold settings: " + + Arrays.toString(diskSpeedLimits)); + for (int i = 0; i < diskSpeedLimits.length; i++) { + diskSpeedLimits[i] = diskSpeedLimits[i] * HDFSContext.HDFS_BLOCK_SIZE; + } + + if(paramMap.get(RUNTIME_SEVERITY) != null) { + double[] confRuntimeThreshold = Utils.getParam(paramMap.get(RUNTIME_SEVERITY), runtimeLimits.length); + if (confRuntimeThreshold != null) { + runtimeLimits = confRuntimeThreshold; + } + } + logger.info(HEURISTIC_NAME + " will use " + RUNTIME_SEVERITY + " with the following threshold settings: " + Arrays + .toString(runtimeLimits)); + for (int i = 0; i < runtimeLimits.length; i++) { + runtimeLimits[i] = runtimeLimits[i] * Statistics.MINUTE_IN_MS; + } + } + + public MapperSpeedHeuristic(HeuristicConfigurationData heuristicConfData) { + this._heuristicConfData = heuristicConfData; + loadParameters(); + } + @Override public String getHeuristicName() { return HEURISTIC_NAME; @@ -91,13 +142,13 @@ public HeuristicResult apply(MapReduceApplicationData data) { return result; } - public static Severity getDiskSpeedSeverity(long speed) { - return Severity.getSeverityDescending(speed, HDFSContext.DISK_READ_SPEED / 2, HDFSContext.DISK_READ_SPEED / 4, - HDFSContext.DISK_READ_SPEED / 8, HDFSContext.DISK_READ_SPEED / 32); + private Severity getDiskSpeedSeverity(long speed) { + return Severity.getSeverityDescending( + speed, diskSpeedLimits[0], diskSpeedLimits[1], diskSpeedLimits[2], diskSpeedLimits[3]); } - public static Severity getRuntimeSeverity(long runtimeMs) { - return Severity.getSeverityAscending(runtimeMs, 5 * Statistics.MINUTE_IN_MS, 10 * Statistics.MINUTE_IN_MS, - 15 * Statistics.MINUTE_IN_MS, 30 * Statistics.MINUTE_IN_MS); + private Severity getRuntimeSeverity(long runtimeMs) { + return Severity.getSeverityAscending( + runtimeMs, runtimeLimits[0], runtimeLimits[1], runtimeLimits[2], runtimeLimits[3]); } } diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristic.java index 1fc5ca457..369e161f3 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristic.java @@ -21,12 +21,59 @@ import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; import com.linkedin.drelephant.mapreduce.MapReduceTaskData; import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; +import com.linkedin.drelephant.util.HeuristicConfigurationData; +import com.linkedin.drelephant.util.Utils; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import org.apache.log4j.Logger; public class MapperSpillHeuristic implements Heuristic { + private static final Logger logger = Logger.getLogger(MapperSpillHeuristic.class); public static final String HEURISTIC_NAME = "Mapper Spill"; private static final long THRESHOLD_SPILL_FACTOR = 10000; + // Severity parameters. + private static final String SPILL_SEVERITY = "spill_severity"; + private static final String NUM_TASKS_SEVERITY = "num_tasks_severity"; + + // Default value of parameters + private double[] numTasksLimits = {50, 100, 500, 1000}; // Number of Map tasks. + private double[] spillLimits = {2.01d, 2.2d, 2.5d, 3.0d}; // Records spilled/total output records + + private HeuristicConfigurationData _heuristicConfData; + + private void loadParameters() { + Map paramMap = _heuristicConfData.getParamMap(); + + if(paramMap.get(NUM_TASKS_SEVERITY) != null) { + double[] confNumTasksThreshold = Utils.getParam(paramMap.get(NUM_TASKS_SEVERITY), numTasksLimits.length); + if (confNumTasksThreshold != null) { + numTasksLimits = confNumTasksThreshold; + } + } + logger.info(HEURISTIC_NAME + " will use " + NUM_TASKS_SEVERITY + " with the following threshold settings: " + + Arrays.toString(numTasksLimits)); + + if(paramMap.get(SPILL_SEVERITY) != null) { + double[] confSpillThreshold = Utils.getParam(paramMap.get(SPILL_SEVERITY), spillLimits.length); + if (confSpillThreshold != null) { + spillLimits = confSpillThreshold; + } + } + logger.info(HEURISTIC_NAME + " will use " + SPILL_SEVERITY + " with the following threshold settings: " + + Arrays.toString(spillLimits)); + for (int i = 0; i < spillLimits.length; i++) { + spillLimits[i] = spillLimits[i] * THRESHOLD_SPILL_FACTOR; + } + } + + public MapperSpillHeuristic(HeuristicConfigurationData heuristicConfData) { + this._heuristicConfData = heuristicConfData; + loadParameters(); + } + @Override public HeuristicResult apply(MapReduceApplicationData data) { @@ -74,16 +121,18 @@ public String getHeuristicName() { return HEURISTIC_NAME; } - public static Severity getSpillSeverity(double ratioSpills) { + private Severity getSpillSeverity(double ratioSpills) { + long normalizedSpillRatio = 0; //Normalize the ratio to integer. normalizedSpillRatio = (long) (ratioSpills * THRESHOLD_SPILL_FACTOR); - return Severity.getSeverityAscending(normalizedSpillRatio, (long) (2.01 * THRESHOLD_SPILL_FACTOR), - (long) (2.2 * THRESHOLD_SPILL_FACTOR), (long) (2.5 * THRESHOLD_SPILL_FACTOR), - (long) (3 * THRESHOLD_SPILL_FACTOR)); + + return Severity.getSeverityAscending( + normalizedSpillRatio, spillLimits[0], spillLimits[1], spillLimits[2], spillLimits[3]); } - public static Severity getNumTasksSeverity(long numTasks) { - return Severity.getSeverityAscending(numTasks, 50, 100, 500, 1000); + private Severity getNumTasksSeverity(long numTasks) { + return Severity.getSeverityAscending( + numTasks, numTasksLimits[0], numTasksLimits[1], numTasksLimits[2], numTasksLimits[3]); } } diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristic.java index 097e9b42b..96587a240 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristic.java @@ -15,9 +15,14 @@ */ package com.linkedin.drelephant.mapreduce.heuristics; +import com.linkedin.drelephant.analysis.HDFSContext; import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; +import com.linkedin.drelephant.util.HeuristicConfigurationData; +import com.linkedin.drelephant.util.Utils; import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; import java.util.List; import com.linkedin.drelephant.analysis.Heuristic; @@ -26,12 +31,68 @@ import com.linkedin.drelephant.mapreduce.MapReduceTaskData; import com.linkedin.drelephant.math.Statistics; +import java.util.Map; import org.apache.commons.io.FileUtils; +import org.apache.log4j.Logger; public class MapperTimeHeuristic implements Heuristic { + private static final Logger logger = Logger.getLogger(MapperTimeHeuristic.class); public static final String HEURISTIC_NAME = "Mapper Time"; + // Severity parameters. + private static final String SHORT_RUNTIME_SEVERITY = "short_runtime_severity_in_min"; + private static final String LONG_RUNTIME_SEVERITY = "long_runtime_severity_in_min"; + private static final String NUM_TASKS_SEVERITY = "num_tasks_severity"; + + // Default value of parameters + private double[] shortRuntimeLimits = {10, 4, 2, 1}; // Limits(ms) for tasks with shorter runtime + private double[] longRuntimeLimits = {15, 30, 60, 120}; // Limits(ms) for tasks with longer runtime + private double[] numTasksLimits = {50, 101, 500, 1000}; // Number of Map tasks. + + private HeuristicConfigurationData _heuristicConfData; + + private void loadParameters() { + Map paramMap = _heuristicConfData.getParamMap(); + + if(paramMap.get(SHORT_RUNTIME_SEVERITY) != null) { + double[] confShortThreshold = Utils.getParam(paramMap.get(SHORT_RUNTIME_SEVERITY), shortRuntimeLimits.length); + if (confShortThreshold != null) { + shortRuntimeLimits = confShortThreshold; + } + } + logger.info(HEURISTIC_NAME + " will use " + SHORT_RUNTIME_SEVERITY + " with the following threshold settings: " + + Arrays.toString(shortRuntimeLimits)); + for (int i = 0; i < shortRuntimeLimits.length; i++) { + shortRuntimeLimits[i] = shortRuntimeLimits[i] * Statistics.MINUTE_IN_MS; + } + + if(paramMap.get(LONG_RUNTIME_SEVERITY) != null) { + double[] confLongThreshold = Utils.getParam(paramMap.get(LONG_RUNTIME_SEVERITY), longRuntimeLimits.length); + if (confLongThreshold != null) { + longRuntimeLimits = confLongThreshold; + } + } + logger.info(HEURISTIC_NAME + " will use " + LONG_RUNTIME_SEVERITY + " with the following threshold settings: " + Arrays.toString(longRuntimeLimits)); + for (int i = 0; i < longRuntimeLimits.length; i++) { + longRuntimeLimits[i] = longRuntimeLimits[i] * Statistics.MINUTE_IN_MS; + } + + if(paramMap.get(NUM_TASKS_SEVERITY) != null) { + double[] confNumTasksThreshold = Utils.getParam(paramMap.get(NUM_TASKS_SEVERITY), numTasksLimits.length); + if (confNumTasksThreshold != null) { + numTasksLimits = confNumTasksThreshold; + } + } + logger.info(HEURISTIC_NAME + " will use " + NUM_TASKS_SEVERITY + " with the following threshold settings: " + + Arrays.toString(numTasksLimits)); + } + + public MapperTimeHeuristic(HeuristicConfigurationData heuristicConfData) { + this._heuristicConfData = heuristicConfData; + loadParameters(); + } + @Override public String getHeuristicName() { return HEURISTIC_NAME; @@ -97,16 +158,17 @@ private Severity longTaskSeverity(long numTasks, long averageTimeMs) { } private Severity getShortRuntimeSeverity(long runtimeMs) { - return Severity.getSeverityDescending(runtimeMs, 10 * Statistics.MINUTE_IN_MS, 4 * Statistics.MINUTE_IN_MS, - 2 * Statistics.MINUTE_IN_MS, 1 * Statistics.MINUTE_IN_MS); + return Severity.getSeverityDescending( + runtimeMs, shortRuntimeLimits[0], shortRuntimeLimits[1], shortRuntimeLimits[2], shortRuntimeLimits[3]); } private Severity getLongRuntimeSeverity(long runtimeMs) { - return Severity.getSeverityAscending(runtimeMs, 15 * Statistics.MINUTE_IN_MS, 30 * Statistics.MINUTE_IN_MS, 1 * Statistics.HOUR_IN_MS, - 2 * Statistics.HOUR_IN_MS); + return Severity.getSeverityAscending( + runtimeMs, longRuntimeLimits[0], longRuntimeLimits[1], longRuntimeLimits[2], longRuntimeLimits[3]); } private Severity getNumTasksSeverity(long numTasks) { - return Severity.getSeverityAscending(numTasks, 50, 101, 500, 1000); + return Severity.getSeverityAscending( + numTasks, numTasksLimits[0], numTasksLimits[1], numTasksLimits[2], numTasksLimits[3]); } } diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerDataSkewHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerDataSkewHeuristic.java index 9b90a9caf..d051aafe5 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerDataSkewHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerDataSkewHeuristic.java @@ -18,13 +18,14 @@ import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; import com.linkedin.drelephant.mapreduce.MapReduceTaskData; +import com.linkedin.drelephant.util.HeuristicConfigurationData; public class ReducerDataSkewHeuristic extends GenericDataSkewHeuristic { public static final String HEURISTIC_NAME = "Reducer Data Skew"; - public ReducerDataSkewHeuristic() { - super(MapReduceCounterHolder.CounterName.REDUCE_SHUFFLE_BYTES, HEURISTIC_NAME); + public ReducerDataSkewHeuristic(HeuristicConfigurationData heuristicConfData) { + super(MapReduceCounterHolder.CounterName.REDUCE_SHUFFLE_BYTES, HEURISTIC_NAME, heuristicConfData); } @Override diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerGCHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerGCHeuristic.java index be8af39a4..a0fa0c7a3 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerGCHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerGCHeuristic.java @@ -2,13 +2,14 @@ import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; import com.linkedin.drelephant.mapreduce.MapReduceTaskData; +import com.linkedin.drelephant.util.HeuristicConfigurationData; public class ReducerGCHeuristic extends GenericGCHeuristic { public static final String HEURISTIC_NAME = "Reducer GC"; - public ReducerGCHeuristic() { - super(HEURISTIC_NAME); + public ReducerGCHeuristic(HeuristicConfigurationData _heuristicConfData) { + super(HEURISTIC_NAME, _heuristicConfData); } @Override diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerMemoryHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerMemoryHeuristic.java index 810989f89..8c18b44a5 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerMemoryHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerMemoryHeuristic.java @@ -17,14 +17,15 @@ import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; import com.linkedin.drelephant.mapreduce.MapReduceTaskData; +import com.linkedin.drelephant.util.HeuristicConfigurationData; public class ReducerMemoryHeuristic extends GenericMemoryHeuristic { public static final String HEURISTIC_NAME = "Reducer Memory"; public static final String REDUCER_MEMORY_CONF = "mapreduce.reduce.memory.mb"; - public ReducerMemoryHeuristic() { - super(REDUCER_MEMORY_CONF, HEURISTIC_NAME); + public ReducerMemoryHeuristic(HeuristicConfigurationData _heuristicConfData) { + super(REDUCER_MEMORY_CONF, HEURISTIC_NAME, _heuristicConfData); } @Override diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristic.java index 6dc92ee9c..2466f765e 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristic.java @@ -17,18 +17,79 @@ import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; import com.linkedin.drelephant.mapreduce.MapReduceTaskData; +import com.linkedin.drelephant.util.HeuristicConfigurationData; +import com.linkedin.drelephant.util.Utils; import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; import java.util.List; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; import com.linkedin.drelephant.math.Statistics; +import java.util.Map; +import org.apache.log4j.Logger; public class ReducerTimeHeuristic implements Heuristic { + private static final Logger logger = Logger.getLogger(ReducerTimeHeuristic.class); public static final String HEURISTIC_NAME = "Reducer Time"; + // Severity parameters. + private static final String SHORT_RUNTIME_SEVERITY = "short_runtime_severity_in_min"; + private static final String LONG_RUNTIME_SEVERITY = "long_runtime_severity_in_min"; + private static final String NUM_TASKS_SEVERITY = "num_tasks_severity"; + + // Default value of parameters + private double[] shortRuntimeLimits = {10, 4, 2, 1}; // Limits(ms) for tasks with shorter runtime + private double[] longRuntimeLimits = {15, 30, 60, 120}; // Limits(ms) for tasks with longer runtime + private double[] numTasksLimits = {50, 101, 500, 1000}; // Number of Reduce tasks. + + private HeuristicConfigurationData _heuristicConfData; + + private void loadParameters() { + Map paramMap = _heuristicConfData.getParamMap(); + + if(paramMap.get(SHORT_RUNTIME_SEVERITY) != null) { + double[] confShortRuntimeLimits = Utils.getParam(paramMap.get(SHORT_RUNTIME_SEVERITY), shortRuntimeLimits.length); + if (confShortRuntimeLimits != null) { + shortRuntimeLimits = confShortRuntimeLimits; + } + } + logger.info(HEURISTIC_NAME + " will use " + SHORT_RUNTIME_SEVERITY + " with the following threshold settings: " + + Arrays.toString(shortRuntimeLimits)); + for (int i = 0; i < shortRuntimeLimits.length; i++) { + shortRuntimeLimits[i] = shortRuntimeLimits[i] * Statistics.MINUTE_IN_MS; + } + + if(paramMap.get(LONG_RUNTIME_SEVERITY) != null) { + double[] confLongRuntimeLimitss = Utils.getParam(paramMap.get(LONG_RUNTIME_SEVERITY), longRuntimeLimits.length); + if (confLongRuntimeLimitss != null) { + longRuntimeLimits = confLongRuntimeLimitss; + } + } + logger.info(HEURISTIC_NAME + " will use " + LONG_RUNTIME_SEVERITY + " with the following threshold settings: " + + Arrays.toString(longRuntimeLimits)); + for (int i = 0; i < longRuntimeLimits.length; i++) { + longRuntimeLimits[i] = longRuntimeLimits[i] * Statistics.MINUTE_IN_MS; + } + + if(paramMap.get(NUM_TASKS_SEVERITY) != null) { + double[] confNumTasksLimits = Utils.getParam(paramMap.get(NUM_TASKS_SEVERITY), numTasksLimits.length); + if (confNumTasksLimits != null) { + numTasksLimits = confNumTasksLimits; + } + } + logger.info(HEURISTIC_NAME + " will use " + NUM_TASKS_SEVERITY + " with the following threshold settings: " + Arrays.toString(numTasksLimits)); + + } + + public ReducerTimeHeuristic(HeuristicConfigurationData heuristicConfData) { + this._heuristicConfData = heuristicConfData; + loadParameters(); + } + @Override public String getHeuristicName() { return HEURISTIC_NAME; @@ -89,16 +150,17 @@ private Severity longTimeSeverity(long runtimeMs, long numTasks) { } private Severity getShortRuntimeSeverity(long runtimeMs) { - return Severity.getSeverityDescending(runtimeMs, 10 * Statistics.MINUTE_IN_MS, 4 * Statistics.MINUTE_IN_MS, - 2 * Statistics.MINUTE_IN_MS, 1 * Statistics.MINUTE_IN_MS); + return Severity.getSeverityDescending( + runtimeMs, shortRuntimeLimits[0], shortRuntimeLimits[1], shortRuntimeLimits[2], shortRuntimeLimits[3]); } private Severity getLongRuntimeSeverity(long runtimeMs) { - return Severity.getSeverityAscending(runtimeMs, 15 * Statistics.MINUTE_IN_MS, 30 * Statistics.MINUTE_IN_MS, 1 * Statistics.HOUR_IN_MS, - 2 * Statistics.HOUR_IN_MS); + return Severity.getSeverityAscending( + runtimeMs, longRuntimeLimits[0], longRuntimeLimits[1], longRuntimeLimits[2], longRuntimeLimits[3]); } private Severity getNumTasksSeverity(long numTasks) { - return Severity.getSeverityAscending(numTasks, 50, 101, 500, 1000); + return Severity.getSeverityAscending( + numTasks, numTasksLimits[0], numTasksLimits[1], numTasksLimits[2], numTasksLimits[3]); } } diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristic.java index 64f7baa27..6bcbc4ad0 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristic.java @@ -16,7 +16,11 @@ package com.linkedin.drelephant.mapreduce.heuristics; import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; +import com.linkedin.drelephant.util.HeuristicConfigurationData; +import com.linkedin.drelephant.util.Utils; import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; import java.util.List; import com.linkedin.drelephant.analysis.Heuristic; @@ -24,11 +28,54 @@ import com.linkedin.drelephant.analysis.Severity; import com.linkedin.drelephant.mapreduce.MapReduceTaskData; import com.linkedin.drelephant.math.Statistics; +import java.util.Map; +import org.apache.log4j.Logger; public class ShuffleSortHeuristic implements Heuristic { + private static final Logger logger = Logger.getLogger(ShuffleSortHeuristic.class); public static final String HEURISTIC_NAME = "Shuffle & Sort"; + // Severity parameters. + private static final String RUNTIME_RATIO_SEVERITY = "runtime_ratio_severity"; + private static final String RUNTIME_SEVERITY = "runtime_severity_in_min"; + + // Default value of parameters + private double[] runtimeRatioLimits = {1, 2, 4, 8}; // Avg Shuffle or Sort Time * 2 / Avg Exec Time + private double[] runtimeLimits = {1, 5, 10, 30}; // Shuffle/Sort Runtime in milli sec + + private HeuristicConfigurationData _heuristicConfData; + + private void loadParameters() { + Map paramMap = _heuristicConfData.getParamMap(); + + if(paramMap.get(RUNTIME_RATIO_SEVERITY) != null) { + double[] confRatioLimitsd = Utils.getParam(paramMap.get(RUNTIME_RATIO_SEVERITY), runtimeRatioLimits.length); + if (confRatioLimitsd != null) { + runtimeRatioLimits = confRatioLimitsd; + } + } + logger.info(HEURISTIC_NAME + " will use " + RUNTIME_RATIO_SEVERITY + " with the following threshold settings: " + + Arrays.toString(runtimeRatioLimits)); + + if(paramMap.get(RUNTIME_SEVERITY) != null) { + double[] confRuntimeLimits = Utils.getParam(paramMap.get(RUNTIME_SEVERITY), runtimeLimits.length); + if (confRuntimeLimits != null) { + runtimeLimits = confRuntimeLimits; + } + } + logger.info(HEURISTIC_NAME + " will use " + RUNTIME_SEVERITY + " with the following threshold settings: " + + Arrays.toString(runtimeLimits)); + for (int i = 0; i < runtimeLimits.length; i++) { + runtimeLimits[i] = runtimeLimits[i] * Statistics.MINUTE_IN_MS; + } + } + + public ShuffleSortHeuristic(HeuristicConfigurationData heuristicConfData) { + this._heuristicConfData = heuristicConfData; + loadParameters(); + } + @Override public String getHeuristicName() { return HEURISTIC_NAME; @@ -76,16 +123,17 @@ public HeuristicResult apply(MapReduceApplicationData data) { return result; } - public static Severity getShuffleSortSeverity(long runtimeMs, long codetimeMs) { - Severity runtimeSeverity = - Severity.getSeverityAscending(runtimeMs, 1 * Statistics.MINUTE_IN_MS, 5 * Statistics.MINUTE_IN_MS, 10 * Statistics.MINUTE_IN_MS, - 30 * Statistics.MINUTE_IN_MS); + private Severity getShuffleSortSeverity(long runtimeMs, long codetimeMs) { + Severity runtimeSeverity = Severity.getSeverityAscending( + runtimeMs, runtimeLimits[0], runtimeLimits[1], runtimeLimits[2], runtimeLimits[3]); if (codetimeMs <= 0) { return runtimeSeverity; } long value = runtimeMs * 2 / codetimeMs; - Severity runtimeRatioSeverity = Severity.getSeverityAscending(value, 1, 2, 4, 8); + + Severity runtimeRatioSeverity = Severity.getSeverityAscending( + value, runtimeRatioLimits[0], runtimeRatioLimits[1], runtimeRatioLimits[2], runtimeRatioLimits[3]); return Severity.min(runtimeSeverity, runtimeRatioSeverity); } diff --git a/app/com/linkedin/drelephant/math/Statistics.java b/app/com/linkedin/drelephant/math/Statistics.java index 12ead2e89..2c8344515 100644 --- a/app/com/linkedin/drelephant/math/Statistics.java +++ b/app/com/linkedin/drelephant/math/Statistics.java @@ -172,10 +172,6 @@ public static String readableTimespan(long milliseconds) { return sb.toString().trim(); } - public static Severity getNumTasksSeverity(long numTasks) { - return Severity.getSeverityAscending(numTasks, 10, 50, 100, 200); - } - public static T[] createSample(Class clazz, T[] objects, int size) { //Skip this process if number of items already smaller than sample size if (objects.length <= size) { diff --git a/app/com/linkedin/drelephant/spark/heuristics/BestPropertiesConventionHeuristic.java b/app/com/linkedin/drelephant/spark/heuristics/BestPropertiesConventionHeuristic.java index 889e5fac2..13a9d535e 100644 --- a/app/com/linkedin/drelephant/spark/heuristics/BestPropertiesConventionHeuristic.java +++ b/app/com/linkedin/drelephant/spark/heuristics/BestPropertiesConventionHeuristic.java @@ -20,7 +20,13 @@ import com.linkedin.drelephant.analysis.Severity; import com.linkedin.drelephant.spark.SparkApplicationData; import com.linkedin.drelephant.spark.SparkEnvironmentData; +import com.linkedin.drelephant.util.HeuristicConfigurationData; import com.linkedin.drelephant.util.MemoryFormatUtils; +import com.linkedin.drelephant.util.Utils; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import org.apache.log4j.Logger; /** @@ -30,12 +36,54 @@ * @author yizhou */ public class BestPropertiesConventionHeuristic implements Heuristic { + private static final Logger logger = Logger.getLogger(BestPropertiesConventionHeuristic.class); + public static final String HEURISTIC_NAME = "Spark Configuration Best Practice"; public static final String SPARK_SERIALIZER = "spark.serializer"; public static final String SPARK_DRIVER_MEMORY = "spark.driver.memory"; public static final String SPARK_SHUFFLE_MANAGER = "spark.shuffle.manager"; public static final String SPARK_EXECUTOR_CORES = "spark.executor.cores"; + // Severity parameters. + private static final String NUM_CORE_SEVERITY = "num_core_severity"; + private static final String DRIVER_MEM_SEVERITY = "driver_memory_severity_in_gb"; + + // Default value of parameters + private double[] numCoreLimit= {2d}; // Spark Executor Cores + private double[] driverMemLimits = {4d, 4d, 8d, 8d}; // Spark Driver Memory + + private HeuristicConfigurationData _heuristicConfData; + + private void loadParameters() { + Map paramMap = _heuristicConfData.getParamMap(); + + if(paramMap.get(NUM_CORE_SEVERITY) != null) { + double[] confNumCoreLimit = Utils.getParam(paramMap.get(NUM_CORE_SEVERITY), numCoreLimit.length); + if (confNumCoreLimit != null) { + numCoreLimit = confNumCoreLimit; + } + } + logger.info(HEURISTIC_NAME + " will use " + NUM_CORE_SEVERITY + " with the following threshold settings: " + + Arrays.toString(numCoreLimit)); + + if(paramMap.get(DRIVER_MEM_SEVERITY) != null) { + double[] confDriverMemLimits = Utils.getParam(paramMap.get(DRIVER_MEM_SEVERITY), driverMemLimits.length); + if (confDriverMemLimits != null) { + driverMemLimits = confDriverMemLimits; + } + } + logger.info(HEURISTIC_NAME + " will use " + DRIVER_MEM_SEVERITY + " with the following threshold settings: " + + Arrays.toString(driverMemLimits)); + for (int i = 0; i < driverMemLimits.length; i++) { + driverMemLimits[i] = (double) MemoryFormatUtils.stringToBytes(Double.toString(driverMemLimits[i]) + "G"); + } + } + + public BestPropertiesConventionHeuristic(HeuristicConfigurationData heuristicConfData) { + this._heuristicConfData = heuristicConfData; + loadParameters(); + } + @Override public HeuristicResult apply(SparkApplicationData data) { SparkEnvironmentData env = data.getEnvironmentData(); @@ -62,18 +110,17 @@ public HeuristicResult apply(SparkApplicationData data) { return result; } - private static Severity getCoreNumSeverity(int cores) { - if (cores > 2) { + private Severity getCoreNumSeverity(int cores) { + if (cores > numCoreLimit[0]) { return Severity.CRITICAL; } else { return Severity.NONE; } } - private static Severity getDriverMemorySeverity(long mem) { - return Severity - .getSeverityAscending(mem, MemoryFormatUtils.stringToBytes("4G"), MemoryFormatUtils.stringToBytes("4G"), - MemoryFormatUtils.stringToBytes("8G"), MemoryFormatUtils.stringToBytes("8G")); + private Severity getDriverMemorySeverity(long mem) { + return Severity.getSeverityAscending( + mem, driverMemLimits[0], driverMemLimits[1], driverMemLimits[2], driverMemLimits[3]); } private static Severity binarySeverity(String expectedValue, String actualValue, boolean ignoreNull, diff --git a/app/com/linkedin/drelephant/spark/heuristics/EventLogLimitHeuristic.java b/app/com/linkedin/drelephant/spark/heuristics/EventLogLimitHeuristic.java index 80891ce68..7b4a4c6ee 100644 --- a/app/com/linkedin/drelephant/spark/heuristics/EventLogLimitHeuristic.java +++ b/app/com/linkedin/drelephant/spark/heuristics/EventLogLimitHeuristic.java @@ -19,6 +19,7 @@ import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; import com.linkedin.drelephant.spark.SparkApplicationData; +import com.linkedin.drelephant.util.HeuristicConfigurationData; /** @@ -29,6 +30,11 @@ */ public class EventLogLimitHeuristic implements Heuristic { public static final String HEURISTIC_NAME = "Spark Event Log Limit"; + private HeuristicConfigurationData _heuristicConfData; + + public EventLogLimitHeuristic(HeuristicConfigurationData heuristicConfData) { + this._heuristicConfData = heuristicConfData; + } @Override public HeuristicResult apply(SparkApplicationData data) { diff --git a/app/com/linkedin/drelephant/spark/heuristics/ExecutorLoadHeuristic.java b/app/com/linkedin/drelephant/spark/heuristics/ExecutorLoadHeuristic.java index 6dc5487f8..e290956ff 100644 --- a/app/com/linkedin/drelephant/spark/heuristics/ExecutorLoadHeuristic.java +++ b/app/com/linkedin/drelephant/spark/heuristics/ExecutorLoadHeuristic.java @@ -21,8 +21,14 @@ import com.linkedin.drelephant.math.Statistics; import com.linkedin.drelephant.spark.SparkApplicationData; import com.linkedin.drelephant.spark.SparkExecutorData; +import com.linkedin.drelephant.util.HeuristicConfigurationData; import com.linkedin.drelephant.util.MemoryFormatUtils; +import com.linkedin.drelephant.util.Utils; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; import java.util.Set; +import org.apache.log4j.Logger; import static com.linkedin.drelephant.spark.SparkExecutorData.EXECUTOR_DRIVER_NAME; @@ -31,9 +37,48 @@ * This heuristic rule observes load details of executors */ public class ExecutorLoadHeuristic implements Heuristic { + private static final Logger logger = Logger.getLogger(ExecutorLoadHeuristic.class); public static final String HEURISTIC_NAME = "Spark Executor Load Balance"; private static final long MEMORY_OBSERVATION_THRESHOLD = MemoryFormatUtils.stringToBytes("1 MB"); + // Severity parameters. + private static final String LOOSER_METRIC_DEV_SEVERITY = "looser_metric_deviation_severity"; + private static final String METRIC_DEV_SEVERITY = "metric_deviation_severity"; + + // Default value of parameters + private double[] looserMetDevLimits = {0.8d, 1d, 1.2d, 1.4d}; // Max deviation from avg. + private double[] metDevLimits = {0.4d, 0.6d, 0.8d, 1.0d}; + + private HeuristicConfigurationData _heuristicConfData; + + private void loadParameters() { + Map paramMap = _heuristicConfData.getParamMap(); + + if(paramMap.get(LOOSER_METRIC_DEV_SEVERITY) != null) { + double[] confLooserMetDevLimits = Utils.getParam(paramMap.get(LOOSER_METRIC_DEV_SEVERITY), + looserMetDevLimits.length); + if (confLooserMetDevLimits != null) { + looserMetDevLimits = confLooserMetDevLimits; + } + } + logger.info(HEURISTIC_NAME + " will use " + LOOSER_METRIC_DEV_SEVERITY + " with the following threshold settings: " + + Arrays.toString(looserMetDevLimits)); + + if(paramMap.get(METRIC_DEV_SEVERITY) != null) { + double[] confMetDevLimits = Utils.getParam(paramMap.get(METRIC_DEV_SEVERITY), metDevLimits.length); + if (confMetDevLimits != null) { + metDevLimits = confMetDevLimits; + } + } + logger.info(HEURISTIC_NAME + " will use " + METRIC_DEV_SEVERITY + " with the following threshold settings: " + + Arrays.toString(metDevLimits)); + } + + public ExecutorLoadHeuristic(HeuristicConfigurationData heuristicConfData) { + this._heuristicConfData = heuristicConfData; + loadParameters(); + } + private class ValueObserver { private final long[] _values; private Long _min; @@ -127,8 +172,8 @@ public HeuristicResult apply(SparkApplicationData data) { } } - Severity severity = Severity.max(getLooserMetricDeviationSeverity(peakMems), getMerticDeviationSeverity(durations), - getMerticDeviationSeverity(inputBytes), getLooserMetricDeviationSeverity(outputBytes)); + Severity severity = Severity.max(getLooserMetricDeviationSeverity(peakMems), getMetricDeviationSeverity(durations), + getMetricDeviationSeverity(inputBytes), getLooserMetricDeviationSeverity(outputBytes)); HeuristicResult result = new HeuristicResult(getHeuristicName(), severity); @@ -157,14 +202,16 @@ public HeuristicResult apply(SparkApplicationData data) { * @param ob * @return the corresponding severity */ - private static Severity getLooserMetricDeviationSeverity(ValueObserver ob) { + private Severity getLooserMetricDeviationSeverity(ValueObserver ob) { double diffFactor = ob.getDeviationFactor(); - return Severity.getSeverityAscending(diffFactor, 0.8d, 1d, 1.2d, 1.4d); + return Severity.getSeverityAscending( + diffFactor, looserMetDevLimits[0], looserMetDevLimits[1], looserMetDevLimits[2], looserMetDevLimits[3]); } - private static Severity getMerticDeviationSeverity(ValueObserver ob) { + private Severity getMetricDeviationSeverity(ValueObserver ob) { double diffFactor = ob.getDeviationFactor(); - return Severity.getSeverityAscending(diffFactor, 0.4d, 0.6d, 0.8d, 1.0d); + return Severity.getSeverityAscending( + diffFactor, metDevLimits[0], metDevLimits[1], metDevLimits[2], metDevLimits[3]); } @Override diff --git a/app/com/linkedin/drelephant/spark/heuristics/JobRuntimeHeuristic.java b/app/com/linkedin/drelephant/spark/heuristics/JobRuntimeHeuristic.java index 337292ff0..99a59ff01 100644 --- a/app/com/linkedin/drelephant/spark/heuristics/JobRuntimeHeuristic.java +++ b/app/com/linkedin/drelephant/spark/heuristics/JobRuntimeHeuristic.java @@ -20,11 +20,17 @@ import com.linkedin.drelephant.analysis.Severity; import com.linkedin.drelephant.spark.SparkApplicationData; import com.linkedin.drelephant.spark.SparkJobProgressData; +import com.linkedin.drelephant.util.HeuristicConfigurationData; +import com.linkedin.drelephant.util.Utils; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Set; import org.apache.commons.lang.StringUtils; +import org.apache.log4j.Logger; /** @@ -33,8 +39,48 @@ * @author yizhou */ public class JobRuntimeHeuristic implements Heuristic { + private static final Logger logger = Logger.getLogger(JobRuntimeHeuristic.class); public static final String HEURISTIC_NAME = "Spark Job Runtime"; + // Severity parameters. + private static final String AVG_JOB_FAILURE_SEVERITY = "avg_job_failure_rate_severity"; + private static final String SINGLE_JOB_FAILURE_SEVERITY = "single_job_failure_rate_severity"; + + // Default value of parameters + private double[] avgJobFailureLimits = {0.1d, 0.3d, 0.5d, 0.5d}; // The avg job failure rate + private double[] jobFailureLimits = {0.0d, 0.3d, 0.5d, 0.5d}; + + private HeuristicConfigurationData _heuristicConfData; + + private void loadParameters() { + Map paramMap = _heuristicConfData.getParamMap(); + + if(paramMap.get(AVG_JOB_FAILURE_SEVERITY) != null) { + double[] confAvgJobFailureLimits = Utils.getParam(paramMap.get(AVG_JOB_FAILURE_SEVERITY), + avgJobFailureLimits.length); + if (confAvgJobFailureLimits != null) { + avgJobFailureLimits = confAvgJobFailureLimits; + } + } + logger.info(HEURISTIC_NAME + " will use " + AVG_JOB_FAILURE_SEVERITY + " with the following threshold settings: " + + Arrays.toString(avgJobFailureLimits)); + + if(paramMap.get(SINGLE_JOB_FAILURE_SEVERITY) != null) { + double[] confJobFailureLimits = Utils.getParam(paramMap.get(SINGLE_JOB_FAILURE_SEVERITY), + jobFailureLimits.length); + if (confJobFailureLimits != null) { + jobFailureLimits = confJobFailureLimits; + } + } + logger.info(HEURISTIC_NAME + " will use " + SINGLE_JOB_FAILURE_SEVERITY + " with the following threshold settings: " + + Arrays.toString(jobFailureLimits)); + } + + public JobRuntimeHeuristic(HeuristicConfigurationData heuristicConfData) { + this._heuristicConfData = heuristicConfData; + loadParameters(); + } + @Override public HeuristicResult apply(SparkApplicationData data) { SparkJobProgressData jobProgressData = data.getJobProgressData(); @@ -78,12 +124,14 @@ public String getHeuristicName() { return HEURISTIC_NAME; } - private static Severity getAvgJobFailureRateSeverity(double rate) { - return Severity.getSeverityAscending(rate, 0.1d, 0.3d, 0.5d, 0.5d); + private Severity getAvgJobFailureRateSeverity(double rate) { + return Severity.getSeverityAscending( + rate, avgJobFailureLimits[0], avgJobFailureLimits[1], avgJobFailureLimits[2], avgJobFailureLimits[3]); } - private static Severity getSingleJobFailureRateSeverity(double rate) { - return Severity.getSeverityAscending(rate, 0.0d, 0.3d, 0.5d, 0.5d); + private Severity getSingleJobFailureRateSeverity(double rate) { + return Severity.getSeverityAscending( + rate, jobFailureLimits[0], jobFailureLimits[1], jobFailureLimits[2], jobFailureLimits[3]); } private static String getJobListString(Collection names) { diff --git a/app/com/linkedin/drelephant/spark/heuristics/MemoryLimitHeuristic.java b/app/com/linkedin/drelephant/spark/heuristics/MemoryLimitHeuristic.java index cd8941471..3a3d32ad0 100644 --- a/app/com/linkedin/drelephant/spark/heuristics/MemoryLimitHeuristic.java +++ b/app/com/linkedin/drelephant/spark/heuristics/MemoryLimitHeuristic.java @@ -21,7 +21,12 @@ import com.linkedin.drelephant.spark.SparkApplicationData; import com.linkedin.drelephant.spark.SparkEnvironmentData; import com.linkedin.drelephant.spark.SparkExecutorData; +import com.linkedin.drelephant.util.HeuristicConfigurationData; import com.linkedin.drelephant.util.MemoryFormatUtils; +import com.linkedin.drelephant.util.Utils; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; import org.apache.log4j.Logger; import org.apache.spark.SparkConf; @@ -44,6 +49,46 @@ public class MemoryLimitHeuristic implements Heuristic { public static final String SPARK_STORAGE_MEMORY_FRACTION = "spark.storage.memoryFraction"; public static final double DEFAULT_SPARK_STORAGE_MEMORY_FRACTION = 0.6d; + // Severity parameters. + private static final String MEM_UTILIZATION_SEVERITY = "mem_util_severity"; + private static final String TOTAL_MEM_SEVERITY = "total_mem_severity_in_tb"; + + // Default value of parameters + private double[] memUtilLimits = {0.8d, 0.6d, 0.4d, 0.2d}; + private double[] totalMemLimits = {0.5d, 1d, 1.5d, 2d}; // Peak Memory / Total Storage Memory + + private HeuristicConfigurationData _heuristicConfData; + + private void loadParameters() { + Map paramMap = _heuristicConfData.getParamMap(); + + if(paramMap.get(MEM_UTILIZATION_SEVERITY) != null) { + double[] confMemUtilLimits = Utils.getParam(paramMap.get(MEM_UTILIZATION_SEVERITY), memUtilLimits.length); + if (confMemUtilLimits != null) { + memUtilLimits = confMemUtilLimits; + } + } + logger.info(HEURISTIC_NAME + " will use " + MEM_UTILIZATION_SEVERITY + " with the following threshold settings: " + + Arrays.toString(memUtilLimits)); + + if(paramMap.get(TOTAL_MEM_SEVERITY) != null) { + double[] confTotalMemLimits = Utils.getParam(paramMap.get(TOTAL_MEM_SEVERITY), totalMemLimits.length); + if (confTotalMemLimits != null) { + totalMemLimits = confTotalMemLimits; + } + } + logger.info(HEURISTIC_NAME + " will use " + TOTAL_MEM_SEVERITY + " with the following threshold settings: " + + Arrays.toString(totalMemLimits)); + for (int i = 0; i < totalMemLimits.length; i++) { + totalMemLimits[i] = MemoryFormatUtils.stringToBytes(totalMemLimits[i] + "T"); + } + } + + public MemoryLimitHeuristic(HeuristicConfigurationData heuristicConfData) { + this._heuristicConfData = heuristicConfData; + loadParameters(); + } + @Override public HeuristicResult apply(SparkApplicationData data) { @@ -146,18 +191,18 @@ public String getHeuristicName() { return HEURISTIC_NAME; } - public static Severity getTotalMemorySeverity(long memory) { - return Severity - .getSeverityAscending(memory, MemoryFormatUtils.stringToBytes("0.5T"), MemoryFormatUtils.stringToBytes("1T"), - MemoryFormatUtils.stringToBytes("1.5T"), MemoryFormatUtils.stringToBytes("2T")); + public Severity getTotalMemorySeverity(long memory) { + return Severity.getSeverityAscending( + memory, totalMemLimits[0], totalMemLimits[1], totalMemLimits[2], totalMemLimits[3]); } - private static Severity getMemoryUtilizationSeverity(long peakMemory, long totalStorageMemory) { + private Severity getMemoryUtilizationSeverity(long peakMemory, long totalStorageMemory) { double fraction = peakMemory * 1.0 / totalStorageMemory; if (totalStorageMemory < MemoryFormatUtils.stringToBytes("500 GB")) { return Severity.NONE; } else { - return Severity.getSeverityDescending(fraction, 0.8d, 0.6d, 0.4d, 0.2d); + return Severity.getSeverityDescending( + fraction, memUtilLimits[0], memUtilLimits[1], memUtilLimits[2], memUtilLimits[3]); } } } diff --git a/app/com/linkedin/drelephant/spark/heuristics/StageRuntimeHeuristic.java b/app/com/linkedin/drelephant/spark/heuristics/StageRuntimeHeuristic.java index 2e43719d0..b0af2feec 100644 --- a/app/com/linkedin/drelephant/spark/heuristics/StageRuntimeHeuristic.java +++ b/app/com/linkedin/drelephant/spark/heuristics/StageRuntimeHeuristic.java @@ -21,11 +21,18 @@ import com.linkedin.drelephant.math.Statistics; import com.linkedin.drelephant.spark.SparkApplicationData; import com.linkedin.drelephant.spark.SparkJobProgressData; +import com.linkedin.drelephant.util.HeuristicConfigurationData; +import com.linkedin.drelephant.util.MemoryFormatUtils; +import com.linkedin.drelephant.util.Utils; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Set; import org.apache.commons.lang.StringUtils; +import org.apache.log4j.Logger; /** @@ -33,8 +40,62 @@ * */ public class StageRuntimeHeuristic implements Heuristic { + private static final Logger logger = Logger.getLogger(StageRuntimeHeuristic.class); public static final String HEURISTIC_NAME = "Spark Stage Runtime"; + // Severity parameters + private static final String STAGE_FAILURE_SEVERITY = "stage_failure_rate_severity"; + private static final String SINGLE_STAGE_FAILURE_SEVERITY = "single_stage_tasks_failure_rate_severity"; + private static final String STAGE_RUNTIME_SEVERITY = "stage_runtime_severity_in_min"; + + // Default value of parameters + private double[] stageFailRateLimits = {0.3d, 0.3d, 0.5d, 0.5d}; + private double[] singleStageFailLimits = {0.0d, 0.3d, 0.5d, 0.5d}; + private double[] stageRuntimeLimits = {15, 30, 60, 60}; + + private HeuristicConfigurationData _heuristicConfData; + + private void loadParameters() { + Map paramMap = _heuristicConfData.getParamMap(); + + if(paramMap.get(STAGE_FAILURE_SEVERITY) != null) { + double[] confStageFailRateLimits = Utils.getParam(paramMap.get(STAGE_FAILURE_SEVERITY), + stageFailRateLimits.length); + if (confStageFailRateLimits != null) { + stageFailRateLimits = confStageFailRateLimits; + } + } + logger.info(HEURISTIC_NAME + " will use " + STAGE_FAILURE_SEVERITY + " with the following threshold settings: " + + Arrays.toString(stageFailRateLimits)); + + if(paramMap.get(SINGLE_STAGE_FAILURE_SEVERITY) != null) { + double[] confSingleFailLimits = Utils.getParam(paramMap.get(SINGLE_STAGE_FAILURE_SEVERITY), + singleStageFailLimits.length); + if (confSingleFailLimits != null) { + singleStageFailLimits = confSingleFailLimits; + } + } + logger.info(HEURISTIC_NAME + " will use " + SINGLE_STAGE_FAILURE_SEVERITY + " with the following threshold" + + " settings: " + Arrays.toString(singleStageFailLimits)); + + if(paramMap.get(STAGE_RUNTIME_SEVERITY) != null) { + double[] confStageRuntimeLimits = Utils.getParam(paramMap.get(STAGE_RUNTIME_SEVERITY), stageRuntimeLimits.length); + if (confStageRuntimeLimits != null) { + stageRuntimeLimits = confStageRuntimeLimits; + } + } + logger.info(HEURISTIC_NAME + " will use " + STAGE_RUNTIME_SEVERITY + " with the following threshold settings: " + + Arrays.toString(stageRuntimeLimits)); + for (int i = 0; i < stageRuntimeLimits.length; i++) { + stageRuntimeLimits[i] = stageRuntimeLimits[i] * Statistics.MINUTE_IN_MS; + } + } + + public StageRuntimeHeuristic(HeuristicConfigurationData heuristicConfData) { + this._heuristicConfData = heuristicConfData; + loadParameters(); + } + @Override public HeuristicResult apply(SparkApplicationData data) { SparkJobProgressData jobProgressData = data.getJobProgressData(); @@ -84,17 +145,19 @@ public String getHeuristicName() { return HEURISTIC_NAME; } - private static Severity getStageRuntimeSeverity(long runtime) { - return Severity.getSeverityAscending(runtime, 15 * Statistics.MINUTE_IN_MS, 30 * Statistics.MINUTE_IN_MS, - 60 * Statistics.MINUTE_IN_MS, 60 * Statistics.MINUTE_IN_MS); + private Severity getStageRuntimeSeverity(long runtime) { + return Severity.getSeverityDescending( + runtime, stageRuntimeLimits[0], stageRuntimeLimits[1], stageRuntimeLimits[2], stageRuntimeLimits[3]); } - private static Severity getStageFailureRateSeverity(double rate) { - return Severity.getSeverityAscending(rate, 0.3d, 0.3d, 0.5d, 0.5d); + private Severity getStageFailureRateSeverity(double rate) { + return Severity.getSeverityDescending( + rate, stageFailRateLimits[0], stageFailRateLimits[1], stageFailRateLimits[2], stageFailRateLimits[3]); } - private static Severity getSingleStageTasksFailureRate(double rate) { - return Severity.getSeverityAscending(rate, 0.0d, 0.3d, 0.5d, 0.5d); + private Severity getSingleStageTasksFailureRate(double rate) { + return Severity.getSeverityDescending( + rate, singleStageFailLimits[0], singleStageFailLimits[1], singleStageFailLimits[2], singleStageFailLimits[3]); } private static String getStageListString(Collection names) { diff --git a/app/com/linkedin/drelephant/util/FetcherConfiguration.java b/app/com/linkedin/drelephant/util/FetcherConfiguration.java new file mode 100644 index 000000000..53eed6fd5 --- /dev/null +++ b/app/com/linkedin/drelephant/util/FetcherConfiguration.java @@ -0,0 +1,110 @@ +/* + * Copyright 2015 LinkedIn Corp. + * + * 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.linkedin.drelephant.util; + +import com.linkedin.drelephant.analysis.ApplicationType; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import java.util.Map; +import org.apache.log4j.Logger; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + + +/** + * The Fetcher Configuration Utilities + */ +public class FetcherConfiguration { + private static final Logger logger = Logger.getLogger(FetcherConfiguration.class); + private List _fetchersConfDataList; + + public FetcherConfiguration(Element configuration) { + parseFetcherConfiguration(configuration); + } + + /** + * Returns the list of Fetchers along with their Configuration Information + * + * @return A list of Configuration Data for the fetchers + */ + public List getFetchersConfigurationData() { + return _fetchersConfDataList; + } + + /** + * Parses the Fetcher configuration file and loads the Fetcher Information to a list of FetcherConfigurationData + * + * @param configuration The dom Element to be parsed + */ + private void parseFetcherConfiguration(Element configuration) { + _fetchersConfDataList = new ArrayList(); + + NodeList nodes = configuration.getChildNodes(); + int n = 0; + for (int i = 0; i < nodes.getLength(); i++) { + // Each heuristic node + Node node = nodes.item(i); + if (node.getNodeType() == Node.ELEMENT_NODE) { + n++; + Element fetcherNode = (Element) node; + + String className; + Node classNameNode = fetcherNode.getElementsByTagName("classname").item(0); + if (classNameNode == null) { + throw new RuntimeException("No tag 'classname' in fetcher " + n); + } + className = classNameNode.getTextContent(); + if (className.equals("")) { + throw new RuntimeException("Empty tag 'classname' in fetcher " + n); + } + + Node appTypeNode = fetcherNode.getElementsByTagName("applicationtype").item(0); + if (appTypeNode == null) { + throw new RuntimeException( + "No tag or invalid tag 'applicationtype' in fetcher " + n + " classname " + className); + } + String appTypeStr = appTypeNode.getTextContent(); + if (appTypeStr == null) { + logger.error("Application type is not specified in fetcher " + n + " classname " + className + + ". Skipping this configuration."); + continue; + } + ApplicationType appType = new ApplicationType(appTypeStr); + + // Check if parameters are defined for the heuristic + Map paramsMap = new HashMap(); + Node paramsNode = fetcherNode.getElementsByTagName("params").item(0); + if (paramsNode != null) { + NodeList paramsList = paramsNode.getChildNodes(); + for (int j = 0; j < paramsList.getLength(); j++) { + Node paramNode = paramsList.item(j); + if (paramNode != null && !paramsMap.containsKey(paramNode.getNodeName())) { + paramsMap.put(paramNode.getNodeName(), paramNode.getTextContent()); + } + } + } + + FetcherConfigurationData fetcherData = new FetcherConfigurationData(className, appType, paramsMap); + _fetchersConfDataList.add(fetcherData); + + } + } + } + +} diff --git a/app/com/linkedin/drelephant/util/FetcherConfigurationData.java b/app/com/linkedin/drelephant/util/FetcherConfigurationData.java new file mode 100644 index 000000000..168280794 --- /dev/null +++ b/app/com/linkedin/drelephant/util/FetcherConfigurationData.java @@ -0,0 +1,47 @@ +/* + * Copyright 2015 LinkedIn Corp. + * + * 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.linkedin.drelephant.util; + +import com.linkedin.drelephant.analysis.ApplicationType; +import java.util.Map; + + +/** + * The Fetcher Configuration Holder + */ +public class FetcherConfigurationData { + private final String _className; + private final ApplicationType _appType; + private final Map _paramMap; + + public FetcherConfigurationData(String className, ApplicationType appType, Map paramMap) { + _className = className; + _appType = appType; + _paramMap = paramMap; + } + + public String getClassName() { + return _className; + } + + public ApplicationType getAppType() { + return _appType; + } + + public Map getParamMap() { + return _paramMap; + } +} diff --git a/app/com/linkedin/drelephant/util/HeuristicConfiguration.java b/app/com/linkedin/drelephant/util/HeuristicConfiguration.java index bbbab6bd7..9838267ac 100644 --- a/app/com/linkedin/drelephant/util/HeuristicConfiguration.java +++ b/app/com/linkedin/drelephant/util/HeuristicConfiguration.java @@ -17,8 +17,10 @@ import com.linkedin.drelephant.analysis.ApplicationType; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.apache.log4j.Logger; import org.w3c.dom.Element; import org.w3c.dom.Node; @@ -43,6 +45,7 @@ private void parseHeuristicConfiguration(Element configuration) { NodeList nodes = configuration.getChildNodes(); int n = 0; for (int i = 0; i < nodes.getLength(); i++) { + // Each heuristic node Node node = nodes.item(i); if (node.getNodeType() == Node.ELEMENT_NODE) { n++; @@ -91,11 +94,25 @@ private void parseHeuristicConfiguration(Element configuration) { } ApplicationType appType = new ApplicationType(appTypeStr); + // Check if parameters are defined for the heuristic + Map paramsMap = new HashMap(); + Node paramsNode = heuristicNode.getElementsByTagName("params").item(0); + if (paramsNode != null) { + NodeList paramsList = paramsNode.getChildNodes(); + for (int j = 0; j < paramsList.getLength(); j++) { + Node paramNode = paramsList.item(j); + if (paramNode != null && !paramsMap.containsKey(paramNode.getNodeName())) { + paramsMap.put(paramNode.getNodeName(), paramNode.getTextContent()); + } + } + } + HeuristicConfigurationData heuristicData = new HeuristicConfigurationData(heuristicName, className, viewName, - appType); + appType, paramsMap); _heuristicsConfDataList.add(heuristicData); } } } + } diff --git a/app/com/linkedin/drelephant/util/HeuristicConfigurationData.java b/app/com/linkedin/drelephant/util/HeuristicConfigurationData.java index ccd90d1cf..d0d96c866 100644 --- a/app/com/linkedin/drelephant/util/HeuristicConfigurationData.java +++ b/app/com/linkedin/drelephant/util/HeuristicConfigurationData.java @@ -16,6 +16,7 @@ package com.linkedin.drelephant.util; import com.linkedin.drelephant.analysis.ApplicationType; +import java.util.Map; public class HeuristicConfigurationData { @@ -23,12 +24,15 @@ public class HeuristicConfigurationData { private final String _className; private final String _viewName; private final ApplicationType _appType; + private final Map _paramMap; - public HeuristicConfigurationData(String heuristicName, String className, String viewName, ApplicationType appType) { + public HeuristicConfigurationData(String heuristicName, String className, String viewName, ApplicationType appType, + Map paramMap) { _heuristicName = heuristicName; _className = className; _viewName = viewName; _appType = appType; + _paramMap = paramMap; } public String getHeuristicName() { @@ -46,4 +50,8 @@ public String getViewName() { public ApplicationType getAppType() { return _appType; } + + public Map getParamMap() { + return _paramMap; + } } diff --git a/app/com/linkedin/drelephant/util/Utils.java b/app/com/linkedin/drelephant/util/Utils.java index c04cfc09b..56d2c7d92 100644 --- a/app/com/linkedin/drelephant/util/Utils.java +++ b/app/com/linkedin/drelephant/util/Utils.java @@ -17,11 +17,13 @@ import com.linkedin.drelephant.DaliMetricsAPI; import com.linkedin.drelephant.ElephantContext; +import com.linkedin.drelephant.analysis.Severity; import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -29,6 +31,9 @@ import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; @@ -243,4 +248,38 @@ public static String[] parseCsvLine(String line) { store.add(curVal.toString()); return store.toArray(new String[store.size()]); } + + /** + * Returns the configured thresholds after evaluating and verifying the levels. + * + * @param rawLimits A comma separated string of threshold limits + * @param thresholdLevels The number of threshold levels + * @return The evaluated threshold limits + */ + public static double[] getParam(String rawLimits, int thresholdLevels) { + double[] parsedLimits = new double[thresholdLevels]; + + if (rawLimits != null) { + String[] thresholds = rawLimits.split(","); + if (thresholds.length != thresholdLevels) { + logger.error("Could not find " + thresholdLevels + " threshold levels in " + rawLimits); + parsedLimits = null; + } else { + // Evaluate the limits + ScriptEngineManager mgr = new ScriptEngineManager(); + ScriptEngine engine = mgr.getEngineByName("JavaScript"); + for (int i = 0; i < thresholdLevels; i++) { + try { + parsedLimits[i] = Double.parseDouble(engine.eval(thresholds[i]).toString()); + } catch (ScriptException e) { + logger.error("Could not evaluate " + thresholds[i] + " in " + rawLimits); + parsedLimits = null; + } + } + } + } + + return parsedLimits; + } + } diff --git a/app/org/apache/spark/deploy/history/SparkFSFetcher.scala b/app/org/apache/spark/deploy/history/SparkFSFetcher.scala index 08d974543..33d837118 100644 --- a/app/org/apache/spark/deploy/history/SparkFSFetcher.scala +++ b/app/org/apache/spark/deploy/history/SparkFSFetcher.scala @@ -21,11 +21,9 @@ import java.security.PrivilegedAction import java.io.BufferedInputStream import java.io.InputStream - - -import com.linkedin.drelephant.HadoopSecurity -import com.linkedin.drelephant.analysis.AnalyticJob; -import com.linkedin.drelephant.analysis.ElephantFetcher +import com.linkedin.drelephant.util.{MemoryFormatUtils, FetcherConfigurationData, Utils} +import com.linkedin.drelephant.{ElephantContext, HadoopSecurity} +import com.linkedin.drelephant.analysis.{ApplicationType, AnalyticJob, ElephantFetcher}; import com.linkedin.drelephant.spark.SparkApplicationData import org.apache.hadoop.conf.Configuration import org.apache.hadoop.fs.{Path, FileSystem} @@ -46,11 +44,18 @@ import org.apache.spark.io.CompressionCodec * * @author yizhou */ -class SparkFSFetcher extends ElephantFetcher[SparkApplicationData] { - +class SparkFSFetcher(fetcherConfData: FetcherConfigurationData) extends ElephantFetcher[SparkApplicationData] { import SparkFSFetcher._ + if (fetcherConfData.getParamMap.get(LOG_SIZE_XML_FIELD) != null) { + val logLimitSize = Utils.getParam(fetcherConfData.getParamMap.get(LOG_SIZE_XML_FIELD), 1) + if (logLimitSize != null) { + EVENT_LOG_SIZE_LIMIT_MB = MemoryFormatUtils.stringToBytes(logLimitSize(0) + "M"); + } + } + logger.info("The event log limit of Spark application is set to " + EVENT_LOG_SIZE_LIMIT_MB + " MB") + private val _sparkConf = new SparkConf() /* Lazy loading for the log directory is very important. Hadoop Configuration() takes time to load itself to reflect @@ -150,7 +155,7 @@ class SparkFSFetcher extends ElephantFetcher[SparkApplicationData] { dataCollection.getConf().setProperty("spark.app.id", appId) logger.info("The event log of Spark application: " + appId + " is over the limit size of " - + EVENT_LOG_SIZE_LIMIT + " bytes, the parsing process gets throttled.") + + EVENT_LOG_SIZE_LIMIT_MB + " MB, the parsing process gets throttled.") } else { logger.info("Replaying Spark logs for application: " + appId) @@ -217,15 +222,18 @@ class SparkFSFetcher extends ElephantFetcher[SparkApplicationData] { * @return If the event log parsing should be throttled */ private def shouldThrottle(eventLogPath: Path): Boolean = { - fs.getFileStatus(eventLogPath).getLen() > EVENT_LOG_SIZE_LIMIT + fs.getFileStatus(eventLogPath).getLen() > EVENT_LOG_SIZE_LIMIT_MB } } private object SparkFSFetcher { private val logger = Logger.getLogger(SparkFSFetcher.getClass) + val DEFAULT_LOG_DIR = "/system/spark-history" - val EVENT_LOG_SIZE_LIMIT = 104857600L // 100MB + + val LOG_SIZE_XML_FIELD = "event_log_size_limit_in_mb" + var EVENT_LOG_SIZE_LIMIT_MB = 100d // 100MB // Constants used to parse <= Spark 1.2.0 log directories. val LOG_PREFIX = "EVENT_LOG_" diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/JobQueueLimitHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/JobQueueLimitHeuristicTest.java index afbea960c..dcc9a8f83 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/JobQueueLimitHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/JobQueueLimitHeuristicTest.java @@ -15,7 +15,12 @@ */ package com.linkedin.drelephant.mapreduce.heuristics; +import com.linkedin.drelephant.analysis.ApplicationType; +import com.linkedin.drelephant.util.HeuristicConfigurationData; import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.Properties; import org.junit.Test; @@ -31,8 +36,10 @@ public class JobQueueLimitHeuristicTest extends TestCase { + private static Map paramMap = new HashMap(); + private static Heuristic _heuristic = new JobQueueLimitHeuristic(new HeuristicConfigurationData("test_heuristic", "test_class", + "test_view", new ApplicationType("test_apptype"), paramMap)); - Heuristic _heuristic = new JobQueueLimitHeuristic(); private static final int NUM_TASKS = 100; @Test diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperDataSkewHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperDataSkewHeuristicTest.java index ca10b3cff..30ac2c078 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperDataSkewHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperDataSkewHeuristicTest.java @@ -15,6 +15,7 @@ */ package com.linkedin.drelephant.mapreduce.heuristics; +import com.linkedin.drelephant.analysis.ApplicationType; import com.linkedin.drelephant.analysis.HDFSContext; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; @@ -23,15 +24,22 @@ import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; import com.linkedin.drelephant.mapreduce.MapReduceTaskData; +import com.linkedin.drelephant.util.HeuristicConfigurationData; import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import junit.framework.TestCase; public class MapperDataSkewHeuristicTest extends TestCase { private static final long UNITSIZE = HDFSContext.HDFS_BLOCK_SIZE / 64; //1MB - Heuristic _heuristic = new MapperDataSkewHeuristic(); + + private static Map paramsMap = new HashMap(); + private static Heuristic _heuristic = new MapperDataSkewHeuristic(new HeuristicConfigurationData("test_heuristic", + "test_class", "test_view", new ApplicationType("test_apptype"), paramsMap)); public void testCritical() throws IOException { assertEquals(Severity.CRITICAL, analyzeJob(200, 200, 1 * UNITSIZE, 100 * UNITSIZE)); diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperGCHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperGCHeuristicTest.java index 685356cb2..e811fa1be 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperGCHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperGCHeuristicTest.java @@ -1,5 +1,6 @@ package com.linkedin.drelephant.mapreduce.heuristics; +import com.linkedin.drelephant.analysis.ApplicationType; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; @@ -7,12 +8,20 @@ import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; import com.linkedin.drelephant.mapreduce.MapReduceTaskData; +import com.linkedin.drelephant.util.HeuristicConfigurationData; import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import junit.framework.TestCase; public class MapperGCHeuristicTest extends TestCase { - Heuristic _heuristic = new MapperGCHeuristic(); + + private static Map paramsMap = new HashMap(); + private static Heuristic _heuristic = new MapperGCHeuristic(new HeuristicConfigurationData("test_heuristic", + "test_class", "test_view", new ApplicationType("test_apptype"), paramsMap)); + private static int NUMTASKS = 100; public void testGCCritical() throws IOException { diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperMemoryHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperMemoryHeuristicTest.java index a3cdd5150..1f91cd67b 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperMemoryHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperMemoryHeuristicTest.java @@ -15,6 +15,7 @@ */ package com.linkedin.drelephant.mapreduce.heuristics; +import com.linkedin.drelephant.analysis.ApplicationType; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; @@ -22,7 +23,11 @@ import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; import com.linkedin.drelephant.mapreduce.MapReduceTaskData; +import com.linkedin.drelephant.util.HeuristicConfigurationData; import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.Properties; import org.apache.commons.io.FileUtils; @@ -31,7 +36,11 @@ public class MapperMemoryHeuristicTest extends TestCase { - Heuristic _heuristic = new MapperMemoryHeuristic(); + + private static Map paramsMap = new HashMap(); + private static Heuristic _heuristic = new MapperMemoryHeuristic(new HeuristicConfigurationData("test_heuristic", + "test_class", "test_view", new ApplicationType("test_apptype"), paramsMap)); + private int NUMTASKS = 100; public void testLargeContainerSizeCritical() throws IOException { diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristicTest.java index caffd3056..51dbe5205 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristicTest.java @@ -15,6 +15,7 @@ */ package com.linkedin.drelephant.mapreduce.heuristics; +import com.linkedin.drelephant.analysis.ApplicationType; import com.linkedin.drelephant.analysis.HDFSContext; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; @@ -24,13 +25,20 @@ import com.linkedin.drelephant.mapreduce.MapReduceTaskData; import com.linkedin.drelephant.math.Statistics; +import com.linkedin.drelephant.util.HeuristicConfigurationData; import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import junit.framework.TestCase; public class MapperSpeedHeuristicTest extends TestCase { - Heuristic _heuristic = new MapperSpeedHeuristic(); + private static Map paramsMap = new HashMap(); + private static Heuristic _heuristic = new MapperSpeedHeuristic(new HeuristicConfigurationData("test_heuristic", + "test_class", "test_view", new ApplicationType("test_apptype"), paramsMap)); + private static final long UNITSIZE = HDFSContext.HDFS_BLOCK_SIZE / 64; private static final long MINUTE_IN_MS = Statistics.MINUTE_IN_MS; private static final int NUMTASKS = 100; diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristicTest.java index 791cf98c6..b05ff9034 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristicTest.java @@ -15,19 +15,26 @@ */ package com.linkedin.drelephant.mapreduce.heuristics; +import com.linkedin.drelephant.analysis.ApplicationType; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; import com.linkedin.drelephant.mapreduce.MapReduceTaskData; +import com.linkedin.drelephant.util.HeuristicConfigurationData; import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import junit.framework.TestCase; public class MapperSpillHeuristicTest extends TestCase { - Heuristic heuristic = new MapperSpillHeuristic(); + private static Map paramsMap = new HashMap(); + private static Heuristic _heuristic = new MapperSpillHeuristic(new HeuristicConfigurationData("test_heuristic", + "test_class", "test_view", new ApplicationType("test_apptype"), paramsMap)); public void testCritical() throws IOException { // Spill ratio 3.0, 1000 tasks @@ -72,7 +79,7 @@ private Severity analyzeJob(long spilledRecords, long mapRecords, int numTasks) } MapReduceApplicationData data = new MapReduceApplicationData().setCounters(jobCounter).setMapperData(mappers); - HeuristicResult result = heuristic.apply(data); + HeuristicResult result = _heuristic.apply(data); return result.getSeverity(); } } diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristicTest.java index 3f16afa74..43ab9a592 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristicTest.java @@ -15,6 +15,7 @@ */ package com.linkedin.drelephant.mapreduce.heuristics; +import com.linkedin.drelephant.analysis.ApplicationType; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; @@ -22,14 +23,21 @@ import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; import com.linkedin.drelephant.mapreduce.MapReduceTaskData; import com.linkedin.drelephant.math.Statistics; +import com.linkedin.drelephant.util.HeuristicConfigurationData; import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import junit.framework.TestCase; public class MapperTimeHeuristicTest extends TestCase { private static final long DUMMY_INPUT_SIZE = 0; - Heuristic _heuristic = new MapperTimeHeuristic(); + + private static Map paramsMap = new HashMap(); + private static Heuristic _heuristic = new MapperTimeHeuristic(new HeuristicConfigurationData("test_heuristic", + "test_class", "test_view", new ApplicationType("test_apptype"), paramsMap)); // Test batch 1: Large runtime. Heuristic is not affected by various number of tasks */ diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerDataSkewHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerDataSkewHeuristicTest.java index 8752011ba..048d4ac21 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerDataSkewHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerDataSkewHeuristicTest.java @@ -13,8 +13,10 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.mapreduce.heuristics; +import com.linkedin.drelephant.analysis.ApplicationType; import com.linkedin.drelephant.analysis.HDFSContext; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; @@ -23,14 +25,21 @@ import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; import com.linkedin.drelephant.mapreduce.MapReduceTaskData; +import com.linkedin.drelephant.util.HeuristicConfigurationData; import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import junit.framework.TestCase; public class ReducerDataSkewHeuristicTest extends TestCase { private static final long UNITSIZE = HDFSContext.HDFS_BLOCK_SIZE / 64; //1mb - Heuristic _heuristic = new ReducerDataSkewHeuristic(); + + private static Map paramsMap = new HashMap(); + private static Heuristic _heuristic = new ReducerDataSkewHeuristic(new HeuristicConfigurationData("test_heuristic", + "test_class", "test_view", new ApplicationType("test_apptype"), paramsMap)); public void testCritical() throws IOException { assertEquals(Severity.CRITICAL, analyzeJob(200, 200, 1 * UNITSIZE, 100 * UNITSIZE)); diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerGCHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerGCHeuristicTest.java index 8630dc3d8..9893c5041 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerGCHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerGCHeuristicTest.java @@ -1,5 +1,6 @@ package com.linkedin.drelephant.mapreduce.heuristics; +import com.linkedin.drelephant.analysis.ApplicationType; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; @@ -7,12 +8,19 @@ import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; import com.linkedin.drelephant.mapreduce.MapReduceTaskData; +import com.linkedin.drelephant.util.HeuristicConfigurationData; import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import junit.framework.TestCase; public class ReducerGCHeuristicTest extends TestCase { - Heuristic _heuristic = new ReducerGCHeuristic(); + private static Map paramsMap = new HashMap(); + private static Heuristic _heuristic = new ReducerGCHeuristic(new HeuristicConfigurationData("test_heuristic", + "test_class", "test_view", new ApplicationType("test_apptype"), paramsMap)); + private static int NUMTASKS = 100; public void testGCCritical() throws IOException { diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerMemoryHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerMemoryHeuristicTest.java index 763faca31..fe781ab20 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerMemoryHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerMemoryHeuristicTest.java @@ -15,6 +15,7 @@ */ package com.linkedin.drelephant.mapreduce.heuristics; +import com.linkedin.drelephant.analysis.ApplicationType; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; @@ -22,7 +23,11 @@ import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; import com.linkedin.drelephant.mapreduce.MapReduceTaskData; +import com.linkedin.drelephant.util.HeuristicConfigurationData; import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.Properties; import org.apache.commons.io.FileUtils; @@ -31,7 +36,10 @@ public class ReducerMemoryHeuristicTest extends TestCase { - Heuristic _heuristic = new ReducerMemoryHeuristic(); + private static Map paramsMap = new HashMap(); + private static Heuristic _heuristic = new ReducerMemoryHeuristic(new HeuristicConfigurationData("test_heuristic", + "test_class", "test_view", new ApplicationType("test_apptype"), paramsMap)); + private int NUMTASKS = 100; public void testLargeContainerSizeCritical() throws IOException { diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristicTest.java index 20a545113..15ceec36e 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristicTest.java @@ -15,6 +15,8 @@ */ package com.linkedin.drelephant.mapreduce.heuristics; +import com.linkedin.drelephant.analysis.ApplicationType; +import com.linkedin.drelephant.util.HeuristicConfigurationData; import java.io.IOException; import com.linkedin.drelephant.analysis.Heuristic; @@ -25,11 +27,18 @@ import com.linkedin.drelephant.mapreduce.MapReduceTaskData; import com.linkedin.drelephant.math.Statistics; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import junit.framework.TestCase; public class ReducerTimeHeuristicTest extends TestCase { - Heuristic _heuristic = new ReducerTimeHeuristic(); + + private static Map paramsMap = new HashMap(); + private static Heuristic _heuristic = new ReducerTimeHeuristic(new HeuristicConfigurationData("test_heuristic", + "test_class", "test_view", new ApplicationType("test_apptype"), paramsMap)); + private static final long MINUTE_IN_MS = Statistics.MINUTE_IN_MS;; public void testShortRunetimeCritical() throws IOException { diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristicTest.java index 4d9bfc9dd..8a767728e 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristicTest.java @@ -15,6 +15,8 @@ */ package com.linkedin.drelephant.mapreduce.heuristics; +import com.linkedin.drelephant.analysis.ApplicationType; +import com.linkedin.drelephant.util.HeuristicConfigurationData; import java.io.IOException; import com.linkedin.drelephant.analysis.Heuristic; @@ -25,11 +27,18 @@ import com.linkedin.drelephant.mapreduce.MapReduceTaskData; import com.linkedin.drelephant.math.Statistics; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import junit.framework.TestCase; public class ShuffleSortHeuristicTest extends TestCase { - Heuristic _heuristic = new ShuffleSortHeuristic(); + + private static Map paramsMap = new HashMap(); + private static Heuristic _heuristic = new ShuffleSortHeuristic(new HeuristicConfigurationData("test_heuristic", + "test_class", "test_view", new ApplicationType("test_apptype"), paramsMap)); + private static final int NUMTASKS = 100; private static final long MINUTE_IN_MS = Statistics.MINUTE_IN_MS;; diff --git a/test/com/linkedin/drelephant/spark/heuristics/BestPropertiesConventionHeuristicTest.java b/test/com/linkedin/drelephant/spark/heuristics/BestPropertiesConventionHeuristicTest.java index 32354d8b0..271364544 100644 --- a/test/com/linkedin/drelephant/spark/heuristics/BestPropertiesConventionHeuristicTest.java +++ b/test/com/linkedin/drelephant/spark/heuristics/BestPropertiesConventionHeuristicTest.java @@ -1,8 +1,15 @@ package com.linkedin.drelephant.spark.heuristics; +import com.linkedin.drelephant.analysis.ApplicationType; +import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.Severity; +import com.linkedin.drelephant.mapreduce.heuristics.ShuffleSortHeuristic; import com.linkedin.drelephant.spark.MockSparkApplicationData; import com.linkedin.drelephant.spark.SparkApplicationData; +import com.linkedin.drelephant.util.HeuristicConfigurationData; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.Properties; import junit.framework.TestCase; @@ -66,6 +73,8 @@ private Severity analyzeJob(Properties sparkProperties) { for (String key : sparkProperties.stringPropertyNames()) { data.getEnvironmentData().addSparkProperty(key, sparkProperties.getProperty(key)); } - return new BestPropertiesConventionHeuristic().apply(data).getSeverity(); + Map paramsMap = new HashMap(); + return new BestPropertiesConventionHeuristic(new HeuristicConfigurationData("test_heuristic", "test_class", + "test_view", new ApplicationType("test_apptype"), paramsMap)).apply(data).getSeverity(); } } diff --git a/test/com/linkedin/drelephant/spark/heuristics/MemoryLimitHeuristicTest.java b/test/com/linkedin/drelephant/spark/heuristics/MemoryLimitHeuristicTest.java index 539521d16..8927db2c0 100644 --- a/test/com/linkedin/drelephant/spark/heuristics/MemoryLimitHeuristicTest.java +++ b/test/com/linkedin/drelephant/spark/heuristics/MemoryLimitHeuristicTest.java @@ -1,13 +1,18 @@ package com.linkedin.drelephant.spark.heuristics; +import com.linkedin.drelephant.analysis.ApplicationType; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; import com.linkedin.drelephant.spark.MockSparkApplicationData; import com.linkedin.drelephant.spark.SparkApplicationData; import com.linkedin.drelephant.spark.SparkEnvironmentData; import com.linkedin.drelephant.spark.SparkExecutorData; +import com.linkedin.drelephant.util.HeuristicConfigurationData; import com.linkedin.drelephant.util.MemoryFormatUtils; +import java.util.HashMap; import java.util.HashSet; +import java.util.List; +import java.util.Map; import java.util.Set; import junit.framework.TestCase; @@ -112,6 +117,8 @@ private HeuristicResult getJobresult(int executors, String memPerExecutor, Strin info.memUsed = peakMemToGenerate; exe.setExecutorInfo(String.valueOf(i), info); } - return new MemoryLimitHeuristic().apply(data); + Map paramsMap = new HashMap(); + return new MemoryLimitHeuristic(new HeuristicConfigurationData("test_heuristic", "test_class", "test_view", + new ApplicationType("test_apptype"), paramsMap)).apply(data); } } diff --git a/test/com/linkedin/drelephant/util/UtilsTest.java b/test/com/linkedin/drelephant/util/UtilsTest.java index 07c1b724a..3ba5d9e54 100644 --- a/test/com/linkedin/drelephant/util/UtilsTest.java +++ b/test/com/linkedin/drelephant/util/UtilsTest.java @@ -15,6 +15,8 @@ */ package com.linkedin.drelephant.util; +import com.linkedin.drelephant.analysis.ApplicationType; +import java.util.HashMap; import java.util.Map; import junit.framework.TestCase; @@ -35,4 +37,28 @@ public void testParseJavaOptions() { assertEquals("bar2", options2.get("foo2")); assertEquals("bar3", options2.get("foo3")); } + + public void testGetParam() { + Map paramMap = new HashMap(); + paramMap.put("test_severity_1", "10, 50, 100, 200"); + paramMap.put("test_severity_2", "2, 4, 8"); + paramMap.put("test_param_1", "2!"); + paramMap.put("test_param_2", "2"); + + double limits1[] = Utils.getParam(paramMap.get("test_severity_1"), 4); + assertEquals(10d, limits1[0]); + assertEquals(50d, limits1[1]); + assertEquals(100d, limits1[2]); + assertEquals(200d, limits1[3]); + + double limits2[] = Utils.getParam(paramMap.get("test_severity_2"), 4); + assertEquals(null, limits2); + + double limits3[] = Utils.getParam(paramMap.get("test_param_1"), 1); + assertEquals(null, limits3); + + double limits4[] = Utils.getParam(paramMap.get("test_param_2"), 1); + assertEquals(2d, limits4[0]); + } + } From 6ce11ca0301d9bbc7bb0f9cf47c8d6e383079e9b Mon Sep 17 00:00:00 2001 From: akrai Date: Fri, 29 Jan 2016 21:33:25 +0530 Subject: [PATCH 126/272] LIHADOOP-17131 Prepare Readme for Dr. Elephant RB=650949 G=superfriends-reviewers R=annag,fli,rratti,shanm,viramach A=viramach --- README.md | 111 ++++++++++++++++++++++++++++------ images/readme/critical.png | Bin 0 -> 3652 bytes images/readme/dashboard.png | Bin 0 -> 749091 bytes images/readme/jobdetails.png | Bin 0 -> 400180 bytes images/readme/low.png | Bin 0 -> 3652 bytes images/readme/moderate.png | Bin 0 -> 3652 bytes images/readme/none.png | Bin 0 -> 3652 bytes images/readme/search.png | Bin 0 -> 846342 bytes images/readme/severe.png | Bin 0 -> 3652 bytes images/readme/suggestions.png | Bin 0 -> 503587 bytes 10 files changed, 94 insertions(+), 17 deletions(-) create mode 100644 images/readme/critical.png create mode 100755 images/readme/dashboard.png create mode 100755 images/readme/jobdetails.png create mode 100644 images/readme/low.png create mode 100644 images/readme/moderate.png create mode 100644 images/readme/none.png create mode 100755 images/readme/search.png create mode 100644 images/readme/severe.png create mode 100755 images/readme/suggestions.png diff --git a/README.md b/README.md index e9ffcd502..b1e597751 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,81 @@ +# Dr. Elephant - Copyright 2015 LinkedIn Corp. +Dr. Elephant is a performance monitoring and tuning tool for Hadoop. The goal of Dr. Elephant is to improve developer +productivity and increase cluster efficiency by making it easier to tune Hadoop jobs. It analyzes Hadoop jobs using a +set of configurable heuristics that provide insights on how a job performed and uses the results to make suggestions on +how to tune the job to make it perform more efficiently. - 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 +## Why Dr. Elephant? +Efficient use of Hadoop cluster resources, and developer productivity, are big problems for users of Hadoop. There are +no actively maintained tools provided by the open source community to bridge this gap. Dr. Elephant, in addition to +solving this problem, is easy to use and extensible. - http://www.apache.org/licenses/LICENSE-2.0 +## Key Features +* Pluggable and configurable Heuristics that diagnose a job +* Integration with Azkaban scheduler and designed to integrate with any hadoop scheduler such as Oozie. +* Representation of historic performance of jobs and flows +* Job level comparison of flows +* Diagnostic heuristics for Map/Reduce and Spark +* Easily extendable to newer job types, applications and schedulers +* Rest API to fetch all the information - 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. +## How does it work? +Dr. Elephant gets a list of all recent succeeded and failed applications, once every minute, from the Resource manager. +The metadata for each application, viz, the job counters, configurations and the task data, are fetched from the Job +History server. Once it has all the metadata, Dr. Elephant runs a set of different Heuristics on them and generates a +diagnostic report on how the individual heuristics and the job as a whole performed. These are then tagged with one of +five severity levels, to indicate potential performance problems. + +## Use Cases +At Linkedin, developers use Dr. Elephant for a number of different use cases including monitoring how their flow is +performing on the cluster, understanding why their flow is running slow, how and what can be tuned to improve their +flow, comparing their flow against previous executions, troubleshooting etc. Dr. Elephant’s performance green-lighting +is a prerequisite to run jobs on production clusters. + +## Sample Job Analysis/Tuning +Dr. Elephant’s home page, or the dashboard, includes all the latest analysed jobs along with some statistics. + +unable to load image + +Once a job completes, it can be found in the Dashboard, or by filtering on the Search page. One can filter jobs by the +job id, the flow execution url(if scheduled from a scheduler), the user who triggered the job, job finish time, the type +of the job, or even based on severity of the individual heuristics. + +unable to load image + +The search results provide a high level analysis report of the jobs using color coding to represent severity levels on +how the job and the heuristics performed. The color Red means the job is in critical state and requires tuning while +Green means the job is running efficiently. + +## Severity levels + +Severity is a measure of the job's performance. It says how severe a job is in terms of efficiency. There are five +severity levels that judge a heuristic/job based on the configured thresholds. The 5 severities in the decreasing order +of severeness are + +CRTICAL > SEVERE > MODERATE > LOW > NONE + +| SEVERITY | COLOR | DESCRIPTION | +| -------- | --------------------------------------- | -------------------------------------------------- | +| CRITICAL | ![Alt text](images/readme/critical.png) | The job is in critical state and must be tuned | +| SEVERE | ![Alt text](images/readme/severe.png) | There is scope for improvement | +| MODERATE | ![Alt text](images/readme/moderate.png) | There is scope for further improvement | +| LOW | ![Alt text](images/readme/low.png) | There is scope for few minor improvements | +| NONE | ![Alt text](images/readme/none.png) | The job is safe. No tuning necessary | +Once one filters and identifies one’s job, one can click on the result to get the complete report. The report includes +details on each of the individual heuristics and a link, [Explain], which provides suggestions on how to tune the job to +improve that heuristic. -# Dr Elephant +unable to load image + +unable to load image + +## Heuristics +One can go to the help page in Dr. Elephant to see what each of the heuristics mean. For more information on the +Heuristics, one can refer to the complete documentation here . + +## Dr. Elephant Setup ### Compiling & testing locally @@ -127,9 +188,9 @@ cd /export/apps/elephant/ ``` mapreduce - Job Queue Limit - com.linkedin.drelephant.mapreduce.heuristics.JobQueueLimitHeuristic - views.html.helpJobQueueLimit + Mapper GC + com.linkedin.drelephant.mapreduce.heuristics.MapperGCHeuristic + views.html.help.mapreduce.helpGC ``` * A sample entry showing how to override/configure severity thresholds would look like, @@ -140,10 +201,26 @@ cd /export/apps/elephant/ com.linkedin.drelephant.mapreduce.heuristics.MapperDataSkewHeuristic views.html.help.mapreduce.helpMapperDataSkew - 10, 50, 100, 200 - 2, 4, 8, 16 - 1/8, 1/4, 1/2, 1 + 10, 50, 100, 200 + 2, 4, 8, 16 + 1/8, 1/4, 1/2, 1 ``` * Run Doctor Elephant, it should now include the new heuristics. + +## License + + Copyright 2015 LinkedIn Corp. + + 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. \ No newline at end of file diff --git a/images/readme/critical.png b/images/readme/critical.png new file mode 100644 index 0000000000000000000000000000000000000000..26e83f6231417aae4dbf5a2c7e9e5a12107ea3ab GIT binary patch literal 3652 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 z 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

;da9GV}_8MIq_xhb5>T0NyWs_y8blH>t<`eo1 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μ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 z zHg)$KX^4VIwC4C|e=~`Y7@@kGz;yy%c1sDZFm+&uJu}`eC3pq-J)kJQsFk1ZOOJz( z!$qPRjecg}MP=;YJO2-fX}x#d6toxSk1dZK_lAE*=Px1MM%)_m7^-8D+v@aPMg`wk z+_(Gtd=gm084Sbhi`z_|x0y)!`H3SKA-FzG(9wNu1QP*imx1?Ei>>1h?KuI+0$xQ! zJW3I#gDAkmsUV_^1#dV^Jqasj*GnwfXZs=erZKRwXZ30LiyF^`>+0*1?5w{$OY9q4{*sn<2@dW4ALleAQf}^8*Yhia zAsJenQu~)6yY7=(&eX~Z9wc#wy6$XmZ^Dt~Xb1=y4YDq*#zGkJP%`3NdMj@pVfY?$ z!@-CRW+EaYqFUE-9ZtxtRWURqLO^|Z4}!${XDuhR6gW8)S+2?}C_s8F9$2X&WrM)X zje&ttJa?m4W_~Fv1$9Pi#ER(2YANKtIeMpnUmfCUkm2S2j2BIlT|H}Skigdq;3*}G8_ZV3 zWLG=~3k`K)F_nqCfeQP0`RytpQa>wC8X|>Q|Hyw0Mhfeh!qq{C&%L?Sd-WCcRz&+={{FbS2z2QMf2G~m`wvW)smXaLZz;rtpKyXU{V^rf?B%&ps*QKIw zh!^`CCo+`U7|j@3o4F31i^P1}>AATBQz{wYTBg&t?m=-iUhP!IgUuWOBrbR$lY1A2 zsCIciL?SQ_y})QH;jl_!$B5&O#v^jX zTh24tw>xZdd(v$pCtCk3Ou#=ptJ{4qh@4*6;$vG3#YN(^*94Fiy0=nbD~VcocbCv% zAi}=NVk3v`BY;P$?$CB2X&d$y%Siw`B)SerUR@ffGVc+@{(~n^2>${H+SVeRM z<58IW=CfYOPn8axfit_^&W#~GGnm3a5)y$CQxKghTWL2ABQ-k3@Ax4uNisV#^CkBQ z&zouK%9{MV)R7)=m^c7%5(#cjvc@!7eT?@4w=nK>&of9Q=b#w_Rge9t%G66|2wW{!)vqC?Kl&w3w7MB${x2YFM#diqC52Z)9V~Yv9Iu&ivM_j z4X^2#sGjDoo#+Bm1MU`_*MJ!G*Hcnbf`}nXA*YP8AGsQNz=6_Llouqso*p`$PF|d) z^g}SK+0&;pP_GBKnUo|p8Z}??fv8NbR@3jM2Qv}eBN`&kpCPTeZx&#nbkUHAeo=Pz z1KjAN!^2Ms@m$cU(whIIR4)Wt)T^o}i|V#<;lS+-PnOt+UW(AkVo5lM3SQl8#)el6 z;14)BT1YVk(yC4}Y@~X7+bO5T%=$}dX^GVUJ0Ue{Fjs%Ch}@plhDj~J5~{lQeCD{n ztoDRND#mNw6{b;#DJvAP$HvBHE?Yu)u%O?mpoyue?sy9D1rXf(`r}8Nv&kMj3TC*s zh#1HMZ)v&rOaj6~wHth@%WO?dm{F^0ObjsupPRzT^r%E@BLWLJR~k~C>A6xw*w`+M zi;KhU2m+P@71^7o#T~lmwm!xtMh|Jeq_~(6;eG@22Vk_!c!Tgif6O3e6YjhT#A8Fo zT5*d1IaRdiHV%fAgQe7O(WIoLNCv2o^$HH@8A7>j^jC=wLf7q@-5?hfi^|%RTdP z#<{=#s=Vj?DhMi^s}Ojgt+!TRfOb58=j7<4%3HhbV#kxEoC8A)KcnM38jt=@X!Hor^?EsF=XaYqKn3hCx zIRK!z-BjV;e5%&vRlpDf3im-QcC~?WGQoB1rc3FT&rRBM=j4tE3_t4w3i{I?B@wwE zFxj}Lto#jJx-V#$IcgqT>YZf)FgeW?Z3JSV*djvaS?i;shZ{3(h`jvUB;@q4hG;+a z{j@~_gKhZ{AU(%qo;`cE3uPo-y**@wcvf94aX#b@tdEvUM4#nQz84k|9{v=FzY0CT zt-Yt9&;TCq7i(4^|ISu>ah^^P1#I{1;+WFy?N*vk56F851khBY^Ozyin-Rt{Rab?kaC&eTkj@Ga9X>oB4o? z-);4w1rku=6gJEq(R+KI*->eMfh4fTnqm6@&35K~1Qpv)?Le06%3%{LGTRUd!e)HxL&^#gHt=W+|pxMekXZlGw zLCpBei<5J(stY>T_LbBm&p284J7z`+`?+U&ytT*$iKjWEQcFrxper^qZVF`8uDD z17-ibw6_FubIup;Sx}?>p3$-dE@7_l@_yULj)$E*dns8ZN$Tb6*DrMQzrkQ$x9+xJY{weAixsCey)?1QOxPY-B8Axva*d9b-XvJ2*7;^6hwukW7Y-oW&-aEVTO+A zZ?=Qn+U=sAsrWgRNjx2G1e9f%fGg=HMn=AfjHEtw{we~H(fQ1G;Y1fXV>B1R`~qnd z!)Fva-4a8LfGq?5hxLf`%*@h?=HH?BpU?`QRXu(mCpm-gfD*<@j8WRVy3gj4a{Pd0 z+*coy@menzV}4lCkBb}rCG76*ewmZ=Cb-r&2-w-#1(KTO7inMFS;$2dI?lFV!wrRp zhXV=Tud-5%U;zLU;uEB#Uy4kc?gQh$erTavcy$K84gT@O!8ANyV6}~D=%tydDJz6x z!ZI=87CFTroY!aqn;zwd2lyh}QL!rql@E|(W>6h#gAW41hXDq|M6{l*J$>uN)NNKt zNhUC`w?(14o?NvZxlY0OpMwMEhF$_z9t9XC1NiD9vM}d`-jjaE$!|S=%mzLSRMgZjgsl31V#j^#hTHC@)PGP- zD|B0X1aWtoNWRp`;ZtB`W>Vy&ZHIh@^t&6Heou8k7 zXR}9T)bJ2a_e}Et9OU128sHI;&SeAu3O8_a(bqf&nl-zcEgAygyM9RRBcSuCj!wL0 z(UOxp37G_;&23r(2@x)lL&d@RbW0Hw1c-Xv7KSjWiz|i(vSDIF_?`2^wpHYCHpEO& z_KSW1 z-ku*8ATIe=Ld-R%?d`TKAylEOgnS3wcb6wdpixy*Q~RCfHo(cGWfpWyE7cnm%-%c3 z5__u+6ieJkE&#s(GX6`!rHKi9%`eYyJ1r|FN>udWIT5gJ=jZo-C(?tYHHAsqn-Ru* zfAGyqGIiejx}%wTWwyX_N8}wTW)gRaUz_a4Yyq=9SWps<<(}OOzZZI4B`ZW2UN%!y1 zA7mbqDJ*na%jkkxgMBVn2$U31y#zzaK!niD)Aoh&lGHUR=DAZy-LJ+K#Zzi*4J-P0 zLK!(r&=O$60C90*dfIuY>Gb*YC%~@;Tx-(0UUDRPo&hTeo?EwW-B(rhEwMt&+X%29&Bnp>8WHtNQJoH?XeC1CJ#FjCFjQLq~Vys zgrX2Q|BO0r~xeR-E7@F$`tQrW;&N+cFMDfyXbYufWt@25xF7K0Chize;_A z*1X2Uqm<*;AG9(gwy6FM6C&HQTz7G31Rufl~!+28$bru(N0M1v!8h6al`U zPNf5PJ0a?1VQ@P;KVj8iT2{vCEhOFJa9^6Bb7%`SIW{&orx5=WQB(sz(?s{C2$h*{ zf(cS?v(_g#7qAEnpd<>Wk9uPxmTM0RW`-dZ62Kl0^TUNacb=AowG0n4Kn)X>NeSC7 z8EUT}BbOzyTpt`djVP$aSt`C4kc}-R{{&R?5pX8hfxBpl3()TZC!Qk!6ahvLZI0}4 zj*yFxCBA;WorTGUWx@4kZg8=QZ`pT8B7Z^L|160enRH?r=BGM(#C6n}*0y@>P8GIi z`4VTu1ej_yg4YHPl~ZxL%fGRw=PbAo6vm;mR9N65+W+6=^)jb#t4g@4%xI6v{eN01 zdP^h+J_xjpuZH#c!TpYu)c?87v5q6aFM)ZEn}t;H{kBM9C&OGH)$uy+GU0jMaTa)Fw52ZuYzeWPVyz_hTiKv?zZvz(ZpKk4J+ zQ^wtMS{1*sJ#2STT$}-ZI~?VLg%-=TxHcE;XDH=2){zbl4hRIqHOQVk0j_+<|18N& z$tnG3z&af%e4rg%6^~lUKYFvA?AhRtux$TbsOV_oc1t&uy%rbl7VoM{n8IpV@#=us z#5%+?UW3XD*RQ|x zles;s2-97&_V`y&-w=2S|9LpygFcBaQLX#;pT!t?4;mj#{fOYhxmewHnlN_zcLM!8 zAc+Y87-TNq%G%oLoru%ieXzT7fHCl4wCKTQ(G&kYD>uWB(TSeu<@)`4vJ)q6I5-Hv zYzWrq_@Dl~0i~0}!~Y3&@Q-%>KQD$P4P|DB?}fiogNY7jDgN_TPnVd3JJ5f>@cX%H z)8C-W|M`w8N#lP>qRQ~E|4gC&`P-5bN-~!paBm_Y z^MY;4`M(GVxjWRdP)`9K12pgdy!7|y)a_@$T!;Z|1CX?Ur=_Z{-at4}2_k^h`PY36 z{3aNw|L5l{MF{^7H!m**f>3|vYqn4ke#$ZR@~zyy)PjQc}S^G?JEKTXFwOB z|9WUh7gpEY+}(leK`>tupt}|hzh?#xQczf!K-o!2IRa@FkvCD)*?FJhpNYqx>s}&G zKtKM!QF*l8|989(@f`m?mkM#CGwm)VtY66dK2Qp=giszHphvzAZuq-D*rnSZr`5z2ffpt_Wy>z_zYP6$Bcs{{#>aT1Z$2 zX1q@59ebocqX}o?52)geW?2`*=64>^fRC}HFR3Onx|1E~M9Dq=#VrZaS z?k20&1u-^KXsCmZi!31*h0-fBrd~ovPh5Sk1R7z6{WJJ-AHI&Ekk@JN?_Z9DuR^yJ zfd?wr63i`M=-HG*6V?H2S+$fGNii&-c<*HAIw&@z{O6Wez5C_Q-QMN?5KJ@(&5y9# z$cyI8zV2&!&c_76_WWX|M{O&nklrVkzZ{GZgHpvLuJj}biZ?{0L!c*#;)2{Z(`Xo= zV<`|%p^5MG5Zd`!D|C_sXN*7UspSEn=Zia&W1Oz6Tx{LbUJu(-`Cx0ZtY>=dt-agS z0v-zM)n})8q=vp#;d=hXRznHq?QaC~K)ffN!(m{>ga7Eb>9Zo6Ktt+i5B> zkISU0pc}e&AJ6dsogk4J8=zZ-&>?U)77fQUO@Qu7P)^}o=Gz$lo&CKf`#bV>Rb=#G zRy*&ih`ne6bF9U=hBLE#`fb=EECyPKR6=({36O=xC#VxR~3PbeQXUe1-&IZnLhJk;q zR2t*`jec%48W~Yxp_zu_eJf6dD$%lIgZarPP*q9oO{#lVJT4H_1Yyy{+uW6gCdwMW z4(45i-62+EL~70Kwut``j5E30+VCJK7zQmQXHUkx(NF+gJ2W-tVr<6BG)N8Mo%4jQ z1iWq|z;l&hDR+{9qi~xTMh|%|Uyj;FE6OSzBW($-Dp~59tZ*aq`SkI=`2p5J8f4ncf}-E8RA z_!ZlwWq&~rlqMvE!TPT4(uu-=%ZfB-Pu;mlsz6rcgmHi^A7TUuxT39f2pfD8_0CUW zvvD%*SW-$}=1BflozAx3Xr<&N@@I2OjLgwhm2a1mhTI-!*c$UaVi3pTKuES#N;ufdiX7XU$1ZKA-RY@W!1AW zL(LMwOo@>^n#dK>;}Q*x`($XI^=yQpu7Pb z!h0}Kc#kxCntJ+CS7ydL;k}?9dcB)ekYqKNbOV9RKC*zzbwSzm9C!(i!3ta;>y5(P zrX906?5GoWWU3Y!`fnPdlJwJ%Sca;&__btyGkJvT6ZUQ8M?^54b)f1_QGJI z@`(=6C0FyXREQgXhE}v!8Tvg=EWdY(6Wo93L`&$)24)T=f?$gA2hP75BHnN?v|UEc z91Io}3T2YqCEg=3RV-B>yHiByAsz*)dIhllb(wMhEPmq9*82>f&*5>9F#L`D?6ZuM z8+PQ&{dpB(^$Y`O+nY-%F8FuQTev#^3)X+EZ@VkW#u=`3^R#3n$Nr8!?E!URK0TPQ zZy=vr{UU;5U3nq&vlNLI)8~Ku%c6t<(g=99VLmUFyMJ-M&!w-mX(cH9$aUKgh!dbx zDBE5E57D_dm$R_Tys9S&s0eTUU54lt?URVhT-Us2ir9AE`} zVX6D@^X^;kTKl9i5LAJQ#**-)=VJ-m-ra2 zGXv>3t+)HdK3_L)`I>LoTE>^e|aKf z{dTe)0`^w*JPVKplB5b@X_eXGv0+)1Ux*uEv)FBDCAd8~W{8^fxd~YVh`1vkB;N3= zJwjT+=$+^d-D$fhr}IOv+giU4!7K&N-(jr>6M;jr6$5}NFtcc=-Xb$T z&2zrn@dAuv0JnTwx8WvqWGs)SwO)GAtKA_^faUmUwD($IRC}qpY{|gS=3pcuKfTY=J?6jGR1)E*@4&7!G~PllMADe^rJzoRQ}?99eZj1O7d`CkrV=-NvP%8O z@<-pvA_p`*v~yXB-`|+msJj*$LNI%BcV>S*bH^=5VgkJ1z`-PIHW9SQYAH{)YWf&9 z-~5Vwwg1EP$wYGhWU`Ji}+U0fmgM^A-Qa; z`N(v7&mveEQNw4R4eY6EF|J*(9-|gHBIgv_+R`= z2}`^UtQgpshqf6s$jCdB(Z3ETnfvg>N z1WS1)&NE})z*vmQ2x(f_+*-Zwc5j2*iks=VOja{=o=7g!#Layai>OuQlPo0{9M9|FPcU@(sXfagW-0IGkqX?ZDKe)h5*?3H|S@%XfO_a zUu%O&Mshr4D$^O;Uwly2WY8Zu)P^d^{T454G*CiPd=B~&4h`K!K2Q;`#yj|l=#o{Qp7mc8LJ1c^{c2A~K#*~lqQWx+ zvGmV<1j^#yK1AhqrdL7VgJ;<<^Stq@u4ysQRT$5lE0f`kXn^Zfi)D$BGD#}mz6rt! zm&#FNOoVti zW2gNB=)wf4^U^JH`RO0~*zO1x&F$FAp5+~=Sn`RcuWj2pm&-cr%c_V2IN>^c^TdxQzv z0koV`BHEjccx7+fI2aE?(2yo6uj8PgL+1w>LfbhIyN)AXkOcfCRKy2?-*9hRh*RPm zNSincJWiw>unxpmkSFc!vIAKHk{t{aaEuK3`mTU5h=3#rl#+@*=AvCML|)3ER|UT4 z8aLC{kl5vO%ph#KZTTpu#UmNI57xWvl{8cV>qEeX?9FJ}Ay#{G z#uuag;cP)uHSD2x=W{Q9G2zM}jR;h{jh=IxiehZ|Xv3YsbzA3H;96sM#XHg6P~jS> z3dKt`mxlWvwQZ=73xoLb3@F+l839$$`TO@l%6eQQZo|>&^Igi?9?L34bNaRr-WVo6 z?k_4<-c7ZcvqaCbWjNNR(j#WW7;M%Gm_^CKH|+S4=Pj1*a0RV;R5W?ygO*6^>7&IW zli1s^8%_rNEy5?Jm2>;xN`V^X>iah6;LIxa26;ch@##}`p8+VWjCl9kC%ii)g-_$| zNFMwcdCr3`Q)t3t+l9(aezhT-8MD7YKT@e5a)R)0o%guD#_>db}UU@ zL_b>iGw;_TQAdVM@}ytI8q;;)lWthOMfX+KDSC#`%1jO!Nmd;%`G3TEHe)`>ZNrhx z1p}82>)iPPq!XaQn{G}~sFY+j0>41kbF<0(L!U0n9dbt`ua|Yfk@syipPlD8gs*&C z97s7YtDvuu?rjl zhZ3Di<~XAmo2XCo7g1j$Q(_c)vgN$^i_sS#{WBqM*vsSY)1UNo_O_|4~N5-NF zgtl^tx?(End?~s+%Si((#m}n|4+RSeJ(k2fi$=jwF` zcZDx$-o@T}S~Hn%V?ln07^b5j5*K~II_&+l=|{)nGBG#@+yayo9q(Dl^*_7YcbJQWl>8SXLUjA2x? z#)_^(t0lYVt+|Zpl|b5$rW*{ zOZeJ1R2PiLU2ogVsOFa2IivJ2YM8Q8%Y2)Xd>b1p9~&E+5=%Wjn^7zi>92slF5nXp zIu~9ndYDz!N#6=kG`mFh;>6UJr^L+X)(UK*$4Z5(8Ik*jXqu4uuOhD8E9x$mE(7~R zSbtOR?Os2wk(!I0-Ypd(Pqxc-43ljMg2sSb>Nh zjh_3C_h(NRaOvpuF_#pVUglC{@337t7p!sZt`en1*?AiFua}NKPhE0NXjty#H_5d6 z5v#)HuWT~EfhQVw3^1OH^z!`?s_rh|?aEgxG*x3!qV0_LFPmGg+fN@3azy-o+_0m% zKn%}niFoi_I48~Ovf^Qzw6xTQ<7{~}xwYQw{;W%;=S+QP&jyBtU7t?ud(`LNl#p*u zTXR`1$07%_u~DjEqvkdqWmj`=q46M`VBXU z96Q?+xD31kOKs-!(P8H%+3`!a1N=%Yhw1m%GU}R#S?h%yHw6tGx{sYMTXU-k+$$Fw zAD#>d?TT>SmvzbId90&hP*NIgOBQi z#o))`MO^=$Rh2|yP9@hUeyXE5SLR$s?u-GAumsK7>F|b`6R+phS0vM9jd;6GMuhgI z@4aePl-Kff%JX)cP9PB!h6|0GL2C4B9NV_v`)VTdEu$d&W{U{Q8qdfitgIQV0WUa* zA7T_X@5qd`wu$PqRQGg>Uvvrd#)YtOv6>wUW}aRDhH)+1Q^OukY7G^cXQh{BQ>!G| z%YT)(C25)5nnR%^Q~0(M6!0arE3qt_xQ()F!Ny&$B4+0r*-`T0GzB{st}}VCd-M`p} zZ=uaDuV!1^&QcWTq`IJ2WNO+e810eMTvAr579Po%lowxYEoak{Q>rrk{(U_2la(vi zL}lRIoyL;?85xYgS%zmfqziSgeALZb$5jg`smo5YH@AI1sblw4xAe}`REx3W(UQ2# z>=0QS^-r>-@oeAN1IPn4R&tmB;

&6)L8=&3Sp07u7M$ajt%w#pHP^<69Xj&b1%D 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 F2~O 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 literal 0 HcmV?d00001 diff --git a/images/readme/moderate.png b/images/readme/moderate.png new file mode 100644 index 0000000000000000000000000000000000000000..c5c129c29e287536643a6314f4f67a0cf3c99f36 GIT binary patch literal 3652 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 literal 0 HcmV?d00001 diff --git a/images/readme/none.png b/images/readme/none.png new file mode 100644 index 0000000000000000000000000000000000000000..7f23d04a68e59648d12a9859496ac7388fe42ef4 GIT binary patch literal 3652 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 literal 0 HcmV?d00001 diff --git a/images/readme/search.png b/images/readme/search.png new file mode 100755 index 0000000000000000000000000000000000000000..1f13d98df2a636bad38c533a6062a344b39ccbec GIT binary patch literal 846342 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

>Jm$OuM^mpG#0ND2TWTe- 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 z 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

-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 z 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 Date: Fri, 22 Jan 2016 08:35:37 +0530 Subject: [PATCH 127/272] LIHADOOP-16587 Dr. Elephant should provide search by start time RB=646592 G=superfriends-reviewers R=annag,fli,rratti,shanm,viramach A=fli --- .../drelephant/analysis/AnalyticJob.java | 2 +- app/controllers/Application.java | 188 +++++++++++++----- app/views/emailcritical.scala.html | 4 +- app/views/main.scala.html | 10 +- app/views/page/comparePage.scala.html | 4 +- app/views/page/flowHistoryPage.scala.html | 2 +- app/views/page/jobHistoryPage.scala.html | 2 +- app/views/page/searchPage.scala.html | 12 +- app/views/results/compareResults.scala.html | 2 +- app/views/results/flowDetails.scala.html | 2 +- .../results/flowHistoryResults.scala.html | 4 +- app/views/results/jobDetails.scala.html | 4 +- .../results/jobHistoryResults.scala.html | 2 +- app/views/results/searchResults.scala.html | 2 +- conf/evolutions/default/8.sql | 25 +++ conf/routes | 8 +- public/js/flowhistoryform.js | 66 ++++-- public/js/graphutility.js | 7 +- public/js/jobhistoryform.js | 63 ++++-- public/js/searchform.js | 8 +- 20 files changed, 287 insertions(+), 130 deletions(-) create mode 100644 conf/evolutions/default/8.sql diff --git a/app/com/linkedin/drelephant/analysis/AnalyticJob.java b/app/com/linkedin/drelephant/analysis/AnalyticJob.java index 69b3723a0..2e2bc79be 100644 --- a/app/com/linkedin/drelephant/analysis/AnalyticJob.java +++ b/app/com/linkedin/drelephant/analysis/AnalyticJob.java @@ -155,7 +155,7 @@ public JobResult getAnalysis() throws Exception { result.url = getTrackingUrl(); result.username = getUser(); result.startTime = getStartTime(); - result.analysisTime = System.currentTimeMillis(); + result.analysisTime = getFinishTime(); result.jobName = getName(); result.jobType = jobTypeName; diff --git a/app/controllers/Application.java b/app/controllers/Application.java index b571ade14..8f0a8b385 100644 --- a/app/controllers/Application.java +++ b/app/controllers/Application.java @@ -75,20 +75,26 @@ public class Application extends Controller { private static final int JOB_HISTORY_LIMIT = 5000; // Set to avoid memory error. private static final int MAX_HISTORY_LIMIT = 15; // Upper limit on the number of executions to display - private static final String FORM_JOB_ID = "jobid"; - private static final String FORM_FLOW_URL = "flowurl"; - private static final String FORM_USER = "user"; - private static final String FORM_SEVERITY = "severity"; - private static final String FORM_JOB_TYPE = "jobtype"; - private static final String FORM_ANALYSIS = "analysis"; - private static final String FORM_START_DATE = "start-date"; - private static final String FORM_END_DATE = "end-date"; - - private static final String COMPARE_FLOW_URL1 = "flowurl1"; - private static final String COMPARE_FLOW_URL2 = "flowurl2"; - - private static final String HISTORY_FLOW_URL = "historyflowurl"; - private static final String HISTORY_JOB_URL = "historyjoburl"; + // Form and Rest parameters + private static final String JOB_ID = "id"; + private static final String FLOW_URL = "flow-url"; + private static final String FLOW_EXEC_URL = "flow-exec-url"; + private static final String JOB_URL = "job-url"; + private static final String USER = "user"; + private static final String SEVERITY = "severity"; + private static final String JOB_TYPE = "job-type"; + private static final String ANALYSIS = "analysis"; + private static final String STARTED_TIME_BEGIN = "started-time-begin"; + private static final String STARTED_TIME_END = "started-time-end"; + private static final String FINISHED_TIME_BEGIN = "finished-time-begin"; + private static final String FINISHED_TIME_END = "finished-time-end"; + private static final String COMPARE_FLOW_URL1 = "flow-exec-url1"; + private static final String COMPARE_FLOW_URL2 = "flow-exec-url2"; + private static final String PAGE = "page"; + + // Time range specifer. [TIME_RANGE_BEGIN, TIME_RANGE_END] + private static final boolean TIME_RANGE_BEGIN = false; + private static final boolean TIME_RANGE_END = true; private static long _lastFetch = 0; private static int _numJobsAnalyzed = 0; @@ -109,9 +115,9 @@ public class Application extends Controller { public static Result search() { // Search and display job information when job id or flow execution url is provided. DynamicForm form = Form.form().bindFromRequest(request()); - String jobId = form.get(FORM_JOB_ID); + String jobId = form.get(JOB_ID); jobId = jobId != null ? jobId.trim() : ""; - String flowUrl = form.get(FORM_FLOW_URL); + String flowUrl = form.get(FLOW_URL); flowUrl = (flowUrl != null) ? flowUrl.trim() : null; if (!jobId.isEmpty()) { JobResult result = JobResult.find.byId(jobId); @@ -131,9 +137,9 @@ public static Result search() { int pageLength = paginationStats.getPageLength(); paginationStats.setCurrentPage(1); final Map searchString = request().queryString(); - if (searchString.containsKey("page")) { + if (searchString.containsKey(PAGE)) { try { - paginationStats.setCurrentPage(Integer.parseInt(searchString.get("page")[0])); + paginationStats.setCurrentPage(Integer.parseInt(searchString.get(PAGE)[0])); } catch (NumberFormatException ex) { logger.error("Error parsing page number. Setting current page to 1."); paginationStats.setCurrentPage(1); @@ -143,7 +149,7 @@ public static Result search() { int paginationBarStartIndex = paginationStats.getPaginationBarStartIndex(); ExpressionList query = generateQuery(); List results = - query.order().desc("analysisTime").setFirstRow((paginationBarStartIndex - 1) * pageLength) + query.order().desc(JobResult.TABLE.ANALYSIS_TIME).setFirstRow((paginationBarStartIndex - 1) * pageLength) .setMaxRows((paginationStats.getPageBarLength() - 1) * pageLength + 1).findList(); paginationStats.setQueryString(getQueryString()); if (results.isEmpty() || currentPage > paginationStats.computePaginationBarEndIndex(results.size())) { @@ -204,7 +210,7 @@ private static Map>> compareFlows(String flo */ public static Result flowHistory() { DynamicForm form = Form.form().bindFromRequest(request()); - String flowUrl = form.get(HISTORY_FLOW_URL); + String flowUrl = form.get(FLOW_URL); flowUrl = (flowUrl != null) ? flowUrl.trim() : null; if (flowUrl == null || flowUrl.isEmpty()) { return ok(flowHistoryPage.render(flowHistoryResults.render(null, null, null, null))); @@ -248,7 +254,7 @@ public static Result flowHistory() { */ public static Result jobHistory() { DynamicForm form = Form.form().bindFromRequest(request()); - String jobUrl = form.get(HISTORY_JOB_URL); + String jobUrl = form.get(JOB_URL); jobUrl = (jobUrl != null) ? jobUrl.trim() : null; if (jobUrl == null || jobUrl.isEmpty()) { return ok(jobHistoryPage.render(jobHistoryResults.render(null, null, -1, null))); @@ -294,7 +300,7 @@ private static String getQueryString() { for (Map.Entry entry : entries) { final String key = entry.getKey(); final String value = entry.getValue()[0]; - if (!key.equals("page")) { + if (!key.equals(PAGE)) { fields.add(new BasicNameValuePair(key, value)); } } @@ -305,16 +311,22 @@ private static String getQueryString() { } } + /** + * Build SQL expression + * + * @return An sql expression on Job Result + */ private static ExpressionList generateQuery() { - SimpleDateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy"); DynamicForm form = Form.form().bindFromRequest(request()); - String username = form.get(FORM_USER); + String username = form.get(USER); username = username != null ? username.trim().toLowerCase() : null; - String severity = form.get(FORM_SEVERITY); - String jobType = form.get(FORM_JOB_TYPE); - String analysis = form.get(FORM_ANALYSIS); - String dateStart = form.get(FORM_START_DATE); - String dateEnd = form.get(FORM_END_DATE); + String severity = form.get(SEVERITY); + String jobType = form.get(JOB_TYPE); + String analysis = form.get(ANALYSIS); + String finishedTimeBegin = form.get(FINISHED_TIME_BEGIN); + String finishedTimeEnd = form.get(FINISHED_TIME_END); + String startedTimeBegin = form.get(STARTED_TIME_BEGIN); + String startedTimeEnd = form.get(STARTED_TIME_END); ExpressionList query = JobResult.find.where(); @@ -349,34 +361,102 @@ private static ExpressionList generateQuery() { query = query.ge(JobResult.TABLE.SEVERITY, severity); } } - if (isSet(dateStart)) { - try { - Date date = dateFormat.parse(dateStart); - query = query.gt(JobResult.TABLE.ANALYSIS_TIME, date.getTime()); - } catch (ParseException e) { - logger.error("Error while parsing dateStart. " + dateStart + " is an invalid date. Filter not applied."); + + // Time Predicates. Both the startedTimeBegin and startedTimeEnd are inclusive in the filter + if (isSet(startedTimeBegin)) { + long time = getTimeRange(startedTimeBegin, TIME_RANGE_BEGIN); + if (time > 0) { + query = query.ge(JobResult.TABLE.ANALYSIS_TIME, time); + } + } + if (isSet(startedTimeEnd)) { + // Here we want to include the end time in the filter + long time = getTimeRange(startedTimeEnd, TIME_RANGE_END); + if (time > 0) { + query = query.le(JobResult.TABLE.ANALYSIS_TIME, time); + } + } + if (isSet(finishedTimeBegin)) { + long time = getTimeRange(finishedTimeBegin, TIME_RANGE_BEGIN); + if (time > 0) { + query = query.ge(JobResult.TABLE.ANALYSIS_TIME, time); + } + } + if (isSet(finishedTimeEnd)) { + // Here we want to include the end time in the filter + long time = getTimeRange(finishedTimeEnd, TIME_RANGE_END); + if (time > 0) { + query = query.le(JobResult.TABLE.ANALYSIS_TIME, time); } } - if (isSet(dateEnd)) { + + return query; + } + + /** + * Parse the string for time and return the epoch value depending on + * if the time represents the beginning or end of a range. + * + * The String can an epoch value or a Date value. + * + * For time in epoch value, return the String after parsing it to long. + * For time in MM/dd/yyyy format, parse the time and return the epoch + * value such that we include the entire day in the filter + * + * @param time The String to be parsed + * @param rangeEnd Range Indicator. true if it is the range End(to), false indicates the range Beginning(from) + * @return The epoch value + */ + private static long getTimeRange(String time, boolean timeEnd) { + long unixTime = 0; + if (isUnixTimeStamp(time)) { + unixTime = Long.parseLong(time); + } else { + SimpleDateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy"); try { - Date date = dateFormat.parse(dateEnd); - Calendar c = Calendar.getInstance(); - c.setTime(date); - c.add(Calendar.DATE, 1); - date = c.getTime(); - query = query.lt(JobResult.TABLE.ANALYSIS_TIME, date.getTime()); + if (timeEnd) { + Date date = dateFormat.parse(time); + Calendar c = Calendar.getInstance(); + c.setTime(date); + c.add(Calendar.DATE, 1); + date = c.getTime(); + unixTime = date.getTime() - 1; + } else { + Date date = dateFormat.parse(time); + unixTime = date.getTime(); + } } catch (ParseException e) { - logger.error("Error while parsing dateEnd. " + dateEnd + " is an invalid date. Filter not applied."); + logger.error("Error while parsing time. " + time + " is an invalid date. Filter not applied."); } } - return query; + return unixTime; + } + + /** + * Checks if the String represents a time in epoch. + * + * @param unixTime The string to be verified. + * @return true if the String contains a valid unix timestamp, false otherwise. + */ + private static boolean isUnixTimeStamp(String unixTime) { + boolean valid = false; + try { + long time = Long.parseLong(unixTime); + if (time > 0 && time < System.currentTimeMillis()) { + valid = true; + } + } catch (NumberFormatException ex) { + valid = false; + } + + return valid; } private static boolean isSet(String property) { return property != null && !property.isEmpty(); } - public static Result dashboard(int page) { + public static Result dashboard() { long now = System.currentTimeMillis(); if (now - _lastFetch > FETCH_DELAY) { _numJobsAnalyzed = JobResult.find.where().gt(JobResult.TABLE.ANALYSIS_TIME, now - DAY).findRowCount(); @@ -448,7 +528,7 @@ private static void fillHelpPages() { * * @param map The results map to be pruned. * @param size Total number of jobs in the map - * @param maxLimit The upper limit on the number of executions to be displayed. + * @param execLimit The upper limit on the number of executions to be displayed. * @return A map after applying the limit. */ private static Map> limitResults(Map> map, int size, int execLimit) { @@ -487,7 +567,7 @@ private static Map> limitResults(Map results = JobResult.find.where().eq(JobResult.TABLE.FLOW_EXEC_URL, execUrl).findList(); if (results.size() == 0) { @@ -589,9 +669,9 @@ private static Map> groupJobs(List results, G public static Result restSearch() { DynamicForm form = Form.form().bindFromRequest(request()); - String jobId = form.get(FORM_JOB_ID); + String jobId = form.get(JOB_ID); jobId = jobId != null ? jobId.trim() : ""; - String flowUrl = form.get(FORM_FLOW_URL); + String flowUrl = form.get(FLOW_URL); flowUrl = (flowUrl != null) ? flowUrl.trim() : null; if (!jobId.isEmpty()) { JobResult result = JobResult.find.byId(jobId); @@ -606,8 +686,8 @@ public static Result restSearch() { } int page = 1; - if (request().queryString().containsKey("page")) { - page = Integer.parseInt(request().queryString().get("page")[0]); + if (request().queryString().containsKey(PAGE)) { + page = Integer.parseInt(request().queryString().get(PAGE)[0]); if (page <= 0) { page = 1; } @@ -615,7 +695,7 @@ public static Result restSearch() { ExpressionList query = generateQuery(); List results = - query.order().desc("analysisTime").setFirstRow((page - 1) * REST_PAGE_LENGTH) + query.order().desc(JobResult.TABLE.ANALYSIS_TIME).setFirstRow((page - 1) * REST_PAGE_LENGTH) .setMaxRows(REST_PAGE_LENGTH).findList(); return ok(Json.toJson(results)); } @@ -759,7 +839,7 @@ public static Result restJobGraphData(String jobUrl) { // Fetch available flow executions with latest JOB_HISTORY_LIMIT mr jobs. List results = JobResult.find.where().eq(JobResult.TABLE.JOB_URL, jobUrl).order() .desc(JobResult.TABLE.ANALYSIS_TIME).setMaxRows(JOB_HISTORY_LIMIT).findList(); - if(results.size() == 0) { + if (results.size() == 0) { logger.info("No results for Job url"); } Map> flowExecUrlToJobsMap = limitResults(groupJobs(results, GroupBy.FLOW_EXECUTION_URL), @@ -830,7 +910,7 @@ private static int getMRJobScore(JobResult job) { public static Result testEmail() { DynamicForm form = Form.form().bindFromRequest(request()); - String jobId = form.get("jobid"); + String jobId = form.get(JOB_ID); if (jobId != null && !jobId.isEmpty()) { JobResult result = JobResult.find.byId(jobId); if (result != null) { diff --git a/app/views/emailcritical.scala.html b/app/views/emailcritical.scala.html index dd08518f6..bc11f6275 100644 --- a/app/views/emailcritical.scala.html +++ b/app/views/emailcritical.scala.html @@ -24,7 +24,7 @@

Dr. Elephant needs your help!

Your Hadoop job is endangering the elephants living in our cluster.

- Please take a look here to figure out + Please take a look here to figure out what chemicals is causing the elephants to go nuts.

@@ -38,7 +38,7 @@

Dr. Elephant needs your help!

- [Dr. Elephant] + [Dr. Elephant] [Jobtracker]
@result.jobId
@result.jobName

diff --git a/app/views/main.scala.html b/app/views/main.scala.html index 7190dc62a..df70f14bf 100644 --- a/app/views/main.scala.html +++ b/app/views/main.scala.html @@ -37,10 +37,6 @@ - @@ -67,7 +63,7 @@ Job History Help - + @@ -76,8 +72,8 @@ @content - - + + diff --git a/app/views/page/comparePage.scala.html b/app/views/page/comparePage.scala.html index e76597791..a07c3173b 100644 --- a/app/views/page/comparePage.scala.html +++ b/app/views/page/comparePage.scala.html @@ -31,12 +31,12 @@
- +
- +
diff --git a/app/views/page/flowHistoryPage.scala.html b/app/views/page/flowHistoryPage.scala.html index a8c68e550..b8eed6095 100644 --- a/app/views/page/flowHistoryPage.scala.html +++ b/app/views/page/flowHistoryPage.scala.html @@ -24,7 +24,7 @@
- +
diff --git a/app/views/page/jobHistoryPage.scala.html b/app/views/page/jobHistoryPage.scala.html index 668a2a3bf..1680bdadb 100644 --- a/app/views/page/jobHistoryPage.scala.html +++ b/app/views/page/jobHistoryPage.scala.html @@ -24,7 +24,7 @@
- +
diff --git a/app/views/page/searchPage.scala.html b/app/views/page/searchPage.scala.html index 2769947d0..7cc48dc8e 100644 --- a/app/views/page/searchPage.scala.html +++ b/app/views/page/searchPage.scala.html @@ -32,11 +32,11 @@
- +
- +
@@ -45,10 +45,10 @@
- +
- @for((appType, jobTypeList) <- com.linkedin.drelephant.ElephantContext.instance().getAppTypeToJobTypes()) { @for(jobType <- jobTypeList) { @@ -88,10 +88,10 @@
- +
- +
diff --git a/app/views/results/compareResults.scala.html b/app/views/results/compareResults.scala.html index 4b633ce62..fedb83662 100644 --- a/app/views/results/compareResults.scala.html +++ b/app/views/results/compareResults.scala.html @@ -36,7 +36,7 @@ @if(jobs != null) { @for(result <- jobs) { + href="@routes.Application.search()?id=@result.jobId"> @tags.jobSummary(result) } diff --git a/app/views/results/flowDetails.scala.html b/app/views/results/flowDetails.scala.html index c69d89d76..c59890318 100644 --- a/app/views/results/flowDetails.scala.html +++ b/app/views/results/flowDetails.scala.html @@ -38,7 +38,7 @@

@for(result <- jobs) { + href="@routes.Application.search()?id=@result.jobId"> @tags.jobSummary(result) } diff --git a/app/views/results/flowHistoryResults.scala.html b/app/views/results/flowHistoryResults.scala.html index aaaa16034..6dd0f3e3b 100644 --- a/app/views/results/flowHistoryResults.scala.html +++ b/app/views/results/flowHistoryResults.scala.html @@ -57,7 +57,7 @@ Flow Executions @for(jobDefUrl <- jobList) { - Job @{jobDefIndex = jobDefIndex + 1; jobDefIndex} @@ -79,7 +79,7 @@ @if(jobMap.get(jobDefUrl) != null) { @for((result,jobCount) <- jobMap.get(jobDefUrl).zipWithIndex) { - diff --git a/app/views/results/jobHistoryResults.scala.html b/app/views/results/jobHistoryResults.scala.html index b8febe6c6..fcda44ae2 100644 --- a/app/views/results/jobHistoryResults.scala.html +++ b/app/views/results/jobHistoryResults.scala.html @@ -74,7 +74,7 @@ @if(i <= jobs.length) { @for((heuristicResult, jobCount) <- jobs(i-1).heuristicResults.zipWithIndex) { -
diff --git a/app/views/results/searchResults.scala.html b/app/views/results/searchResults.scala.html index ec15a3c8a..c1ac13912 100644 --- a/app/views/results/searchResults.scala.html +++ b/app/views/results/searchResults.scala.html @@ -30,7 +30,7 @@

@title

@for(result <- results) { + href="@routes.Application.search()?id=@result.jobId"> @tags.jobSummary(result) } diff --git a/conf/evolutions/default/8.sql b/conf/evolutions/default/8.sql new file mode 100644 index 000000000..b2691bd27 --- /dev/null +++ b/conf/evolutions/default/8.sql @@ -0,0 +1,25 @@ +# +# Copyright 2015 LinkedIn Corp. +# +# 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. +# + +# add/remove index on start_time + +# --- !Ups + +create index ix_job_result_start_time on job_result (start_time); + +# --- !Downs + +drop index ix_job_result_start_time ON job_result; \ No newline at end of file diff --git a/conf/routes b/conf/routes index 5f52445da..cc7c5377b 100644 --- a/conf/routes +++ b/conf/routes @@ -18,9 +18,9 @@ # This file defines all application routes (Higher priority routes first) # ~~~~ -# Home page -GET / controllers.Application.dashboard(page: Int = 0) -GET /dashboard/:page controllers.Application.dashboard(page: Int) +# Application calls +GET / controllers.Application.dashboard() +GET /dashboard controllers.Application.dashboard() GET /help controllers.Application.help() GET /email controllers.Application.testEmail() GET /search controllers.Application.search() @@ -28,6 +28,8 @@ GET /compare controllers.Application.compare() GET /flowhistory controllers.Application.flowHistory() GET /jobhistory controllers.Application.jobHistory() GET /flowrelated controllers.Application.flowRelated() + +# Rest calls GET /rest/job controllers.Application.restJobResult(id: String) GET /rest/jobexec controllers.Application.restJobExecResult(url: String) GET /rest/flowexec controllers.Application.restFlowExecResult(url: String) diff --git a/public/js/flowhistoryform.js b/public/js/flowhistoryform.js index 1ac4d015f..470747497 100644 --- a/public/js/flowhistoryform.js +++ b/public/js/flowhistoryform.js @@ -17,7 +17,7 @@ $(document).ready(function(){ /* Plot graph for data obtained from ajax call */ - $.getJSON('/rest/flowgraphdata?url=' + queryString()['historyflowurl'], function(data) { + $.getJSON('/rest/flowgraphdata?url=' + queryString()['flow-url'], function(data) { updateExecTimezone(data); // Compute the jobDefUrl list such that the job numbers in the tooltip match the corresponding job in the table. @@ -40,9 +40,9 @@ $(document).ready(function(){ /** * Example tooltip content: * - * Execution 1 + * Sat Oct 17 2015 01:47:59 GMT+0530 (IST) * Flow score = 163672 - * Top poor jobs: + * Top poor jobs * Job 25 45% * Job 16 20% * job 14 10% @@ -50,11 +50,22 @@ $(document).ready(function(){ */ function getGraphTooltipContent(record, jobDefList) { - var content = ["" + record.flowtime + ""]; - content.push("Flow Score = " + record.score); + var content = document.createElement("div"); + content.style.textAlign = "center"; + + var heading = document.createElement("b"); + heading.appendChild(document.createTextNode(record.flowtime)); + heading.appendChild(document.createElement("br")); + + var details = document.createElement("p"); + details.appendChild(document.createTextNode("Job Score = " + record.score)); + details.appendChild(document.createElement("br")); + + var jobTable = document.createElement("table"); if (record.score != 0) { var jobLimit = 3; - content.push("Top poor jobs:"); + details.appendChild(document.createTextNode("Score Distribution")); + details.appendChild(document.createElement("br")); var scoreList = []; for (var i = 0; i < record.jobscores.length; i++) { @@ -67,7 +78,6 @@ function getGraphTooltipContent(record, jobDefList) { }); // Traverse ordered list - var jobRow = ""; for (var jobIndex = 0; jobIndex < scoreList.length; jobIndex++) { var width = scoreList[jobIndex][0]; @@ -78,21 +88,39 @@ function getGraphTooltipContent(record, jobDefList) { break; } - // cell 1 var jobDefUrl = record.jobscores[index]['jobdefurl']; - var jobLink = "/jobhistory?historyjoburl=" + encodeURIComponent(jobDefUrl); - var jobRef = "Job " + (jobDefList.indexOf(jobDefUrl) + 1) + ""; - var cell1 = "" + jobRef + ""; - - // cell 2 - var jobScoreRect = "
" + +width.toFixed(2) + "\%
"; - var cell2 = "" + jobScoreRect + ""; - - jobRow = jobRow + "" + cell1 + cell2 + ""; + var jobLink = "/jobhistory?job-url=" + encodeURIComponent(jobDefUrl); + var jobRef = document.createElement("a"); + jobRef.setAttribute("href", jobLink); + jobRef.appendChild(document.createTextNode("Job " + jobDefList.indexOf(jobDefUrl) + 1)); + + var tableCell1 = document.createElement("td"); + tableCell1.style.padding = "3px"; + tableCell1.setAttribute("width", "65px"); + tableCell1.appendChild(jobRef); + + var jobScoreRect = document.createElement("div"); + jobScoreRect.style.padding = "3px"; + jobScoreRect.style.background = "red"; + jobScoreRect.style.width = width + "%"; + jobScoreRect.appendChild(document.createTextNode(+width.toFixed(2) + "%")); + + var tableCell2 = document.createElement("td"); + tableCell2.appendChild(jobScoreRect); + + var tableRow = document.createElement("tr"); + tableRow.appendChild(tableCell1); + tableRow.appendChild(tableCell2); + + jobTable.appendChild(tableRow); } - var jobTable = "" + jobRow + "
"; - content.push(jobTable); + jobTable.setAttribute("border", "1px"); + jobTable.style.width = "100%"; } + + content.appendChild(heading); + content.appendChild(details); + content.appendChild(jobTable); return content; } \ No newline at end of file diff --git a/public/js/graphutility.js b/public/js/graphutility.js index a9454fe2f..388397646 100644 --- a/public/js/graphutility.js +++ b/public/js/graphutility.js @@ -160,6 +160,7 @@ function plotter(graphData, jobDefList) { tooltip.append("foreignObject") .attr("width", tooltipWidth + "px") .append("xhtml:body") + .attr("id", "graph_tooltip") .style("background", "rgba(255, 255, 255, 0.9)") .style("text-align", "left") .style("border-radius", "5px") @@ -183,11 +184,13 @@ function plotter(graphData, jobDefList) { } // Add content to tooltip - tooltip.select("body").html(getGraphTooltipContent(record, jobDefList).join("
")); + var graphTooltip = document.getElementById("graph_tooltip"); + graphTooltip.innerHTML = ''; + graphTooltip.appendChild(getGraphTooltipContent(record, jobDefList)); // Set position of highlighted circle tooltip.select("circle").style("display", "inline").attr("transform", "translate(" + xRange(record.flowtime) + "," - + yRange(record.score) + ")"); + + yRange(record.score) +")"); // Set position of tooltip tooltip.select("foreignObject").attr("height", tooltip.select("body").style("height")); diff --git a/public/js/jobhistoryform.js b/public/js/jobhistoryform.js index 1330261d0..309aefd48 100644 --- a/public/js/jobhistoryform.js +++ b/public/js/jobhistoryform.js @@ -17,7 +17,7 @@ $(document).ready(function(){ /* Plot graph for data obtained from ajax call */ - $.getJSON('/rest/jobgraphdata?url=' + queryString()['historyjoburl'], function(data) { + $.getJSON('/rest/jobgraphdata?url=' + queryString()['job-url'], function(data) { updateExecTimezone(data); plotter(data, []); }); @@ -28,20 +28,31 @@ $(document).ready(function(){ /** * Example tooltip content: * - * Execution 1 + * Sat Oct 17 2015 01:47:59 GMT+0530 (IST) * Job score = 163672 - * Top poor stages: + * Top poor stages * Stage 1 65% * Stage 26 25% * Stage 12 11% */ function getGraphTooltipContent(record, jobDefList) { - var content = ["" + record.flowtime + ""]; - content.push("Job Score = " + record.score); + var content = document.createElement("div"); + content.style.textAlign = "center"; + + var heading = document.createElement("b"); + heading.appendChild(document.createTextNode(record.flowtime)); + heading.appendChild(document.createElement("br")); + + var details = document.createElement("p"); + details.appendChild(document.createTextNode("Job Score = " + record.score)); + details.appendChild(document.createElement("br")); + + var jobTable = document.createElement("table"); if (record.score != 0) { var jobLimit = 3; - content.push("Top poor stages:"); + details.appendChild(document.createTextNode("Score Distribution")); + details.appendChild(document.createElement("br")); var scoreList = []; for (var i = 0; i < record.stagescores.length; i++) { @@ -49,12 +60,11 @@ function getGraphTooltipContent(record, jobDefList) { scoreList.push([scoreWidth, i]); } - scoreList.sort(function(left, right) { + scoreList.sort(function (left, right) { return left[0] > right[0] ? -1 : 1; }); - var stageRows = ""; - for (var stageIndex = 0; stageIndex < scoreList.length; stageIndex++) { + for (var stageIndex = 0; stageIndex < scoreList.length; stageIndex++) { var width = scoreList[stageIndex][0]; var index = scoreList[stageIndex][1]; @@ -64,19 +74,32 @@ function getGraphTooltipContent(record, jobDefList) { break; } - // cell 1 - var cell1 = "Stage " + (index + 1) + ""; + var tableCell1 = document.createElement("td"); + tableCell1.style.padding = "3px"; + tableCell1.setAttribute("width", "65px"); + tableCell1.appendChild(document.createTextNode("Stage " + (index + 1))); - // cell 2 - var stageScoreRect = "
" + +width.toFixed(2) - + "\%
"; - var cell2 = "" + stageScoreRect + ""; + var stageScoreRect = document.createElement("div"); + stageScoreRect.style.padding = "3px"; + stageScoreRect.style.background = "red"; + stageScoreRect.style.width = width + "%"; + stageScoreRect.appendChild(document.createTextNode(+width.toFixed(2) + "%")); - stageRows = stageRows + "" + cell1 + cell2 + ""; - } + var tableCell2 = document.createElement("td"); + tableCell2.appendChild(stageScoreRect); - var jobTable = "" + stageRows + "
"; - content.push(jobTable); + var tableRow = document.createElement("tr"); + tableRow.appendChild(tableCell1); + tableRow.appendChild(tableCell2); + + jobTable.appendChild(tableRow); + } + jobTable.setAttribute("border", "1px"); + jobTable.style.width = "100%"; } + + content.appendChild(heading); + content.appendChild(details); + content.appendChild(jobTable); return content; -} +} \ No newline at end of file diff --git a/public/js/searchform.js b/public/js/searchform.js index 9d8af0f0e..270fc94e5 100644 --- a/public/js/searchform.js +++ b/public/js/searchform.js @@ -21,14 +21,14 @@ $(document).ready(function(){ var jobid = $("#form-job-id"); var flowurl = $("#form-flow-url"); var user = $("#form-user"); - var jobtypeEnable = $("#form-jobtype-enable"); - var jobtype = $("#form-jobtype"); + var jobtypeEnable = $("#form-job-type-enable"); + var jobtype = $("#form-job-type"); var severityEnable = $("#form-severity-enable"); var severity = $("#form-severity"); var analysis = $("#form-analysis"); var datetimeEnable = $("#form-datetime-enable"); - var startDate = $("#form-start-date"); - var endDate = $("#form-end-date"); + var startDate = $("#form-finished-time-begin"); + var endDate = $("#form-finished-time-end"); startDate.datepicker(); endDate.datepicker(); From 8fddb193c72b2aa7c55ae0780d7a6ba7ecd7335f Mon Sep 17 00:00:00 2001 From: akrai Date: Sat, 30 Jan 2016 18:56:01 +0530 Subject: [PATCH 128/272] LIHADOOP-17485: Refactor and cleanup Dr. Elephant code RB=660706 G=superfriends-reviewers R=annag,fli,rratti,shanm,viramach A=annag --- README.md | 37 +- app-conf/elephant.config | 23 - app/Global.java | 8 +- app/com/linkedin/drelephant/DrElephant.java | 4 + .../linkedin/drelephant/ElephantContext.java | 21 +- .../linkedin/drelephant/ElephantRunner.java | 13 +- .../drelephant/analysis/AnalyticJob.java | 118 +++- .../analysis/AnalyticJobGenerator.java | 3 +- .../analysis/AnalyticJobGeneratorHadoop2.java | 49 +- .../drelephant/analysis/ApplicationType.java | 3 +- .../drelephant/analysis/ElephantFetcher.java | 5 +- .../drelephant/analysis/HDFSContext.java | 9 +- .../analysis/HadoopApplicationData.java | 1 + .../analysis/HadoopSystemContext.java | 6 +- .../drelephant/analysis/Heuristic.java | 1 + .../drelephant/analysis/HeuristicResult.java | 25 + .../linkedin/drelephant/analysis/JobType.java | 5 + .../drelephant/analysis/Severity.java | 76 ++- .../fetcher}/FetcherConfiguration.java | 5 +- .../fetcher}/FetcherConfigurationData.java | 3 +- .../heuristic}/HeuristicConfiguration.java | 6 +- .../HeuristicConfigurationData.java | 6 +- .../jobtype}/JobTypeConf.java | 16 +- .../mapreduce/MapReduceFetcherHadoop2.java | 30 +- .../{ => data}/MapReduceApplicationData.java | 12 +- .../MapReduceCounterData.java} | 10 +- .../{ => data}/MapReduceTaskData.java | 15 +- .../heuristics/ExceptionHeuristic.java | 23 +- .../heuristics/GenericDataSkewHeuristic.java | 17 +- .../heuristics/GenericGCHeuristic.java | 32 +- .../heuristics/GenericMemoryHeuristic.java | 20 +- .../heuristics/JobQueueLimitHeuristic.java | 7 +- .../heuristics/MapperDataSkewHeuristic.java | 14 +- .../heuristics/MapperGCHeuristic.java | 22 +- .../heuristics/MapperMemoryHeuristic.java | 7 +- .../heuristics/MapperSpeedHeuristic.java | 12 +- .../heuristics/MapperSpillHeuristic.java | 17 +- .../heuristics/MapperTimeHeuristic.java | 16 +- .../heuristics/ReducerDataSkewHeuristic.java | 14 +- .../heuristics/ReducerGCHeuristic.java | 22 +- .../heuristics/ReducerMemoryHeuristic.java | 7 +- .../heuristics/ReducerTimeHeuristic.java | 11 +- .../heuristics/ShuffleSortHeuristic.java | 11 +- .../linkedin/drelephant/math/Statistics.java | 11 +- .../drelephant/notifications/EmailThread.java | 3 +- .../{ => security}/HadoopSecurity.java | 6 +- .../{ => data}/SparkApplicationData.java | 4 +- .../{ => data}/SparkEnvironmentData.java | 3 +- .../spark/{ => data}/SparkExecutorData.java | 6 +- .../spark/{ => data}/SparkGeneralData.java | 4 +- .../{ => data}/SparkJobProgressData.java | 13 +- .../spark/{ => data}/SparkStorageData.java | 4 +- .../BestPropertiesConventionHeuristic.java | 10 +- .../heuristics/EventLogLimitHeuristic.java | 7 +- .../heuristics/ExecutorLoadHeuristic.java | 10 +- .../spark/heuristics/JobRuntimeHeuristic.java | 10 +- .../heuristics/MemoryLimitHeuristic.java | 18 +- .../heuristics/StageRuntimeHeuristic.java | 9 +- .../drelephant/util/InfoExtractor.java | 5 +- .../drelephant/util/MemoryFormatUtils.java | 1 + app/com/linkedin/drelephant/util/Utils.java | 21 +- .../drelephant/util/YarnURLUtils.java | 2 +- app/controllers/Application.java | 625 ++++++++++-------- app/controllers/PaginationStats.java | 46 +- app/controllers/QueryHandler.java | 1 + app/model/JobHeuristicResult.java | 1 + app/model/JobResult.java | 1 + app/model/StringResult.java | 35 - .../deploy/history/SparkDataCollection.scala | 11 +- .../spark/deploy/history/SparkFSFetcher.scala | 13 +- .../StorageStatusTrackingListener.scala | 1 + app/views/help/helpNoData.scala.html | 1 + .../help/mapreduce/helpException.scala.html | 16 + app/views/help/mapreduce/helpGC.scala.html | 1 + .../mapreduce/helpJobQueueLimit.scala.html | 1 + .../mapreduce/helpMapperDataSkew.scala.html | 1 + .../mapreduce/helpMapperMemory.scala.html | 1 + .../help/mapreduce/helpMapperSpeed.scala.html | 1 + .../help/mapreduce/helpMapperTime.scala.html | 1 + .../help/mapreduce/helpMemory.scala.html | 1 + .../mapreduce/helpReducerDataSkew.scala.html | 1 + .../mapreduce/helpReducerMemory.scala.html | 1 + .../help/mapreduce/helpReducerTime.scala.html | 1 + .../help/mapreduce/helpShuffleSort.scala.html | 1 + app/views/results/compareResults.scala.html | 4 +- app/views/results/flowDetails.scala.html | 4 +- .../results/flowHistoryResults.scala.html | 16 +- .../results/jobHistoryResults.scala.html | 3 +- app/views/results/searchResults.scala.html | 4 +- build.sbt | 2 +- compile.sh | 4 +- conf/routes | 1 - project/.DS_Store | Bin 0 -> 6148 bytes public/js/searchform.js | 170 ++--- start.sh => scripts/start.sh | 0 stop.sh => scripts/stop.sh | 0 .../JobQueueLimitHeuristicTest.java | 12 +- .../MapperDataSkewHeuristicTest.java | 20 +- .../heuristics/MapperGCHeuristicTest.java | 33 +- .../heuristics/MapperMemoryHeuristicTest.java | 16 +- .../heuristics/MapperSpeedHeuristicTest.java | 16 +- .../heuristics/MapperSpillHeuristicTest.java | 18 +- .../heuristics/MapperTimeHeuristicTest.java | 16 +- .../ReducerDataSkewHeuristicTest.java | 19 +- .../heuristics/ReducerGCHeuristicTest.java | 33 +- .../ReducerMemoryHeuristicTest.java | 16 +- .../heuristics/ReducerTimeHeuristicTest.java | 12 +- .../heuristics/ShuffleSortHeuristicTest.java | 12 +- .../spark/MockSparkApplicationData.java | 9 +- ...BestPropertiesConventionHeuristicTest.java | 7 +- .../heuristics/MemoryLimitHeuristicTest.java | 13 +- .../util/MemoryFormatUtilsTest.java | 5 +- .../linkedin/drelephant/util/UtilsTest.java | 1 + 113 files changed, 1328 insertions(+), 809 deletions(-) delete mode 100644 app-conf/elephant.config rename app/com/linkedin/drelephant/{util => configurations/fetcher}/FetcherConfiguration.java (97%) rename app/com/linkedin/drelephant/{util => configurations/fetcher}/FetcherConfigurationData.java (95%) rename app/com/linkedin/drelephant/{util => configurations/heuristic}/HeuristicConfiguration.java (97%) rename app/com/linkedin/drelephant/{util => configurations/heuristic}/HeuristicConfigurationData.java (93%) rename app/com/linkedin/drelephant/{util => configurations/jobtype}/JobTypeConf.java (95%) rename app/com/linkedin/drelephant/mapreduce/{ => data}/MapReduceApplicationData.java (93%) rename app/com/linkedin/drelephant/mapreduce/{MapReduceCounterHolder.java => data/MapReduceCounterData.java} (97%) rename app/com/linkedin/drelephant/mapreduce/{ => data}/MapReduceTaskData.java (80%) rename app/com/linkedin/drelephant/{ => security}/HadoopSecurity.java (95%) rename app/com/linkedin/drelephant/spark/{ => data}/SparkApplicationData.java (95%) rename app/com/linkedin/drelephant/spark/{ => data}/SparkEnvironmentData.java (97%) rename app/com/linkedin/drelephant/spark/{ => data}/SparkExecutorData.java (95%) rename app/com/linkedin/drelephant/spark/{ => data}/SparkGeneralData.java (97%) rename app/com/linkedin/drelephant/spark/{ => data}/SparkJobProgressData.java (94%) rename app/com/linkedin/drelephant/spark/{ => data}/SparkStorageData.java (96%) delete mode 100644 app/model/StringResult.java create mode 100644 project/.DS_Store rename start.sh => scripts/start.sh (100%) rename stop.sh => scripts/stop.sh (100%) diff --git a/README.md b/README.md index b1e597751..79dca364c 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,7 @@ jvm\_props="... -Devolutionplugin=enabled -DapplyEvolutions.default=true" ``` * To start dr-elephant, run the start script specifying a path to the application's configuration files. ``` -$DR\_RELEASE/bin/start.sh -appconf $DR\_RELEASE/../../app-conf +$DR\_RELEASE/bin/start.sh $DR\_RELEASE/../../app-conf ``` * To stop dr-elephant run, ``` @@ -209,6 +209,41 @@ cd /export/apps/elephant/ ``` * Run Doctor Elephant, it should now include the new heuristics. +## Project Structure + + app → Contains all the source files + └ com.linkedin.drelepahnt → Application Daemons + └ org.apache.spark → Spark Support + └ controllers → Controller logic + └ models → Includes models that Map to DB + └ views → Page templates + + app-conf → Application Configurations + └ elephant.conf → Port, DB, Keytab and other JVM Configurations (Overrides application.conf) + └ FetcherConf.xml → Fetcher Configurations + └ HeuristicConf.xml → Heuristic Configurations + └ JobTypeConf.xml → JobType Configurations + + conf → Configurations files + └ evolutions → DB Schema + └ application.conf → Main configuration file + └ log4j.properties → log configuration file + └ routes → Routes definition + + public → Public assets + └ assets → Library files + └ css → CSS files + └ images → Image files + └ js → Javascript files + + scripts + └ start.sh → Starts Dr. Elephant + └ stop.sh → Stops Dr. Elephant + + test → Source folder for unit tests + + compile.sh → Compiles the application + ## License Copyright 2015 LinkedIn Corp. diff --git a/app-conf/elephant.config b/app-conf/elephant.config deleted file mode 100644 index 575810fcc..000000000 --- a/app-conf/elephant.config +++ /dev/null @@ -1,23 +0,0 @@ -# -# Copyright 2015 LinkedIn Corp. -# -# 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. -# - -port=8080 -db_url=localhost -db_name=drelephant -db_user=root -db_password= -keytab_location="/export/apps/hadoop/keytabs/dr_elephant-service.keytab" -keytab_user="elephant/eat1-magicaz01.grid.linkedin.com" diff --git a/app/Global.java b/app/Global.java index b47240b93..c2956deab 100644 --- a/app/Global.java +++ b/app/Global.java @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ + import com.linkedin.drelephant.DrElephant; import com.sun.security.sasl.util.AbstractSaslImpl; @@ -26,12 +27,15 @@ import java.util.logging.Level; +/** + * This class manages all the global settings + */ public class Global extends GlobalSettings { DrElephant _drElephant; public void onStart(Application app) { - Logger.info("Application has started"); + Logger.info("Starting Application..."); fixJavaKerberos(); @@ -44,7 +48,7 @@ public void onStart(Application app) { } public void onStop(Application app) { - Logger.info("Application shutdown..."); + Logger.info("Stopping application..."); if (_drElephant != null) { _drElephant.kill(); } diff --git a/app/com/linkedin/drelephant/DrElephant.java b/app/com/linkedin/drelephant/DrElephant.java index df52adbc8..1dcf768b4 100644 --- a/app/com/linkedin/drelephant/DrElephant.java +++ b/app/com/linkedin/drelephant/DrElephant.java @@ -13,11 +13,15 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant; import java.io.IOException; +/** + * The main class which starts Dr. Elephant + */ public class DrElephant extends Thread { private ElephantRunner _elephant; diff --git a/app/com/linkedin/drelephant/ElephantContext.java b/app/com/linkedin/drelephant/ElephantContext.java index c5251be7e..4daa90499 100644 --- a/app/com/linkedin/drelephant/ElephantContext.java +++ b/app/com/linkedin/drelephant/ElephantContext.java @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant; import com.google.common.collect.ImmutableMap; @@ -23,11 +24,11 @@ import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.JobType; -import com.linkedin.drelephant.util.FetcherConfiguration; -import com.linkedin.drelephant.util.FetcherConfigurationData; -import com.linkedin.drelephant.util.HeuristicConfiguration; -import com.linkedin.drelephant.util.HeuristicConfigurationData; -import com.linkedin.drelephant.util.JobTypeConf; +import com.linkedin.drelephant.configurations.fetcher.FetcherConfiguration; +import com.linkedin.drelephant.configurations.fetcher.FetcherConfigurationData; +import com.linkedin.drelephant.configurations.heuristic.HeuristicConfiguration; +import com.linkedin.drelephant.configurations.heuristic.HeuristicConfigurationData; +import com.linkedin.drelephant.configurations.jobtype.JobTypeConf; import com.linkedin.drelephant.util.Utils; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; @@ -63,8 +64,7 @@ public class ElephantContext { private List _fetchersConfData; private final Map _nameToType = new HashMap(); - private final Map> _typeToHeuristics = - new HashMap>(); + private final Map> _typeToHeuristics = new HashMap>(); private final Map _typeToFetcher = new HashMap(); private Map> _appTypeToJobTypes = new HashMap>(); @@ -205,7 +205,7 @@ private void configureSupportedApplicationTypes() { _typeToHeuristics.keySet().retainAll(supportedTypes); _appTypeToJobTypes.keySet().retainAll(supportedTypes); - logger.info("ElephantContext configured:"); + logger.info("Configuring ElephantContext..."); for (ApplicationType type : supportedTypes) { _nameToType.put(type.getName(), type); @@ -216,12 +216,15 @@ private void configureSupportedApplicationTypes() { } List jobTypes = _appTypeToJobTypes.get(type); - logger.info("Supports " + type.getName() + " application type, using " + _typeToFetcher.get(type) + logger.info("Supports " + type.getName() + " application type, using " + _typeToFetcher.get(type).toString() + " fetcher class with Heuristics [" + StringUtils.join(classes, ", ") + "] and following JobTypes [" + StringUtils.join(jobTypes, ", ") + "]."); } } + /** + * Load all the job types configured in JobTypeConf.xml + */ private void loadJobTypes() { JobTypeConf conf = new JobTypeConf(JOB_TYPES_CONF); _appTypeToJobTypes = conf.getAppTypeToJobTypeList(); diff --git a/app/com/linkedin/drelephant/ElephantRunner.java b/app/com/linkedin/drelephant/ElephantRunner.java index 319ffe852..879a9f1a5 100644 --- a/app/com/linkedin/drelephant/ElephantRunner.java +++ b/app/com/linkedin/drelephant/ElephantRunner.java @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant; import com.linkedin.drelephant.analysis.AnalyticJob; @@ -21,6 +22,7 @@ import com.linkedin.drelephant.analysis.HadoopSystemContext; import com.linkedin.drelephant.analysis.AnalyticJobGeneratorHadoop2; +import com.linkedin.drelephant.security.HadoopSecurity; import java.io.IOException; import java.security.PrivilegedAction; import java.util.List; @@ -37,10 +39,15 @@ import org.apache.log4j.Logger; +/** + * The class that runs the Dr. Elephant daemon + */ public class ElephantRunner implements Runnable { - private static final long WAIT_INTERVAL = 60 * 1000; - private static final int EXECUTOR_NUM = 5; private static final Logger logger = Logger.getLogger(ElephantRunner.class); + + private static final long WAIT_INTERVAL = 60 * 1000; // Interval between fetches and retries + private static final int EXECUTOR_NUM = 5; // The number of executor threads to analyse the jobs + private AtomicBoolean _running = new AtomicBoolean(true); private long lastRun; private HadoopSecurity _hadoopSecurity; @@ -86,7 +93,7 @@ public Void run() { while (_running.get() && !Thread.currentThread().isInterrupted()) { lastRun = System.currentTimeMillis(); - logger.info("Fetching analytic job list....."); + logger.info("Fetching analytic job list..."); try { _hadoopSecurity.checkLogin(); diff --git a/app/com/linkedin/drelephant/analysis/AnalyticJob.java b/app/com/linkedin/drelephant/analysis/AnalyticJob.java index 2e2bc79be..2631abcdc 100644 --- a/app/com/linkedin/drelephant/analysis/AnalyticJob.java +++ b/app/com/linkedin/drelephant/analysis/AnalyticJob.java @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.analysis; import com.linkedin.drelephant.ElephantContext; @@ -27,14 +28,13 @@ /** * This class wraps some basic meta data of a completed application run (notice that the information is generally the - * same regardless of hadoop versions and application types), and then promises to return the analyzed result - * later. + * same regardless of hadoop versions and application types), and then promises to return the analyzed result later. */ public class AnalyticJob { private static final Logger logger = Logger.getLogger(AnalyticJob.class); - // The defualt job type when the data matches nothing. - private static final String UNKNOWN_JOB_TYPE = "Unknown"; - private static final int _RETRY_LIMIT = 3; + + private static final String UNKNOWN_JOB_TYPE = "Unknown"; // The default job type when the data matches nothing. + private static final int _RETRY_LIMIT = 3; // Number of times a job needs to be tried before dropping private int _retries = 0; private ApplicationType _type; @@ -46,73 +46,164 @@ public class AnalyticJob { private long _startTime; private long _finishTime; + /** + * Returns the application type + * E.g., Mapreduce or Spark + * + * @return The application type + */ public ApplicationType getAppType() { return _type; } + /** + * Set the application type of this job. + * + * @param type The Application type + * @return The analytic job + */ public AnalyticJob setAppType(ApplicationType type) { _type = type; return this; } + /** + * Set the application id of this job + * + * @param appId The application id of the job obtained resource manager + * @return The analytic job + */ public AnalyticJob setAppId(String appId) { _appId = appId; return this; } + /** + * Set the id of the job + * jobId is the appId with the prefix 'application_' replaced by 'job_' + * + * @param jobId The job id + * @return The analytic job + */ public AnalyticJob setJobId(String jobId) { _jobId = jobId; return this; } + /** + * Set the name of the analytic job + * + * @param name + * @return The analytic job + */ public AnalyticJob setName(String name) { _name = name; return this; } + /** + * Sets the user who ran the job + * + * @param user The username of the user + * @return The analytic job + */ public AnalyticJob setUser(String user) { _user = user; return this; } + /** + * Sets the start time of the job + * Start time is the time at which the job was submitted by the resource manager + * + * @param startTime + * @return The analytic job + */ public AnalyticJob setStartTime(long startTime) { _startTime = startTime; return this; } + /** + * Sets the finish time of the job + * + * @param finishTime + * @return The analytic job + */ public AnalyticJob setFinishTime(long finishTime) { _finishTime = finishTime; return this; } + /** + * Returns the application id + * + * @return The analytic job + */ public String getAppId() { return _appId; } + /** + * Returns the job id + * + * @return The job id + */ public String getJobId() { return _jobId; } + /** + * Returns the name of the analytic job + * + * @return the analytic job's name + */ public String getName() { return _name; } + /** + * Returns the user who ran the job + * + * @return The user who ran the analytic job + */ public String getUser() { return _user; } + /** + * Returns the time at which the job was submitted by the resource manager + * + * @return The start time + */ public long getStartTime() { return _startTime; } + /** + * Returns the finish time of the job. + * + * @return The finish time + */ public long getFinishTime() { return _finishTime; } + /** + * Returns the tracking url of the job + * + * @return The tracking url in resource manager + */ public String getTrackingUrl() { return _trackingUrl; } + /** + * Sets the tracking url for the job + * + * @param trackingUrl The url to track the job + * @return The analytic job + */ public AnalyticJob setTrackingUrl(String trackingUrl) { _trackingUrl = trackingUrl; return this; @@ -121,15 +212,18 @@ public AnalyticJob setTrackingUrl(String trackingUrl) { /** * Returns the analysed JobResult that could be directly serialized into DB. * + * This method fetches the data using the appropriate application fetcher, runs all the heuristics on them and + * loads it into the JobResult model. + * * @throws Exception if the analysis process encountered a problem. * @return the analysed JobResult */ public JobResult getAnalysis() throws Exception { ElephantFetcher fetcher = ElephantContext.instance().getFetcherForApplicationType(getAppType()); - HadoopApplicationData data = fetcher.fetchData(this); - List analysisResults = new ArrayList(); + // Run all heuristics over the fetched data + List analysisResults = new ArrayList(); if (data == null || data.isEmpty()) { // Example: a MR job has 0 mappers and 0 reducers logger.info("No Data Received for analytic job: " + getAppId()); @@ -147,10 +241,8 @@ public JobResult getAnalysis() throws Exception { JobType jobType = ElephantContext.instance().matchJobType(data); String jobTypeName = jobType == null ? UNKNOWN_JOB_TYPE : jobType.getName(); + // Load job information JobResult result = new JobResult(); - // Note: before adding Spark analysers, all JobResult are using job ids as the primary key. But Spark (and many - // other non-mapreduce applications) does not have a job id. To maintain backwards compatibility, we replace - // 'application' with 'job' to form a pseudo one. result.jobId = Utils.getJobIdFromApplicationId(getAppId()); result.url = getTrackingUrl(); result.username = getUser(); @@ -163,10 +255,10 @@ public JobResult getAnalysis() throws Exception { if (result.jobName.length() > 100) { result.jobName = result.jobName.substring(0, 97) + "..."; } - result.heuristicResults = new ArrayList(); + // Load Job Heuristic information + result.heuristicResults = new ArrayList(); Severity worstSeverity = Severity.NONE; - for (HeuristicResult heuristicResult : analysisResults) { JobHeuristicResult detail = new JobHeuristicResult(); detail.analysisName = heuristicResult.getAnalysis(); @@ -179,8 +271,8 @@ public JobResult getAnalysis() throws Exception { result.heuristicResults.add(detail); worstSeverity = Severity.max(worstSeverity, detail.severity); } - result.severity = worstSeverity; + // Retrieve Azkaban execution, flow and jobs URLs from jobData and store them into result. InfoExtractor.retrieveURLs(result, data); diff --git a/app/com/linkedin/drelephant/analysis/AnalyticJobGenerator.java b/app/com/linkedin/drelephant/analysis/AnalyticJobGenerator.java index 2e7e8e9f4..6010e3f66 100644 --- a/app/com/linkedin/drelephant/analysis/AnalyticJobGenerator.java +++ b/app/com/linkedin/drelephant/analysis/AnalyticJobGenerator.java @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.analysis; import java.io.IOException; @@ -24,9 +25,9 @@ /** * Provides AnalyticJobs that will yield to analysis results later. This class basically generates to-dos that could be * executed later. - * */ public interface AnalyticJobGenerator { + /** * Configures the provider instance * diff --git a/app/com/linkedin/drelephant/analysis/AnalyticJobGeneratorHadoop2.java b/app/com/linkedin/drelephant/analysis/AnalyticJobGeneratorHadoop2.java index 57831bcec..0798fc3b9 100644 --- a/app/com/linkedin/drelephant/analysis/AnalyticJobGeneratorHadoop2.java +++ b/app/com/linkedin/drelephant/analysis/AnalyticJobGeneratorHadoop2.java @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.analysis; import com.linkedin.drelephant.ElephantContext; @@ -37,13 +38,14 @@ /** * This class provides a list of analysis promises to be generated under Hadoop YARN environment - * */ public class AnalyticJobGeneratorHadoop2 implements AnalyticJobGenerator { private static final Logger logger = Logger.getLogger(AnalyticJobGeneratorHadoop2.class); private static final String RESOURCE_MANAGER_ADDRESS = "yarn.resourcemanager.webapp.address"; + // We provide one minute job fetch delay due to the job sending lag from AM/NM to JobHistoryServer HDFS private static final long FETCH_DELAY = 60000; + // Generate a token update interval with a random deviation so that it does not update the token exactly at the same // time with other token updaters (e.g. ElephantFetchers). private static final long TOKEN_UPDATE_INTERVAL = @@ -70,36 +72,38 @@ public void configure(Configuration configuration) } } + /** + * Fetch all the succeeded and failed applications/analytic jobs from the resource manager. + * + * @return + * @throws IOException + * @throws AuthenticationException + */ @Override public List fetchAnalyticJobs() throws IOException, AuthenticationException { List appList = new ArrayList(); - // There is a lag of job data from AM/NM to JobHistoryServer HDFS, we shouldn't use the current - // time, since there might be new jobs arriving after we fetch jobs. - // We provide one minute delay to address this lag. + // There is a lag of job data from AM/NM to JobHistoryServer HDFS, we shouldn't use the current time, since there + // might be new jobs arriving after we fetch jobs. We provide one minute delay to address this lag. _currentTime = System.currentTimeMillis() - FETCH_DELAY; updateAuthToken(); logger.info("Fetching recent finished application runs between last time: " + (_lastTime + 1) + ", and current time: " + _currentTime); - URL succeededAppsURL = - new URL(new URL("http://" + _resourceManagerAddress), String.format( + // Fetch all succeeded apps + URL succeededAppsURL = new URL(new URL("http://" + _resourceManagerAddress), String.format( "/ws/v1/cluster/apps?finalStatus=SUCCEEDED&finishedTimeBegin=%s&finishedTimeEnd=%s", String.valueOf(_lastTime + 1), String.valueOf(_currentTime))); - List succeededApps = readApps(succeededAppsURL); - appList.addAll(succeededApps); - URL failedAppsURL = - new URL(new URL("http://" + _resourceManagerAddress), String.format( + // Fetch all failed apps + URL failedAppsURL = new URL(new URL("http://" + _resourceManagerAddress), String.format( "/ws/v1/cluster/apps?finalStatus=FAILED&finishedTimeBegin=%s&finishedTimeEnd=%s", String.valueOf(_lastTime + 1), String.valueOf(_currentTime))); - List failedApps = readApps(failedAppsURL); - appList.addAll(failedApps); // Append promises from the retry queue at the end of the list @@ -116,21 +120,40 @@ public void addIntoRetries(AnalyticJob promise) { _retryQueue.add(promise); } + /** + * Authenticate and update the token + */ private void updateAuthToken() { if (_currentTime - _tokenUpdatedTime > TOKEN_UPDATE_INTERVAL) { - logger.info("AnalysisProvider updating its Authenticate Token...."); + logger.info("AnalysisProvider updating its Authenticate Token..."); _token = new AuthenticatedURL.Token(); _authenticatedURL = new AuthenticatedURL(); _tokenUpdatedTime = _currentTime; } } + /** + * Connect to url using token and return the JsonNode + * + * @param url The url to connect to + * @return + * @throws IOException Unable to get the stream + * @throws AuthenticationException Authencation problem + */ private JsonNode readJsonNode(URL url) throws IOException, AuthenticationException { HttpURLConnection conn = _authenticatedURL.openConnection(url, _token); return _objectMapper.readTree(conn.getInputStream()); } + /** + * Parse the returned json from Resource manager + * + * @param url The REST call + * @return + * @throws IOException + * @throws AuthenticationException Problem authenticating to resource manager + */ private List readApps(URL url) throws IOException, AuthenticationException{ List appList = new ArrayList(); diff --git a/app/com/linkedin/drelephant/analysis/ApplicationType.java b/app/com/linkedin/drelephant/analysis/ApplicationType.java index b5f281aea..dfa09386a 100644 --- a/app/com/linkedin/drelephant/analysis/ApplicationType.java +++ b/app/com/linkedin/drelephant/analysis/ApplicationType.java @@ -13,12 +13,11 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.analysis; /** * Manages and represents supported application types. - * - * @author yizhou */ public class ApplicationType { private final String _name; diff --git a/app/com/linkedin/drelephant/analysis/ElephantFetcher.java b/app/com/linkedin/drelephant/analysis/ElephantFetcher.java index e999cda15..e4e178892 100644 --- a/app/com/linkedin/drelephant/analysis/ElephantFetcher.java +++ b/app/com/linkedin/drelephant/analysis/ElephantFetcher.java @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.analysis; /** @@ -25,10 +26,10 @@ public interface ElephantFetcher { /** * Given an application id, fetches the data object * - * @param appId the application id + * @param job The job being analysed * @return the fetched data * @throws Exception */ public T fetchData(AnalyticJob job) throws Exception; -} +} \ No newline at end of file diff --git a/app/com/linkedin/drelephant/analysis/HDFSContext.java b/app/com/linkedin/drelephant/analysis/HDFSContext.java index 8d5a05b74..35deedc3d 100644 --- a/app/com/linkedin/drelephant/analysis/HDFSContext.java +++ b/app/com/linkedin/drelephant/analysis/HDFSContext.java @@ -13,15 +13,19 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.analysis; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.log4j.Logger; - import java.io.IOException; + +/** + * The HDFS Information + */ public final class HDFSContext { private static final Logger logger = Logger.getLogger(HDFSContext.class); @@ -32,6 +36,9 @@ private HDFSContext() { // Empty on purpose } + /** + * Captures the HDFS Block Size + */ public static void load() { try { HDFS_BLOCK_SIZE = FileSystem.get(new Configuration()).getDefaultBlockSize(new Path("/")); diff --git a/app/com/linkedin/drelephant/analysis/HadoopApplicationData.java b/app/com/linkedin/drelephant/analysis/HadoopApplicationData.java index bbf3e3d88..9058a0ad7 100644 --- a/app/com/linkedin/drelephant/analysis/HadoopApplicationData.java +++ b/app/com/linkedin/drelephant/analysis/HadoopApplicationData.java @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.analysis; import java.util.Properties; diff --git a/app/com/linkedin/drelephant/analysis/HadoopSystemContext.java b/app/com/linkedin/drelephant/analysis/HadoopSystemContext.java index 5ed366cd9..ab38fe533 100644 --- a/app/com/linkedin/drelephant/analysis/HadoopSystemContext.java +++ b/app/com/linkedin/drelephant/analysis/HadoopSystemContext.java @@ -13,14 +13,18 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.analysis; import org.apache.hadoop.conf.Configuration; + +/** + * Hadoop System Information + */ public final class HadoopSystemContext { private static final String MAPREDUCE_FRAMEWORK_NAME_PROP = "mapreduce.framework.name"; - private static final String MAPRED_JOB_TRACKER_PROP = "mapred.job.tracker.http.address"; private static final String YARN = "yarn"; /** diff --git a/app/com/linkedin/drelephant/analysis/Heuristic.java b/app/com/linkedin/drelephant/analysis/Heuristic.java index 420413d35..6d9136381 100644 --- a/app/com/linkedin/drelephant/analysis/Heuristic.java +++ b/app/com/linkedin/drelephant/analysis/Heuristic.java @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.analysis; /** diff --git a/app/com/linkedin/drelephant/analysis/HeuristicResult.java b/app/com/linkedin/drelephant/analysis/HeuristicResult.java index 9ebb49f47..cc556554a 100644 --- a/app/com/linkedin/drelephant/analysis/HeuristicResult.java +++ b/app/com/linkedin/drelephant/analysis/HeuristicResult.java @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.analysis; import com.linkedin.drelephant.util.Utils; @@ -22,6 +23,9 @@ import org.apache.commons.lang.StringUtils; +/** + * Holds the Heuristic analysis result Information + */ public class HeuristicResult { public static final HeuristicResult NO_DATA = new HeuristicResult("No Data Received", Severity.LOW); @@ -30,16 +34,32 @@ public class HeuristicResult { private List _details; private int _detailsColumns = 0; + /** + * Heuristic Result Constructor + * + * @param analysis The name of the heuristic + * @param severity The severity level of the heuristic + */ public HeuristicResult(String analysis, Severity severity) { this._analysis = analysis; this._severity = severity; this._details = new ArrayList(); } + /** + * Returns the heuristic analyser name + * + * @return the analysis name + */ public String getAnalysis() { return _analysis; } + /** + * Returns the severity of the Heuristic + * + * @return The severity + */ public Severity getSeverity() { return _severity; } @@ -83,6 +103,11 @@ public void addDetail(String... parts) { } } + /** + * Set the severity of the heuristic + * + * @param severity The severity to be set + */ public void setSeverity(Severity severity) { this._severity = severity; } diff --git a/app/com/linkedin/drelephant/analysis/JobType.java b/app/com/linkedin/drelephant/analysis/JobType.java index 57caa0dfd..d6a4dbb12 100644 --- a/app/com/linkedin/drelephant/analysis/JobType.java +++ b/app/com/linkedin/drelephant/analysis/JobType.java @@ -13,12 +13,17 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.analysis; import java.util.Properties; import java.util.regex.Pattern; +/** + * Job Type holder. Includes for each Job type, the configuration key that uniquely identifies that type and the + * regex pattern to match the property. + */ public class JobType { private final String _name; private final String _confName; diff --git a/app/com/linkedin/drelephant/analysis/Severity.java b/app/com/linkedin/drelephant/analysis/Severity.java index c94ea5049..8aad62b1d 100644 --- a/app/com/linkedin/drelephant/analysis/Severity.java +++ b/app/com/linkedin/drelephant/analysis/Severity.java @@ -13,11 +13,15 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.analysis; import com.avaje.ebean.annotation.EnumValue; +/** + * The severities that you can use + */ public enum Severity { @EnumValue("4") CRITICAL(4, "Critical", "danger"), @@ -38,24 +42,50 @@ public enum Severity { private String _text; private String _bootstrapColor; + /** + * @param value The severity value + * @param text The severity name + * @param bootstrapColor The severity level for color coding + */ Severity(int value, String text, String bootstrapColor) { this._value = value; this._text = text; this._bootstrapColor = bootstrapColor; } + /** + * Returns the severity level + * + * @return The severity value (0 to 5) + */ public int getValue() { return _value; } + /** + * Returns the Severity level Name + * + * @return Severity level (None, Low, Moderate, Sever, Critical) + */ public String getText() { return _text; } + /** + * Returns the severity level for color coding + * + * @return The severity level (color) + */ public String getBootstrapColor() { return _bootstrapColor; } + /** + * Returns the Severity corresponding to the severity value, NONE severity otherwise + * + * @param value The severity values (0 to 5) + * @return The severity + */ public static Severity byValue(int value) { for (Severity severity : values()) { if (severity._value == value) { @@ -65,6 +95,13 @@ public static Severity byValue(int value) { return NONE; } + /** + * Returns the maximum of the severities + * + * @param a One severity + * @param b The other severity + * @return Max(a,b) + */ public static Severity max(Severity a, Severity b) { if (a._value > b._value) { return a; @@ -72,6 +109,12 @@ public static Severity max(Severity a, Severity b) { return b; } + /** + * Returns the maximum of the severities in the array + * + * @param severities Arbitrary number of severities + * @return Max(severities) + */ public static Severity max(Severity... severities) { Severity currentSeverity = NONE; for (Severity severity : severities) { @@ -80,6 +123,13 @@ public static Severity max(Severity... severities) { return currentSeverity; } + /** + * Returns the minimum of the severities + * + * @param a One severity + * @param b The other severity + * @return Min(a,b) + */ public static Severity min(Severity a, Severity b) { if (a._value < b._value) { return a; @@ -87,7 +137,18 @@ public static Severity min(Severity a, Severity b) { return b; } - public static Severity getSeverityAscending(Number value, Number low, Number moderate, Number severe, Number critical) { + /** + * Returns the severity level of the value in the given thresholds + * low < moderate < severe < critical + * + * Critical when value is greater than the critical threshold + * None when the value is less than the low threshold. + * + * @param value The value being tested + * @return One of the 5 severity levels + */ + public static Severity getSeverityAscending(Number value, Number low, Number moderate, Number severe, + Number critical) { if (value.doubleValue() >= critical.doubleValue()) { return CRITICAL; } @@ -103,7 +164,18 @@ public static Severity getSeverityAscending(Number value, Number low, Number mod return NONE; } - public static Severity getSeverityDescending(Number value, Number low, Number moderate, Number severe, Number critical) { + /** + * Returns the severity level of the value in the given thresholds + * low > moderate > severe > critical + * + * Critical when value is less than the critical threshold + * None when the value is greater than the low threshold. + * + * @param value The value being tested + * @return One of the 5 severity levels + */ + public static Severity getSeverityDescending(Number value, Number low, Number moderate, Number severe, + Number critical) { if (value.doubleValue() <= critical.doubleValue()) { return CRITICAL; } diff --git a/app/com/linkedin/drelephant/util/FetcherConfiguration.java b/app/com/linkedin/drelephant/configurations/fetcher/FetcherConfiguration.java similarity index 97% rename from app/com/linkedin/drelephant/util/FetcherConfiguration.java rename to app/com/linkedin/drelephant/configurations/fetcher/FetcherConfiguration.java index 53eed6fd5..b6006c670 100644 --- a/app/com/linkedin/drelephant/util/FetcherConfiguration.java +++ b/app/com/linkedin/drelephant/configurations/fetcher/FetcherConfiguration.java @@ -13,7 +13,8 @@ * License for the specific language governing permissions and limitations under * the License. */ -package com.linkedin.drelephant.util; + +package com.linkedin.drelephant.configurations.fetcher; import com.linkedin.drelephant.analysis.ApplicationType; import java.util.ArrayList; @@ -28,7 +29,7 @@ /** - * The Fetcher Configuration Utilities + * This class manages the Fetcher Configurations */ public class FetcherConfiguration { private static final Logger logger = Logger.getLogger(FetcherConfiguration.class); diff --git a/app/com/linkedin/drelephant/util/FetcherConfigurationData.java b/app/com/linkedin/drelephant/configurations/fetcher/FetcherConfigurationData.java similarity index 95% rename from app/com/linkedin/drelephant/util/FetcherConfigurationData.java rename to app/com/linkedin/drelephant/configurations/fetcher/FetcherConfigurationData.java index 168280794..251908635 100644 --- a/app/com/linkedin/drelephant/util/FetcherConfigurationData.java +++ b/app/com/linkedin/drelephant/configurations/fetcher/FetcherConfigurationData.java @@ -13,7 +13,8 @@ * License for the specific language governing permissions and limitations under * the License. */ -package com.linkedin.drelephant.util; + +package com.linkedin.drelephant.configurations.fetcher; import com.linkedin.drelephant.analysis.ApplicationType; import java.util.Map; diff --git a/app/com/linkedin/drelephant/util/HeuristicConfiguration.java b/app/com/linkedin/drelephant/configurations/heuristic/HeuristicConfiguration.java similarity index 97% rename from app/com/linkedin/drelephant/util/HeuristicConfiguration.java rename to app/com/linkedin/drelephant/configurations/heuristic/HeuristicConfiguration.java index 9838267ac..436a04fc3 100644 --- a/app/com/linkedin/drelephant/util/HeuristicConfiguration.java +++ b/app/com/linkedin/drelephant/configurations/heuristic/HeuristicConfiguration.java @@ -13,7 +13,8 @@ * License for the specific language governing permissions and limitations under * the License. */ -package com.linkedin.drelephant.util; + +package com.linkedin.drelephant.configurations.heuristic; import com.linkedin.drelephant.analysis.ApplicationType; import java.util.ArrayList; @@ -27,6 +28,9 @@ import org.w3c.dom.NodeList; +/** + * This class manages the Heuristic Configurations + */ public class HeuristicConfiguration { private static final Logger logger = Logger.getLogger(HeuristicConfiguration.class); private List _heuristicsConfDataList; diff --git a/app/com/linkedin/drelephant/util/HeuristicConfigurationData.java b/app/com/linkedin/drelephant/configurations/heuristic/HeuristicConfigurationData.java similarity index 93% rename from app/com/linkedin/drelephant/util/HeuristicConfigurationData.java rename to app/com/linkedin/drelephant/configurations/heuristic/HeuristicConfigurationData.java index d0d96c866..459d9c474 100644 --- a/app/com/linkedin/drelephant/util/HeuristicConfigurationData.java +++ b/app/com/linkedin/drelephant/configurations/heuristic/HeuristicConfigurationData.java @@ -13,12 +13,16 @@ * License for the specific language governing permissions and limitations under * the License. */ -package com.linkedin.drelephant.util; + +package com.linkedin.drelephant.configurations.heuristic; import com.linkedin.drelephant.analysis.ApplicationType; import java.util.Map; +/** + * The Heuristic Configuration Holder + */ public class HeuristicConfigurationData { private final String _heuristicName; private final String _className; diff --git a/app/com/linkedin/drelephant/util/JobTypeConf.java b/app/com/linkedin/drelephant/configurations/jobtype/JobTypeConf.java similarity index 95% rename from app/com/linkedin/drelephant/util/JobTypeConf.java rename to app/com/linkedin/drelephant/configurations/jobtype/JobTypeConf.java index 6a41a5146..4882d9e7e 100644 --- a/app/com/linkedin/drelephant/util/JobTypeConf.java +++ b/app/com/linkedin/drelephant/configurations/jobtype/JobTypeConf.java @@ -13,33 +13,29 @@ * License for the specific language governing permissions and limitations under * the License. */ -package com.linkedin.drelephant.util; + +package com.linkedin.drelephant.configurations.jobtype; import com.linkedin.drelephant.analysis.ApplicationType; -import java.io.IOException; -import java.io.InputStream; +import com.linkedin.drelephant.util.Utils; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.PatternSyntaxException; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; - import org.apache.log4j.Logger; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; -import org.xml.sax.SAXException; import com.linkedin.drelephant.analysis.JobType; -import play.Play; - +/** + * This class manages the job type configurations + */ public class JobTypeConf { private static final Logger logger = Logger.getLogger(JobTypeConf.class); private static final int TYPE_LEN_LIMIT = 20; diff --git a/app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop2.java b/app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop2.java index 8a859e097..ab25f1ba4 100644 --- a/app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop2.java +++ b/app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop2.java @@ -13,12 +13,16 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.mapreduce; import com.linkedin.drelephant.analysis.AnalyticJob; import com.linkedin.drelephant.analysis.ElephantFetcher; +import com.linkedin.drelephant.mapreduce.data.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.data.MapReduceCounterData; +import com.linkedin.drelephant.mapreduce.data.MapReduceTaskData; import com.linkedin.drelephant.math.Statistics; -import com.linkedin.drelephant.util.FetcherConfigurationData; +import com.linkedin.drelephant.configurations.fetcher.FetcherConfigurationData; import com.linkedin.drelephant.util.Utils; import java.io.IOException; @@ -42,6 +46,9 @@ import org.codehaus.jackson.map.ObjectMapper; +/** + * This class implements the Fetcher for MapReduce Applications on Hadoop2 + */ public class MapReduceFetcherHadoop2 implements ElephantFetcher { private static final Logger logger = Logger.getLogger(ElephantFetcher.class); // We provide one minute job fetch delay due to the job sending lag from AM/NM to JobHistoryServer HDFS @@ -54,8 +61,9 @@ public class MapReduceFetcherHadoop2 implements ElephantFetcher mappe boolean isMapper = task.get("type").getValueAsText().equals("MAP"); URL taskCounterURL = getTaskCounterURL(jobId, taskId); - MapReduceCounterHolder taskCounter = getTaskCounter(taskCounterURL); + MapReduceCounterData taskCounter = getTaskCounter(taskCounterURL); URL taskAttemptURL = getTaskAttemptURL(jobId, taskId, attemptId); long[] taskExecTime = getTaskExecTime(taskAttemptURL); @@ -340,7 +349,8 @@ public Integer initialValue() { @Override public Pattern initialValue() { // Example: "Task task_1443068695259_9143_m_000475 failed 1 times" - return Pattern.compile("Task[\\s\\u00A0]+(.*)[\\s\\u00A0]+failed[\\s\\u00A0]+([0-9])[\\s\\u00A0]+times[\\s\\u00A0]+"); + return Pattern.compile( + "Task[\\s\\u00A0]+(.*)[\\s\\u00A0]+failed[\\s\\u00A0]+([0-9])[\\s\\u00A0]+times[\\s\\u00A0]+"); } }; diff --git a/app/com/linkedin/drelephant/mapreduce/MapReduceApplicationData.java b/app/com/linkedin/drelephant/mapreduce/data/MapReduceApplicationData.java similarity index 93% rename from app/com/linkedin/drelephant/mapreduce/MapReduceApplicationData.java rename to app/com/linkedin/drelephant/mapreduce/data/MapReduceApplicationData.java index 9d6db3416..c365a05fc 100644 --- a/app/com/linkedin/drelephant/mapreduce/MapReduceApplicationData.java +++ b/app/com/linkedin/drelephant/mapreduce/data/MapReduceApplicationData.java @@ -13,13 +13,17 @@ * License for the specific language governing permissions and limitations under * the License. */ -package com.linkedin.drelephant.mapreduce; + +package com.linkedin.drelephant.mapreduce.data; import com.linkedin.drelephant.analysis.ApplicationType; import com.linkedin.drelephant.analysis.HadoopApplicationData; import java.util.Properties; +/** + * This class contains the MapReduce Application Information + */ public class MapReduceApplicationData implements HadoopApplicationData { private static final ApplicationType APPLICATION_TYPE = new ApplicationType("MAPREDUCE"); @@ -32,7 +36,7 @@ public class MapReduceApplicationData implements HadoopApplicationData { private String _jobName = ""; private long _startTime = 0; private long _finishTime = 0; - private MapReduceCounterHolder _counterHolder; + private MapReduceCounterData _counterHolder; private MapReduceTaskData[] _mapperData; private MapReduceTaskData[] _reducerData; private Properties _jobConf; @@ -88,7 +92,7 @@ public MapReduceApplicationData setUrl(String url) { return this; } - public MapReduceApplicationData setCounters(MapReduceCounterHolder counterHolder) { + public MapReduceApplicationData setCounters(MapReduceCounterData counterHolder) { this._counterHolder = counterHolder; return this; } @@ -108,7 +112,7 @@ public MapReduceApplicationData setJobConf(Properties jobConf) { return this; } - public MapReduceCounterHolder getCounters() { + public MapReduceCounterData getCounters() { return _counterHolder; } diff --git a/app/com/linkedin/drelephant/mapreduce/MapReduceCounterHolder.java b/app/com/linkedin/drelephant/mapreduce/data/MapReduceCounterData.java similarity index 97% rename from app/com/linkedin/drelephant/mapreduce/MapReduceCounterHolder.java rename to app/com/linkedin/drelephant/mapreduce/data/MapReduceCounterData.java index 1b3d4b81a..f9aac579b 100644 --- a/app/com/linkedin/drelephant/mapreduce/MapReduceCounterHolder.java +++ b/app/com/linkedin/drelephant/mapreduce/data/MapReduceCounterData.java @@ -13,7 +13,8 @@ * License for the specific language governing permissions and limitations under * the License. */ -package com.linkedin.drelephant.mapreduce; + +package com.linkedin.drelephant.mapreduce.data; import java.util.Collections; import java.util.HashMap; @@ -21,7 +22,10 @@ import java.util.Set; -public class MapReduceCounterHolder { +/** + * This class manages all the MapReduce Counters + */ +public class MapReduceCounterData { // This is a map of group to all the counters in the group and their values. private final Map> _pubCounters; @@ -30,7 +34,7 @@ public String toString() { return _pubCounters.toString(); } - public MapReduceCounterHolder() { + public MapReduceCounterData() { _pubCounters = new HashMap>(8); } diff --git a/app/com/linkedin/drelephant/mapreduce/MapReduceTaskData.java b/app/com/linkedin/drelephant/mapreduce/data/MapReduceTaskData.java similarity index 80% rename from app/com/linkedin/drelephant/mapreduce/MapReduceTaskData.java rename to app/com/linkedin/drelephant/mapreduce/data/MapReduceTaskData.java index adefcc396..dee7de705 100644 --- a/app/com/linkedin/drelephant/mapreduce/MapReduceTaskData.java +++ b/app/com/linkedin/drelephant/mapreduce/data/MapReduceTaskData.java @@ -13,16 +13,21 @@ * License for the specific language governing permissions and limitations under * the License. */ -package com.linkedin.drelephant.mapreduce; +package com.linkedin.drelephant.mapreduce.data; + + +/** + * This class manages the MapReduce Tasks + */ public class MapReduceTaskData { - private MapReduceCounterHolder _counterHolder; + private MapReduceCounterData _counterHolder; private long _totalTimeMs = 0; private long _shuffleTimeMs = 0; private long _sortTimeMs = 0; private boolean _timed = false; - public MapReduceTaskData(MapReduceCounterHolder counterHolder, long[] time) { + public MapReduceTaskData(MapReduceCounterData counterHolder, long[] time) { this._counterHolder = counterHolder; this._totalTimeMs = time[0]; this._shuffleTimeMs = time[1]; @@ -30,11 +35,11 @@ public MapReduceTaskData(MapReduceCounterHolder counterHolder, long[] time) { this._timed = true; } - public MapReduceTaskData(MapReduceCounterHolder counterHolder) { + public MapReduceTaskData(MapReduceCounterData counterHolder) { this._counterHolder = counterHolder; } - public MapReduceCounterHolder getCounters() { + public MapReduceCounterData getCounters() { return _counterHolder; } diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/ExceptionHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/ExceptionHeuristic.java index bcda0db69..c35b4bae9 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/ExceptionHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/ExceptionHeuristic.java @@ -1,10 +1,26 @@ +/* + * Copyright 2015 LinkedIn Corp. + * + * 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.linkedin.drelephant.mapreduce.heuristics; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; -import com.linkedin.drelephant.util.HeuristicConfigurationData; +import com.linkedin.drelephant.mapreduce.data.MapReduceApplicationData; +import com.linkedin.drelephant.configurations.heuristic.HeuristicConfigurationData; public class ExceptionHeuristic implements Heuristic { @@ -31,7 +47,8 @@ public HeuristicResult apply(MapReduceApplicationData data) { if (diagnosticInfo != null) { result.addDetail(diagnosticInfo); } else { - String msg = "Unable to find stacktrace info. Please find the real problem in the Jobhistory link above.\nException can happen either in task log or Application Master log."; + String msg = "Unable to find stacktrace info. Please find the real problem in the Jobhistory link above.\n" + + "Exception can happen either in task log or Application Master log."; result.addDetail(msg); } return result; diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/GenericDataSkewHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericDataSkewHeuristic.java index 51957850e..a8b2c4e9f 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/GenericDataSkewHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericDataSkewHeuristic.java @@ -13,26 +13,29 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.mapreduce.heuristics; import com.linkedin.drelephant.analysis.HDFSContext; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; -import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; -import com.linkedin.drelephant.mapreduce.MapReduceTaskData; +import com.linkedin.drelephant.mapreduce.data.MapReduceCounterData; +import com.linkedin.drelephant.mapreduce.data.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.data.MapReduceTaskData; import com.linkedin.drelephant.math.Statistics; -import com.linkedin.drelephant.util.HeuristicConfigurationData; +import com.linkedin.drelephant.configurations.heuristic.HeuristicConfigurationData; import com.linkedin.drelephant.util.Utils; import java.util.Arrays; -import java.util.HashMap; import java.util.Map; import org.apache.commons.io.FileUtils; import org.apache.log4j.Logger; +/** + * This Heuristic analyses the skewness in the task input data + */ public abstract class GenericDataSkewHeuristic implements Heuristic { private static final Logger logger = Logger.getLogger(GenericDataSkewHeuristic.class); @@ -46,7 +49,7 @@ public abstract class GenericDataSkewHeuristic implements Heuristic { private static final Logger logger = Logger.getLogger(GenericGCHeuristic.class); @@ -86,8 +104,8 @@ public HeuristicResult apply(MapReduceApplicationData data) { if (task.timed()) { runtimesMs.add(task.getTotalRunTimeMs()); } - gcMs.add(task.getCounters().get(MapReduceCounterHolder.CounterName.GC_MILLISECONDS)); - cpuMs.add(task.getCounters().get(MapReduceCounterHolder.CounterName.CPU_MILLISECONDS)); + gcMs.add(task.getCounters().get(MapReduceCounterData.CounterName.GC_MILLISECONDS)); + cpuMs.add(task.getCounters().get(MapReduceCounterData.CounterName.CPU_MILLISECONDS)); } long avgRuntimeMs = Statistics.average(runtimesMs); diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/GenericMemoryHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericMemoryHeuristic.java index 3d2f2c621..997bbb01a 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/GenericMemoryHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericMemoryHeuristic.java @@ -13,20 +13,20 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.mapreduce.heuristics; -import com.linkedin.drelephant.util.HeuristicConfigurationData; +import com.linkedin.drelephant.configurations.heuristic.HeuristicConfigurationData; import com.linkedin.drelephant.util.Utils; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; import java.util.List; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; -import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; -import com.linkedin.drelephant.mapreduce.MapReduceTaskData; +import com.linkedin.drelephant.mapreduce.data.MapReduceCounterData; +import com.linkedin.drelephant.mapreduce.data.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.data.MapReduceTaskData; import com.linkedin.drelephant.math.Statistics; import java.util.Map; @@ -34,6 +34,9 @@ import org.apache.log4j.Logger; +/** + * This heuristic deals with the efficiency of container size + */ public abstract class GenericMemoryHeuristic implements Heuristic { private static final Logger logger = Logger.getLogger(GenericMemoryHeuristic.class); private static final long CONTAINER_MEMORY_DEFAULT_BYTES = 2048L * FileUtils.ONE_MB; @@ -106,7 +109,8 @@ public HeuristicResult apply(MapReduceApplicationData data) { } catch (NumberFormatException e) { // Some job has a string var like "${VAR}" for this config. if(containerSizeStr.startsWith("$")) { - String realContainerConf = containerSizeStr.substring(containerSizeStr.indexOf("{")+1, containerSizeStr.indexOf("}")); + String realContainerConf = containerSizeStr.substring(containerSizeStr.indexOf("{")+1, + containerSizeStr.indexOf("}")); containerMem = Long.parseLong(data.getConf().getProperty(realContainerConf)); } else { throw e; @@ -121,8 +125,8 @@ public HeuristicResult apply(MapReduceApplicationData data) { long taskPMin = Long.MAX_VALUE; long taskPMax = 0; for (MapReduceTaskData task : tasks) { - long taskPMem = task.getCounters().get(MapReduceCounterHolder.CounterName.PHYSICAL_MEMORY_BYTES); - long taskVMem = task.getCounters().get(MapReduceCounterHolder.CounterName.VIRTUAL_MEMORY_BYTES); + long taskPMem = task.getCounters().get(MapReduceCounterData.CounterName.PHYSICAL_MEMORY_BYTES); + long taskVMem = task.getCounters().get(MapReduceCounterData.CounterName.VIRTUAL_MEMORY_BYTES); taskPMems.add(taskPMem); taskPMin = Math.min(taskPMin, taskPMem); taskPMax = Math.max(taskPMax, taskPMem); diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/JobQueueLimitHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/JobQueueLimitHeuristic.java index 4ee221b69..35bc804c3 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/JobQueueLimitHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/JobQueueLimitHeuristic.java @@ -13,11 +13,12 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.mapreduce.heuristics; -import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; -import com.linkedin.drelephant.mapreduce.MapReduceTaskData; -import com.linkedin.drelephant.util.HeuristicConfigurationData; +import com.linkedin.drelephant.mapreduce.data.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.data.MapReduceTaskData; +import com.linkedin.drelephant.configurations.heuristic.HeuristicConfigurationData; import java.util.Properties; import java.util.concurrent.TimeUnit; diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperDataSkewHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperDataSkewHeuristic.java index c03c5b0a8..7297fd37a 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperDataSkewHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperDataSkewHeuristic.java @@ -13,19 +13,23 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.mapreduce.heuristics; -import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; -import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; -import com.linkedin.drelephant.mapreduce.MapReduceTaskData; -import com.linkedin.drelephant.util.HeuristicConfigurationData; +import com.linkedin.drelephant.mapreduce.data.MapReduceCounterData; +import com.linkedin.drelephant.mapreduce.data.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.data.MapReduceTaskData; +import com.linkedin.drelephant.configurations.heuristic.HeuristicConfigurationData; +/** + * This Heuristic analyses the skewness in the mapper input data + */ public class MapperDataSkewHeuristic extends GenericDataSkewHeuristic { public static final String HEURISTIC_NAME = "Mapper Data Skew"; public MapperDataSkewHeuristic(HeuristicConfigurationData heuristicConfData) { - super(MapReduceCounterHolder.CounterName.HDFS_BYTES_READ, HEURISTIC_NAME, heuristicConfData); + super(MapReduceCounterData.CounterName.HDFS_BYTES_READ, HEURISTIC_NAME, heuristicConfData); } @Override diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperGCHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperGCHeuristic.java index 9e8472973..13e826758 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperGCHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperGCHeuristic.java @@ -1,8 +1,24 @@ +/* + * Copyright 2015 LinkedIn Corp. + * + * 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.linkedin.drelephant.mapreduce.heuristics; -import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; -import com.linkedin.drelephant.mapreduce.MapReduceTaskData; -import com.linkedin.drelephant.util.HeuristicConfigurationData; +import com.linkedin.drelephant.mapreduce.data.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.data.MapReduceTaskData; +import com.linkedin.drelephant.configurations.heuristic.HeuristicConfigurationData; public class MapperGCHeuristic extends GenericGCHeuristic { diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperMemoryHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperMemoryHeuristic.java index 5a8018a91..1fbf53c45 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperMemoryHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperMemoryHeuristic.java @@ -13,11 +13,12 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.mapreduce.heuristics; -import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; -import com.linkedin.drelephant.mapreduce.MapReduceTaskData; -import com.linkedin.drelephant.util.HeuristicConfigurationData; +import com.linkedin.drelephant.mapreduce.data.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.data.MapReduceTaskData; +import com.linkedin.drelephant.configurations.heuristic.HeuristicConfigurationData; public class MapperMemoryHeuristic extends GenericMemoryHeuristic { diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristic.java index f0ed6125f..694c8c9df 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristic.java @@ -13,23 +13,23 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.mapreduce.heuristics; -import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; -import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; +import com.linkedin.drelephant.mapreduce.data.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.data.MapReduceCounterData; -import com.linkedin.drelephant.util.HeuristicConfigurationData; +import com.linkedin.drelephant.configurations.heuristic.HeuristicConfigurationData; import com.linkedin.drelephant.util.Utils; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; import java.util.List; import com.linkedin.drelephant.analysis.HDFSContext; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.mapreduce.MapReduceTaskData; +import com.linkedin.drelephant.mapreduce.data.MapReduceTaskData; import com.linkedin.drelephant.math.Statistics; import java.util.Map; @@ -104,7 +104,7 @@ public HeuristicResult apply(MapReduceApplicationData data) { for (MapReduceTaskData task : tasks) { if (task.timed()) { - long inputBytes = task.getCounters().get(MapReduceCounterHolder.CounterName.HDFS_BYTES_READ); + long inputBytes = task.getCounters().get(MapReduceCounterData.CounterName.HDFS_BYTES_READ); long runtimeMs = task.getTotalRunTimeMs(); inputByteSizes.add(inputBytes); runtimesMs.add(runtimeMs); diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristic.java index 369e161f3..1685e9ed0 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristic.java @@ -13,18 +13,18 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.mapreduce.heuristics; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; -import com.linkedin.drelephant.mapreduce.MapReduceTaskData; -import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; -import com.linkedin.drelephant.util.HeuristicConfigurationData; +import com.linkedin.drelephant.mapreduce.data.MapReduceCounterData; +import com.linkedin.drelephant.mapreduce.data.MapReduceTaskData; +import com.linkedin.drelephant.mapreduce.data.MapReduceApplicationData; +import com.linkedin.drelephant.configurations.heuristic.HeuristicConfigurationData; import com.linkedin.drelephant.util.Utils; import java.util.Arrays; -import java.util.HashMap; import java.util.Map; import org.apache.log4j.Logger; @@ -88,8 +88,8 @@ public HeuristicResult apply(MapReduceApplicationData data) { double ratioSpills = 0.0; for (MapReduceTaskData task : tasks) { - totalSpills += task.getCounters().get(MapReduceCounterHolder.CounterName.SPILLED_RECORDS); - totalOutputRecords += task.getCounters().get(MapReduceCounterHolder.CounterName.MAP_OUTPUT_RECORDS); + totalSpills += task.getCounters().get(MapReduceCounterData.CounterName.SPILLED_RECORDS); + totalOutputRecords += task.getCounters().get(MapReduceCounterData.CounterName.MAP_OUTPUT_RECORDS); } //If both totalSpills and totalOutputRecords are zero then set ratioSpills to zero. @@ -109,7 +109,8 @@ public HeuristicResult apply(MapReduceApplicationData data) { result.addDetail("Number of tasks", Integer.toString(tasks.length)); result.addDetail("Avg spilled records per task", tasks.length == 0 ? "0" : Long.toString(totalSpills/tasks.length)); - result.addDetail("Avg output records per task", tasks.length == 0 ? "0" : Long.toString(totalOutputRecords/tasks.length)); + result.addDetail( + "Avg output records per task", tasks.length == 0 ? "0" : Long.toString(totalOutputRecords/tasks.length)); result.addDetail("Ratio of spilled records to output records", Double.toString(ratioSpills)); return result; diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristic.java index 96587a240..2133c828e 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristic.java @@ -13,22 +13,21 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.mapreduce.heuristics; -import com.linkedin.drelephant.analysis.HDFSContext; -import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; -import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; -import com.linkedin.drelephant.util.HeuristicConfigurationData; +import com.linkedin.drelephant.mapreduce.data.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.data.MapReduceCounterData; +import com.linkedin.drelephant.configurations.heuristic.HeuristicConfigurationData; import com.linkedin.drelephant.util.Utils; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; import java.util.List; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.mapreduce.MapReduceTaskData; +import com.linkedin.drelephant.mapreduce.data.MapReduceTaskData; import com.linkedin.drelephant.math.Statistics; import java.util.Map; @@ -73,7 +72,8 @@ private void loadParameters() { longRuntimeLimits = confLongThreshold; } } - logger.info(HEURISTIC_NAME + " will use " + LONG_RUNTIME_SEVERITY + " with the following threshold settings: " + Arrays.toString(longRuntimeLimits)); + logger.info(HEURISTIC_NAME + " will use " + LONG_RUNTIME_SEVERITY + " with the following threshold settings: " + + Arrays.toString(longRuntimeLimits)); for (int i = 0; i < longRuntimeLimits.length; i++) { longRuntimeLimits[i] = longRuntimeLimits[i] * Statistics.MINUTE_IN_MS; } @@ -113,7 +113,7 @@ public HeuristicResult apply(MapReduceApplicationData data) { long taskMaxMs = 0; for (MapReduceTaskData task : tasks) { - inputBytes.add(task.getCounters().get(MapReduceCounterHolder.CounterName.HDFS_BYTES_READ)); + inputBytes.add(task.getCounters().get(MapReduceCounterData.CounterName.HDFS_BYTES_READ)); if (task.timed()) { long taskTime = task.getTotalRunTimeMs(); runtimesMs.add(taskTime); diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerDataSkewHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerDataSkewHeuristic.java index d051aafe5..69bbf5dde 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerDataSkewHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerDataSkewHeuristic.java @@ -13,19 +13,23 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.mapreduce.heuristics; -import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; -import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; -import com.linkedin.drelephant.mapreduce.MapReduceTaskData; -import com.linkedin.drelephant.util.HeuristicConfigurationData; +import com.linkedin.drelephant.mapreduce.data.MapReduceCounterData; +import com.linkedin.drelephant.mapreduce.data.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.data.MapReduceTaskData; +import com.linkedin.drelephant.configurations.heuristic.HeuristicConfigurationData; +/** + * This Heuristic analyses the skewness in the reducer input data + */ public class ReducerDataSkewHeuristic extends GenericDataSkewHeuristic { public static final String HEURISTIC_NAME = "Reducer Data Skew"; public ReducerDataSkewHeuristic(HeuristicConfigurationData heuristicConfData) { - super(MapReduceCounterHolder.CounterName.REDUCE_SHUFFLE_BYTES, HEURISTIC_NAME, heuristicConfData); + super(MapReduceCounterData.CounterName.REDUCE_SHUFFLE_BYTES, HEURISTIC_NAME, heuristicConfData); } @Override diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerGCHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerGCHeuristic.java index a0fa0c7a3..5ccd0639a 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerGCHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerGCHeuristic.java @@ -1,8 +1,24 @@ +/* + * Copyright 2015 LinkedIn Corp. + * + * 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.linkedin.drelephant.mapreduce.heuristics; -import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; -import com.linkedin.drelephant.mapreduce.MapReduceTaskData; -import com.linkedin.drelephant.util.HeuristicConfigurationData; +import com.linkedin.drelephant.mapreduce.data.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.data.MapReduceTaskData; +import com.linkedin.drelephant.configurations.heuristic.HeuristicConfigurationData; public class ReducerGCHeuristic extends GenericGCHeuristic { diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerMemoryHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerMemoryHeuristic.java index 8c18b44a5..35d268949 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerMemoryHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerMemoryHeuristic.java @@ -13,11 +13,12 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.mapreduce.heuristics; -import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; -import com.linkedin.drelephant.mapreduce.MapReduceTaskData; -import com.linkedin.drelephant.util.HeuristicConfigurationData; +import com.linkedin.drelephant.mapreduce.data.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.data.MapReduceTaskData; +import com.linkedin.drelephant.configurations.heuristic.HeuristicConfigurationData; public class ReducerMemoryHeuristic extends GenericMemoryHeuristic { diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristic.java index 2466f765e..903dffd05 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristic.java @@ -13,15 +13,15 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.mapreduce.heuristics; -import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; -import com.linkedin.drelephant.mapreduce.MapReduceTaskData; -import com.linkedin.drelephant.util.HeuristicConfigurationData; +import com.linkedin.drelephant.mapreduce.data.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.data.MapReduceTaskData; +import com.linkedin.drelephant.configurations.heuristic.HeuristicConfigurationData; import com.linkedin.drelephant.util.Utils; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; import java.util.List; import com.linkedin.drelephant.analysis.Heuristic; @@ -81,7 +81,8 @@ private void loadParameters() { numTasksLimits = confNumTasksLimits; } } - logger.info(HEURISTIC_NAME + " will use " + NUM_TASKS_SEVERITY + " with the following threshold settings: " + Arrays.toString(numTasksLimits)); + logger.info(HEURISTIC_NAME + " will use " + NUM_TASKS_SEVERITY + " with the following threshold settings: " + + Arrays.toString(numTasksLimits)); } diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristic.java index 6bcbc4ad0..0fb241656 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristic.java @@ -13,25 +13,28 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.mapreduce.heuristics; -import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; -import com.linkedin.drelephant.util.HeuristicConfigurationData; +import com.linkedin.drelephant.mapreduce.data.MapReduceApplicationData; +import com.linkedin.drelephant.configurations.heuristic.HeuristicConfigurationData; import com.linkedin.drelephant.util.Utils; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; import java.util.List; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.mapreduce.MapReduceTaskData; +import com.linkedin.drelephant.mapreduce.data.MapReduceTaskData; import com.linkedin.drelephant.math.Statistics; import java.util.Map; import org.apache.log4j.Logger; +/** + * Analyses the efficiency of Shuffle and Sort + */ public class ShuffleSortHeuristic implements Heuristic { private static final Logger logger = Logger.getLogger(ShuffleSortHeuristic.class); public static final String HEURISTIC_NAME = "Shuffle & Sort"; diff --git a/app/com/linkedin/drelephant/math/Statistics.java b/app/com/linkedin/drelephant/math/Statistics.java index 2c8344515..77720858b 100644 --- a/app/com/linkedin/drelephant/math/Statistics.java +++ b/app/com/linkedin/drelephant/math/Statistics.java @@ -13,9 +13,8 @@ * License for the specific language governing permissions and limitations under * the License. */ -package com.linkedin.drelephant.math; -import com.linkedin.drelephant.analysis.Severity; +package com.linkedin.drelephant.math; import java.lang.reflect.Array; import java.util.ArrayList; @@ -25,11 +24,13 @@ import java.util.Random; +/** + * This class includes all the statistical operations + */ public final class Statistics { public static final long SECOND_IN_MS = 1000L; public static final long MINUTE_IN_MS = 60L * SECOND_IN_MS; - public static final long HOUR_IN_MS = 60L * MINUTE_IN_MS; private Statistics() { } @@ -191,7 +192,9 @@ public static T[] createSample(Class clazz, T[] objects, int size) { return result; } - // Create a random sample within the original array + /** + * Create a random sample within the original array + */ public static void shuffleArraySample(T[] array, int sampleSize) { if (array.length <= sampleSize) { return; diff --git a/app/com/linkedin/drelephant/notifications/EmailThread.java b/app/com/linkedin/drelephant/notifications/EmailThread.java index 428a46fc8..88331645a 100644 --- a/app/com/linkedin/drelephant/notifications/EmailThread.java +++ b/app/com/linkedin/drelephant/notifications/EmailThread.java @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.notifications; import com.linkedin.drelephant.analysis.Severity; @@ -101,7 +102,7 @@ private void sendCriticalEmail(JobResult result) { } email.setSSLOnConnect(true); email.setFrom(_smtpFrom); - email.addTo(result.username + "@linkedin.com"); + email.addTo(result.username + "@xyz.com"); email.setSubject("Dr. Elephant - Hadoop Job Status Notification"); email.setHtmlMsg(html); email.setDebug(true); diff --git a/app/com/linkedin/drelephant/HadoopSecurity.java b/app/com/linkedin/drelephant/security/HadoopSecurity.java similarity index 95% rename from app/com/linkedin/drelephant/HadoopSecurity.java rename to app/com/linkedin/drelephant/security/HadoopSecurity.java index 744b82831..6391ce1ad 100644 --- a/app/com/linkedin/drelephant/HadoopSecurity.java +++ b/app/com/linkedin/drelephant/security/HadoopSecurity.java @@ -13,7 +13,8 @@ * License for the specific language governing permissions and limitations under * the License. */ -package com.linkedin.drelephant; + +package com.linkedin.drelephant.security; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.security.UserGroupInformation; @@ -25,6 +26,9 @@ import play.Play; +/** + * The class handles authentication when cluster is security enabled + */ public class HadoopSecurity { private static final Logger logger = Logger.getLogger(HadoopSecurity.class); diff --git a/app/com/linkedin/drelephant/spark/SparkApplicationData.java b/app/com/linkedin/drelephant/spark/data/SparkApplicationData.java similarity index 95% rename from app/com/linkedin/drelephant/spark/SparkApplicationData.java rename to app/com/linkedin/drelephant/spark/data/SparkApplicationData.java index 38d04a144..c557adeef 100644 --- a/app/com/linkedin/drelephant/spark/SparkApplicationData.java +++ b/app/com/linkedin/drelephant/spark/data/SparkApplicationData.java @@ -13,14 +13,14 @@ * License for the specific language governing permissions and limitations under * the License. */ -package com.linkedin.drelephant.spark; + +package com.linkedin.drelephant.spark.data; import com.linkedin.drelephant.analysis.HadoopApplicationData; /** * This holds a collection of all SparkApplicationData - * */ public interface SparkApplicationData extends HadoopApplicationData { diff --git a/app/com/linkedin/drelephant/spark/SparkEnvironmentData.java b/app/com/linkedin/drelephant/spark/data/SparkEnvironmentData.java similarity index 97% rename from app/com/linkedin/drelephant/spark/SparkEnvironmentData.java rename to app/com/linkedin/drelephant/spark/data/SparkEnvironmentData.java index 2c04b6b6a..fa5e4af45 100644 --- a/app/com/linkedin/drelephant/spark/SparkEnvironmentData.java +++ b/app/com/linkedin/drelephant/spark/data/SparkEnvironmentData.java @@ -13,7 +13,8 @@ * License for the specific language governing permissions and limitations under * the License. */ -package com.linkedin.drelephant.spark; + +package com.linkedin.drelephant.spark.data; import java.util.Properties; diff --git a/app/com/linkedin/drelephant/spark/SparkExecutorData.java b/app/com/linkedin/drelephant/spark/data/SparkExecutorData.java similarity index 95% rename from app/com/linkedin/drelephant/spark/SparkExecutorData.java rename to app/com/linkedin/drelephant/spark/data/SparkExecutorData.java index 6297b88d1..b4b83983a 100644 --- a/app/com/linkedin/drelephant/spark/SparkExecutorData.java +++ b/app/com/linkedin/drelephant/spark/data/SparkExecutorData.java @@ -13,7 +13,8 @@ * License for the specific language governing permissions and limitations under * the License. */ -package com.linkedin.drelephant.spark; + +package com.linkedin.drelephant.spark.data; import java.util.HashMap; import java.util.Map; @@ -21,8 +22,7 @@ /** - * This class contains Spark exeuctor information. - * + * This class contains Spark executor information. */ public class SparkExecutorData { public static final String EXECUTOR_DRIVER_NAME = "driver"; diff --git a/app/com/linkedin/drelephant/spark/SparkGeneralData.java b/app/com/linkedin/drelephant/spark/data/SparkGeneralData.java similarity index 97% rename from app/com/linkedin/drelephant/spark/SparkGeneralData.java rename to app/com/linkedin/drelephant/spark/data/SparkGeneralData.java index ee64186cb..9b65a1faa 100644 --- a/app/com/linkedin/drelephant/spark/SparkGeneralData.java +++ b/app/com/linkedin/drelephant/spark/data/SparkGeneralData.java @@ -13,14 +13,14 @@ * License for the specific language governing permissions and limitations under * the License. */ -package com.linkedin.drelephant.spark; + +package com.linkedin.drelephant.spark.data; import java.util.Set; /** * This class holds Spark application information - * */ public class SparkGeneralData { private Set _adminAcls; diff --git a/app/com/linkedin/drelephant/spark/SparkJobProgressData.java b/app/com/linkedin/drelephant/spark/data/SparkJobProgressData.java similarity index 94% rename from app/com/linkedin/drelephant/spark/SparkJobProgressData.java rename to app/com/linkedin/drelephant/spark/data/SparkJobProgressData.java index c444de954..182569e9c 100644 --- a/app/com/linkedin/drelephant/spark/SparkJobProgressData.java +++ b/app/com/linkedin/drelephant/spark/data/SparkJobProgressData.java @@ -13,7 +13,8 @@ * License for the specific language governing permissions and limitations under * the License. */ -package com.linkedin.drelephant.spark; + +package com.linkedin.drelephant.spark.data; import java.util.ArrayList; import java.util.Collection; @@ -28,7 +29,6 @@ /** * This class represents information contained in a job runtime process. - * */ public class SparkJobProgressData { private static final Logger logger = Logger.getLogger(SparkJobProgressData.class); @@ -251,10 +251,11 @@ public double getFailureRate() { @Override public String toString() { return String.format("{numActiveTasks:%s, numCompleteTasks:%s, completedIndices:%s, numFailedTasks:%s," - + " executorRunTime:%s, inputBytes:%s, outputBytes:%s, shuffleReadBytes:%s, shuffleWriteBytes:%s, memoryBytesSpilled:%s," - + " diskBytesSpilled:%s, name:%s, description:%s}", numActiveTasks, numCompleteTasks, - getListString(completedIndices), numFailedTasks, executorRunTime, inputBytes, outputBytes, shuffleReadBytes, - shuffleWriteBytes, memoryBytesSpilled, diskBytesSpilled, name, description); + + " executorRunTime:%s, inputBytes:%s, outputBytes:%s, shuffleReadBytes:%s, shuffleWriteBytes:%s," + + " memoryBytesSpilled:%s, diskBytesSpilled:%s, name:%s, description:%s}", + numActiveTasks, numCompleteTasks, getListString(completedIndices), numFailedTasks, executorRunTime, + inputBytes, outputBytes, shuffleReadBytes, shuffleWriteBytes, memoryBytesSpilled, diskBytesSpilled, name, + description); } } diff --git a/app/com/linkedin/drelephant/spark/SparkStorageData.java b/app/com/linkedin/drelephant/spark/data/SparkStorageData.java similarity index 96% rename from app/com/linkedin/drelephant/spark/SparkStorageData.java rename to app/com/linkedin/drelephant/spark/data/SparkStorageData.java index a8c9b9f45..5ace485e3 100644 --- a/app/com/linkedin/drelephant/spark/SparkStorageData.java +++ b/app/com/linkedin/drelephant/spark/data/SparkStorageData.java @@ -13,7 +13,8 @@ * License for the specific language governing permissions and limitations under * the License. */ -package com.linkedin.drelephant.spark; + +package com.linkedin.drelephant.spark.data; import java.util.List; import org.apache.spark.storage.RDDInfo; @@ -22,7 +23,6 @@ /** * This class holds information related to Spark storage (RDDs specifically) information. - * */ public class SparkStorageData { private List _rddInfoList; diff --git a/app/com/linkedin/drelephant/spark/heuristics/BestPropertiesConventionHeuristic.java b/app/com/linkedin/drelephant/spark/heuristics/BestPropertiesConventionHeuristic.java index 13a9d535e..e623f7fa2 100644 --- a/app/com/linkedin/drelephant/spark/heuristics/BestPropertiesConventionHeuristic.java +++ b/app/com/linkedin/drelephant/spark/heuristics/BestPropertiesConventionHeuristic.java @@ -13,18 +13,18 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.spark.heuristics; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.spark.SparkApplicationData; -import com.linkedin.drelephant.spark.SparkEnvironmentData; -import com.linkedin.drelephant.util.HeuristicConfigurationData; +import com.linkedin.drelephant.spark.data.SparkApplicationData; +import com.linkedin.drelephant.spark.data.SparkEnvironmentData; +import com.linkedin.drelephant.configurations.heuristic.HeuristicConfigurationData; import com.linkedin.drelephant.util.MemoryFormatUtils; import com.linkedin.drelephant.util.Utils; import java.util.Arrays; -import java.util.HashMap; import java.util.Map; import org.apache.log4j.Logger; @@ -32,8 +32,6 @@ /** * This heuristic rule check some of the most commonly set spark properties and make sure the user is following * a best convention of them. - * - * @author yizhou */ public class BestPropertiesConventionHeuristic implements Heuristic { private static final Logger logger = Logger.getLogger(BestPropertiesConventionHeuristic.class); diff --git a/app/com/linkedin/drelephant/spark/heuristics/EventLogLimitHeuristic.java b/app/com/linkedin/drelephant/spark/heuristics/EventLogLimitHeuristic.java index 7b4a4c6ee..7dc60705a 100644 --- a/app/com/linkedin/drelephant/spark/heuristics/EventLogLimitHeuristic.java +++ b/app/com/linkedin/drelephant/spark/heuristics/EventLogLimitHeuristic.java @@ -13,20 +13,19 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.spark.heuristics; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.spark.SparkApplicationData; -import com.linkedin.drelephant.util.HeuristicConfigurationData; +import com.linkedin.drelephant.spark.data.SparkApplicationData; +import com.linkedin.drelephant.configurations.heuristic.HeuristicConfigurationData; /** * This is a safeguard heuristic rule that makes sure if a log size passes the limit, we do not automatically * approve it. - * - * @author yizhou */ public class EventLogLimitHeuristic implements Heuristic { public static final String HEURISTIC_NAME = "Spark Event Log Limit"; diff --git a/app/com/linkedin/drelephant/spark/heuristics/ExecutorLoadHeuristic.java b/app/com/linkedin/drelephant/spark/heuristics/ExecutorLoadHeuristic.java index e290956ff..90d6280d3 100644 --- a/app/com/linkedin/drelephant/spark/heuristics/ExecutorLoadHeuristic.java +++ b/app/com/linkedin/drelephant/spark/heuristics/ExecutorLoadHeuristic.java @@ -13,24 +13,24 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.spark.heuristics; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; import com.linkedin.drelephant.math.Statistics; -import com.linkedin.drelephant.spark.SparkApplicationData; -import com.linkedin.drelephant.spark.SparkExecutorData; -import com.linkedin.drelephant.util.HeuristicConfigurationData; +import com.linkedin.drelephant.spark.data.SparkApplicationData; +import com.linkedin.drelephant.spark.data.SparkExecutorData; +import com.linkedin.drelephant.configurations.heuristic.HeuristicConfigurationData; import com.linkedin.drelephant.util.MemoryFormatUtils; import com.linkedin.drelephant.util.Utils; import java.util.Arrays; -import java.util.HashMap; import java.util.Map; import java.util.Set; import org.apache.log4j.Logger; -import static com.linkedin.drelephant.spark.SparkExecutorData.EXECUTOR_DRIVER_NAME; +import static com.linkedin.drelephant.spark.data.SparkExecutorData.EXECUTOR_DRIVER_NAME; /** diff --git a/app/com/linkedin/drelephant/spark/heuristics/JobRuntimeHeuristic.java b/app/com/linkedin/drelephant/spark/heuristics/JobRuntimeHeuristic.java index 99a59ff01..adfe6ea44 100644 --- a/app/com/linkedin/drelephant/spark/heuristics/JobRuntimeHeuristic.java +++ b/app/com/linkedin/drelephant/spark/heuristics/JobRuntimeHeuristic.java @@ -13,19 +13,19 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.spark.heuristics; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.spark.SparkApplicationData; -import com.linkedin.drelephant.spark.SparkJobProgressData; -import com.linkedin.drelephant.util.HeuristicConfigurationData; +import com.linkedin.drelephant.spark.data.SparkApplicationData; +import com.linkedin.drelephant.spark.data.SparkJobProgressData; +import com.linkedin.drelephant.configurations.heuristic.HeuristicConfigurationData; import com.linkedin.drelephant.util.Utils; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -35,8 +35,6 @@ /** * This heuristic rule observes the runtime characteristics of the spark application run. - * - * @author yizhou */ public class JobRuntimeHeuristic implements Heuristic { private static final Logger logger = Logger.getLogger(JobRuntimeHeuristic.class); diff --git a/app/com/linkedin/drelephant/spark/heuristics/MemoryLimitHeuristic.java b/app/com/linkedin/drelephant/spark/heuristics/MemoryLimitHeuristic.java index 3a3d32ad0..69ca471c2 100644 --- a/app/com/linkedin/drelephant/spark/heuristics/MemoryLimitHeuristic.java +++ b/app/com/linkedin/drelephant/spark/heuristics/MemoryLimitHeuristic.java @@ -13,30 +13,28 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.spark.heuristics; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.spark.SparkApplicationData; -import com.linkedin.drelephant.spark.SparkEnvironmentData; -import com.linkedin.drelephant.spark.SparkExecutorData; -import com.linkedin.drelephant.util.HeuristicConfigurationData; +import com.linkedin.drelephant.spark.data.SparkApplicationData; +import com.linkedin.drelephant.spark.data.SparkEnvironmentData; +import com.linkedin.drelephant.spark.data.SparkExecutorData; +import com.linkedin.drelephant.configurations.heuristic.HeuristicConfigurationData; import com.linkedin.drelephant.util.MemoryFormatUtils; import com.linkedin.drelephant.util.Utils; import java.util.Arrays; -import java.util.HashMap; import java.util.Map; import org.apache.log4j.Logger; import org.apache.spark.SparkConf; -import static com.linkedin.drelephant.spark.SparkExecutorData.EXECUTOR_DRIVER_NAME; +import static com.linkedin.drelephant.spark.data.SparkExecutorData.EXECUTOR_DRIVER_NAME; /** * This heuristic checks for memory consumption. - * - * @author yizhou */ public class MemoryLimitHeuristic implements Heuristic { public static final String HEURISTIC_NAME = "Spark Memory Limit"; @@ -192,8 +190,8 @@ public String getHeuristicName() { } public Severity getTotalMemorySeverity(long memory) { - return Severity.getSeverityAscending( - memory, totalMemLimits[0], totalMemLimits[1], totalMemLimits[2], totalMemLimits[3]); + return Severity.getSeverityAscending(memory, totalMemLimits[0], totalMemLimits[1], totalMemLimits[2], + totalMemLimits[3]); } private Severity getMemoryUtilizationSeverity(long peakMemory, long totalStorageMemory) { diff --git a/app/com/linkedin/drelephant/spark/heuristics/StageRuntimeHeuristic.java b/app/com/linkedin/drelephant/spark/heuristics/StageRuntimeHeuristic.java index b0af2feec..59dc98099 100644 --- a/app/com/linkedin/drelephant/spark/heuristics/StageRuntimeHeuristic.java +++ b/app/com/linkedin/drelephant/spark/heuristics/StageRuntimeHeuristic.java @@ -13,21 +13,20 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.spark.heuristics; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; import com.linkedin.drelephant.math.Statistics; -import com.linkedin.drelephant.spark.SparkApplicationData; -import com.linkedin.drelephant.spark.SparkJobProgressData; -import com.linkedin.drelephant.util.HeuristicConfigurationData; -import com.linkedin.drelephant.util.MemoryFormatUtils; +import com.linkedin.drelephant.spark.data.SparkApplicationData; +import com.linkedin.drelephant.spark.data.SparkJobProgressData; +import com.linkedin.drelephant.configurations.heuristic.HeuristicConfigurationData; import com.linkedin.drelephant.util.Utils; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; diff --git a/app/com/linkedin/drelephant/util/InfoExtractor.java b/app/com/linkedin/drelephant/util/InfoExtractor.java index 083ae7103..782bf28d3 100644 --- a/app/com/linkedin/drelephant/util/InfoExtractor.java +++ b/app/com/linkedin/drelephant/util/InfoExtractor.java @@ -13,10 +13,11 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.util; import com.linkedin.drelephant.analysis.HadoopApplicationData; -import com.linkedin.drelephant.spark.SparkApplicationData; +import com.linkedin.drelephant.spark.data.SparkApplicationData; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -27,7 +28,7 @@ import model.JobResult; -import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.data.MapReduceApplicationData; /** diff --git a/app/com/linkedin/drelephant/util/MemoryFormatUtils.java b/app/com/linkedin/drelephant/util/MemoryFormatUtils.java index 0d9e8f80c..601d1f2ef 100644 --- a/app/com/linkedin/drelephant/util/MemoryFormatUtils.java +++ b/app/com/linkedin/drelephant/util/MemoryFormatUtils.java @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.util; import java.text.DecimalFormat; diff --git a/app/com/linkedin/drelephant/util/Utils.java b/app/com/linkedin/drelephant/util/Utils.java index 56d2c7d92..945cb152a 100644 --- a/app/com/linkedin/drelephant/util/Utils.java +++ b/app/com/linkedin/drelephant/util/Utils.java @@ -13,23 +13,21 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.util; import com.linkedin.drelephant.DaliMetricsAPI; import com.linkedin.drelephant.ElephantContext; -import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; -import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.data.MapReduceCounterData; +import com.linkedin.drelephant.mapreduce.data.MapReduceApplicationData; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; -import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; @@ -46,6 +44,9 @@ import play.Play; +/** + * This class contains all the utility methods. + */ public final class Utils { private static final Logger logger = Logger.getLogger(Utils.class); // Matching x.x.x or x.x.x-li1 (x are numbers) @@ -56,9 +57,13 @@ private Utils() { } /** - * Given a mapreduce job's application id, get its corresponding job id. + * Given a mapreduce job's application id, get its corresponding job id + * + * Note: before adding Spark analysers, all JobResult were using job ids as the primary key. But Spark and many + * other non-mapreduce applications do not have a job id. To maintain backwards compatibility, we replace + * 'application' with 'job' to form a pseudo job id. * - * @param appId + * @param appId The application id of the job * @return the corresponding job id */ public static String getJobIdFromApplicationId(String appId) { @@ -90,7 +95,7 @@ public static void publishMetrics(MapReduceApplicationData jobData) { jobData.getFinishTime()); DaliMetricsAPI.HadoopCounters metricsEvent = new DaliMetricsAPI.HadoopCounters(eventContext, jobProperties); - MapReduceCounterHolder counterHolder = jobData.getCounters(); + MapReduceCounterData counterHolder = jobData.getCounters(); logger.info("HadoopCounterHolder: {" + counterHolder + "}"); Set groupNames = counterHolder.getGroupNames(); logger.info("group names: [" + StringUtils.join(groupNames, ",") + "]"); diff --git a/app/com/linkedin/drelephant/util/YarnURLUtils.java b/app/com/linkedin/drelephant/util/YarnURLUtils.java index 2e59dc4d2..3b43973e1 100644 --- a/app/com/linkedin/drelephant/util/YarnURLUtils.java +++ b/app/com/linkedin/drelephant/util/YarnURLUtils.java @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.util; import java.net.MalformedURLException; @@ -21,7 +22,6 @@ /** * This provide URLs for YARN APIs. - * */ public class YarnURLUtils { private static final String MAPREDUCE_JOBS_PATH = "/ws/v1/history/mapreduce/jobs"; diff --git a/app/controllers/Application.java b/app/controllers/Application.java index 8f0a8b385..78e1d7060 100644 --- a/app/controllers/Application.java +++ b/app/controllers/Application.java @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ + package controllers; import com.avaje.ebean.ExpressionList; @@ -22,7 +23,7 @@ import com.google.common.collect.Sets; import com.linkedin.drelephant.ElephantContext; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.util.HeuristicConfigurationData; +import com.linkedin.drelephant.configurations.heuristic.HeuristicConfigurationData; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.text.ParseException; @@ -92,7 +93,7 @@ public class Application extends Controller { private static final String COMPARE_FLOW_URL2 = "flow-exec-url2"; private static final String PAGE = "page"; - // Time range specifer. [TIME_RANGE_BEGIN, TIME_RANGE_END] + // Time range specifier. [TIME_RANGE_BEGIN, TIME_RANGE_END] private static final boolean TIME_RANGE_BEGIN = false; private static final boolean TIME_RANGE_END = true; @@ -112,13 +113,40 @@ public class Application extends Controller { } } + /** + * Controls the Home page of Dr. Elephant + */ + public static Result dashboard() { + long now = System.currentTimeMillis(); + if (now - _lastFetch > FETCH_DELAY) { + _numJobsAnalyzed = JobResult.find.where().gt(JobResult.TABLE.ANALYSIS_TIME, now - DAY).findRowCount(); + _numJobsCritical = + JobResult.find.where().gt(JobResult.TABLE.ANALYSIS_TIME, now - DAY) + .eq(JobResult.TABLE.SEVERITY, Severity.CRITICAL.getValue()).findRowCount(); + _numJobsSevere = + JobResult.find.where().gt(JobResult.TABLE.ANALYSIS_TIME, now - DAY) + .eq(JobResult.TABLE.SEVERITY, Severity.SEVERE.getValue()).findRowCount(); + _lastFetch = now; + } + List results = + JobResult.find.where().gt(JobResult.TABLE.ANALYSIS_TIME, now - DAY).order().desc(JobResult.TABLE.ANALYSIS_TIME) + .setMaxRows(50).fetch("heuristicResults").findList(); + + return ok(homePage.render(_numJobsAnalyzed, _numJobsSevere, _numJobsCritical, + searchResults.render("Latest analysis", results))); + } + + /** + * Controls the Search Feature + */ public static Result search() { - // Search and display job information when job id or flow execution url is provided. DynamicForm form = Form.form().bindFromRequest(request()); String jobId = form.get(JOB_ID); jobId = jobId != null ? jobId.trim() : ""; String flowUrl = form.get(FLOW_URL); flowUrl = (flowUrl != null) ? flowUrl.trim() : null; + + // Search and display job information when job id or flow execution url is provided. if (!jobId.isEmpty()) { JobResult result = JobResult.find.byId(jobId); if (result != null) { @@ -147,6 +175,8 @@ public static Result search() { } int currentPage = paginationStats.getCurrentPage(); int paginationBarStartIndex = paginationStats.getPaginationBarStartIndex(); + + // Filter jobs by search parameters ExpressionList query = generateQuery(); List results = query.order().desc(JobResult.TABLE.ANALYSIS_TIME).setFirstRow((paginationBarStartIndex - 1) * pageLength) @@ -164,6 +194,113 @@ public static Result search() { } } + /** + * Parses the request for the queryString + * + * @return URL Encoded String of Parameter Value Pair + */ + private static String getQueryString() { + List fields = new LinkedList(); + final Set> entries = request().queryString().entrySet(); + for (Map.Entry entry : entries) { + final String key = entry.getKey(); + final String value = entry.getValue()[0]; + if (!key.equals(PAGE)) { + fields.add(new BasicNameValuePair(key, value)); + } + } + if (fields.isEmpty()) { + return null; + } else { + return URLEncodedUtils.format(fields, "utf-8"); + } + } + + /** + * Build SQL expression + * + * @return An sql expression on Job Result + */ + private static ExpressionList generateQuery() { + DynamicForm form = Form.form().bindFromRequest(request()); + String username = form.get(USER); + username = username != null ? username.trim().toLowerCase() : null; + String severity = form.get(SEVERITY); + String jobType = form.get(JOB_TYPE); + String analysis = form.get(ANALYSIS); + String finishedTimeBegin = form.get(FINISHED_TIME_BEGIN); + String finishedTimeEnd = form.get(FINISHED_TIME_END); + String startedTimeBegin = form.get(STARTED_TIME_BEGIN); + String startedTimeEnd = form.get(STARTED_TIME_END); + + ExpressionList query = JobResult.find.where(); + + RawSql rawsql = null; + // Hint usage of username index to mysql whenever our query contains a predicate on username + if (isSet(severity) && isSet(analysis)) { + if (isSet(username)) { + rawsql = RawSqlBuilder.parse(QueryHandler.getSqlJoinQueryWithUsernameIndex().toString()).create(); + } else { + rawsql = RawSqlBuilder.parse(QueryHandler.getSqlJoinQuery().toString()).create(); + } + } else { + if (isSet(username)) { + rawsql = RawSqlBuilder.parse(QueryHandler.getJobResultQueryWithUsernameIndex().toString()).create(); + } + } + query = query.query().setRawSql(rawsql).where(); + + // Build predicates + if (isSet(username)) { + query = query.like(JobResult.TABLE.USERNAME, username); + } + if (isSet(jobType)) { + query = query.eq(JobResult.TABLE.JOB_TYPE, jobType); + } + if (isSet(severity)) { + if (isSet(analysis)) { + query = + query.eq(JobHeuristicResult.TABLE.TABLE_NAME + "." + JobHeuristicResult.TABLE.ANALYSIS_NAME, analysis).ge( + JobHeuristicResult.TABLE.TABLE_NAME + "." + JobHeuristicResult.TABLE.SEVERITY, severity); + } else { + query = query.ge(JobResult.TABLE.SEVERITY, severity); + } + } + + // Time Predicates. Both the startedTimeBegin and startedTimeEnd are inclusive in the filter + if (isSet(startedTimeBegin)) { + long time = getTimeRange(startedTimeBegin, TIME_RANGE_BEGIN); + if (time > 0) { + query = query.ge(JobResult.TABLE.ANALYSIS_TIME, time); + } + } + if (isSet(startedTimeEnd)) { + // Here we want to include the end time in the filter + long time = getTimeRange(startedTimeEnd, TIME_RANGE_END); + if (time > 0) { + query = query.le(JobResult.TABLE.ANALYSIS_TIME, time); + } + } + if (isSet(finishedTimeBegin)) { + long time = getTimeRange(finishedTimeBegin, TIME_RANGE_BEGIN); + if (time > 0) { + query = query.ge(JobResult.TABLE.ANALYSIS_TIME, time); + } + } + if (isSet(finishedTimeEnd)) { + // Here we want to include the end time in the filter + long time = getTimeRange(finishedTimeEnd, TIME_RANGE_END); + if (time > 0) { + query = query.le(JobResult.TABLE.ANALYSIS_TIME, time); + } + } + + return query; + } + + /** + Controls the Compare Feature + */ public static Result compare() { DynamicForm form = Form.form().bindFromRequest(request()); String flowExecUrl1 = form.get(COMPARE_FLOW_URL1); @@ -174,10 +311,11 @@ public static Result compare() { } /** - * Compares 2 flow executions at job level. + * Helper Method for the compare controller. + * This Compares 2 flow executions at job level. * * @param flowExecUrl1 The flow execution url to be compared - * @param flowExecUrl2 The other flow exceution url to be compared against + * @param flowExecUrl2 The other flow execution url to be compared against * @return A map of Job Urls to the list of jobs corresponding to the 2 flow execution urls */ private static Map>> compareFlows(String flowExecUrl1, String flowExecUrl2) { @@ -206,7 +344,7 @@ private static Map>> compareFlows(String flo } /** - * Display flow execution history(MAX_HISTORY_LIMIT) + * Controls the flow history. Displays max MAX_HISTORY_LIMIT executions */ public static Result flowHistory() { DynamicForm form = Form.form().bindFromRequest(request()); @@ -222,8 +360,8 @@ public static Result flowHistory() { if (results.size() == 0) { return notFound("Unable to find record on flow url: " + flowUrl); } - Map> flowExecUrlToJobsMap = limitResults(groupJobs(results, GroupBy.FLOW_EXECUTION_URL), - results.size(), MAX_HISTORY_LIMIT); + Map> flowExecUrlToJobsMap = limitHistoryResults( + groupJobs(results, GroupBy.FLOW_EXECUTION_URL), results.size(), MAX_HISTORY_LIMIT); // Compute flow execution data List filteredResults = new ArrayList(); // All mr jobs starting from latest execution @@ -246,11 +384,12 @@ public static Result flowHistory() { // from latest execution first followed by any other extra job def url that may appear in previous executions. List jobDefUrlList = new ArrayList(groupJobs(filteredResults, GroupBy.JOB_DEFINITION_URL).keySet()); - return ok(flowHistoryPage.render(flowHistoryResults.render(flowUrl, executionMap, jobDefUrlList, flowExecTimeList))); + return ok(flowHistoryPage.render(flowHistoryResults.render(flowUrl, executionMap, jobDefUrlList, + flowExecTimeList))); } /** - * Display job execution history(HISTORY_LIMIT) + * Controls Job History. Displays at max MAX_HISTORY_LIMIT executions */ public static Result jobHistory() { DynamicForm form = Form.form().bindFromRequest(request()); @@ -266,8 +405,8 @@ public static Result jobHistory() { if (results.size() == 0) { return notFound("Unable to find record on job url: " + jobUrl); } - Map> flowExecUrlToJobsMap = limitResults(groupJobs(results, GroupBy.FLOW_EXECUTION_URL), - results.size(), MAX_HISTORY_LIMIT); + Map> flowExecUrlToJobsMap = + limitHistoryResults(groupJobs(results, GroupBy.FLOW_EXECUTION_URL), results.size(), MAX_HISTORY_LIMIT); // Compute job execution data List flowExecTimeList = new ArrayList(); @@ -294,103 +433,105 @@ public static Result jobHistory() { return ok(jobHistoryPage.render(jobHistoryResults.render(jobUrl, executionMap, maxStages, flowExecTimeList))); } - private static String getQueryString() { - List fields = new LinkedList(); - final Set> entries = request().queryString().entrySet(); - for (Map.Entry entry : entries) { - final String key = entry.getKey(); - final String value = entry.getValue()[0]; - if (!key.equals(PAGE)) { - fields.add(new BasicNameValuePair(key, value)); - } - } - if (fields.isEmpty()) { - return null; - } else { - return URLEncodedUtils.format(fields, "utf-8"); - } - } - /** - * Build SQL expression + * Applies a limit on the number of executions to be displayed after trying to maximize the correctness. * - * @return An sql expression on Job Result + * Correctness: + * When the number of jobs are less than the JOB_HISTORY_LIMIT, we can show all the executions correctly. However, + * when the number of jobs are greater than the JOB_HISTORY_LIMIT, we cannot simply prune the jobs at that point and + * show the history because we may skip some jobs which belong to the last flow execution. For the flow executions + * we display, we want to ensure we show all the jobs belonging to that flow. + * + * So, when the number of executions are less than 10, we skip the last execution and when the number of executions + * are greater than 10, we skip the last 3 executions just to maximise the correctness. + * + * @param map The results map to be pruned. + * @param size Total number of jobs in the map + * @param execLimit The upper limit on the number of executions to be displayed. + * @return A map after applying the limit. */ - private static ExpressionList generateQuery() { - DynamicForm form = Form.form().bindFromRequest(request()); - String username = form.get(USER); - username = username != null ? username.trim().toLowerCase() : null; - String severity = form.get(SEVERITY); - String jobType = form.get(JOB_TYPE); - String analysis = form.get(ANALYSIS); - String finishedTimeBegin = form.get(FINISHED_TIME_BEGIN); - String finishedTimeEnd = form.get(FINISHED_TIME_END); - String startedTimeBegin = form.get(STARTED_TIME_BEGIN); - String startedTimeEnd = form.get(STARTED_TIME_END); - - ExpressionList query = JobResult.find.where(); + private static Map> limitHistoryResults(Map> map, int size, + int execLimit) { + Map> resultMap = new LinkedHashMap>(); - RawSql rawsql = null; - // Hint usage of username index to mysql whenever our query contains a predicate on username - if (isSet(severity) && isSet(analysis)) { - if (isSet(username)) { - rawsql = RawSqlBuilder.parse(QueryHandler.getSqlJoinQueryWithUsernameIndex().toString()).create(); - } else { - rawsql = RawSqlBuilder.parse(QueryHandler.getSqlJoinQuery().toString()).create(); - } + int limit; + if (size < JOB_HISTORY_LIMIT) { + // No pruning needed. 100% correct. + limit = execLimit; } else { - if (isSet(username)) { - rawsql = RawSqlBuilder.parse(QueryHandler.getJobResultQueryWithUsernameIndex().toString()).create(); - } - } - query = query.query().setRawSql(rawsql).where(); - - // Build predicates - if (isSet(username)) { - query = query.like(JobResult.TABLE.USERNAME, username); - } - if (isSet(jobType)) { - query = query.eq(JobResult.TABLE.JOB_TYPE, jobType); - } - if (isSet(severity)) { - if (isSet(analysis)) { - query = - query.eq(JobHeuristicResult.TABLE.TABLE_NAME + "." + JobHeuristicResult.TABLE.ANALYSIS_NAME, analysis).ge( - JobHeuristicResult.TABLE.TABLE_NAME + "." + JobHeuristicResult.TABLE.SEVERITY, severity); + Set keySet = map.keySet(); + if (keySet.size() > 10) { + // Prune last 3 executions + limit = keySet.size() > (execLimit + 3) ? execLimit : keySet.size() - 3; } else { - query = query.ge(JobResult.TABLE.SEVERITY, severity); + // Prune the last execution + limit = keySet.size() - 1; } } - // Time Predicates. Both the startedTimeBegin and startedTimeEnd are inclusive in the filter - if (isSet(startedTimeBegin)) { - long time = getTimeRange(startedTimeBegin, TIME_RANGE_BEGIN); - if (time > 0) { - query = query.ge(JobResult.TABLE.ANALYSIS_TIME, time); + // Filtered results + int i = 1; + for (Map.Entry> entry : map.entrySet()) { + if (i > limit) { + break; } + resultMap.put(entry.getKey(), entry.getValue()); + i++; } - if (isSet(startedTimeEnd)) { - // Here we want to include the end time in the filter - long time = getTimeRange(startedTimeEnd, TIME_RANGE_END); - if (time > 0) { - query = query.le(JobResult.TABLE.ANALYSIS_TIME, time); + + return resultMap; + } + + /** + * Controls the Help Page + */ + public static Result help() { + DynamicForm form = Form.form().bindFromRequest(request()); + String topic = form.get("topic"); + Html page = null; + String title = "Help"; + if (topic != null && !topic.isEmpty()) { + page = _helpPages.get(topic); + if (page != null) { + title = topic; } } - if (isSet(finishedTimeBegin)) { - long time = getTimeRange(finishedTimeBegin, TIME_RANGE_BEGIN); - if (time > 0) { - query = query.ge(JobResult.TABLE.ANALYSIS_TIME, time); + return ok(helpPage.render(title, page)); + } + + /** + * Create a map to cache pages. + */ + private static void fillHelpPages() { + logger.info("Loading help pages for pluggable heuristics"); + List heuristicsConfList = ElephantContext.instance().getHeuristicsConfigurationData(); + for (HeuristicConfigurationData heuristicConf : heuristicsConfList) { + Class viewClass = null; + String heuristicName = null; + try { + String viewName = heuristicConf.getViewName(); + logger.info("Loading help page " + viewName); + viewClass = Play.current().classloader().loadClass(viewName); + heuristicName = heuristicConf.getHeuristicName(); + } catch (ClassNotFoundException e) { + throw new RuntimeException("Could not find class " + heuristicConf.getViewName(), e); } - } - if (isSet(finishedTimeEnd)) { - // Here we want to include the end time in the filter - long time = getTimeRange(finishedTimeEnd, TIME_RANGE_END); - if (time > 0) { - query = query.le(JobResult.TABLE.ANALYSIS_TIME, time); + + try { + Method render = viewClass.getDeclaredMethod("render"); + Html page = (Html) render.invoke(null); + _helpPages.put(heuristicName, page); + } catch (NoSuchMethodException e) { + throw new RuntimeException(viewClass.getName() + " is not a valid view.", e); + } catch (IllegalAccessException e) { + throw new RuntimeException(viewClass.getName() + " is not a valid view.", e); + } catch (InvocationTargetException e) { + throw new RuntimeException(viewClass.getName() + " is not a valid view.", e); + } catch (Exception e) { + // More descriptive on other Runtime Exceptions such as ClassCastException IllegalArgumentException + throw new RuntimeException(viewClass.getName() + " is not a valid view.", e); } } - - return query; } /** @@ -404,7 +545,7 @@ private static ExpressionList generateQuery() { * value such that we include the entire day in the filter * * @param time The String to be parsed - * @param rangeEnd Range Indicator. true if it is the range End(to), false indicates the range Beginning(from) + * @param timeEnd Range Indicator. true if it is the range End(to), false indicates the range Beginning(from) * @return The epoch value */ private static long getTimeRange(String time, boolean timeEnd) { @@ -452,132 +593,19 @@ private static boolean isUnixTimeStamp(String unixTime) { return valid; } - private static boolean isSet(String property) { - return property != null && !property.isEmpty(); - } - - public static Result dashboard() { - long now = System.currentTimeMillis(); - if (now - _lastFetch > FETCH_DELAY) { - _numJobsAnalyzed = JobResult.find.where().gt(JobResult.TABLE.ANALYSIS_TIME, now - DAY).findRowCount(); - _numJobsCritical = - JobResult.find.where().gt(JobResult.TABLE.ANALYSIS_TIME, now - DAY) - .eq(JobResult.TABLE.SEVERITY, Severity.CRITICAL.getValue()).findRowCount(); - _numJobsSevere = - JobResult.find.where().gt(JobResult.TABLE.ANALYSIS_TIME, now - DAY) - .eq(JobResult.TABLE.SEVERITY, Severity.SEVERE.getValue()).findRowCount(); - _lastFetch = now; - } - List results = - JobResult.find.where().gt(JobResult.TABLE.ANALYSIS_TIME, now - DAY).order().desc(JobResult.TABLE.ANALYSIS_TIME) - .setMaxRows(50).fetch("heuristicResults").findList(); - - return ok(homePage.render(_numJobsAnalyzed, _numJobsSevere, _numJobsCritical, - searchResults.render("Latest analysis", results))); - } - - public static Result help() { - DynamicForm form = Form.form().bindFromRequest(request()); - String topic = form.get("topic"); - Html page = null; - String title = "Help"; - if (topic != null && !topic.isEmpty()) { - page = _helpPages.get(topic); - if (page != null) { - title = topic; - } - } - return ok(helpPage.render(title, page)); - } - - //create a map to cache pages. - private static void fillHelpPages() { - logger.info("Loading help pages for pluggable heuristics"); - List heuristicsConfList = ElephantContext.instance().getHeuristicsConfigurationData(); - for (HeuristicConfigurationData heuristicConf : heuristicsConfList) { - Class viewClass = null; - String heuristicName = null; - try { - String viewName = heuristicConf.getViewName(); - logger.info("Loading help page " + viewName); - viewClass = Play.current().classloader().loadClass(viewName); - heuristicName = heuristicConf.getHeuristicName(); - } catch (ClassNotFoundException e) { - throw new RuntimeException("Could not find class " + heuristicConf.getViewName(), e); - } - - try { - Method render = viewClass.getDeclaredMethod("render"); - Html page = (Html) render.invoke(null); - _helpPages.put(heuristicName, page); - } catch (NoSuchMethodException e) { - throw new RuntimeException(viewClass.getName() + " is not a valid view.", e); - } catch (IllegalAccessException e) { - throw new RuntimeException(viewClass.getName() + " is not a valid view.", e); - } catch (InvocationTargetException e) { - throw new RuntimeException(viewClass.getName() + " is not a valid view.", e); - } catch (Exception e) { - // More descriptive on other Runtime Exceptions such as ClassCastException IllegalArgumentException - throw new RuntimeException(viewClass.getName() + " is not a valid view.", e); - } - } - } - /** - * Applies a limit on the number of executions to be displayed after trying to maximize the correctness. + * Checks if the property is set * - * @param map The results map to be pruned. - * @param size Total number of jobs in the map - * @param execLimit The upper limit on the number of executions to be displayed. - * @return A map after applying the limit. + * @param property The property to tbe checked. + * @return true if set, false otherwise */ - private static Map> limitResults(Map> map, int size, int execLimit) { - Map> resultMap = new LinkedHashMap>(); - - int limit; - if (size < JOB_HISTORY_LIMIT) { - // No pruning needed. 100% correct. - limit = execLimit; - } else { - Set keySet = map.keySet(); - if (keySet.size() > 10) { - // Prune last 3 executions - limit = keySet.size() > (execLimit + 3) ? execLimit : keySet.size() - 3; - } else { - // Prune the last execution - limit = keySet.size() - 1; - } - } - - // Filtered results - int i = 1; - for (Map.Entry> entry : map.entrySet()) { - if (i > limit) { - break; - } - resultMap.put(entry.getKey(), entry.getValue()); - i++; - } - - return resultMap; + private static boolean isSet(String property) { + return property != null && !property.isEmpty(); } /** - * A listing of all other jobs that were found from the same flow execution. + * Rest API for searching a particular job information */ - public static Result flowRelated() { - - String execUrl = request().queryString().get(FLOW_EXEC_URL)[0]; - List results = JobResult.find.where().eq(JobResult.TABLE.FLOW_EXEC_URL, execUrl).findList(); - - if (results.size() == 0) { - return notFound("Unable to find record on flow exec: " + execUrl); - } - - Map> map = groupJobs(results, GroupBy.JOB_EXECUTION_URL); - return ok(searchPage.render(null, flowDetails.render(execUrl, map))); - } - public static Result restJobResult(String jobId) { if (jobId == null || jobId.isEmpty()) { @@ -593,6 +621,9 @@ public static Result restJobResult(String jobId) { return ok(Json.toJson(result)); } + /** + * Rest API for searching all jobs triggered by a particular Scheduler Job + */ public static Result restJobExecResult(String jobExecUrl) { if (jobExecUrl == null || jobExecUrl.isEmpty()) { @@ -608,6 +639,9 @@ public static Result restJobExecResult(String jobExecUrl) { return ok(Json.toJson(result)); } + /** + * Rest API for searching all jobs under a particular flow execution + */ public static Result restFlowExecResult(String flowExecUrl) { if (flowExecUrl == null || flowExecUrl.isEmpty()) { @@ -667,6 +701,11 @@ private static Map> groupJobs(List results, G return resultMap; } + /** + * The Rest API for Search Feature + * + * http://localhost:8080/rest/search?username=abc&job-type=HadoopJava + */ public static Result restSearch() { DynamicForm form = Form.form().bindFromRequest(request()); String jobId = form.get(JOB_ID); @@ -700,6 +739,9 @@ public static Result restSearch() { return ok(Json.toJson(results)); } + /** + * The Rest API for Compare Feature + */ public static Result restCompare() { DynamicForm form = Form.form().bindFromRequest(request()); String flowExecUrl1 = form.get(COMPARE_FLOW_URL1); @@ -712,36 +754,40 @@ public static Result restCompare() { /** * The data for plotting the flow history graph * - * [ - * { - * "flowtime": , - * "score": 1000, - * "jobscores": [ - * { - * "jobdefurl:" "url", - * "jobscore": 500 - * }, - * { - * "jobdefurl:" "url", - * "jobscore": 500 - * } - * ] - * }, - * { - * "flowtime": , - * "score": 700, - * "jobscores": [ - * { - * "jobdefurl:" "url", - * "jobscore": 0 - * }, - * { - * "jobdefurl:" "url", - * "jobscore": 700 - * } - * ] - * } - * ] + *
+   * {@code
+   *   [
+   *     {
+   *       "flowtime": ,
+   *       "score": 1000,
+   *       "jobscores": [
+   *         {
+   *           "jobdefurl:" "url",
+   *           "jobscore": 500
+   *         },
+   *         {
+   *           "jobdefurl:" "url",
+   *           "jobscore": 500
+   *         }
+   *       ]
+   *     },
+   *     {
+   *       "flowtime": ,
+   *       "score": 700,
+   *       "jobscores": [
+   *         {
+   *           "jobdefurl:" "url",
+   *           "jobscore": 0
+   *         },
+   *         {
+   *           "jobdefurl:" "url",
+   *           "jobscore": 700
+   *         }
+   *       ]
+   *     }
+   *   ]
+   * }
+   * 
*/ public static Result restFlowGraphData(String flowUrl) { JsonArray datasets = new JsonArray(); @@ -755,8 +801,8 @@ public static Result restFlowGraphData(String flowUrl) { if (results.size() == 0) { logger.info("No results for Job url"); } - Map> flowExecUrlToJobsMap = limitResults(groupJobs(results, GroupBy.FLOW_EXECUTION_URL), - results.size(), MAX_HISTORY_LIMIT); + Map> flowExecUrlToJobsMap = limitHistoryResults( + groupJobs(results, GroupBy.FLOW_EXECUTION_URL), results.size(), MAX_HISTORY_LIMIT); // Compute the graph data starting from the earliest available execution to latest List keyList = new ArrayList(flowExecUrlToJobsMap.keySet()); @@ -797,38 +843,44 @@ public static Result restFlowGraphData(String flowUrl) { } /** - * The data for plotting the job history graph + * The data for plotting the job history graph. While plotting the job history + * graph an ajax call is made to this to fetch the graph data. * - * [ - * { - * "flowtime": , - * "score": 1000, - * "stagescores": [ - * { - * "stageid:" "id", - * "stagescore": 500 - * }, - * { - * "stageid:" "id", - * "stagescore": 500 - * } - * ] - * }, - * { - * "flowtime": , - * "score": 700, - * "stagescores": [ - * { - * "stageid:" "id", - * "stagescore": 0 - * }, - * { - * "stageid:" "id", - * "stagescore": 700 - * } - * ] - * } - * ] + * Data Returned: + *
+   * {@code
+   *   [
+   *     {
+   *       "flowtime": ,
+   *       "score": 1000,
+   *       "stagescores": [
+   *         {
+   *           "stageid:" "id",
+   *           "stagescore": 500
+   *         },
+   *         {
+   *           "stageid:" "id",
+   *           "stagescore": 500
+   *         }
+   *       ]
+   *     },
+   *     {
+   *       "flowtime": ,
+   *       "score": 700,
+   *       "stagescores": [
+   *         {
+   *           "stageid:" "id",
+   *           "stagescore": 0
+   *         },
+   *         {
+   *           "stageid:" "id",
+   *           "stagescore": 700
+   *         }
+   *       ]
+   *     }
+   *   ]
+   * }
+   * 
*/ public static Result restJobGraphData(String jobUrl) { JsonArray datasets = new JsonArray(); @@ -842,8 +894,8 @@ public static Result restJobGraphData(String jobUrl) { if (results.size() == 0) { logger.info("No results for Job url"); } - Map> flowExecUrlToJobsMap = limitResults(groupJobs(results, GroupBy.FLOW_EXECUTION_URL), - results.size(), MAX_HISTORY_LIMIT); + Map> flowExecUrlToJobsMap = limitHistoryResults( + groupJobs(results, GroupBy.FLOW_EXECUTION_URL), results.size(), MAX_HISTORY_LIMIT); // Compute the graph data starting from the earliest available execution to latest List keyList = new ArrayList(flowExecUrlToJobsMap.keySet()); @@ -881,12 +933,19 @@ public static Result restJobGraphData(String jobUrl) { return ok(new Gson().toJson(datasets)); } + /** + * Calculates and returns the Heuristic Score for MapReduce Jobs. + * + * Heuristic Score = Number of Tasks(map/reduce) * Severity (When severity > 1) + * + * @param heuristicResult The Heuristic whose score has to be computed + * @return The Score + */ private static int getHeuristicScore(JobHeuristicResult heuristicResult) { int heuristicScore = 0; int severity = heuristicResult.severity.getValue(); if (severity != 0 && severity != 1) { - int numTasks = 0; for (String[] dataArray : heuristicResult.getDataArray()) { if (dataArray[0] != null && dataArray[0].toLowerCase().equals("number of tasks")) { return severity * Integer.parseInt(dataArray[1]); @@ -897,6 +956,14 @@ private static int getHeuristicScore(JobHeuristicResult heuristicResult) { return heuristicScore; } + /** + * Calculates and return the Mapreduce job score. + * + * Job Score = Sum of individual Heuristic Scores + * + * @param job The JobResult whose score has to be computed + * @return The Score + */ private static int getMRJobScore(JobResult job) { int jobScore = 0; diff --git a/app/controllers/PaginationStats.java b/app/controllers/PaginationStats.java index 68cb26bca..787207227 100644 --- a/app/controllers/PaginationStats.java +++ b/app/controllers/PaginationStats.java @@ -13,8 +13,12 @@ * License for the specific language governing permissions and limitations under * the License. */ + package controllers; +/** + * This class handles the pagination of results in search page + */ public class PaginationStats { public int currentPage = 1; public int paginationBarStartIndex = 1; @@ -23,15 +27,29 @@ public class PaginationStats { public int pageBarLength; public String queryString = null; + /** + * The constructor for the PaginationStats + * + * @param pageLength The number of results per page + * @param pageBarLength The Length of the pagination bar at the bottom + */ public PaginationStats(int pageLength, int pageBarLength) { this.pageLength = pageLength; this.pageBarLength = pageBarLength; } + /** + * Return the current page number + * @return page number + */ public int getCurrentPage() { return currentPage; } + /** + * Set the current page number + * @param currentPage The number to set + */ public void setCurrentPage(int currentPage) { if (currentPage < 1) { this.currentPage = 1; @@ -41,35 +59,59 @@ public void setCurrentPage(int currentPage) { } /** - * Compute paginationBarStartIndex. - * paginationBarStartIndex is computed such that the currentPage remains at the center of the Pagination Bar. + * Computes the paginationBarStartIndex. It is computed such that the currentPage + * remains at the center of the Pagination Bar. + * + * @return The start Index of the Pagination bar */ public int getPaginationBarStartIndex() { this.paginationBarStartIndex = Math.max(this.currentPage - this.pageBarLength / 2, 1); return this.paginationBarStartIndex; } + /** + * Compute the Pagination Bar end index depending on the number of serach results + * to be displayed. + * + * @param resultSize The fetched result size + * @return The end index of the Pagination bar + */ public int computePaginationBarEndIndex(int resultSize) { this.paginationBarEndIndex = this.paginationBarStartIndex + (resultSize - 1) / this.pageLength; return this.paginationBarEndIndex; } + /** + * Returns the pagination bar end Index + */ public int getPaginationBarEndIndex() { return this.paginationBarEndIndex; } + /** + * Returns the query string + */ public String getQueryString() { return queryString; } + /** + * Sets the query string + */ public void setQueryString(String queryString) { this.queryString = queryString; } + /** + * Returns the Pagination bar length + */ public int getPageBarLength() { return pageBarLength; } + /** + * Returns the number of results per page + */ public int getPageLength() { return pageLength; } diff --git a/app/controllers/QueryHandler.java b/app/controllers/QueryHandler.java index 1cfb8da3e..2d82d73ea 100644 --- a/app/controllers/QueryHandler.java +++ b/app/controllers/QueryHandler.java @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ + package controllers; import model.JobHeuristicResult; diff --git a/app/model/JobHeuristicResult.java b/app/model/JobHeuristicResult.java index 25f645abb..a237ece9f 100644 --- a/app/model/JobHeuristicResult.java +++ b/app/model/JobHeuristicResult.java @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ + package model; import javax.persistence.CascadeType; diff --git a/app/model/JobResult.java b/app/model/JobResult.java index 060cf734b..813506f6b 100644 --- a/app/model/JobResult.java +++ b/app/model/JobResult.java @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ + package model; import com.fasterxml.jackson.annotation.JsonManagedReference; diff --git a/app/model/StringResult.java b/app/model/StringResult.java deleted file mode 100644 index 3eb862579..000000000 --- a/app/model/StringResult.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2015 LinkedIn Corp. - * - * 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 model; - -import javax.persistence.Entity; - -import com.avaje.ebean.annotation.Sql; - - -@Entity -@Sql -public class StringResult { - String string; - - public void setString(String val) { - string = val; - } - - public String getString() { - return string; - } -} diff --git a/app/org/apache/spark/deploy/history/SparkDataCollection.scala b/app/org/apache/spark/deploy/history/SparkDataCollection.scala index ceedf7507..d748c7314 100644 --- a/app/org/apache/spark/deploy/history/SparkDataCollection.scala +++ b/app/org/apache/spark/deploy/history/SparkDataCollection.scala @@ -13,13 +13,14 @@ * License for the specific language governing permissions and limitations under * the License. */ + package org.apache.spark.deploy.history import com.linkedin.drelephant.analysis.ApplicationType -import com.linkedin.drelephant.spark.SparkExecutorData.ExecutorInfo -import com.linkedin.drelephant.spark.SparkJobProgressData.JobInfo -import com.linkedin.drelephant.spark._ +import com.linkedin.drelephant.spark.data._ +import SparkExecutorData.ExecutorInfo +import SparkJobProgressData.JobInfo import org.apache.spark.scheduler.{StageInfo, ApplicationEventListener} import org.apache.spark.storage.{StorageStatusTrackingListener, StorageStatus, RDDInfo, StorageStatusListener} import org.apache.spark.ui.env.EnvironmentListener @@ -27,8 +28,6 @@ import org.apache.spark.ui.exec.ExecutorsListener import org.apache.spark.ui.jobs.JobProgressListener import org.apache.spark.ui.storage.StorageListener -import java.lang.{Long => JLong} -import java.util.{Map => JMap} import java.util.{Set => JSet} import java.util.{HashSet => JHashSet} import java.util.{List => JList} @@ -45,8 +44,6 @@ import org.apache.spark.util.collection.OpenHashSet * Notice: * This has to live in Spark's scope because ApplicationEventListener is in private[spark] scope. And it is problematic * to compile if written in Java. - * - * @author yizhou */ class SparkDataCollection(applicationEventListener: ApplicationEventListener, jobProgressListener: JobProgressListener, diff --git a/app/org/apache/spark/deploy/history/SparkFSFetcher.scala b/app/org/apache/spark/deploy/history/SparkFSFetcher.scala index 33d837118..8a26d99b5 100644 --- a/app/org/apache/spark/deploy/history/SparkFSFetcher.scala +++ b/app/org/apache/spark/deploy/history/SparkFSFetcher.scala @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ + package org.apache.spark.deploy.history @@ -20,11 +21,11 @@ import java.net.URI import java.security.PrivilegedAction import java.io.BufferedInputStream import java.io.InputStream - -import com.linkedin.drelephant.util.{MemoryFormatUtils, FetcherConfigurationData, Utils} -import com.linkedin.drelephant.{ElephantContext, HadoopSecurity} +import com.linkedin.drelephant.configurations.fetcher.FetcherConfigurationData +import com.linkedin.drelephant.security.HadoopSecurity +import com.linkedin.drelephant.spark.data.SparkApplicationData +import com.linkedin.drelephant.util.{MemoryFormatUtils, Utils} import com.linkedin.drelephant.analysis.{ApplicationType, AnalyticJob, ElephantFetcher}; -import com.linkedin.drelephant.spark.SparkApplicationData import org.apache.hadoop.conf.Configuration import org.apache.hadoop.fs.{Path, FileSystem} import org.apache.hadoop.hdfs.web.WebHdfsFileSystem @@ -41,8 +42,6 @@ import org.apache.spark.io.CompressionCodec /** * A wrapper that replays Spark event history from files and then fill proper data objects. - * - * @author yizhou */ class SparkFSFetcher(fetcherConfData: FetcherConfigurationData) extends ElephantFetcher[SparkApplicationData] { @@ -149,7 +148,7 @@ class SparkFSFetcher(fetcherConfData: FetcherConfigurationData) extends Elephant if (logInput == null) { dataCollection.throttle() - // Since the data set is empty, we need to set the appilication id, + // Since the data set is empty, we need to set the application id, // so that we could detect this is Spark job type dataCollection.getGeneralData().setApplicationId(appId) dataCollection.getConf().setProperty("spark.app.id", appId) diff --git a/app/org/apache/spark/storage/StorageStatusTrackingListener.scala b/app/org/apache/spark/storage/StorageStatusTrackingListener.scala index f862bdd67..8cc3c7ef1 100644 --- a/app/org/apache/spark/storage/StorageStatusTrackingListener.scala +++ b/app/org/apache/spark/storage/StorageStatusTrackingListener.scala @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ + package org.apache.spark.storage diff --git a/app/views/help/helpNoData.scala.html b/app/views/help/helpNoData.scala.html index 15e60fd1c..ad30f69a3 100644 --- a/app/views/help/helpNoData.scala.html +++ b/app/views/help/helpNoData.scala.html @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. *@ +

This means that the JobTracker no longer provides information to Dr. Elephant for that job.
The JobTracker wipes job data fairly quickly, and sometimes, will result in an empty set of data for certain jobs. diff --git a/app/views/help/mapreduce/helpException.scala.html b/app/views/help/mapreduce/helpException.scala.html index 751038503..fdc0a0193 100644 --- a/app/views/help/mapreduce/helpException.scala.html +++ b/app/views/help/mapreduce/helpException.scala.html @@ -1,3 +1,19 @@ +@* +* Copyright 2015 LinkedIn Corp. +* +* 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. +*@ +

Dr. Elephant now starts to provide exception info for failed jobs.
The stacktrace is the exception of the failed task that caused this job to fail.
diff --git a/app/views/help/mapreduce/helpGC.scala.html b/app/views/help/mapreduce/helpGC.scala.html index 4320b0f83..1061668ae 100644 --- a/app/views/help/mapreduce/helpGC.scala.html +++ b/app/views/help/mapreduce/helpGC.scala.html @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. *@ +

This analysis gauges your job's GC efficiency. It shows the ratio between the task GC time and task CPU time.

diff --git a/app/views/help/mapreduce/helpJobQueueLimit.scala.html b/app/views/help/mapreduce/helpJobQueueLimit.scala.html index bbda84b99..a02e106a2 100644 --- a/app/views/help/mapreduce/helpJobQueueLimit.scala.html +++ b/app/views/help/mapreduce/helpJobQueueLimit.scala.html @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. *@ +

The task execution limit on the Default queue is 15 minutes (900 seconds). You really, really want to be clear of that limit or your job may fail as data grows or if you get assigned some slow nodes. So while running a job on the default queue make sure each task runs between 5 to 15 minutes.

diff --git a/app/views/help/mapreduce/helpMapperDataSkew.scala.html b/app/views/help/mapreduce/helpMapperDataSkew.scala.html index 950357c8d..5d3c118ca 100644 --- a/app/views/help/mapreduce/helpMapperDataSkew.scala.html +++ b/app/views/help/mapreduce/helpMapperDataSkew.scala.html @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. *@ +

This analysis shows whether there is a data-skew for the data entering mapper tasks.

diff --git a/app/views/help/mapreduce/helpMapperMemory.scala.html b/app/views/help/mapreduce/helpMapperMemory.scala.html index 02cb79c4a..dc22087e1 100644 --- a/app/views/help/mapreduce/helpMapperMemory.scala.html +++ b/app/views/help/mapreduce/helpMapperMemory.scala.html @@ -13,4 +13,5 @@ * License for the specific language governing permissions and limitations under * the License. *@ + @helpMemory() \ No newline at end of file diff --git a/app/views/help/mapreduce/helpMapperSpeed.scala.html b/app/views/help/mapreduce/helpMapperSpeed.scala.html index c8fd5841d..6c87e5fd0 100644 --- a/app/views/help/mapreduce/helpMapperSpeed.scala.html +++ b/app/views/help/mapreduce/helpMapperSpeed.scala.html @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. *@ +

This analysis shows the effectiveness of your mapper code.
This should allow you to determine if your mapper is CPU-bound or if your mapper is outputting huge amounts of data. diff --git a/app/views/help/mapreduce/helpMapperTime.scala.html b/app/views/help/mapreduce/helpMapperTime.scala.html index 2ba8a6ee2..1c7839ec1 100644 --- a/app/views/help/mapreduce/helpMapperTime.scala.html +++ b/app/views/help/mapreduce/helpMapperTime.scala.html @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. *@ +

This analysis shows how well the number of mappers is adjusted.
This should allow you to better tweak the number of mappers for your job.
diff --git a/app/views/help/mapreduce/helpMemory.scala.html b/app/views/help/mapreduce/helpMemory.scala.html index 010ad280e..bfe161c50 100644 --- a/app/views/help/mapreduce/helpMemory.scala.html +++ b/app/views/help/mapreduce/helpMemory.scala.html @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. *@ +

This analysis shows the task memory utilization.
We check the ratio between your task's consumed memory AND the requested container memory.
diff --git a/app/views/help/mapreduce/helpReducerDataSkew.scala.html b/app/views/help/mapreduce/helpReducerDataSkew.scala.html index 7ef25ec92..93e3d1e34 100644 --- a/app/views/help/mapreduce/helpReducerDataSkew.scala.html +++ b/app/views/help/mapreduce/helpReducerDataSkew.scala.html @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. *@ +

This analysis shows whether there is a data-skew for the data entering reducer tasks.

diff --git a/app/views/help/mapreduce/helpReducerMemory.scala.html b/app/views/help/mapreduce/helpReducerMemory.scala.html index 02cb79c4a..dc22087e1 100644 --- a/app/views/help/mapreduce/helpReducerMemory.scala.html +++ b/app/views/help/mapreduce/helpReducerMemory.scala.html @@ -13,4 +13,5 @@ * License for the specific language governing permissions and limitations under * the License. *@ + @helpMemory() \ No newline at end of file diff --git a/app/views/help/mapreduce/helpReducerTime.scala.html b/app/views/help/mapreduce/helpReducerTime.scala.html index 1776950c4..41b991a88 100644 --- a/app/views/help/mapreduce/helpReducerTime.scala.html +++ b/app/views/help/mapreduce/helpReducerTime.scala.html @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. *@ +

This analysis shows the efficiency of your reducers.
This should allow you to better adjust the number of reducers for your job.
diff --git a/app/views/help/mapreduce/helpShuffleSort.scala.html b/app/views/help/mapreduce/helpShuffleSort.scala.html index 82097563b..f4f770bbe 100644 --- a/app/views/help/mapreduce/helpShuffleSort.scala.html +++ b/app/views/help/mapreduce/helpShuffleSort.scala.html @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. *@ +

This analysis shows how much time the reducer spends in shuffle and sort steps versus in the reducer code.
This should allow you to understand the efficiency of your reducer. diff --git a/app/views/results/compareResults.scala.html b/app/views/results/compareResults.scala.html index fedb83662..ebe828acc 100644 --- a/app/views/results/compareResults.scala.html +++ b/app/views/results/compareResults.scala.html @@ -14,7 +14,7 @@ * the License. *@ -@(results: Map[String, Map[String, List[model.JobResult]]]) +@(results: java.util.Map[String, java.util.Map[String, java.util.List[model.JobResult]]]) @* * The layout of the comparison results. @@ -35,7 +35,7 @@

Flow Execution URL: @flowexecurl @if(jobs != null) { @for(result <- jobs) { - @tags.jobSummary(result) diff --git a/app/views/results/flowDetails.scala.html b/app/views/results/flowDetails.scala.html index c59890318..ec75c86bb 100644 --- a/app/views/results/flowDetails.scala.html +++ b/app/views/results/flowDetails.scala.html @@ -14,7 +14,7 @@ * the License. *@ -@(execurl: String, results: Map[String, List[model.JobResult]]) +@(execurl: String, results: java.util.Map[String, java.util.List[model.JobResult]]) @* * Displays all the mr jobs belonging to a flow grouped by job exec url @@ -37,7 +37,7 @@

Job Execution URL: @url
@for(result <- jobs) { - @tags.jobSummary(result) diff --git a/app/views/results/flowHistoryResults.scala.html b/app/views/results/flowHistoryResults.scala.html index 6dd0f3e3b..b982a28cf 100644 --- a/app/views/results/flowHistoryResults.scala.html +++ b/app/views/results/flowHistoryResults.scala.html @@ -14,21 +14,21 @@ * the License. *@ -@(flowUrl: String, results: Map[String, Map[String, List[model.JobResult]]], jobList: List[String], flowExecTimeList: List[Long]) +@(flowUrl: String, results: java.util.Map[String, java.util.Map[String, java.util.List[model.JobResult]]], + jobList: java.util.List[String], flowExecTimeList: java.util.List[Long]) @import com.linkedin.drelephant.analysis.Severity - @import scala.Predef; var jobDefIndex = 0 @getSeverityColor(severity : Severity) = @{ var color: String = "#5cb85c"; // LOW or NONE - if(severity.getText().equalsIgnoreCase("CRITICAL")) { - color = "#d9534f"; - } else if(severity.getText().equalsIgnoreCase("SEVERE")) { - color = "#e4804e"; - } else if(severity.getText().equalsIgnoreCase("MODERATE")) { - color = "#f0ad4e"; + if(severity.getText.equalsIgnoreCase("CRITICAL")) { + color = "#d9534f" + } else if(severity.getText.equalsIgnoreCase("SEVERE")) { + color = "#e4804e" + } else if(severity.getText.equalsIgnoreCase("MODERATE")) { + color = "#f0ad4e" } color diff --git a/app/views/results/jobHistoryResults.scala.html b/app/views/results/jobHistoryResults.scala.html index fcda44ae2..4e9038d48 100644 --- a/app/views/results/jobHistoryResults.scala.html +++ b/app/views/results/jobHistoryResults.scala.html @@ -14,7 +14,8 @@ * the License. *@ -@(jobUrl: String, results: Map[String, List[model.JobResult]], maxStages: Int, flowExecTimeList: List[Long]) +@(jobUrl: String, results: java.util.Map[String, java.util.List[model.JobResult]], maxStages: Int, + flowExecTimeList:java.util.List[Long]) @import com.linkedin.drelephant.analysis.Severity diff --git a/app/views/results/searchResults.scala.html b/app/views/results/searchResults.scala.html index c1ac13912..03bc202f9 100644 --- a/app/views/results/searchResults.scala.html +++ b/app/views/results/searchResults.scala.html @@ -14,7 +14,7 @@ * the License. *@ -@(title: String, results: List[model.JobResult]) +@(title: String, results: java.util.List[model.JobResult]) @* * The layout of the job results. @@ -29,7 +29,7 @@

@title

@for(result <- results) { - @tags.jobSummary(result) diff --git a/build.sbt b/build.sbt index 182a6d216..e94130ac1 100644 --- a/build.sbt +++ b/build.sbt @@ -66,4 +66,4 @@ val ArtifactoryBaseUrl = "http://artifactory.corp.linkedin.com:8081/artifactory/ resolvers += Resolver.url("LI repo repository", url(ArtifactoryBaseUrl + "release"))(LinkedInPatterns) -playJavaSettings +playJavaSettings \ No newline at end of file diff --git a/compile.sh b/compile.sh index cdb293ee6..802c4cd8e 100755 --- a/compile.sh +++ b/compile.sh @@ -22,8 +22,8 @@ trap "exit" SIGINT SIGTERM project_root=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) cd ${project_root} -start_script=${project_root}/start.sh -stop_script=${project_root}/stop.sh +start_script=${project_root}/scripts/start.sh +stop_script=${project_root}/scripts/stop.sh # Echo the value of pwd in the script so that it is clear what is being removed. rm -rf ${project_root}/dist diff --git a/conf/routes b/conf/routes index cc7c5377b..5dae181f1 100644 --- a/conf/routes +++ b/conf/routes @@ -27,7 +27,6 @@ GET /search controllers.Application.search() GET /compare controllers.Application.compare() GET /flowhistory controllers.Application.flowHistory() GET /jobhistory controllers.Application.jobHistory() -GET /flowrelated controllers.Application.flowRelated() # Rest calls GET /rest/job controllers.Application.restJobResult(id: String) diff --git a/project/.DS_Store b/project/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..82b0b23cd13bec9775b56c062c0a85430ab54988 GIT binary patch literal 6148 zcmeHK&1%~~5T1=yN3ky|Z6MH#jxCttpax$RCn@w$Fm*{NO>w12#9M@9EV(8agU)%3 z{=Y|QPI-X#QF?N}{YiXIfbq2)Rbtt3>aZ2d&`i-%U3)IGxKS{EqFv&t0OY|6c ze<4LquZh*3poe2br<7w4$kAg_$vUrOt?HfP4MsSsMzd!k8d_T{C?B-1-!OYz-U>LZT0kJ>0 zP=UV2QlZ>BVB{45XrWpPd~uf$Ij%-uW2q1=5T;UrDwXLg22<&%cXgbvu~ewiiRsG+ z(<3u|Lt%1s^zX`WV!lFS4FiUOWd>@h=!o-w@6Y%DXIfD+ZXg+u!YCO8RWQ yGC6V9y3p59AtJ6+C{tkcaV#Bi6mLMKpv@Hy(AQWhL=D9J5s)+(V;K0W4EzG7THlfY literal 0 HcmV?d00001 diff --git a/public/js/searchform.js b/public/js/searchform.js index 270fc94e5..92a5e268f 100644 --- a/public/js/searchform.js +++ b/public/js/searchform.js @@ -16,96 +16,96 @@ $(document).ready(function(){ - var form = $("#search-form"); + var form = $("#search-form"); - var jobid = $("#form-job-id"); - var flowurl = $("#form-flow-url"); - var user = $("#form-user"); - var jobtypeEnable = $("#form-job-type-enable"); - var jobtype = $("#form-job-type"); - var severityEnable = $("#form-severity-enable"); - var severity = $("#form-severity"); - var analysis = $("#form-analysis"); - var datetimeEnable = $("#form-datetime-enable"); - var startDate = $("#form-finished-time-begin"); - var endDate = $("#form-finished-time-end"); + var jobid = $("#form-job-id"); + var flowurl = $("#form-flow-url"); + var user = $("#form-user"); + var jobtypeEnable = $("#form-job-type-enable"); + var jobtype = $("#form-job-type"); + var severityEnable = $("#form-severity-enable"); + var severity = $("#form-severity"); + var analysis = $("#form-analysis"); + var datetimeEnable = $("#form-datetime-enable"); + var startDate = $("#form-finished-time-begin"); + var endDate = $("#form-finished-time-end"); - startDate.datepicker(); - endDate.datepicker(); + startDate.datepicker(); + endDate.datepicker(); - var updateForm = function(){ - if(jobid.val()) { - flowurl.prop('disabled', true); - user.prop('disabled', true); - severity.prop('disabled', true); - analysis.prop('disabled', true); - jobtype.prop('disabled', true); - jobtypeEnable.prop('disabled', true); - severityEnable.prop('disabled', true); - datetimeEnable.prop('disabled', true); - startDate.prop('disabled', true); - endDate.prop('disabled', true); - } else if(flowurl.val()) { - jobid.prop('disabled', true); - user.prop('disabled', true); - severity.prop('disabled', true); - analysis.prop('disabled', true); - jobtype.prop('disabled', true); - jobtypeEnable.prop('disabled', true); - severityEnable.prop('disabled', true); - datetimeEnable.prop('disabled', true); - startDate.prop('disabled', true); - endDate.prop('disabled', true); - } - else{ - jobid.prop('disabled', false); - flowurl.prop('disabled', false); - jobtypeEnable.prop('disabled', false); - severityEnable.prop('disabled', false); - datetimeEnable.prop('disabled', false); - user.prop('disabled', false); - if(jobtypeEnable.prop('checked')){ - jobtype.prop('disabled', false); - } - else { - jobtype.prop('disabled', true); - } - if(severityEnable.prop('checked')){ - severity.prop('disabled', false); - analysis.prop('disabled', false); - } - else { - severity.prop('disabled', true); - analysis.prop('disabled', true); - } - if(datetimeEnable.prop('checked')){ - startDate.prop('disabled', false); - endDate.prop('disabled', false); - } - else { - startDate.prop('disabled', true); - endDate.prop('disabled', true); - } - } + var updateForm = function(){ + if(jobid.val()) { + flowurl.prop('disabled', true); + user.prop('disabled', true); + severity.prop('disabled', true); + analysis.prop('disabled', true); + jobtype.prop('disabled', true); + jobtypeEnable.prop('disabled', true); + severityEnable.prop('disabled', true); + datetimeEnable.prop('disabled', true); + startDate.prop('disabled', true); + endDate.prop('disabled', true); + } else if(flowurl.val()) { + jobid.prop('disabled', true); + user.prop('disabled', true); + severity.prop('disabled', true); + analysis.prop('disabled', true); + jobtype.prop('disabled', true); + jobtypeEnable.prop('disabled', true); + severityEnable.prop('disabled', true); + datetimeEnable.prop('disabled', true); + startDate.prop('disabled', true); + endDate.prop('disabled', true); } - jobid.on("propertychange keyup input paste", updateForm); - flowurl.on("propertychange keyup input paste", updateForm); - jobtypeEnable.change(updateForm); - severityEnable.change(updateForm); - datetimeEnable.change(updateForm); + else{ + jobid.prop('disabled', false); + flowurl.prop('disabled', false); + jobtypeEnable.prop('disabled', false); + severityEnable.prop('disabled', false); + datetimeEnable.prop('disabled', false); + user.prop('disabled', false); + if(jobtypeEnable.prop('checked')){ + jobtype.prop('disabled', false); + } + else { + jobtype.prop('disabled', true); + } + if(severityEnable.prop('checked')){ + severity.prop('disabled', false); + analysis.prop('disabled', false); + } + else { + severity.prop('disabled', true); + analysis.prop('disabled', true); + } + if(datetimeEnable.prop('checked')){ + startDate.prop('disabled', false); + endDate.prop('disabled', false); + } + else { + startDate.prop('disabled', true); + endDate.prop('disabled', true); + } + } + } + jobid.on("propertychange keyup input paste", updateForm); + flowurl.on("propertychange keyup input paste", updateForm); + jobtypeEnable.change(updateForm); + severityEnable.change(updateForm); + datetimeEnable.change(updateForm); - form.submit(function(event){ - var data = form.serialize(); - localStorage.setItem('search-form', data); - //Remove useless fields from the URL - form.find('input[name]').filter(function(){return !$(this).val();}).attr('name', ''); - }); + form.submit(function(event){ + var data = form.serialize(); + localStorage.setItem('search-form', data); + //Remove useless fields from the URL + form.find('input[name]').filter(function(){return !$(this).val();}).attr('name', ''); + }); - try { - var data = localStorage.getItem('search-form'); - form.deserialize(data); - } - catch(e){} + try { + var data = localStorage.getItem('search-form'); + form.deserialize(data); + } + catch(e){} - updateForm(); + updateForm(); }); \ No newline at end of file diff --git a/start.sh b/scripts/start.sh similarity index 100% rename from start.sh rename to scripts/start.sh diff --git a/stop.sh b/scripts/stop.sh similarity index 100% rename from stop.sh rename to scripts/stop.sh diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/JobQueueLimitHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/JobQueueLimitHeuristicTest.java index dcc9a8f83..4cda1e53d 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/JobQueueLimitHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/JobQueueLimitHeuristicTest.java @@ -13,13 +13,13 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.mapreduce.heuristics; import com.linkedin.drelephant.analysis.ApplicationType; -import com.linkedin.drelephant.util.HeuristicConfigurationData; +import com.linkedin.drelephant.configurations.heuristic.HeuristicConfigurationData; import java.io.IOException; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.Properties; @@ -30,9 +30,9 @@ import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; -import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; -import com.linkedin.drelephant.mapreduce.MapReduceTaskData; +import com.linkedin.drelephant.mapreduce.data.MapReduceCounterData; +import com.linkedin.drelephant.mapreduce.data.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.data.MapReduceTaskData; public class JobQueueLimitHeuristicTest extends TestCase { @@ -68,7 +68,7 @@ public void testNonDefaultRuntimeNone() throws IOException { } private Severity analyzeJob(long runtimeMs, String queueName) throws IOException { - MapReduceCounterHolder dummyCounter = new MapReduceCounterHolder(); + MapReduceCounterData dummyCounter = new MapReduceCounterData(); MapReduceTaskData[] mappers = new MapReduceTaskData[2 * NUM_TASKS / 3]; MapReduceTaskData[] reducers = new MapReduceTaskData[NUM_TASKS / 3]; Properties jobConf = new Properties(); diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperDataSkewHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperDataSkewHeuristicTest.java index 30ac2c078..7813827ef 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperDataSkewHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperDataSkewHeuristicTest.java @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.mapreduce.heuristics; import com.linkedin.drelephant.analysis.ApplicationType; @@ -20,15 +21,14 @@ import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; -import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; -import com.linkedin.drelephant.mapreduce.MapReduceTaskData; +import com.linkedin.drelephant.mapreduce.data.MapReduceCounterData; +import com.linkedin.drelephant.mapreduce.data.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.data.MapReduceTaskData; -import com.linkedin.drelephant.util.HeuristicConfigurationData; +import com.linkedin.drelephant.configurations.heuristic.HeuristicConfigurationData; import java.io.IOException; import java.util.HashMap; -import java.util.List; import java.util.Map; import junit.framework.TestCase; @@ -71,14 +71,14 @@ public void testSmallTasks() throws IOException { private Severity analyzeJob(int numSmallTasks, int numLargeTasks, long smallInputSize, long largeInputSize) throws IOException { - MapReduceCounterHolder jobCounter = new MapReduceCounterHolder(); + MapReduceCounterData jobCounter = new MapReduceCounterData(); MapReduceTaskData[] mappers = new MapReduceTaskData[numSmallTasks + numLargeTasks]; - MapReduceCounterHolder smallCounter = new MapReduceCounterHolder(); - smallCounter.set(MapReduceCounterHolder.CounterName.HDFS_BYTES_READ, smallInputSize); + MapReduceCounterData smallCounter = new MapReduceCounterData(); + smallCounter.set(MapReduceCounterData.CounterName.HDFS_BYTES_READ, smallInputSize); - MapReduceCounterHolder largeCounter = new MapReduceCounterHolder(); - largeCounter.set(MapReduceCounterHolder.CounterName.HDFS_BYTES_READ, largeInputSize); + MapReduceCounterData largeCounter = new MapReduceCounterData(); + largeCounter.set(MapReduceCounterData.CounterName.HDFS_BYTES_READ, largeInputSize); int i = 0; for (; i < numSmallTasks; i++) { diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperGCHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperGCHeuristicTest.java index e811fa1be..e3de6f228 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperGCHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperGCHeuristicTest.java @@ -1,17 +1,32 @@ +/* + * Copyright 2015 LinkedIn Corp. + * + * 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.linkedin.drelephant.mapreduce.heuristics; import com.linkedin.drelephant.analysis.ApplicationType; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; -import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; -import com.linkedin.drelephant.mapreduce.MapReduceTaskData; +import com.linkedin.drelephant.mapreduce.data.MapReduceCounterData; +import com.linkedin.drelephant.mapreduce.data.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.data.MapReduceTaskData; -import com.linkedin.drelephant.util.HeuristicConfigurationData; +import com.linkedin.drelephant.configurations.heuristic.HeuristicConfigurationData; import java.io.IOException; import java.util.HashMap; -import java.util.List; import java.util.Map; import junit.framework.TestCase; @@ -46,12 +61,12 @@ public void testShortTasksNone() throws IOException { private Severity analyzeJob(long runtimeMs, long cpuMs, long gcMs) throws IOException { - MapReduceCounterHolder jobCounter = new MapReduceCounterHolder(); + MapReduceCounterData jobCounter = new MapReduceCounterData(); MapReduceTaskData[] mappers = new MapReduceTaskData[NUMTASKS]; - MapReduceCounterHolder counter = new MapReduceCounterHolder(); - counter.set(MapReduceCounterHolder.CounterName.CPU_MILLISECONDS, cpuMs); - counter.set(MapReduceCounterHolder.CounterName.GC_MILLISECONDS, gcMs); + MapReduceCounterData counter = new MapReduceCounterData(); + counter.set(MapReduceCounterData.CounterName.CPU_MILLISECONDS, cpuMs); + counter.set(MapReduceCounterData.CounterName.GC_MILLISECONDS, gcMs); int i = 0; for (; i < NUMTASKS; i++) { diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperMemoryHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperMemoryHeuristicTest.java index 1f91cd67b..b718e04f2 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperMemoryHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperMemoryHeuristicTest.java @@ -13,20 +13,20 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.mapreduce.heuristics; import com.linkedin.drelephant.analysis.ApplicationType; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; -import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; -import com.linkedin.drelephant.mapreduce.MapReduceTaskData; +import com.linkedin.drelephant.mapreduce.data.MapReduceCounterData; +import com.linkedin.drelephant.mapreduce.data.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.data.MapReduceTaskData; -import com.linkedin.drelephant.util.HeuristicConfigurationData; +import com.linkedin.drelephant.configurations.heuristic.HeuristicConfigurationData; import java.io.IOException; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.Properties; @@ -69,11 +69,11 @@ public void testDefaultContainerNoneMore() throws IOException { } private Severity analyzeJob(long taskAvgMemMB, long containerMemMB) throws IOException { - MapReduceCounterHolder jobCounter = new MapReduceCounterHolder(); + MapReduceCounterData jobCounter = new MapReduceCounterData(); MapReduceTaskData[] mappers = new MapReduceTaskData[NUMTASKS]; - MapReduceCounterHolder counter = new MapReduceCounterHolder(); - counter.set(MapReduceCounterHolder.CounterName.PHYSICAL_MEMORY_BYTES, taskAvgMemMB* FileUtils.ONE_MB); + MapReduceCounterData counter = new MapReduceCounterData(); + counter.set(MapReduceCounterData.CounterName.PHYSICAL_MEMORY_BYTES, taskAvgMemMB* FileUtils.ONE_MB); Properties p = new Properties(); p.setProperty(MapperMemoryHeuristic.MAPPER_MEMORY_CONF, Long.toString(containerMemMB)); diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristicTest.java index 51dbe5205..ce20430b3 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristicTest.java @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.mapreduce.heuristics; import com.linkedin.drelephant.analysis.ApplicationType; @@ -20,16 +21,15 @@ import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; -import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; -import com.linkedin.drelephant.mapreduce.MapReduceTaskData; +import com.linkedin.drelephant.mapreduce.data.MapReduceCounterData; +import com.linkedin.drelephant.mapreduce.data.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.data.MapReduceTaskData; import com.linkedin.drelephant.math.Statistics; -import com.linkedin.drelephant.util.HeuristicConfigurationData; +import com.linkedin.drelephant.configurations.heuristic.HeuristicConfigurationData; import java.io.IOException; import java.util.HashMap; -import java.util.List; import java.util.Map; import junit.framework.TestCase; @@ -68,11 +68,11 @@ public void testShortTask() throws IOException { } private Severity analyzeJob(long runtimeMs, long readBytes) throws IOException { - MapReduceCounterHolder jobCounter = new MapReduceCounterHolder(); + MapReduceCounterData jobCounter = new MapReduceCounterData(); MapReduceTaskData[] mappers = new MapReduceTaskData[NUMTASKS]; - MapReduceCounterHolder counter = new MapReduceCounterHolder(); - counter.set(MapReduceCounterHolder.CounterName.HDFS_BYTES_READ, readBytes); + MapReduceCounterData counter = new MapReduceCounterData(); + counter.set(MapReduceCounterData.CounterName.HDFS_BYTES_READ, readBytes); int i = 0; for (; i < NUMTASKS; i++) { diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristicTest.java index b05ff9034..bba1c9dd7 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristicTest.java @@ -13,19 +13,19 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.mapreduce.heuristics; import com.linkedin.drelephant.analysis.ApplicationType; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; -import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; -import com.linkedin.drelephant.mapreduce.MapReduceTaskData; -import com.linkedin.drelephant.util.HeuristicConfigurationData; +import com.linkedin.drelephant.mapreduce.data.MapReduceCounterData; +import com.linkedin.drelephant.mapreduce.data.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.data.MapReduceTaskData; +import com.linkedin.drelephant.configurations.heuristic.HeuristicConfigurationData; import java.io.IOException; import java.util.HashMap; -import java.util.List; import java.util.Map; import junit.framework.TestCase; @@ -67,12 +67,12 @@ public void testSmallNumTasks() throws IOException { } private Severity analyzeJob(long spilledRecords, long mapRecords, int numTasks) throws IOException { - MapReduceCounterHolder jobCounter = new MapReduceCounterHolder(); + MapReduceCounterData jobCounter = new MapReduceCounterData(); MapReduceTaskData[] mappers = new MapReduceTaskData[numTasks]; - MapReduceCounterHolder counter = new MapReduceCounterHolder(); - counter.set(MapReduceCounterHolder.CounterName.SPILLED_RECORDS, spilledRecords); - counter.set(MapReduceCounterHolder.CounterName.MAP_OUTPUT_RECORDS, mapRecords); + MapReduceCounterData counter = new MapReduceCounterData(); + counter.set(MapReduceCounterData.CounterName.SPILLED_RECORDS, spilledRecords); + counter.set(MapReduceCounterData.CounterName.MAP_OUTPUT_RECORDS, mapRecords); for (int i=0; i < numTasks; i++) { mappers[i] = new MapReduceTaskData(counter, new long[4]); diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristicTest.java index 43ab9a592..675fc30af 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristicTest.java @@ -13,20 +13,20 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.mapreduce.heuristics; import com.linkedin.drelephant.analysis.ApplicationType; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; -import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; -import com.linkedin.drelephant.mapreduce.MapReduceTaskData; +import com.linkedin.drelephant.mapreduce.data.MapReduceCounterData; +import com.linkedin.drelephant.mapreduce.data.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.data.MapReduceTaskData; import com.linkedin.drelephant.math.Statistics; -import com.linkedin.drelephant.util.HeuristicConfigurationData; +import com.linkedin.drelephant.configurations.heuristic.HeuristicConfigurationData; import java.io.IOException; import java.util.HashMap; -import java.util.List; import java.util.Map; import junit.framework.TestCase; @@ -87,11 +87,11 @@ public void testShortRuntimeTasksNone() throws IOException { } private Severity analyzeJob(int numTasks, long runtime) throws IOException { - MapReduceCounterHolder jobCounter = new MapReduceCounterHolder(); + MapReduceCounterData jobCounter = new MapReduceCounterData(); MapReduceTaskData[] mappers = new MapReduceTaskData[numTasks]; - MapReduceCounterHolder taskCounter = new MapReduceCounterHolder(); - taskCounter.set(MapReduceCounterHolder.CounterName.HDFS_BYTES_READ, DUMMY_INPUT_SIZE); + MapReduceCounterData taskCounter = new MapReduceCounterData(); + taskCounter.set(MapReduceCounterData.CounterName.HDFS_BYTES_READ, DUMMY_INPUT_SIZE); int i = 0; for (; i < numTasks; i++) { diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerDataSkewHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerDataSkewHeuristicTest.java index 048d4ac21..94cf4832c 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerDataSkewHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerDataSkewHeuristicTest.java @@ -21,15 +21,14 @@ import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; -import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; -import com.linkedin.drelephant.mapreduce.MapReduceTaskData; +import com.linkedin.drelephant.mapreduce.data.MapReduceCounterData; +import com.linkedin.drelephant.mapreduce.data.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.data.MapReduceTaskData; -import com.linkedin.drelephant.util.HeuristicConfigurationData; +import com.linkedin.drelephant.configurations.heuristic.HeuristicConfigurationData; import java.io.IOException; import java.util.HashMap; -import java.util.List; import java.util.Map; import junit.framework.TestCase; @@ -71,14 +70,14 @@ public void testSmallTasks() throws IOException { private Severity analyzeJob(int numSmallTasks, int numLargeTasks, long smallInputSize, long largeInputSize) throws IOException { - MapReduceCounterHolder jobCounter = new MapReduceCounterHolder(); + MapReduceCounterData jobCounter = new MapReduceCounterData(); MapReduceTaskData[] reducers = new MapReduceTaskData[numSmallTasks + numLargeTasks]; - MapReduceCounterHolder smallCounter = new MapReduceCounterHolder(); - smallCounter.set(MapReduceCounterHolder.CounterName.REDUCE_SHUFFLE_BYTES, smallInputSize); + MapReduceCounterData smallCounter = new MapReduceCounterData(); + smallCounter.set(MapReduceCounterData.CounterName.REDUCE_SHUFFLE_BYTES, smallInputSize); - MapReduceCounterHolder largeCounter = new MapReduceCounterHolder(); - largeCounter.set(MapReduceCounterHolder.CounterName.REDUCE_SHUFFLE_BYTES, largeInputSize); + MapReduceCounterData largeCounter = new MapReduceCounterData(); + largeCounter.set(MapReduceCounterData.CounterName.REDUCE_SHUFFLE_BYTES, largeInputSize); int i = 0; for (; i < numSmallTasks; i++) { diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerGCHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerGCHeuristicTest.java index 9893c5041..9b0ceee9f 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerGCHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerGCHeuristicTest.java @@ -1,17 +1,32 @@ +/* + * Copyright 2015 LinkedIn Corp. + * + * 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.linkedin.drelephant.mapreduce.heuristics; import com.linkedin.drelephant.analysis.ApplicationType; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; -import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; -import com.linkedin.drelephant.mapreduce.MapReduceTaskData; +import com.linkedin.drelephant.mapreduce.data.MapReduceCounterData; +import com.linkedin.drelephant.mapreduce.data.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.data.MapReduceTaskData; -import com.linkedin.drelephant.util.HeuristicConfigurationData; +import com.linkedin.drelephant.configurations.heuristic.HeuristicConfigurationData; import java.io.IOException; import java.util.HashMap; -import java.util.List; import java.util.Map; import junit.framework.TestCase; @@ -45,12 +60,12 @@ public void testShortTasksNone() throws IOException { private Severity analyzeJob(long runtimeMs, long cpuMs, long gcMs) throws IOException { - MapReduceCounterHolder jobCounter = new MapReduceCounterHolder(); + MapReduceCounterData jobCounter = new MapReduceCounterData(); MapReduceTaskData[] reducers = new MapReduceTaskData[NUMTASKS]; - MapReduceCounterHolder counter = new MapReduceCounterHolder(); - counter.set(MapReduceCounterHolder.CounterName.CPU_MILLISECONDS, cpuMs); - counter.set(MapReduceCounterHolder.CounterName.GC_MILLISECONDS, gcMs); + MapReduceCounterData counter = new MapReduceCounterData(); + counter.set(MapReduceCounterData.CounterName.CPU_MILLISECONDS, cpuMs); + counter.set(MapReduceCounterData.CounterName.GC_MILLISECONDS, gcMs); int i = 0; for (; i < NUMTASKS; i++) { diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerMemoryHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerMemoryHeuristicTest.java index fe781ab20..430688adb 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerMemoryHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerMemoryHeuristicTest.java @@ -13,20 +13,20 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.mapreduce.heuristics; import com.linkedin.drelephant.analysis.ApplicationType; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; -import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; -import com.linkedin.drelephant.mapreduce.MapReduceTaskData; +import com.linkedin.drelephant.mapreduce.data.MapReduceCounterData; +import com.linkedin.drelephant.mapreduce.data.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.data.MapReduceTaskData; -import com.linkedin.drelephant.util.HeuristicConfigurationData; +import com.linkedin.drelephant.configurations.heuristic.HeuristicConfigurationData; import java.io.IOException; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.Properties; @@ -68,11 +68,11 @@ public void testDefaultContainerNoneMore() throws IOException { } private Severity analyzeJob(long taskAvgMemMB, long containerMemMB) throws IOException { - MapReduceCounterHolder jobCounter = new MapReduceCounterHolder(); + MapReduceCounterData jobCounter = new MapReduceCounterData(); MapReduceTaskData[] reducers = new MapReduceTaskData[NUMTASKS]; - MapReduceCounterHolder counter = new MapReduceCounterHolder(); - counter.set(MapReduceCounterHolder.CounterName.PHYSICAL_MEMORY_BYTES, taskAvgMemMB* FileUtils.ONE_MB); + MapReduceCounterData counter = new MapReduceCounterData(); + counter.set(MapReduceCounterData.CounterName.PHYSICAL_MEMORY_BYTES, taskAvgMemMB* FileUtils.ONE_MB); Properties p = new Properties(); p.setProperty(ReducerMemoryHeuristic.REDUCER_MEMORY_CONF, Long.toString(containerMemMB)); diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristicTest.java index 15ceec36e..40e028e8b 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristicTest.java @@ -13,22 +13,22 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.mapreduce.heuristics; import com.linkedin.drelephant.analysis.ApplicationType; -import com.linkedin.drelephant.util.HeuristicConfigurationData; +import com.linkedin.drelephant.configurations.heuristic.HeuristicConfigurationData; import java.io.IOException; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; -import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; -import com.linkedin.drelephant.mapreduce.MapReduceTaskData; +import com.linkedin.drelephant.mapreduce.data.MapReduceCounterData; +import com.linkedin.drelephant.mapreduce.data.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.data.MapReduceTaskData; import com.linkedin.drelephant.math.Statistics; import java.util.HashMap; -import java.util.List; import java.util.Map; import junit.framework.TestCase; @@ -79,7 +79,7 @@ public void testLongRunetimeSevereMore() throws IOException { } private Severity analyzeJob(long runtimeMs, int numTasks) throws IOException { - MapReduceCounterHolder dummyCounter = new MapReduceCounterHolder(); + MapReduceCounterData dummyCounter = new MapReduceCounterData(); MapReduceTaskData[] reducers = new MapReduceTaskData[numTasks]; int i = 0; diff --git a/test/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristicTest.java b/test/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristicTest.java index 8a767728e..f813b7390 100644 --- a/test/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristicTest.java +++ b/test/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristicTest.java @@ -13,22 +13,22 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.mapreduce.heuristics; import com.linkedin.drelephant.analysis.ApplicationType; -import com.linkedin.drelephant.util.HeuristicConfigurationData; +import com.linkedin.drelephant.configurations.heuristic.HeuristicConfigurationData; import java.io.IOException; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.mapreduce.MapReduceCounterHolder; -import com.linkedin.drelephant.mapreduce.MapReduceApplicationData; -import com.linkedin.drelephant.mapreduce.MapReduceTaskData; +import com.linkedin.drelephant.mapreduce.data.MapReduceCounterData; +import com.linkedin.drelephant.mapreduce.data.MapReduceApplicationData; +import com.linkedin.drelephant.mapreduce.data.MapReduceTaskData; import com.linkedin.drelephant.math.Statistics; import java.util.HashMap; -import java.util.List; import java.util.Map; import junit.framework.TestCase; @@ -91,7 +91,7 @@ public void testShortSort() throws IOException { } private Severity analyzeJob(long shuffleTimeMs, long sortTimeMs, long reduceTimeMs) throws IOException { - MapReduceCounterHolder dummyCounter = new MapReduceCounterHolder(); + MapReduceCounterData dummyCounter = new MapReduceCounterData(); MapReduceTaskData[] reducers = new MapReduceTaskData[NUMTASKS]; int i = 0; diff --git a/test/com/linkedin/drelephant/spark/MockSparkApplicationData.java b/test/com/linkedin/drelephant/spark/MockSparkApplicationData.java index 8a71bbb2a..025191250 100644 --- a/test/com/linkedin/drelephant/spark/MockSparkApplicationData.java +++ b/test/com/linkedin/drelephant/spark/MockSparkApplicationData.java @@ -13,16 +13,21 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.spark; import com.linkedin.drelephant.analysis.ApplicationType; +import com.linkedin.drelephant.spark.data.SparkApplicationData; +import com.linkedin.drelephant.spark.data.SparkEnvironmentData; +import com.linkedin.drelephant.spark.data.SparkExecutorData; +import com.linkedin.drelephant.spark.data.SparkGeneralData; +import com.linkedin.drelephant.spark.data.SparkJobProgressData; +import com.linkedin.drelephant.spark.data.SparkStorageData; import java.util.Properties; /** * This is a pseudo local implementation of SparkApplicationData interface, supposed to be used for test purpose. - * - * @author yizhou */ public class MockSparkApplicationData implements SparkApplicationData { private static final ApplicationType APPLICATION_TYPE = new ApplicationType("SPARK"); diff --git a/test/com/linkedin/drelephant/spark/heuristics/BestPropertiesConventionHeuristicTest.java b/test/com/linkedin/drelephant/spark/heuristics/BestPropertiesConventionHeuristicTest.java index 271364544..699280b2e 100644 --- a/test/com/linkedin/drelephant/spark/heuristics/BestPropertiesConventionHeuristicTest.java +++ b/test/com/linkedin/drelephant/spark/heuristics/BestPropertiesConventionHeuristicTest.java @@ -1,14 +1,11 @@ package com.linkedin.drelephant.spark.heuristics; import com.linkedin.drelephant.analysis.ApplicationType; -import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.Severity; -import com.linkedin.drelephant.mapreduce.heuristics.ShuffleSortHeuristic; import com.linkedin.drelephant.spark.MockSparkApplicationData; -import com.linkedin.drelephant.spark.SparkApplicationData; -import com.linkedin.drelephant.util.HeuristicConfigurationData; +import com.linkedin.drelephant.spark.data.SparkApplicationData; +import com.linkedin.drelephant.configurations.heuristic.HeuristicConfigurationData; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.Properties; import junit.framework.TestCase; diff --git a/test/com/linkedin/drelephant/spark/heuristics/MemoryLimitHeuristicTest.java b/test/com/linkedin/drelephant/spark/heuristics/MemoryLimitHeuristicTest.java index 8927db2c0..a59b8a3d0 100644 --- a/test/com/linkedin/drelephant/spark/heuristics/MemoryLimitHeuristicTest.java +++ b/test/com/linkedin/drelephant/spark/heuristics/MemoryLimitHeuristicTest.java @@ -4,19 +4,18 @@ import com.linkedin.drelephant.analysis.HeuristicResult; import com.linkedin.drelephant.analysis.Severity; import com.linkedin.drelephant.spark.MockSparkApplicationData; -import com.linkedin.drelephant.spark.SparkApplicationData; -import com.linkedin.drelephant.spark.SparkEnvironmentData; -import com.linkedin.drelephant.spark.SparkExecutorData; -import com.linkedin.drelephant.util.HeuristicConfigurationData; +import com.linkedin.drelephant.spark.data.SparkApplicationData; +import com.linkedin.drelephant.spark.data.SparkEnvironmentData; +import com.linkedin.drelephant.spark.data.SparkExecutorData; +import com.linkedin.drelephant.configurations.heuristic.HeuristicConfigurationData; import com.linkedin.drelephant.util.MemoryFormatUtils; import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; import junit.framework.TestCase; -import static com.linkedin.drelephant.spark.SparkExecutorData.EXECUTOR_DRIVER_NAME; +import static com.linkedin.drelephant.spark.data.SparkExecutorData.EXECUTOR_DRIVER_NAME; import static com.linkedin.drelephant.spark.heuristics.MemoryLimitHeuristic.SPARK_DRIVER_MEMORY; import static com.linkedin.drelephant.spark.heuristics.MemoryLimitHeuristic.SPARK_EXECUTOR_INSTANCES; import static com.linkedin.drelephant.spark.heuristics.MemoryLimitHeuristic.SPARK_EXECUTOR_MEMORY; @@ -25,8 +24,6 @@ /** * This class tests the heuristic rule: MemoryLimitHeuristic - * - * @author yizhou */ public class MemoryLimitHeuristicTest extends TestCase { public void testTotalMemoryRule() { diff --git a/test/com/linkedin/drelephant/util/MemoryFormatUtilsTest.java b/test/com/linkedin/drelephant/util/MemoryFormatUtilsTest.java index 40aef2545..a8cb540e1 100644 --- a/test/com/linkedin/drelephant/util/MemoryFormatUtilsTest.java +++ b/test/com/linkedin/drelephant/util/MemoryFormatUtilsTest.java @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.util; import java.util.Random; @@ -20,9 +21,7 @@ /** - * This class test MemoryFormatUtils/ - * - * @author yizhou + * This class test MemoryFormatUtils */ public class MemoryFormatUtilsTest extends TestCase { public void testBytesToString() { diff --git a/test/com/linkedin/drelephant/util/UtilsTest.java b/test/com/linkedin/drelephant/util/UtilsTest.java index 3ba5d9e54..d804c72c9 100644 --- a/test/com/linkedin/drelephant/util/UtilsTest.java +++ b/test/com/linkedin/drelephant/util/UtilsTest.java @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.linkedin.drelephant.util; import com.linkedin.drelephant.analysis.ApplicationType; From e0bb36c8a03112dae19ce5961ea12c0d179c897c Mon Sep 17 00:00:00 2001 From: akrai Date: Sun, 14 Feb 2016 21:24:36 +0530 Subject: [PATCH 129/272] LIHADOOP-17502: Improve Dr. Elephant's history graph and tooltips RB=661471 G=superfriends-reviewers R=annag,fli,rratti,shanm,viramach A=fli --- app/views/main.scala.html | 2 +- .../results/flowHistoryResults.scala.html | 8 +- .../results/jobHistoryResults.scala.html | 10 +- public/css/main.css | 24 +++-- public/js/flowhistoryform.js | 15 ++- public/js/graphutility.js | 96 ++++++++++--------- public/js/jobhistoryform.js | 14 ++- 7 files changed, 102 insertions(+), 67 deletions(-) diff --git a/app/views/main.scala.html b/app/views/main.scala.html index df70f14bf..633bc93d4 100644 --- a/app/views/main.scala.html +++ b/app/views/main.scala.html @@ -60,7 +60,7 @@ Search Compare Flow History - Job History + Job History Help
diff --git a/app/views/results/flowHistoryResults.scala.html b/app/views/results/flowHistoryResults.scala.html index b982a28cf..eed6c65bc 100644 --- a/app/views/results/flowHistoryResults.scala.html +++ b/app/views/results/flowHistoryResults.scala.html @@ -35,7 +35,7 @@ } @if(results != null && results.nonEmpty) { - @tags.panel(){ Flow History Results: @flowUrl} { + @tags.panel(){ Flow History Results: @flowUrl} { @@ -43,12 +43,14 @@
-
+
+
+
@@ -85,7 +87,7 @@

@result.jobId

@for(heuristicResult <- result.heuristicResults) { -

@heuristicResult.analysisName

+

@heuristicResult.analysisName

}
diff --git a/app/views/results/jobHistoryResults.scala.html b/app/views/results/jobHistoryResults.scala.html index 4e9038d48..54ab26f93 100644 --- a/app/views/results/jobHistoryResults.scala.html +++ b/app/views/results/jobHistoryResults.scala.html @@ -34,19 +34,21 @@ } @if(results != null && results.nonEmpty) { - @tags.panel(){ Job History Results: @jobUrl} { + @tags.panel(){ Job History Results: @jobUrl} {
-
+
+
+
@@ -81,9 +83,9 @@

@heuristicResult.job.jobId

@heuristicResult.analysisName

-
+
- @for(dataArray <- heuristicResult.getDataArray()) { + @for(dataArray <- heuristicResult.getDataArray) { @for(cell <- dataArray) { diff --git a/public/css/main.css b/public/css/main.css index 665a38038..44cd41771 100644 --- a/public/css/main.css +++ b/public/css/main.css @@ -101,18 +101,22 @@ a.list-group-item-danger.active:focus { /* Graph axis */ .axis path, .axis line { - fill: none; - stroke: #777; shape-rendering: crispEdges; } -.y.axis path { - display:none; +.x.axis path { + stroke: rgb(192, 208, 224); } -.tick { - stroke-dasharray: 5, 5, 1, 5; +.y.axis line { + fill:#0077b5; +} +.y.axis path { + display:none; /* Hide the y axis */ } /* Override bootstrap. Horizontal scrollable history table */ +body { + font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Arial Narrow, Helvetica, sans-serif; +} .table-responsive { overflow-x: auto } @@ -140,10 +144,12 @@ a.list-group-item-danger.active:focus { .hasTooltip div { display:none; position:fixed; - background-color:white; + font-size:12px; + color:#CCCCCC; + background:rgba(30, 30, 30, 0.9); + text-align: center; border-radius:5px; - color:black; - border:2px solid gray; + border:1px solid black; padding:5px; overflow:hidden; } diff --git a/public/js/flowhistoryform.js b/public/js/flowhistoryform.js index 470747497..a32bca4db 100644 --- a/public/js/flowhistoryform.js +++ b/public/js/flowhistoryform.js @@ -59,14 +59,19 @@ function getGraphTooltipContent(record, jobDefList) { var details = document.createElement("p"); details.appendChild(document.createTextNode("Job Score = " + record.score)); - details.appendChild(document.createElement("br")); var jobTable = document.createElement("table"); if (record.score != 0) { var jobLimit = 3; - details.appendChild(document.createTextNode("Score Distribution")); details.appendChild(document.createElement("br")); + var tableHeader = document.createElement("th"); + tableHeader.setAttribute("colspan", "2"); + tableHeader.style.padding = "3px"; + tableHeader.style.textAlign = "center"; + tableHeader.appendChild(document.createTextNode("Score Distribution")); + jobTable.appendChild(tableHeader); + var scoreList = []; for (var i = 0; i < record.jobscores.length; i++) { var scoreWidth = record.jobscores[i]["jobscore"] * 100 / record.score; @@ -92,10 +97,11 @@ function getGraphTooltipContent(record, jobDefList) { var jobLink = "/jobhistory?job-url=" + encodeURIComponent(jobDefUrl); var jobRef = document.createElement("a"); jobRef.setAttribute("href", jobLink); - jobRef.appendChild(document.createTextNode("Job " + jobDefList.indexOf(jobDefUrl) + 1)); + jobRef.appendChild(document.createTextNode("Job " + (jobDefList.indexOf(jobDefUrl) + 1))); var tableCell1 = document.createElement("td"); tableCell1.style.padding = "3px"; + tableCell1.style.border = "none"; tableCell1.setAttribute("width", "65px"); tableCell1.appendChild(jobRef); @@ -106,6 +112,7 @@ function getGraphTooltipContent(record, jobDefList) { jobScoreRect.appendChild(document.createTextNode(+width.toFixed(2) + "%")); var tableCell2 = document.createElement("td"); + tableCell2.style.border = "none"; tableCell2.appendChild(jobScoreRect); var tableRow = document.createElement("tr"); @@ -115,7 +122,7 @@ function getGraphTooltipContent(record, jobDefList) { jobTable.appendChild(tableRow); } - jobTable.setAttribute("border", "1px"); + jobTable.setAttribute("border", "2px solid black"); jobTable.style.width = "100%"; } diff --git a/public/js/graphutility.js b/public/js/graphutility.js index 388397646..4e8f4d6d7 100644 --- a/public/js/graphutility.js +++ b/public/js/graphutility.js @@ -33,7 +33,7 @@ function plotter(graphData, jobDefList) { /////////// DEFINE THE GRAPH ATTRIBUTES ///////////// // Define the Margins for the GRAPH Dimensions - var MARGINS = {top: 50, right: 50, bottom: 100, left: 100}, + var MARGINS = {top: 50, right: 50, bottom: 50, left: 50}, WIDTH = graphContainer.style("width").replace("px", ""), HEIGHT = graphContainer.style("height").replace("px", ""), GRAPH_WIDTH = WIDTH - MARGINS.left - MARGINS.right, @@ -59,24 +59,24 @@ function plotter(graphData, jobDefList) { .interpolate('linear'); /* - var customTimeFormat = d3.time.format.multi([ - [".%L", function(d) { return d.getMilliseconds(); }], - [":%S", function(d) { return d.getSeconds(); }], - ["%I:%M", function(d) { return d.getMinutes(); }], - ["%I %p", function(d) { return d.getHours(); }], - ["%a %d", function(d) { return d.getDay() && d.getDate() != 1; }], - ["%b %d", function(d) { return d.getDate() != 1; }], - ["%B", function(d) { return d.getMonth(); }], - ["%Y", function() { return true; }] - ]); - */ + var customTimeFormat = d3.time.format.multi([ + [".%L", function(d) { return d.getMilliseconds(); }], + [":%S", function(d) { return d.getSeconds(); }], + ["%I:%M", function(d) { return d.getMinutes(); }], + ["%I %p", function(d) { return d.getHours(); }], + ["%a %d", function(d) { return d.getDay() && d.getDate() != 1; }], + ["%b %d", function(d) { return d.getDate() != 1; }], + ["%B", function(d) { return d.getMonth(); }], + ["%Y", function() { return true; }] + ]); + */ var customTimeFormat = d3.time.format("%Y-%b-%d"); // x-axis definition var xAxis = d3.svg.axis() .scale(xRange) - .tickSize(3) + .tickSize(0) .orient("bottom") .ticks(9) .tickFormat(customTimeFormat); @@ -84,8 +84,9 @@ function plotter(graphData, jobDefList) { // y-axis definition var yAxis = d3.svg.axis() .scale(yRange) - .tickSize(-1 * (GRAPH_WIDTH)) // Adds horizontal lines in the graph + //.tickSize(-1 * (GRAPH_WIDTH)) // Adds horizontal lines in the graph .ticks(5) // Set 5 levels (5 horizontal lines) + .tickFormat(d3.format("s")) .orient("left"); /////////// ADD CONTENTS TO THE GRAPH CONTAINER ///////////// @@ -94,31 +95,30 @@ function plotter(graphData, jobDefList) { graphContainer.append("svg:g") .attr("class", "x axis") .attr("transform", "translate(0," + (HEIGHT - MARGINS.bottom) + ")") - .call(xAxis); + .call(xAxis) + .selectAll("text") + .attr("y", "10px"); // Space between axis line and tick names // Add the y-axis graphContainer.append("svg:g") .attr("class", "y axis") .attr("transform", "translate(" + (MARGINS.left) + ", 0)") - .call(yAxis); - - // Add label for the x axis - graphContainer.append("svg:text") - .style("text-anchor", "middle") - .attr("transform", "translate(" + ((WIDTH - MARGINS.right + MARGINS.left)/2) + ", " + (HEIGHT - (MARGINS.bottom/2)) + ")") - .text("Executions"); + .call(yAxis) + .selectAll("text") + .attr("fill", "rgb(0, 119, 181)"); // Add label for the y axis graphContainer.append("svg:text") - .style("text-anchor", "middle") - .attr("transform", "translate(" + (MARGINS.left/3) + ", " + (HEIGHT/2) + ")rotate(-90)") - .text("Performance Score"); + .style("font-size", "16px") + .style("fill", "#606060") + .attr("transform", "translate(" + (MARGINS.left/10) + ", " + MARGINS.top/2 + ")") + .text("Performance Score (Lower the better)"); // Add the graph function graphContainer.append("svg:path") .attr("d", lineFunc(graphData)) - .attr("stroke", "black") - .attr("stroke-width", 2) + .attr("stroke", "#0077b5") + .attr("stroke-width", 1.5) .attr("fill", "none"); // Add the small bubble dots on the graph line @@ -126,10 +126,10 @@ function plotter(graphData, jobDefList) { .selectAll("scatter-dots") .data(graphData) .enter().append("svg:circle") - .style({stroke: 'white', fill: 'green'}) + .style({stroke: 'white', fill: '#0077b5'}) .attr("cx", function (d) { return xRange(d.flowtime); } ) .attr("cy", function (d) { return yRange(d.score); } ) - .attr("r", 5); + .attr("r", 4); /////////// THE TOOLTIPS FOR THE GRAPH ///////////// @@ -148,24 +148,26 @@ function plotter(graphData, jobDefList) { var tooltip = graphContainer.append("svg:g"); // Add the highlight bubble - var highlightCircleRad = 10; + var highlightCircleRad = 7; tooltip.append("svg:circle") .attr("stroke", "white") - .attr("fill", "green") + .attr("fill", "#0077b5") .attr("r", highlightCircleRad) .style("display", "none"); // Add the tooltip - var tooltipWidth = 300; + var tooltipWidth = 260; tooltip.append("foreignObject") .attr("width", tooltipWidth + "px") .append("xhtml:body") .attr("id", "graph_tooltip") - .style("background", "rgba(255, 255, 255, 0.9)") - .style("text-align", "left") + .style("background", "rgba(30, 30, 30, 0.9)") + .style("font-size", "12px") + .style("color", "rgb(204, 204, 204)") + .style("text-align", "center") .style("border-radius", "5px") .style("padding", "5px") - .style("border", "2px solid gray"); + .style("border", "1.5px solid black"); var bisectExec = d3.bisector(function(d) { return d.flowtime; }).left; @@ -189,14 +191,22 @@ function plotter(graphData, jobDefList) { graphTooltip.appendChild(getGraphTooltipContent(record, jobDefList)); // Set position of highlighted circle - tooltip.select("circle").style("display", "inline").attr("transform", "translate(" + xRange(record.flowtime) + "," - + yRange(record.score) +")"); - - // Set position of tooltip - tooltip.select("foreignObject").attr("height", tooltip.select("body").style("height")); - tooltip.select("foreignObject").attr("transform", "translate(" + (xRange(record.flowtime) - (tooltipWidth/2)) + "," - + (yRange(record.score) - tooltip.select("body").style("height").replace("px", "") - 10) + ")"); - + tooltip.select("circle") + .style("display", "inline") + .attr("transform", "translate(" + xRange(record.flowtime) + "," + yRange(record.score) +")"); + + // Set position of tooltip. + var x = xRange(record.flowtime) - (tooltipWidth) - 20; + var y = yRange(record.score) - tooltip.select("body").style("height").replace("px", "")/2 - 10; + if (x < MARGINS.left) { + x = xRange(record.flowtime) + 20; + } + tooltip.select("foreignObject") + .attr("height", tooltip.select("body").style("height")); + tooltip.select("foreignObject") + .transition() + .duration(100) + .attr("transform", "translate(" + x + "," + y + ")"); } } diff --git a/public/js/jobhistoryform.js b/public/js/jobhistoryform.js index 309aefd48..687c717df 100644 --- a/public/js/jobhistoryform.js +++ b/public/js/jobhistoryform.js @@ -46,14 +46,19 @@ function getGraphTooltipContent(record, jobDefList) { var details = document.createElement("p"); details.appendChild(document.createTextNode("Job Score = " + record.score)); - details.appendChild(document.createElement("br")); var jobTable = document.createElement("table"); if (record.score != 0) { var jobLimit = 3; - details.appendChild(document.createTextNode("Score Distribution")); details.appendChild(document.createElement("br")); + var tableHeader = document.createElement("th"); + tableHeader.setAttribute("colspan", "2"); + tableHeader.style.padding = "3px"; + tableHeader.style.textAlign = "center"; + tableHeader.appendChild(document.createTextNode("Score Distribution")); + jobTable.appendChild(tableHeader); + var scoreList = []; for (var i = 0; i < record.stagescores.length; i++) { var scoreWidth = record.stagescores[i]["stagescore"] * 100 / record.score; @@ -76,6 +81,7 @@ function getGraphTooltipContent(record, jobDefList) { var tableCell1 = document.createElement("td"); tableCell1.style.padding = "3px"; + tableCell1.style.border = "none"; tableCell1.setAttribute("width", "65px"); tableCell1.appendChild(document.createTextNode("Stage " + (index + 1))); @@ -86,6 +92,7 @@ function getGraphTooltipContent(record, jobDefList) { stageScoreRect.appendChild(document.createTextNode(+width.toFixed(2) + "%")); var tableCell2 = document.createElement("td"); + tableCell2.style.border = "none"; tableCell2.appendChild(stageScoreRect); var tableRow = document.createElement("tr"); @@ -94,7 +101,8 @@ function getGraphTooltipContent(record, jobDefList) { jobTable.appendChild(tableRow); } - jobTable.setAttribute("border", "1px"); + + jobTable.setAttribute("border", "2px solid black"); jobTable.style.width = "100%"; } From 96b7b5e059dfb12e9845adcaf2c892946ea05c2f Mon Sep 17 00:00:00 2001 From: akrai Date: Tue, 16 Feb 2016 14:48:15 +0530 Subject: [PATCH 130/272] LIHADOOP-16100: Dr. Elephant should show timestamp specific to timezones or at least UTC. RB=661685 G=superfriends-reviewers R=annag,fli,rratti,shanm,viramach A=annag,fli --- app/controllers/Application.java | 68 +++++----------------------- app/views/page/searchPage.scala.html | 11 +++-- app/views/tags/formatDate.scala.html | 26 ----------- app/views/tags/jobHeader.scala.html | 9 +++- public/js/searchform.js | 52 +++++++++++++++------ 5 files changed, 63 insertions(+), 103 deletions(-) delete mode 100644 app/views/tags/formatDate.scala.html diff --git a/app/controllers/Application.java b/app/controllers/Application.java index 78e1d7060..e16c89970 100644 --- a/app/controllers/Application.java +++ b/app/controllers/Application.java @@ -37,6 +37,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.TimeZone; import model.JobHeuristicResult; import model.JobResult; import org.apache.http.client.utils.URLEncodedUtils; @@ -269,27 +270,25 @@ private static ExpressionList generateQuery() { // Time Predicates. Both the startedTimeBegin and startedTimeEnd are inclusive in the filter if (isSet(startedTimeBegin)) { - long time = getTimeRange(startedTimeBegin, TIME_RANGE_BEGIN); + long time = parseTime(startedTimeBegin); if (time > 0) { query = query.ge(JobResult.TABLE.ANALYSIS_TIME, time); } } if (isSet(startedTimeEnd)) { - // Here we want to include the end time in the filter - long time = getTimeRange(startedTimeEnd, TIME_RANGE_END); + long time = parseTime(startedTimeEnd); if (time > 0) { query = query.le(JobResult.TABLE.ANALYSIS_TIME, time); } } if (isSet(finishedTimeBegin)) { - long time = getTimeRange(finishedTimeBegin, TIME_RANGE_BEGIN); + long time = parseTime(finishedTimeBegin); if (time > 0) { query = query.ge(JobResult.TABLE.ANALYSIS_TIME, time); } } if (isSet(finishedTimeEnd)) { - // Here we want to include the end time in the filter - long time = getTimeRange(finishedTimeEnd, TIME_RANGE_END); + long time = parseTime(finishedTimeEnd); if (time > 0) { query = query.le(JobResult.TABLE.ANALYSIS_TIME, time); } @@ -535,62 +534,19 @@ private static void fillHelpPages() { } /** - * Parse the string for time and return the epoch value depending on - * if the time represents the beginning or end of a range. + * Parse the string for time in long * - * The String can an epoch value or a Date value. - * - * For time in epoch value, return the String after parsing it to long. - * For time in MM/dd/yyyy format, parse the time and return the epoch - * value such that we include the entire day in the filter - * - * @param time The String to be parsed - * @param timeEnd Range Indicator. true if it is the range End(to), false indicates the range Beginning(from) - * @return The epoch value + * @param time The string to be parsed + * @return the epoch value */ - private static long getTimeRange(String time, boolean timeEnd) { + private static long parseTime(String time) { long unixTime = 0; - if (isUnixTimeStamp(time)) { - unixTime = Long.parseLong(time); - } else { - SimpleDateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy"); - try { - if (timeEnd) { - Date date = dateFormat.parse(time); - Calendar c = Calendar.getInstance(); - c.setTime(date); - c.add(Calendar.DATE, 1); - date = c.getTime(); - unixTime = date.getTime() - 1; - } else { - Date date = dateFormat.parse(time); - unixTime = date.getTime(); - } - } catch (ParseException e) { - logger.error("Error while parsing time. " + time + " is an invalid date. Filter not applied."); - } - } - return unixTime; - } - - /** - * Checks if the String represents a time in epoch. - * - * @param unixTime The string to be verified. - * @return true if the String contains a valid unix timestamp, false otherwise. - */ - private static boolean isUnixTimeStamp(String unixTime) { - boolean valid = false; try { - long time = Long.parseLong(unixTime); - if (time > 0 && time < System.currentTimeMillis()) { - valid = true; - } + unixTime = Long.parseLong(time); } catch (NumberFormatException ex) { - valid = false; + // return 0 } - - return valid; + return unixTime; } /** diff --git a/app/views/page/searchPage.scala.html b/app/views/page/searchPage.scala.html index 7cc48dc8e..faf4b3847 100644 --- a/app/views/page/searchPage.scala.html +++ b/app/views/page/searchPage.scala.html @@ -88,13 +88,16 @@
- + + +
- + +
- + } } @@ -106,4 +109,4 @@ @tags.pagination(paginationStats, routes.Application.search()) } } -} +} \ No newline at end of file diff --git a/app/views/tags/formatDate.scala.html b/app/views/tags/formatDate.scala.html deleted file mode 100644 index 4fd2052ac..000000000 --- a/app/views/tags/formatDate.scala.html +++ /dev/null @@ -1,26 +0,0 @@ -@* -* Copyright 2015 LinkedIn Corp. -* -* 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. -*@ - -@(time: Long) -@{ - import java.sql.Timestamp - import java.text.DateFormat - import java.util.TimeZone - - val ts = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.LONG, Locale.ENGLISH) - ts.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles")) - ts.format(new Timestamp(time)) -} \ No newline at end of file diff --git a/app/views/tags/jobHeader.scala.html b/app/views/tags/jobHeader.scala.html index f6a042a3d..56ac8d483 100644 --- a/app/views/tags/jobHeader.scala.html +++ b/app/views/tags/jobHeader.scala.html @@ -28,6 +28,11 @@

[@result.username] [@result.jobType] @result.jobId

-
@(tags.formatDate(result.analysisTime))
+
@result.jobName
-

\ No newline at end of file +

+ + \ No newline at end of file diff --git a/public/js/searchform.js b/public/js/searchform.js index 92a5e268f..f7a51ea47 100644 --- a/public/js/searchform.js +++ b/public/js/searchform.js @@ -17,6 +17,7 @@ $(document).ready(function(){ var form = $("#search-form"); + var formSubmit = $("#submit-button"); var jobid = $("#form-job-id"); var flowurl = $("#form-flow-url"); @@ -27,11 +28,13 @@ $(document).ready(function(){ var severity = $("#form-severity"); var analysis = $("#form-analysis"); var datetimeEnable = $("#form-datetime-enable"); - var startDate = $("#form-finished-time-begin"); - var endDate = $("#form-finished-time-end"); + var finishTimeBeginDate = $("#form-finished-time-begin-date"); + var finishTimeEndDate = $("#form-finished-time-end-date"); + var finishTimeBeginTimestamp = $("#form-finished-time-begin"); + var finishTimeEndTimestamp = $("#form-finished-time-end"); - startDate.datepicker(); - endDate.datepicker(); + finishTimeBeginDate.datepicker(); + finishTimeEndDate.datepicker(); var updateForm = function(){ if(jobid.val()) { @@ -43,8 +46,8 @@ $(document).ready(function(){ jobtypeEnable.prop('disabled', true); severityEnable.prop('disabled', true); datetimeEnable.prop('disabled', true); - startDate.prop('disabled', true); - endDate.prop('disabled', true); + finishTimeBeginDate.prop('disabled', true); + finishTimeEndDate.prop('disabled', true); } else if(flowurl.val()) { jobid.prop('disabled', true); user.prop('disabled', true); @@ -54,8 +57,8 @@ $(document).ready(function(){ jobtypeEnable.prop('disabled', true); severityEnable.prop('disabled', true); datetimeEnable.prop('disabled', true); - startDate.prop('disabled', true); - endDate.prop('disabled', true); + finishTimeBeginDate.prop('disabled', true); + finishTimeEndDate.prop('disabled', true); } else{ jobid.prop('disabled', false); @@ -79,12 +82,12 @@ $(document).ready(function(){ analysis.prop('disabled', true); } if(datetimeEnable.prop('checked')){ - startDate.prop('disabled', false); - endDate.prop('disabled', false); + finishTimeBeginDate.prop('disabled', false); + finishTimeEndDate.prop('disabled', false); } else { - startDate.prop('disabled', true); - endDate.prop('disabled', true); + finishTimeBeginDate.prop('disabled', true); + finishTimeEndDate.prop('disabled', true); } } } @@ -94,11 +97,30 @@ $(document).ready(function(){ severityEnable.change(updateForm); datetimeEnable.change(updateForm); - form.submit(function(event){ - var data = form.serialize(); - localStorage.setItem('search-form', data); + formSubmit.click(function() { + + var formParams = form.serialize(); + + // Convert the dates from user time-zone to epoch timestamp + if(datetimeEnable.prop('checked')) { + var dateBegin = finishTimeBeginDate.val(); + if (dateBegin !== '') { + finishTimeBeginTimestamp.val(new Date(dateBegin).getTime()); + finishTimeBeginDate.val(''); // Remove this parameter from appearing in url + } + var dateEnd = finishTimeEndDate.val(); + if (dateEnd !== '') { + finishTimeEndTimestamp.val(new Date(dateEnd).getTime()); + finishTimeEndDate.val(''); // Remove this parameter from appearing in url + } + } + + // Cache the search parameters + localStorage.setItem('search-form', formParams); //Remove useless fields from the URL form.find('input[name]').filter(function(){return !$(this).val();}).attr('name', ''); + + form.submit(); }); try { From 30ca499639a64342c57713f59598c3c3ae4bb9a6 Mon Sep 17 00:00:00 2001 From: Fangshi Li Date: Mon, 16 Nov 2015 14:02:09 -0800 Subject: [PATCH 131/272] LIHADOOP-15268: Task sampling in Dr. Elephant --- .../linkedin/drelephant/ElephantRunner.java | 2 +- .../mapreduce/MapReduceFetcherHadoop2.java | 36 ++++++++++++++----- .../mapreduce/data/MapReduceTaskData.java | 36 ++++++++++++++++--- .../heuristics/GenericDataSkewHeuristic.java | 20 +++++++---- .../heuristics/GenericGCHeuristic.java | 6 ++-- .../heuristics/GenericMemoryHeuristic.java | 14 ++++---- .../heuristics/MapperSpeedHeuristic.java | 3 +- .../heuristics/MapperSpillHeuristic.java | 7 ++-- .../heuristics/MapperTimeHeuristic.java | 5 +-- .../heuristics/ReducerTimeHeuristic.java | 2 +- .../heuristics/ShuffleSortHeuristic.java | 2 +- 11 files changed, 97 insertions(+), 36 deletions(-) diff --git a/app/com/linkedin/drelephant/ElephantRunner.java b/app/com/linkedin/drelephant/ElephantRunner.java index 879a9f1a5..0002aa5e9 100644 --- a/app/com/linkedin/drelephant/ElephantRunner.java +++ b/app/com/linkedin/drelephant/ElephantRunner.java @@ -46,7 +46,7 @@ public class ElephantRunner implements Runnable { private static final Logger logger = Logger.getLogger(ElephantRunner.class); private static final long WAIT_INTERVAL = 60 * 1000; // Interval between fetches and retries - private static final int EXECUTOR_NUM = 5; // The number of executor threads to analyse the jobs + private static final int EXECUTOR_NUM = 3; // The number of executor threads to analyse the jobs private AtomicBoolean _running = new AtomicBoolean(true); private long lastRun; diff --git a/app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop2.java b/app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop2.java index ab25f1ba4..b6a87430b 100644 --- a/app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop2.java +++ b/app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop2.java @@ -31,6 +31,7 @@ import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Properties; import java.util.Random; @@ -51,6 +52,7 @@ */ public class MapReduceFetcherHadoop2 implements ElephantFetcher { private static final Logger logger = Logger.getLogger(ElephantFetcher.class); + private static final int MAX_SAMPLE_SIZE = 200; // We provide one minute job fetch delay due to the job sending lag from AM/NM to JobHistoryServer HDFS private URLFactory _urlFactory; @@ -295,18 +297,36 @@ private void getTaskDataAll(URL url, String jobId, List mappe String attemptId = task.get("successfulAttempt").getValueAsText(); boolean isMapper = task.get("type").getValueAsText().equals("MAP"); - URL taskCounterURL = getTaskCounterURL(jobId, taskId); + if (isMapper) { + mapperList.add(new MapReduceTaskData(taskId, attemptId)); + } else { + reducerList.add(new MapReduceTaskData(taskId, attemptId)); + } + } + + getTaskData(jobId, mapperList); + getTaskData(jobId, reducerList); + } + + private void getTaskData(String jobId, List taskList) throws IOException, AuthenticationException { + if (taskList.size() > MAX_SAMPLE_SIZE) { + logger.info(jobId + " needs sampling."); + Collections.shuffle(taskList); + } + + int sampleSize = Math.min(taskList.size(), MAX_SAMPLE_SIZE); + + for(int i=0; i < sampleSize; i++) { + MapReduceTaskData data = taskList.get(i); + + URL taskCounterURL = getTaskCounterURL(jobId, data.getTaskId()); MapReduceCounterData taskCounter = getTaskCounter(taskCounterURL); - URL taskAttemptURL = getTaskAttemptURL(jobId, taskId, attemptId); + URL taskAttemptURL = getTaskAttemptURL(jobId, data.getTaskId(), data.getAttemptId()); long[] taskExecTime = getTaskExecTime(taskAttemptURL); - MapReduceTaskData taskData = new MapReduceTaskData(taskCounter, taskExecTime); - if (isMapper) { - mapperList.add(taskData); - } else { - reducerList.add(taskData); - } + data.setCounter(taskCounter); + data.setTime(taskExecTime); } } diff --git a/app/com/linkedin/drelephant/mapreduce/data/MapReduceTaskData.java b/app/com/linkedin/drelephant/mapreduce/data/MapReduceTaskData.java index dee7de705..d664603f6 100644 --- a/app/com/linkedin/drelephant/mapreduce/data/MapReduceTaskData.java +++ b/app/com/linkedin/drelephant/mapreduce/data/MapReduceTaskData.java @@ -22,23 +22,43 @@ */ public class MapReduceTaskData { private MapReduceCounterData _counterHolder; + private String _taskId; + // The successful attempt id + private String _attemptId; private long _totalTimeMs = 0; private long _shuffleTimeMs = 0; private long _sortTimeMs = 0; - private boolean _timed = false; + private boolean _sampled = false; public MapReduceTaskData(MapReduceCounterData counterHolder, long[] time) { this._counterHolder = counterHolder; this._totalTimeMs = time[0]; this._shuffleTimeMs = time[1]; this._sortTimeMs = time[2]; - this._timed = true; + this._sampled = true; } public MapReduceTaskData(MapReduceCounterData counterHolder) { this._counterHolder = counterHolder; } + public MapReduceTaskData(String taskId, String taskAttemptId) { + this._taskId = taskId; + this._attemptId = taskAttemptId; + } + + public void setCounter(MapReduceCounterData counterHolder) { + this._counterHolder = counterHolder; + this._sampled = true; + } + + public void setTime(long[] time) { + this._totalTimeMs = time[0]; + this._shuffleTimeMs = time[1]; + this._sortTimeMs = time[2]; + this._sampled = true; + } + public MapReduceCounterData getCounters() { return _counterHolder; } @@ -59,7 +79,15 @@ public long getSortTimeMs() { return _sortTimeMs; } - public boolean timed() { - return _timed; + public boolean isSampled() { + return _sampled; + } + + public String getTaskId() { + return _taskId; + } + + public String getAttemptId() { + return _attemptId; } } diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/GenericDataSkewHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericDataSkewHeuristic.java index a8b2c4e9f..018d83b4d 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/GenericDataSkewHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericDataSkewHeuristic.java @@ -16,6 +16,7 @@ package com.linkedin.drelephant.mapreduce.heuristics; +import com.google.common.primitives.Longs; import com.linkedin.drelephant.analysis.HDFSContext; import com.linkedin.drelephant.analysis.Heuristic; import com.linkedin.drelephant.analysis.HeuristicResult; @@ -28,7 +29,10 @@ import com.linkedin.drelephant.configurations.heuristic.HeuristicConfigurationData; import com.linkedin.drelephant.util.Utils; import java.util.Arrays; +import java.util.ArrayList; import java.util.Map; +import java.util.List; + import org.apache.commons.io.FileUtils; import org.apache.log4j.Logger; @@ -113,14 +117,18 @@ public HeuristicResult apply(MapReduceApplicationData data) { MapReduceTaskData[] tasks = getTasks(data); //Gather data - long[] inputBytes = new long[tasks.length]; + List inputBytes = new ArrayList(); for (int i = 0; i < tasks.length; i++) { - inputBytes[i] = tasks[i].getCounters().get(_counterName); + if (tasks[i].isSampled()) { + inputBytes.add(tasks[i].getCounters().get(_counterName)); + } } - //Analyze data - long[][] groups = Statistics.findTwoGroups(inputBytes); + // Ratio of total tasks / sampled tasks + double scale = ((double)tasks.length) / inputBytes.size(); + //Analyze data. TODO: This is a temp fix. findTwogroups should support list as input + long[][] groups = Statistics.findTwoGroups(Longs.toArray(inputBytes)); long avg1 = Statistics.average(groups[0]); long avg2 = Statistics.average(groups[1]); @@ -140,8 +148,8 @@ public HeuristicResult apply(MapReduceApplicationData data) { HeuristicResult result = new HeuristicResult(_heuristicName, severity); result.addDetail("Number of tasks", Integer.toString(tasks.length)); - result.addDetail("Group A", groups[0].length + " tasks @ " + FileUtils.byteCountToDisplaySize(avg1) + " avg"); - result.addDetail("Group B", groups[1].length + " tasks @ " + FileUtils.byteCountToDisplaySize(avg2) + " avg"); + result.addDetail("Group A", Math.round(groups[0].length*scale) + " tasks @ " + FileUtils.byteCountToDisplaySize(avg1) + " avg"); + result.addDetail("Group B", Math.round(groups[1].length*scale) + " tasks @ " + FileUtils.byteCountToDisplaySize(avg2) + " avg"); return result; } diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/GenericGCHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericGCHeuristic.java index 3c30a24be..758286877 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/GenericGCHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericGCHeuristic.java @@ -101,11 +101,11 @@ public HeuristicResult apply(MapReduceApplicationData data) { List runtimesMs = new ArrayList(); for (MapReduceTaskData task : tasks) { - if (task.timed()) { + if (task.isSampled()) { runtimesMs.add(task.getTotalRunTimeMs()); + gcMs.add(task.getCounters().get(MapReduceCounterData.CounterName.GC_MILLISECONDS)); + cpuMs.add(task.getCounters().get(MapReduceCounterData.CounterName.CPU_MILLISECONDS)); } - gcMs.add(task.getCounters().get(MapReduceCounterData.CounterName.GC_MILLISECONDS)); - cpuMs.add(task.getCounters().get(MapReduceCounterData.CounterName.CPU_MILLISECONDS)); } long avgRuntimeMs = Statistics.average(runtimesMs); diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/GenericMemoryHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericMemoryHeuristic.java index 997bbb01a..8ad62894c 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/GenericMemoryHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericMemoryHeuristic.java @@ -125,15 +125,15 @@ public HeuristicResult apply(MapReduceApplicationData data) { long taskPMin = Long.MAX_VALUE; long taskPMax = 0; for (MapReduceTaskData task : tasks) { - long taskPMem = task.getCounters().get(MapReduceCounterData.CounterName.PHYSICAL_MEMORY_BYTES); - long taskVMem = task.getCounters().get(MapReduceCounterData.CounterName.VIRTUAL_MEMORY_BYTES); - taskPMems.add(taskPMem); - taskPMin = Math.min(taskPMin, taskPMem); - taskPMax = Math.max(taskPMax, taskPMem); - if (task.timed()) { + if (task.isSampled()) { runtimesMs.add(task.getTotalRunTimeMs()); + long taskPMem = task.getCounters().get(MapReduceCounterData.CounterName.PHYSICAL_MEMORY_BYTES); + long taskVMem = task.getCounters().get(MapReduceCounterData.CounterName.VIRTUAL_MEMORY_BYTES); + taskPMems.add(taskPMem); + taskPMin = Math.min(taskPMin, taskPMem); + taskPMax = Math.max(taskPMax, taskPMem); + taskVMems.add(taskVMem); } - taskVMems.add(taskVMem); } if(taskPMin == Long.MAX_VALUE) { diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristic.java index 694c8c9df..4240ff575 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristic.java @@ -103,7 +103,8 @@ public HeuristicResult apply(MapReduceApplicationData data) { List runtimesMs = new ArrayList(); for (MapReduceTaskData task : tasks) { - if (task.timed()) { + + if (task.isSampled()) { long inputBytes = task.getCounters().get(MapReduceCounterData.CounterName.HDFS_BYTES_READ); long runtimeMs = task.getTotalRunTimeMs(); inputByteSizes.add(inputBytes); diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristic.java index 1685e9ed0..c02a6aac0 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristic.java @@ -88,8 +88,11 @@ public HeuristicResult apply(MapReduceApplicationData data) { double ratioSpills = 0.0; for (MapReduceTaskData task : tasks) { - totalSpills += task.getCounters().get(MapReduceCounterData.CounterName.SPILLED_RECORDS); - totalOutputRecords += task.getCounters().get(MapReduceCounterData.CounterName.MAP_OUTPUT_RECORDS); + + if (task.isSampled()) { + totalSpills += task.getCounters().get(MapReduceCounterData.CounterName.SPILLED_RECORDS); + totalOutputRecords += task.getCounters().get(MapReduceCounterData.CounterName.MAP_OUTPUT_RECORDS); + } } //If both totalSpills and totalOutputRecords are zero then set ratioSpills to zero. diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristic.java index 2133c828e..90a680fde 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristic.java @@ -113,8 +113,9 @@ public HeuristicResult apply(MapReduceApplicationData data) { long taskMaxMs = 0; for (MapReduceTaskData task : tasks) { - inputBytes.add(task.getCounters().get(MapReduceCounterData.CounterName.HDFS_BYTES_READ)); - if (task.timed()) { + + if (task.isSampled()) { + inputBytes.add(task.getCounters().get(MapReduceCounterData.CounterName.HDFS_BYTES_READ)); long taskTime = task.getTotalRunTimeMs(); runtimesMs.add(taskTime); taskMinMs = Math.min(taskMinMs, taskTime); diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristic.java index 903dffd05..e0f7a87a4 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristic.java @@ -110,7 +110,7 @@ public HeuristicResult apply(MapReduceApplicationData data) { long taskMaxMs = 0; for (MapReduceTaskData task : tasks) { - if (task.timed()) { + if (task.isSampled()) { long taskTime = task.getTotalRunTimeMs(); runTimesMs.add(taskTime); taskMinMs = Math.min(taskMinMs, taskTime); diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristic.java index 0fb241656..5f60dfccd 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristic.java @@ -98,7 +98,7 @@ public HeuristicResult apply(MapReduceApplicationData data) { List sortTimeMs = new ArrayList(); for (MapReduceTaskData task : tasks) { - if (task.timed()) { + if (task.isSampled()) { execTimeMs.add(task.getCodeExecutionTimeMs()); shuffleTimeMs.add(task.getShuffleTimeMs()); sortTimeMs.add(task.getSortTimeMs()); From cee14d38f14ef9ba6909f0f66a67ce82da4c678c Mon Sep 17 00:00:00 2001 From: akrai Date: Thu, 25 Feb 2016 19:02:07 +0530 Subject: [PATCH 132/272] LIHADOOP-17669: Update Notice and copyright year on Dr. Elephant RB=668782 G=superfriends-reviewers R=annag,fli,shanm,viramach A=fli,shanm --- LICENSE | 2 +- NOTICE | 478 ++---------------- README.md | 2 +- app-conf/CounterPublisherConf.xml | 2 +- app-conf/FetcherConf.xml | 2 +- app-conf/HeuristicConf.xml | 2 +- app-conf/JobTypeConf.xml | 2 +- app/Global.java | 2 +- .../linkedin/drelephant/DaliMetricsAPI.java | 2 +- app/com/linkedin/drelephant/DrElephant.java | 2 +- .../linkedin/drelephant/ElephantContext.java | 2 +- .../linkedin/drelephant/ElephantRunner.java | 2 +- .../drelephant/analysis/AnalyticJob.java | 2 +- .../analysis/AnalyticJobGenerator.java | 2 +- .../analysis/AnalyticJobGeneratorHadoop2.java | 2 +- .../drelephant/analysis/ApplicationType.java | 2 +- .../drelephant/analysis/ElephantFetcher.java | 2 +- .../drelephant/analysis/HDFSContext.java | 2 +- .../analysis/HadoopApplicationData.java | 2 +- .../analysis/HadoopSystemContext.java | 2 +- .../drelephant/analysis/Heuristic.java | 2 +- .../drelephant/analysis/HeuristicResult.java | 2 +- .../linkedin/drelephant/analysis/JobType.java | 2 +- .../drelephant/analysis/Severity.java | 2 +- .../fetcher/FetcherConfiguration.java | 2 +- .../fetcher/FetcherConfigurationData.java | 2 +- .../heuristic/HeuristicConfiguration.java | 2 +- .../heuristic/HeuristicConfigurationData.java | 2 +- .../configurations/jobtype/JobTypeConf.java | 2 +- .../mapreduce/MapReduceFetcherHadoop2.java | 2 +- .../data/MapReduceApplicationData.java | 2 +- .../mapreduce/data/MapReduceCounterData.java | 2 +- .../mapreduce/data/MapReduceTaskData.java | 2 +- .../heuristics/ExceptionHeuristic.java | 2 +- .../heuristics/GenericDataSkewHeuristic.java | 2 +- .../heuristics/GenericGCHeuristic.java | 2 +- .../heuristics/GenericMemoryHeuristic.java | 2 +- .../heuristics/JobQueueLimitHeuristic.java | 2 +- .../heuristics/MapperDataSkewHeuristic.java | 2 +- .../heuristics/MapperGCHeuristic.java | 2 +- .../heuristics/MapperMemoryHeuristic.java | 2 +- .../heuristics/MapperSpeedHeuristic.java | 2 +- .../heuristics/MapperSpillHeuristic.java | 2 +- .../heuristics/MapperTimeHeuristic.java | 2 +- .../heuristics/ReducerDataSkewHeuristic.java | 2 +- .../heuristics/ReducerGCHeuristic.java | 2 +- .../heuristics/ReducerMemoryHeuristic.java | 2 +- .../heuristics/ReducerTimeHeuristic.java | 2 +- .../heuristics/ShuffleSortHeuristic.java | 2 +- .../linkedin/drelephant/math/Statistics.java | 2 +- .../drelephant/notifications/EmailThread.java | 2 +- .../drelephant/security/HadoopSecurity.java | 2 +- .../spark/data/SparkApplicationData.java | 2 +- .../spark/data/SparkEnvironmentData.java | 2 +- .../spark/data/SparkExecutorData.java | 2 +- .../spark/data/SparkGeneralData.java | 2 +- .../spark/data/SparkJobProgressData.java | 2 +- .../spark/data/SparkStorageData.java | 2 +- .../BestPropertiesConventionHeuristic.java | 2 +- .../heuristics/EventLogLimitHeuristic.java | 2 +- .../heuristics/ExecutorLoadHeuristic.java | 2 +- .../spark/heuristics/JobRuntimeHeuristic.java | 2 +- .../heuristics/MemoryLimitHeuristic.java | 2 +- .../heuristics/StageRuntimeHeuristic.java | 2 +- .../drelephant/util/InfoExtractor.java | 2 +- .../drelephant/util/MemoryFormatUtils.java | 2 +- app/com/linkedin/drelephant/util/Utils.java | 2 +- .../drelephant/util/YarnURLUtils.java | 2 +- app/controllers/Application.java | 2 +- app/controllers/PaginationStats.java | 2 +- app/controllers/QueryHandler.java | 2 +- app/model/JobHeuristicResult.java | 2 +- app/model/JobResult.java | 2 +- .../deploy/history/SparkDataCollection.scala | 2 +- .../spark/deploy/history/SparkFSFetcher.scala | 2 +- .../StorageStatusTrackingListener.scala | 2 +- app/views/emailcritical.scala.html | 2 +- app/views/help/helpNoData.scala.html | 2 +- .../help/mapreduce/helpException.scala.html | 2 +- app/views/help/mapreduce/helpGC.scala.html | 2 +- .../mapreduce/helpJobQueueLimit.scala.html | 2 +- .../mapreduce/helpMapperDataSkew.scala.html | 2 +- .../mapreduce/helpMapperMemory.scala.html | 2 +- .../help/mapreduce/helpMapperSpeed.scala.html | 2 +- .../help/mapreduce/helpMapperSpill.scala.html | 2 +- .../help/mapreduce/helpMapperTime.scala.html | 2 +- .../help/mapreduce/helpMemory.scala.html | 2 +- .../mapreduce/helpReducerDataSkew.scala.html | 2 +- .../mapreduce/helpReducerMemory.scala.html | 2 +- .../help/mapreduce/helpReducerTime.scala.html | 2 +- .../help/mapreduce/helpShuffleSort.scala.html | 2 +- .../help/spark/helpBestProperties.scala.html | 2 +- .../help/spark/helpEventLogLimit.scala.html | 2 +- .../help/spark/helpExecutorLoad.scala.html | 2 +- .../help/spark/helpJobRuntime.scala.html | 2 +- .../help/spark/helpMemoryLimit.scala.html | 2 +- .../help/spark/helpStageRuntime.scala.html | 2 +- app/views/main.scala.html | 2 +- app/views/page/comparePage.scala.html | 2 +- app/views/page/flowHistoryPage.scala.html | 2 +- app/views/page/helpPage.scala.html | 2 +- app/views/page/homePage.scala.html | 2 +- app/views/page/jobHistoryPage.scala.html | 2 +- app/views/page/searchPage.scala.html | 2 +- app/views/results/compareResults.scala.html | 2 +- app/views/results/flowDetails.scala.html | 2 +- .../results/flowHistoryResults.scala.html | 2 +- app/views/results/jobDetails.scala.html | 2 +- .../results/jobHistoryResults.scala.html | 2 +- app/views/results/searchResults.scala.html | 2 +- app/views/tags/column.scala.html | 2 +- app/views/tags/jobHeader.scala.html | 2 +- app/views/tags/jobSummary.scala.html | 2 +- app/views/tags/pagination.scala.html | 2 +- app/views/tags/panel.scala.html | 2 +- build.sbt | 2 +- compile.sh | 2 +- conf/application.conf | 2 +- conf/evolutions/default/1.sql | 2 +- conf/evolutions/default/2.sql | 2 +- conf/evolutions/default/3.sql | 2 +- conf/evolutions/default/4.sql | 2 +- conf/evolutions/default/5.sql | 2 +- conf/evolutions/default/6.sql | 2 +- conf/evolutions/default/7.sql | 2 +- conf/evolutions/default/8.sql | 2 +- conf/log4j.properties | 2 +- conf/routes | 2 +- project/build.properties | 2 +- project/plugins.sbt | 2 +- public/css/main.css | 2 +- public/js/flowhistoryform.js | 2 +- public/js/graphutility.js | 2 +- public/js/jobhistoryform.js | 2 +- public/js/searchform.js | 2 +- scripts/start.sh | 2 +- scripts/stop.sh | 2 +- .../JobQueueLimitHeuristicTest.java | 2 +- .../MapperDataSkewHeuristicTest.java | 2 +- .../heuristics/MapperGCHeuristicTest.java | 2 +- .../heuristics/MapperMemoryHeuristicTest.java | 2 +- .../heuristics/MapperSpeedHeuristicTest.java | 2 +- .../heuristics/MapperSpillHeuristicTest.java | 2 +- .../heuristics/MapperTimeHeuristicTest.java | 2 +- .../ReducerDataSkewHeuristicTest.java | 2 +- .../heuristics/ReducerGCHeuristicTest.java | 2 +- .../ReducerMemoryHeuristicTest.java | 2 +- .../heuristics/ReducerTimeHeuristicTest.java | 2 +- .../heuristics/ShuffleSortHeuristicTest.java | 2 +- .../spark/MockSparkApplicationData.java | 2 +- .../util/MemoryFormatUtilsTest.java | 2 +- .../linkedin/drelephant/util/UtilsTest.java | 2 +- 152 files changed, 202 insertions(+), 578 deletions(-) diff --git a/LICENSE b/LICENSE index 5e8a14cef..7a4a3ea24 100644 --- a/LICENSE +++ b/LICENSE @@ -187,7 +187,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2015 LinkedIn Corp. All rights reserved. + Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/NOTICE b/NOTICE index 56d05a9fb..da93bf945 100644 --- a/NOTICE +++ b/NOTICE @@ -1,4 +1,4 @@ -Copyright 2015 LinkedIn Corp. +Copyright 2016 LinkedIn Corp. 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 @@ -11,454 +11,78 @@ 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. +------------------------------------------------------------------------------ -This product includes software developed at -The Apache Software Foundation (http://www.apache.org/) - -======================================================================== -Common Development and Distribution License 1.0 -======================================================================== - -The following components are provided under the Common Development and Distribution License 1.0. See project link for details. - - (CDDL 1.0) Glassfish Jasper (org.mortbay.jetty:jsp-2.1:6.1.14 - http://jetty.mortbay.org/project/modules/jsp-2.1) - (CDDL 1.0) Servlet Specification 2.5 API (org.mortbay.jetty:servlet-api-2.5:6.1.14 - http://jetty.mortbay.org/project/modules/servlet-api-2.5) - (COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0) (GNU General Public Library) Streaming API for XML (javax.xml.stream:stax-api:1.0-2 - no url defined) - (COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0) JavaBeans Activation Framework (JAF) (javax.activation:activation:1.1 - http://java.sun.com/products/javabeans/jaf/index.jsp) - -======================================================================== -Common Development and Distribution License 1.1 -======================================================================== - -The following components are provided under the Common Development and Distribution License 1.1. See project link for details. - - (CDDL 1.1) (GPL2 w/ CPE) JAXB API bundle for GlassFish V3 (javax.xml.bind:jaxb-api:2.2.2 - https://jaxb.dev.java.net/) - (CDDL 1.1) (GPL2 w/ CPE) JAXB RI (com.sun.xml.bind:jaxb-impl:2.2.3-1 - http://jaxb.java.net/) - (CDDL 1.1) (GPL2 w/ CPE) jersey-core (com.sun.jersey:jersey-core:1.9 - https://jersey.java.net/jersey-core/) - (CDDL 1.1) (GPL2 w/ CPE) jersey-guice (com.sun.jersey.contribs:jersey-guice:1.9 - https://jersey.java.net/jersey-contribs/jersey-guice/) - (CDDL 1.1) (GPL2 w/ CPE) jersey-json (com.sun.jersey:jersey-json:1.9 - https://jersey.java.net/jersey-json/) - (CDDL 1.1) (GPL2 w/ CPE) jersey-server (com.sun.jersey:jersey-server:1.9 - https://jersey.java.net/jersey-server/) - - -======================================================================== -NOTICE files -======================================================================== - -The following NOTICEs pertain to software distributed with this project. - -Apache Avro -Copyright 2009-2013 The Apache Software Foundation - -Apache Commons BeanUtils -Copyright 2000-2008 The Apache Software Foundation - -Apache Commons CLI -Copyright 2001-2009 The Apache Software Foundation - -Apache Commons Codec -Copyright 2002-2011 The Apache Software Foundation - --------------------------------------------------------------------------------- -src/test/org/apache/commons/codec/language/DoubleMetaphoneTest.java contains -test data from http://aspell.sourceforge.net/test/batch0.tab. - -Copyright (C) 2002 Kevin Atkinson (kevina@gnu.org). Verbatim copying -and distribution of this entire article is permitted in any medium, -provided this notice is preserved. --------------------------------------------------------------------------------- - -Apache Commons Collections -Copyright 2001-2008 The Apache Software Foundation - -Apache Commons Compress -Copyright 2002-2012 The Apache Software Foundation - -Apache Commons Configuration -Copyright 2001-2008 The Apache Software Foundation - -Apache Commons Daemon -Copyright 1999-2013 The Apache Software Foundation - -Apache Jakarta Commons Digester -Copyright 2001-2006 The Apache Software Foundation - -Apache Jakarta HttpClient -Copyright 1999-2007 The Apache Software Foundation - -Apache Commons IO -Copyright 2002-2012 The Apache Software Foundation - -Apache Commons Lang -Copyright 2001-2011 The Apache Software Foundation - -Apache Commons Logging -Copyright 2003-2013 The Apache Software Foundation - -Apache Commons Math -Copyright 2001-2012 The Apache Software Foundation - -=============================================================================== - -The BracketFinder (package org.apache.commons.math3.optimization.univariate) -and PowellOptimizer (package org.apache.commons.math3.optimization.general) -classes are based on the Python code in module "optimize.py" (version 0.5) -developed by Travis E. Oliphant for the SciPy library (http://www.scipy.org/) -Copyright © 2003-2009 SciPy Developers. -=============================================================================== - -The LinearConstraint, LinearObjectiveFunction, LinearOptimizer, -RelationShip, SimplexSolver and SimplexTableau classes in package -org.apache.commons.math3.optimization.linear include software developed by -Benjamin McCann (http://www.benmccann.com) and distributed with -the following copyright: Copyright 2009 Google Inc. -=============================================================================== - -This product includes software developed by the -University of Chicago, as Operator of Argonne National -Laboratory. -The LevenbergMarquardtOptimizer class in package -org.apache.commons.math3.optimization.general includes software -translated from the lmder, lmpar and qrsolv Fortran routines -from the Minpack package -Minpack Copyright Notice (1999) University of Chicago. All rights reserved -=============================================================================== - -The GraggBulirschStoerIntegrator class in package -org.apache.commons.math3.ode.nonstiff includes software translated -from the odex Fortran routine developed by E. Hairer and G. Wanner. -Original source copyright: -Copyright (c) 2004, Ernst Hairer -=============================================================================== - -The EigenDecompositionImpl class in package -org.apache.commons.math3.linear includes software translated -from some LAPACK Fortran routines. Original source copyright: -Copyright (c) 1992-2008 The University of Tennessee. All rights reserved. -=============================================================================== - -The MersenneTwister class in package org.apache.commons.math3.random -includes software translated from the 2002-01-26 version of -the Mersenne-Twister generator written in C by Makoto Matsumoto and Takuji -Nishimura. Original source copyright: -Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura, -All rights reserved -=============================================================================== - -The LocalizedFormatsTest class in the unit tests is an adapted version of -the OrekitMessagesTest class from the orekit library distributed under the -terms of the Apache 2 licence. Original source copyright: -Copyright 2010 CS Systèmes d'Information -=============================================================================== - -The HermiteInterpolator class and its corresponding test have been imported from -the orekit library distributed under the terms of the Apache 2 licence. Original -source copyright: -Copyright 2010-2012 CS Systèmes d'Information -=============================================================================== - -The creation of the package "o.a.c.m.analysis.integration.gauss" was inspired -by an original code donated by Sébastien Brisard. -=============================================================================== - -The complete text of licenses and disclaimers associated with the the original -sources enumerated above at the time of code translation are in the LICENSE.txt -file. - -Apache Commons Net -Copyright 2001-2012 The Apache Software Foundation - -Google Guice - Core Library -Copyright 2006-2011 Google, Inc. - -Google Guice - Extensions - Servlet -Copyright 2006-2011 Google, Inc. - -This product includes software developed by Google -Snappy: http://code.google.com/p/snappy/ -New BSD License - -Apache HttpComponents HttpClient -Copyright 1999-2013 The Apache Software Foundation - -Apache HttpComponents HttpCore -Copyright 2005-2013 The Apache Software Foundation - -This project contains annotations derived from JCIP-ANNOTATIONS -Copyright (c) 2005 Brian Goetz and Tim Peierls. See http://www.jcip.net - -Apache log4j -Copyright 2007 The Apache Software Foundation - -This product includes/uses SLF4J (http://www.slf4j.org/), -Copyright (c) 2004-2008 QOS.ch - -This product includes/uses XZ for Java (http://tukaani.org/xz/java.html) -Lasse Collin and others - -This product includes/uses Guava (http://code.google.com/p/guava-libraries/) -Copyright (C) 2006 Google Inc. - -This product includes/uses Gson (http://code.google.com/p/google-gson) -The Apache Software License, Version 2.0 - -This product includes Jettison (http://jettison.codehaus.org) -Copyright 2006 Envoi Solutions LLC - -This product includes/uses xmlenc (http://xmlenc.sourceforge.net/) -Copyright 2003-2005, Ernst de Haan - -This product includes/uses javax.inject (http://code.google.com/p/atinject/) -The Apache Software License, Version 2.0 - -This product includes/uses FindBugs-jsr305 (http://findbugs.sourceforge.net/) -The Apache Software License, Version 2.0 (com.google.code.findbugs:jsr305:1.3.9) - -This product includes/uses jasper-compiler -The Apache Software License, Version 2.0 - -This product includes/uses jasper-runtime -The Apache Software License, Version 2.0 - -This product includes/uses Apache Hadoop Annotations -The Apache Software License, Version 2.0 +This product automatically loads third party code from an external repository(configurable) using Play's build system. +Such third party code is subject to other license terms than as set forth above. Please review the complete list of +dependencies for applicable license terms. -This product includes/uses Apache Hadoop Auth -The Apache Software License, Version 2.0 +In addition, such third party code may also depend on and load multiple tiers of dependencies. Please review the +applicable licenses of the additional dependencies. -This product includes/uses Apache Hadoop Common -The Apache Software License, Version 2.0 +------------------------------------------------------------------------------ -This product includes/uses Apache Hadoop HDFS -The Apache Software License, Version 2.0 - -This product includes/uses Apache Hadoop Mapreduce Client Core -The Apache Software License, Version 2.0 - -This product includes/uses Apache Hadoop Yarn API -The Apache Software License, Version 2.0 - -This product includes/uses Apache Hadoop Yarn Common -The Apache Software License, Version 2.0 - -This product includes/uses JSch (http://www.jcraft.com/jsch/) -3-clause BSD - -This product includes/uses Protocol Buffer Java API (http://code.google.com/p/protobuf) -BSD or BSD-style License - -This product includes/uses java-xmlbuilder (http://iharder.sourceforge.net/current/java/base64/) -Robert Harder - public domain Base64 encoder/decoder - -========================================================================= -The Netty Project -========================================================================= - -Please visit the Netty web site for more information: - - * http://netty.io/ - -Copyright 2011 The Netty Project - -The Netty Project 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. - -Also, please refer to each LICENSE..txt file, which is located in -the 'license' directory of the distribution file, for the license terms of the -components that this product depends on. - -------------------------------------------------------------------------------- -This product contains the extensions to Java Collections Framework which has -been derived from the works by JSR-166 EG, Doug Lea, and Jason T. Greene: - - * LICENSE: - * license/LICENSE.jsr166y.txt (Public Domain) - * HOMEPAGE: - * http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/ - * http://viewvc.jboss.org/cgi-bin/viewvc.cgi/jbosscache/experimental/jsr166/ - -This product contains a modified version of Robert Harder's Public Domain -Base64 Encoder and Decoder, which can be obtained at: - - * LICENSE: - * license/LICENSE.base64.txt (Public Domain) - * HOMEPAGE: - * http://iharder.sourceforge.net/current/java/base64/ - -This product contains a modified version of 'JZlib', a re-implementation of -zlib in pure Java, which can be obtained at: - - * LICENSE: - * license/LICENSE.jzlib.txt (BSD Style License) - * HOMEPAGE: - * http://www.jcraft.com/jzlib/ - -This product optionally depends on 'Protocol Buffers', Google's data -interchange format, which can be obtained at: - - * LICENSE: - * license/LICENSE.protobuf.txt (New BSD License) - * HOMEPAGE: - * http://code.google.com/p/protobuf/ - -This product optionally depends on 'SLF4J', a simple logging facade for Java, -which can be obtained at: - - * LICENSE: - * license/LICENSE.slf4j.txt (MIT License) - * HOMEPAGE: - * http://www.slf4j.org/ - -This product optionally depends on 'Apache Commons Logging', a logging -framework, which can be obtained at: - - * LICENSE: - * license/LICENSE.commons-logging.txt (Apache License 2.0) - * HOMEPAGE: - * http://commons.apache.org/logging/ - -This product optionally depends on 'Apache Log4J', a logging framework, -which can be obtained at: - - * LICENSE: - * license/LICENSE.log4j.txt (Apache License 2.0) - * HOMEPAGE: - * http://logging.apache.org/log4j/ - -This product optionally depends on 'JBoss Logging', a logging framework, -which can be obtained at: - - * LICENSE: - * license/LICENSE.jboss-logging.txt (GNU LGPL 2.1) - * HOMEPAGE: - * http://anonsvn.jboss.org/repos/common/common-logging-spi/ - -This product optionally depends on 'Apache Felix', an open source OSGi -framework implementation, which can be obtained at: - - * LICENSE: - * license/LICENSE.felix.txt (Apache License 2.0) - * HOMEPAGE: - * http://felix.apache.org/ - -This product optionally depends on 'Webbit', a Java event based -WebSocket and HTTP server: - - * LICENSE: - * license/LICENSE.webbit.txt (BSD License) - * HOMEPAGE: - * https://github.com/joewalnes/webbit - -========================================================================= -# Jackson JSON processor -========================================================================= - -Jackson is a high-performance, Free/Open Source JSON processing library. -It was originally written by Tatu Saloranta (tatu.saloranta@iki.fi), and has -been in development since 2007. -It is currently developed by a community of developers, as well as supported -commercially by FasterXML.com. - -## Licensing - -Jackson core and extension components may licensed under different licenses. -To find the details that apply to this artifact see the accompanying LICENSE file. -For more information, including possible other licensing options, contact -FasterXML.com (http://fasterxml.com). - -## Credits - -A list of contributors may be found from CREDITS file, which is included -in some artifacts (usually source distributions); but is always available -from the source code management (SCM) system project uses. - -========================================================================= -== NOTICE file corresponding to section 4(d) of the Apache License, == -== Version 2.0, in this case for the distribution of jets3t. == -========================================================================= - -This product includes software developed by: - -The Apache Software Foundation (http://www.apache.org/). - -The ExoLab Project (http://www.exolab.org/) - -Sun Microsystems (http://www.sun.com/) - -Codehaus (http://castor.codehaus.org) - -Safehaus (http://jug.safehaus.org/Home) +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/) -============================================================== - Jetty Web Container - Copyright 1995-2009 Mort Bay Consulting Pty Ltd -============================================================== +This product includes/uses Apache Commons IO (https://commons.apache.org/proper/commons-io/) +Copyright (c) 2002-2016 The Apache Software Foundation +License: Apache 2.0 -The Jetty Web Container is Copyright Mort Bay Consulting Pty Ltd -unless otherwise noted. It is licensed under the apache 2.0 -license. +This product includes/uses Gson (https://github.com/google/gson) +Copyright (C) 2008 Google Inc. +License: Apache 2.0 -The javax.servlet package used by Jetty is copyright -Sun Microsystems, Inc and Apache Software Foundation. It is -distributed under the Common Development and Distribution License. -You can obtain a copy of the license at -https://glassfish.dev.java.net/public/CDDLv1.0.html. +This product includes/uses Jsoup (https://github.com/jhy/jsoup) +Copyright (c) 2009 - 2015 Jonathan Hedley (jonathan@hedley.net) +License: The MIT License (https://github.com/jhy/jsoup/blob/master/LICENSE) -The UnixCrypt.java code ~Implements the one way cryptography used by -Unix systems for simple password protection. Copyright 1996 Aki Yoshida, -modified April 2001 by Iris Van den Broeke, Daniel Deville. -Permission to use, copy, modify and distribute UnixCrypt -for non-commercial or commercial purposes and without fee is -granted provided that the copyright notice appears in all copies. +This product includes/uses Guava (https://github.com/google/guava) +License: Apache 2.0 -The default JSP implementation is provided by the Glassfish JSP engine -from project Glassfish http://glassfish.dev.java.net. Copyright 2005 -Sun Microsystems, Inc. and portions Copyright Apache Software Foundation. +This product includes/uses Jackson Databind (http://github.com/FasterXML/jackson) +License: Apache 2.0 -Some portions of the code are Copyright: - 2006 Tim Vernum - 1999 Jason Gilbert. +This product includes/uses Apache Hadoop (http://hadoop.apache.org/) +Notice: https://github.com/apache/hadoop/blob/trunk/NOTICE.txt +License: Apache 2.0 -The jboss integration module contains some LGPL code. +This product includes/uses Apache Spark (http://spark.apache.org/) +Notice: https://github.com/apache/spark/blob/master/NOTICE +License: Apache 2.0 (https://github.com/apache/spark/blob/master/LICENSE) -The win32 Java Service Wrapper (v3.2.3) is Copyright (c) 1999, 2006 -Tanuki Software, Inc. and 2001 Silver Egg Technology. It is -covered by an open license which is viewable at -http://svn.codehaus.org/jetty/jetty/branches/jetty-6.1/extras/win32service/LICENSE.txt +This product includes/uses Play Framework (https://www.playframework.com/) +Copyright (C) 2009-2016 Typesafe Inc. (http://www.typesafe.com) +License: Apache 2.0 +This product requires the following play dependencies in addition to the core Play library +javaEbean, the Ebean plugin for Java. +javaJdbc, the Java database API. -------------------------------------- +------------------------------------------------------------------------------ Attribution for JavaScript Libraries -------------------------------------- +------------------------------------------------------------------------------ -jQuery -http://jquery.com/ +jQuery (http://jquery.com/) +Copyright 2005, 2014 jQuery Foundation, Inc. and other contributors License: MIT License (http://jquery.org/license) -Copyright 2005, 2012 jQuery Foundation, Inc. and other contributors -jQuery Deserialize -https://github.com/kflorence/jquery-deserialize/ +jQuery Deserialize (https://github.com/kflorence/jquery-deserialize/) +Copyright (c) 2015 Kyle Florence License: Dual licensed under the MIT and GPLv2 licenses. -Bootstrap -http://getbootstrap.com/ +Bootstrap (http://getbootstrap.com/) +Copyright 2011-2016 Twitter, Inc. License: MIT License (https://github.com/twbs/bootstrap/blob/master/LICENSE) -Copyright 2011-2014 Twitter, Inc. -Bootstrap Datepicker -http://www.eyecon.ro/bootstrap-datepicker -License: Apache License, Version 2.0 +Bootstrap Datepicker (http://www.eyecon.ro/bootstrap-datepicker) Copyright 2012 Stefan Petre +License: Apache 2.0 -D3.js -http://d3js.org +D3.js (http://d3js.org) +Copyright (c) 2010-2016, Michael Bostock License: BSD 3-Clause (https://github.com/mbostock/d3/blob/master/LICENSE) -Copyright 2015 Mike Bostock \ No newline at end of file + +------------------------------------------------------------------------------ + + diff --git a/README.md b/README.md index 79dca364c..f51ebe676 100644 --- a/README.md +++ b/README.md @@ -246,7 +246,7 @@ cd /export/apps/elephant/ ## License - Copyright 2015 LinkedIn Corp. + Copyright 2016 LinkedIn Corp. 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 diff --git a/app-conf/CounterPublisherConf.xml b/app-conf/CounterPublisherConf.xml index cdc3dfde1..91e2101b0 100644 --- a/app-conf/CounterPublisherConf.xml +++ b/app-conf/CounterPublisherConf.xml @@ -1,6 +1,6 @@ - - - - - hdfs - /data/HadoopCounters - - - - diff --git a/app/com/linkedin/drelephant/DaliMetricsAPI.java b/app/com/linkedin/drelephant/DaliMetricsAPI.java deleted file mode 100644 index acc148bbd..000000000 --- a/app/com/linkedin/drelephant/DaliMetricsAPI.java +++ /dev/null @@ -1,797 +0,0 @@ -/* - * Copyright 2016 LinkedIn Corp. - * - * 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.linkedin.drelephant; - -import com.linkedin.events.KafkaAuditHeader; -import com.linkedin.events.hadoop.AggregationScope; -import com.linkedin.events.hadoop.AzkabanContext; -import com.linkedin.events.hadoop.Counter; -import com.linkedin.events.hadoop.ExecutionContext; -import com.linkedin.events.hadoop.HadoopHeader; -import com.linkedin.events.hadoop.HadoopMetricsEvent; -import com.linkedin.events.hadoop.State; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.InetAddress; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.UnknownHostException; -import java.nio.ByteBuffer; -import java.util.Calendar; -import java.util.Date; -import java.util.GregorianCalendar; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.Set; -import java.util.StringTokenizer; -import java.util.TimeZone; -import java.util.UUID; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import org.apache.avro.Schema; -import org.apache.avro.file.DataFileWriter; -import org.apache.avro.generic.GenericDatumWriter; -import org.apache.avro.generic.IndexedRecord; -import org.apache.avro.io.DatumWriter; -import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.fs.FSDataOutputStream; -import org.apache.hadoop.fs.FileSystem; -import org.apache.hadoop.fs.Path; -import org.apache.hadoop.fs.permission.FsPermission; -import org.apache.log4j.Logger; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.NodeList; -import org.xml.sax.SAXException; -import play.Play; - - -/** - * TODO Decide if the contained classes should be split out. - * - * This class provides a set of classes that can be used to construct and publish hadoop - * MetricsEvent. The inner classes are: - * - * CountersToPublish: This class takes in a hadoop configuration value (string) that indicates which counters are to be published - * when the job finishes. The key for this configuration is defined in the JobProperties class. - * EventContext: This is one of the two objects that an application constructs if a metrics event is to be published. The event context - * has the start and end time window in which the metrics were gathered,and also the job name and container ID. - * JobProperties: THis is the other object that the application needs to construct if a metrics event is to be published. - * It is filled in from the hadoop job configuration. It has the azkaban related fields, and a couple of other config - * properties. It also has the config property of the counters to publish. JobProperties uses that to construct - * CountersToPublish - * HadoopCounters: Represents the metrics event that we need to publish. In the final API exposed to (map-reduce) applications, - * I expect another class called HadoopMetrics (or some such) that allows applications to add counters, gauges, etc. - * Application must construct this class (with JobProperties and EventContext) in order to publish an event. - * The build() method in this class returns an avro IndexedRecord that can then be handed to the publisher to publish. - * CommonEventsHeaderBuilder: A private class used by the metrics library to construct the kafka audit header. - * - * MetricsPublisher: An interface that every event publisher must implement. Has a publish() method to publish an - * IndexedRecord. - * HDFSMetricsPublisher: Implements a class that publishes metrics to HDFS. It is constructed via a static method - * (should really be in a factory) that takes an XML file as input. The XML file has configuration parameters for - * the HDFS publisher (e.g. the root directory under which events should be published). - */ -public class DaliMetricsAPI { - private static final Logger LOG = Logger.getLogger(DaliMetricsAPI.class); - - /** - * A helper class to keep track of which metrics to publish. - */ - public static class CountersToPublish { - private static final String ALL_COUNTERS = "*"; - private final Map> _countersToPublish; - /** - * @param counterNames A string of the format "groupName:counterName,groupName:counterName,..." - * counterName can be '*' meaning all counters in the group. - * @return A map with one key per counter group -- the value being a set - */ - public CountersToPublish(String counterNames) { - Map> countersToPublish = null; - - if (counterNames != null && counterNames.length() != 0) { - countersToPublish = new HashMap>(4); - StringTokenizer st = new StringTokenizer(counterNames, ","); - - while (st.hasMoreTokens()) { - String val = st.nextToken(); - String[] groupCounter = val.split(":"); - if (groupCounter.length != 2) { - // Malformed group+counter - LOG.error("Ignoring malformed group/counter pair:'" + val + "'"); - continue; - } - if (groupCounter[1] == null) { - // TODO - LOG.error("Ignoring malformed counter name:'" + val + "'"); - continue; - } - String groupName = groupCounter[0]; - String counterName = groupCounter[1]; - if (!countersToPublish.containsKey(groupName)) { - countersToPublish.put(groupName, new HashSet(4)); - } - countersToPublish.get(groupName).add(counterName); - } - } - _countersToPublish = countersToPublish; - } - - /* - * Returns true if no counters need to be published. - */ - public boolean isEmpty() { - return _countersToPublish == null; - } - - /* - * Returns true if the specified counter is to be published. - */ - public boolean hasCounter(String groupName, String counterName) { - if (_countersToPublish == null) { - return false; - } - Set countersInGroup = _countersToPublish.get(groupName); - if (countersInGroup == null) { - return false; - } - if (countersInGroup.contains(ALL_COUNTERS) || countersInGroup.contains(counterName)) { - return true; - } - return false; - } - } - - /** - * EventContext is a helper class that the application needs to fill in before - * requesting to publish counters for a job. - * - * In the more general case of a task publishing metrics, we need to expand this - * to contain a builder that can populate the members. - * - * Keeping code to a minimum for now. - */ - public static class EventContext { - private final String _containerId; - private final AggregationScope _scope; - private final State _state; - private final String _jobName; - private final String _jobId; - private final long _startTime; - private final long _endTime; - - /** - * For now, we only publish for successful jobs, so this constructor works. - * @param jobName hadoop job name from JobReport - * @param jobId hadoop job Id from JobReport - */ - public EventContext(String jobName, String jobId, long startTime, long endTime) { - _containerId = null; - _scope = AggregationScope.JOB; - _state = State.SUCCEEDED; - _jobName = jobName; - _jobId = jobId; - _startTime = startTime; - _endTime = endTime; - } - - public String getContainerId() { - return _containerId; - } - - public String getJobName() { - return _jobName; - } - - public String getJobId() { - return _jobId; - } - - public State getState() { - return _state; - } - - public AggregationScope getScope() { - return _scope; - } - - public long getStartTime() { - return _startTime; - } - - public long getEndTime() { - return _endTime; - } - - /* - * TODO - * - Add a builder (setSuccess(), setJobScope() etc. may make more sense than exposing 'State', for example) - * - May be sub-class with a container Id for task-level API? - */ - } - - /** - * JobProperties is a helper class that the application needs to fill in before - * requesting to publish counters for a job. - * - * Inside the cluster, Configuration can be used to populate this. - */ - public static class JobProperties { - private static final String UNKNOWN_CLUSTERNAME = "Unknown"; - private static final String UNKNOWN_USERNAME = "Unknown"; - - private static final String CONFIG_KEY_PUB_COUNTERS = "mapreduce.job.publish-counters"; - private static final String CONFIG_KEY_AZ_JOBID = "azkaban.job.id"; - private static final String CONFIG_KEY_AZ_FLOWID = "azkaban.flow.flowid"; - private static final String CONFIG_KEY_AZ_PROJNAME = "azkaban.flow.projectname"; - private static final String CONFIG_KEY_AZ_PROJVER = "azkaban.flow.projectversion"; - private static final String CONFIG_KEY_AZ_PROJ_LAST_CHANGED_DATE = "azkaban.flow.projectlastchangeddate"; - private static final String CONFIG_KEY_AZ_EXECURL = "azkaban.link.jobexec.url"; - private static final String CONFIG_KEY_AZ_EXECID = "azkaban.flow.execid"; - private static final String CONFIG_KEY_NN_HOSTPORT = "dfs.namenode.http-address"; - private static final String CONFIG_KEY_USER_NAME = "mapreduce.job.user.name"; - - private final CountersToPublish _countersToPublish; - private final String _azkabanJobId; - private final String _azkabanFlowId; - private final String _azkabanProjectName; - private final String _azkabanProjectVersion; - private final long _azkabanProjectLastChangedDate; - private final String _azkabanHostName; - private final int _execId; - private final String _clusterName; - private final String _userName; - - public JobProperties(Properties properties) { - _countersToPublish = new CountersToPublish(properties.getProperty(CONFIG_KEY_PUB_COUNTERS)); - _azkabanJobId = properties.getProperty(CONFIG_KEY_AZ_JOBID); - _azkabanFlowId = properties.getProperty(CONFIG_KEY_AZ_FLOWID); - _execId = decodeExecId(properties.getProperty(CONFIG_KEY_AZ_EXECID)); - _azkabanProjectName = properties.getProperty(CONFIG_KEY_AZ_PROJNAME); - _azkabanProjectVersion = properties.getProperty(CONFIG_KEY_AZ_PROJVER); - _azkabanProjectLastChangedDate = parseDate(properties.getProperty(CONFIG_KEY_AZ_PROJ_LAST_CHANGED_DATE)); - _azkabanHostName = url2HostName(properties.getProperty(CONFIG_KEY_AZ_EXECURL)); - _clusterName = nnHostPort2ClusterName(properties.getProperty(CONFIG_KEY_NN_HOSTPORT)); - _userName = properties.getProperty(CONFIG_KEY_USER_NAME); - } - - // TODO Add a constructor with org.apache.hadoop.mapreduce.v2.api.records.JobReport for use inside cluster? - - /* - * We get a string that should parse into a long value of msecs since epoch. - */ - private long parseDate(final String msecsSinceEpoch) { - long secsSinceEpoch = -1; - if (msecsSinceEpoch == null) { - return secsSinceEpoch; - } - try { - secsSinceEpoch = Long.parseLong(msecsSinceEpoch)/1000L; - } catch (NumberFormatException e) { - LOG.warn("Could not parse msecs since epoch " + msecsSinceEpoch); - } - return secsSinceEpoch; - } - - private int decodeExecId(String execIdStr) { - int execId = -1; - if (execIdStr != null) { - try { - execId = Integer.parseInt(execIdStr); - } catch (NumberFormatException e) { - // TODO Log the exception? - LOG.warn("Illegal value for execId:'" + execIdStr + "'"); - } - } - return execId; - } - - private String url2HostName(String urlStr) { - String hostName = null; - if (urlStr != null && urlStr.length() > 0) { - try { - URL url = new URL(urlStr); - hostName = url.getHost(); - } catch (MalformedURLException e) { - // TODO TBD log the exception? - LOG.warn("Could not determine hostname from URL:'" + urlStr + "'"); - } - } - return hostName; - } - - /** - * Helper method to map a name node URL to a cluster name. It is assumed that the URL is of the form - * datacenter-clusternamennNN.grid.linkedin.com (where N is a digit, 'clustername' is the name of the cluster) - * @param hostPort - * @return name of the cluster or "Unknown" if cluster name cannot be determined. - */ - private String nnHostPort2ClusterName(String hostPort) { - if (hostPort == null || hostPort.length() == 0) { - return UNKNOWN_CLUSTERNAME; - } - String hostName = url2HostName("http://" + hostPort); - if (hostName == null) { - return UNKNOWN_CLUSTERNAME; - } - // Assume the hostname is of the type -nn.grid.linkedin.com - // May not work all the time! - String hostNameSansDomain = hostName.replace(".grid.linkedin.com", ""); - if (hostNameSansDomain.length() < 7) { - // need 4 chars for nn and one char for '-' and at least one char for datacenter, and one char for clusterName - return UNKNOWN_CLUSTERNAME; - } - int clusterNameStart = hostNameSansDomain.indexOf('-'); - if (clusterNameStart == -1) { // There is no dash here, just return unknown clustername - return UNKNOWN_CLUSTERNAME; - } - clusterNameStart++; - int clusterNameEnd = hostNameSansDomain.length()-4; - return hostNameSansDomain.substring(clusterNameStart, clusterNameEnd); - } - - public CountersToPublish getCountersToPublish() { - return _countersToPublish; - } - - public long getAzkabanProjectLastChangedDate() { - return _azkabanProjectLastChangedDate; - } - - public int getExecId() { - return _execId; - } - - public String getAzkabanHostName(final String defaultValue) { - return _azkabanHostName == null ? defaultValue : _azkabanHostName; - } - - public String getAzkabanProjectVersion(final String defaultValue) { - return _azkabanProjectVersion == null ? defaultValue : _azkabanProjectVersion; - } - - public String getAzkabanFlowId(final String defaultValue) { - return _azkabanFlowId == null ? defaultValue : _azkabanFlowId; - } - - public String getAzkabanJobId(final String defaultValue) { - return _azkabanJobId == null ? defaultValue : _azkabanJobId; - } - - public String getAzkabanProjectName(final String defaultValue) { - return _azkabanProjectName == null ? defaultValue : _azkabanProjectName; - } - - public String getClusterName() { - return _clusterName; - } - - public String getUserName() { - return _userName; - } - - // If any of the properties are set, then the job is coming through azkaban. - public boolean hasAnyAzkabanProperties() { - return _execId != -1 || - _azkabanFlowId != null || - _azkabanHostName != null || - _azkabanJobId != null || - _azkabanProjectLastChangedDate != -1L || - _azkabanProjectName != null || - _azkabanProjectVersion != null; - } - - // TODO Add a constructor from org.apache.hadoop.conf.Configuration - } - - /** - * A class that the applications can use to construct the HadoopMetricsEvent avro - * event and hand it to the publisher to publish. - * - * This class takes care of populating the headers from the data given, and provides methods - * to add counters to the event. - */ - public static class HadoopCounters { - private final CountersToPublish _countersToPublish; - private final HadoopMetricsEvent _hadoopMetricsEvent; - private final List _counters; - private int _numCounters = 0; - - public HadoopCounters(EventContext eventContext, JobProperties jobProperties) { - _countersToPublish = jobProperties.getCountersToPublish(); - - _counters = new LinkedList(); - - AzkabanContext azkabanContext = null; - // Fill in Azkaban Context - if (jobProperties.hasAnyAzkabanProperties()) { - azkabanContext = new AzkabanContext(); - azkabanContext.executionId = jobProperties.getExecId(); - azkabanContext.flowName = jobProperties.getAzkabanFlowId("Unknown"); - azkabanContext.hostName = jobProperties.getAzkabanHostName("Unknown"); - azkabanContext.lastChangedDate = jobProperties.getAzkabanProjectLastChangedDate(); - azkabanContext.projectName = jobProperties.getAzkabanProjectName("Unknown"); - azkabanContext.projectVersion = jobProperties.getAzkabanProjectVersion("Unknown"); - azkabanContext.jobName = jobProperties.getAzkabanJobId("Unknown"); - } - - - ExecutionContext executionContext = new ExecutionContext(); - executionContext = new ExecutionContext(); - executionContext.applicationId = eventContext.getJobId(); - executionContext.applicationName = eventContext.getJobName(); - executionContext.state = eventContext.getState(); - executionContext.containerId = eventContext.getContainerId(); - executionContext.userName = jobProperties.getUserName(); - executionContext.clusterName = jobProperties.getClusterName(); - - HadoopHeader hadoopHeader = new HadoopHeader(); - hadoopHeader = new HadoopHeader(); - hadoopHeader.aggregationScope = eventContext.getScope(); - hadoopHeader.startTime = eventContext.getStartTime(); - hadoopHeader.endTime = eventContext.getEndTime(); - hadoopHeader.azkabanContext = azkabanContext; - hadoopHeader.executionContext = executionContext; - - _hadoopMetricsEvent = new HadoopMetricsEvent(); - _hadoopMetricsEvent.hadoopHeader = hadoopHeader; - _hadoopMetricsEvent.auditHeader = new CommonEventHeaderBuilder().build(); - } - - /** - * Add a counter to the list of counters to be published, if the counter is configured to be - * published. If not, then do nothing. - * - * TODO Replace a counter that is already added? Or throw error? - * - * @param groupName - * @param counterName - * @param value - */ - public void addCounter(String groupName, String counterName, long value) { - if (_countersToPublish.hasCounter(groupName, counterName)) { - Counter counter = new Counter(); - counter.counterGroup = groupName; - counter.counterName = counterName; - counter.value = value; - _counters.add(counter); - _numCounters++; - } - } - - /** - * @return the total number of counters added to the publish list. - */ - public int getNumCounters() { - return _numCounters; - } - - /** - * Build the avro event and return an IndexedRecord. - * @note It is not guaranteed that this record will be serialized correctly, since no verification is done here - * TODO Add verification so that avro publish will not fail. Or, return a serialized bytebuffer? - * @return - */ - public IndexedRecord build() { - _hadoopMetricsEvent.counters = _counters; - return _hadoopMetricsEvent; - } - } - - /** - * A helper class to fill in the kafka audit header. - * This class should not be exposed outside the DaliMetrics API. - */ - static class CommonEventHeaderBuilder { - private static final String HADOOP_SERVICE = "hadoop"; - private static final String UNKNOWN_HOSTNAME = "Unknown"; - - private static String hostName = null; - - private final KafkaAuditHeader _auditHeader; - public CommonEventHeaderBuilder() { - _auditHeader = new KafkaAuditHeader(); - } - public KafkaAuditHeader build() { - _auditHeader.time = System.currentTimeMillis(); - _auditHeader.server = getHostName(); - _auditHeader.appName = HADOOP_SERVICE; - byte[] bytes = getUUID(); - com.linkedin.events.UUID liUUID = new com.linkedin.events.UUID(); - System.arraycopy(bytes, 0, liUUID.bytes(), 0, bytes.length); - _auditHeader.messageId = liUUID; - - return _auditHeader; - } - - /** - * A helper method to get (and cache) the hostname - * @return name of this host or "Unknown" if there are errors - */ - private synchronized static String getHostName() { - - if (hostName != null) { - return hostName; - } - try { - hostName = InetAddress.getLocalHost().getHostName(); - return hostName; - } catch (UnknownHostException e) { - LOG.warn("Could not determine hostname", e); - return UNKNOWN_HOSTNAME; - } - } - - /** - * A helper method to return a 16-byte UUID - * @return UUID of 16 bytes - */ - private byte[] getUUID() { - final UUID uuid = UUID.randomUUID(); - final ByteBuffer bb = ByteBuffer.allocate(16); // Default is big-endian - bb.putLong(uuid.getMostSignificantBits()); - bb.putLong(uuid.getLeastSignificantBits()); - return bb.array(); - } - - } - - /** - * This interface us to be implemented by all the publishers. - */ - public interface MetricsPublisher { - /** - * A thread-safe publish method that publishes an event - * @param event - * @throws IOException - */ - public void publish(IndexedRecord event) throws IOException; - } - - /** - * An implementation of a publisher to publish to HDFS file system. - */ - public static class HDFSMetricsPublisher implements MetricsPublisher { - private static String DEFAULT_BASE_PATH = "/data/HadoopMetrics/jobs"; - - private final String _basePath; - - private HDFSMetricsPublisher(String basePath) { - if (basePath.endsWith("/")) { - _basePath = basePath; - } else { - _basePath = basePath + "/"; - } - } - - /** - * @param configFileName - * @return null if the config file does not exist or hdfs publishers not configured, or any misconfiguration is detected. - */ - public static MetricsPublisher createFromXml(String configFileName) { - MetricsPublisher metricsPublisher = null; - if (configFileName != null) { - InputStream instream = null; - LOG.info("Attempting to read metrics publish configuration from " + configFileName); - instream = Play.application().resourceAsStream(configFileName); - if (instream == null) { - LOG.info("Configuation file not present in classpath. File: " + configFileName); - return null; - } - LOG.info("Configuation file loaded. File: " + configFileName); - metricsPublisher = getHdfsMetricsPublisher(configFileName, instream); - } - if (metricsPublisher == null) { - LOG.info("Not valid config so metrics will not be published."); - } - return metricsPublisher; - } - - private static HDFSMetricsPublisher getHdfsMetricsPublisher(String logmsgPrefix, InputStream instream) { - try { - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - DocumentBuilder builder = factory.newDocumentBuilder(); - Document document = builder.parse(instream); - NodeList publishers = document.getElementsByTagName("publisher"); - if (publishers.getLength() == 0) { - LOG.info("No publishers configured"); - return null; - } - Element publisherNode = (Element)publishers.item(0); - NodeList schemes = publisherNode.getElementsByTagName("scheme"); - if (schemes.getLength() == 0) { - LOG.info("No publish scheme configured"); - return null; - } - Element schemeElement = (Element)schemes.item(0); - if (schemeElement.getTextContent() == null || - !schemeElement.getTextContent().equals("hdfs")) { - LOG.info("Publish scheme must be \"hdfs\""); - return null; - } - NodeList basePathNodes = publisherNode.getElementsByTagName("basepath"); - if (basePathNodes == null || basePathNodes.getLength() == 0) { - LOG.info("HDFS Base path not configured, assumed " + DEFAULT_BASE_PATH); - return new HDFSMetricsPublisher(DEFAULT_BASE_PATH); - } - Element basePathElement = (Element)basePathNodes.item(0); - if (basePathElement == null || basePathElement.getTextContent()== null) { - LOG.info("HDFS Base path not configured, assumed " + DEFAULT_BASE_PATH); - return new HDFSMetricsPublisher(DEFAULT_BASE_PATH); - } - String basePath = basePathElement.getTextContent(); - if (!basePath.startsWith("/")) { - LOG.error("Ignoring illegal value for basepath:'" + basePath + "'(must start with '/'). Using " - + DEFAULT_BASE_PATH + " instead"); - return new HDFSMetricsPublisher(DEFAULT_BASE_PATH); - } - return new HDFSMetricsPublisher(basePath); - } catch (ParserConfigurationException e) { - throw new RuntimeException(logmsgPrefix + ":Exception during parse", e); - } catch (SAXException e) { - throw new RuntimeException(logmsgPrefix + ":Exception during parse", e); - } catch (IOException e) { - throw new RuntimeException(logmsgPrefix + ":Exception during parse", e); - } - } - - private Object extractField(final IndexedRecord indexedRecord, - final String fieldName, - final boolean mustBeRecord) - throws IOException { - - final Schema schema = indexedRecord.getSchema(); - final Schema.Field field = schema.getField(fieldName); - if (field == null) { - throw new IOException(fieldName + ":No such field in " + schema.getName()); - } - final int fieldPos = field.pos(); - final Object fieldValue = indexedRecord.get(fieldPos); - if (fieldValue == null) { - throw new IOException(fieldName + ":Null value in " + schema.getName()); - } - if (mustBeRecord) { - final Schema fieldSchema = field.schema(); - if (fieldSchema == null) { - throw new IOException(fieldName + ":No such record in " + schema.getName()); - } - } - - return fieldValue; - } - - private long extractPublishTime(IndexedRecord event) - throws IOException { - - IndexedRecord auditHeader = (IndexedRecord)extractField(event, "auditHeader", true); - Long time = (Long)extractField(auditHeader, "time", false); - return time; - } - - private String extractApplicationId(IndexedRecord event) - throws IOException { - - IndexedRecord hadoopHeader = (IndexedRecord)extractField(event, "hadoopHeader", true); - IndexedRecord executionContext = (IndexedRecord)extractField(hadoopHeader, "executionContext", true); - String applicationId = (String)extractField(executionContext, "applicationId", false); - return applicationId; - } - - @Override - public void publish(IndexedRecord event) - throws IOException { - - long pubTime = extractPublishTime(event); - String applicationId = extractApplicationId(event); - String path = getPathName(pubTime, applicationId); - - OutputStream outStream = getOutputStream(path); - - writeToStream(outStream, event); - } - - private OutputStream getOutputStream(String pathName) - throws IOException { - - Configuration config = new Configuration(); - // TODO Decide permissions correctly. Right now, this is world-reasable, but perhaps it should be group-readable? - FsPermission perms = new FsPermission((short)0644); - Path path = new Path(pathName); - FileSystem fs = null; - try { - fs = path.getFileSystem(config); - } catch (IOException e) { - LOG.error("Could not get file system", e); - throw e; - } - LOG.debug("Publishing metrics to " + fs + ", path=" + path); - - try { - FSDataOutputStream fileOutputStream = FileSystem.create(fs, path, perms); - return fileOutputStream; - } catch (IOException e) { - LOG.error("Exception writing to avro file", e); - throw e; - } - } - - private String getPathName(long pubTime, String fileName) { - GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("UTC")); - cal.setTime(new Date(pubTime)); - String year = Integer.toString(cal.get(Calendar.YEAR)); - String month = String.format("%02d", cal.get(Calendar.MONTH) + 1); // Month seems to start from 0! - String day = String.format("%02d", cal.get(Calendar.DATE)); - String hour = String.format("%02d", cal.get(Calendar.HOUR_OF_DAY)); - String path = _basePath + "/hourly/" + year + "/" + month + "/" + day + "/" + hour + "/" + fileName + ".avro"; - return path; - } - - private void writeToStream(OutputStream outStream, IndexedRecord event) - throws IOException { - DatumWriter dw = new GenericDatumWriter(event.getSchema()); - DataFileWriter dfw = new DataFileWriter(dw); - dfw.create(event.getSchema(), outStream); - dfw.append(event); - dfw.close(); - } - } - - // For testing purposes - public static void main(String[] args) throws Exception { - Properties props = new Properties(); - props.setProperty(JobProperties.CONFIG_KEY_PUB_COUNTERS, "fruits:*"); - props.setProperty(JobProperties.CONFIG_KEY_AZ_EXECID, "21345"); - props.setProperty(JobProperties.CONFIG_KEY_AZ_EXECURL, "http://host:6556/exec/url"); - props.setProperty(JobProperties.CONFIG_KEY_AZ_FLOWID, "flowId"); - props.setProperty(JobProperties.CONFIG_KEY_AZ_JOBID, "jobId"); - props.setProperty(JobProperties.CONFIG_KEY_AZ_PROJ_LAST_CHANGED_DATE, "2014-12-10"); - props.setProperty(JobProperties.CONFIG_KEY_AZ_PROJNAME, "projName"); - props.setProperty(JobProperties.CONFIG_KEY_AZ_PROJVER, "v1.3"); - props.setProperty(JobProperties.CONFIG_KEY_NN_HOSTPORT, "http://eat1-magicnn01:8723/name/node"); - props.setProperty(JobProperties.CONFIG_KEY_USER_NAME, "user"); - JobProperties jp = new JobProperties(props); - - EventContext ec = new EventContext("jobName", "jobId", 123456L, 234567L); - - HadoopCounters hm = new HadoopCounters(ec, jp); - hm.addCounter("fruits", "apple", 45); - IndexedRecord hme = hm.build(); - publish(hme); - } - - public static void publish(IndexedRecord event) - throws IOException { - String path = "/tmp/xx.avro"; - OutputStream outStream = new FileOutputStream(new File(path)); - - DatumWriter dw = new GenericDatumWriter(event.getSchema()); - DataFileWriter dfw = new DataFileWriter(dw); - dfw.create(event.getSchema(), outStream); - dfw.append(event); - dfw.close(); - } - -} diff --git a/app/com/linkedin/drelephant/ElephantContext.java b/app/com/linkedin/drelephant/ElephantContext.java index 5ea6ac2db..92514643a 100644 --- a/app/com/linkedin/drelephant/ElephantContext.java +++ b/app/com/linkedin/drelephant/ElephantContext.java @@ -68,8 +68,6 @@ public class ElephantContext { private final Map _typeToFetcher = new HashMap(); private Map> _appTypeToJobTypes = new HashMap>(); - private final DaliMetricsAPI.MetricsPublisher _metricsPublisher; - public static void init() { INSTANCE = new ElephantContext(); } @@ -84,14 +82,6 @@ public static ElephantContext instance() { // private on purpose private ElephantContext() { loadConfiguration(); - - // Load metrics publisher - _metricsPublisher = DaliMetricsAPI.HDFSMetricsPublisher.createFromXml(OPT_METRICS_PUB_CONF); - if (_metricsPublisher == null) { - logger.info("No metrics will be published. "); - } else { - logger.info("Metrics publisher configured. "); - } } private void loadConfiguration() { @@ -283,15 +273,6 @@ public ElephantFetcher getFetcherForApplicationType(ApplicationType type) { return _typeToFetcher.get(type); } - /** - * Get the DALI metrics publisher - * - * @return the DALI Metrics publisher - */ - public DaliMetricsAPI.MetricsPublisher getMetricsPublisher() { - return _metricsPublisher; - } - /** * Get the application type given a type name. * diff --git a/app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop2.java b/app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop2.java index 0fd0b014a..0d3604896 100644 --- a/app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop2.java +++ b/app/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop2.java @@ -105,10 +105,6 @@ public MapReduceApplicationData fetchData(AnalyticJob analyticJob) throws IOExce MapReduceTaskData[] reducerData = reducerList.toArray(new MapReduceTaskData[reducerList.size()]); jobData.setCounters(jobCounter).setMapperData(mapperData).setReducerData(reducerData); - - // TODO: publisher should not be here. refactor before open source - Utils.publishMetrics(jobData); - } else if (state.equals("FAILED")) { jobData.setSucceeded(false); diff --git a/app/com/linkedin/drelephant/util/Utils.java b/app/com/linkedin/drelephant/util/Utils.java index 46c3117c6..1ccbf1af4 100644 --- a/app/com/linkedin/drelephant/util/Utils.java +++ b/app/com/linkedin/drelephant/util/Utils.java @@ -16,18 +16,12 @@ package com.linkedin.drelephant.util; -import com.linkedin.drelephant.DaliMetricsAPI; -import com.linkedin.drelephant.ElephantContext; -import com.linkedin.drelephant.mapreduce.data.MapReduceCounterData; -import com.linkedin.drelephant.mapreduce.data.MapReduceApplicationData; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Properties; -import java.util.Set; import java.util.regex.Pattern; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; @@ -35,9 +29,6 @@ import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; -import org.apache.avro.generic.IndexedRecord; -import org.apache.commons.lang.StringUtils; -import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.w3c.dom.Document; import org.xml.sax.SAXException; @@ -70,64 +61,6 @@ public static String getJobIdFromApplicationId(String appId) { return appId.replaceAll("application", "job"); } - /** - * Given a MapreduceApplicationData instance, publish the corresponding Dali Metrics if needed. - * - * @param jobData the data to check for publishing - */ - public static void publishMetrics(MapReduceApplicationData jobData) { - DaliMetricsAPI.MetricsPublisher metricsPublisher = ElephantContext.instance().getMetricsPublisher(); - if (metricsPublisher == null) { - return; - } - - Properties jobConf = jobData.getConf(); - // We may have something to publish, but we don't know until we have iterated through the counters that we have. - // We assume that we need to publish something until we find out we don't. - DaliMetricsAPI.JobProperties jobProperties = new DaliMetricsAPI.JobProperties(jobConf); - if (jobProperties.getCountersToPublish().isEmpty()) { - // Nothing to do - return; - } - logger.info("Publishing counters for job[" + jobData.getJobId() + "]"); - DaliMetricsAPI.EventContext eventContext = - new DaliMetricsAPI.EventContext(jobData.getJobName(), jobData.getJobId(), jobData.getStartTime(), - jobData.getFinishTime()); - DaliMetricsAPI.HadoopCounters metricsEvent = new DaliMetricsAPI.HadoopCounters(eventContext, jobProperties); - - MapReduceCounterData counterHolder = jobData.getCounters(); - logger.info("HadoopCounterHolder: {" + counterHolder + "}"); - Set groupNames = counterHolder.getGroupNames(); - logger.info("group names: [" + StringUtils.join(groupNames, ",") + "]"); - for (String group : groupNames) { - Map counters = counterHolder.getAllCountersInGroup(group); - for (Map.Entry entry : counters.entrySet()) { - String counterName = entry.getKey(); - Long value = entry.getValue(); - logger.info(String.format("%s,,,,,,,%s", counterName, String.valueOf(value))); - metricsEvent.addCounter(group, counterName, value); - } - } - - if (metricsEvent.getNumCounters() == 0) { - logger.info("No counters need to be published for job [" + jobData.getJobId() + "]"); - // The counters that were configured were not collected in HadoopCounterHolder. - return; - } - IndexedRecord event = metricsEvent.build(); - try { - metricsPublisher.publish(event); - } catch (IOException e) { - // The lower level should have logged a message. - // A checked exception from the publish() call should mean that the event was not formed correctly for some reason. - // Aside from a code bug, the most common reason for this will probably be that some mandatory fields in the - // event were missing. - // Could also mean that some derived values (e.g. hostname to URL, execId translation to an integer, etc.) - // may have failed. There is little we can do at this point to fix those, so ignore the exception. - logger.log(Level.WARN, "Publish failed:", e); - } - } - /** * Load an XML document from a file path * diff --git a/build.sbt b/build.sbt index 3ecd78596..10c5ab6d8 100644 --- a/build.sbt +++ b/build.sbt @@ -32,10 +32,7 @@ libraryDependencies ++= Seq( "org.apache.commons" % "commons-email" % "1.3.2", "org.codehaus.jackson" % "jackson-mapper-asl" % "1.7.3", "org.jsoup" % "jsoup" % "1.7.3", - // The following two dependencies are pulled in by the metrics library. - "org.apache.avro" % "avro" % "1.4.0", - "com.linkedin.avro-schemas" % "avro-schemas-tracking" % "6.0.518", - // Spark dependencies, exclude avro transitive dependencies to avoid being overriden by 1.7.x + // TODO: Cleanup Spark dependencies "org.apache.spark" % "spark-core_2.10" % "1.4.0" excludeAll( ExclusionRule(organization = "org.apache.avro"), ExclusionRule(organization = "org.apache.hadoop"), From 5cfd4496bc0490651f0fd636546f6bfc3110b250 Mon Sep 17 00:00:00 2001 From: akrai Date: Thu, 25 Feb 2016 19:16:56 +0530 Subject: [PATCH 134/272] LIHADOOP-17537: Replace bootstrap-datepicker library with a copy from github RB=668783 G=superfriends-reviewers R=annag,fli,shanm,viramach A=fli --- .../bootstrap-datepicker/css/datepicker.css | 468 +++- .../js/bootstrap-datepicker.js | 2390 +++++++++++++---- public/js/searchform.js | 10 +- 3 files changed, 2304 insertions(+), 564 deletions(-) diff --git a/public/assets/bootstrap-datepicker/css/datepicker.css b/public/assets/bootstrap-datepicker/css/datepicker.css index b7065b7d5..3dbdf4173 100755 --- a/public/assets/bootstrap-datepicker/css/datepicker.css +++ b/public/assets/bootstrap-datepicker/css/datepicker.css @@ -1,51 +1,88 @@ /*! - * Datepicker for Bootstrap + * Datepicker for Bootstrap v1.5.1 (https://github.com/eternicode/bootstrap-datepicker) * * Copyright 2012 Stefan Petre - * Licensed under the Apache License v2.0 - * http://www.apache.org/licenses/LICENSE-2.0 - * + * Improvements by Andrew Rowls + * Licensed under the Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) */ .datepicker { - top: 0; - left: 0; padding: 4px; - margin-top: 1px; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; - /*.dow { - border-top: 1px solid #ddd !important; - }*/ - + direction: ltr; +} +.datepicker-inline { + width: 220px; +} +.datepicker.datepicker-rtl { + direction: rtl; +} +.datepicker.datepicker-rtl table tr td span { + float: right; +} +.datepicker-dropdown { + top: 0; + left: 0; } -.datepicker:before { +.datepicker-dropdown:before { content: ''; display: inline-block; border-left: 7px solid transparent; border-right: 7px solid transparent; - border-bottom: 7px solid #ccc; + border-bottom: 7px solid #999999; + border-top: 0; border-bottom-color: rgba(0, 0, 0, 0.2); position: absolute; - top: -7px; - left: 6px; } -.datepicker:after { +.datepicker-dropdown:after { content: ''; display: inline-block; border-left: 6px solid transparent; border-right: 6px solid transparent; border-bottom: 6px solid #ffffff; + border-top: 0; position: absolute; - top: -6px; +} +.datepicker-dropdown.datepicker-orient-left:before { + left: 6px; +} +.datepicker-dropdown.datepicker-orient-left:after { left: 7px; } +.datepicker-dropdown.datepicker-orient-right:before { + right: 6px; +} +.datepicker-dropdown.datepicker-orient-right:after { + right: 7px; +} +.datepicker-dropdown.datepicker-orient-bottom:before { + top: -7px; +} +.datepicker-dropdown.datepicker-orient-bottom:after { + top: -6px; +} +.datepicker-dropdown.datepicker-orient-top:before { + bottom: -7px; + border-bottom: 0; + border-top: 7px solid #999999; +} +.datepicker-dropdown.datepicker-orient-top:after { + bottom: -6px; + border-bottom: 0; + border-top: 6px solid #ffffff; +} .datepicker > div { display: none; } .datepicker table { - width: 100%; margin: 0; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; } .datepicker td, .datepicker th { @@ -55,128 +92,381 @@ -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; + border: none; +} +.table-striped .datepicker table tr td, +.table-striped .datepicker table tr th { + background-color: transparent; } -.datepicker td.day:hover { +.datepicker table tr td.day:hover, +.datepicker table tr td.day.focused { background: #eeeeee; cursor: pointer; } -.datepicker td.day.disabled { - color: #eeeeee; +.datepicker table tr td.old, +.datepicker table tr td.new { + color: #999999; } -.datepicker td.old, -.datepicker td.new { +.datepicker table tr td.disabled, +.datepicker table tr td.disabled:hover { + background: none; color: #999999; + cursor: default; +} +.datepicker table tr td.highlighted { + background: #d9edf7; + border-radius: 0; +} +.datepicker table tr td.today, +.datepicker table tr td.today:hover, +.datepicker table tr td.today.disabled, +.datepicker table tr td.today.disabled:hover { + background-color: #fde19a; + background-image: -moz-linear-gradient(to bottom, #fdd49a, #fdf59a); + background-image: -ms-linear-gradient(to bottom, #fdd49a, #fdf59a); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fdd49a), to(#fdf59a)); + background-image: -webkit-linear-gradient(to bottom, #fdd49a, #fdf59a); + background-image: -o-linear-gradient(to bottom, #fdd49a, #fdf59a); + background-image: linear-gradient(to bottom, #fdd49a, #fdf59a); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fdd49a', endColorstr='#fdf59a', GradientType=0); + border-color: #fdf59a #fdf59a #fbed50; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + color: #000; +} +.datepicker table tr td.today:hover, +.datepicker table tr td.today:hover:hover, +.datepicker table tr td.today.disabled:hover, +.datepicker table tr td.today.disabled:hover:hover, +.datepicker table tr td.today:active, +.datepicker table tr td.today:hover:active, +.datepicker table tr td.today.disabled:active, +.datepicker table tr td.today.disabled:hover:active, +.datepicker table tr td.today.active, +.datepicker table tr td.today:hover.active, +.datepicker table tr td.today.disabled.active, +.datepicker table tr td.today.disabled:hover.active, +.datepicker table tr td.today.disabled, +.datepicker table tr td.today:hover.disabled, +.datepicker table tr td.today.disabled.disabled, +.datepicker table tr td.today.disabled:hover.disabled, +.datepicker table tr td.today[disabled], +.datepicker table tr td.today:hover[disabled], +.datepicker table tr td.today.disabled[disabled], +.datepicker table tr td.today.disabled:hover[disabled] { + background-color: #fdf59a; +} +.datepicker table tr td.today:active, +.datepicker table tr td.today:hover:active, +.datepicker table tr td.today.disabled:active, +.datepicker table tr td.today.disabled:hover:active, +.datepicker table tr td.today.active, +.datepicker table tr td.today:hover.active, +.datepicker table tr td.today.disabled.active, +.datepicker table tr td.today.disabled:hover.active { + background-color: #fbf069 \9; +} +.datepicker table tr td.today:hover:hover { + color: #000; } -.datepicker td.active, -.datepicker td.active:hover { - color: #ffffff; +.datepicker table tr td.today.active:hover { + color: #fff; +} +.datepicker table tr td.range, +.datepicker table tr td.range:hover, +.datepicker table tr td.range.disabled, +.datepicker table tr td.range.disabled:hover { + background: #eeeeee; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} +.datepicker table tr td.range.today, +.datepicker table tr td.range.today:hover, +.datepicker table tr td.range.today.disabled, +.datepicker table tr td.range.today.disabled:hover { + background-color: #f3d17a; + background-image: -moz-linear-gradient(to bottom, #f3c17a, #f3e97a); + background-image: -ms-linear-gradient(to bottom, #f3c17a, #f3e97a); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f3c17a), to(#f3e97a)); + background-image: -webkit-linear-gradient(to bottom, #f3c17a, #f3e97a); + background-image: -o-linear-gradient(to bottom, #f3c17a, #f3e97a); + background-image: linear-gradient(to bottom, #f3c17a, #f3e97a); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f3c17a', endColorstr='#f3e97a', GradientType=0); + border-color: #f3e97a #f3e97a #edde34; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} +.datepicker table tr td.range.today:hover, +.datepicker table tr td.range.today:hover:hover, +.datepicker table tr td.range.today.disabled:hover, +.datepicker table tr td.range.today.disabled:hover:hover, +.datepicker table tr td.range.today:active, +.datepicker table tr td.range.today:hover:active, +.datepicker table tr td.range.today.disabled:active, +.datepicker table tr td.range.today.disabled:hover:active, +.datepicker table tr td.range.today.active, +.datepicker table tr td.range.today:hover.active, +.datepicker table tr td.range.today.disabled.active, +.datepicker table tr td.range.today.disabled:hover.active, +.datepicker table tr td.range.today.disabled, +.datepicker table tr td.range.today:hover.disabled, +.datepicker table tr td.range.today.disabled.disabled, +.datepicker table tr td.range.today.disabled:hover.disabled, +.datepicker table tr td.range.today[disabled], +.datepicker table tr td.range.today:hover[disabled], +.datepicker table tr td.range.today.disabled[disabled], +.datepicker table tr td.range.today.disabled:hover[disabled] { + background-color: #f3e97a; +} +.datepicker table tr td.range.today:active, +.datepicker table tr td.range.today:hover:active, +.datepicker table tr td.range.today.disabled:active, +.datepicker table tr td.range.today.disabled:hover:active, +.datepicker table tr td.range.today.active, +.datepicker table tr td.range.today:hover.active, +.datepicker table tr td.range.today.disabled.active, +.datepicker table tr td.range.today.disabled:hover.active { + background-color: #efe24b \9; +} +.datepicker table tr td.selected, +.datepicker table tr td.selected:hover, +.datepicker table tr td.selected.disabled, +.datepicker table tr td.selected.disabled:hover { + background-color: #9e9e9e; + background-image: -moz-linear-gradient(to bottom, #b3b3b3, #808080); + background-image: -ms-linear-gradient(to bottom, #b3b3b3, #808080); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#b3b3b3), to(#808080)); + background-image: -webkit-linear-gradient(to bottom, #b3b3b3, #808080); + background-image: -o-linear-gradient(to bottom, #b3b3b3, #808080); + background-image: linear-gradient(to bottom, #b3b3b3, #808080); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#b3b3b3', endColorstr='#808080', GradientType=0); + border-color: #808080 #808080 #595959; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + color: #fff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.datepicker table tr td.selected:hover, +.datepicker table tr td.selected:hover:hover, +.datepicker table tr td.selected.disabled:hover, +.datepicker table tr td.selected.disabled:hover:hover, +.datepicker table tr td.selected:active, +.datepicker table tr td.selected:hover:active, +.datepicker table tr td.selected.disabled:active, +.datepicker table tr td.selected.disabled:hover:active, +.datepicker table tr td.selected.active, +.datepicker table tr td.selected:hover.active, +.datepicker table tr td.selected.disabled.active, +.datepicker table tr td.selected.disabled:hover.active, +.datepicker table tr td.selected.disabled, +.datepicker table tr td.selected:hover.disabled, +.datepicker table tr td.selected.disabled.disabled, +.datepicker table tr td.selected.disabled:hover.disabled, +.datepicker table tr td.selected[disabled], +.datepicker table tr td.selected:hover[disabled], +.datepicker table tr td.selected.disabled[disabled], +.datepicker table tr td.selected.disabled:hover[disabled] { + background-color: #808080; +} +.datepicker table tr td.selected:active, +.datepicker table tr td.selected:hover:active, +.datepicker table tr td.selected.disabled:active, +.datepicker table tr td.selected.disabled:hover:active, +.datepicker table tr td.selected.active, +.datepicker table tr td.selected:hover.active, +.datepicker table tr td.selected.disabled.active, +.datepicker table tr td.selected.disabled:hover.active { + background-color: #666666 \9; +} +.datepicker table tr td.active, +.datepicker table tr td.active:hover, +.datepicker table tr td.active.disabled, +.datepicker table tr td.active.disabled:hover { background-color: #006dcc; - background-image: -moz-linear-gradient(top, #0088cc, #0044cc); + background-image: -moz-linear-gradient(to bottom, #0088cc, #0044cc); + background-image: -ms-linear-gradient(to bottom, #0088cc, #0044cc); background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc)); - background-image: -webkit-linear-gradient(top, #0088cc, #0044cc); - background-image: -o-linear-gradient(top, #0088cc, #0044cc); + background-image: -webkit-linear-gradient(to bottom, #0088cc, #0044cc); + background-image: -o-linear-gradient(to bottom, #0088cc, #0044cc); background-image: linear-gradient(to bottom, #0088cc, #0044cc); background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0044cc', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0); border-color: #0044cc #0044cc #002a80; border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - *background-color: #0044cc; - /* Darken IE7 buttons by default so they stand out more given they won't have borders */ - - filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); color: #fff; text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); } -.datepicker td.active:hover, -.datepicker td.active:hover:hover, -.datepicker td.active:focus, -.datepicker td.active:hover:focus, -.datepicker td.active:active, -.datepicker td.active:hover:active, -.datepicker td.active.active, -.datepicker td.active:hover.active, -.datepicker td.active.disabled, -.datepicker td.active:hover.disabled, -.datepicker td.active[disabled], -.datepicker td.active:hover[disabled] { - color: #ffffff; +.datepicker table tr td.active:hover, +.datepicker table tr td.active:hover:hover, +.datepicker table tr td.active.disabled:hover, +.datepicker table tr td.active.disabled:hover:hover, +.datepicker table tr td.active:active, +.datepicker table tr td.active:hover:active, +.datepicker table tr td.active.disabled:active, +.datepicker table tr td.active.disabled:hover:active, +.datepicker table tr td.active.active, +.datepicker table tr td.active:hover.active, +.datepicker table tr td.active.disabled.active, +.datepicker table tr td.active.disabled:hover.active, +.datepicker table tr td.active.disabled, +.datepicker table tr td.active:hover.disabled, +.datepicker table tr td.active.disabled.disabled, +.datepicker table tr td.active.disabled:hover.disabled, +.datepicker table tr td.active[disabled], +.datepicker table tr td.active:hover[disabled], +.datepicker table tr td.active.disabled[disabled], +.datepicker table tr td.active.disabled:hover[disabled] { background-color: #0044cc; - *background-color: #003bb3; } -.datepicker td.active:active, -.datepicker td.active:hover:active, -.datepicker td.active.active, -.datepicker td.active:hover.active { +.datepicker table tr td.active:active, +.datepicker table tr td.active:hover:active, +.datepicker table tr td.active.disabled:active, +.datepicker table tr td.active.disabled:hover:active, +.datepicker table tr td.active.active, +.datepicker table tr td.active:hover.active, +.datepicker table tr td.active.disabled.active, +.datepicker table tr td.active.disabled:hover.active { background-color: #003399 \9; } -.datepicker td span { +.datepicker table tr td span { display: block; - width: 47px; + width: 23%; height: 54px; line-height: 54px; float: left; - margin: 2px; + margin: 1%; cursor: pointer; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; } -.datepicker td span:hover { +.datepicker table tr td span:hover { background: #eeeeee; } -.datepicker td span.active { - color: #ffffff; +.datepicker table tr td span.disabled, +.datepicker table tr td span.disabled:hover { + background: none; + color: #999999; + cursor: default; +} +.datepicker table tr td span.active, +.datepicker table tr td span.active:hover, +.datepicker table tr td span.active.disabled, +.datepicker table tr td span.active.disabled:hover { background-color: #006dcc; - background-image: -moz-linear-gradient(top, #0088cc, #0044cc); + background-image: -moz-linear-gradient(to bottom, #0088cc, #0044cc); + background-image: -ms-linear-gradient(to bottom, #0088cc, #0044cc); background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc)); - background-image: -webkit-linear-gradient(top, #0088cc, #0044cc); - background-image: -o-linear-gradient(top, #0088cc, #0044cc); + background-image: -webkit-linear-gradient(to bottom, #0088cc, #0044cc); + background-image: -o-linear-gradient(to bottom, #0088cc, #0044cc); background-image: linear-gradient(to bottom, #0088cc, #0044cc); background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0044cc', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0); border-color: #0044cc #0044cc #002a80; border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - *background-color: #0044cc; - /* Darken IE7 buttons by default so they stand out more given they won't have borders */ - - filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); color: #fff; text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); } -.datepicker td span.active:hover, -.datepicker td span.active:focus, -.datepicker td span.active:active, -.datepicker td span.active.active, -.datepicker td span.active.disabled, -.datepicker td span.active[disabled] { - color: #ffffff; +.datepicker table tr td span.active:hover, +.datepicker table tr td span.active:hover:hover, +.datepicker table tr td span.active.disabled:hover, +.datepicker table tr td span.active.disabled:hover:hover, +.datepicker table tr td span.active:active, +.datepicker table tr td span.active:hover:active, +.datepicker table tr td span.active.disabled:active, +.datepicker table tr td span.active.disabled:hover:active, +.datepicker table tr td span.active.active, +.datepicker table tr td span.active:hover.active, +.datepicker table tr td span.active.disabled.active, +.datepicker table tr td span.active.disabled:hover.active, +.datepicker table tr td span.active.disabled, +.datepicker table tr td span.active:hover.disabled, +.datepicker table tr td span.active.disabled.disabled, +.datepicker table tr td span.active.disabled:hover.disabled, +.datepicker table tr td span.active[disabled], +.datepicker table tr td span.active:hover[disabled], +.datepicker table tr td span.active.disabled[disabled], +.datepicker table tr td span.active.disabled:hover[disabled] { background-color: #0044cc; - *background-color: #003bb3; } -.datepicker td span.active:active, -.datepicker td span.active.active { +.datepicker table tr td span.active:active, +.datepicker table tr td span.active:hover:active, +.datepicker table tr td span.active.disabled:active, +.datepicker table tr td span.active.disabled:hover:active, +.datepicker table tr td span.active.active, +.datepicker table tr td span.active:hover.active, +.datepicker table tr td span.active.disabled.active, +.datepicker table tr td span.active.disabled:hover.active { background-color: #003399 \9; } -.datepicker td span.old { +.datepicker table tr td span.old, +.datepicker table tr td span.new { color: #999999; } -.datepicker th.switch { +.datepicker .datepicker-switch { width: 145px; } -.datepicker th.next, -.datepicker th.prev { - font-size: 21px; -} -.datepicker thead tr:first-child th { +.datepicker .datepicker-switch, +.datepicker .prev, +.datepicker .next, +.datepicker tfoot tr th { cursor: pointer; } -.datepicker thead tr:first-child th:hover { +.datepicker .datepicker-switch:hover, +.datepicker .prev:hover, +.datepicker .next:hover, +.datepicker tfoot tr th:hover { background: #eeeeee; } +.datepicker .cw { + font-size: 10px; + width: 12px; + padding: 0 2px 0 5px; + vertical-align: middle; +} +.input-append.date .add-on, +.input-prepend.date .add-on { + cursor: pointer; +} .input-append.date .add-on i, .input-prepend.date .add-on i { - display: block; - cursor: pointer; - width: 16px; - height: 16px; -} \ No newline at end of file + margin-top: 3px; +} +.input-daterange input { + text-align: center; +} +.input-daterange input:first-child { + -webkit-border-radius: 3px 0 0 3px; + -moz-border-radius: 3px 0 0 3px; + border-radius: 3px 0 0 3px; +} +.input-daterange input:last-child { + -webkit-border-radius: 0 3px 3px 0; + -moz-border-radius: 0 3px 3px 0; + border-radius: 0 3px 3px 0; +} +.input-daterange .add-on { + display: inline-block; + width: auto; + min-width: 16px; + height: 18px; + padding: 4px 5px; + font-weight: normal; + line-height: 18px; + text-align: center; + text-shadow: 0 1px 0 #ffffff; + vertical-align: middle; + background-color: #eeeeee; + border: 1px solid #ccc; + margin-left: -5px; + margin-right: -5px; +} diff --git a/public/assets/bootstrap-datepicker/js/bootstrap-datepicker.js b/public/assets/bootstrap-datepicker/js/bootstrap-datepicker.js index bf3a56df0..a1647da5e 100755 --- a/public/assets/bootstrap-datepicker/js/bootstrap-datepicker.js +++ b/public/assets/bootstrap-datepicker/js/bootstrap-datepicker.js @@ -1,474 +1,1918 @@ -/* ========================================================= - * bootstrap-datepicker.js - * http://www.eyecon.ro/bootstrap-datepicker - * ========================================================= - * Copyright 2012 Stefan Petre - * - * 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 +/*! + * Datepicker for Bootstrap v1.5.1 (https://github.com/eternicode/bootstrap-datepicker) * - * 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. - * ========================================================= */ - -!function( $ ) { - - // Picker object - - var Datepicker = function(element, options){ - this.element = $(element); - this.format = DPGlobal.parseFormat(options.format||this.element.data('date-format')||'mm/dd/yyyy'); - this.picker = $(DPGlobal.template) - .appendTo('body') - .on({ - click: $.proxy(this.click, this)//, - //mousedown: $.proxy(this.mousedown, this) - }); - this.isInput = this.element.is('input'); - this.component = this.element.is('.date') ? this.element.find('.add-on') : false; - - if (this.isInput) { - this.element.on({ - focus: $.proxy(this.show, this), - //blur: $.proxy(this.hide, this), - keyup: $.proxy(this.update, this) - }); - } else { - if (this.component){ - this.component.on('click', $.proxy(this.show, this)); - } else { - this.element.on('click', $.proxy(this.show, this)); - } - } - - this.minViewMode = options.minViewMode||this.element.data('date-minviewmode')||0; - if (typeof this.minViewMode === 'string') { - switch (this.minViewMode) { - case 'months': - this.minViewMode = 1; - break; - case 'years': - this.minViewMode = 2; - break; - default: - this.minViewMode = 0; - break; - } - } - this.viewMode = options.viewMode||this.element.data('date-viewmode')||0; - if (typeof this.viewMode === 'string') { - switch (this.viewMode) { - case 'months': - this.viewMode = 1; - break; - case 'years': - this.viewMode = 2; - break; - default: - this.viewMode = 0; - break; - } - } - this.startViewMode = this.viewMode; - this.weekStart = options.weekStart||this.element.data('date-weekstart')||0; - this.weekEnd = this.weekStart === 0 ? 6 : this.weekStart - 1; - this.onRender = options.onRender; - this.fillDow(); - this.fillMonths(); - this.update(); - this.showMode(); - }; - - Datepicker.prototype = { - constructor: Datepicker, - - show: function(e) { - this.picker.show(); - this.height = this.component ? this.component.outerHeight() : this.element.outerHeight(); - this.place(); - $(window).on('resize', $.proxy(this.place, this)); - if (e ) { - e.stopPropagation(); - e.preventDefault(); - } - if (!this.isInput) { - } - var that = this; - $(document).on('mousedown', function(ev){ - if ($(ev.target).closest('.datepicker').length == 0) { - that.hide(); - } - }); - this.element.trigger({ - type: 'show', - date: this.date - }); - }, - - hide: function(){ - this.picker.hide(); - $(window).off('resize', this.place); - this.viewMode = this.startViewMode; - this.showMode(); - if (!this.isInput) { - $(document).off('mousedown', this.hide); - } - //this.set(); - this.element.trigger({ - type: 'hide', - date: this.date - }); - }, - - set: function() { - var formated = DPGlobal.formatDate(this.date, this.format); - if (!this.isInput) { - if (this.component){ - this.element.find('input').prop('value', formated); - } - this.element.data('date', formated); - } else { - this.element.prop('value', formated); - } - }, - - setValue: function(newDate) { - if (typeof newDate === 'string') { - this.date = DPGlobal.parseDate(newDate, this.format); - } else { - this.date = new Date(newDate); - } - this.set(); - this.viewDate = new Date(this.date.getFullYear(), this.date.getMonth(), 1, 0, 0, 0, 0); - this.fill(); - }, - - place: function(){ - var offset = this.component ? this.component.offset() : this.element.offset(); - this.picker.css({ - top: offset.top + this.height, - left: offset.left - }); - }, - - update: function(newDate){ - this.date = DPGlobal.parseDate( - typeof newDate === 'string' ? newDate : (this.isInput ? this.element.prop('value') : this.element.data('date')), - this.format - ); - this.viewDate = new Date(this.date.getFullYear(), this.date.getMonth(), 1, 0, 0, 0, 0); - this.fill(); - }, - - fillDow: function(){ - var dowCnt = this.weekStart; - var html = '
'; - while (dowCnt < this.weekStart + 7) { - html += ''; - } - html += ''; - this.picker.find('.datepicker-days thead').append(html); - }, - - fillMonths: function(){ - var html = ''; - var i = 0 - while (i < 12) { - html += ''+DPGlobal.dates.monthsShort[i++]+''; - } - this.picker.find('.datepicker-months td').append(html); - }, - - fill: function() { - var d = new Date(this.viewDate), - year = d.getFullYear(), - month = d.getMonth(), - currentDate = this.date.valueOf(); - this.picker.find('.datepicker-days th:eq(1)') - .text(DPGlobal.dates.months[month]+' '+year); - var prevMonth = new Date(year, month-1, 28,0,0,0,0), - day = DPGlobal.getDaysInMonth(prevMonth.getFullYear(), prevMonth.getMonth()); - prevMonth.setDate(day); - prevMonth.setDate(day - (prevMonth.getDay() - this.weekStart + 7)%7); - var nextMonth = new Date(prevMonth); - nextMonth.setDate(nextMonth.getDate() + 42); - nextMonth = nextMonth.valueOf(); - var html = []; - var clsName, - prevY, - prevM; - while(prevMonth.valueOf() < nextMonth) { - if (prevMonth.getDay() === this.weekStart) { - html.push(''); - } - clsName = this.onRender(prevMonth); - prevY = prevMonth.getFullYear(); - prevM = prevMonth.getMonth(); - if ((prevM < month && prevY === year) || prevY < year) { - clsName += ' old'; - } else if ((prevM > month && prevY === year) || prevY > year) { - clsName += ' new'; - } - if (prevMonth.valueOf() === currentDate) { - clsName += ' active'; - } - html.push(''); - if (prevMonth.getDay() === this.weekEnd) { - html.push(''); - } - prevMonth.setDate(prevMonth.getDate()+1); - } - this.picker.find('.datepicker-days tbody').empty().append(html.join('')); - var currentYear = this.date.getFullYear(); - - var months = this.picker.find('.datepicker-months') - .find('th:eq(1)') - .text(year) - .end() - .find('span').removeClass('active'); - if (currentYear === year) { - months.eq(this.date.getMonth()).addClass('active'); - } - - html = ''; - year = parseInt(year/10, 10) * 10; - var yearCont = this.picker.find('.datepicker-years') - .find('th:eq(1)') - .text(year + '-' + (year + 9)) - .end() - .find('td'); - year -= 1; - for (var i = -1; i < 11; i++) { - html += ''+year+''; - year += 1; - } - yearCont.html(html); - }, - - click: function(e) { - e.stopPropagation(); - e.preventDefault(); - var target = $(e.target).closest('span, td, th'); - if (target.length === 1) { - switch(target[0].nodeName.toLowerCase()) { - case 'th': - switch(target[0].className) { - case 'switch': - this.showMode(1); - break; - case 'prev': - case 'next': - this.viewDate['set'+DPGlobal.modes[this.viewMode].navFnc].call( - this.viewDate, - this.viewDate['get'+DPGlobal.modes[this.viewMode].navFnc].call(this.viewDate) + - DPGlobal.modes[this.viewMode].navStep * (target[0].className === 'prev' ? -1 : 1) - ); - this.fill(); - this.set(); - break; - } - break; - case 'span': - if (target.is('.month')) { - var month = target.parent().find('span').index(target); - this.viewDate.setMonth(month); - } else { - var year = parseInt(target.text(), 10)||0; - this.viewDate.setFullYear(year); - } - if (this.viewMode !== 0) { - this.date = new Date(this.viewDate); - this.element.trigger({ - type: 'changeDate', - date: this.date, - viewMode: DPGlobal.modes[this.viewMode].clsName - }); - } - this.showMode(-1); - this.fill(); - this.set(); - break; - case 'td': - if (target.is('.day') && !target.is('.disabled')){ - var day = parseInt(target.text(), 10)||1; - var month = this.viewDate.getMonth(); - if (target.is('.old')) { - month -= 1; - } else if (target.is('.new')) { - month += 1; - } - var year = this.viewDate.getFullYear(); - this.date = new Date(year, month, day,0,0,0,0); - this.viewDate = new Date(year, month, Math.min(28, day),0,0,0,0); - this.fill(); - this.set(); - this.element.trigger({ - type: 'changeDate', - date: this.date, - viewMode: DPGlobal.modes[this.viewMode].clsName - }); - } - break; - } - } - }, - - mousedown: function(e){ - e.stopPropagation(); - e.preventDefault(); - }, - - showMode: function(dir) { - if (dir) { - this.viewMode = Math.max(this.minViewMode, Math.min(2, this.viewMode + dir)); - } - this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).show(); - } - }; - - $.fn.datepicker = function ( option, val ) { - return this.each(function () { - var $this = $(this), - data = $this.data('datepicker'), - options = typeof option === 'object' && option; - if (!data) { - $this.data('datepicker', (data = new Datepicker(this, $.extend({}, $.fn.datepicker.defaults,options)))); - } - if (typeof option === 'string') data[option](val); - }); - }; - - $.fn.datepicker.defaults = { - onRender: function(date) { - return ''; - } - }; - $.fn.datepicker.Constructor = Datepicker; - - var DPGlobal = { - modes: [ - { - clsName: 'days', - navFnc: 'Month', - navStep: 1 - }, - { - clsName: 'months', - navFnc: 'FullYear', - navStep: 1 - }, - { - clsName: 'years', - navFnc: 'FullYear', - navStep: 10 - }], - dates:{ - days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"], - daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"], - daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"], - months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], - monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] - }, - isLeapYear: function (year) { - return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0)) - }, - getDaysInMonth: function (year, month) { - return [31, (DPGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month] - }, - parseFormat: function(format){ - var separator = format.match(/[.\/\-\s].*?/), - parts = format.split(/\W+/); - if (!separator || !parts || parts.length === 0){ - throw new Error("Invalid date format."); - } - return {separator: separator, parts: parts}; - }, - parseDate: function(date, format) { - var parts = date.split(format.separator), - date = new Date(), - val; - date.setHours(0); - date.setMinutes(0); - date.setSeconds(0); - date.setMilliseconds(0); - if (parts.length === format.parts.length) { - var year = date.getFullYear(), day = date.getDate(), month = date.getMonth(); - for (var i=0, cnt = format.parts.length; i < cnt; i++) { - val = parseInt(parts[i], 10)||1; - switch(format.parts[i]) { - case 'dd': - case 'd': - day = val; - date.setDate(val); - break; - case 'mm': - case 'm': - month = val - 1; - date.setMonth(val - 1); - break; - case 'yy': - year = 2000 + val; - date.setFullYear(2000 + val); - break; - case 'yyyy': - year = val; - date.setFullYear(val); - break; - } - } - date = new Date(year, month, day, 0 ,0 ,0); - } - return date; - }, - formatDate: function(date, format){ - var val = { - d: date.getDate(), - m: date.getMonth() + 1, - yy: date.getFullYear().toString().substring(2), - yyyy: date.getFullYear() - }; - val.dd = (val.d < 10 ? '0' : '') + val.d; - val.mm = (val.m < 10 ? '0' : '') + val.m; - var date = []; - for (var i=0, cnt = format.parts.length; i < cnt; i++) { - date.push(val[format.parts[i]]); - } - return date.join(format.separator); - }, - headTemplate: ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - '', - contTemplate: '' - }; - DPGlobal.template = '
@cell
'+DPGlobal.dates.daysMin[(dowCnt++)%7]+'
'+prevMonth.getDate() + '
'+ - DPGlobal.headTemplate+ - ''+ - '
'+ - '
'+ - '
'+ - ''+ - DPGlobal.headTemplate+ - DPGlobal.contTemplate+ - '
'+ - '
'+ - '
'+ - ''+ - DPGlobal.headTemplate+ - DPGlobal.contTemplate+ - '
'+ - '
'+ - '
'; - -}( window.jQuery ); \ No newline at end of file + * Copyright 2012 Stefan Petre + * Improvements by Andrew Rowls + * Licensed under the Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) + */(function(factory){ + if (typeof define === "function" && define.amd) { + define(["jquery"], factory); + } else if (typeof exports === 'object') { + factory(require('jquery')); + } else { + factory(jQuery); + } +}(function($, undefined){ + + function UTCDate(){ + return new Date(Date.UTC.apply(Date, arguments)); + } + function UTCToday(){ + var today = new Date(); + return UTCDate(today.getFullYear(), today.getMonth(), today.getDate()); + } + function isUTCEquals(date1, date2) { + return ( + date1.getUTCFullYear() === date2.getUTCFullYear() && + date1.getUTCMonth() === date2.getUTCMonth() && + date1.getUTCDate() === date2.getUTCDate() + ); + } + function alias(method){ + return function(){ + return this[method].apply(this, arguments); + }; + } + function isValidDate(d) { + return d && !isNaN(d.getTime()); + } + + var DateArray = (function(){ + var extras = { + get: function(i){ + return this.slice(i)[0]; + }, + contains: function(d){ + // Array.indexOf is not cross-browser; + // $.inArray doesn't work with Dates + var val = d && d.valueOf(); + for (var i=0, l=this.length; i < l; i++) + if (this[i].valueOf() === val) + return i; + return -1; + }, + remove: function(i){ + this.splice(i,1); + }, + replace: function(new_array){ + if (!new_array) + return; + if (!$.isArray(new_array)) + new_array = [new_array]; + this.clear(); + this.push.apply(this, new_array); + }, + clear: function(){ + this.length = 0; + }, + copy: function(){ + var a = new DateArray(); + a.replace(this); + return a; + } + }; + + return function(){ + var a = []; + a.push.apply(a, arguments); + $.extend(a, extras); + return a; + }; + })(); + + + // Picker object + + var Datepicker = function(element, options){ + $(element).data('datepicker', this); + this._process_options(options); + + this.dates = new DateArray(); + this.viewDate = this.o.defaultViewDate; + this.focusDate = null; + + this.element = $(element); + this.isInline = false; + this.isInput = this.element.is('input'); + this.component = this.element.hasClass('date') ? this.element.find('.add-on, .input-group-addon, .btn') : false; + this.hasInput = this.component && this.element.find('input').length; + if (this.component && this.component.length === 0) + this.component = false; + + this.picker = $(DPGlobal.template); + this._buildEvents(); + this._attachEvents(); + + if (this.isInline){ + this.picker.addClass('datepicker-inline').appendTo(this.element); + } + else { + this.picker.addClass('datepicker-dropdown dropdown-menu'); + } + + if (this.o.rtl){ + this.picker.addClass('datepicker-rtl'); + } + + this.viewMode = this.o.startView; + + if (this.o.calendarWeeks) + this.picker.find('thead .datepicker-title, tfoot .today, tfoot .clear') + .attr('colspan', function(i, val){ + return parseInt(val) + 1; + }); + + this._allow_update = false; + + this.setStartDate(this._o.startDate); + this.setEndDate(this._o.endDate); + this.setDaysOfWeekDisabled(this.o.daysOfWeekDisabled); + this.setDaysOfWeekHighlighted(this.o.daysOfWeekHighlighted); + this.setDatesDisabled(this.o.datesDisabled); + + this.fillDow(); + this.fillMonths(); + + this._allow_update = true; + + this.update(); + this.showMode(); + + if (this.isInline){ + this.show(); + } + }; + + Datepicker.prototype = { + constructor: Datepicker, + + _process_options: function(opts){ + // Store raw options for reference + this._o = $.extend({}, this._o, opts); + // Processed options + var o = this.o = $.extend({}, this._o); + + // Check if "de-DE" style date is available, if not language should + // fallback to 2 letter code eg "de" + var lang = o.language; + if (!dates[lang]){ + lang = lang.split('-')[0]; + if (!dates[lang]) + lang = defaults.language; + } + o.language = lang; + + switch (o.startView){ + case 2: + case 'decade': + o.startView = 2; + break; + case 1: + case 'year': + o.startView = 1; + break; + default: + o.startView = 0; + } + + switch (o.minViewMode){ + case 1: + case 'months': + o.minViewMode = 1; + break; + case 2: + case 'years': + o.minViewMode = 2; + break; + default: + o.minViewMode = 0; + } + + switch (o.maxViewMode) { + case 0: + case 'days': + o.maxViewMode = 0; + break; + case 1: + case 'months': + o.maxViewMode = 1; + break; + default: + o.maxViewMode = 2; + } + + o.startView = Math.min(o.startView, o.maxViewMode); + o.startView = Math.max(o.startView, o.minViewMode); + + // true, false, or Number > 0 + if (o.multidate !== true){ + o.multidate = Number(o.multidate) || false; + if (o.multidate !== false) + o.multidate = Math.max(0, o.multidate); + } + o.multidateSeparator = String(o.multidateSeparator); + + o.weekStart %= 7; + o.weekEnd = (o.weekStart + 6) % 7; + + var format = DPGlobal.parseFormat(o.format); + if (o.startDate !== -Infinity){ + if (!!o.startDate){ + if (o.startDate instanceof Date) + o.startDate = this._local_to_utc(this._zero_time(o.startDate)); + else + o.startDate = DPGlobal.parseDate(o.startDate, format, o.language); + } + else { + o.startDate = -Infinity; + } + } + if (o.endDate !== Infinity){ + if (!!o.endDate){ + if (o.endDate instanceof Date) + o.endDate = this._local_to_utc(this._zero_time(o.endDate)); + else + o.endDate = DPGlobal.parseDate(o.endDate, format, o.language); + } + else { + o.endDate = Infinity; + } + } + + o.daysOfWeekDisabled = o.daysOfWeekDisabled||[]; + if (!$.isArray(o.daysOfWeekDisabled)) + o.daysOfWeekDisabled = o.daysOfWeekDisabled.split(/[,\s]*/); + o.daysOfWeekDisabled = $.map(o.daysOfWeekDisabled, function(d){ + return parseInt(d, 10); + }); + + o.daysOfWeekHighlighted = o.daysOfWeekHighlighted||[]; + if (!$.isArray(o.daysOfWeekHighlighted)) + o.daysOfWeekHighlighted = o.daysOfWeekHighlighted.split(/[,\s]*/); + o.daysOfWeekHighlighted = $.map(o.daysOfWeekHighlighted, function(d){ + return parseInt(d, 10); + }); + + o.datesDisabled = o.datesDisabled||[]; + if (!$.isArray(o.datesDisabled)) { + var datesDisabled = []; + datesDisabled.push(DPGlobal.parseDate(o.datesDisabled, format, o.language)); + o.datesDisabled = datesDisabled; + } + o.datesDisabled = $.map(o.datesDisabled,function(d){ + return DPGlobal.parseDate(d, format, o.language); + }); + + var plc = String(o.orientation).toLowerCase().split(/\s+/g), + _plc = o.orientation.toLowerCase(); + plc = $.grep(plc, function(word){ + return /^auto|left|right|top|bottom$/.test(word); + }); + o.orientation = {x: 'auto', y: 'auto'}; + if (!_plc || _plc === 'auto') + ; // no action + else if (plc.length === 1){ + switch (plc[0]){ + case 'top': + case 'bottom': + o.orientation.y = plc[0]; + break; + case 'left': + case 'right': + o.orientation.x = plc[0]; + break; + } + } + else { + _plc = $.grep(plc, function(word){ + return /^left|right$/.test(word); + }); + o.orientation.x = _plc[0] || 'auto'; + + _plc = $.grep(plc, function(word){ + return /^top|bottom$/.test(word); + }); + o.orientation.y = _plc[0] || 'auto'; + } + if (o.defaultViewDate) { + var year = o.defaultViewDate.year || new Date().getFullYear(); + var month = o.defaultViewDate.month || 0; + var day = o.defaultViewDate.day || 1; + o.defaultViewDate = UTCDate(year, month, day); + } else { + o.defaultViewDate = UTCToday(); + } + }, + _events: [], + _secondaryEvents: [], + _applyEvents: function(evs){ + for (var i=0, el, ch, ev; i < evs.length; i++){ + el = evs[i][0]; + if (evs[i].length === 2){ + ch = undefined; + ev = evs[i][1]; + } + else if (evs[i].length === 3){ + ch = evs[i][1]; + ev = evs[i][2]; + } + el.on(ev, ch); + } + }, + _unapplyEvents: function(evs){ + for (var i=0, el, ev, ch; i < evs.length; i++){ + el = evs[i][0]; + if (evs[i].length === 2){ + ch = undefined; + ev = evs[i][1]; + } + else if (evs[i].length === 3){ + ch = evs[i][1]; + ev = evs[i][2]; + } + el.off(ev, ch); + } + }, + _buildEvents: function(){ + var events = { + keyup: $.proxy(function(e){ + if ($.inArray(e.keyCode, [27, 37, 39, 38, 40, 32, 13, 9]) === -1) + this.update(); + }, this), + keydown: $.proxy(this.keydown, this), + paste: $.proxy(this.paste, this) + }; + + if (this.o.showOnFocus === true) { + events.focus = $.proxy(this.show, this); + } + + if (this.isInput) { // single input + this._events = [ + [this.element, events] + ]; + } + else if (this.component && this.hasInput) { // component: input + button + this._events = [ + // For components that are not readonly, allow keyboard nav + [this.element.find('input'), events], + [this.component, { + click: $.proxy(this.show, this) + }] + ]; + } + else if (this.element.is('div')){ // inline datepicker + this.isInline = true; + } + else { + this._events = [ + [this.element, { + click: $.proxy(this.show, this) + }] + ]; + } + this._events.push( + // Component: listen for blur on element descendants + [this.element, '*', { + blur: $.proxy(function(e){ + this._focused_from = e.target; + }, this) + }], + // Input: listen for blur on element + [this.element, { + blur: $.proxy(function(e){ + this._focused_from = e.target; + }, this) + }] + ); + + if (this.o.immediateUpdates) { + // Trigger input updates immediately on changed year/month + this._events.push([this.element, { + 'changeYear changeMonth': $.proxy(function(e){ + this.update(e.date); + }, this) + }]); + } + + this._secondaryEvents = [ + [this.picker, { + click: $.proxy(this.click, this) + }], + [$(window), { + resize: $.proxy(this.place, this) + }], + [$(document), { + mousedown: $.proxy(function(e){ + // Clicked outside the datepicker, hide it + if (!( + this.element.is(e.target) || + this.element.find(e.target).length || + this.picker.is(e.target) || + this.picker.find(e.target).length || + this.picker.hasClass('datepicker-inline') + )){ + this.hide(); + } + }, this) + }] + ]; + }, + _attachEvents: function(){ + this._detachEvents(); + this._applyEvents(this._events); + }, + _detachEvents: function(){ + this._unapplyEvents(this._events); + }, + _attachSecondaryEvents: function(){ + this._detachSecondaryEvents(); + this._applyEvents(this._secondaryEvents); + }, + _detachSecondaryEvents: function(){ + this._unapplyEvents(this._secondaryEvents); + }, + _trigger: function(event, altdate){ + var date = altdate || this.dates.get(-1), + local_date = this._utc_to_local(date); + + this.element.trigger({ + type: event, + date: local_date, + dates: $.map(this.dates, this._utc_to_local), + format: $.proxy(function(ix, format){ + if (arguments.length === 0){ + ix = this.dates.length - 1; + format = this.o.format; + } + else if (typeof ix === 'string'){ + format = ix; + ix = this.dates.length - 1; + } + format = format || this.o.format; + var date = this.dates.get(ix); + return DPGlobal.formatDate(date, format, this.o.language); + }, this) + }); + }, + + show: function(){ + var element = this.component ? this.element.find('input') : this.element; + if (element.attr('readonly') && this.o.enableOnReadonly === false) + return; + if (!this.isInline) + this.picker.appendTo(this.o.container); + this.place(); + this.picker.show(); + this._attachSecondaryEvents(); + this._trigger('show'); + if ((window.navigator.msMaxTouchPoints || 'ontouchstart' in document) && this.o.disableTouchKeyboard) { + $(this.element).blur(); + } + return this; + }, + + hide: function(){ + if (this.isInline) + return this; + if (!this.picker.is(':visible')) + return this; + this.focusDate = null; + this.picker.hide().detach(); + this._detachSecondaryEvents(); + this.viewMode = this.o.startView; + this.showMode(); + + if ( + this.o.forceParse && + ( + this.isInput && this.element.val() || + this.hasInput && this.element.find('input').val() + ) + ) + this.setValue(); + this._trigger('hide'); + return this; + }, + + remove: function(){ + this.hide(); + this._detachEvents(); + this._detachSecondaryEvents(); + this.picker.remove(); + delete this.element.data().datepicker; + if (!this.isInput){ + delete this.element.data().date; + } + return this; + }, + + paste: function(evt){ + var dateString; + if (evt.originalEvent.clipboardData && evt.originalEvent.clipboardData.types + && $.inArray('text/plain', evt.originalEvent.clipboardData.types) !== -1) { + dateString = evt.originalEvent.clipboardData.getData('text/plain'); + } + else if (window.clipboardData) { + dateString = window.clipboardData.getData('Text'); + } + else { + return; + } + this.setDate(dateString); + this.update(); + evt.preventDefault(); + }, + + _utc_to_local: function(utc){ + return utc && new Date(utc.getTime() + (utc.getTimezoneOffset()*60000)); + }, + _local_to_utc: function(local){ + return local && new Date(local.getTime() - (local.getTimezoneOffset()*60000)); + }, + _zero_time: function(local){ + return local && new Date(local.getFullYear(), local.getMonth(), local.getDate()); + }, + _zero_utc_time: function(utc){ + return utc && new Date(Date.UTC(utc.getUTCFullYear(), utc.getUTCMonth(), utc.getUTCDate())); + }, + + getDates: function(){ + return $.map(this.dates, this._utc_to_local); + }, + + getUTCDates: function(){ + return $.map(this.dates, function(d){ + return new Date(d); + }); + }, + + getDate: function(){ + return this._utc_to_local(this.getUTCDate()); + }, + + getUTCDate: function(){ + var selected_date = this.dates.get(-1); + if (typeof selected_date !== 'undefined') { + return new Date(selected_date); + } else { + return null; + } + }, + + clearDates: function(){ + var element; + if (this.isInput) { + element = this.element; + } else if (this.component) { + element = this.element.find('input'); + } + + if (element) { + element.val(''); + } + + this.update(); + this._trigger('changeDate'); + + if (this.o.autoclose) { + this.hide(); + } + }, + setDates: function(){ + var args = $.isArray(arguments[0]) ? arguments[0] : arguments; + this.update.apply(this, args); + this._trigger('changeDate'); + this.setValue(); + return this; + }, + + setUTCDates: function(){ + var args = $.isArray(arguments[0]) ? arguments[0] : arguments; + this.update.apply(this, $.map(args, this._utc_to_local)); + this._trigger('changeDate'); + this.setValue(); + return this; + }, + + setDate: alias('setDates'), + setUTCDate: alias('setUTCDates'), + + setValue: function(){ + var formatted = this.getFormattedDate(); + if (!this.isInput){ + if (this.component){ + this.element.find('input').val(formatted); + } + } + else { + this.element.val(formatted); + } + return this; + }, + + getFormattedDate: function(format){ + if (format === undefined) + format = this.o.format; + + var lang = this.o.language; + return $.map(this.dates, function(d){ + return DPGlobal.formatDate(d, format, lang); + }).join(this.o.multidateSeparator); + }, + + setStartDate: function(startDate){ + this._process_options({startDate: startDate}); + this.update(); + this.updateNavArrows(); + return this; + }, + + setEndDate: function(endDate){ + this._process_options({endDate: endDate}); + this.update(); + this.updateNavArrows(); + return this; + }, + + setDaysOfWeekDisabled: function(daysOfWeekDisabled){ + this._process_options({daysOfWeekDisabled: daysOfWeekDisabled}); + this.update(); + this.updateNavArrows(); + return this; + }, + + setDaysOfWeekHighlighted: function(daysOfWeekHighlighted){ + this._process_options({daysOfWeekHighlighted: daysOfWeekHighlighted}); + this.update(); + return this; + }, + + setDatesDisabled: function(datesDisabled){ + this._process_options({datesDisabled: datesDisabled}); + this.update(); + this.updateNavArrows(); + }, + + place: function(){ + if (this.isInline) + return this; + var calendarWidth = this.picker.outerWidth(), + calendarHeight = this.picker.outerHeight(), + visualPadding = 10, + container = $(this.o.container), + windowWidth = container.width(), + scrollTop = this.o.container === 'body' ? $(document).scrollTop() : container.scrollTop(), + appendOffset = container.offset(); + + var parentsZindex = []; + this.element.parents().each(function(){ + var itemZIndex = $(this).css('z-index'); + if (itemZIndex !== 'auto' && itemZIndex !== 0) parentsZindex.push(parseInt(itemZIndex)); + }); + var zIndex = Math.max.apply(Math, parentsZindex) + this.o.zIndexOffset; + var offset = this.component ? this.component.parent().offset() : this.element.offset(); + var height = this.component ? this.component.outerHeight(true) : this.element.outerHeight(false); + var width = this.component ? this.component.outerWidth(true) : this.element.outerWidth(false); + var left = offset.left - appendOffset.left, + top = offset.top - appendOffset.top; + + if (this.o.container !== 'body') { + top += scrollTop; + } + + this.picker.removeClass( + 'datepicker-orient-top datepicker-orient-bottom '+ + 'datepicker-orient-right datepicker-orient-left' + ); + + if (this.o.orientation.x !== 'auto'){ + this.picker.addClass('datepicker-orient-' + this.o.orientation.x); + if (this.o.orientation.x === 'right') + left -= calendarWidth - width; + } + // auto x orientation is best-placement: if it crosses a window + // edge, fudge it sideways + else { + if (offset.left < 0) { + // component is outside the window on the left side. Move it into visible range + this.picker.addClass('datepicker-orient-left'); + left -= offset.left - visualPadding; + } else if (left + calendarWidth > windowWidth) { + // the calendar passes the widow right edge. Align it to component right side + this.picker.addClass('datepicker-orient-right'); + left += width - calendarWidth; + } else { + // Default to left + this.picker.addClass('datepicker-orient-left'); + } + } + + // auto y orientation is best-situation: top or bottom, no fudging, + // decision based on which shows more of the calendar + var yorient = this.o.orientation.y, + top_overflow; + if (yorient === 'auto'){ + top_overflow = -scrollTop + top - calendarHeight; + yorient = top_overflow < 0 ? 'bottom' : 'top'; + } + + this.picker.addClass('datepicker-orient-' + yorient); + if (yorient === 'top') + top -= calendarHeight + parseInt(this.picker.css('padding-top')); + else + top += height; + + if (this.o.rtl) { + var right = windowWidth - (left + width); + this.picker.css({ + top: top, + right: right, + zIndex: zIndex + }); + } else { + this.picker.css({ + top: top, + left: left, + zIndex: zIndex + }); + } + return this; + }, + + _allow_update: true, + update: function(){ + if (!this._allow_update) + return this; + + var oldDates = this.dates.copy(), + dates = [], + fromArgs = false; + if (arguments.length){ + $.each(arguments, $.proxy(function(i, date){ + if (date instanceof Date) + date = this._local_to_utc(date); + dates.push(date); + }, this)); + fromArgs = true; + } + else { + dates = this.isInput + ? this.element.val() + : this.element.data('date') || this.element.find('input').val(); + if (dates && this.o.multidate) + dates = dates.split(this.o.multidateSeparator); + else + dates = [dates]; + delete this.element.data().date; + } + + dates = $.map(dates, $.proxy(function(date){ + return DPGlobal.parseDate(date, this.o.format, this.o.language); + }, this)); + dates = $.grep(dates, $.proxy(function(date){ + return ( + !this.dateWithinRange(date) || + !date + ); + }, this), true); + this.dates.replace(dates); + + if (this.dates.length) + this.viewDate = new Date(this.dates.get(-1)); + else if (this.viewDate < this.o.startDate) + this.viewDate = new Date(this.o.startDate); + else if (this.viewDate > this.o.endDate) + this.viewDate = new Date(this.o.endDate); + else + this.viewDate = this.o.defaultViewDate; + + if (fromArgs){ + // setting date by clicking + this.setValue(); + } + else if (dates.length){ + // setting date by typing + if (String(oldDates) !== String(this.dates)) + this._trigger('changeDate'); + } + if (!this.dates.length && oldDates.length) + this._trigger('clearDate'); + + this.fill(); + this.element.change(); + return this; + }, + + fillDow: function(){ + var dowCnt = this.o.weekStart, + html = ''; + if (this.o.calendarWeeks){ + this.picker.find('.datepicker-days .datepicker-switch') + .attr('colspan', function(i, val){ + return parseInt(val) + 1; + }); + html += ' '; + } + while (dowCnt < this.o.weekStart + 7){ + html += ''+dates[this.o.language].daysMin[(dowCnt++)%7]+''; + } + html += ''; + this.picker.find('.datepicker-days thead').append(html); + }, + + fillMonths: function(){ + var html = '', + i = 0; + while (i < 12){ + html += ''+dates[this.o.language].monthsShort[i++]+''; + } + this.picker.find('.datepicker-months td').html(html); + }, + + setRange: function(range){ + if (!range || !range.length) + delete this.range; + else + this.range = $.map(range, function(d){ + return d.valueOf(); + }); + this.fill(); + }, + + getClassNames: function(date){ + var cls = [], + year = this.viewDate.getUTCFullYear(), + month = this.viewDate.getUTCMonth(), + today = new Date(); + if (date.getUTCFullYear() < year || (date.getUTCFullYear() === year && date.getUTCMonth() < month)){ + cls.push('old'); + } + else if (date.getUTCFullYear() > year || (date.getUTCFullYear() === year && date.getUTCMonth() > month)){ + cls.push('new'); + } + if (this.focusDate && date.valueOf() === this.focusDate.valueOf()) + cls.push('focused'); + // Compare internal UTC date with local today, not UTC today + if (this.o.todayHighlight && + date.getUTCFullYear() === today.getFullYear() && + date.getUTCMonth() === today.getMonth() && + date.getUTCDate() === today.getDate()){ + cls.push('today'); + } + if (this.dates.contains(date) !== -1) + cls.push('active'); + if (!this.dateWithinRange(date) || this.dateIsDisabled(date)){ + cls.push('disabled'); + } + if ($.inArray(date.getUTCDay(), this.o.daysOfWeekHighlighted) !== -1){ + cls.push('highlighted'); + } + + if (this.range){ + if (date > this.range[0] && date < this.range[this.range.length-1]){ + cls.push('range'); + } + if ($.inArray(date.valueOf(), this.range) !== -1){ + cls.push('selected'); + } + if (date.valueOf() === this.range[0]){ + cls.push('range-start'); + } + if (date.valueOf() === this.range[this.range.length-1]){ + cls.push('range-end'); + } + } + return cls; + }, + + fill: function(){ + var d = new Date(this.viewDate), + year = d.getUTCFullYear(), + month = d.getUTCMonth(), + startYear = this.o.startDate !== -Infinity ? this.o.startDate.getUTCFullYear() : -Infinity, + startMonth = this.o.startDate !== -Infinity ? this.o.startDate.getUTCMonth() : -Infinity, + endYear = this.o.endDate !== Infinity ? this.o.endDate.getUTCFullYear() : Infinity, + endMonth = this.o.endDate !== Infinity ? this.o.endDate.getUTCMonth() : Infinity, + todaytxt = dates[this.o.language].today || dates['en'].today || '', + cleartxt = dates[this.o.language].clear || dates['en'].clear || '', + titleFormat = dates[this.o.language].titleFormat || dates['en'].titleFormat, + tooltip; + if (isNaN(year) || isNaN(month)) + return; + this.picker.find('.datepicker-days thead .datepicker-switch') + .text(DPGlobal.formatDate(new UTCDate(year, month), titleFormat, this.o.language)); + this.picker.find('tfoot .today') + .text(todaytxt) + .toggle(this.o.todayBtn !== false); + this.picker.find('tfoot .clear') + .text(cleartxt) + .toggle(this.o.clearBtn !== false); + this.picker.find('thead .datepicker-title') + .text(this.o.title) + .toggle(this.o.title !== ''); + this.updateNavArrows(); + this.fillMonths(); + var prevMonth = UTCDate(year, month-1, 28), + day = DPGlobal.getDaysInMonth(prevMonth.getUTCFullYear(), prevMonth.getUTCMonth()); + prevMonth.setUTCDate(day); + prevMonth.setUTCDate(day - (prevMonth.getUTCDay() - this.o.weekStart + 7)%7); + var nextMonth = new Date(prevMonth); + if (prevMonth.getUTCFullYear() < 100){ + nextMonth.setUTCFullYear(prevMonth.getUTCFullYear()); + } + nextMonth.setUTCDate(nextMonth.getUTCDate() + 42); + nextMonth = nextMonth.valueOf(); + var html = []; + var clsName; + while (prevMonth.valueOf() < nextMonth){ + if (prevMonth.getUTCDay() === this.o.weekStart){ + html.push(''); + if (this.o.calendarWeeks){ + // ISO 8601: First week contains first thursday. + // ISO also states week starts on Monday, but we can be more abstract here. + var + // Start of current week: based on weekstart/current date + ws = new Date(+prevMonth + (this.o.weekStart - prevMonth.getUTCDay() - 7) % 7 * 864e5), + // Thursday of this week + th = new Date(Number(ws) + (7 + 4 - ws.getUTCDay()) % 7 * 864e5), + // First Thursday of year, year from thursday + yth = new Date(Number(yth = UTCDate(th.getUTCFullYear(), 0, 1)) + (7 + 4 - yth.getUTCDay())%7*864e5), + // Calendar week: ms between thursdays, div ms per day, div 7 days + calWeek = (th - yth) / 864e5 / 7 + 1; + html.push(''+ calWeek +''); + + } + } + clsName = this.getClassNames(prevMonth); + clsName.push('day'); + + if (this.o.beforeShowDay !== $.noop){ + var before = this.o.beforeShowDay(this._utc_to_local(prevMonth)); + if (before === undefined) + before = {}; + else if (typeof(before) === 'boolean') + before = {enabled: before}; + else if (typeof(before) === 'string') + before = {classes: before}; + if (before.enabled === false) + clsName.push('disabled'); + if (before.classes) + clsName = clsName.concat(before.classes.split(/\s+/)); + if (before.tooltip) + tooltip = before.tooltip; + } + + clsName = $.unique(clsName); + html.push(''+prevMonth.getUTCDate() + ''); + tooltip = null; + if (prevMonth.getUTCDay() === this.o.weekEnd){ + html.push(''); + } + prevMonth.setUTCDate(prevMonth.getUTCDate()+1); + } + this.picker.find('.datepicker-days tbody').empty().append(html.join('')); + + var monthsTitle = dates[this.o.language].monthsTitle || dates['en'].monthsTitle || 'Months'; + var months = this.picker.find('.datepicker-months') + .find('.datepicker-switch') + .text(this.o.maxViewMode < 2 ? monthsTitle : year) + .end() + .find('span').removeClass('active'); + + $.each(this.dates, function(i, d){ + if (d.getUTCFullYear() === year) + months.eq(d.getUTCMonth()).addClass('active'); + }); + + if (year < startYear || year > endYear){ + months.addClass('disabled'); + } + if (year === startYear){ + months.slice(0, startMonth).addClass('disabled'); + } + if (year === endYear){ + months.slice(endMonth+1).addClass('disabled'); + } + + if (this.o.beforeShowMonth !== $.noop){ + var that = this; + $.each(months, function(i, month){ + if (!$(month).hasClass('disabled')) { + var moDate = new Date(year, i, 1); + var before = that.o.beforeShowMonth(moDate); + if (before === false) + $(month).addClass('disabled'); + } + }); + } + + html = ''; + year = parseInt(year/10, 10) * 10; + var yearCont = this.picker.find('.datepicker-years') + .find('.datepicker-switch') + .text(year + '-' + (year + 9)) + .end() + .find('td'); + year -= 1; + var years = $.map(this.dates, function(d){ + return d.getUTCFullYear(); + }), + classes; + for (var i = -1; i < 11; i++){ + classes = ['year']; + tooltip = null; + + if (i === -1) + classes.push('old'); + else if (i === 10) + classes.push('new'); + if ($.inArray(year, years) !== -1) + classes.push('active'); + if (year < startYear || year > endYear) + classes.push('disabled'); + + if (this.o.beforeShowYear !== $.noop) { + var yrBefore = this.o.beforeShowYear(new Date(year, 0, 1)); + if (yrBefore === undefined) + yrBefore = {}; + else if (typeof(yrBefore) === 'boolean') + yrBefore = {enabled: yrBefore}; + else if (typeof(yrBefore) === 'string') + yrBefore = {classes: yrBefore}; + if (yrBefore.enabled === false) + classes.push('disabled'); + if (yrBefore.classes) + classes = classes.concat(yrBefore.classes.split(/\s+/)); + if (yrBefore.tooltip) + tooltip = yrBefore.tooltip; + } + + html += '' + year + ''; + year += 1; + } + yearCont.html(html); + }, + + updateNavArrows: function(){ + if (!this._allow_update) + return; + + var d = new Date(this.viewDate), + year = d.getUTCFullYear(), + month = d.getUTCMonth(); + switch (this.viewMode){ + case 0: + if (this.o.startDate !== -Infinity && year <= this.o.startDate.getUTCFullYear() && month <= this.o.startDate.getUTCMonth()){ + this.picker.find('.prev').css({visibility: 'hidden'}); + } + else { + this.picker.find('.prev').css({visibility: 'visible'}); + } + if (this.o.endDate !== Infinity && year >= this.o.endDate.getUTCFullYear() && month >= this.o.endDate.getUTCMonth()){ + this.picker.find('.next').css({visibility: 'hidden'}); + } + else { + this.picker.find('.next').css({visibility: 'visible'}); + } + break; + case 1: + case 2: + if (this.o.startDate !== -Infinity && year <= this.o.startDate.getUTCFullYear() || this.o.maxViewMode < 2){ + this.picker.find('.prev').css({visibility: 'hidden'}); + } + else { + this.picker.find('.prev').css({visibility: 'visible'}); + } + if (this.o.endDate !== Infinity && year >= this.o.endDate.getUTCFullYear() || this.o.maxViewMode < 2){ + this.picker.find('.next').css({visibility: 'hidden'}); + } + else { + this.picker.find('.next').css({visibility: 'visible'}); + } + break; + } + }, + + click: function(e){ + e.preventDefault(); + e.stopPropagation(); + var target = $(e.target).closest('span, td, th'), + year, month, day; + if (target.length === 1){ + switch (target[0].nodeName.toLowerCase()){ + case 'th': + switch (target[0].className){ + case 'datepicker-switch': + this.showMode(1); + break; + case 'prev': + case 'next': + var dir = DPGlobal.modes[this.viewMode].navStep * (target[0].className === 'prev' ? -1 : 1); + switch (this.viewMode){ + case 0: + this.viewDate = this.moveMonth(this.viewDate, dir); + this._trigger('changeMonth', this.viewDate); + break; + case 1: + case 2: + this.viewDate = this.moveYear(this.viewDate, dir); + if (this.viewMode === 1) + this._trigger('changeYear', this.viewDate); + break; + } + this.fill(); + break; + case 'today': + this.showMode(-2); + var which = this.o.todayBtn === 'linked' ? null : 'view'; + this._setDate(UTCToday(), which); + break; + case 'clear': + this.clearDates(); + break; + } + break; + case 'span': + if (!target.hasClass('disabled')){ + this.viewDate.setUTCDate(1); + if (target.hasClass('month')){ + day = 1; + month = target.parent().find('span').index(target); + year = this.viewDate.getUTCFullYear(); + this.viewDate.setUTCMonth(month); + this._trigger('changeMonth', this.viewDate); + if (this.o.minViewMode === 1){ + this._setDate(UTCDate(year, month, day)); + this.showMode(); + } else { + this.showMode(-1); + } + } + else { + day = 1; + month = 0; + year = parseInt(target.text(), 10)||0; + this.viewDate.setUTCFullYear(year); + this._trigger('changeYear', this.viewDate); + if (this.o.minViewMode === 2){ + this._setDate(UTCDate(year, month, day)); + } + this.showMode(-1); + } + this.fill(); + } + break; + case 'td': + if (target.hasClass('day') && !target.hasClass('disabled')){ + day = parseInt(target.text(), 10)||1; + year = this.viewDate.getUTCFullYear(); + month = this.viewDate.getUTCMonth(); + if (target.hasClass('old')){ + if (month === 0){ + month = 11; + year -= 1; + } + else { + month -= 1; + } + } + else if (target.hasClass('new')){ + if (month === 11){ + month = 0; + year += 1; + } + else { + month += 1; + } + } + this._setDate(UTCDate(year, month, day)); + } + break; + } + } + if (this.picker.is(':visible') && this._focused_from){ + $(this._focused_from).focus(); + } + delete this._focused_from; + }, + + _toggle_multidate: function(date){ + var ix = this.dates.contains(date); + if (!date){ + this.dates.clear(); + } + + if (ix !== -1){ + if (this.o.multidate === true || this.o.multidate > 1 || this.o.toggleActive){ + this.dates.remove(ix); + } + } else if (this.o.multidate === false) { + this.dates.clear(); + this.dates.push(date); + } + else { + this.dates.push(date); + } + + if (typeof this.o.multidate === 'number') + while (this.dates.length > this.o.multidate) + this.dates.remove(0); + }, + + _setDate: function(date, which){ + if (!which || which === 'date') + this._toggle_multidate(date && new Date(date)); + if (!which || which === 'view') + this.viewDate = date && new Date(date); + + this.fill(); + this.setValue(); + if (!which || which !== 'view') { + this._trigger('changeDate'); + } + var element; + if (this.isInput){ + element = this.element; + } + else if (this.component){ + element = this.element.find('input'); + } + if (element){ + element.change(); + } + if (this.o.autoclose && (!which || which === 'date')){ + this.hide(); + } + }, + + moveDay: function(date, dir){ + var newDate = new Date(date); + newDate.setUTCDate(date.getUTCDate() + dir); + + return newDate; + }, + + moveWeek: function(date, dir){ + return this.moveDay(date, dir * 7); + }, + + moveMonth: function(date, dir){ + if (!isValidDate(date)) + return this.o.defaultViewDate; + if (!dir) + return date; + var new_date = new Date(date.valueOf()), + day = new_date.getUTCDate(), + month = new_date.getUTCMonth(), + mag = Math.abs(dir), + new_month, test; + dir = dir > 0 ? 1 : -1; + if (mag === 1){ + test = dir === -1 + // If going back one month, make sure month is not current month + // (eg, Mar 31 -> Feb 31 == Feb 28, not Mar 02) + ? function(){ + return new_date.getUTCMonth() === month; + } + // If going forward one month, make sure month is as expected + // (eg, Jan 31 -> Feb 31 == Feb 28, not Mar 02) + : function(){ + return new_date.getUTCMonth() !== new_month; + }; + new_month = month + dir; + new_date.setUTCMonth(new_month); + // Dec -> Jan (12) or Jan -> Dec (-1) -- limit expected date to 0-11 + if (new_month < 0 || new_month > 11) + new_month = (new_month + 12) % 12; + } + else { + // For magnitudes >1, move one month at a time... + for (var i=0; i < mag; i++) + // ...which might decrease the day (eg, Jan 31 to Feb 28, etc)... + new_date = this.moveMonth(new_date, dir); + // ...then reset the day, keeping it in the new month + new_month = new_date.getUTCMonth(); + new_date.setUTCDate(day); + test = function(){ + return new_month !== new_date.getUTCMonth(); + }; + } + // Common date-resetting loop -- if date is beyond end of month, make it + // end of month + while (test()){ + new_date.setUTCDate(--day); + new_date.setUTCMonth(new_month); + } + return new_date; + }, + + moveYear: function(date, dir){ + return this.moveMonth(date, dir*12); + }, + + moveAvailableDate: function(date, dir, fn){ + do { + date = this[fn](date, dir); + + if (!this.dateWithinRange(date)) + return false; + + fn = 'moveDay'; + } + while (this.dateIsDisabled(date)); + + return date; + }, + + weekOfDateIsDisabled: function(date){ + return $.inArray(date.getUTCDay(), this.o.daysOfWeekDisabled) !== -1; + }, + + dateIsDisabled: function(date){ + return ( + this.weekOfDateIsDisabled(date) || + $.grep(this.o.datesDisabled, function(d){ + return isUTCEquals(date, d); + }).length > 0 + ); + }, + + dateWithinRange: function(date){ + return date >= this.o.startDate && date <= this.o.endDate; + }, + + keydown: function(e){ + if (!this.picker.is(':visible')){ + if (e.keyCode === 40 || e.keyCode === 27) { // allow down to re-show picker + this.show(); + e.stopPropagation(); + } + return; + } + var dateChanged = false, + dir, newViewDate, + focusDate = this.focusDate || this.viewDate; + switch (e.keyCode){ + case 27: // escape + if (this.focusDate){ + this.focusDate = null; + this.viewDate = this.dates.get(-1) || this.viewDate; + this.fill(); + } + else + this.hide(); + e.preventDefault(); + e.stopPropagation(); + break; + case 37: // left + case 38: // up + case 39: // right + case 40: // down + if (!this.o.keyboardNavigation || this.o.daysOfWeekDisabled.length === 7) + break; + dir = e.keyCode === 37 || e.keyCode === 38 ? -1 : 1; + if (e.ctrlKey){ + newViewDate = this.moveAvailableDate(focusDate, dir, 'moveYear'); + + if (newViewDate) + this._trigger('changeYear', this.viewDate); + } + else if (e.shiftKey){ + newViewDate = this.moveAvailableDate(focusDate, dir, 'moveMonth'); + + if (newViewDate) + this._trigger('changeMonth', this.viewDate); + } + else if (e.keyCode === 37 || e.keyCode === 39){ + newViewDate = this.moveAvailableDate(focusDate, dir, 'moveDay'); + } + else if (!this.weekOfDateIsDisabled(focusDate)){ + newViewDate = this.moveAvailableDate(focusDate, dir, 'moveWeek'); + } + if (newViewDate){ + this.focusDate = this.viewDate = newViewDate; + this.setValue(); + this.fill(); + e.preventDefault(); + } + break; + case 13: // enter + if (!this.o.forceParse) + break; + focusDate = this.focusDate || this.dates.get(-1) || this.viewDate; + if (this.o.keyboardNavigation) { + this._toggle_multidate(focusDate); + dateChanged = true; + } + this.focusDate = null; + this.viewDate = this.dates.get(-1) || this.viewDate; + this.setValue(); + this.fill(); + if (this.picker.is(':visible')){ + e.preventDefault(); + e.stopPropagation(); + if (this.o.autoclose) + this.hide(); + } + break; + case 9: // tab + this.focusDate = null; + this.viewDate = this.dates.get(-1) || this.viewDate; + this.fill(); + this.hide(); + break; + } + if (dateChanged){ + if (this.dates.length) + this._trigger('changeDate'); + else + this._trigger('clearDate'); + var element; + if (this.isInput){ + element = this.element; + } + else if (this.component){ + element = this.element.find('input'); + } + if (element){ + element.change(); + } + } + }, + + showMode: function(dir){ + if (dir){ + this.viewMode = Math.max(this.o.minViewMode, Math.min(this.o.maxViewMode, this.viewMode + dir)); + } + this.picker + .children('div') + .hide() + .filter('.datepicker-' + DPGlobal.modes[this.viewMode].clsName) + .show(); + this.updateNavArrows(); + } + }; + + var DateRangePicker = function(element, options){ + $(element).data('datepicker', this); + this.element = $(element); + this.inputs = $.map(options.inputs, function(i){ + return i.jquery ? i[0] : i; + }); + delete options.inputs; + + datepickerPlugin.call($(this.inputs), options) + .on('changeDate', $.proxy(this.dateUpdated, this)); + + this.pickers = $.map(this.inputs, function(i){ + return $(i).data('datepicker'); + }); + this.updateDates(); + }; + DateRangePicker.prototype = { + updateDates: function(){ + this.dates = $.map(this.pickers, function(i){ + return i.getUTCDate(); + }); + this.updateRanges(); + }, + updateRanges: function(){ + var range = $.map(this.dates, function(d){ + return d.valueOf(); + }); + $.each(this.pickers, function(i, p){ + p.setRange(range); + }); + }, + dateUpdated: function(e){ + // `this.updating` is a workaround for preventing infinite recursion + // between `changeDate` triggering and `setUTCDate` calling. Until + // there is a better mechanism. + if (this.updating) + return; + this.updating = true; + + var dp = $(e.target).data('datepicker'); + + if (typeof(dp) === "undefined") { + return; + } + + var new_date = dp.getUTCDate(), + i = $.inArray(e.target, this.inputs), + j = i - 1, + k = i + 1, + l = this.inputs.length; + if (i === -1) + return; + + $.each(this.pickers, function(i, p){ + if (!p.getUTCDate()) + p.setUTCDate(new_date); + }); + + if (new_date < this.dates[j]){ + // Date being moved earlier/left + while (j >= 0 && new_date < this.dates[j]){ + this.pickers[j--].setUTCDate(new_date); + } + } + else if (new_date > this.dates[k]){ + // Date being moved later/right + while (k < l && new_date > this.dates[k]){ + this.pickers[k++].setUTCDate(new_date); + } + } + this.updateDates(); + + delete this.updating; + }, + remove: function(){ + $.map(this.pickers, function(p){ p.remove(); }); + delete this.element.data().datepicker; + } + }; + + function opts_from_el(el, prefix){ + // Derive options from element data-attrs + var data = $(el).data(), + out = {}, inkey, + replace = new RegExp('^' + prefix.toLowerCase() + '([A-Z])'); + prefix = new RegExp('^' + prefix.toLowerCase()); + function re_lower(_,a){ + return a.toLowerCase(); + } + for (var key in data) + if (prefix.test(key)){ + inkey = key.replace(replace, re_lower); + out[inkey] = data[key]; + } + return out; + } + + function opts_from_locale(lang){ + // Derive options from locale plugins + var out = {}; + // Check if "de-DE" style date is available, if not language should + // fallback to 2 letter code eg "de" + if (!dates[lang]){ + lang = lang.split('-')[0]; + if (!dates[lang]) + return; + } + var d = dates[lang]; + $.each(locale_opts, function(i,k){ + if (k in d) + out[k] = d[k]; + }); + return out; + } + + var old = $.fn.datepicker; + var datepickerPlugin = function(option){ + var args = Array.apply(null, arguments); + args.shift(); + var internal_return; + this.each(function(){ + var $this = $(this), + data = $this.data('datepicker'), + options = typeof option === 'object' && option; + if (!data){ + var elopts = opts_from_el(this, 'date'), + // Preliminary otions + xopts = $.extend({}, defaults, elopts, options), + locopts = opts_from_locale(xopts.language), + // Options priority: js args, data-attrs, locales, defaults + opts = $.extend({}, defaults, locopts, elopts, options); + if ($this.hasClass('input-daterange') || opts.inputs){ + $.extend(opts, { + inputs: opts.inputs || $this.find('input').toArray() + }); + data = new DateRangePicker(this, opts); + } + else { + data = new Datepicker(this, opts); + } + $this.data('datepicker', data); + } + if (typeof option === 'string' && typeof data[option] === 'function'){ + internal_return = data[option].apply(data, args); + } + }); + + if ( + internal_return === undefined || + internal_return instanceof Datepicker || + internal_return instanceof DateRangePicker + ) + return this; + + if (this.length > 1) + throw new Error('Using only allowed for the collection of a single element (' + option + ' function)'); + else + return internal_return; + }; + $.fn.datepicker = datepickerPlugin; + + var defaults = $.fn.datepicker.defaults = { + autoclose: false, + beforeShowDay: $.noop, + beforeShowMonth: $.noop, + beforeShowYear: $.noop, + calendarWeeks: false, + clearBtn: false, + toggleActive: false, + daysOfWeekDisabled: [], + daysOfWeekHighlighted: [], + datesDisabled: [], + endDate: Infinity, + forceParse: true, + format: 'mm/dd/yyyy', + keyboardNavigation: true, + language: 'en', + minViewMode: 0, + maxViewMode: 2, + multidate: false, + multidateSeparator: ',', + orientation: "auto", + rtl: false, + startDate: -Infinity, + startView: 0, + todayBtn: false, + todayHighlight: false, + weekStart: 0, + disableTouchKeyboard: false, + enableOnReadonly: true, + showOnFocus: true, + zIndexOffset: 10, + container: 'body', + immediateUpdates: false, + title: '' + }; + var locale_opts = $.fn.datepicker.locale_opts = [ + 'format', + 'rtl', + 'weekStart' + ]; + $.fn.datepicker.Constructor = Datepicker; + var dates = $.fn.datepicker.dates = { + en: { + days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], + daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], + daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"], + months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], + monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], + today: "Today", + clear: "Clear", + titleFormat: "MM yyyy" + } + }; + + var DPGlobal = { + modes: [ + { + clsName: 'days', + navFnc: 'Month', + navStep: 1 + }, + { + clsName: 'months', + navFnc: 'FullYear', + navStep: 1 + }, + { + clsName: 'years', + navFnc: 'FullYear', + navStep: 10 + }], + isLeapYear: function(year){ + return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0)); + }, + getDaysInMonth: function(year, month){ + return [31, (DPGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]; + }, + validParts: /dd?|DD?|mm?|MM?|yy(?:yy)?/g, + nonpunctuation: /[^ -\/:-@\[\u3400-\u9fff-`{-~\t\n\r]+/g, + parseFormat: function(format){ + if (typeof format.toValue === 'function' && typeof format.toDisplay === 'function') + return format; + // IE treats \0 as a string end in inputs (truncating the value), + // so it's a bad format delimiter, anyway + var separators = format.replace(this.validParts, '\0').split('\0'), + parts = format.match(this.validParts); + if (!separators || !separators.length || !parts || parts.length === 0){ + throw new Error("Invalid date format."); + } + return {separators: separators, parts: parts}; + }, + parseDate: function(date, format, language){ + if (!date) + return undefined; + if (date instanceof Date) + return date; + if (typeof format === 'string') + format = DPGlobal.parseFormat(format); + if (format.toValue) + return format.toValue(date, format, language); + var part_re = /([\-+]\d+)([dmwy])/, + parts = date.match(/([\-+]\d+)([dmwy])/g), + fn_map = { + d: 'moveDay', + m: 'moveMonth', + w: 'moveWeek', + y: 'moveYear' + }, + part, dir, i, fn; + if (/^[\-+]\d+[dmwy]([\s,]+[\-+]\d+[dmwy])*$/.test(date)){ + date = new Date(); + for (i=0; i < parts.length; i++){ + part = part_re.exec(parts[i]); + dir = parseInt(part[1]); + fn = fn_map[part[2]]; + date = Datepicker.prototype[fn](date, dir); + } + return UTCDate(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()); + } + parts = date && date.match(this.nonpunctuation) || []; + date = new Date(); + var parsed = {}, + setters_order = ['yyyy', 'yy', 'M', 'MM', 'm', 'mm', 'd', 'dd'], + setters_map = { + yyyy: function(d,v){ + return d.setUTCFullYear(v); + }, + yy: function(d,v){ + return d.setUTCFullYear(2000+v); + }, + m: function(d,v){ + if (isNaN(d)) + return d; + v -= 1; + while (v < 0) v += 12; + v %= 12; + d.setUTCMonth(v); + while (d.getUTCMonth() !== v) + d.setUTCDate(d.getUTCDate()-1); + return d; + }, + d: function(d,v){ + return d.setUTCDate(v); + } + }, + val, filtered; + setters_map['M'] = setters_map['MM'] = setters_map['mm'] = setters_map['m']; + setters_map['dd'] = setters_map['d']; + date = UTCToday(); + var fparts = format.parts.slice(); + // Remove noop parts + if (parts.length !== fparts.length){ + fparts = $(fparts).filter(function(i,p){ + return $.inArray(p, setters_order) !== -1; + }).toArray(); + } + // Process remainder + function match_part(){ + var m = this.slice(0, parts[i].length), + p = parts[i].slice(0, m.length); + return m.toLowerCase() === p.toLowerCase(); + } + if (parts.length === fparts.length){ + var cnt; + for (i=0, cnt = fparts.length; i < cnt; i++){ + val = parseInt(parts[i], 10); + part = fparts[i]; + if (isNaN(val)){ + switch (part){ + case 'MM': + filtered = $(dates[language].months).filter(match_part); + val = $.inArray(filtered[0], dates[language].months) + 1; + break; + case 'M': + filtered = $(dates[language].monthsShort).filter(match_part); + val = $.inArray(filtered[0], dates[language].monthsShort) + 1; + break; + } + } + parsed[part] = val; + } + var _date, s; + for (i=0; i < setters_order.length; i++){ + s = setters_order[i]; + if (s in parsed && !isNaN(parsed[s])){ + _date = new Date(date); + setters_map[s](_date, parsed[s]); + if (!isNaN(_date)) + date = _date; + } + } + } + return date; + }, + formatDate: function(date, format, language){ + if (!date) + return ''; + if (typeof format === 'string') + format = DPGlobal.parseFormat(format); + if (format.toDisplay) + return format.toDisplay(date, format, language); + var val = { + d: date.getUTCDate(), + D: dates[language].daysShort[date.getUTCDay()], + DD: dates[language].days[date.getUTCDay()], + m: date.getUTCMonth() + 1, + M: dates[language].monthsShort[date.getUTCMonth()], + MM: dates[language].months[date.getUTCMonth()], + yy: date.getUTCFullYear().toString().substring(2), + yyyy: date.getUTCFullYear() + }; + val.dd = (val.d < 10 ? '0' : '') + val.d; + val.mm = (val.m < 10 ? '0' : '') + val.m; + date = []; + var seps = $.extend([], format.separators); + for (var i=0, cnt = format.parts.length; i <= cnt; i++){ + if (seps.length) + date.push(seps.shift()); + date.push(val[format.parts[i]]); + } + return date.join(''); + }, + headTemplate: ''+ + ''+ + ''+ + ''+ + ''+ + '«'+ + ''+ + '»'+ + ''+ + '', + contTemplate: '', + footTemplate: ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + '' + }; + DPGlobal.template = '
'+ + '
'+ + ''+ + DPGlobal.headTemplate+ + ''+ + DPGlobal.footTemplate+ + '
'+ + '
'+ + '
'+ + ''+ + DPGlobal.headTemplate+ + DPGlobal.contTemplate+ + DPGlobal.footTemplate+ + '
'+ + '
'+ + '
'+ + ''+ + DPGlobal.headTemplate+ + DPGlobal.contTemplate+ + DPGlobal.footTemplate+ + '
'+ + '
'+ + '
'; + + $.fn.datepicker.DPGlobal = DPGlobal; + + + /* DATEPICKER NO CONFLICT + * =================== */ + + $.fn.datepicker.noConflict = function(){ + $.fn.datepicker = old; + return this; + }; + + /* DATEPICKER VERSION + * =================== */ + $.fn.datepicker.version = '1.5.1'; + + /* DATEPICKER DATA-API + * ================== */ + + $(document).on( + 'focus.datepicker.data-api click.datepicker.data-api', + '[data-provide="datepicker"]', + function(e){ + var $this = $(this); + if ($this.data('datepicker')) + return; + e.preventDefault(); + // component click requires us to explicitly show it + datepickerPlugin.call($this, 'show'); + } + ); + $(function(){ + datepickerPlugin.call($('[data-provide="datepicker-inline"]')); + }); + +})); diff --git a/public/js/searchform.js b/public/js/searchform.js index 6f2f22620..65681bb90 100644 --- a/public/js/searchform.js +++ b/public/js/searchform.js @@ -33,8 +33,14 @@ $(document).ready(function(){ var finishTimeBeginTimestamp = $("#form-finished-time-begin"); var finishTimeEndTimestamp = $("#form-finished-time-end"); - finishTimeBeginDate.datepicker(); - finishTimeEndDate.datepicker(); + finishTimeBeginDate.datepicker({ + autoclose: true, + todayHighlight: true, + }); + finishTimeEndDate.datepicker({ + autoclose: true, + todayHighlight: true, + }); var updateForm = function(){ if(jobid.val()) { From c467979a859bece9a8bf8146588eb27122bc9308 Mon Sep 17 00:00:00 2001 From: akrai Date: Mon, 15 Feb 2016 15:33:14 +0530 Subject: [PATCH 135/272] LIHADOOP-17538: Code changes to support new schema RB=666697 G=superfriends-reviewers R=annag,fli,rratti,shanm,viramach A=fli --- NOTICE | 4 +- app-conf/elephant.conf | 2 + .../linkedin/drelephant/ElephantContext.java | 11 +- .../linkedin/drelephant/ElephantRunner.java | 11 +- .../drelephant/analysis/AnalyticJob.java | 93 ++- .../analysis/AnalyticJobGeneratorHadoop2.java | 10 +- .../drelephant/analysis/Heuristic.java | 9 +- .../drelephant/analysis/HeuristicResult.java | 76 +- .../analysis/HeuristicResultDetails.java | 49 ++ ...ypeConf.java => JobTypeConfiguration.java} | 20 +- .../heuristics/ExceptionHeuristic.java | 14 +- .../heuristics/GenericDataSkewHeuristic.java | 52 +- .../heuristics/GenericGCHeuristic.java | 48 +- .../heuristics/GenericMemoryHeuristic.java | 56 +- .../heuristics/JobQueueLimitHeuristic.java | 31 +- .../heuristics/MapperDataSkewHeuristic.java | 3 +- .../heuristics/MapperGCHeuristic.java | 8 +- .../heuristics/MapperMemoryHeuristic.java | 8 +- .../heuristics/MapperSpeedHeuristic.java | 37 +- .../heuristics/MapperSpillHeuristic.java | 48 +- .../heuristics/MapperTimeHeuristic.java | 51 +- .../heuristics/ReducerDataSkewHeuristic.java | 3 +- .../heuristics/ReducerGCHeuristic.java | 8 +- .../heuristics/ReducerMemoryHeuristic.java | 8 +- .../heuristics/ReducerTimeHeuristic.java | 49 +- .../heuristics/ShuffleSortHeuristic.java | 39 +- .../linkedin/drelephant/math/Statistics.java | 40 +- .../drelephant/notifications/EmailThread.java | 12 +- .../BestPropertiesConventionHeuristic.java | 47 +- .../heuristics/EventLogLimitHeuristic.java | 18 +- .../heuristics/ExecutorLoadHeuristic.java | 58 +- .../spark/heuristics/JobRuntimeHeuristic.java | 49 +- .../heuristics/MemoryLimitHeuristic.java | 29 +- .../heuristics/StageRuntimeHeuristic.java | 57 +- .../drelephant/util/InfoExtractor.java | 77 +- app/com/linkedin/drelephant/util/Utils.java | 121 ++- app/controllers/Application.java | 702 +++++++++++------- app/controllers/IdUrlPair.java | 68 ++ app/controllers/QueryHandler.java | 58 -- app/model/JobResult.java | 123 --- .../AppHeuristicResult.java} | 62 +- app/models/AppHeuristicResultDetails.java | 61 ++ app/models/AppResult.java | 144 ++++ app/views/emailcritical.scala.html | 16 +- app/views/page/comparePage.scala.html | 8 +- app/views/page/flowHistoryPage.scala.html | 4 +- app/views/page/helpPage.scala.html | 2 +- app/views/page/homePage.scala.html | 4 +- app/views/page/jobHistoryPage.scala.html | 4 +- app/views/page/searchPage.scala.html | 24 +- app/views/results/compareResults.scala.html | 12 +- app/views/results/flowDetails.scala.html | 8 +- .../results/flowHistoryResults.scala.html | 33 +- app/views/results/jobDetails.scala.html | 83 ++- .../results/jobHistoryResults.scala.html | 37 +- app/views/results/searchResults.scala.html | 6 +- app/views/tags/jobHeader.scala.html | 16 +- app/views/tags/jobSummary.scala.html | 8 +- build.sbt | 2 +- conf/application.conf | 8 +- conf/routes | 11 +- project/plugins.sbt | 2 +- public/css/main.css | 29 +- public/js/flowhistoryform.js | 9 +- public/js/graphutility.js | 16 +- public/js/jobhistoryform.js | 2 +- public/js/searchform.js | 22 +- .../drelephant/analysis/SeverityTest.java | 50 ++ .../fetcher/FetcherConfigurationTest.java | 109 +++ .../heuristic/HeuristicConfigurationTest.java | 124 ++++ .../jobtype/JobTypeConfigurationTest.java | 131 ++++ .../MapReduceFetcherHadoop2Test.java | 35 + .../drelephant/math/StatisticsTest.java | 86 +++ .../heuristics/MemoryLimitHeuristicTest.java | 7 +- .../linkedin/drelephant/util/UtilsTest.java | 49 +- test/controllers/ApplicationTest.java | 48 ++ .../fetcher/FetcherConfTest1.xml | 27 + .../fetcher/FetcherConfTest2.xml | 27 + .../fetcher/FetcherConfTest3.xml | 23 + .../fetcher/FetcherConfTest4.xml | 22 + .../heuristic/HeuristicConfTest1.xml | 48 ++ .../heuristic/HeuristicConfTest2.xml | 48 ++ .../heuristic/HeuristicConfTest3.xml | 48 ++ .../heuristic/HeuristicConfTest4.xml | 48 ++ .../heuristic/HeuristicConfTest5.xml | 47 ++ .../jobtype/JobTypeConfTest1.xml | 46 ++ .../jobtype/JobTypeConfTest2.xml | 46 ++ .../jobtype/JobTypeConfTest3.xml | 45 ++ .../jobtype/JobTypeConfTest4.xml | 45 ++ .../jobtype/JobTypeConfTest5.xml | 26 + .../jobtype/JobTypeConfTest6.xml | 48 ++ 91 files changed, 2796 insertions(+), 1277 deletions(-) create mode 100644 app/com/linkedin/drelephant/analysis/HeuristicResultDetails.java rename app/com/linkedin/drelephant/configurations/jobtype/{JobTypeConf.java => JobTypeConfiguration.java} (93%) create mode 100644 app/controllers/IdUrlPair.java delete mode 100644 app/controllers/QueryHandler.java delete mode 100644 app/model/JobResult.java rename app/{model/JobHeuristicResult.java => models/AppHeuristicResult.java} (52%) create mode 100644 app/models/AppHeuristicResultDetails.java create mode 100644 app/models/AppResult.java create mode 100644 test/com/linkedin/drelephant/analysis/SeverityTest.java create mode 100644 test/com/linkedin/drelephant/configurations/fetcher/FetcherConfigurationTest.java create mode 100644 test/com/linkedin/drelephant/configurations/heuristic/HeuristicConfigurationTest.java create mode 100644 test/com/linkedin/drelephant/configurations/jobtype/JobTypeConfigurationTest.java create mode 100644 test/com/linkedin/drelephant/mapreduce/MapReduceFetcherHadoop2Test.java create mode 100644 test/com/linkedin/drelephant/math/StatisticsTest.java create mode 100644 test/controllers/ApplicationTest.java create mode 100644 test/resources/configurations/fetcher/FetcherConfTest1.xml create mode 100644 test/resources/configurations/fetcher/FetcherConfTest2.xml create mode 100644 test/resources/configurations/fetcher/FetcherConfTest3.xml create mode 100644 test/resources/configurations/fetcher/FetcherConfTest4.xml create mode 100644 test/resources/configurations/heuristic/HeuristicConfTest1.xml create mode 100644 test/resources/configurations/heuristic/HeuristicConfTest2.xml create mode 100644 test/resources/configurations/heuristic/HeuristicConfTest3.xml create mode 100644 test/resources/configurations/heuristic/HeuristicConfTest4.xml create mode 100644 test/resources/configurations/heuristic/HeuristicConfTest5.xml create mode 100644 test/resources/configurations/jobtype/JobTypeConfTest1.xml create mode 100644 test/resources/configurations/jobtype/JobTypeConfTest2.xml create mode 100644 test/resources/configurations/jobtype/JobTypeConfTest3.xml create mode 100644 test/resources/configurations/jobtype/JobTypeConfTest4.xml create mode 100644 test/resources/configurations/jobtype/JobTypeConfTest5.xml create mode 100644 test/resources/configurations/jobtype/JobTypeConfTest6.xml diff --git a/NOTICE b/NOTICE index da93bf945..b58fadbec 100644 --- a/NOTICE +++ b/NOTICE @@ -75,8 +75,8 @@ Bootstrap (http://getbootstrap.com/) Copyright 2011-2016 Twitter, Inc. License: MIT License (https://github.com/twbs/bootstrap/blob/master/LICENSE) -Bootstrap Datepicker (http://www.eyecon.ro/bootstrap-datepicker) -Copyright 2012 Stefan Petre +Bootstrap Datepicker (https://github.com/eternicode/bootstrap-datepicker) +Copyright 2012 Stefan Petre, Improvements by Andrew Rowls License: Apache 2.0 D3.js (http://d3js.org) diff --git a/app-conf/elephant.conf b/app-conf/elephant.conf index 23e037ded..45ec6f23a 100644 --- a/app-conf/elephant.conf +++ b/app-conf/elephant.conf @@ -1,8 +1,10 @@ port=8080 + db_url=localhost db_name=drelephant db_user=root db_password= + #jvm_props="-Devolutionplugin=enabled -DapplyEvolutions.default=true" keytab_location="/export/apps/hadoop/keytabs/dr_elephant-service.keytab" keytab_user="elephant/eat1-magicaz01.grid.linkedin.com" diff --git a/app/com/linkedin/drelephant/ElephantContext.java b/app/com/linkedin/drelephant/ElephantContext.java index 92514643a..88de85721 100644 --- a/app/com/linkedin/drelephant/ElephantContext.java +++ b/app/com/linkedin/drelephant/ElephantContext.java @@ -28,7 +28,7 @@ import com.linkedin.drelephant.configurations.fetcher.FetcherConfigurationData; import com.linkedin.drelephant.configurations.heuristic.HeuristicConfiguration; import com.linkedin.drelephant.configurations.heuristic.HeuristicConfigurationData; -import com.linkedin.drelephant.configurations.jobtype.JobTypeConf; +import com.linkedin.drelephant.configurations.jobtype.JobTypeConfiguration; import com.linkedin.drelephant.util.Utils; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; @@ -175,8 +175,8 @@ private void loadHeuristics() { // Bind No_DATA heuristic to its helper pages, no need to add any real configurations _heuristicsConfData.add( - new HeuristicConfigurationData(HeuristicResult.NO_DATA.getAnalysis(), null, "views.html.help.helpNoData", null, - null)); + new HeuristicConfigurationData(HeuristicResult.NO_DATA.getHeuristicName(), + HeuristicResult.NO_DATA.getHeuristicClassName(), "views.html.help.helpNoData", null, null)); } /** @@ -216,7 +216,8 @@ private void configureSupportedApplicationTypes() { * Load all the job types configured in JobTypeConf.xml */ private void loadJobTypes() { - JobTypeConf conf = new JobTypeConf(JOB_TYPES_CONF); + Document document = Utils.loadXMLDoc(JOB_TYPES_CONF); + JobTypeConfiguration conf = new JobTypeConfiguration(document.getDocumentElement()); _appTypeToJobTypes = conf.getAppTypeToJobTypeList(); } @@ -243,7 +244,7 @@ public Map> getAllHeuristicNames() { List nameList = new ArrayList(); for (Heuristic heuristic : list) { - nameList.add(heuristic.getHeuristicName()); + nameList.add(heuristic.getHeuristicConfData().getHeuristicName()); } Collections.sort(nameList); diff --git a/app/com/linkedin/drelephant/ElephantRunner.java b/app/com/linkedin/drelephant/ElephantRunner.java index 62d11fd3d..9db1acc80 100644 --- a/app/com/linkedin/drelephant/ElephantRunner.java +++ b/app/com/linkedin/drelephant/ElephantRunner.java @@ -32,7 +32,7 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicBoolean; -import model.JobResult; +import models.AppResult; import org.apache.commons.lang.exception.ExceptionUtils; import org.apache.hadoop.conf.Configuration; @@ -149,7 +149,7 @@ public void run() { analyticJob = _jobQueue.take(); logger.info("Executor thread " + _threadId + " analyzing " + analyticJob.getAppType().getName() + " " + analyticJob.getAppId()); - JobResult result = analyticJob.getAnalysis(); + AppResult result = analyticJob.getAnalysis(); result.save(); } catch (InterruptedException ex) { @@ -162,9 +162,10 @@ public void run() { logger.error("Add analytic job id [" + analyticJob.getAppId() + "] into the retry list."); _analyticJobGenerator.addIntoRetries(analyticJob); } else { - logger.error( - "Drop the analytic job. Reason: reached the max retries for application id = [" + analyticJob.getAppId() - + "]."); + if (analyticJob != null) { + logger.error("Drop the analytic job. Reason: reached the max retries for application id = [" + + analyticJob.getAppId() + "]."); + } } } } diff --git a/app/com/linkedin/drelephant/analysis/AnalyticJob.java b/app/com/linkedin/drelephant/analysis/AnalyticJob.java index f0151c419..f90d7ed11 100644 --- a/app/com/linkedin/drelephant/analysis/AnalyticJob.java +++ b/app/com/linkedin/drelephant/analysis/AnalyticJob.java @@ -20,9 +20,11 @@ import com.linkedin.drelephant.util.InfoExtractor; import com.linkedin.drelephant.util.Utils; import java.util.ArrayList; +import java.util.Date; import java.util.List; -import model.JobHeuristicResult; -import model.JobResult; +import models.AppHeuristicResult; +import models.AppHeuristicResultDetails; +import models.AppResult; import org.apache.log4j.Logger; @@ -41,6 +43,7 @@ public class AnalyticJob { private String _appId; private String _jobId; private String _name; + private String _queueName; private String _user; private String _trackingUrl; private long _startTime; @@ -79,25 +82,24 @@ public AnalyticJob setAppId(String appId) { } /** - * Set the id of the job - * jobId is the appId with the prefix 'application_' replaced by 'job_' + * Set the name of the analytic job * - * @param jobId The job id + * @param name * @return The analytic job */ - public AnalyticJob setJobId(String jobId) { - _jobId = jobId; + public AnalyticJob setName(String name) { + _name = name; return this; } /** - * Set the name of the analytic job + * Set the queue name in which the analytic jobs was submitted * - * @param name + * @param name the name of the queue * @return The analytic job */ - public AnalyticJob setName(String name) { - _name = name; + public AnalyticJob setQueueName(String name) { + _queueName = name; return this; } @@ -120,6 +122,10 @@ public AnalyticJob setUser(String user) { * @return The analytic job */ public AnalyticJob setStartTime(long startTime) { + // TIMESTAMP range starts from FROM_UNIXTIME(1) = 1970-01-01 00:00:01 + if (startTime <= 0) { + startTime = 1; + } _startTime = startTime; return this; } @@ -131,6 +137,10 @@ public AnalyticJob setStartTime(long startTime) { * @return The analytic job */ public AnalyticJob setFinishTime(long finishTime) { + // TIMESTAMP range starts from FROM_UNIXTIME(1) = 1970-01-01 00:00:01 + if (finishTime <= 0) { + finishTime = 1; + } _finishTime = finishTime; return this; } @@ -198,6 +208,15 @@ public String getTrackingUrl() { return _trackingUrl; } + /** + * Returns the queue in which the application was submitted + * + * @return The queue name + */ + public String getQueueName() { + return _queueName; + } + /** * Sets the tracking url for the job * @@ -210,15 +229,15 @@ public AnalyticJob setTrackingUrl(String trackingUrl) { } /** - * Returns the analysed JobResult that could be directly serialized into DB. + * Returns the analysed AppResult that could be directly serialized into DB. * * This method fetches the data using the appropriate application fetcher, runs all the heuristics on them and - * loads it into the JobResult model. + * loads it into the AppResult model. * * @throws Exception if the analysis process encountered a problem. - * @return the analysed JobResult + * @return the analysed AppResult */ - public JobResult getAnalysis() throws Exception { + public AppResult getAnalysis() throws Exception { ElephantFetcher fetcher = ElephantContext.instance().getFetcherForApplicationType(getAppType()); HadoopApplicationData data = fetcher.fetchData(this); @@ -227,6 +246,7 @@ public JobResult getAnalysis() throws Exception { if (data == null || data.isEmpty()) { // Example: a MR job has 0 mappers and 0 reducers logger.info("No Data Received for analytic job: " + getAppId()); + HeuristicResult.NO_DATA.addResultDetail("No Data Received", ""); analysisResults.add(HeuristicResult.NO_DATA); } else { List heuristics = ElephantContext.instance().getHeuristicsForApplicationType(getAppType()); @@ -242,36 +262,45 @@ public JobResult getAnalysis() throws Exception { String jobTypeName = jobType == null ? UNKNOWN_JOB_TYPE : jobType.getName(); // Load job information - JobResult result = new JobResult(); - result.jobId = Utils.getJobIdFromApplicationId(getAppId()); - result.url = getTrackingUrl(); + AppResult result = new AppResult(); + result.id = getAppId(); + result.trackingUrl = getTrackingUrl(); + result.queueName = getQueueName(); result.username = getUser(); - result.startTime = getStartTime(); - result.analysisTime = getFinishTime(); - result.jobName = getName(); + result.startTime = new Date(getStartTime()); + result.finishTime = new Date(getFinishTime()); + result.name = getName(); result.jobType = jobTypeName; // Truncate long names - if (result.jobName.length() > 100) { - result.jobName = result.jobName.substring(0, 97) + "..."; + if (result.name.length() > 255) { + result.name = result.name.substring(0, 252) + "..."; } - // Load Job Heuristic information - result.heuristicResults = new ArrayList(); + // Load App Heuristic information + int jobScore = 0; + result.yarnAppHeuristicResults = new ArrayList(); Severity worstSeverity = Severity.NONE; for (HeuristicResult heuristicResult : analysisResults) { - JobHeuristicResult detail = new JobHeuristicResult(); - detail.analysisName = heuristicResult.getAnalysis(); - detail.data = heuristicResult.getDetailsCSV(); - detail.dataColumns = heuristicResult.getDetailsColumns(); + AppHeuristicResult detail = new AppHeuristicResult(); + detail.heuristicClass = heuristicResult.getHeuristicClassName(); + detail.heuristicName = heuristicResult.getHeuristicName(); detail.severity = heuristicResult.getSeverity(); - if (detail.dataColumns < 1) { - detail.dataColumns = 1; + detail.score = heuristicResult.getScore(); + for (HeuristicResultDetails heuristicResultDetails : heuristicResult.getHeuristicResultDetails()) { + AppHeuristicResultDetails heuristicDetail = new AppHeuristicResultDetails(); + heuristicDetail.yarnAppHeuristicResult = detail; + heuristicDetail.name = heuristicResultDetails.getName(); + heuristicDetail.value = heuristicResultDetails.getValue(); + heuristicDetail.details = heuristicResultDetails.getDetails(); + detail.yarnAppHeuristicResultDetails.add(heuristicDetail); } - result.heuristicResults.add(detail); + result.yarnAppHeuristicResults.add(detail); worstSeverity = Severity.max(worstSeverity, detail.severity); + jobScore += detail.score; } result.severity = worstSeverity; + result.score = jobScore; // Retrieve Azkaban execution, flow and jobs URLs from jobData and store them into result. InfoExtractor.retrieveURLs(result, data); diff --git a/app/com/linkedin/drelephant/analysis/AnalyticJobGeneratorHadoop2.java b/app/com/linkedin/drelephant/analysis/AnalyticJobGeneratorHadoop2.java index 2d9adf8a8..16a28cbb8 100644 --- a/app/com/linkedin/drelephant/analysis/AnalyticJobGeneratorHadoop2.java +++ b/app/com/linkedin/drelephant/analysis/AnalyticJobGeneratorHadoop2.java @@ -27,7 +27,7 @@ import java.util.Queue; import java.util.Random; import java.util.concurrent.ConcurrentLinkedQueue; -import model.JobResult; +import models.AppResult; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.security.authentication.client.AuthenticatedURL; import org.apache.hadoop.security.authentication.client.AuthenticationException; @@ -161,14 +161,14 @@ private List readApps(URL url) throws IOException, AuthenticationEx JsonNode apps = rootNode.path("apps").path("app"); for (JsonNode app : apps) { - String id = app.get("id").getValueAsText(); - String jobId = Utils.getJobIdFromApplicationId(id); + String appId = app.get("id").getValueAsText(); // When called first time after launch, hit the DB and avoid duplicated analytic jobs that have been analyzed // before. - if (_lastTime > 0 || _lastTime == 0 && JobResult.find.byId(jobId) == null && JobResult.find.byId(id) == null) { + if (_lastTime > 0 || (_lastTime == 0 && AppResult.find.byId(appId) == null)) { String user = app.get("user").getValueAsText(); String name = app.get("name").getValueAsText(); + String queueName = app.get("queue").getValueAsText(); String trackingUrl = app.get("trackingUrl") != null? app.get("trackingUrl").getValueAsText() : null; long startTime = app.get("startedTime").getLongValue(); long finishTime = app.get("finishedTime").getLongValue(); @@ -179,7 +179,7 @@ private List readApps(URL url) throws IOException, AuthenticationEx // If the application type is supported if (type != null) { AnalyticJob analyticJob = new AnalyticJob(); - analyticJob.setAppId(id).setAppType(type).setJobId(jobId).setUser(user).setName(name) + analyticJob.setAppId(appId).setAppType(type).setUser(user).setName(name).setQueueName(queueName) .setTrackingUrl(trackingUrl).setStartTime(startTime).setFinishTime(finishTime); appList.add(analyticJob); diff --git a/app/com/linkedin/drelephant/analysis/Heuristic.java b/app/com/linkedin/drelephant/analysis/Heuristic.java index f7d906aad..31e7a0a67 100644 --- a/app/com/linkedin/drelephant/analysis/Heuristic.java +++ b/app/com/linkedin/drelephant/analysis/Heuristic.java @@ -16,6 +16,9 @@ package com.linkedin.drelephant.analysis; +import com.linkedin.drelephant.configurations.heuristic.HeuristicConfigurationData; + + /** * This interface defines the Heuristic rule interface. * @@ -31,9 +34,9 @@ public interface Heuristic { public HeuristicResult apply(T data); /** - * Get the heuristic name + * Get the heuristic Configuration * - * @return the name + * @return the heuristic configuration data */ - public String getHeuristicName(); + public HeuristicConfigurationData getHeuristicConfData(); } diff --git a/app/com/linkedin/drelephant/analysis/HeuristicResult.java b/app/com/linkedin/drelephant/analysis/HeuristicResult.java index 558cc24d6..ad2aff499 100644 --- a/app/com/linkedin/drelephant/analysis/HeuristicResult.java +++ b/app/com/linkedin/drelephant/analysis/HeuristicResult.java @@ -27,32 +27,46 @@ * Holds the Heuristic analysis result Information */ public class HeuristicResult { - public static final HeuristicResult NO_DATA = new HeuristicResult("No Data Received", Severity.LOW); + public static final HeuristicResult NO_DATA = new HeuristicResult("NoDataReceived", "No Data Received", Severity.LOW, 0); - private String _analysis; + private String _heuristicClass; + private String _heuristicName; private Severity _severity; - private List _details; - private int _detailsColumns = 0; + private int _score; + private List _heuristicResultDetails; /** * Heuristic Result Constructor * - * @param analysis The name of the heuristic - * @param severity The severity level of the heuristic + * @param heuristicClass The Heuristic class + * @param heuristicName The name of the Heursitic + * @param severity The severity of the result + * @param score The computed score */ - public HeuristicResult(String analysis, Severity severity) { - this._analysis = analysis; + public HeuristicResult(String heuristicClass, String heuristicName, Severity severity, int score) { + this._heuristicClass = heuristicClass; + this._heuristicName = heuristicName; this._severity = severity; - this._details = new ArrayList(); + this._score = score; + this._heuristicResultDetails = new ArrayList(); + } + + /** + * Returns the heuristic analyser class name + * + * @return the heursitic class name + */ + public String getHeuristicClassName() { + return _heuristicClass; } /** * Returns the heuristic analyser name * - * @return the analysis name + * @return the heuristic name */ - public String getAnalysis() { - return _analysis; + public String getHeuristicName() { + return _heuristicName; } /** @@ -64,43 +78,31 @@ public Severity getSeverity() { return _severity; } - /** - * Gets a list of lines of comma-separated strings - * - * @return - */ - public List getDetails() { - return _details; + public int getScore() { + return _score; } /** - * Create a string that contains lines of comma-separated strings + * Gets a list of HeuristicResultDetails * * @return */ - public String getDetailsCSV() { - return Utils.combineCsvLines(_details.toArray(new String[_details.size()])); + public List getHeuristicResultDetails() { + return _heuristicResultDetails; } /** - * Gets the number of columns in the csv formatted details store - * - * @return + * Add the App Heuristic Result Detail entry */ - public int getDetailsColumns() { - return _detailsColumns; + public void addResultDetail(String name, String value, String details) { + _heuristicResultDetails.add(new HeuristicResultDetails(name, value, details)); } /** - * Add a new line to the csv formatted details store - * - * @param parts strings to join into a single line + * Add the App Heuristic Result Detail without details */ - public void addDetail(String... parts) { - _details.add(Utils.createCsvLine(parts)); - if (parts.length > _detailsColumns) { - _detailsColumns = parts.length; - } + public void addResultDetail(String name, String value) { + _heuristicResultDetails.add(new HeuristicResultDetails(name, value, null)); } /** @@ -114,7 +116,7 @@ public void setSeverity(Severity severity) { @Override public String toString() { - return "{analysis: " + _analysis + ", severity: " + _severity + ", details: [" + StringUtils.join(_details, " ") - + "]}"; + return "{analysis: " + _heuristicClass + ", severity: " + _severity + ", details: [" + + StringUtils.join(_heuristicResultDetails, " ") + "]}"; } } diff --git a/app/com/linkedin/drelephant/analysis/HeuristicResultDetails.java b/app/com/linkedin/drelephant/analysis/HeuristicResultDetails.java new file mode 100644 index 000000000..5503f4931 --- /dev/null +++ b/app/com/linkedin/drelephant/analysis/HeuristicResultDetails.java @@ -0,0 +1,49 @@ +/* + * Copyright 2015 LinkedIn Corp. + * + * 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.linkedin.drelephant.analysis; + +import java.util.ArrayList; +import java.util.List; + + +/** + * Holds the analysis details for each Heuristic + */ +public class HeuristicResultDetails { + + private String _name; + private String _value; + private String _details; + + public HeuristicResultDetails(String name, String value, String details) { + this._name = name; + this._value = value; + this._details = details; + } + + public String getDetails() { + return _details; + } + + public String getValue() { + return _value; + } + + public String getName() { + return _name; + } +} diff --git a/app/com/linkedin/drelephant/configurations/jobtype/JobTypeConf.java b/app/com/linkedin/drelephant/configurations/jobtype/JobTypeConfiguration.java similarity index 93% rename from app/com/linkedin/drelephant/configurations/jobtype/JobTypeConf.java rename to app/com/linkedin/drelephant/configurations/jobtype/JobTypeConfiguration.java index b55f85976..21e61b5bf 100644 --- a/app/com/linkedin/drelephant/configurations/jobtype/JobTypeConf.java +++ b/app/com/linkedin/drelephant/configurations/jobtype/JobTypeConfiguration.java @@ -36,36 +36,32 @@ /** * This class manages the job type configurations */ -public class JobTypeConf { - private static final Logger logger = Logger.getLogger(JobTypeConf.class); +public class JobTypeConfiguration { + private static final Logger logger = Logger.getLogger(JobTypeConfiguration.class); private static final int TYPE_LEN_LIMIT = 20; - private final String _configFilePath; private Map> _appTypeToJobTypeList = new HashMap>(); - public JobTypeConf(String configFilePath) { - _configFilePath = configFilePath; - parseJobTypeConf(); + public JobTypeConfiguration(Element configuration) { + parseJobTypeConfiguration(configuration); } public Map> getAppTypeToJobTypeList() { return _appTypeToJobTypeList; } - private void parseJobTypeConf() { - logger.info("Loading job type config file " + _configFilePath); + private void parseJobTypeConfiguration(Element configuration) { Map defaultMap = new HashMap(); - Document document = Utils.loadXMLDoc(_configFilePath); - - NodeList nodes = document.getDocumentElement().getChildNodes(); + NodeList nodes = configuration.getChildNodes(); + int n = 0; for (int i = 0; i < nodes.getLength(); i++) { Node node = nodes.item(i); - int n = 0; if (node.getNodeType() == Node.ELEMENT_NODE) { n++; Element jobTypeNode = (Element) node; + String jobTypeName; Node jobTypeNameNode = jobTypeNode.getElementsByTagName("name").item(0); if (jobTypeNameNode == null) { diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/ExceptionHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/ExceptionHeuristic.java index d23ed730b..b5cb7e52f 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/ExceptionHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/ExceptionHeuristic.java @@ -25,7 +25,6 @@ public class ExceptionHeuristic implements Heuristic { - public static final String HEURISTIC_NAME = "Exception"; private HeuristicConfigurationData _heuristicConfData; public ExceptionHeuristic(HeuristicConfigurationData heuristicConfData) { @@ -33,8 +32,8 @@ public ExceptionHeuristic(HeuristicConfigurationData heuristicConfData) { } @Override - public String getHeuristicName() { - return HEURISTIC_NAME; + public HeuristicConfigurationData getHeuristicConfData() { + return _heuristicConfData; } @Override @@ -42,14 +41,15 @@ public HeuristicResult apply(MapReduceApplicationData data) { if (data.getSucceeded()) { return null; } - HeuristicResult result = new HeuristicResult(HEURISTIC_NAME, Severity.MODERATE); + HeuristicResult result = new HeuristicResult( + _heuristicConfData.getClassName(), _heuristicConfData.getHeuristicName(), Severity.MODERATE, 0); String diagnosticInfo = data.getDiagnosticInfo(); if (diagnosticInfo != null) { - result.addDetail(diagnosticInfo); + result.addResultDetail("Error", "Stacktrace", diagnosticInfo); } else { - String msg = "Unable to find stacktrace info. Please find the real problem in the Jobhistory link above.\n" + String msg = "Unable to find stacktrace info. Please find the real problem in the Jobhistory link above." + "Exception can happen either in task log or Application Master log."; - result.addDetail(msg); + result.addResultDetail("Error", msg); } return result; } diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/GenericDataSkewHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericDataSkewHeuristic.java index 127b8ccb6..2c75e8bb0 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/GenericDataSkewHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericDataSkewHeuristic.java @@ -54,59 +54,52 @@ public abstract class GenericDataSkewHeuristic implements Heuristic paramMap = _heuristicConfData.getParamMap(); + String heuristicName = _heuristicConfData.getHeuristicName(); - if(paramMap.get(NUM_TASKS_SEVERITY) != null) { - double[] confNumTasksThreshold = Utils.getParam(paramMap.get(NUM_TASKS_SEVERITY), numTasksLimits.length); - if (confNumTasksThreshold != null) { - numTasksLimits = confNumTasksThreshold; - } + double[] confNumTasksThreshold = Utils.getParam(paramMap.get(NUM_TASKS_SEVERITY), numTasksLimits.length); + if (confNumTasksThreshold != null) { + numTasksLimits = confNumTasksThreshold; } - logger.info(_heuristicName + " will use " + NUM_TASKS_SEVERITY + " with the following threshold settings: " + logger.info(heuristicName + " will use " + NUM_TASKS_SEVERITY + " with the following threshold settings: " + Arrays.toString(numTasksLimits)); - if(paramMap.get(DEVIATION_SEVERITY) != null) { - double[] confDeviationThreshold = Utils.getParam(paramMap.get(DEVIATION_SEVERITY), deviationLimits.length); - if (confDeviationThreshold != null) { - deviationLimits = confDeviationThreshold; - } + double[] confDeviationThreshold = Utils.getParam(paramMap.get(DEVIATION_SEVERITY), deviationLimits.length); + if (confDeviationThreshold != null) { + deviationLimits = confDeviationThreshold; } - logger.info(_heuristicName + " will use " + DEVIATION_SEVERITY + " with the following threshold settings: " + logger.info(heuristicName + " will use " + DEVIATION_SEVERITY + " with the following threshold settings: " + Arrays.toString(deviationLimits)); - if(paramMap.get(FILES_SEVERITY) != null) { - double[] confFilesThreshold = Utils.getParam(paramMap.get(FILES_SEVERITY), filesLimits.length); - if (confFilesThreshold != null) { - filesLimits = confFilesThreshold; - } + double[] confFilesThreshold = Utils.getParam(paramMap.get(FILES_SEVERITY), filesLimits.length); + if (confFilesThreshold != null) { + filesLimits = confFilesThreshold; } - logger.info(_heuristicName + " will use " + FILES_SEVERITY + " with the following threshold settings: " + logger.info(heuristicName + " will use " + FILES_SEVERITY + " with the following threshold settings: " + Arrays.toString(filesLimits)); for (int i = 0; i < filesLimits.length; i++) { filesLimits[i] = filesLimits[i] * HDFSContext.HDFS_BLOCK_SIZE; } } - protected GenericDataSkewHeuristic(MapReduceCounterData.CounterName counterName, String heuristicName, + protected GenericDataSkewHeuristic(MapReduceCounterData.CounterName counterName, HeuristicConfigurationData heuristicConfData) { this._counterName = counterName; - this._heuristicName = heuristicName; this._heuristicConfData = heuristicConfData; loadParameters(); } + protected abstract MapReduceTaskData[] getTasks(MapReduceApplicationData data); + @Override - public String getHeuristicName() { - return _heuristicName; + public HeuristicConfigurationData getHeuristicConfData() { + return _heuristicConfData; } - protected abstract MapReduceTaskData[] getTasks(MapReduceApplicationData data); - @Override public HeuristicResult apply(MapReduceApplicationData data) { @@ -145,11 +138,12 @@ public HeuristicResult apply(MapReduceApplicationData data) { severity = Severity.min(severity, Severity.getSeverityAscending( groups[0].length, numTasksLimits[0], numTasksLimits[1], numTasksLimits[2], numTasksLimits[3])); - HeuristicResult result = new HeuristicResult(_heuristicName, severity); + HeuristicResult result = new HeuristicResult(_heuristicConfData.getClassName(), + _heuristicConfData.getHeuristicName(), severity, Utils.getHeuristicScore(severity, tasks.length)); - result.addDetail("Number of tasks", Integer.toString(tasks.length)); - result.addDetail("Group A", Math.round(groups[0].length*scale) + " tasks @ " + FileUtils.byteCountToDisplaySize(avg1) + " avg"); - result.addDetail("Group B", Math.round(groups[1].length*scale) + " tasks @ " + FileUtils.byteCountToDisplaySize(avg2) + " avg"); + result.addResultDetail("Number of tasks", Integer.toString(tasks.length)); + result.addResultDetail("Group A", groups[0].length + " tasks @ " + FileUtils.byteCountToDisplaySize(avg1) + " avg"); + result.addResultDetail("Group B", groups[1].length + " tasks @ " + FileUtils.byteCountToDisplaySize(avg2) + " avg"); return result; } diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/GenericGCHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericGCHeuristic.java index 0723c68fc..8513c0586 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/GenericGCHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericGCHeuristic.java @@ -46,41 +46,31 @@ public abstract class GenericGCHeuristic implements Heuristic paramMap = _heuristicConfData.getParamMap(); + String heuristicName = _heuristicConfData.getHeuristicName(); - if(paramMap.get(GC_RATIO_SEVERITY) != null) { - double[] confGcRatioThreshold = Utils.getParam(paramMap.get(GC_RATIO_SEVERITY), gcRatioLimits.length); - if (confGcRatioThreshold != null) { - gcRatioLimits = confGcRatioThreshold; - } + double[] confGcRatioThreshold = Utils.getParam(paramMap.get(GC_RATIO_SEVERITY), gcRatioLimits.length); + if (confGcRatioThreshold != null) { + gcRatioLimits = confGcRatioThreshold; } - logger.info(_heuristicName + " will use " + GC_RATIO_SEVERITY + " with the following threshold settings: " + logger.info(heuristicName + " will use " + GC_RATIO_SEVERITY + " with the following threshold settings: " + Arrays.toString(gcRatioLimits)); - if(paramMap.get(RUNTIME_SEVERITY) != null) { - double[] confRuntimeThreshold = Utils.getParam(paramMap.get(RUNTIME_SEVERITY), runtimeLimits.length); - if (confRuntimeThreshold != null) { - runtimeLimits = confRuntimeThreshold; - } + double[] confRuntimeThreshold = Utils.getParam(paramMap.get(RUNTIME_SEVERITY), runtimeLimits.length); + if (confRuntimeThreshold != null) { + runtimeLimits = confRuntimeThreshold; } - logger.info(_heuristicName + " will use " + RUNTIME_SEVERITY + " with the following threshold settings: " + logger.info(heuristicName + " will use " + RUNTIME_SEVERITY + " with the following threshold settings: " + Arrays.toString(runtimeLimits)); for (int i = 0; i < runtimeLimits.length; i++) { runtimeLimits[i] = runtimeLimits[i] * Statistics.MINUTE_IN_MS; } } - protected GenericGCHeuristic(String heuristicName, HeuristicConfigurationData heuristicConfData) { - this._heuristicName = heuristicName; + protected GenericGCHeuristic(HeuristicConfigurationData heuristicConfData) { this._heuristicConfData = heuristicConfData; loadParameters(); @@ -88,6 +78,11 @@ protected GenericGCHeuristic(String heuristicName, HeuristicConfigurationData he protected abstract MapReduceTaskData[] getTasks(MapReduceApplicationData data); + @Override + public HeuristicConfigurationData getHeuristicConfData() { + return _heuristicConfData; + } + @Override public HeuristicResult apply(MapReduceApplicationData data) { @@ -120,13 +115,14 @@ public HeuristicResult apply(MapReduceApplicationData data) { severity = getGcRatioSeverity(avgRuntimeMs, avgCpuMs, avgGcMs); } - HeuristicResult result = new HeuristicResult(_heuristicName, severity); + HeuristicResult result = new HeuristicResult(_heuristicConfData.getClassName(), + _heuristicConfData.getHeuristicName(), severity, Utils.getHeuristicScore(severity, tasks.length)); - result.addDetail("Number of tasks", Integer.toString(tasks.length)); - result.addDetail("Avg task runtime (ms)", Long.toString(avgRuntimeMs)); - result.addDetail("Avg task CPU time (ms)", Long.toString(avgCpuMs)); - result.addDetail("Avg task GC time (ms)", Long.toString(avgGcMs)); - result.addDetail("Task GC/CPU ratio", Double.toString(ratio)); + result.addResultDetail("Number of tasks", Integer.toString(tasks.length)); + result.addResultDetail("Avg task runtime (ms)", Long.toString(avgRuntimeMs)); + result.addResultDetail("Avg task CPU time (ms)", Long.toString(avgCpuMs)); + result.addResultDetail("Avg task GC time (ms)", Long.toString(avgGcMs)); + result.addResultDetail("Task GC/CPU ratio", Double.toString(ratio)); return result; } diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/GenericMemoryHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericMemoryHeuristic.java index 9d0622459..c7dabeecc 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/GenericMemoryHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/GenericMemoryHeuristic.java @@ -50,42 +50,31 @@ public abstract class GenericMemoryHeuristic implements Heuristic paramMap = _heuristicConfData.getParamMap(); + String heuristicName = _heuristicConfData.getHeuristicName(); - if(paramMap.get(MEM_RATIO_SEVERITY) != null) { - double[] confMemRatioLimits = Utils.getParam(paramMap.get(MEM_RATIO_SEVERITY), memRatioLimits.length); - if (confMemRatioLimits != null) { - memRatioLimits = confMemRatioLimits; - } + double[] confMemRatioLimits = Utils.getParam(paramMap.get(MEM_RATIO_SEVERITY), memRatioLimits.length); + if (confMemRatioLimits != null) { + memRatioLimits = confMemRatioLimits; } - logger.info(_heuristicName + " will use " + MEM_RATIO_SEVERITY + " with the following threshold settings: " + logger.info(heuristicName + " will use " + MEM_RATIO_SEVERITY + " with the following threshold settings: " + Arrays.toString(memRatioLimits)); - if(paramMap.get(CONTAINER_MEM_SEVERITY) != null) { - double[] confMemoryLimits = Utils.getParam(paramMap.get(CONTAINER_MEM_SEVERITY), memoryLimits.length); - if (confMemoryLimits != null) { - memoryLimits = confMemoryLimits; - } + double[] confMemoryLimits = Utils.getParam(paramMap.get(CONTAINER_MEM_SEVERITY), memoryLimits.length); + if (confMemoryLimits != null) { + memoryLimits = confMemoryLimits; } - logger.info(_heuristicName + " will use " + CONTAINER_MEM_SEVERITY + " with the following threshold settings: " + logger.info(heuristicName + " will use " + CONTAINER_MEM_SEVERITY + " with the following threshold settings: " + Arrays.toString(memoryLimits)); for (int i = 0; i < memoryLimits.length; i++) { memoryLimits[i] = memoryLimits[i] * CONTAINER_MEMORY_DEFAULT_BYTES; } } - protected GenericMemoryHeuristic(String containerMemConf, String heuristicName, - HeuristicConfigurationData heuristicConfData) { - this._heuristicName = heuristicName; + protected GenericMemoryHeuristic(String containerMemConf, HeuristicConfigurationData heuristicConfData) { this._containerMemConf = containerMemConf; this._heuristicConfData = heuristicConfData; @@ -94,6 +83,11 @@ protected GenericMemoryHeuristic(String containerMemConf, String heuristicName, protected abstract MapReduceTaskData[] getTasks(MapReduceApplicationData data); + @Override + public HeuristicConfigurationData getHeuristicConfData() { + return _heuristicConfData; + } + @Override public HeuristicResult apply(MapReduceApplicationData data) { @@ -102,6 +96,9 @@ public HeuristicResult apply(MapReduceApplicationData data) { } String containerSizeStr = data.getConf().getProperty(_containerMemConf); + if (containerSizeStr == null) { + return null; + } long containerMem; try { @@ -151,15 +148,16 @@ public HeuristicResult apply(MapReduceApplicationData data) { severity = getTaskMemoryUtilSeverity(taskPMemAvg, containerMem); } - HeuristicResult result = new HeuristicResult(_heuristicName, severity); + HeuristicResult result = new HeuristicResult(_heuristicConfData.getClassName(), + _heuristicConfData.getHeuristicName(), severity, Utils.getHeuristicScore(severity, tasks.length)); - result.addDetail("Number of tasks", Integer.toString(tasks.length)); - result.addDetail("Avg task runtime", Statistics.readableTimespan(averageTimeMs)); - result.addDetail("Avg Physical Memory (MB)", Long.toString(taskPMemAvg/FileUtils.ONE_MB)); - result.addDetail("Max Physical Memory (MB)", Long.toString(taskPMax/FileUtils.ONE_MB)); - result.addDetail("Min Physical Memory (MB)", Long.toString(taskPMin/FileUtils.ONE_MB)); - result.addDetail("Avg Virtual Memory (MB)", Long.toString(taskVMemAvg/FileUtils.ONE_MB)); - result.addDetail("Requested Container Memory", FileUtils.byteCountToDisplaySize(containerMem)); + result.addResultDetail("Number of tasks", Integer.toString(tasks.length)); + result.addResultDetail("Avg task runtime", Statistics.readableTimespan(averageTimeMs)); + result.addResultDetail("Avg Physical Memory (MB)", Long.toString(taskPMemAvg / FileUtils.ONE_MB)); + result.addResultDetail("Max Physical Memory (MB)", Long.toString(taskPMax / FileUtils.ONE_MB)); + result.addResultDetail("Min Physical Memory (MB)", Long.toString(taskPMin / FileUtils.ONE_MB)); + result.addResultDetail("Avg Virtual Memory (MB)", Long.toString(taskVMemAvg / FileUtils.ONE_MB)); + result.addResultDetail("Requested Container Memory", FileUtils.byteCountToDisplaySize(containerMem)); return result; } diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/JobQueueLimitHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/JobQueueLimitHeuristic.java index 2e715b8ed..0ca8c0ce8 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/JobQueueLimitHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/JobQueueLimitHeuristic.java @@ -28,22 +28,23 @@ public class JobQueueLimitHeuristic implements Heuristic { - public static final String HEURISTIC_NAME = "Queue Time Limit"; private HeuristicConfigurationData _heuristicConfData; - @Override - public String getHeuristicName() { - return HEURISTIC_NAME; - } - protected JobQueueLimitHeuristic(HeuristicConfigurationData heuristicConfData) { this._heuristicConfData = heuristicConfData; } + @Override + public HeuristicConfigurationData getHeuristicConfData() { + return _heuristicConfData; + } + @Override public HeuristicResult apply(MapReduceApplicationData data) { - HeuristicResult result = new HeuristicResult(HEURISTIC_NAME, Severity.NONE); + + HeuristicResult result = new HeuristicResult(_heuristicConfData.getClassName(), + _heuristicConfData.getHeuristicName(), Severity.NONE, 0); Properties jobConf = data.getConf(); long queueTimeoutLimitMs = TimeUnit.MINUTES.toMillis(15); @@ -59,29 +60,29 @@ public HeuristicResult apply(MapReduceApplicationData data) { Severity[] mapTasksSeverity = new Severity[mapTasks.length]; Severity[] redTasksSeverity = new Severity[redTasks.length]; if (queueName.equals("default")) { - result.addDetail("Queue: ", queueName); - result.addDetail("Number of Map tasks", Integer.toString(mapTasks.length)); - result.addDetail("Number of Reduce tasks", Integer.toString(redTasks.length)); + result.addResultDetail("Queue: ", queueName, null); + result.addResultDetail("Number of Map tasks", Integer.toString(mapTasks.length)); + result.addResultDetail("Number of Reduce tasks", Integer.toString(redTasks.length)); // Calculate Severity of Mappers mapTasksSeverity = getTasksSeverity(mapTasks, queueTimeoutLimitMs); - result.addDetail("Number of Map tasks that are in severe state (14 to 14.5 min)", + result.addResultDetail("Number of Map tasks that are in severe state (14 to 14.5 min)", Long.toString(getSeverityFrequency(Severity.SEVERE, mapTasksSeverity))); - result.addDetail("Number of Map tasks that are in critical state (over 14.5 min)", + result.addResultDetail("Number of Map tasks that are in critical state (over 14.5 min)", Long.toString(getSeverityFrequency(Severity.CRITICAL, mapTasksSeverity))); // Calculate Severity of Reducers redTasksSeverity = getTasksSeverity(redTasks, queueTimeoutLimitMs); - result.addDetail("Number of Reduce tasks that are in severe state (14 to 14.5 min)", + result.addResultDetail("Number of Reduce tasks that are in severe state (14 to 14.5 min)", Long.toString(getSeverityFrequency(Severity.SEVERE, redTasksSeverity))); - result.addDetail("Number of Reduce tasks that are in critical state (over 14.5 min)", + result.addResultDetail("Number of Reduce tasks that are in critical state (over 14.5 min)", Long.toString(getSeverityFrequency(Severity.CRITICAL, redTasksSeverity))); // Calculate Job severity result.setSeverity(Severity.max(Severity.max(mapTasksSeverity), Severity.max(redTasksSeverity))); } else { - result.addDetail("This Heuristic is not applicable to " + queueName + " queue"); + result.addResultDetail("Not Applicable", "This Heuristic is not applicable to " + queueName + " queue"); result.setSeverity(Severity.NONE); } return result; diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperDataSkewHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperDataSkewHeuristic.java index 800046847..e598b6807 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperDataSkewHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperDataSkewHeuristic.java @@ -26,10 +26,9 @@ * This Heuristic analyses the skewness in the mapper input data */ public class MapperDataSkewHeuristic extends GenericDataSkewHeuristic { - public static final String HEURISTIC_NAME = "Mapper Data Skew"; public MapperDataSkewHeuristic(HeuristicConfigurationData heuristicConfData) { - super(MapReduceCounterData.CounterName.HDFS_BYTES_READ, HEURISTIC_NAME, heuristicConfData); + super(MapReduceCounterData.CounterName.HDFS_BYTES_READ, heuristicConfData); } @Override diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperGCHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperGCHeuristic.java index 2736e860f..e57320128 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperGCHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperGCHeuristic.java @@ -22,15 +22,9 @@ public class MapperGCHeuristic extends GenericGCHeuristic { - public static final String HEURISTIC_NAME = "Mapper GC"; public MapperGCHeuristic(HeuristicConfigurationData heuristicConfData) { - super(HEURISTIC_NAME, heuristicConfData); - } - - @Override - public String getHeuristicName() { - return HEURISTIC_NAME; + super(heuristicConfData); } @Override diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperMemoryHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperMemoryHeuristic.java index 741c094d4..b973b7450 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperMemoryHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperMemoryHeuristic.java @@ -22,16 +22,10 @@ public class MapperMemoryHeuristic extends GenericMemoryHeuristic { - public static final String HEURISTIC_NAME = "Mapper Memory"; public static final String MAPPER_MEMORY_CONF = "mapreduce.map.memory.mb"; public MapperMemoryHeuristic(HeuristicConfigurationData _heuristicConfData) { - super(MAPPER_MEMORY_CONF, HEURISTIC_NAME, _heuristicConfData); - } - - @Override - public String getHeuristicName() { - return HEURISTIC_NAME; + super(MAPPER_MEMORY_CONF, _heuristicConfData); } @Override diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristic.java index 2b9c1c820..6cdd1abf5 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpeedHeuristic.java @@ -39,7 +39,6 @@ public class MapperSpeedHeuristic implements Heuristic { private static final Logger logger = Logger.getLogger(MapperSpeedHeuristic.class); - public static final String HEURISTIC_NAME = "Mapper Speed"; // Severity parameters. private static final String DISK_SPEED_SEVERITY = "disk_speed_severity"; @@ -53,26 +52,23 @@ public class MapperSpeedHeuristic implements Heuristic private void loadParameters() { Map paramMap = _heuristicConfData.getParamMap(); + String heuristicName = _heuristicConfData.getHeuristicName(); - if(paramMap.get(DISK_SPEED_SEVERITY) != null) { - double[] confDiskSpeedThreshold = Utils.getParam(paramMap.get(DISK_SPEED_SEVERITY), diskSpeedLimits.length); - if (confDiskSpeedThreshold != null) { - diskSpeedLimits = confDiskSpeedThreshold; - } + double[] confDiskSpeedThreshold = Utils.getParam(paramMap.get(DISK_SPEED_SEVERITY), diskSpeedLimits.length); + if (confDiskSpeedThreshold != null) { + diskSpeedLimits = confDiskSpeedThreshold; } - logger.info(HEURISTIC_NAME + " will use " + DISK_SPEED_SEVERITY + " with the following threshold settings: " + logger.info(heuristicName + " will use " + DISK_SPEED_SEVERITY + " with the following threshold settings: " + Arrays.toString(diskSpeedLimits)); for (int i = 0; i < diskSpeedLimits.length; i++) { diskSpeedLimits[i] = diskSpeedLimits[i] * HDFSContext.HDFS_BLOCK_SIZE; } - if(paramMap.get(RUNTIME_SEVERITY) != null) { - double[] confRuntimeThreshold = Utils.getParam(paramMap.get(RUNTIME_SEVERITY), runtimeLimits.length); - if (confRuntimeThreshold != null) { - runtimeLimits = confRuntimeThreshold; - } + double[] confRuntimeThreshold = Utils.getParam(paramMap.get(RUNTIME_SEVERITY), runtimeLimits.length); + if (confRuntimeThreshold != null) { + runtimeLimits = confRuntimeThreshold; } - logger.info(HEURISTIC_NAME + " will use " + RUNTIME_SEVERITY + " with the following threshold settings: " + Arrays + logger.info(heuristicName + " will use " + RUNTIME_SEVERITY + " with the following threshold settings: " + Arrays .toString(runtimeLimits)); for (int i = 0; i < runtimeLimits.length; i++) { runtimeLimits[i] = runtimeLimits[i] * Statistics.MINUTE_IN_MS; @@ -85,8 +81,8 @@ public MapperSpeedHeuristic(HeuristicConfigurationData heuristicConfData) { } @Override - public String getHeuristicName() { - return HEURISTIC_NAME; + public HeuristicConfigurationData getHeuristicConfData() { + return _heuristicConfData; } @Override @@ -133,12 +129,13 @@ public HeuristicResult apply(MapReduceApplicationData data) { //This reduces severity if task runtime is insignificant severity = Severity.min(severity, getRuntimeSeverity(medianRuntimeMs)); - HeuristicResult result = new HeuristicResult(HEURISTIC_NAME, severity); + HeuristicResult result = new HeuristicResult(_heuristicConfData.getClassName(), + _heuristicConfData.getHeuristicName(), severity, Utils.getHeuristicScore(severity, tasks.length)); - result.addDetail("Number of tasks", Integer.toString(tasks.length)); - result.addDetail("Median task input size", FileUtils.byteCountToDisplaySize(medianSize)); - result.addDetail("Median task runtime", Statistics.readableTimespan(medianRuntimeMs)); - result.addDetail("Median task speed", FileUtils.byteCountToDisplaySize(medianSpeed) + "/s"); + result.addResultDetail("Number of tasks", Integer.toString(tasks.length)); + result.addResultDetail("Median task input size", FileUtils.byteCountToDisplaySize(medianSize)); + result.addResultDetail("Median task runtime", Statistics.readableTimespan(medianRuntimeMs)); + result.addResultDetail("Median task speed", FileUtils.byteCountToDisplaySize(medianSpeed) + "/s"); return result; } diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristic.java index ea5fc9ec5..54c875734 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperSpillHeuristic.java @@ -31,7 +31,6 @@ public class MapperSpillHeuristic implements Heuristic { private static final Logger logger = Logger.getLogger(MapperSpillHeuristic.class); - public static final String HEURISTIC_NAME = "Mapper Spill"; private static final long THRESHOLD_SPILL_FACTOR = 10000; // Severity parameters. @@ -46,24 +45,21 @@ public class MapperSpillHeuristic implements Heuristic private void loadParameters() { Map paramMap = _heuristicConfData.getParamMap(); + String heuristicName = _heuristicConfData.getHeuristicName(); - if(paramMap.get(NUM_TASKS_SEVERITY) != null) { - double[] confNumTasksThreshold = Utils.getParam(paramMap.get(NUM_TASKS_SEVERITY), numTasksLimits.length); - if (confNumTasksThreshold != null) { - numTasksLimits = confNumTasksThreshold; - } + double[] confNumTasksThreshold = Utils.getParam(paramMap.get(NUM_TASKS_SEVERITY), numTasksLimits.length); + if (confNumTasksThreshold != null) { + numTasksLimits = confNumTasksThreshold; } - logger.info(HEURISTIC_NAME + " will use " + NUM_TASKS_SEVERITY + " with the following threshold settings: " + logger.info(heuristicName + " will use " + NUM_TASKS_SEVERITY + " with the following threshold settings: " + Arrays.toString(numTasksLimits)); - if(paramMap.get(SPILL_SEVERITY) != null) { - double[] confSpillThreshold = Utils.getParam(paramMap.get(SPILL_SEVERITY), spillLimits.length); - if (confSpillThreshold != null) { - spillLimits = confSpillThreshold; - } + double[] confSpillThreshold = Utils.getParam(paramMap.get(SPILL_SEVERITY), spillLimits.length); + if (confSpillThreshold != null) { + spillLimits = confSpillThreshold; } - logger.info(HEURISTIC_NAME + " will use " + SPILL_SEVERITY + " with the following threshold settings: " - + Arrays.toString(spillLimits)); + logger.info(heuristicName + " will use " + SPILL_SEVERITY + " with the following threshold settings: " + Arrays + .toString(spillLimits)); for (int i = 0; i < spillLimits.length; i++) { spillLimits[i] = spillLimits[i] * THRESHOLD_SPILL_FACTOR; } @@ -74,6 +70,11 @@ public MapperSpillHeuristic(HeuristicConfigurationData heuristicConfData) { loadParameters(); } + @Override + public HeuristicConfigurationData getHeuristicConfData() { + return _heuristicConfData; + } + @Override public HeuristicResult apply(MapReduceApplicationData data) { @@ -108,23 +109,20 @@ public HeuristicResult apply(MapReduceApplicationData data) { Severity taskSeverity = getNumTasksSeverity(tasks.length); severity = Severity.min(severity, taskSeverity); - HeuristicResult result = new HeuristicResult(HEURISTIC_NAME, severity); + HeuristicResult result = new HeuristicResult(_heuristicConfData.getClassName(), + _heuristicConfData.getHeuristicName(), severity, Utils.getHeuristicScore(severity, tasks.length)); - result.addDetail("Number of tasks", Integer.toString(tasks.length)); - result.addDetail("Avg spilled records per task", tasks.length == 0 ? "0" : Long.toString(totalSpills/tasks.length)); - result.addDetail( - "Avg output records per task", tasks.length == 0 ? "0" : Long.toString(totalOutputRecords/tasks.length)); - result.addDetail("Ratio of spilled records to output records", Double.toString(ratioSpills)); + result.addResultDetail("Number of tasks", Integer.toString(tasks.length)); + result.addResultDetail("Avg spilled records per task", + tasks.length == 0 ? "0" : Long.toString(totalSpills / tasks.length)); + result.addResultDetail("Avg output records per task", + tasks.length == 0 ? "0" : Long.toString(totalOutputRecords / tasks.length)); + result.addResultDetail("Ratio of spilled records to output records", Double.toString(ratioSpills)); return result; } - @Override - public String getHeuristicName() { - return HEURISTIC_NAME; - } - private Severity getSpillSeverity(double ratioSpills) { long normalizedSpillRatio = 0; diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristic.java index 3c99d5f33..2b7f32564 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/MapperTimeHeuristic.java @@ -37,7 +37,6 @@ public class MapperTimeHeuristic implements Heuristic { private static final Logger logger = Logger.getLogger(MapperTimeHeuristic.class); - public static final String HEURISTIC_NAME = "Mapper Time"; // Severity parameters. private static final String SHORT_RUNTIME_SEVERITY = "short_runtime_severity_in_min"; @@ -53,39 +52,34 @@ public class MapperTimeHeuristic implements Heuristic private void loadParameters() { Map paramMap = _heuristicConfData.getParamMap(); + String heuristicName = _heuristicConfData.getHeuristicName(); - if(paramMap.get(SHORT_RUNTIME_SEVERITY) != null) { - double[] confShortThreshold = Utils.getParam(paramMap.get(SHORT_RUNTIME_SEVERITY), shortRuntimeLimits.length); - if (confShortThreshold != null) { - shortRuntimeLimits = confShortThreshold; - } + double[] confShortThreshold = Utils.getParam(paramMap.get(SHORT_RUNTIME_SEVERITY), shortRuntimeLimits.length); + if (confShortThreshold != null) { + shortRuntimeLimits = confShortThreshold; } - logger.info(HEURISTIC_NAME + " will use " + SHORT_RUNTIME_SEVERITY + " with the following threshold settings: " + logger.info(heuristicName + " will use " + SHORT_RUNTIME_SEVERITY + " with the following threshold settings: " + Arrays.toString(shortRuntimeLimits)); for (int i = 0; i < shortRuntimeLimits.length; i++) { shortRuntimeLimits[i] = shortRuntimeLimits[i] * Statistics.MINUTE_IN_MS; } - if(paramMap.get(LONG_RUNTIME_SEVERITY) != null) { - double[] confLongThreshold = Utils.getParam(paramMap.get(LONG_RUNTIME_SEVERITY), longRuntimeLimits.length); - if (confLongThreshold != null) { - longRuntimeLimits = confLongThreshold; - } + double[] confLongThreshold = Utils.getParam(paramMap.get(LONG_RUNTIME_SEVERITY), longRuntimeLimits.length); + if (confLongThreshold != null) { + longRuntimeLimits = confLongThreshold; } - logger.info(HEURISTIC_NAME + " will use " + LONG_RUNTIME_SEVERITY + " with the following threshold settings: " + logger.info(heuristicName + " will use " + LONG_RUNTIME_SEVERITY + " with the following threshold settings: " + Arrays.toString(longRuntimeLimits)); for (int i = 0; i < longRuntimeLimits.length; i++) { longRuntimeLimits[i] = longRuntimeLimits[i] * Statistics.MINUTE_IN_MS; } - if(paramMap.get(NUM_TASKS_SEVERITY) != null) { - double[] confNumTasksThreshold = Utils.getParam(paramMap.get(NUM_TASKS_SEVERITY), numTasksLimits.length); - if (confNumTasksThreshold != null) { - numTasksLimits = confNumTasksThreshold; - } + double[] confNumTasksThreshold = Utils.getParam(paramMap.get(NUM_TASKS_SEVERITY), numTasksLimits.length); + if (confNumTasksThreshold != null) { + numTasksLimits = confNumTasksThreshold; } - logger.info(HEURISTIC_NAME + " will use " + NUM_TASKS_SEVERITY + " with the following threshold settings: " - + Arrays.toString(numTasksLimits)); + logger.info(heuristicName + " will use " + NUM_TASKS_SEVERITY + " with the following threshold settings: " + Arrays + .toString(numTasksLimits)); } public MapperTimeHeuristic(HeuristicConfigurationData heuristicConfData) { @@ -94,8 +88,8 @@ public MapperTimeHeuristic(HeuristicConfigurationData heuristicConfData) { } @Override - public String getHeuristicName() { - return HEURISTIC_NAME; + public HeuristicConfigurationData getHeuristicConfData() { + return _heuristicConfData; } @Override @@ -134,13 +128,14 @@ public HeuristicResult apply(MapReduceApplicationData data) { Severity longTaskSeverity = longTaskSeverity(tasks.length, averageTimeMs); Severity severity = Severity.max(shortTaskSeverity, longTaskSeverity); - HeuristicResult result = new HeuristicResult(HEURISTIC_NAME, severity); + HeuristicResult result = new HeuristicResult(_heuristicConfData.getClassName(), + _heuristicConfData.getHeuristicName(), severity, Utils.getHeuristicScore(severity, tasks.length)); - result.addDetail("Number of tasks", Integer.toString(tasks.length)); - result.addDetail("Average task input size", FileUtils.byteCountToDisplaySize(averageSize)); - result.addDetail("Average task runtime", Statistics.readableTimespan(averageTimeMs)); - result.addDetail("Max task runtime", Statistics.readableTimespan(taskMaxMs)); - result.addDetail("Min task runtime", Statistics.readableTimespan(taskMinMs)); + result.addResultDetail("Number of tasks", Integer.toString(tasks.length)); + result.addResultDetail("Average task input size", FileUtils.byteCountToDisplaySize(averageSize)); + result.addResultDetail("Average task runtime", Statistics.readableTimespan(averageTimeMs)); + result.addResultDetail("Max task runtime", Statistics.readableTimespan(taskMaxMs)); + result.addResultDetail("Min task runtime", Statistics.readableTimespan(taskMinMs)); return result; } diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerDataSkewHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerDataSkewHeuristic.java index d030d265e..19d21bd70 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerDataSkewHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerDataSkewHeuristic.java @@ -26,10 +26,9 @@ * This Heuristic analyses the skewness in the reducer input data */ public class ReducerDataSkewHeuristic extends GenericDataSkewHeuristic { - public static final String HEURISTIC_NAME = "Reducer Data Skew"; public ReducerDataSkewHeuristic(HeuristicConfigurationData heuristicConfData) { - super(MapReduceCounterData.CounterName.REDUCE_SHUFFLE_BYTES, HEURISTIC_NAME, heuristicConfData); + super(MapReduceCounterData.CounterName.REDUCE_SHUFFLE_BYTES, heuristicConfData); } @Override diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerGCHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerGCHeuristic.java index a8fbeeb84..a4891760c 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerGCHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerGCHeuristic.java @@ -22,15 +22,9 @@ public class ReducerGCHeuristic extends GenericGCHeuristic { - public static final String HEURISTIC_NAME = "Reducer GC"; public ReducerGCHeuristic(HeuristicConfigurationData _heuristicConfData) { - super(HEURISTIC_NAME, _heuristicConfData); - } - - @Override - public String getHeuristicName() { - return HEURISTIC_NAME; + super(_heuristicConfData); } @Override diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerMemoryHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerMemoryHeuristic.java index 7a1e805d0..1976e4637 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerMemoryHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerMemoryHeuristic.java @@ -22,16 +22,10 @@ public class ReducerMemoryHeuristic extends GenericMemoryHeuristic { - public static final String HEURISTIC_NAME = "Reducer Memory"; public static final String REDUCER_MEMORY_CONF = "mapreduce.reduce.memory.mb"; public ReducerMemoryHeuristic(HeuristicConfigurationData _heuristicConfData) { - super(REDUCER_MEMORY_CONF, HEURISTIC_NAME, _heuristicConfData); - } - - @Override - public String getHeuristicName() { - return HEURISTIC_NAME; + super(REDUCER_MEMORY_CONF, _heuristicConfData); } @Override diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristic.java index e026ff459..e55ed8aef 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/ReducerTimeHeuristic.java @@ -34,7 +34,6 @@ public class ReducerTimeHeuristic implements Heuristic { private static final Logger logger = Logger.getLogger(ReducerTimeHeuristic.class); - public static final String HEURISTIC_NAME = "Reducer Time"; // Severity parameters. private static final String SHORT_RUNTIME_SEVERITY = "short_runtime_severity_in_min"; @@ -50,39 +49,34 @@ public class ReducerTimeHeuristic implements Heuristic private void loadParameters() { Map paramMap = _heuristicConfData.getParamMap(); + String heuristicName = _heuristicConfData.getHeuristicName(); - if(paramMap.get(SHORT_RUNTIME_SEVERITY) != null) { - double[] confShortRuntimeLimits = Utils.getParam(paramMap.get(SHORT_RUNTIME_SEVERITY), shortRuntimeLimits.length); - if (confShortRuntimeLimits != null) { - shortRuntimeLimits = confShortRuntimeLimits; - } + double[] confShortRuntimeLimits = Utils.getParam(paramMap.get(SHORT_RUNTIME_SEVERITY), shortRuntimeLimits.length); + if (confShortRuntimeLimits != null) { + shortRuntimeLimits = confShortRuntimeLimits; } - logger.info(HEURISTIC_NAME + " will use " + SHORT_RUNTIME_SEVERITY + " with the following threshold settings: " + logger.info(heuristicName + " will use " + SHORT_RUNTIME_SEVERITY + " with the following threshold settings: " + Arrays.toString(shortRuntimeLimits)); for (int i = 0; i < shortRuntimeLimits.length; i++) { shortRuntimeLimits[i] = shortRuntimeLimits[i] * Statistics.MINUTE_IN_MS; } - if(paramMap.get(LONG_RUNTIME_SEVERITY) != null) { - double[] confLongRuntimeLimitss = Utils.getParam(paramMap.get(LONG_RUNTIME_SEVERITY), longRuntimeLimits.length); - if (confLongRuntimeLimitss != null) { - longRuntimeLimits = confLongRuntimeLimitss; - } + double[] confLongRuntimeLimitss = Utils.getParam(paramMap.get(LONG_RUNTIME_SEVERITY), longRuntimeLimits.length); + if (confLongRuntimeLimitss != null) { + longRuntimeLimits = confLongRuntimeLimitss; } - logger.info(HEURISTIC_NAME + " will use " + LONG_RUNTIME_SEVERITY + " with the following threshold settings: " + logger.info(heuristicName + " will use " + LONG_RUNTIME_SEVERITY + " with the following threshold settings: " + Arrays.toString(longRuntimeLimits)); for (int i = 0; i < longRuntimeLimits.length; i++) { longRuntimeLimits[i] = longRuntimeLimits[i] * Statistics.MINUTE_IN_MS; } - if(paramMap.get(NUM_TASKS_SEVERITY) != null) { - double[] confNumTasksLimits = Utils.getParam(paramMap.get(NUM_TASKS_SEVERITY), numTasksLimits.length); - if (confNumTasksLimits != null) { - numTasksLimits = confNumTasksLimits; - } + double[] confNumTasksLimits = Utils.getParam(paramMap.get(NUM_TASKS_SEVERITY), numTasksLimits.length); + if (confNumTasksLimits != null) { + numTasksLimits = confNumTasksLimits; } - logger.info(HEURISTIC_NAME + " will use " + NUM_TASKS_SEVERITY + " with the following threshold settings: " - + Arrays.toString(numTasksLimits)); + logger.info(heuristicName + " will use " + NUM_TASKS_SEVERITY + " with the following threshold settings: " + Arrays + .toString(numTasksLimits)); } @@ -92,8 +86,8 @@ public ReducerTimeHeuristic(HeuristicConfigurationData heuristicConfData) { } @Override - public String getHeuristicName() { - return HEURISTIC_NAME; + public HeuristicConfigurationData getHeuristicConfData() { + return _heuristicConfData; } @Override @@ -129,12 +123,13 @@ public HeuristicResult apply(MapReduceApplicationData data) { Severity longTimeSeverity = longTimeSeverity(averageRuntimeMs, tasks.length); Severity severity = Severity.max(shortTimeSeverity, longTimeSeverity); - HeuristicResult result = new HeuristicResult(HEURISTIC_NAME, severity); + HeuristicResult result = new HeuristicResult(_heuristicConfData.getClassName(), + _heuristicConfData.getHeuristicName(), severity, Utils.getHeuristicScore(severity, tasks.length)); - result.addDetail("Number of tasks", Integer.toString(tasks.length)); - result.addDetail("Average task runtime", Statistics.readableTimespan(averageRuntimeMs)); - result.addDetail("Max task runtime", Statistics.readableTimespan(taskMaxMs)); - result.addDetail("Min task runtime", Statistics.readableTimespan(taskMinMs)); + result.addResultDetail("Number of tasks", Integer.toString(tasks.length)); + result.addResultDetail("Average task runtime", Statistics.readableTimespan(averageRuntimeMs)); + result.addResultDetail("Max task runtime", Statistics.readableTimespan(taskMaxMs)); + result.addResultDetail("Min task runtime", Statistics.readableTimespan(taskMinMs)); return result; } diff --git a/app/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristic.java b/app/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristic.java index 2d70a8239..fdc4337c6 100644 --- a/app/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristic.java +++ b/app/com/linkedin/drelephant/mapreduce/heuristics/ShuffleSortHeuristic.java @@ -37,7 +37,6 @@ */ public class ShuffleSortHeuristic implements Heuristic { private static final Logger logger = Logger.getLogger(ShuffleSortHeuristic.class); - public static final String HEURISTIC_NAME = "Shuffle & Sort"; // Severity parameters. private static final String RUNTIME_RATIO_SEVERITY = "runtime_ratio_severity"; @@ -51,24 +50,21 @@ public class ShuffleSortHeuristic implements Heuristic private void loadParameters() { Map paramMap = _heuristicConfData.getParamMap(); + String heuristicName = _heuristicConfData.getHeuristicName(); - if(paramMap.get(RUNTIME_RATIO_SEVERITY) != null) { - double[] confRatioLimitsd = Utils.getParam(paramMap.get(RUNTIME_RATIO_SEVERITY), runtimeRatioLimits.length); - if (confRatioLimitsd != null) { - runtimeRatioLimits = confRatioLimitsd; - } + double[] confRatioLimitsd = Utils.getParam(paramMap.get(RUNTIME_RATIO_SEVERITY), runtimeRatioLimits.length); + if (confRatioLimitsd != null) { + runtimeRatioLimits = confRatioLimitsd; } - logger.info(HEURISTIC_NAME + " will use " + RUNTIME_RATIO_SEVERITY + " with the following threshold settings: " + logger.info(heuristicName + " will use " + RUNTIME_RATIO_SEVERITY + " with the following threshold settings: " + Arrays.toString(runtimeRatioLimits)); - if(paramMap.get(RUNTIME_SEVERITY) != null) { - double[] confRuntimeLimits = Utils.getParam(paramMap.get(RUNTIME_SEVERITY), runtimeLimits.length); - if (confRuntimeLimits != null) { - runtimeLimits = confRuntimeLimits; - } + double[] confRuntimeLimits = Utils.getParam(paramMap.get(RUNTIME_SEVERITY), runtimeLimits.length); + if (confRuntimeLimits != null) { + runtimeLimits = confRuntimeLimits; } - logger.info(HEURISTIC_NAME + " will use " + RUNTIME_SEVERITY + " with the following threshold settings: " - + Arrays.toString(runtimeLimits)); + logger.info(heuristicName + " will use " + RUNTIME_SEVERITY + " with the following threshold settings: " + Arrays + .toString(runtimeLimits)); for (int i = 0; i < runtimeLimits.length; i++) { runtimeLimits[i] = runtimeLimits[i] * Statistics.MINUTE_IN_MS; } @@ -80,8 +76,8 @@ public ShuffleSortHeuristic(HeuristicConfigurationData heuristicConfData) { } @Override - public String getHeuristicName() { - return HEURISTIC_NAME; + public HeuristicConfigurationData getHeuristicConfData() { + return _heuristicConfData; } @Override @@ -114,14 +110,15 @@ public HeuristicResult apply(MapReduceApplicationData data) { Severity sortSeverity = getShuffleSortSeverity(avgSortTimeMs, avgExecTimeMs); Severity severity = Severity.max(shuffleSeverity, sortSeverity); - HeuristicResult result = new HeuristicResult(HEURISTIC_NAME, severity); + HeuristicResult result = new HeuristicResult(_heuristicConfData.getClassName(), + _heuristicConfData.getHeuristicName(), severity, Utils.getHeuristicScore(severity, tasks.length)); - result.addDetail("Number of tasks", Integer.toString(data.getReducerData().length)); - result.addDetail("Average code runtime", Statistics.readableTimespan(avgExecTimeMs)); + result.addResultDetail("Number of tasks", Integer.toString(data.getReducerData().length)); + result.addResultDetail("Average code runtime", Statistics.readableTimespan(avgExecTimeMs)); String shuffleFactor = Statistics.describeFactor(avgShuffleTimeMs, avgExecTimeMs, "x"); - result.addDetail("Average shuffle time", Statistics.readableTimespan(avgShuffleTimeMs) + " " + shuffleFactor); + result.addResultDetail("Average shuffle time", Statistics.readableTimespan(avgShuffleTimeMs) + " " + shuffleFactor); String sortFactor = Statistics.describeFactor(avgSortTimeMs, avgExecTimeMs, "x"); - result.addDetail("Average sort time", Statistics.readableTimespan(avgSortTimeMs) + " " + sortFactor); + result.addResultDetail("Average sort time", Statistics.readableTimespan(avgSortTimeMs) + " " + sortFactor); return result; } diff --git a/app/com/linkedin/drelephant/math/Statistics.java b/app/com/linkedin/drelephant/math/Statistics.java index bb6a2a515..aab1e1783 100644 --- a/app/com/linkedin/drelephant/math/Statistics.java +++ b/app/com/linkedin/drelephant/math/Statistics.java @@ -111,6 +111,12 @@ private static long[] toArray(List input) { return result; } + /** + * Compute average for the given array of long + * + * @param values the values + * @return The average(values) + */ public static long average(long[] values) { //Find average double sum = 0d; @@ -120,6 +126,12 @@ public static long average(long[] values) { return (long) (sum / (double) values.length); } + /** + * Compute average for a List of long values + * + * @param values the values + * @return The average(values) + */ public static long average(List values) { //Find average double sum = 0d; @@ -129,6 +141,12 @@ public static long average(List values) { return (long) (sum / (double) values.size()); } + /** + * Find the median of the given list + * + * @param values The values + * @return The median(values) + */ public static long median(List values) { if (values.size() == 0) { throw new IllegalArgumentException("Median of an empty list is not defined."); @@ -142,6 +160,16 @@ public static long median(List values) { } } + /** + * Compute ratio and display it with a suffix. + * + * Example: Average sort time (0.14x) + * + * @param value The value to be compared + * @param compare The value compared against + * @param suffix The suffix string + * @return The ratio followed by suffix + */ public static String describeFactor(long value, long compare, String suffix) { double factor = (double) value / (double) compare; if (Double.isNaN(factor)) { @@ -150,6 +178,12 @@ public static String describeFactor(long value, long compare, String suffix) { return "(" + String.format("%.2f", factor) + suffix + ")"; } + /** + * Convert milliseconds to readable value + * + * @param milliseconds The number of milliseconds + * @return A String of readable time + */ public static String readableTimespan(long milliseconds) { if (milliseconds == 0) { return "0 sec"; @@ -162,13 +196,13 @@ public static String readableTimespan(long milliseconds) { seconds %= 60; StringBuilder sb = new StringBuilder(); if (hours > 0) { - sb.append(hours).append("hr "); + sb.append(hours).append(" hr"); } if (minutes > 0) { - sb.append(minutes).append("min "); + sb.append(minutes).append(" min"); } if (seconds > 0) { - sb.append(seconds).append("sec "); + sb.append(seconds).append(" sec"); } return sb.toString().trim(); } diff --git a/app/com/linkedin/drelephant/notifications/EmailThread.java b/app/com/linkedin/drelephant/notifications/EmailThread.java index 4e9541f1d..2833457c5 100644 --- a/app/com/linkedin/drelephant/notifications/EmailThread.java +++ b/app/com/linkedin/drelephant/notifications/EmailThread.java @@ -17,7 +17,7 @@ package com.linkedin.drelephant.notifications; import com.linkedin.drelephant.analysis.Severity; -import model.JobResult; +import models.AppResult; import org.apache.commons.mail.DefaultAuthenticator; import org.apache.commons.mail.EmailException; import org.apache.commons.mail.HtmlEmail; @@ -30,7 +30,7 @@ public class EmailThread extends Thread { - private LinkedBlockingQueue _resultQueue; + private LinkedBlockingQueue _resultQueue; private AtomicBoolean _running = new AtomicBoolean(true); private String _smtpHost; @@ -40,7 +40,7 @@ public class EmailThread extends Thread { public EmailThread() { setName("Email Thread"); - _resultQueue = new LinkedBlockingQueue(); + _resultQueue = new LinkedBlockingQueue(); _smtpHost = Play.application().configuration().getString("smtp.host"); _smtpPort = Play.application().configuration().getInt("smtp.port"); String smtpUser = Play.application().configuration().getString("smtp.user"); @@ -54,7 +54,7 @@ public EmailThread() { @Override public void run() { while (_running.get()) { - JobResult result = null; + AppResult result = null; while (result == null && _running.get()) { try { result = _resultQueue.take(); @@ -80,7 +80,7 @@ public void kill() { this.interrupt(); } - public void enqueue(JobResult result) { + public void enqueue(AppResult result) { try { _resultQueue.put(result); } catch (InterruptedException e) { @@ -88,7 +88,7 @@ public void enqueue(JobResult result) { } } - private void sendCriticalEmail(JobResult result) { + private void sendCriticalEmail(AppResult result) { try { //Generate content String html = emailcritical.render(result).body(); diff --git a/app/com/linkedin/drelephant/spark/heuristics/BestPropertiesConventionHeuristic.java b/app/com/linkedin/drelephant/spark/heuristics/BestPropertiesConventionHeuristic.java index 252c5d969..46c0609c2 100644 --- a/app/com/linkedin/drelephant/spark/heuristics/BestPropertiesConventionHeuristic.java +++ b/app/com/linkedin/drelephant/spark/heuristics/BestPropertiesConventionHeuristic.java @@ -36,7 +36,6 @@ public class BestPropertiesConventionHeuristic implements Heuristic { private static final Logger logger = Logger.getLogger(BestPropertiesConventionHeuristic.class); - public static final String HEURISTIC_NAME = "Spark Configuration Best Practice"; public static final String SPARK_SERIALIZER = "spark.serializer"; public static final String SPARK_DRIVER_MEMORY = "spark.driver.memory"; public static final String SPARK_SHUFFLE_MANAGER = "spark.shuffle.manager"; @@ -54,23 +53,20 @@ public class BestPropertiesConventionHeuristic implements Heuristic paramMap = _heuristicConfData.getParamMap(); + String heuristicName = _heuristicConfData.getHeuristicName(); - if(paramMap.get(NUM_CORE_SEVERITY) != null) { - double[] confNumCoreLimit = Utils.getParam(paramMap.get(NUM_CORE_SEVERITY), numCoreLimit.length); - if (confNumCoreLimit != null) { - numCoreLimit = confNumCoreLimit; - } + double[] confNumCoreLimit = Utils.getParam(paramMap.get(NUM_CORE_SEVERITY), numCoreLimit.length); + if (confNumCoreLimit != null) { + numCoreLimit = confNumCoreLimit; } - logger.info(HEURISTIC_NAME + " will use " + NUM_CORE_SEVERITY + " with the following threshold settings: " + logger.info(heuristicName + " will use " + NUM_CORE_SEVERITY + " with the following threshold settings: " + Arrays.toString(numCoreLimit)); - if(paramMap.get(DRIVER_MEM_SEVERITY) != null) { - double[] confDriverMemLimits = Utils.getParam(paramMap.get(DRIVER_MEM_SEVERITY), driverMemLimits.length); - if (confDriverMemLimits != null) { - driverMemLimits = confDriverMemLimits; - } + double[] confDriverMemLimits = Utils.getParam(paramMap.get(DRIVER_MEM_SEVERITY), driverMemLimits.length); + if (confDriverMemLimits != null) { + driverMemLimits = confDriverMemLimits; } - logger.info(HEURISTIC_NAME + " will use " + DRIVER_MEM_SEVERITY + " with the following threshold settings: " + logger.info(heuristicName + " will use " + DRIVER_MEM_SEVERITY + " with the following threshold settings: " + Arrays.toString(driverMemLimits)); for (int i = 0; i < driverMemLimits.length; i++) { driverMemLimits[i] = (double) MemoryFormatUtils.stringToBytes(Double.toString(driverMemLimits[i]) + "G"); @@ -82,6 +78,11 @@ public BestPropertiesConventionHeuristic(HeuristicConfigurationData heuristicCon loadParameters(); } + @Override + public HeuristicConfigurationData getHeuristicConfData() { + return _heuristicConfData; + } + @Override public HeuristicResult apply(SparkApplicationData data) { SparkEnvironmentData env = data.getEnvironmentData(); @@ -97,13 +98,14 @@ public HeuristicResult apply(SparkApplicationData data) { Severity sortSeverity = binarySeverity("sort", sparkShuffleManager, true, Severity.MODERATE); Severity executorCoreSeverity = getCoreNumSeverity(coreNum); - HeuristicResult result = new HeuristicResult(getHeuristicName(), - Severity.max(kryoSeverity, driverMemSeverity, sortSeverity, executorCoreSeverity)); + HeuristicResult result = new HeuristicResult(_heuristicConfData.getClassName(), + _heuristicConfData.getHeuristicName(), Severity.max(kryoSeverity, driverMemSeverity, sortSeverity, + executorCoreSeverity), 0); - result.addDetail(SPARK_SERIALIZER, propertyToString(sparkSerializer)); - result.addDetail(SPARK_DRIVER_MEMORY, propertyToString(sparkDriverMemory)); - result.addDetail(SPARK_SHUFFLE_MANAGER, propertyToString(sparkShuffleManager)); - result.addDetail(SPARK_EXECUTOR_CORES, propertyToString(sparkExecutorCores)); + result.addResultDetail(SPARK_SERIALIZER, propertyToString(sparkSerializer)); + result.addResultDetail(SPARK_DRIVER_MEMORY, propertyToString(sparkDriverMemory)); + result.addResultDetail(SPARK_SHUFFLE_MANAGER, propertyToString(sparkShuffleManager)); + result.addResultDetail(SPARK_EXECUTOR_CORES, propertyToString(sparkExecutorCores)); return result; } @@ -139,11 +141,6 @@ private static Severity binarySeverity(String expectedValue, String actualValue, } private static String propertyToString(String val) { - return val == null ? "not presented, using default" : val; - } - - @Override - public String getHeuristicName() { - return HEURISTIC_NAME; + return val == null ? "Not presented. Using default" : val; } } diff --git a/app/com/linkedin/drelephant/spark/heuristics/EventLogLimitHeuristic.java b/app/com/linkedin/drelephant/spark/heuristics/EventLogLimitHeuristic.java index aeff74578..201b756c2 100644 --- a/app/com/linkedin/drelephant/spark/heuristics/EventLogLimitHeuristic.java +++ b/app/com/linkedin/drelephant/spark/heuristics/EventLogLimitHeuristic.java @@ -28,29 +28,29 @@ * approve it. */ public class EventLogLimitHeuristic implements Heuristic { - public static final String HEURISTIC_NAME = "Spark Event Log Limit"; private HeuristicConfigurationData _heuristicConfData; public EventLogLimitHeuristic(HeuristicConfigurationData heuristicConfData) { this._heuristicConfData = heuristicConfData; } + @Override + public HeuristicConfigurationData getHeuristicConfData() { + return _heuristicConfData; + } + @Override public HeuristicResult apply(SparkApplicationData data) { Severity severity = getSeverity(data); - HeuristicResult result = new HeuristicResult(getHeuristicName(), severity); + HeuristicResult result = new HeuristicResult(_heuristicConfData.getClassName(), + _heuristicConfData.getHeuristicName(), severity, 0); if (severity == Severity.CRITICAL) { - result.addDetail("Spark job's event log passes the limit. No actual log data is fetched." - + " All other heuristic rules will not make sense."); + result.addResultDetail("Large Log File", "Spark job's event log passes the limit. No actual log data is fetched." + + " All other heuristic rules will not make sense.", null); } return result; } - @Override - public String getHeuristicName() { - return HEURISTIC_NAME; - } - private Severity getSeverity(SparkApplicationData data) { if (data.isThrottled()) { return Severity.CRITICAL; diff --git a/app/com/linkedin/drelephant/spark/heuristics/ExecutorLoadHeuristic.java b/app/com/linkedin/drelephant/spark/heuristics/ExecutorLoadHeuristic.java index 9fd778192..086d5fe33 100644 --- a/app/com/linkedin/drelephant/spark/heuristics/ExecutorLoadHeuristic.java +++ b/app/com/linkedin/drelephant/spark/heuristics/ExecutorLoadHeuristic.java @@ -38,7 +38,6 @@ */ public class ExecutorLoadHeuristic implements Heuristic { private static final Logger logger = Logger.getLogger(ExecutorLoadHeuristic.class); - public static final String HEURISTIC_NAME = "Spark Executor Load Balance"; private static final long MEMORY_OBSERVATION_THRESHOLD = MemoryFormatUtils.stringToBytes("1 MB"); // Severity parameters. @@ -53,24 +52,21 @@ public class ExecutorLoadHeuristic implements Heuristic { private void loadParameters() { Map paramMap = _heuristicConfData.getParamMap(); + String heuristicName = _heuristicConfData.getHeuristicName(); - if(paramMap.get(LOOSER_METRIC_DEV_SEVERITY) != null) { - double[] confLooserMetDevLimits = Utils.getParam(paramMap.get(LOOSER_METRIC_DEV_SEVERITY), - looserMetDevLimits.length); - if (confLooserMetDevLimits != null) { - looserMetDevLimits = confLooserMetDevLimits; - } + double[] confLooserMetDevLimits = Utils.getParam(paramMap.get(LOOSER_METRIC_DEV_SEVERITY), + looserMetDevLimits.length); + if (confLooserMetDevLimits != null) { + looserMetDevLimits = confLooserMetDevLimits; } - logger.info(HEURISTIC_NAME + " will use " + LOOSER_METRIC_DEV_SEVERITY + " with the following threshold settings: " + logger.info(heuristicName + " will use " + LOOSER_METRIC_DEV_SEVERITY + " with the following threshold settings: " + Arrays.toString(looserMetDevLimits)); - if(paramMap.get(METRIC_DEV_SEVERITY) != null) { - double[] confMetDevLimits = Utils.getParam(paramMap.get(METRIC_DEV_SEVERITY), metDevLimits.length); - if (confMetDevLimits != null) { - metDevLimits = confMetDevLimits; - } + double[] confMetDevLimits = Utils.getParam(paramMap.get(METRIC_DEV_SEVERITY), metDevLimits.length); + if (confMetDevLimits != null) { + metDevLimits = confMetDevLimits; } - logger.info(HEURISTIC_NAME + " will use " + METRIC_DEV_SEVERITY + " with the following threshold settings: " + logger.info(heuristicName + " will use " + METRIC_DEV_SEVERITY + " with the following threshold settings: " + Arrays.toString(metDevLimits)); } @@ -137,6 +133,11 @@ public double getDeviationFactor() { } } + @Override + public HeuristicConfigurationData getHeuristicConfData() { + return _heuristicConfData; + } + @Override public HeuristicResult apply(SparkApplicationData data) { SparkExecutorData executorData = data.getExecutorData(); @@ -175,22 +176,21 @@ public HeuristicResult apply(SparkApplicationData data) { Severity severity = Severity.max(getLooserMetricDeviationSeverity(peakMems), getMetricDeviationSeverity(durations), getMetricDeviationSeverity(inputBytes), getLooserMetricDeviationSeverity(outputBytes)); - HeuristicResult result = new HeuristicResult(getHeuristicName(), severity); + HeuristicResult result = new HeuristicResult(_heuristicConfData.getClassName(), + _heuristicConfData.getHeuristicName(), severity, 0); - result.addDetail("Average peak storage memory", String - .format("%s (%s~%s)", MemoryFormatUtils.bytesToString(peakMems.getAvg()), + result.addResultDetail("Average peak storage memory", + String.format("%s (%s~%s)", MemoryFormatUtils.bytesToString(peakMems.getAvg()), MemoryFormatUtils.bytesToString(peakMems.getMin()), MemoryFormatUtils.bytesToString(peakMems.getMax()))); - result.addDetail("Average runtime", String - .format("%s (%s~%s)", Statistics.readableTimespan(durations.getAvg()), + result.addResultDetail("Average runtime", + String.format("%s (%s~%s)", Statistics.readableTimespan(durations.getAvg()), Statistics.readableTimespan(durations.getMin()), Statistics.readableTimespan(durations.getMax()))); - result.addDetail("Average input size", String - .format("%s (%s~%s)", MemoryFormatUtils.bytesToString(inputBytes.getAvg()), - MemoryFormatUtils.bytesToString(inputBytes.getMin()), - MemoryFormatUtils.bytesToString(inputBytes.getMax()))); - result.addDetail("Average output size", String - .format("%s (%s~%s)", MemoryFormatUtils.bytesToString(outputBytes.getAvg()), - MemoryFormatUtils.bytesToString(outputBytes.getMin()), - MemoryFormatUtils.bytesToString(outputBytes.getMax()))); + result.addResultDetail("Average input size", + String.format("%s (%s~%s)", MemoryFormatUtils.bytesToString(inputBytes.getAvg()), + MemoryFormatUtils.bytesToString(inputBytes.getMin()), MemoryFormatUtils.bytesToString(inputBytes.getMax()))); + result.addResultDetail("Average output size", + String.format("%s (%s~%s)", MemoryFormatUtils.bytesToString(outputBytes.getAvg()), + MemoryFormatUtils.bytesToString(outputBytes.getMin()), MemoryFormatUtils.bytesToString(outputBytes.getMax()))); return result; } @@ -214,8 +214,4 @@ private Severity getMetricDeviationSeverity(ValueObserver ob) { diffFactor, metDevLimits[0], metDevLimits[1], metDevLimits[2], metDevLimits[3]); } - @Override - public String getHeuristicName() { - return HEURISTIC_NAME; - } } diff --git a/app/com/linkedin/drelephant/spark/heuristics/JobRuntimeHeuristic.java b/app/com/linkedin/drelephant/spark/heuristics/JobRuntimeHeuristic.java index 3b3520d0e..e88283dd7 100644 --- a/app/com/linkedin/drelephant/spark/heuristics/JobRuntimeHeuristic.java +++ b/app/com/linkedin/drelephant/spark/heuristics/JobRuntimeHeuristic.java @@ -38,7 +38,6 @@ */ public class JobRuntimeHeuristic implements Heuristic { private static final Logger logger = Logger.getLogger(JobRuntimeHeuristic.class); - public static final String HEURISTIC_NAME = "Spark Job Runtime"; // Severity parameters. private static final String AVG_JOB_FAILURE_SEVERITY = "avg_job_failure_rate_severity"; @@ -52,25 +51,22 @@ public class JobRuntimeHeuristic implements Heuristic { private void loadParameters() { Map paramMap = _heuristicConfData.getParamMap(); + String heuristicName = _heuristicConfData.getHeuristicName(); - if(paramMap.get(AVG_JOB_FAILURE_SEVERITY) != null) { - double[] confAvgJobFailureLimits = Utils.getParam(paramMap.get(AVG_JOB_FAILURE_SEVERITY), - avgJobFailureLimits.length); - if (confAvgJobFailureLimits != null) { - avgJobFailureLimits = confAvgJobFailureLimits; - } + double[] confAvgJobFailureLimits = Utils.getParam(paramMap.get(AVG_JOB_FAILURE_SEVERITY), + avgJobFailureLimits.length); + if (confAvgJobFailureLimits != null) { + avgJobFailureLimits = confAvgJobFailureLimits; } - logger.info(HEURISTIC_NAME + " will use " + AVG_JOB_FAILURE_SEVERITY + " with the following threshold settings: " + logger.info(heuristicName + " will use " + AVG_JOB_FAILURE_SEVERITY + " with the following threshold settings: " + Arrays.toString(avgJobFailureLimits)); - if(paramMap.get(SINGLE_JOB_FAILURE_SEVERITY) != null) { - double[] confJobFailureLimits = Utils.getParam(paramMap.get(SINGLE_JOB_FAILURE_SEVERITY), - jobFailureLimits.length); - if (confJobFailureLimits != null) { - jobFailureLimits = confJobFailureLimits; - } + double[] confJobFailureLimits = Utils.getParam(paramMap.get(SINGLE_JOB_FAILURE_SEVERITY), + jobFailureLimits.length); + if (confJobFailureLimits != null) { + jobFailureLimits = confJobFailureLimits; } - logger.info(HEURISTIC_NAME + " will use " + SINGLE_JOB_FAILURE_SEVERITY + " with the following threshold settings: " + logger.info(heuristicName + " will use " + SINGLE_JOB_FAILURE_SEVERITY + " with the following threshold settings: " + Arrays.toString(jobFailureLimits)); } @@ -79,6 +75,11 @@ public JobRuntimeHeuristic(HeuristicConfigurationData heuristicConfData) { loadParameters(); } + @Override + public HeuristicConfigurationData getHeuristicConfData() { + return _heuristicConfData; + } + @Override public HeuristicResult apply(SparkApplicationData data) { SparkJobProgressData jobProgressData = data.getJobProgressData(); @@ -106,22 +107,18 @@ public HeuristicResult apply(SparkApplicationData data) { endSeverity = Severity.max(endSeverity, severity); } - HeuristicResult result = new HeuristicResult(getHeuristicName(), endSeverity); + HeuristicResult result = new HeuristicResult(_heuristicConfData.getClassName(), + _heuristicConfData.getHeuristicName(), endSeverity, 0); - result.addDetail("Spark completed jobs number", String.valueOf(completedJobs.size())); - result.addDetail("Spark failed jobs number", String.valueOf(failedJobs.size())); - result.addDetail("Spark failed jobs list", getJobListString(jobProgressData.getFailedJobDescriptions())); - result.addDetail("Spark average job failure rate", String.format("%.3f", avgJobFailureRate)); - result.addDetail("Spark jobs with high task failure rate", getJobListString(highFailureRateJobs)); + result.addResultDetail("Spark completed jobs number", String.valueOf(completedJobs.size())); + result.addResultDetail("Spark failed jobs number", String.valueOf(failedJobs.size())); + result.addResultDetail("Spark failed jobs list", getJobListString(jobProgressData.getFailedJobDescriptions())); + result.addResultDetail("Spark average job failure rate", String.format("%.3f", avgJobFailureRate)); + result.addResultDetail("Spark jobs with high task failure rate", getJobListString(highFailureRateJobs)); return result; } - @Override - public String getHeuristicName() { - return HEURISTIC_NAME; - } - private Severity getAvgJobFailureRateSeverity(double rate) { return Severity.getSeverityAscending( rate, avgJobFailureLimits[0], avgJobFailureLimits[1], avgJobFailureLimits[2], avgJobFailureLimits[3]); diff --git a/app/com/linkedin/drelephant/spark/heuristics/MemoryLimitHeuristic.java b/app/com/linkedin/drelephant/spark/heuristics/MemoryLimitHeuristic.java index 08690768a..6224b787a 100644 --- a/app/com/linkedin/drelephant/spark/heuristics/MemoryLimitHeuristic.java +++ b/app/com/linkedin/drelephant/spark/heuristics/MemoryLimitHeuristic.java @@ -37,7 +37,6 @@ * This heuristic checks for memory consumption. */ public class MemoryLimitHeuristic implements Heuristic { - public static final String HEURISTIC_NAME = "Spark Memory Limit"; private static final Logger logger = Logger.getLogger(MemoryLimitHeuristic.class); public static final String SPARK_EXECUTOR_MEMORY = "spark.executor.memory"; @@ -59,6 +58,7 @@ public class MemoryLimitHeuristic implements Heuristic { private void loadParameters() { Map paramMap = _heuristicConfData.getParamMap(); + String heuristicName = _heuristicConfData.getHeuristicName(); if(paramMap.get(MEM_UTILIZATION_SEVERITY) != null) { double[] confMemUtilLimits = Utils.getParam(paramMap.get(MEM_UTILIZATION_SEVERITY), memUtilLimits.length); @@ -66,7 +66,7 @@ private void loadParameters() { memUtilLimits = confMemUtilLimits; } } - logger.info(HEURISTIC_NAME + " will use " + MEM_UTILIZATION_SEVERITY + " with the following threshold settings: " + logger.info(heuristicName + " will use " + MEM_UTILIZATION_SEVERITY + " with the following threshold settings: " + Arrays.toString(memUtilLimits)); if(paramMap.get(TOTAL_MEM_SEVERITY) != null) { @@ -75,7 +75,7 @@ private void loadParameters() { totalMemLimits = confTotalMemLimits; } } - logger.info(HEURISTIC_NAME + " will use " + TOTAL_MEM_SEVERITY + " with the following threshold settings: " + logger.info(heuristicName + " will use " + TOTAL_MEM_SEVERITY + " with the following threshold settings: " + Arrays.toString(totalMemLimits)); for (int i = 0; i < totalMemLimits.length; i++) { totalMemLimits[i] = MemoryFormatUtils.stringToBytes(totalMemLimits[i] + "T"); @@ -87,6 +87,11 @@ public MemoryLimitHeuristic(HeuristicConfigurationData heuristicConfData) { loadParameters(); } + @Override + public HeuristicConfigurationData getHeuristicConfData() { + return _heuristicConfData; + } + @Override public HeuristicResult apply(SparkApplicationData data) { @@ -104,15 +109,16 @@ public HeuristicResult apply(SparkApplicationData data) { Severity memoryUtilizationServerity = getMemoryUtilizationSeverity(peakMem, totalStorageMem); HeuristicResult result = - new HeuristicResult(getHeuristicName(), Severity.max(totalMemorySeverity, memoryUtilizationServerity)); + new HeuristicResult(_heuristicConfData.getClassName(), _heuristicConfData.getHeuristicName(), + Severity.max(totalMemorySeverity, memoryUtilizationServerity), 0); - result.addDetail("Total executor memory allocated", String + result.addResultDetail("Total executor memory allocated", String .format("%s (%s x %s)", MemoryFormatUtils.bytesToString(totalExecutorMem), MemoryFormatUtils.bytesToString(perExecutorMem), executorNum)); - result.addDetail("Total driver memory allocated", MemoryFormatUtils.bytesToString(totalDriverMem)); - result.addDetail("Total memory allocated for storage", MemoryFormatUtils.bytesToString(totalStorageMem)); - result.addDetail("Total memory used at peak", MemoryFormatUtils.bytesToString(peakMem)); - result.addDetail("Memory utilization rate", String.format("%1.3f", peakMem * 1.0 / totalStorageMem)); + result.addResultDetail("Total driver memory allocated", MemoryFormatUtils.bytesToString(totalDriverMem)); + result.addResultDetail("Total memory allocated for storage", MemoryFormatUtils.bytesToString(totalStorageMem)); + result.addResultDetail("Total memory used at peak", MemoryFormatUtils.bytesToString(peakMem)); + result.addResultDetail("Memory utilization rate", String.format("%1.3f", peakMem * 1.0 / totalStorageMem)); return result; } @@ -184,11 +190,6 @@ private static long getTotalStorageMem(SparkApplicationData data) { return totalStorageMem; } - @Override - public String getHeuristicName() { - return HEURISTIC_NAME; - } - public Severity getTotalMemorySeverity(long memory) { return Severity.getSeverityAscending(memory, totalMemLimits[0], totalMemLimits[1], totalMemLimits[2], totalMemLimits[3]); diff --git a/app/com/linkedin/drelephant/spark/heuristics/StageRuntimeHeuristic.java b/app/com/linkedin/drelephant/spark/heuristics/StageRuntimeHeuristic.java index 7064b946d..a7197bdd4 100644 --- a/app/com/linkedin/drelephant/spark/heuristics/StageRuntimeHeuristic.java +++ b/app/com/linkedin/drelephant/spark/heuristics/StageRuntimeHeuristic.java @@ -40,7 +40,6 @@ */ public class StageRuntimeHeuristic implements Heuristic { private static final Logger logger = Logger.getLogger(StageRuntimeHeuristic.class); - public static final String HEURISTIC_NAME = "Spark Stage Runtime"; // Severity parameters private static final String STAGE_FAILURE_SEVERITY = "stage_failure_rate_severity"; @@ -56,34 +55,29 @@ public class StageRuntimeHeuristic implements Heuristic { private void loadParameters() { Map paramMap = _heuristicConfData.getParamMap(); + String heuristicName = _heuristicConfData.getHeuristicName(); - if(paramMap.get(STAGE_FAILURE_SEVERITY) != null) { - double[] confStageFailRateLimits = Utils.getParam(paramMap.get(STAGE_FAILURE_SEVERITY), - stageFailRateLimits.length); - if (confStageFailRateLimits != null) { - stageFailRateLimits = confStageFailRateLimits; - } + double[] confStageFailRateLimits = Utils.getParam(paramMap.get(STAGE_FAILURE_SEVERITY), + stageFailRateLimits.length); + if (confStageFailRateLimits != null) { + stageFailRateLimits = confStageFailRateLimits; } - logger.info(HEURISTIC_NAME + " will use " + STAGE_FAILURE_SEVERITY + " with the following threshold settings: " + logger.info(heuristicName + " will use " + STAGE_FAILURE_SEVERITY + " with the following threshold settings: " + Arrays.toString(stageFailRateLimits)); - if(paramMap.get(SINGLE_STAGE_FAILURE_SEVERITY) != null) { - double[] confSingleFailLimits = Utils.getParam(paramMap.get(SINGLE_STAGE_FAILURE_SEVERITY), - singleStageFailLimits.length); - if (confSingleFailLimits != null) { - singleStageFailLimits = confSingleFailLimits; - } + double[] confSingleFailLimits = Utils.getParam(paramMap.get(SINGLE_STAGE_FAILURE_SEVERITY), + singleStageFailLimits.length); + if (confSingleFailLimits != null) { + singleStageFailLimits = confSingleFailLimits; } - logger.info(HEURISTIC_NAME + " will use " + SINGLE_STAGE_FAILURE_SEVERITY + " with the following threshold" + logger.info(heuristicName + " will use " + SINGLE_STAGE_FAILURE_SEVERITY + " with the following threshold" + " settings: " + Arrays.toString(singleStageFailLimits)); - if(paramMap.get(STAGE_RUNTIME_SEVERITY) != null) { - double[] confStageRuntimeLimits = Utils.getParam(paramMap.get(STAGE_RUNTIME_SEVERITY), stageRuntimeLimits.length); - if (confStageRuntimeLimits != null) { - stageRuntimeLimits = confStageRuntimeLimits; - } + double[] confStageRuntimeLimits = Utils.getParam(paramMap.get(STAGE_RUNTIME_SEVERITY), stageRuntimeLimits.length); + if (confStageRuntimeLimits != null) { + stageRuntimeLimits = confStageRuntimeLimits; } - logger.info(HEURISTIC_NAME + " will use " + STAGE_RUNTIME_SEVERITY + " with the following threshold settings: " + logger.info(heuristicName + " will use " + STAGE_RUNTIME_SEVERITY + " with the following threshold settings: " + Arrays.toString(stageRuntimeLimits)); for (int i = 0; i < stageRuntimeLimits.length; i++) { stageRuntimeLimits[i] = stageRuntimeLimits[i] * Statistics.MINUTE_IN_MS; @@ -95,6 +89,11 @@ public StageRuntimeHeuristic(HeuristicConfigurationData heuristicConfData) { loadParameters(); } + @Override + public HeuristicConfigurationData getHeuristicConfData() { + return _heuristicConfData; + } + @Override public HeuristicResult apply(SparkApplicationData data) { SparkJobProgressData jobProgressData = data.getJobProgressData(); @@ -129,21 +128,17 @@ public HeuristicResult apply(SparkApplicationData data) { endSeverity = Severity.max(endSeverity, tasksFailureRateSeverity, runtimeSeverity); } - HeuristicResult result = new HeuristicResult(getHeuristicName(), endSeverity); + HeuristicResult result = new HeuristicResult(_heuristicConfData.getClassName(), + _heuristicConfData.getHeuristicName(), endSeverity, 0); - result.addDetail("Spark stage completed", String.valueOf(completedStages.size())); - result.addDetail("Spark stage failed", String.valueOf(failedStages.size())); - result.addDetail("Spark average stage failure rate", String.format("%.3f", avgStageFailureRate)); - result.addDetail("Spark problematic stages:", getStageListString(problematicStages)); + result.addResultDetail("Spark stage completed", String.valueOf(completedStages.size())); + result.addResultDetail("Spark stage failed", String.valueOf(failedStages.size())); + result.addResultDetail("Spark average stage failure rate", String.format("%.3f", avgStageFailureRate)); + result.addResultDetail("Spark problematic stages:", getStageListString(problematicStages)); return result; } - @Override - public String getHeuristicName() { - return HEURISTIC_NAME; - } - private Severity getStageRuntimeSeverity(long runtime) { return Severity.getSeverityDescending( runtime, stageRuntimeLimits[0], stageRuntimeLimits[1], stageRuntimeLimits[2], stageRuntimeLimits[3]); diff --git a/app/com/linkedin/drelephant/util/InfoExtractor.java b/app/com/linkedin/drelephant/util/InfoExtractor.java index fd3e37a34..b824f7161 100644 --- a/app/com/linkedin/drelephant/util/InfoExtractor.java +++ b/app/com/linkedin/drelephant/util/InfoExtractor.java @@ -26,7 +26,7 @@ import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; -import model.JobResult; +import models.AppResult; import com.linkedin.drelephant.mapreduce.data.MapReduceApplicationData; @@ -43,9 +43,10 @@ public class InfoExtractor { private static final String AZKABAN_JOB_URL = "azkaban.link.job.url"; private static final String AZKABAN_EXECUTION_URL = "azkaban.link.execution.url"; private static final String AZKABAN_ATTEMPT_URL = "azkaban.link.attempt.url"; + private static final String AZKABAN_JOB_NAME = "azkaban.job.id"; // TODO: this utils class is not ideal, probably should merge retrieve URLs logics directly into the data interface? - public static void retrieveURLs(JobResult result, HadoopApplicationData data) { + public static void retrieveURLs(AppResult result, HadoopApplicationData data) { if (data instanceof MapReduceApplicationData) { retrieveURLs(result, (MapReduceApplicationData) data); } else if (data instanceof SparkApplicationData) { @@ -53,19 +54,42 @@ public static void retrieveURLs(JobResult result, HadoopApplicationData data) { } } - public static void retrieveURLs(JobResult result, MapReduceApplicationData appData) { + public static void retrieveURLs(AppResult result, MapReduceApplicationData appData) { Properties jobConf = appData.getConf(); String jobId = appData.getJobId(); - result.jobExecUrl = truncate(jobConf.getProperty(AZKABAN_ATTEMPT_URL), jobId); - // For jobs launched by Azkaban, we consider different attempts to be - // different jobs - result.jobUrl = truncate(jobConf.getProperty(AZKABAN_JOB_URL), jobId); - result.flowExecUrl = truncate(jobConf.getProperty(AZKABAN_EXECUTION_URL), jobId); - result.flowUrl = truncate(jobConf.getProperty(AZKABAN_WORKFLOW_URL), jobId); + + result.jobExecId = jobConf.getProperty(AZKABAN_ATTEMPT_URL) != null ? + truncate(jobConf.getProperty(AZKABAN_ATTEMPT_URL), jobId) : ""; + // For jobs launched by Azkaban, we consider different attempts to be different jobs + result.jobDefId = jobConf.getProperty(AZKABAN_JOB_URL) != null ? + truncate(jobConf.getProperty(AZKABAN_JOB_URL), jobId) : ""; + result.flowExecId = jobConf.getProperty(AZKABAN_EXECUTION_URL) != null ? + truncate(jobConf.getProperty(AZKABAN_EXECUTION_URL), jobId) : ""; + result.flowDefId = jobConf.getProperty(AZKABAN_WORKFLOW_URL) != null ? + truncate(jobConf.getProperty(AZKABAN_WORKFLOW_URL), jobId) : ""; + + // For Azkaban, The url and ids are the same + result.jobExecUrl = result.jobExecId; + result.jobDefUrl = result.jobDefId; + result.flowExecUrl = result.flowExecId; + result.flowDefUrl = result.flowDefId; + + if (!result.jobExecId.isEmpty()) { + result.scheduler = "azkaban"; + result.workflowDepth = 0; + } + result.jobName = jobConf.getProperty(AZKABAN_JOB_NAME) != null ? jobConf.getProperty(AZKABAN_JOB_NAME) : ""; + + // Truncate long job names + if (result.jobName.length() > 255) { + result.jobName = result.jobName.substring(0, 252) + "..."; + } } - public static void retrieveURLs(JobResult result, SparkApplicationData appData) { + public static void retrieveURLs(AppResult result, SparkApplicationData appData) { String prop = appData.getEnvironmentData().getSparkProperty(SPARK_EXTRA_JAVA_OPTIONS); + String appId = appData.getAppId(); + if (prop != null) { try { Map options = Utils.parseJavaOptions(prop); @@ -76,10 +100,31 @@ public static void retrieveURLs(JobResult result, SparkApplicationData appData) } logger.info("Parsed options:" + StringUtils.join(s, ",")); - result.jobExecUrl = unescapeString(options.get(AZKABAN_ATTEMPT_URL)); - result.jobUrl = unescapeString(options.get(AZKABAN_JOB_URL)); - result.flowExecUrl = unescapeString(options.get(AZKABAN_EXECUTION_URL)); - result.flowUrl = unescapeString(options.get(AZKABAN_WORKFLOW_URL)); + result.jobExecId = options.get(AZKABAN_ATTEMPT_URL) != null ? + truncate(unescapeString(options.get(AZKABAN_ATTEMPT_URL)), appId) : ""; + result.jobDefId = options.get(AZKABAN_JOB_URL) != null ? + truncate(unescapeString(options.get(AZKABAN_JOB_URL)), appId) : ""; + result.flowExecId = options.get(AZKABAN_EXECUTION_URL) != null ? + truncate(unescapeString(options.get(AZKABAN_EXECUTION_URL)), appId) : ""; + result.flowDefId = options.get(AZKABAN_WORKFLOW_URL) != null ? + truncate(unescapeString(options.get(AZKABAN_WORKFLOW_URL)), appId) : ""; + + result.jobExecUrl = result.jobExecId; + result.jobDefUrl = result.jobDefId; + result.flowExecUrl = result.flowExecId; + result.flowDefUrl = result.flowDefId; + + if (!result.jobExecId.isEmpty()) { + result.scheduler = "azkaban"; + result.workflowDepth = 0; + } + result.jobName = options.get(AZKABAN_JOB_NAME) != null ? unescapeString(options.get(AZKABAN_JOB_NAME)) : ""; + + // Truncate long job names + if (result.jobName.length() > 255) { + result.jobName = result.jobName.substring(0, 252) + "..."; + } + } catch (IllegalArgumentException e) { logger.error("Encountered error while parsing java options into urls: " + e.getMessage()); } @@ -105,9 +150,9 @@ private static String unescapeString(String s) { } public static String truncate(String value, String jobId) { - if (value != null && value.length() > JobResult.URL_LEN_LIMIT) { + if (value != null && value.length() > AppResult.URL_LEN_LIMIT) { logger.info("Truncate long URL in job result for job: " + jobId + ". Original Url: " + value); - value = value.substring(0, JobResult.URL_LEN_LIMIT); + value = value.substring(0, AppResult.URL_LEN_LIMIT); } return value; } diff --git a/app/com/linkedin/drelephant/util/Utils.java b/app/com/linkedin/drelephant/util/Utils.java index 1ccbf1af4..d2066c9f1 100644 --- a/app/com/linkedin/drelephant/util/Utils.java +++ b/app/com/linkedin/drelephant/util/Utils.java @@ -16,13 +16,11 @@ package com.linkedin.drelephant.util; +import com.linkedin.drelephant.analysis.Severity; import java.io.IOException; import java.io.InputStream; -import java.util.ArrayList; import java.util.HashMap; -import java.util.List; import java.util.Map; -import java.util.regex.Pattern; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; @@ -40,8 +38,6 @@ */ public final class Utils { private static final Logger logger = Logger.getLogger(Utils.class); - // Matching x.x.x or x.x.x-li1 (x are numbers) - public static final Pattern VERSION_PATTERN = Pattern.compile("(\\d+)(?:\\.\\d+)*(?:\\-[\\dA-Za-z]+)?"); private Utils() { // do nothing @@ -50,7 +46,7 @@ private Utils() { /** * Given a mapreduce job's application id, get its corresponding job id * - * Note: before adding Spark analysers, all JobResult were using job ids as the primary key. But Spark and many + * Note: before adding Spark analysers, all AppResult were using job ids as the primary key. But Spark and many * other non-mapreduce applications do not have a job id. To maintain backwards compatibility, we replace * 'application' with 'job' to form a pseudo job id. * @@ -122,71 +118,6 @@ public static Map parseJavaOptions(String str) { return options; } - public static String combineCsvLines(String[] lines) { - StringBuilder sb = new StringBuilder(); - for (String line : lines) { - sb.append(line).append("\n"); - } - return sb.toString().trim(); - } - - public static String createCsvLine(String... parts) { - StringBuilder sb = new StringBuilder(); - String quotes = "\""; - String comma = ","; - for (int i = 0; i < parts.length; i++) { - sb.append(quotes).append(parts[i].replaceAll(quotes, quotes + quotes)).append(quotes); - if (i != parts.length - 1) { - sb.append(comma); - } - } - return sb.toString(); - } - - public static String[][] parseCsvLines(String data) { - if (data.isEmpty()) { - return new String[0][]; - } - String[] lines = data.split("\n"); - String[][] result = new String[lines.length][]; - for (int i = 0; i < lines.length; i++) { - result[i] = parseCsvLine(lines[i]); - } - return result; - } - - public static String[] parseCsvLine(String line) { - List store = new ArrayList(); - StringBuilder curVal = new StringBuilder(); - boolean inquotes = false; - for (int i = 0; i < line.length(); i++) { - char ch = line.charAt(i); - if (inquotes) { - if (ch == '\"') { - inquotes = false; - } else { - curVal.append(ch); - } - } else { - if (ch == '\"') { - inquotes = true; - if (curVal.length() > 0) { - //if this is the second quote in a value, add a quote - //this is for the double quote in the middle of a value - curVal.append('\"'); - } - } else if (ch == ',') { - store.add(curVal.toString()); - curVal = new StringBuilder(); - } else { - curVal.append(ch); - } - } - } - store.add(curVal.toString()); - return store.toArray(new String[store.size()]); - } - /** * Returns the configured thresholds after evaluating and verifying the levels. * @@ -195,15 +126,16 @@ public static String[] parseCsvLine(String line) { * @return The evaluated threshold limits */ public static double[] getParam(String rawLimits, int thresholdLevels) { - double[] parsedLimits = new double[thresholdLevels]; + double[] parsedLimits = null; - if (rawLimits != null) { + if (rawLimits != null && !rawLimits.isEmpty()) { String[] thresholds = rawLimits.split(","); if (thresholds.length != thresholdLevels) { logger.error("Could not find " + thresholdLevels + " threshold levels in " + rawLimits); parsedLimits = null; } else { // Evaluate the limits + parsedLimits = new double[thresholdLevels]; ScriptEngineManager mgr = new ScriptEngineManager(); ScriptEngine engine = mgr.getEngineByName("JavaScript"); for (int i = 0; i < thresholdLevels; i++) { @@ -220,4 +152,47 @@ public static double[] getParam(String rawLimits, int thresholdLevels) { return parsedLimits; } + /** + * Combine the parts into a comma separated String + * + * Example: + * input: part1 = "foo" and part2 = "bar" + * output = "foo,bar" + * + * @param parts The parts to combine + * @return The comma separated string + */ + public static String commaSeparated(String... parts) { + StringBuilder sb = new StringBuilder(); + String comma = ","; + if (parts.length != 0) { + sb.append(parts[0]); + } + for (int i = 1; i < parts.length; i++) { + if (parts[i] != null && !parts[i].isEmpty()) { + sb.append(comma); + sb.append(parts[i]); + } + } + return sb.toString(); + } + + /** + * Compute the score for the heuristic based on the number of tasks and severity. + * This is applicable only to mapreduce applications. + * + * Score = severity * num of tasks (where severity NOT in [NONE, LOW]) + * + * @param severity The heuristic severity + * @param tasks The number of tasks (map/reduce) + * @return + */ + public static int getHeuristicScore(Severity severity, int tasks) { + int score = 0; + if (severity != Severity.NONE && severity != Severity.LOW) { + score = severity.getValue() * tasks; + } + return score; + } + } diff --git a/app/controllers/Application.java b/app/controllers/Application.java index 70b653b01..0990c5d9c 100644 --- a/app/controllers/Application.java +++ b/app/controllers/Application.java @@ -17,19 +17,19 @@ package controllers; import com.avaje.ebean.ExpressionList; -import com.avaje.ebean.RawSql; -import com.avaje.ebean.RawSqlBuilder; +import com.fasterxml.jackson.core.JsonGenerationException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.linkedin.drelephant.ElephantContext; import com.linkedin.drelephant.analysis.Severity; import com.linkedin.drelephant.configurations.heuristic.HeuristicConfigurationData; +import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.text.ParseException; -import java.text.SimpleDateFormat; import java.util.ArrayList; -import java.util.Calendar; +import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.LinkedHashMap; @@ -37,9 +37,9 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.TimeZone; -import model.JobHeuristicResult; -import model.JobResult; +import java.util.TreeSet; +import models.AppHeuristicResult; +import models.AppResult; import org.apache.http.client.utils.URLEncodedUtils; import org.apache.http.message.BasicNameValuePair; import org.apache.log4j.Logger; @@ -78,11 +78,11 @@ public class Application extends Controller { private static final int MAX_HISTORY_LIMIT = 15; // Upper limit on the number of executions to display // Form and Rest parameters - private static final String JOB_ID = "id"; - private static final String FLOW_URL = "flow-url"; - private static final String FLOW_EXEC_URL = "flow-exec-url"; - private static final String JOB_URL = "job-url"; - private static final String USER = "user"; + private static final String APP_ID = "id"; + private static final String FLOW_DEF_ID = "flow-def-id"; + private static final String FLOW_EXEC_ID = "flow-exec-id"; + private static final String JOB_DEF_ID = "job-def-id"; + private static final String USERNAME = "username"; private static final String SEVERITY = "severity"; private static final String JOB_TYPE = "job-type"; private static final String ANALYSIS = "analysis"; @@ -90,14 +90,10 @@ public class Application extends Controller { private static final String STARTED_TIME_END = "started-time-end"; private static final String FINISHED_TIME_BEGIN = "finished-time-begin"; private static final String FINISHED_TIME_END = "finished-time-end"; - private static final String COMPARE_FLOW_URL1 = "flow-exec-url1"; - private static final String COMPARE_FLOW_URL2 = "flow-exec-url2"; + private static final String COMPARE_FLOW_ID1 = "flow-exec-id1"; + private static final String COMPARE_FLOW_ID2 = "flow-exec-id2"; private static final String PAGE = "page"; - // Time range specifier. [TIME_RANGE_BEGIN, TIME_RANGE_END] - private static final boolean TIME_RANGE_BEGIN = false; - private static final boolean TIME_RANGE_END = true; - private static long _lastFetch = 0; private static int _numJobsAnalyzed = 0; private static int _numJobsCritical = 0; @@ -115,23 +111,39 @@ public class Application extends Controller { } /** - * Controls the Home page of Dr. Elephant + * Controls the Home page of Dr. Elephant. + * + * Displays the latest jobs which were analysed in the last 24 hours. */ public static Result dashboard() { long now = System.currentTimeMillis(); + Date finishDate = new Date(now - DAY); + + // Update statistics only after FETCH_DELAY if (now - _lastFetch > FETCH_DELAY) { - _numJobsAnalyzed = JobResult.find.where().gt(JobResult.TABLE.ANALYSIS_TIME, now - DAY).findRowCount(); - _numJobsCritical = - JobResult.find.where().gt(JobResult.TABLE.ANALYSIS_TIME, now - DAY) - .eq(JobResult.TABLE.SEVERITY, Severity.CRITICAL.getValue()).findRowCount(); - _numJobsSevere = - JobResult.find.where().gt(JobResult.TABLE.ANALYSIS_TIME, now - DAY) - .eq(JobResult.TABLE.SEVERITY, Severity.SEVERE.getValue()).findRowCount(); + _numJobsAnalyzed = AppResult.find.where() + .gt(AppResult.TABLE.FINISH_TIME, finishDate) + .findRowCount(); + _numJobsCritical = AppResult.find.where() + .gt(AppResult.TABLE.FINISH_TIME, finishDate) + .eq(AppResult.TABLE.SEVERITY, Severity.CRITICAL.getValue()) + .findRowCount(); + _numJobsSevere = AppResult.find.where() + .gt(AppResult.TABLE.FINISH_TIME, finishDate) + .eq(AppResult.TABLE.SEVERITY, Severity.SEVERE.getValue()) + .findRowCount(); _lastFetch = now; } - List results = - JobResult.find.where().gt(JobResult.TABLE.ANALYSIS_TIME, now - DAY).order().desc(JobResult.TABLE.ANALYSIS_TIME) - .setMaxRows(50).fetch("heuristicResults").findList(); + + // Fetch only required fields for jobs analysed in the last 24 hours up to a max of 50 jobs + List results = AppResult.find + .select(AppResult.getSearchFields()) + .where() + .gt(AppResult.TABLE.FINISH_TIME, finishDate) + .order().desc(AppResult.TABLE.FINISH_TIME) + .setMaxRows(50) + .fetch(AppResult.TABLE.APP_HEURISTIC_RESULTS, AppHeuristicResult.getSearchFields()) + .findList(); return ok(homePage.render(_numJobsAnalyzed, _numJobsSevere, _numJobsCritical, searchResults.render("Latest analysis", results))); @@ -142,26 +154,38 @@ public static Result dashboard() { */ public static Result search() { DynamicForm form = Form.form().bindFromRequest(request()); - String jobId = form.get(JOB_ID); - jobId = jobId != null ? jobId.trim() : ""; - String flowUrl = form.get(FLOW_URL); - flowUrl = (flowUrl != null) ? flowUrl.trim() : null; - - // Search and display job information when job id or flow execution url is provided. - if (!jobId.isEmpty()) { - JobResult result = JobResult.find.byId(jobId); + String appId = form.get(APP_ID); + appId = appId != null ? appId.trim() : ""; + if (appId.contains("job")) { + appId = appId.replaceAll("job", "application"); + } + String flowExecId = form.get(FLOW_EXEC_ID); + flowExecId = (flowExecId != null) ? flowExecId.trim() : null; + + // Search and display job details when job id or flow execution url is provided. + if (!appId.isEmpty()) { + AppResult result = AppResult.find.select("*") + .fetch(AppResult.TABLE.APP_HEURISTIC_RESULTS, "*") + .fetch(AppResult.TABLE.APP_HEURISTIC_RESULTS + "." + + AppHeuristicResult.TABLE.APP_HEURISTIC_RESULT_DETAILS, "*") + .where() + .idEq(appId).findUnique(); if (result != null) { return ok(searchPage.render(null, jobDetails.render(result))); } else { return ok(searchPage.render(null, jobDetails.render(null))); } - } else if (flowUrl != null && !flowUrl.isEmpty()) { - List results = JobResult.find.where().eq(JobResult.TABLE.FLOW_EXEC_URL, flowUrl).findList(); - Map> map = groupJobs(results, GroupBy.JOB_EXECUTION_URL); - return ok(searchPage.render(null, flowDetails.render(flowUrl, map))); + } else if (flowExecId != null && !flowExecId.isEmpty()) { + List results = AppResult.find + .select(AppResult.getSearchFields() + "," + AppResult.TABLE.JOB_EXEC_ID) + .fetch(AppResult.TABLE.APP_HEURISTIC_RESULTS, AppHeuristicResult.getSearchFields()) + .where().eq(AppResult.TABLE.FLOW_EXEC_ID, flowExecId) + .findList(); + Map> map = groupJobs(results, GroupBy.JOB_EXECUTION_ID); + return ok(searchPage.render(null, flowDetails.render(flowExecId, map))); } - // Paginate the results + // Prepare pagination of results PaginationStats paginationStats = new PaginationStats(PAGE_LENGTH, PAGE_BAR_LENGTH); int pageLength = paginationStats.getPageLength(); paginationStats.setCurrentPage(1); @@ -178,20 +202,22 @@ public static Result search() { int paginationBarStartIndex = paginationStats.getPaginationBarStartIndex(); // Filter jobs by search parameters - ExpressionList query = generateQuery(); - List results = - query.order().desc(JobResult.TABLE.ANALYSIS_TIME).setFirstRow((paginationBarStartIndex - 1) * pageLength) - .setMaxRows((paginationStats.getPageBarLength() - 1) * pageLength + 1).findList(); + ExpressionList searchQuery = AppResult.find.select(AppResult.getSearchFields()).where(); + ExpressionList query = generateSearchQuery(searchQuery); + List results = query + .order().desc(AppResult.TABLE.FINISH_TIME) + .setFirstRow((paginationBarStartIndex - 1) * pageLength) + .setMaxRows((paginationStats.getPageBarLength() - 1) * pageLength + 1) + .fetch(AppResult.TABLE.APP_HEURISTIC_RESULTS, AppHeuristicResult.getSearchFields()) + .findList(); paginationStats.setQueryString(getQueryString()); if (results.isEmpty() || currentPage > paginationStats.computePaginationBarEndIndex(results.size())) { return ok(searchPage.render(null, jobDetails.render(null))); } else { - return ok(searchPage.render( - paginationStats, - searchResults.render( - "Results", - results.subList((currentPage - paginationBarStartIndex) * pageLength, - Math.min(results.size(), (currentPage - paginationBarStartIndex + 1) * pageLength))))); + return ok(searchPage.render(paginationStats, + searchResults.render("Results", + results.subList((currentPage - paginationBarStartIndex) * pageLength, Math.min(results.size(), + (currentPage - paginationBarStartIndex + 1) * pageLength))))); } } @@ -218,13 +244,13 @@ private static String getQueryString() { } /** - * Build SQL expression + * Build SQL predicates for Search Query * - * @return An sql expression on Job Result + * @return An sql expression on App Result */ - private static ExpressionList generateQuery() { + private static ExpressionList generateSearchQuery(ExpressionList query) { DynamicForm form = Form.form().bindFromRequest(request()); - String username = form.get(USER); + String username = form.get(USERNAME); username = username != null ? username.trim().toLowerCase() : null; String severity = form.get(SEVERITY); String jobType = form.get(JOB_TYPE); @@ -234,37 +260,19 @@ private static ExpressionList generateQuery() { String startedTimeBegin = form.get(STARTED_TIME_BEGIN); String startedTimeEnd = form.get(STARTED_TIME_END); - ExpressionList query = JobResult.find.where(); - - RawSql rawsql = null; - // Hint usage of username index to mysql whenever our query contains a predicate on username - if (isSet(severity) && isSet(analysis)) { - if (isSet(username)) { - rawsql = RawSqlBuilder.parse(QueryHandler.getSqlJoinQueryWithUsernameIndex().toString()).create(); - } else { - rawsql = RawSqlBuilder.parse(QueryHandler.getSqlJoinQuery().toString()).create(); - } - } else { - if (isSet(username)) { - rawsql = RawSqlBuilder.parse(QueryHandler.getJobResultQueryWithUsernameIndex().toString()).create(); - } - } - query = query.query().setRawSql(rawsql).where(); - // Build predicates if (isSet(username)) { - query = query.like(JobResult.TABLE.USERNAME, username); + query = query.eq(AppResult.TABLE.USERNAME, username); } if (isSet(jobType)) { - query = query.eq(JobResult.TABLE.JOB_TYPE, jobType); + query = query.eq(AppResult.TABLE.JOB_TYPE, jobType); } if (isSet(severity)) { if (isSet(analysis)) { - query = - query.eq(JobHeuristicResult.TABLE.TABLE_NAME + "." + JobHeuristicResult.TABLE.ANALYSIS_NAME, analysis).ge( - JobHeuristicResult.TABLE.TABLE_NAME + "." + JobHeuristicResult.TABLE.SEVERITY, severity); + query = query.eq(AppResult.TABLE.APP_HEURISTIC_RESULTS + "." + AppHeuristicResult.TABLE.HEURISTIC_NAME, analysis) + .ge(AppResult.TABLE.APP_HEURISTIC_RESULTS + "." + AppHeuristicResult.TABLE.SEVERITY, severity); } else { - query = query.ge(JobResult.TABLE.SEVERITY, severity); + query = query.ge(AppResult.TABLE.SEVERITY, severity); } } @@ -272,25 +280,25 @@ private static ExpressionList generateQuery() { if (isSet(startedTimeBegin)) { long time = parseTime(startedTimeBegin); if (time > 0) { - query = query.ge(JobResult.TABLE.ANALYSIS_TIME, time); + query = query.ge(AppResult.TABLE.FINISH_TIME, new Date(time)); } } if (isSet(startedTimeEnd)) { long time = parseTime(startedTimeEnd); if (time > 0) { - query = query.le(JobResult.TABLE.ANALYSIS_TIME, time); + query = query.le(AppResult.TABLE.FINISH_TIME, new Date(time)); } } if (isSet(finishedTimeBegin)) { long time = parseTime(finishedTimeBegin); if (time > 0) { - query = query.ge(JobResult.TABLE.ANALYSIS_TIME, time); + query = query.ge(AppResult.TABLE.FINISH_TIME, new Date(time)); } } if (isSet(finishedTimeEnd)) { long time = parseTime(finishedTimeEnd); if (time > 0) { - query = query.le(JobResult.TABLE.ANALYSIS_TIME, time); + query = query.le(AppResult.TABLE.FINISH_TIME, new Date(time)); } } @@ -302,41 +310,74 @@ private static ExpressionList generateQuery() { */ public static Result compare() { DynamicForm form = Form.form().bindFromRequest(request()); - String flowExecUrl1 = form.get(COMPARE_FLOW_URL1); - flowExecUrl1 = (flowExecUrl1 != null) ? flowExecUrl1.trim() : null; - String flowExecUrl2 = form.get(COMPARE_FLOW_URL2); - flowExecUrl2 = (flowExecUrl2 != null) ? flowExecUrl2.trim() : null; - return ok(comparePage.render(compareResults.render(compareFlows(flowExecUrl1, flowExecUrl2)))); + String flowExecId1 = form.get(COMPARE_FLOW_ID1); + flowExecId1 = (flowExecId1 != null) ? flowExecId1.trim() : null; + String flowExecId2 = form.get(COMPARE_FLOW_ID2); + flowExecId2 = (flowExecId2 != null) ? flowExecId2.trim() : null; + + List results1 = null; + List results2 = null; + if (flowExecId1 != null && !flowExecId1.isEmpty() && flowExecId2 != null && !flowExecId2.isEmpty()) { + results1 = AppResult.find + .select(AppResult.getSearchFields() + "," + AppResult.TABLE.JOB_DEF_ID + "," + AppResult.TABLE.JOB_DEF_URL + + "," + AppResult.TABLE.FLOW_EXEC_ID + "," + AppResult.TABLE.FLOW_EXEC_URL) + .where().eq(AppResult.TABLE.FLOW_EXEC_ID, flowExecId1).setMaxRows(100) + .fetch(AppResult.TABLE.APP_HEURISTIC_RESULTS, AppHeuristicResult.getSearchFields()) + .findList(); + results2 = AppResult.find + .select( + AppResult.getSearchFields() + "," + AppResult.TABLE.JOB_DEF_ID + "," + AppResult.TABLE.JOB_DEF_URL + "," + + AppResult.TABLE.FLOW_EXEC_ID + "," + AppResult.TABLE.FLOW_EXEC_URL) + .where().eq(AppResult.TABLE.FLOW_EXEC_ID, flowExecId2).setMaxRows(100) + .fetch(AppResult.TABLE.APP_HEURISTIC_RESULTS, AppHeuristicResult.getSearchFields()) + .findList(); + } + return ok(comparePage.render(compareResults.render(compareFlows(results1, results2)))); } /** * Helper Method for the compare controller. * This Compares 2 flow executions at job level. * - * @param flowExecUrl1 The flow execution url to be compared - * @param flowExecUrl2 The other flow execution url to be compared against + * @param results1 The list of jobs under flow execution 1 + * @param results2 The list of jobs under flow execution 2 * @return A map of Job Urls to the list of jobs corresponding to the 2 flow execution urls */ - private static Map>> compareFlows(String flowExecUrl1, String flowExecUrl2) { - Map>> jobDefMap = new HashMap>>(); - - if (flowExecUrl1 != null && !flowExecUrl1.isEmpty() && flowExecUrl2 != null && !flowExecUrl2.isEmpty()) { - List results1 = JobResult.find.where().eq(JobResult.TABLE.FLOW_EXEC_URL, flowExecUrl1).findList(); - List results2 = JobResult.find.where().eq(JobResult.TABLE.FLOW_EXEC_URL, flowExecUrl2).findList(); - - Map> map1 = groupJobs(results1, GroupBy.JOB_DEFINITION_URL); - Map> map2 = groupJobs(results2, GroupBy.JOB_DEFINITION_URL); - - // We want to display jobs that are common to the two flows first and then display jobs in flow 1 and flow 2. - Set CommonFlows = Sets.intersection(map1.keySet(), map2.keySet()); - Set orderedFlowSet = Sets.union(CommonFlows, map1.keySet()); - Set union = Sets.union(orderedFlowSet, map2.keySet()); - - for (String jobDefUrl : union) { - Map> flowExecMap = new LinkedHashMap>(); - flowExecMap.put(flowExecUrl1, map1.get(jobDefUrl)); - flowExecMap.put(flowExecUrl2, map2.get(jobDefUrl)); - jobDefMap.put(jobDefUrl, flowExecMap); + private static Map>> compareFlows(List results1, + List results2) { + Map>> jobDefMap = new HashMap>>(); + + if (results1 != null && !results1.isEmpty() && results2 != null && !results2.isEmpty()) { + + IdUrlPair flow1 = new IdUrlPair(results1.get(0).flowExecId, results1.get(0).flowExecUrl); + IdUrlPair flow2 = new IdUrlPair(results2.get(0).flowExecId, results2.get(0).flowExecUrl); + + Map> map1 = groupJobs(results1, GroupBy.JOB_DEFINITION_ID); + Map> map2 = groupJobs(results2, GroupBy.JOB_DEFINITION_ID); + + final Set group1 = new TreeSet(new Comparator(){ + public int compare(final IdUrlPair o1, final IdUrlPair o2){ + return o1.getId().compareToIgnoreCase(o2.getId()); + } + } ); + group1.addAll(map1.keySet()); + final Set group2 = new TreeSet(new Comparator(){ + public int compare(final IdUrlPair o1, final IdUrlPair o2){ + return o1.getId().compareToIgnoreCase(o2.getId()); + } + } ); + group2.addAll(map2.keySet()); + + // Display jobs that are common to the two flows first followed by jobs in flow 1 and flow 2. + Set CommonJobs = Sets.intersection(group1, group2); + Set orderedFlowSet = Sets.union(CommonJobs, group1); + Set union = Sets.union(orderedFlowSet, group2); + + for (IdUrlPair pair : union) { + Map> flowExecMap = new LinkedHashMap>(); + flowExecMap.put(flow1, map1.get(pair)); + flowExecMap.put(flow2, map2.get(pair)); + jobDefMap.put(pair, flowExecMap); } } return jobDefMap; @@ -347,43 +388,55 @@ private static Map>> compareFlows(String flo */ public static Result flowHistory() { DynamicForm form = Form.form().bindFromRequest(request()); - String flowUrl = form.get(FLOW_URL); - flowUrl = (flowUrl != null) ? flowUrl.trim() : null; - if (flowUrl == null || flowUrl.isEmpty()) { + String flowDefId = form.get(FLOW_DEF_ID); + flowDefId = (flowDefId != null) ? flowDefId.trim() : null; + if (flowDefId == null || flowDefId.isEmpty()) { return ok(flowHistoryPage.render(flowHistoryResults.render(null, null, null, null))); } // Fetch available flow executions with latest JOB_HISTORY_LIMIT mr jobs. - List results = JobResult.find.where().eq(JobResult.TABLE.FLOW_URL, flowUrl).order() - .desc(JobResult.TABLE.ANALYSIS_TIME).setMaxRows(JOB_HISTORY_LIMIT).findList(); + List results = AppResult.find + .select( + AppResult.getSearchFields() + "," + AppResult.TABLE.FLOW_EXEC_ID + "," + AppResult.TABLE.FLOW_EXEC_URL + "," + + AppResult.TABLE.JOB_DEF_ID + "," + AppResult.TABLE.JOB_DEF_URL + "," + AppResult.TABLE.JOB_NAME) + .where().eq(AppResult.TABLE.FLOW_DEF_ID, flowDefId) + .order().desc(AppResult.TABLE.FINISH_TIME) + .setMaxRows(JOB_HISTORY_LIMIT) + .fetch(AppResult.TABLE.APP_HEURISTIC_RESULTS, AppHeuristicResult.getSearchFields()) + .findList(); if (results.size() == 0) { - return notFound("Unable to find record on flow url: " + flowUrl); + return notFound("Unable to find record on flow url: " + flowDefId); } - Map> flowExecUrlToJobsMap = limitHistoryResults( - groupJobs(results, GroupBy.FLOW_EXECUTION_URL), results.size(), MAX_HISTORY_LIMIT); + Map> flowExecIdToJobsMap = limitHistoryResults( + groupJobs(results, GroupBy.FLOW_EXECUTION_ID), results.size(), MAX_HISTORY_LIMIT); // Compute flow execution data - List filteredResults = new ArrayList(); // All mr jobs starting from latest execution - List flowExecTimeList = new ArrayList(); // To map executions to resp execution time - Map>> executionMap = new LinkedHashMap>>(); - for (Map.Entry> entry: flowExecUrlToJobsMap.entrySet()) { + List filteredResults = new ArrayList(); // All jobs starting from latest execution + List flowExecTimeList = new ArrayList(); // To map executions to resp execution time + Map>> executionMap = + new LinkedHashMap>>(); + for (Map.Entry> entry: flowExecIdToJobsMap.entrySet()) { - // Reverse the list content from desc order of analysis_time to increasing order so that when grouping we get + // Reverse the list content from desc order of finish time to increasing order so that when grouping we get // the job list in the order of completion. - List mrJobsList = Lists.reverse(entry.getValue()); + List mrJobsList = Lists.reverse(entry.getValue()); - // Flow exec time is the analysis_time of the last mr job in the flow - flowExecTimeList.add(mrJobsList.get(mrJobsList.size() - 1).analysisTime); + // Flow exec time is the finish time of the last mr job in the flow + flowExecTimeList.add(mrJobsList.get(mrJobsList.size() - 1).finishTime.getTime()); filteredResults.addAll(mrJobsList); - executionMap.put(entry.getKey(), groupJobs(mrJobsList, GroupBy.JOB_DEFINITION_URL)); + executionMap.put(entry.getKey(), groupJobs(mrJobsList, GroupBy.JOB_DEFINITION_ID)); } // Calculate unique list of jobs (job def url) to maintain order across executions. List will contain job def urls // from latest execution first followed by any other extra job def url that may appear in previous executions. - List jobDefUrlList = new ArrayList(groupJobs(filteredResults, GroupBy.JOB_DEFINITION_URL).keySet()); + Map idPairToJobNameMap = new HashMap(); + Map> filteredMap = groupJobs(filteredResults, GroupBy.JOB_DEFINITION_ID); + for (Map.Entry> entry: filteredMap.entrySet()) { + idPairToJobNameMap.put(entry.getKey(), filteredMap.get(entry.getKey()).get(0).jobName); + } - return ok(flowHistoryPage.render(flowHistoryResults.render(flowUrl, executionMap, jobDefUrlList, + return ok(flowHistoryPage.render(flowHistoryResults.render(flowDefId, executionMap, idPairToJobNameMap, flowExecTimeList))); } @@ -392,44 +445,50 @@ public static Result flowHistory() { */ public static Result jobHistory() { DynamicForm form = Form.form().bindFromRequest(request()); - String jobUrl = form.get(JOB_URL); - jobUrl = (jobUrl != null) ? jobUrl.trim() : null; - if (jobUrl == null || jobUrl.isEmpty()) { + String jobDefId = form.get(JOB_DEF_ID); + jobDefId = (jobDefId != null) ? jobDefId.trim() : null; + if (jobDefId == null || jobDefId.isEmpty()) { return ok(jobHistoryPage.render(jobHistoryResults.render(null, null, -1, null))); } // Fetch all job executions - List results = JobResult.find.where().eq(JobResult.TABLE.JOB_URL, jobUrl).order() - .desc(JobResult.TABLE.ANALYSIS_TIME).setMaxRows(JOB_HISTORY_LIMIT).findList(); + List results = AppResult.find + .select(AppResult.getSearchFields() + "," + AppResult.TABLE.FLOW_EXEC_ID + "," + AppResult.TABLE.FLOW_EXEC_URL) + .where().eq(AppResult.TABLE.JOB_DEF_ID, jobDefId) + .order().desc(AppResult.TABLE.FINISH_TIME).setMaxRows(JOB_HISTORY_LIMIT) + .fetch(AppResult.TABLE.APP_HEURISTIC_RESULTS, "*") + .fetch(AppResult.TABLE.APP_HEURISTIC_RESULTS + "." + + AppHeuristicResult.TABLE.APP_HEURISTIC_RESULT_DETAILS, "*") + .findList(); if (results.size() == 0) { - return notFound("Unable to find record on job url: " + jobUrl); + return notFound("Unable to find record on job url: " + jobDefId); } - Map> flowExecUrlToJobsMap = - limitHistoryResults(groupJobs(results, GroupBy.FLOW_EXECUTION_URL), results.size(), MAX_HISTORY_LIMIT); + Map> flowExecIdToJobsMap = + limitHistoryResults(groupJobs(results, GroupBy.FLOW_EXECUTION_ID), results.size(), MAX_HISTORY_LIMIT); // Compute job execution data List flowExecTimeList = new ArrayList(); int maxStages = 0; - Map> executionMap = new LinkedHashMap>(); - for (Map.Entry> entry: flowExecUrlToJobsMap.entrySet()) { + Map> executionMap = new LinkedHashMap>(); + for (Map.Entry> entry: flowExecIdToJobsMap.entrySet()) { - // Reverse the list content from desc order of analysis_time to increasing order so that when grouping we get + // Reverse the list content from desc order of finish time to increasing order so that when grouping we get // the job list in the order of completion. - List mrJobsList = Lists.reverse(entry.getValue()); + List mrJobsList = Lists.reverse(entry.getValue()); - // Get the analysis_time of the last mr job that completed in current flow. - flowExecTimeList.add(mrJobsList.get(mrJobsList.size() - 1).analysisTime); + // Get the finish time of the last mr job that completed in current flow. + flowExecTimeList.add(mrJobsList.get(mrJobsList.size() - 1).finishTime.getTime()); // Find the maximum number of mr stages for any job execution - int stageSize = flowExecUrlToJobsMap.get(entry.getKey()).size(); + int stageSize = flowExecIdToJobsMap.get(entry.getKey()).size(); if (stageSize > maxStages) { maxStages = stageSize; } - executionMap.put(entry.getKey(), Lists.reverse(flowExecUrlToJobsMap.get(entry.getKey()))); + executionMap.put(entry.getKey(), Lists.reverse(flowExecIdToJobsMap.get(entry.getKey()))); } - return ok(jobHistoryPage.render(jobHistoryResults.render(jobUrl, executionMap, maxStages, flowExecTimeList))); + return ok(jobHistoryPage.render(jobHistoryResults.render(jobDefId, executionMap, maxStages, flowExecTimeList))); } /** @@ -449,16 +508,16 @@ public static Result jobHistory() { * @param execLimit The upper limit on the number of executions to be displayed. * @return A map after applying the limit. */ - private static Map> limitHistoryResults(Map> map, int size, - int execLimit) { - Map> resultMap = new LinkedHashMap>(); + private static Map> limitHistoryResults(Map> map, + int size, int execLimit) { + Map> resultMap = new LinkedHashMap>(); int limit; if (size < JOB_HISTORY_LIMIT) { // No pruning needed. 100% correct. limit = execLimit; } else { - Set keySet = map.keySet(); + Set keySet = map.keySet(); if (keySet.size() > 10) { // Prune last 3 executions limit = keySet.size() > (execLimit + 3) ? execLimit : keySet.size() - 3; @@ -470,7 +529,7 @@ private static Map> limitHistoryResults(Map> entry : map.entrySet()) { + for (Map.Entry> entry : map.entrySet()) { if (i > limit) { break; } @@ -561,35 +620,45 @@ private static boolean isSet(String property) { /** * Rest API for searching a particular job information + * E.g, localhost:8080/rest/job?id=xyz */ - public static Result restJobResult(String jobId) { + public static Result restAppResult(String id) { - if (jobId == null || jobId.isEmpty()) { + if (id == null || id.isEmpty()) { return badRequest("No job id provided."); } - - JobResult result = JobResult.find.byId(jobId); - - if (result == null) { - return notFound("Unable to find record on job id: " + jobId); + if (id.contains("job")) { + id = id.replaceAll("job", "application"); } + AppResult result = AppResult.find.select("*") + .fetch(AppResult.TABLE.APP_HEURISTIC_RESULTS, "*") + .fetch(AppResult.TABLE.APP_HEURISTIC_RESULTS + "." + + AppHeuristicResult.TABLE.APP_HEURISTIC_RESULT_DETAILS, "*") + .where() + .idEq(id).findUnique(); + return ok(Json.toJson(result)); } /** * Rest API for searching all jobs triggered by a particular Scheduler Job + * E.g., localhost:8080/rest/jobexec?id=xyz */ - public static Result restJobExecResult(String jobExecUrl) { + public static Result restJobExecResult(String jobExecId) { - if (jobExecUrl == null || jobExecUrl.isEmpty()) { + if (jobExecId == null || jobExecId.isEmpty()) { return badRequest("No job exec url provided."); } - List result = JobResult.find.where().eq(JobResult.TABLE.JOB_EXEC_URL, jobExecUrl).findList(); + List result = AppResult.find.select("*") + .fetch(AppResult.TABLE.APP_HEURISTIC_RESULTS, "*") + .fetch(AppResult.TABLE.APP_HEURISTIC_RESULTS + "." + AppHeuristicResult.TABLE.APP_HEURISTIC_RESULT_DETAILS, "*") + .where().eq(AppResult.TABLE.JOB_EXEC_ID, jobExecId) + .findList(); if (result.size() == 0) { - return notFound("Unable to find record on job exec url: " + jobExecUrl); + return notFound("Unable to find record on job exec url: " + jobExecId); } return ok(Json.toJson(result)); @@ -597,63 +666,91 @@ public static Result restJobExecResult(String jobExecUrl) { /** * Rest API for searching all jobs under a particular flow execution + * E.g., localhost:8080/rest/flowexec?id=xyz */ - public static Result restFlowExecResult(String flowExecUrl) { + public static Result restFlowExecResult(String flowExecId) { - if (flowExecUrl == null || flowExecUrl.isEmpty()) { + if (flowExecId == null || flowExecId.isEmpty()) { return badRequest("No flow exec url provided."); } - List results = JobResult.find.where().eq(JobResult.TABLE.FLOW_EXEC_URL, flowExecUrl).findList(); + List results = AppResult.find.select("*") + .fetch(AppResult.TABLE.APP_HEURISTIC_RESULTS, "*") + .fetch(AppResult.TABLE.APP_HEURISTIC_RESULTS + "." + AppHeuristicResult.TABLE.APP_HEURISTIC_RESULT_DETAILS, "*") + .where().eq(AppResult.TABLE.FLOW_EXEC_ID, flowExecId) + .findList(); if (results.size() == 0) { - return notFound("Unable to find record on flow exec url: " + flowExecUrl); + return notFound("Unable to find record on flow exec url: " + flowExecId); } - Map> resMap = groupJobs(results, GroupBy.JOB_EXECUTION_URL); + Map> groupMap = groupJobs(results, GroupBy.JOB_EXECUTION_ID); + + Map> resMap = new HashMap>(); + for (Map.Entry> entry : groupMap.entrySet()) { + IdUrlPair jobExecPair = entry.getKey(); + List value = entry.getValue(); + resMap.put(jobExecPair.getId(), value); + } return ok(Json.toJson(resMap)); } static enum GroupBy { - JOB_EXECUTION_URL, - JOB_DEFINITION_URL, - FLOW_EXECUTION_URL + JOB_EXECUTION_ID, + JOB_DEFINITION_ID, + FLOW_EXECUTION_ID } /** - * Grouping a list of JobResult by GroupBy enum. + * Grouping a list of AppResult by GroupBy enum. * - * @param results The list of jobs of type JobResult to be grouped. + * @param results The list of jobs of type AppResult to be grouped. * @param groupBy The field by which the results have to be grouped. * @return A map with the grouped field as the key and the list of jobs as the value. */ - private static Map> groupJobs(List results, GroupBy groupBy) { - - Map> resultMap = new LinkedHashMap>(); + private static Map> groupJobs(List results, GroupBy groupBy) { + Map> groupMap = new LinkedHashMap>(); + Map idUrlMap = new HashMap(); - for (JobResult result : results) { - String field = null; + for (AppResult result : results) { + String idField = null; + String urlField = null; switch (groupBy) { - case JOB_EXECUTION_URL: - field = result.jobExecUrl; + case JOB_EXECUTION_ID: + idField = result.jobExecId; + urlField = result.jobExecUrl; break; - case JOB_DEFINITION_URL: - field = result.jobUrl; + case JOB_DEFINITION_ID: + idField = result.jobDefId; + urlField = result.jobDefUrl; break; - case FLOW_EXECUTION_URL: - field = result.flowExecUrl; + case FLOW_EXECUTION_ID: + idField = result.flowExecId; + urlField = result.flowExecUrl; break; } + if (!idUrlMap.containsKey(idField)) { + idUrlMap.put(idField, urlField); + } - if (resultMap.containsKey(field)) { - resultMap.get(field).add(result); + if (groupMap.containsKey(idField)) { + groupMap.get(idField).add(result); } else { - List list = new ArrayList(); + List list = new ArrayList(); list.add(result); - resultMap.put(field, list); + groupMap.put(idField, list); } } + + // Construct the final result map with the key as a (id, url) pair. + Map> resultMap = new LinkedHashMap>(); + for (Map.Entry> entry : groupMap.entrySet()) { + String key = entry.getKey(); + List value = entry.getValue(); + resultMap.put(new IdUrlPair(key, idUrlMap.get(key)), value); + } + return resultMap; } @@ -664,19 +761,33 @@ private static Map> groupJobs(List results, G */ public static Result restSearch() { DynamicForm form = Form.form().bindFromRequest(request()); - String jobId = form.get(JOB_ID); - jobId = jobId != null ? jobId.trim() : ""; - String flowUrl = form.get(FLOW_URL); - flowUrl = (flowUrl != null) ? flowUrl.trim() : null; - if (!jobId.isEmpty()) { - JobResult result = JobResult.find.byId(jobId); + String appId = form.get(APP_ID); + appId = appId != null ? appId.trim() : ""; + if (appId.contains("job")) { + appId = appId.replaceAll("job", "application"); + } + String flowExecId = form.get(FLOW_EXEC_ID); + flowExecId = (flowExecId != null) ? flowExecId.trim() : null; + if (!appId.isEmpty()) { + AppResult result = AppResult.find.select("*") + .fetch(AppResult.TABLE.APP_HEURISTIC_RESULTS, "*") + .fetch(AppResult.TABLE.APP_HEURISTIC_RESULTS + "." + + AppHeuristicResult.TABLE.APP_HEURISTIC_RESULT_DETAILS, "*") + .where() + .idEq(appId).findUnique(); if (result != null) { return ok(Json.toJson(result)); } else { - return notFound("Unable to find record on job id: " + jobId); + return notFound("Unable to find record on id: " + appId); } - } else if (flowUrl != null && !flowUrl.isEmpty()) { - List results = JobResult.find.where().eq(JobResult.TABLE.FLOW_EXEC_URL, flowUrl).findList(); + } else if (flowExecId != null && !flowExecId.isEmpty()) { + List results = AppResult.find + .select("*") + .fetch(AppResult.TABLE.APP_HEURISTIC_RESULTS, "*") + .fetch(AppResult.TABLE.APP_HEURISTIC_RESULTS + "." + + AppHeuristicResult.TABLE.APP_HEURISTIC_RESULT_DETAILS, "*") + .where().eq(AppResult.TABLE.FLOW_EXEC_ID, flowExecId) + .findList(); return ok(Json.toJson(results)); } @@ -688,23 +799,65 @@ public static Result restSearch() { } } - ExpressionList query = generateQuery(); - List results = - query.order().desc(JobResult.TABLE.ANALYSIS_TIME).setFirstRow((page - 1) * REST_PAGE_LENGTH) - .setMaxRows(REST_PAGE_LENGTH).findList(); + ExpressionList searchQuery = AppResult.find.select("*").where(); + ExpressionList query = generateSearchQuery(searchQuery); + List results = query + .order().desc(AppResult.TABLE.FINISH_TIME) + .setFirstRow((page - 1) * REST_PAGE_LENGTH) + .setMaxRows(REST_PAGE_LENGTH) + .fetch(AppResult.TABLE.APP_HEURISTIC_RESULTS, "*") + .fetch(AppResult.TABLE.APP_HEURISTIC_RESULTS + "." + AppHeuristicResult.TABLE.APP_HEURISTIC_RESULT_DETAILS, "*") + .findList(); return ok(Json.toJson(results)); } /** * The Rest API for Compare Feature + * E.g., localhost:8080/rest/compare?flow-exec-id1=abc&flow-exec-id2=xyz */ public static Result restCompare() { DynamicForm form = Form.form().bindFromRequest(request()); - String flowExecUrl1 = form.get(COMPARE_FLOW_URL1); - flowExecUrl1 = (flowExecUrl1 != null) ? flowExecUrl1.trim() : null; - String flowExecUrl2 = form.get(COMPARE_FLOW_URL2); - flowExecUrl2 = (flowExecUrl2 != null) ? flowExecUrl2.trim() : null; - return ok(Json.toJson(compareFlows(flowExecUrl1, flowExecUrl2))); + String flowExecId1 = form.get(COMPARE_FLOW_ID1); + flowExecId1 = (flowExecId1 != null) ? flowExecId1.trim() : null; + String flowExecId2 = form.get(COMPARE_FLOW_ID2); + flowExecId2 = (flowExecId2 != null) ? flowExecId2.trim() : null; + + List results1 = null; + List results2 = null; + if (flowExecId1 != null && !flowExecId1.isEmpty() && flowExecId2 != null && !flowExecId2.isEmpty()) { + results1 = AppResult.find + .select("*").where() + .eq(AppResult.TABLE.FLOW_EXEC_ID, flowExecId1).setMaxRows(100) + .fetch(AppResult.TABLE.APP_HEURISTIC_RESULTS, "*") + .fetch(AppResult.TABLE.APP_HEURISTIC_RESULTS + "." + + AppHeuristicResult.TABLE.APP_HEURISTIC_RESULT_DETAILS, "*") + .findList(); + results2 = AppResult.find + .select("*").where() + .eq(AppResult.TABLE.FLOW_EXEC_ID, flowExecId2).setMaxRows(100) + .fetch(AppResult.TABLE.APP_HEURISTIC_RESULTS, "*") + .fetch(AppResult.TABLE.APP_HEURISTIC_RESULTS + "." + + AppHeuristicResult.TABLE.APP_HEURISTIC_RESULT_DETAILS, "*") + .findList(); + } + + Map>> compareResults = compareFlows(results1, results2); + + Map>> resMap = new HashMap>>(); + for (Map.Entry>> entry : compareResults.entrySet()) { + IdUrlPair jobExecPair = entry.getKey(); + Map> value = entry.getValue(); + for (Map.Entry> innerEntry : value.entrySet()) { + IdUrlPair flowExecPair = innerEntry.getKey(); + List results = innerEntry.getValue(); + Map> resultMap = new HashMap>(); + resultMap.put(flowExecPair.getId(), results); + resMap.put(jobExecPair.getId(), resultMap); + } + + } + + return ok(Json.toJson(resMap)); } /** @@ -714,29 +867,33 @@ public static Result restCompare() { * {@code * [ * { - * "flowtime": , + * "flowtime": , * "score": 1000, * "jobscores": [ * { * "jobdefurl:" "url", + * "jobexecurl:" "url", * "jobscore": 500 * }, * { * "jobdefurl:" "url", + * "jobexecurl:" "url", * "jobscore": 500 * } * ] * }, * { - * "flowtime": , + * "flowtime": , * "score": 700, * "jobscores": [ * { * "jobdefurl:" "url", + * "jobexecurl:" "url", * "jobscore": 0 * }, * { * "jobdefurl:" "url", + * "jobexecurl:" "url", * "jobscore": 700 * } * ] @@ -745,42 +902,51 @@ public static Result restCompare() { * } * */ - public static Result restFlowGraphData(String flowUrl) { + public static Result restFlowGraphData(String flowDefId) { JsonArray datasets = new JsonArray(); - if (flowUrl == null || flowUrl.isEmpty()) { + if (flowDefId == null || flowDefId.isEmpty()) { return ok(new Gson().toJson(datasets)); } // Fetch available flow executions with latest JOB_HISTORY_LIMIT mr jobs. - List results = JobResult.find.where().eq(JobResult.TABLE.FLOW_URL, flowUrl).order() - .desc(JobResult.TABLE.ANALYSIS_TIME).setMaxRows(JOB_HISTORY_LIMIT).findList(); + List results = AppResult.find + .select("*") + .where().eq(AppResult.TABLE.FLOW_DEF_ID, flowDefId) + .order().desc(AppResult.TABLE.FINISH_TIME) + .setMaxRows(JOB_HISTORY_LIMIT) + // The 2nd and 3rd table are not required for plotting the graph + //.fetch(AppResult.TABLE.APP_HEURISTIC_RESULTS, AppHeuristicResult.getSearchFields()) + //.fetch(AppResult.TABLE.APP_HEURISTIC_RESULTS + "." + // + AppHeuristicResult.TABLE.APP_HEURISTIC_RESULT_DETAILS, "*") + .findList(); if (results.size() == 0) { logger.info("No results for Job url"); } - Map> flowExecUrlToJobsMap = limitHistoryResults( - groupJobs(results, GroupBy.FLOW_EXECUTION_URL), results.size(), MAX_HISTORY_LIMIT); + Map> flowExecIdToJobsMap = limitHistoryResults( + groupJobs(results, GroupBy.FLOW_EXECUTION_ID), results.size(), MAX_HISTORY_LIMIT); // Compute the graph data starting from the earliest available execution to latest - List keyList = new ArrayList(flowExecUrlToJobsMap.keySet()); + List keyList = new ArrayList(flowExecIdToJobsMap.keySet()); for(int i = keyList.size() - 1; i >= 0; i--) { - String flowExecUrl = keyList.get(i); + IdUrlPair flowExecPair = keyList.get(i); int flowPerfScore = 0; JsonArray jobScores = new JsonArray(); - List mrJobsList = Lists.reverse(flowExecUrlToJobsMap.get(flowExecUrl)); - Map> jobDefUrlToJobsMap = groupJobs(mrJobsList, GroupBy.JOB_DEFINITION_URL); + List mrJobsList = Lists.reverse(flowExecIdToJobsMap.get(flowExecPair)); + Map> jobDefIdToJobsMap = groupJobs(mrJobsList, GroupBy.JOB_DEFINITION_ID); - // Compute the execution records - for (String jobDefUrl : jobDefUrlToJobsMap.keySet()) { + // Compute the execution records. Note that each entry in the jobDefIdToJobsMap will have at least one AppResult + for (IdUrlPair jobDefPair : jobDefIdToJobsMap.keySet()) { // Compute job perf score int jobPerfScore = 0; - for (JobResult job : jobDefUrlToJobsMap.get(jobDefUrl)) { - jobPerfScore += getMRJobScore(job); + for (AppResult job : jobDefIdToJobsMap.get(jobDefPair)) { + jobPerfScore += job.score; } // A job in jobscores list JsonObject jobScore = new JsonObject(); jobScore.addProperty("jobscore", jobPerfScore); - jobScore.addProperty("jobdefurl", jobDefUrl); + jobScore.addProperty("jobdefurl", jobDefPair.getUrl()); + jobScore.addProperty("jobexecurl", jobDefIdToJobsMap.get(jobDefPair).get(0).jobExecUrl); jobScores.add(jobScore); flowPerfScore += jobPerfScore; @@ -788,7 +954,7 @@ public static Result restFlowGraphData(String flowUrl) { // Execution record JsonObject dataset = new JsonObject(); - dataset.addProperty("flowtime", mrJobsList.get(mrJobsList.size() - 1).analysisTime); + dataset.addProperty("flowtime", mrJobsList.get(mrJobsList.size() - 1).finishTime.getTime()); dataset.addProperty("score", flowPerfScore); dataset.add("jobscores", jobScores); @@ -807,7 +973,7 @@ public static Result restFlowGraphData(String flowUrl) { * {@code * [ * { - * "flowtime": , + * "flowtime": , * "score": 1000, * "stagescores": [ * { @@ -821,7 +987,7 @@ public static Result restFlowGraphData(String flowUrl) { * ] * }, * { - * "flowtime": , + * "flowtime": , * "score": 700, * "stagescores": [ * { @@ -838,39 +1004,43 @@ public static Result restFlowGraphData(String flowUrl) { * } * */ - public static Result restJobGraphData(String jobUrl) { + public static Result restJobGraphData(String jobDefId) { JsonArray datasets = new JsonArray(); - if (jobUrl == null || jobUrl.isEmpty()) { + if (jobDefId == null || jobDefId.isEmpty()) { return ok(new Gson().toJson(datasets)); } // Fetch available flow executions with latest JOB_HISTORY_LIMIT mr jobs. - List results = JobResult.find.where().eq(JobResult.TABLE.JOB_URL, jobUrl).order() - .desc(JobResult.TABLE.ANALYSIS_TIME).setMaxRows(JOB_HISTORY_LIMIT).findList(); + List results = AppResult.find + .select(AppResult.getSearchFields() + "," + AppResult.TABLE.FLOW_EXEC_ID + "," + AppResult.TABLE.FLOW_EXEC_URL) + .where().eq(AppResult.TABLE.JOB_DEF_ID, jobDefId) + .order().desc(AppResult.TABLE.FINISH_TIME).setMaxRows(JOB_HISTORY_LIMIT) + .fetch(AppResult.TABLE.APP_HEURISTIC_RESULTS, "*") + .findList(); if (results.size() == 0) { logger.info("No results for Job url"); } - Map> flowExecUrlToJobsMap = limitHistoryResults( - groupJobs(results, GroupBy.FLOW_EXECUTION_URL), results.size(), MAX_HISTORY_LIMIT); + Map> flowExecIdToJobsMap = limitHistoryResults( + groupJobs(results, GroupBy.FLOW_EXECUTION_ID), results.size(), MAX_HISTORY_LIMIT); // Compute the graph data starting from the earliest available execution to latest - List keyList = new ArrayList(flowExecUrlToJobsMap.keySet()); + List keyList = new ArrayList(flowExecIdToJobsMap.keySet()); for(int i = keyList.size() - 1; i >= 0; i--) { - String flowExecUrl = keyList.get(i); + IdUrlPair flowExecPair = keyList.get(i); int jobPerfScore = 0; JsonArray stageScores = new JsonArray(); - List mrJobsList = Lists.reverse(flowExecUrlToJobsMap.get(flowExecUrl)); - for (JobResult job : flowExecUrlToJobsMap.get(flowExecUrl)) { + List mrJobsList = Lists.reverse(flowExecIdToJobsMap.get(flowExecPair)); + for (AppResult appResult : flowExecIdToJobsMap.get(flowExecPair)) { - // Each MR job triggered by jobUrl for flowExecUrl + // Each MR job triggered by jobDefId for flowExecId int mrPerfScore = 0; - for (JobHeuristicResult heuristicResult : job.heuristicResults) { - mrPerfScore += getHeuristicScore(heuristicResult); + for (AppHeuristicResult appHeuristicResult : appResult.yarnAppHeuristicResults) { + mrPerfScore += appHeuristicResult.score; } // A particular mr stage JsonObject stageScore = new JsonObject(); - stageScore.addProperty("stageid", job.jobId); + stageScore.addProperty("stageid", appResult.id); stageScore.addProperty("stagescore", mrPerfScore); stageScores.add(stageScore); @@ -879,7 +1049,7 @@ public static Result restJobGraphData(String jobUrl) { // Execution record JsonObject dataset = new JsonObject(); - dataset.addProperty("flowtime", mrJobsList.get(mrJobsList.size() - 1).analysisTime); + dataset.addProperty("flowtime", mrJobsList.get(mrJobsList.size() - 1).finishTime.getTime()); dataset.addProperty("score", jobPerfScore); dataset.add("stagescores", stageScores); @@ -889,53 +1059,15 @@ public static Result restJobGraphData(String jobUrl) { return ok(new Gson().toJson(datasets)); } - /** - * Calculates and returns the Heuristic Score for MapReduce Jobs. - * - * Heuristic Score = Number of Tasks(map/reduce) * Severity (When severity > 1) - * - * @param heuristicResult The Heuristic whose score has to be computed - * @return The Score - */ - private static int getHeuristicScore(JobHeuristicResult heuristicResult) { - int heuristicScore = 0; - - int severity = heuristicResult.severity.getValue(); - if (severity != 0 && severity != 1) { - for (String[] dataArray : heuristicResult.getDataArray()) { - if (dataArray[0] != null && dataArray[0].toLowerCase().equals("number of tasks")) { - return severity * Integer.parseInt(dataArray[1]); - } - } - } - - return heuristicScore; - } - - /** - * Calculates and return the Mapreduce job score. - * - * Job Score = Sum of individual Heuristic Scores - * - * @param job The JobResult whose score has to be computed - * @return The Score - */ - private static int getMRJobScore(JobResult job) { - int jobScore = 0; - - for (JobHeuristicResult heuristicResult : job.heuristicResults) { - jobScore += getHeuristicScore(heuristicResult); - } - - return jobScore; - } - public static Result testEmail() { DynamicForm form = Form.form().bindFromRequest(request()); - String jobId = form.get(JOB_ID); - if (jobId != null && !jobId.isEmpty()) { - JobResult result = JobResult.find.byId(jobId); + String appId = form.get(APP_ID); + if (appId.contains("job")) { + appId = appId.replaceAll("job", "application"); + } + if (appId != null && !appId.isEmpty()) { + AppResult result = AppResult.find.byId(appId); if (result != null) { return ok(emailcritical.render(result)); } diff --git a/app/controllers/IdUrlPair.java b/app/controllers/IdUrlPair.java new file mode 100644 index 000000000..6d53d3c22 --- /dev/null +++ b/app/controllers/IdUrlPair.java @@ -0,0 +1,68 @@ +/* + * Copyright 2015 LinkedIn Corp. + * + * 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 controllers; + + +public class IdUrlPair { + + public final String id; + public final String url; + + public IdUrlPair(String id, String url) { + this.id = id; + this.url = url; + } + + public String getId() { + return id; + } + + public String getUrl() { + return url; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final IdUrlPair other = (IdUrlPair) obj; + if (id == null) { + if (other.getId() != null) { + return false; + } + } else if (!id.equals(other.getId())) { + return false; + } + + return true; + } +} diff --git a/app/controllers/QueryHandler.java b/app/controllers/QueryHandler.java deleted file mode 100644 index ddaaae30b..000000000 --- a/app/controllers/QueryHandler.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2016 LinkedIn Corp. - * - * 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 controllers; - -import model.JobHeuristicResult; -import model.JobResult; - -class QueryHandler { - - static StringBuilder getJobResultQuery() { - final StringBuilder sqlQueryBuilder = new StringBuilder(); - sqlQueryBuilder.append("SELECT " + JobResult.getColumnList()); - sqlQueryBuilder.append(" FROM " + JobResult.TABLE.TABLE_NAME); - return sqlQueryBuilder; - } - - static StringBuilder getJobResultQueryWithUsernameIndex() { - StringBuilder sqlQueryBuilder = getJobResultQuery(); - setUseIndex(sqlQueryBuilder, JobResult.TABLE.USERNAME_INDEX); - return sqlQueryBuilder; - } - - static StringBuilder getSqlJoinQuery() { - StringBuilder sqlQueryBuilder = getJobResultQuery(); - sqlQueryBuilder.append(" JOIN " + JobHeuristicResult.TABLE.TABLE_NAME); - sqlQueryBuilder.append(" ON " + JobHeuristicResult.TABLE.TABLE_NAME + "." + JobHeuristicResult.TABLE.JOB_JOB_ID - + " = " + JobResult.TABLE.TABLE_NAME + "." + JobResult.TABLE.JOB_ID); - return sqlQueryBuilder; - } - - static StringBuilder getSqlJoinQueryWithUsernameIndex() { - StringBuilder sqlQueryBuilder = getJobResultQuery(); - sqlQueryBuilder = setUseIndex(sqlQueryBuilder, JobResult.TABLE.USERNAME_INDEX); - sqlQueryBuilder.append(" JOIN " + JobHeuristicResult.TABLE.TABLE_NAME); - sqlQueryBuilder.append(" ON " + JobHeuristicResult.TABLE.TABLE_NAME + "." + JobHeuristicResult.TABLE.JOB_JOB_ID - + " = " + JobResult.TABLE.TABLE_NAME + "." + JobResult.TABLE.JOB_ID); - return sqlQueryBuilder; - } - - private static StringBuilder setUseIndex(StringBuilder sqlQueryBuilder, String index) { - return sqlQueryBuilder.append(" USE INDEX ( " + index + ")"); - } - -} diff --git a/app/model/JobResult.java b/app/model/JobResult.java deleted file mode 100644 index 40e5e35ae..000000000 --- a/app/model/JobResult.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright 2016 LinkedIn Corp. - * - * 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 model; - -import com.fasterxml.jackson.annotation.JsonManagedReference; -import com.linkedin.drelephant.analysis.Severity; - -import play.db.ebean.Model; - -import java.util.List; - -import javax.persistence.CascadeType; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.OneToMany; - -import org.apache.commons.lang.StringUtils; - - -@Entity -public class JobResult extends Model { - - private static final long serialVersionUID = 1L; - public static final int URL_LEN_LIMIT = 2048; - - public static class TABLE { - public static final String TABLE_NAME = "job_result"; - public static final String JOB_ID = "job_id"; - public static final String USERNAME = "username"; - public static final String JOB_NAME = "job_name"; - public static final String START_TIME = "start_time"; - public static final String ANALYSIS_TIME = "analysis_time"; - public static final String SEVERITY = "severity"; - public static final String JOB_TYPE = "job_type"; - public static final String URL = "url"; - public static final String CLUSTER = "cluster"; - public static final String JOB_EXEC_URL = "job_exec_url"; - public static final String JOB_URL = "job_url"; - public static final String FLOW_EXEC_URL = "flow_exec_url"; - public static final String FLOW_URL = "flow_url"; - public static final String[] TABLE_COLUMNS = { - "job_result.job_id", - "job_result.username", - "job_result.job_name", - "job_result.start_time", - "job_result.analysis_time", - "job_result.severity", - "job_result.job_type", - "job_result.url", - "job_result.cluster", - "job_result.job_exec_url", - "job_result.job_url", - "job_result.flow_exec_url", - "job_result.flow_url" - }; - public static final String USERNAME_INDEX = "ix_job_result_username_1"; - } - - public static String getColumnList() { - return StringUtils.join(TABLE.TABLE_COLUMNS, ','); - } - - @Id - @Column(length = 50) - public String jobId; - - @Column(length = 50) - public String username; - - @Column(length = 100) - public String jobName; - - @Column - public long startTime; - - @Column - public long analysisTime; - - @Column - public Severity severity; - - @Column - public String jobType; - - @Column(length = 200) - public String url; - - @Column(length = 100) - public String cluster; - - @Column(length = URL_LEN_LIMIT) - public String jobExecUrl; - - @Column(length = URL_LEN_LIMIT) - public String jobUrl; - - @Column(length = URL_LEN_LIMIT) - public String flowExecUrl; - - @Column(length = URL_LEN_LIMIT) - public String flowUrl; - - @JsonManagedReference - @OneToMany(cascade = CascadeType.ALL, mappedBy = "job") - public List heuristicResults; - - public static Finder find = new Finder(String.class, JobResult.class); -} diff --git a/app/model/JobHeuristicResult.java b/app/models/AppHeuristicResult.java similarity index 52% rename from app/model/JobHeuristicResult.java rename to app/models/AppHeuristicResult.java index 64686cc3f..71bea5da8 100644 --- a/app/model/JobHeuristicResult.java +++ b/app/models/AppHeuristicResult.java @@ -14,8 +14,10 @@ * the License. */ -package model; +package models; +import com.fasterxml.jackson.annotation.JsonManagedReference; +import java.util.List; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; @@ -23,6 +25,8 @@ import javax.persistence.Lob; import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; import org.apache.commons.lang.StringUtils; import com.fasterxml.jackson.annotation.JsonBackReference; @@ -34,30 +38,23 @@ @Entity -public class JobHeuristicResult extends Model { +@Table(name = "yarn_app_heuristic_result") +public class AppHeuristicResult extends Model { - private static final long serialVersionUID = 123L; + private static final long serialVersionUID = 2L; public static class TABLE { - public static final String TABLE_NAME = "job_heuristic_result"; + public static final String TABLE_NAME = "yarn_app_heuristic_result"; public static final String ID = "id"; - public static final String JOB_JOB_ID = "job_job_id"; + public static final String APP_RESULT_ID = "yarnAppResult"; + public static final String HEURISTIC_NAME = "heuristicName"; public static final String SEVERITY = "severity"; - public static final String ANALYSIS_NAME = "analysis_name"; - public static final String DATA_COLUMNS = "data_columns"; - public static final String DATA = "data"; - public static final String TABLE_COLUMNS[] = { - "job_heuristic_result.id", - "job_heuristic_result.job_job_id", - "job_heuristic_result.severity", - "job_heuristic_result.analysis_name", - "job_heuristic_result.data_columns", - "job_heuristic_result.data" - }; + public static final String SCORE = "score"; + public static final String APP_HEURISTIC_RESULT_DETAILS = "yarnAppHeuristicResultDetails"; } - public static String getColumnList() { - return StringUtils.join(TABLE.TABLE_COLUMNS, ','); + public static String getSearchFields() { + return Utils.commaSeparated(AppHeuristicResult.TABLE.HEURISTIC_NAME, AppHeuristicResult.TABLE.SEVERITY); } @JsonIgnore @@ -66,23 +63,22 @@ public static String getColumnList() { @JsonBackReference @ManyToOne(cascade = CascadeType.ALL) - public JobResult job; + public AppResult yarnAppResult; - @Column - public Severity severity; + @Column(nullable = false) + public String heuristicClass; - @Column - public String analysisName; + @Column(nullable = false) + public String heuristicName; - @JsonIgnore - @Lob - public String data; + @Column(nullable = false) + public Severity severity; - @JsonIgnore - @Column - public int dataColumns; + @Column(nullable = false) + public int score; - public String[][] getDataArray() { - return Utils.parseCsvLines(data); - } -} + @JsonManagedReference + @OneToMany(cascade = CascadeType.ALL, mappedBy = "yarnAppHeuristicResult") + public List yarnAppHeuristicResultDetails; + +} \ No newline at end of file diff --git a/app/models/AppHeuristicResultDetails.java b/app/models/AppHeuristicResultDetails.java new file mode 100644 index 000000000..0975fc6bb --- /dev/null +++ b/app/models/AppHeuristicResultDetails.java @@ -0,0 +1,61 @@ +/* + * Copyright 2015 LinkedIn Corp. + * + * 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 models; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Embeddable; +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; +import javax.persistence.ManyToOne; + +import javax.persistence.Table; + +import com.fasterxml.jackson.annotation.JsonBackReference; + +import play.db.ebean.Model; + + +@Entity +@Table(name = "yarn_app_heuristic_result_details") +public class AppHeuristicResultDetails extends Model { + + private static final long serialVersionUID = 3L; + + public static class TABLE { + public static final String TABLE_NAME = "yarn_app_heuristic_result_details"; + public static final String APP_HEURISTIC_RESULT_ID = "yarnAppHeuristicResult"; + public static final String NAME = "name"; + public static final String VALUE = "value"; + public static final String DETAILS = "details"; + } + + @JsonBackReference + @ManyToOne(cascade = CascadeType.ALL) + public AppHeuristicResult yarnAppHeuristicResult; + + @Column(length=128, nullable = false) + public String name; + + @Column(length=255, nullable = false) + public String value; + + @Column(nullable = true) + public String details; + +} diff --git a/app/models/AppResult.java b/app/models/AppResult.java new file mode 100644 index 000000000..368a616ec --- /dev/null +++ b/app/models/AppResult.java @@ -0,0 +1,144 @@ +/* + * Copyright 2015 LinkedIn Corp. + * + * 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 models; + +import com.fasterxml.jackson.annotation.JsonManagedReference; +import com.linkedin.drelephant.analysis.Severity; + +import com.linkedin.drelephant.util.Utils; +import java.util.Date; +import play.db.ebean.Model; + +import java.util.List; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.OneToMany; +import javax.persistence.Table; + + +@Entity +@Table(name = "yarn_app_result") +public class AppResult extends Model { + + private static final long serialVersionUID = 1L; + public static final int URL_LEN_LIMIT = 800; + + // Note that the Table column constants are actually the java variable names defined in this model. + // This is because ebean operations require the model variable names to be passed as strings. + public static class TABLE { + public static final String TABLE_NAME = "yarn_app_result"; + public static final String ID = "id"; + public static final String NAME = "name"; + public static final String USERNAME = "username"; + public static final String START_TIME = "startTime"; + public static final String FINISH_TIME = "finishTime"; + public static final String TRACKING_URL = "trackingUrl"; + public static final String JOB_TYPE = "jobType"; + public static final String SEVERITY = "severity"; + public static final String SCORE = "score"; + public static final String WORKFLOW_DEPTH = "workflowDepth"; + public static final String SCHEDULER = "scheduler"; + public static final String JOB_NAME = "jobName"; + public static final String JOB_EXEC_ID = "jobExecId"; + public static final String FLOW_EXEC_ID = "flowExecId"; + public static final String JOB_DEF_ID = "jobDefId"; + public static final String FLOW_DEF_ID = "flowDefId"; + public static final String JOB_EXEC_URL = "jobExecUrl"; + public static final String FLOW_EXEC_URL = "flowExecUrl"; + public static final String JOB_DEF_URL = "jobDefUrl"; + public static final String FLOW_DEF_URL = "flowDefUrl"; + public static final String APP_HEURISTIC_RESULTS = "yarnAppHeuristicResults"; + } + + public static String getSearchFields() { + return Utils.commaSeparated(AppResult.TABLE.NAME, AppResult.TABLE.USERNAME, AppResult.TABLE.JOB_TYPE, + AppResult.TABLE.SEVERITY, AppResult.TABLE.FINISH_TIME); + } + + @Id + @Column(length = 50, unique = true, nullable = false) + public String id; + + @Column(length = 100, nullable = false) + public String name; + + @Column(length = 50, nullable = false) + public String username; + + @Column(length = 50, nullable = false) + public String queueName; + + @Column(nullable = false) + public Date startTime; + + @Column(nullable = false) + public Date finishTime; + + @Column(length = 255, nullable = false) + public String trackingUrl; + + @Column(length = 20, nullable = false) + public String jobType; + + @Column(nullable = false) + public Severity severity; + + @Column(nullable = false) + public int score; + + @Column(nullable = false) + public int workflowDepth; + + @Column(length = 20, nullable = true) + public String scheduler; + + @Column(length = 255, nullable = false) + public String jobName; + + @Column(length = URL_LEN_LIMIT, nullable = false) + public String jobExecId; + + @Column(length = 255, nullable = false) + public String flowExecId; + + @Column(length = URL_LEN_LIMIT, nullable = false) + public String jobDefId; + + @Column(length = URL_LEN_LIMIT, nullable = false) + public String flowDefId; + + @Column(length = URL_LEN_LIMIT, nullable = false) + public String jobExecUrl; + + @Column(length = URL_LEN_LIMIT, nullable = false) + public String flowExecUrl; + + @Column(length = URL_LEN_LIMIT, nullable = false) + public String jobDefUrl; + + @Column(length = URL_LEN_LIMIT, nullable = false) + public String flowDefUrl; + + @JsonManagedReference + @OneToMany(cascade = CascadeType.ALL, mappedBy = "yarnAppResult") + public List yarnAppHeuristicResults; + + public static Finder find = new Finder(String.class, AppResult.class); +} diff --git a/app/views/emailcritical.scala.html b/app/views/emailcritical.scala.html index cb384e2d4..45aa0f1bb 100644 --- a/app/views/emailcritical.scala.html +++ b/app/views/emailcritical.scala.html @@ -14,7 +14,7 @@ * the License. *@ -@(result: model.JobResult) +@(result: models.AppResult) @@ -24,23 +24,23 @@

Dr. Elephant needs your help!

Your Hadoop job is endangering the elephants living in our cluster.

- Please take a look here to figure out + Please take a look here to figure out what chemicals is causing the elephants to go nuts.

Here are the aspects of the job that we need to look at:

    - @for(heuristicResult <- result.heuristicResults) { - @if(heuristicResult.severity == com.linkedin.drelephant.analysis.Severity.CRITICAL) { -
  • @heuristicResult.analysisName
  • + @for(appHeuristicResult <- result.yarnAppHeuristicResults) { + @if(appHeuristicResult.severity == com.linkedin.drelephant.analysis.Severity.CRITICAL) { +
  • @appHeuristicResult.heuristicName
  • } }

- [Dr. Elephant] - [Jobtracker] -
@result.jobId
@result.jobName + [Dr. Elephant] + [Jobtracker] +
@result.id
@result.name

Thanks! diff --git a/app/views/page/comparePage.scala.html b/app/views/page/comparePage.scala.html index 88a7bffd0..37fe22dd8 100644 --- a/app/views/page/comparePage.scala.html +++ b/app/views/page/comparePage.scala.html @@ -30,13 +30,13 @@ @tags.panel(){ Compare } {

- - + +
- - + +
diff --git a/app/views/page/flowHistoryPage.scala.html b/app/views/page/flowHistoryPage.scala.html index 36f7b56c0..74dd9cb0b 100644 --- a/app/views/page/flowHistoryPage.scala.html +++ b/app/views/page/flowHistoryPage.scala.html @@ -23,8 +23,8 @@ @tags.panel(){ Flow History } {
- - + +
diff --git a/app/views/page/helpPage.scala.html b/app/views/page/helpPage.scala.html index 5f45bb3e5..131a0d4e7 100644 --- a/app/views/page/helpPage.scala.html +++ b/app/views/page/helpPage.scala.html @@ -26,7 +26,7 @@ }
} - @com.linkedin.drelephant.analysis.HeuristicResult.NO_DATA.getAnalysis
+ @com.linkedin.drelephant.analysis.HeuristicResult.NO_DATA.getHeuristicName
} } @tags.column(9) { diff --git a/app/views/page/homePage.scala.html b/app/views/page/homePage.scala.html index d3b5f8e90..431f425d0 100644 --- a/app/views/page/homePage.scala.html +++ b/app/views/page/homePage.scala.html @@ -27,8 +27,8 @@ @main("Dr. Elephant", "dashboard") {
-

Dr. Elephant has been busy!

-

We looked through @numJobsAnalyzed jobs today.
+

Hello there, I've been busy!

+

I looked through @numJobsAnalyzed jobs today.
About @numJobsSevere of them could use some tuning.
About @numJobsCritical of them need some serious attention!

diff --git a/app/views/page/jobHistoryPage.scala.html b/app/views/page/jobHistoryPage.scala.html index cd53a5226..aa74d5dec 100644 --- a/app/views/page/jobHistoryPage.scala.html +++ b/app/views/page/jobHistoryPage.scala.html @@ -23,8 +23,8 @@ @tags.panel(){ Job History } {
- - + +
diff --git a/app/views/page/searchPage.scala.html b/app/views/page/searchPage.scala.html index 6a77f108a..f8dbfb296 100644 --- a/app/views/page/searchPage.scala.html +++ b/app/views/page/searchPage.scala.html @@ -31,16 +31,16 @@
- +
- - + +
- - + +
@@ -49,10 +49,10 @@
@for(severity <- com.linkedin.drelephant.analysis.Severity.values()) { - + }
Job Finish Date
- +
- +
diff --git a/app/views/results/compareResults.scala.html b/app/views/results/compareResults.scala.html index 7b529aa73..615ec2cd6 100644 --- a/app/views/results/compareResults.scala.html +++ b/app/views/results/compareResults.scala.html @@ -14,7 +14,7 @@ * the License. *@ -@(results: java.util.Map[String, java.util.Map[String, java.util.List[model.JobResult]]]) +@(results: java.util.Map[IdUrlPair, java.util.Map[IdUrlPair, java.util.List[models.AppResult]]]) @* * The layout of the comparison results. @@ -26,17 +26,17 @@

-qg8?$UnF3K_=CO#Xf^Qb{pT)ua-<=#jbYNsML; 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π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 DHy0yP literal 0 HcmV?d00001 diff --git a/images/readme/severe.png b/images/readme/severe.png new file mode 100644 index 0000000000000000000000000000000000000000..6be36c2013079c2f17b570a516c414641ccf740b GIT binary patch literal 3652 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 literal 0 HcmV?d00001 diff --git a/images/readme/suggestions.png b/images/readme/suggestions.png new file mode 100755 index 0000000000000000000000000000000000000000..f6ff17d75dfa3f2f7cb5cd5ad7d207856035d07d GIT binary patch literal 503587 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�r!3Ch%5?n3e4pcESpRkSxO8Irfl`tixv_X9q%!O6D@K zYU@Dr_sAzm?TB942v?xA8MZ(obwvEyy7x)VbBf7j!;sS*fn?k6ZX{v$`8U#6)SwIo zu2j&wsZPAhK0|}hKEl`zNh2O(r2ujX((lX*Z_M2YCnX4A=9VI1!;wbZliFoC3LwG> zS(e3mhRU4q4^<7NhJW5s(6&INz$qVqOP`1oC~?4D?F7e7Qf+}r0}-0lf3V|=D=v9y zybYP@Dsd|irHsPp>OUfv1ZOU zGAJ|+(0Z`dYgNNY-gAx;^-CkLNY9#lNHGb{SE@trFCv$SKq6M<-jaZ?DPaywT%RuZW-oh_C5LS^v~l zt=YCNJ-_UbDvEH4moVi!zP^MqMjWAY{YAuQL0QxU4T$HW79J$*Nb0=us(i$xz}qE) z9VU7ZibOcGk%(p5S?%K$YDcg9ClTKjQI|9&D|it>$Fqm_kruvx-T~S#L_YUF)8o;O zsMy>N59j}q;3}K=+Lkk0S@hir>fW%B5G53h66UWM(yrda)m>)41D|~s>u&hgPT#@O zX+m^waM1S3MOam$oK$iB0C5cA1C#cR$j^m_Nc$z8WtuLMD&On8v0t9n1MCMl@t#J>euY3yW2kQm-~Q0v(HNQBKO68@-Pui7q-f{jc# z@W$KA7?{HgDh+f+;x2}SBWCXf8}n;DA04JjcjWRcdl5DX5@5RbFV|yr67n3B%`c(7 zH8bA_0<{RzT%p>f$07CDZoGepZh9v>doT!pPT_DHLp04M=&A@79ij8;TqWXEp_{@m z1Z$X{OWsQI^B0_JKM+trL4v~H55JhO-h}0Y)n)QyxUq6z_17;`fZi`&I(N(a<&{zP zu{wbEhh(pCDE8;qRbffMz|L-M9!jbu{8#Ak8g%%<&2-IuYPt?UnPxeeVTP_FQ zx@9)r8rL2Cx9GmNg^nkW{68H`h82;)VKgD3STo{|GbAW1?7*+@I3XsxvzcdjH*^us6l z$Wl(L^Pl~*GO-ht!TibX6t$@IhzHxods#<^71rMt&XH6k2!aEl$w!i<|FV}$R61Ux zvI$i#Y3=e5NU%5EkI;9B9U>wwpeK;hlY$*s6Nr^z;eWu?M$~H1>u-cz7VOmt_vkE9 zkRXE~&Hiyx(6&?`zUZ6q8HzRBdKV91MH#jL z_!U_L(Jw-%J!gI(@49G^je$XtgF~b-;z_%f&ntarIa83)i6^l)iETk2@>$O-QABBS zxxX?PHj%Ne2_!%4dozTac`Sb2N_uEm;XL{r2a(g)xHs_FQ1{$^&`zCq7Yb)s4DBGY zcN_+&{E$z15v6`Y(B?oZF%prhOq@HTUFbGm8RLvPzL&gs^*vI=O%^_5ITKlw_|8cP zloWO18rfCzQp}*~VP)#a*zKD*SL|dH>5(pTDY1#}%5&33+Wv#^bh@LVCIlb)fKA(o zB(}=6G9J11ut z9A~-r?0G%D&V&0KhZ{cfjc~&+!4ez2$ts}Y>ANWWOaRR|=dvHO+ z%U;}IY!u(orM@3vXJ1MjBNR4DDV52mX(az^qX#h}x)!y=5EPxUa_)Nt!rHNOWun5+ zO#GZGZhti~ICwD^5gh*txMiNF3mlKV7~Sv!syXRR$IyGeMEy^wd~z>5XT`10J0Oaf zwqkDNI_tIFEJUyntB9^29P5a5F|0_wf$8328r(3KT6md)XQ5#+1rM5N;y^8a<&Mfy z&D&dk#d~(}$Xo(%Hh^1CgP4>;lP9uR|H{v8Y_E6Nao3DK^&T| z;}{0u-htOrKJ)>1EBIIqID87Q)=&0w5Z>Z=8xW?@W6wwLDh||`0ngGDo3*Ff=ebt= zp=;|2-tSRq9QKvh6UXI7sC|exhSVhgVuNeQ9eN4?!#`S7LL!p+M1W`x@%Em%eo?AE zhLCdHzm|+23rHeSuf<{4E%`-^DY)bDIpO{GA6d|xk0zi-n?KZrt$lR;dtZ6}3G1b~ zoGrL>5E)5{%{E?gPEnBr4FS=AJEh(k5N9}%AiQAjao#A^Pm%aI4vCfu;!#K{=j}kL&*3>xJmg&gY)YUdvf@=BCiPFeeINsZrAd0!ipVi zeL~>5Ja^F+xa;kkpK4k6l$ za5IouoJYjmnBFC5*cf-IMg(}Hv_lQ(vT#q4yB5e8k=m@HqB5F8Um8tp7;FV%!@wx> zNI0$-FAK?f0TZ(iU40xhQ5wKC<<%FU$b z*_=rBG)9l*I)GD70v?BnL}dWNA^?(F%DzMz?j-0lD&N zG_T^&vh!|%%9^8T9pND;^=aYtQIG8^GcriLGx~+4+X73kG_Z$=;5T5G4|e4c5#5M( z6fN`m_h2*bV2Sy+Rb$a)G=-9zNXJ?+gb;)h+NHNf(FV%k&N1FcpTnfAD)%CMzNfFZ<4h44P>t@347{V5t z(g(%d+i--!-#bc@26p`r84{&)@cWC7B}2#d2t;cZ!uSuN1(9ZW8WauU(w=jCq%}%M z_=*5Kx%~NAqf9=sUq)S^rRzx7V7^;F4%-ai1R#7nTgio}@Df?&2`txl9JEtJdG{*y z7_rgQ(q7-Z3{-&#WWD}^HEghc zKtqv+I*4y`pg%${xN)o=r>3E%Ce-CHe`N%kIUO+p&m>u*CD=2A`!MLtO?AF{WR|pQ z#L2jSHw2P&U8E;7BO~%?GlISEj(q?AUD|Pw6x?wZXfR9)?nLoVV=t*84=bNJ`tPa| z7!G>=Tn_6++WIKI+gmVb8-b9M@}c`e#g%U{uN$|ay~Bef-dcJMk~{AP?PLmVe8JPy)fFXj6($OP%x`Wv_QHN`FVfbqI`C0TXV>IYlh82<;?3aRm0u3TcW2VfPsH717$(qpUg9KU> zv5f44#n;52SRSs07O0*=W7L7BylEM3ukC+@YVu? zn(D8LS4k!L;hahZHbf}=v9<(4r~nN=yCS?|h?H`i5;5HxfSGT<4Ei)W@zbK6Z9mFx zU|1Y5+{Xnhc>W0oIxx#<<-ojlY|AEVOF#@(m=34~#>1K))f~dlSOF`FEJ@Ne=y9|u zyd?cP`Vn$aCQkub+3ir;4qcHfcr+8I#LW=lGCVS(u|gQM;T<`(Rl#*@#S3Lo0!umi zPWU+-1RfIHb;G0%e0^uuJJ)^=(S&p01mA)4L}0A?>Y@;IxhM7pU9zx9L7-cD4g5Ue z~dm#j~uvQ6cjF__^phY?8+<>Uak+1Y(eZu zgv!yDSFdXE`54h7p^;#kh(%fv`~~`lEk!iT9v|=-_bEL${mutiC;wO$Vk41GE-?3; zMEENZE37-SYBtQEV)}LE%X7#23`JOp`dFvWd4)%KX(2u#fg^sJS}k%_fvA>s%P(_U z?o+$mD-7>ZNaBK+2>{VmmQ#f{QrTWj?fx_t9!Jx=PxF*pg>_WGGlFnfBjGpE03Lkj zhMVnDYpg$TGKvnlva@W(^ztuvZt3Fq)u(p3>SZg%ogdRF z0y=93zLNC%obz?f2`MM46&5Jt|7d#WCxwv}EG>I@drfY<#N~1O=KhR#k_XKG>=OAs zZEU`$lGe#|KiiqPE#UyMpfIIc~bk)9sm%8!$RV1I@2 zhc=o@Sbq2di=*R@4qS9u`*o%0^d$08V7i>v=z{qbMiqD4k=}6gWafw=A@u`BvP+H2 zU(pYh*WF-KV2wB*=3^}2$d;gC*04Cm2}8w0qg6|2hE-4Qf%z)oc>+7>K-}88V=Nc< z0HYWZc}1pJXxgYQNB%2IaVo%v1SP<*^d>g`%Sg#O`S$J2z(D8JOYE2xG{VdF^#rP` z>n3c5Rac{@(%szMn{cIt@!#A$ZDQl!x*buP4rdSH&B9zSl1R55lD1-R`|>_7zH{b_ ztL({-%@Bx31+X7P38DtpG18i(u^hTcavn|>0Z05?9QpCM3qkxbJ6`;l+ zQ~lM36%s@drm*&6`=OOlW}}H0P}j{Rq2Ifp-4CH5dq$!6+~`K9hlHhxUaXngWr7La zsww)pzRr9lA~paudS_>pJ@nZc>TT(XR0c?(~dHGgZ`shfWi?FOcbJKe+V?} z1A08ytlh{lBeHMRzdtx=-&`nz9HNV^c|5WyyRLAPpi6SprdCx`GedWoo{!@gktQF7 z1hP}jb3S%i_HQ$I$Mt#n>pS6LI&u;Qi{sLF*e81m(=Z`KLEV2)swCscG9p-_wCwZj zUDSG8EZC(c ziVuwR*%SbSZ1NuB&`fbiZW$CbAn$NcAKvi?zQygq&nn;&oVL!x6nvX^f|in;j#HKe zy|6Ndm!|=Y-I`=od$rNauDPx4k+tyO>S(1lh4GajRr6d_yW`}gqSrHL%xw}Xm#GD2 zs_F(GS%_y@e{uiLo}4N4V`A4D`Gp6DVXVdL)w0EQuEd453P5L#rM6?6H!qadF#kjb@;hen}Yh$kmHBhcTP=&1ij42yPI9Z zl}SNrURCeR^uOl6$b3U2v*n#`Xa3hdNrjddY3F+xXeqOd`eag+td!0G)1uGib&eqfhRCfar*Lh zcaRG2E+5RqI& zIB1gucTJO+TKJK&i!;T@&fQ7Zz*edOlpiB1$MIJ7v|s!04LA+}A&qk#hajo}NB+aE zFNAS4rdhixippkWt^);3#lfN4p7+iMU!XsBh7(6bkHy@=0$=!BoTGkOb|@WLYYP zZ^dnuuIFCMd-J<)mtK9VW6;)FWO}Nmu<5cRCGI-aczNeD+1k6Kc9{O`Cz~qd?1fIZ z2$(LsZ|{3^$+~8}c4vdG6zyZgG94(c2hrFJuMu%#QEy~nJ2;87Z_hGpgJBhR_)srg zwzw>^pD_9=vFap8m3kDvAC!4QkXd5$uGy~J94*#V;qIU2d8qUFYi4lH!>fftac9bj z3~gej2r?9(q!|&GM$ZvMWY+?sB$Cz$BPX=fa#L#phexr%{*J(8BeZC5m)11xkhv2a zK<-iTwXtK6WOngefXY+No0ED2e-f{r+}I+a!Rt0ff5hNger)K5diNgVul<|YeWf$6 zrQ{oZ{A@!b=@$P~y6T$$wnP=B_rJ@k)ND>MlVUWt!=A1mGMrCK&9r6nAjA) zV3`s;2$PNBsY3_UjI^#fC!fLPRXj1?Z~Z3e_WrTkW`FU|8C+&Wu*!t@41%D0>+Ck7 z!>NT-;Pr4L$^r?qmvnPJDCFSH=?@}?-o5J$`VdfQ-f!zm>3a}hutfd|K^VwIh|r$| zOvYMnftZdd-}S-4LHYKfP$3gqqL}}HI1L7x_{za}7I)WPOl4jV0NFkGEn$3Of(FB9 z%q$1-r(ZQUONJaI0Gs{+h7FX2=ewTx=W>UgO3KPSEVL_bs86G-~ysV@*Upe60D@Q#xX8vHntaxZ0Vb)KvmXXrC(-5-6ky%r9sm8#n z!OvLl#qB0f20p%B<_ORgQHAv-25L9ZQxAfD!T0&@o|{TMXz7}QpE}KrYVf)MyMuFp z3QA|xNbNg@G>}J|1bpDCm`!;jr zI(scYi8MmqF#j&2Xi)=J0?!28MI;&laA&q$!{>|GOo2IOsjdK#$Tc)H#CJ7LGov0= z)vIN=2-hLP>pJ1FeS6Nje@EEbB+?xuBl=y*=)W@A|f#&Su*udtNEAwp^@C2;Hq1 zpXtQdeevV&e;mKd$aV{DlnM^gG2JV)S}`12KzrKy%`FxkTeh=%#;7~xrjERHBe|4y zksY5W%bXkKq~y>oe?K_$g)W73k*)T?`9GKB?&*DFP{UrIktJsSU%kuBA`Xm>iPVYT ze(fsi35){g@1-9rQwv0(;!Cr@nwoQ8Zj;tVf1_|OfPE5tzB{dsi~x#xVPXby*iSf- za()cg&wHO@y%V!x+*0<{>!`?M`lwA;jTu+mru&qUV6XSIfI34vZpNax`$y20>Z24c zk(-DBaWsM0bAtdu2<`~TIk?O=UASn)>k;X^nToK)r?UBJ(k6xT#@pTXdBo z6LNNWSxsB}uGT8Mwgw`Bf)CH^Moh0v&1^K1W?A@cRsfj)^+2__y6zP zDI+6;M@T5-L&|FcB>@9RCYyWa^Yin9KJ8j4Jwz@%V}Zc78Q*Q%QP*fqDiHSg1YD#P+HmC&Ay%@IIws#vd%b-0`z;6+7#wSJ&=f5! zxT4q3NstP?n`d(@*>k||oY$3|UX>%q#+p?5n0lut~?qdvUXYbNcunrZSP}eKW zBp4IYC~D}os3C*revxm8x&=B?qTJ4cS?4WZLqsaYEr-HKGv{&#>ov!E*V>o6ozJ< z65{?EMN8#7@WA4ele06$M-pE5?k3x9L%r4(m<`{=rk_`}WZXdcV$YF0oeB0Y$8o4( zbZGAHuh1F0johCExA-CtL|zCslrxyv-ed;pgeaA9&ex+OaY8mWpaCl4su8IKd4LLd zIgxrFl4}T35|QZK{RPWf3qjBQiR)8k?*Yo;n#A*R0?Hiv-6Ga+uO>9#Au1095q*L~ zfI9JHQ=c(iC>=)byN|`RE*Fh$wXTg^yCA}fnHM=`RDL)*F!&)vPKf_XOfa){Ept!V zqL=1AH7x)lo##5SRb;}Mk^4c^|3YW8{P56IP*ABKKdu5$&LiL5*|{FbbO@#?;H(Vc zP$f(>fq$O@L?-CAeJ5H7sG|srWnA?HuMcBy4^HKtP+QZ&9;?E>dn4vRKLm>65W{Gh z>=Ou>s~#R(M3V<@DL_Ayd*LAp;iFrr#zEntlJ%8wrYoZIN;-jo9ooi(fcL1X(r|yw z&d)0oOf$GC;9ZZ*4mEIazHoCca;tlnR6QZMYKhV_focFxCKZ(m(pZV`5FCe&P*z9L z9|sHV=XksWaX89fTjwAwgHgXZp)hm~$8(C{Q6>Vd(x~3Ub>cRg1UWEpHSnpxY9yie zCu|vz5~m}VTY1VYXqW3le^iO=NBQtl^7I_igoaQzV?Hz_&&C#;%lrk`=ce$W`_a+S znc+p}PmQ^;zByskSz=dJLtGl>#HEq{dA7RB4F2yjpEm9*@0VEElzsMrCEt_Emsyql z*+^@P>fOi)e{^ujE6Kt!bV>zm71X@ocpcC7>^gW{aH=cL`H*aRNiAgL2v!On6A zD0rl36!@p**18{rxWtn}%XZ>$(s*EucJxJ_@bUjJnad+(e3 zUh4JP9DYQ1oqbxJN>i_es&kMhsXD^SEN@M(&Y6P|So>USi zT~=tXpFg|%tUhN*MUa;8L$=@Yb4ION)@+}=_B4d<-|f6#_UZE=t@<|`w`yt*O*b3A zqzj8r1xa|`c2IOgyjrw~V@7W*j z6IDhTCJ(zOb;Zm{6x_#B{BM;Ep3}YTx$nH&nGb*F9jJ}cC;UvvGk=Gkj) z8{A*kY8m=8+XQ&d>Ia^oZ>V@=9*NTSkd;}|R?%k6RB=B^*)RXTZ_@yml~lnqQ{AE; z@{Ofd5J&u1cnpwGNGCT4>)XZctx=^%r=6yPn`O1fFqPsGeqUmv#v9>{e8m&_bnbeU@^8ZJSKg!G_4?xYn;k{5tSl_% zXEYxpA=U&+*waXVfJ-l|tWIgGfIPJs^N1_2TzWs=qTS*3OT%c*7g*8gJT@wr%4Dzh zS$qP&{5B2}5(x=z*YPTmOF7yZAzJr#xupjIIjaYe(Jp=?C3#M&)rAV@fcaJ7^H27F zmGX}KO>#_dAd(n~)amm}?`#fe7@s{oa=*&#v`P3ISi z#7i)h4Z_S}_7K4u!K?w+vh>)?=%%-7zK}Qwn-{e^T)UI+iy8!C6B!(oN__{K0N`sy zq3HVFUUncaO7KSO08Imz-XP*LbPFz$ReZqF&Bz$*5#xL@Eg(+1UyyWv}cI+1vB_^!wfSef^)~ ze*VXE9M^GN$JNE@Jiq5>yx*_)Yph(c}eJ}7IlJU^X6>CeRiM&;beF1@qY%!kW}ad0L4WWH&Cj1d$r?K9vOw=D`SCIi zJnE{@j&Cb~&Be*?n9o%QKaD4Z5Mg)wxi@qXx@6MXbb^YbhhzaT4bZLxCS25%_fE4y zptn{j(Cjz0{HLGmDf(MouCm?n;n9HTMM{uA%Ss9@&#j*}q0}o^XjTpmA9!~tYmIY# zyQ@Okv-?lCopqxtORm3ghkw7pMT?zI%@Xu0+DbPls-$lm_di0EO7+TO_* zGzrVv3xX8Mc88rB7e00N@_tM-Um}w@oI-vXPw0USqp=r6bf&bvdwy#l{ygqHT3l-BIH#iL! z8~Yd&1ccUi4)}_;-rb@*HvH*?x2oR~!ws2Nh8;aTrLW%;8nr_ftM=_&6cTXo^w&dlvnzzdisez0KB$xQrTOFw=j~g-= z0gJuuQ+ZfBxkp>jg;ARdA5+J+jBTH6$^Mm?Csm1b+s{yhghzbD9I97sKIYp1G1J2U0I2LwOpxrwlHanAk?<6F+I9Mw zY-M0Lz9SK?-xSxhQDis!A&&Y_=<%eJWVqgKrKIH9YV$B6!VfeNj2@!#|0-z7Vp|Yq z8DX#S@K96A-<-FNdxb515%;>nhU4%t<+fCiCSk~9)7bv1@o~qMm8EgDt?0LVh@}|z z4ZNtK0?XcM#YZ3{1w;MfiIpjUI#PgJS$h-2?>5vLRNUb@yuVN+mMU z#ia1Ulb++>W{HFbL^Xat3_$fD*h3ETN=rVP@MwI%{OMF^Jn7yVntO##8n z8$L4CL@U4D(X4Di4&^jvQN#?AauMKz31TtL8*u~P!S|OJ@oIAP?LZ{pZkDb#d`ZfO zq`;hi6&}9Jy%5w?Wx7^We7iytA-)6FxEclfDvClP zSeHlzLIrUDOMe9|7;)^*-R9uv*!3zo?;|`DWra6142Fj>TmF0z{QIBoqV4?schuag zrT5ij@+b2jv>eTM-lS~ADp~q&V{xa~I4?+uP2?Yq`e ze(HqKe;5$vntRfDgiW|jXb>Ser=l)jTCn}S!HrjOWJ33|{Qb*ZU2h-t;Q!egIC~%@ z`pdJLIR^051e2)7TkUBmJfYTZGslcYn5e>Y=G~`riVy9*aQRn6+5MHzU0XWJ`A zUYxBkCWOI^#f3icaIu~1R~MfVIR8ev+kc+?v+%sxAd`s=jkT|&$`jEg=OpgUvnO8K z(7)~E^nZGD&%t-Y54TyIwjR>&dZa@M6#xA+q(1kQekaZ$&--_meyAcPdz$cjiwqx6X3j&jz|7tgI9^bVlTV6F^nL0< zC9Ll5Z1hLF1_Ce}UkS^gfOPNt^~Fp1`2V@UUF<(C7$ExH;UGI z_5Bifg$e^x08Ta?TaaS&#xzpQsP@NMOGQl`o)9B5KQNWH*foG65FV>Y>+E~p9BG`} zMrHt;E1iGJHvL`%TvjlwtbToa*srlVQFa5hVt)bn_YZ{8yq3Y1lx6ce0Z?tV^LMjuJG$E+8BOI#`4ywLQJa!RBpkDynLb$c&ZS4vQA{=~B z7gH!0vKZNzn%xU}gX;iLw|&n;zD$25_8S3wg=Hkmh@pzG773MRwgn_E=)wGphHxR> z(kTb)gjGVKHM%Q5wud8`F5d|l>_sdl;-?+TumAh)yU~Bn2(T_$h+iDD*jVkM_u-o-C;<6@VrigT2eWbomEXa=JEPv#{soU z&CbkbbFQ)oxs@~Tk9YJ9ysk+pH&*&oN1rhuoO)ueeFx=nli<4Hmm%EvMncwU<>zo6 zkgUy*3(tAJ{_w<}9O z2%wV~%ui}Qkr3Ib9^-4}oIJP0-8Fo7gT_~jp{Z+@xhKo$Ou`hd{f3{xT^BzUXejLw+u74_cWSPL9g;HRY9`yz199Ig&82j&`bd@w%Pymfa^u?@LjRs#Yp^tF|ld zy|e=VaQ8HKhe!~S#P;w(60&z&6N-x>(w~5j6%CjVXgLA=j1vZczww1ZE^bpojf5mE zJQ8}PtGoL?>W>BRF(|I&Z({F~DHifH0-rqb0#mmGH(PfY4o4~qbd#_?zl(EF22&N# zVTf1;xUw&PsS{#`a;p_$`J1WT$aLMSE#*e57;z#e2nLqwpSj6vgx2-mc|OC1CPc~8 zFbLChFng8XNC3S!n|Z?zhY>rvMD$AqnCnvyH}aCeLNYHze?Zc)(yyH&m(&EqUXeD1%nYij>P zL_H$2tSMxv7{qk8`LM~nLpXSee>~uPFe2p<&qtjU^*BOgi;otjT7=euGYeqo%ugSF z&0Gf?+!+Xub>Eh>isCXqe1rt`ngphRs>D(0eTNL23gdf0au5yS?7BJm>ja@rSych3xA@&6Iz|IP zkIm;M>K(u^Wh8+G95jMw)_LtKt6SKePi;k$I^77RNqGt&B58<_VUIdGI$8kL4uA;rRdb1iT!VPemU z7OY5-+4(-u)Sz;CybluHj+;{p>ggPp#odGpiZ4hspEwF4H4W{*r|%}75NZjBi2@Xr zWYS86m5>=i%FR~87pTOmGClb7=Ur6Jfu2`DR8Hd#x$H9dMdr!T44t)YSympg8aV6J zzuvXCw>Q9euQ=KkKV?!I<5od;uI?n?hm4oG4iXqd%k2!RFhc0r!O{Tc06c1vn*ilY zxLC>~7@#79#^7hM*9)D5jYM&TF=>n}?NnT0U|ry8(@#Afk|}>BdT-gIw(lMLWNW<& zuw=N&V{+w?*53(pB5p`z$s`E8sRtP=2{or_sD+b`S`c*ImK^&a#%*45oUu2bqzNM3 zK*6k-6t}n`q>-7aiNyd2d;x+N#)KR|x6&M20p&KI)5p~=+`QFkAm{|ocEb9{O%#KN zjjSSg>GOKoUVocQ>t6zBowY0(B9R?@XD>pVSuFhrpfHF0i)iO@@0WTHEHB-B2xhM$ z78!OX4eC=u(#`s_@9^Oexn>ei1B5l=MJyR~_At4VvCI9+#JH9aEv9$N7 zVr&tPZNa!Ry96k2Bpsh_O))Rho_Sr;Ot%ULwcy*US0_$8GvT74U!!?dRU?jibwS-m zX_HTuE5)Z-zW3)8PcYK7J3W=VxRu&ckOREY3WKEx)Tfgr^ z{UrNFBa401ITa^k(WfV;>=nU z7m@Pi7roJ|cMX50kJHfd73oD@c77G3@FX)ky!qt*LglCSBW~nwAB z;u?8e3qftg?{woQo@x%+hKT^=oTdkw*9V27lQsI3%Gg&B#h)itAF$BDu~q8L*UtXo z`tAh)_bIRSPyBrUJuYUaL)~Q2i>Q&^q6$0qp7o$BtA8zQj{Da6M|-!3q56R4iYVRb z&T{2|!}ZF+X*&l*Id8X@D0^nLPKrl`G6t~Q2&Y`P$K@*_tDwzfWi(zSTTHmZakBKtcU(2pO$102xlhx-Ag@kw!@ei1J zY&~HRJ&dH2V;O~9g)zt$TEUxid>KWzg1X4+Bjh+WBzU&r(3do04bVW8%oEnR5!J_& z)8I(PErdSP>^8Tq!`;*gCTlTxwHi%^;K1jA+AaPlt>Zh|KcWk1{pzk{ZyKt3cGRoLtSY6SW;#Lr5&hwRf?gWWIZ!7xWy zdSs+NF<2iU!i+r{wD>~`gktEq?jx?z5R-=)T%yX$=hCkDRSbfmDb46RZrX)6hH1;) z38G6P?Ax3~=?y9#XJ6GY-H{NxiK3!dl;%r9zaf7rl>=83$VLQlaBa()fq-`J0H;q( zlBxMWwEGE%-=OdFaa9Jy3dHFP2iFAidbqn2YWb}8?H~W$F*nd+-uO20%@9+-_%|W| z^27RpV7vhYfPD;l38}Ndb%)i{nn4Tud4Kian{D4qE9fL^K@I&WXA8d!a$iliVE9Sy zY1{flWZR$5xW8g*oT>nnbP;vKH6Sk_PFKpz9B?7Y>?Lx#F-9G@O)%R$>cCi75NA@%4>Ixi$~H5oM5(_VS= zZPb3e{hR7t-WN``Ya}^0w>_bzJrRkUA%ZKp^~aE>cxc=V+Q{${eyRKdd$gPCzjg}a zK0FO|5z;b}RMF}` z1(3FGgvOs06DWX9LLW-$G9r-`#7t_-7iZEqM-JoCEJ45dhr}y5?}W4))&eJ~Ajv9z(->wLepZE7Mqht#RP)e$}z3tRn4fU4-^G`mz(s^IkE z@Bmjli95mD*O)t+?S#4#Kv4*=f`)3i&%W*e$FV~ssR%K3;YxfqsSMflc3+E{2Ou2 zdHE<5jgF-(vdjV^r_P9N>?sX32IYdZjp(_H^m@@=5_J>lF%t43A|fi<5s%oAe(h1k zI&~DI@|fju-Sv*0ixfriPuyr?*ZHoo-;DsrGuBl}vd+%Ve-KO5iA@(S`1$5C(p)>i z6%NlUNjQQuL+GdHkJ_+2g;w5D`^@g}3*!zy7QRNugJ--~?Bl~f%}rKj-|v8E$G+hY zrb&!z%`+C{ad!~+e7uZeV8)5z;e#V-Nu~i8Q1b|5#3{vA(!&W6GA=1r=dU{!Uu!1{ zpj?+zR2*+AUh|ajO~WNSOeC%*^7XBAewZBzc+dYW!|TLjdTkr7OO)+sPGPA;srCn& zF)}?0&$v|Yh!NIXNLJq6Xl;7q$k2PBq1u<^hE#|uv25-j=ls4FXt~xb!mqif1SSGfeCE#lq82u-gUX_BI4_npROm9 z2rAHJuM8&L5Z&a|{>odv{mjaFha<*Yru=6;>pVp1DOAEO90U73+(V_o%;l_M+UXo1 zx1XUQ?vVST{Y8n{Em}Du{0Y2`S3X7Ty=?TUr;qQ`SUhLCTzo@)YQR&TBlCE!?A2~6Df@=%IRmkD6Vj644Kb2S1 z28aU80}0~YEp4p>kJt|%-i~V>y864&?!x?cJXez5T^eF(DBeI=7Ow{_NzdXmChJM)WzX5ydV8t`SL|h>Y9_Zx(D$aa~)&nY9g31kKvKJx@M$Q|{nm zF&z{)=+}*S$Nv23EWdzy2*nBBsJ)I((<_f}-@f5J;niWZrJSbj^`lP5)>atE0BcZ% zX3i~(>XG=kIAVKp;J^VA{-JDNUHv0}lc>0O&M3gRp8=x8+;aqa+UOk43x;FweOst8^nXw5o@hJ3*Rg~5yHYxQxxd>oMM z5)w?HWQ7Y|JG7h7Cn&duH)E8FpAxb?NJ9m04v&eTk3XXSj$+-2!9`#gb=b$cJ9Q_W zGr={Q_yEcwF%mlYib<&L$**Hj;FXt`v)IZ=e~;$NIOo!6lrh1e*htIlJ>0LvlmM3* zs59{L9*uW~1T%nhC@Cqq>hhF8?8o%*iXc8bUIDXgc|4hxmX?m;;Rtg}10Oz67Hgr6 z0ZjJ{@#qaX_6GqbgBOSM0tH|vtZTrR@@adDPl{<}C(LeOU1MzAx6-k%dXuQ&iDQ4^ zyl#1eWWDvP?$%ID?3b>7W~^VF&h)nlcjldLk6#jez5L`{ncCvLC;7A^t7m_8$irNX z+U?Q-Hh9h6l^c23V`72MrtEiIM~z3Kz^V3Sb3Ow7Oo1{-fquX@i$BV{Z*Y~5@*MQ&^4zurrO2%U6!rGo41uo|TQk+g zuQr;0TMB>w^zJr09p1-vUlcc3T}gi$blP?6Z}k!1@L$$6bUaK~Yk*5$Y0l0G2o`zJ zs{UE%Wp>z#c>VXe=SsNGsLC)FdT;Hmyk2lZMxP!$|K~5joHo-^5!SnyV34*0G@w+U z2HjP!og9?kWwl^0@-_+Eq+Al#=!b-<`Fo_BpAtvq5z$PXzcRKieI7v}>Iv*ubx~2` zdpcn>|0TOkL$%QVO2PaAQ?{>nV<&F9yjzd9h@S(yNK6kxFeNI^~ROkyi? zFd-yXNL=f26vk$L(o;vDrllPW+?(fi?bNCIqfGscHRr$hANBt-;v}ZayTgj@_}D;4 z%psTjL9ur)2Bb`1<>`Uy=@8MnXVwdrQ6ujkMKu71cqEz9b0jI9LDCwN_Mw7>2hBjD zA6h2@KDl{X;_dtyX_3_CYscSq`cTZ1HtsiJ;LY#(hre7oPpf92yl-=x(W^g4Pk7xw zw0C40^piucJG^#9?Em@YzQejHM{#MEQv)}}SwA(bd*sP-C{4NK%;)XqvOJXu$=mz$ z=ESZH-M-&`BY}lg?J8Z#@mGc{LgQU4`@czt-CJ=h_FM$M!u=LcP_io+Kef8L%Y5gm zTZ4At;I*l~Zo4{!xw_YYL*HK92Byo?PGW0jj>vaz7JVS*)wWNOUG|-rtYiG;ry2?6 zwqkVDs}yV=1?@L&&iVg5nAUYWH*XXiaL^#oZwec?TX@ z`E%{vYr)1b*-ifV&#ABmx@-<=SXFfrjU(KD8?F%{OWq9hG_e$FRH;YP*c@BRO zoMVj1;qTe~2JT)fc2U!oE?Ms6snS!7^4@zMK=xXQJhte5-M-!VykK`ivyzg|$2OKq zQ@(pZ0t83CRrY61iRtcML*qJ&`B?o6u^rE~tPicEoc}d*X>ylyWpcY=`?K4z+9DQQ zZX!}{YaD}8*RPiH6Z$>VWw4t<_P-L-#e#V`J4HS;{+c*>TEuHr zaiAtQC8zcCtIsX!9x^L53l+WVmSY|~|MZ7=9@6GNKR9Di{pf8>)`=;rq5{t6 zYD>1>s+USBsm z_g7k9xBts|*1@WO*tF~n|F!QofPLC6exsVr^Zwkri!`n!WlJSvE+?uk1Rbbn5&g8_ zzlMowXkmVDsS9KI{9@j#y52j zONN4eNzM%axYnXxC%$WO&r|QI!zwW@vv)N*J9)OmoRktg=N!78iE3fOc;(NwejUEh zKep0t54EdjZ9ds|)JZtM{G^|pD5HK+F5p1g;K6B*f*iK_y3E*JVDLeHE@3pH%Pmaz_gf%24Jf=Qns3gD61EkltIX#l zM8w6r29Y|U5KAbuEL*)KZ#4?kt#9@}m8kp91A!xUxLZW!HF5jJygh=VcAFO)MOYmM&UYy!qZBOI6ovW+7 zk7EAFNx3%hYyXgrgUpK8i65e)S9rEqFuO`xp3_z34V_xwS*~-*VPoFXB|7G*K+ivH~M? zjqvvbrq|~)=r^`}5kJ27@(bJ?oMLsn>zVD=-`H!^Ido6gpDMtA#mNX4_sGdbuw}Aa zC*k~Ditggl*^y}F@~ma7U}{fV>Xm$rRx9zYo_Pxamt*D-uaUkOd>J)OqSzY9KYOJw^MIxbZu^qa%7B|>i3hon6B z(WX4^t{z*AK>FH_S_xqJA~KT)BjP$ex=1;38r(LVGD_ zqGwS;c>DF2p&e=oPoB2qhX)&+d8y{6Ql{s@$FsAnwmG>X#q@b==kTxMd4?hxs?Ey^ zULgL$!BuyfaI zZKf|&BF}AY=*0TM@~9EE1MtUB zuG*Tx`&mm0*H3D#X1aR1{TT5tPtLqct<7gO*g$b&!Yrg--@WVw(3A0Pj?^LlP`QIY(}E7Pk@RbMYuXZ_OkwYXkc$oHt|anKi`?I&BR)@N{hv);17 zt7QQqiMd0gedTXh9(}I3clLRkhzS)@t}+xFj28^Uo3W?fc;R^nWbB|M_1V6$%7Y z3)?g=c5AU0=pCcU%b#6ZzdT>I^jY%8uPDzxbFDY|ZeJ9yZ2Plt(C%Ehz~``-y7yrJ z;6H!<%vpOuiY2cy6Pb6iW8j+Tg-^Pg*XZAi`e-(N+Qs_3MOCaguHtZi_R^X|VvDnm zWlpQO0tpYx^LDIMytdCJu)z6-s|Q(%ICY{6@11JoJ|*Lg`i5KoV@Ov^5V+mHH|*kI zTk&g^L-(vB^KP#J7@F&VYaV?+dz(|s-qG;~PMd3%CSY1leOP5jP=~z3#=DE>k}Q8} zU)!~HbrPvvz9=3Z%$o}fGy~}EUGiaIK!+j)9~X#U?th$iY~lH3yYYW}IK;5||5vX$ zxpu6v&?o+F9NlgdUh8=F*_4}y+_aKee`GL5z+S1uM6+!hr9*7bb*zggSI zK`4F7*s@@R2v8 z)QR_KpZ9kS^C&EiMZTXn#6-Edq_z8F=mqJltp`A`_!w&>nrii~+sQa(GXfl=w-cq< z|NGVceT)CTD4l82#t+M1KbTt6ByFw~FKpRU_(HGxp4Ctz!`>T5YkrCZgp662hIStA z{3&Ndq;sKHb>{yd(YZQ_G2dR#6aXx`?jFfhMF{Q}qOMhdDa$Mb-=c?vgsYCD`ua1MM3# zFLgI3t^2?Hn*5qDF2DcJ&GgS-lGOaa9$0^W$m!XCSQUBGnjG3qCQwk!^$r4$@kc33 zB%l z%L`VN0*o&Sy~C7Ui(CFaVYC1@1I9IqAlPnVpWQ+YeTf=jC$QG z3E?XcmZr>NhI$WvrM=he#|K?t_X~n9vcBB$K_RMd^O{OCI;#HHSy~R>2`5GlndyDi za^9ccewF&{p7Cs2Zh5;%3iq7V7qi*%$w_%QFKuw%Naz)}mu9&r&oFQR`{S96ygmPa z81;M8BH5O7CvfWRZGWK|y%JOycYqpH6i`QAP&KZVx^#){;lqa@p%BLapmjS{M2Qg} zW|{DgAgVyOlm64se`Az#$HKrg}80fXTMOoad>-YWYHvtHuN z#1{BM$$y}*u#gO9@t}bdj#hBNr!Q6JMi71mWneCgM?nxk^^EbU6US+*Sbey=+^h2Pja-?cd55?)M<0Z zR|-xsRs#k^>xe&4dDhu~%;_lrBrkOT@;RT^GB+w)8z7&h_QGm$z6=dH>>0083LuMC z*4w#VQjWn(rec#gTW0K?Xme+ijq9+adqe_eVHEPKUSN~v7vYC6)xoezw9L$AhZD2e z=ewcD727m8wr;3w)Q`Z=O5p*wY80(RE(xud1&ST zZgF204UZ5EdsK{5>4p;+gCKfXw8JS#mx%g$h$8_3nIO5c8*2arc`UcTm8|w=htT$sh& zzGt@Woqaa06C9(8X0o%ZiwX+(K6>upmG`Sn{}~(OTo%>_0^ZgY85!9<0uThvrq9V8 z@Xi00sKaIbtIi8v_m%88)*pk(V+i2@#T8u8uj{PH8+JFK&vBaF>e{9i3y&F!6yrO* zBQSS6W%fPQPUN`l;pP@tuEPBF3Oxv7;gFB#=C*sN8cgx=Fw=Q2LX-*K@^(IC2|bh+}T_U@kUZYhqf7M$-6@2+Zo#Bgw1 zD@J}|C?eG@C#9!fVpt|JFXOoQIg!B-b7pMrr!@=_=P8dg?nEGfa(55DQ8z^_iGy^>* z9N({pyzcqU^^Z}%;2GnL34`N<3y`;M_9=nqoD9TTTGRn?aU2|6T=$^A+kcVx*#nk{a))ITr_?DO+!nb^V2xX!S?)PiTrv-HIc$^OB5{QTCXP4 zh9La|55e;KjqJwVOp(p%#G+)hV+D{sfZFC0p>R|u3(z!OtbM{x`Vn8|f$^y+5+PZY zak%~TQ#^f$D`4Kg7IS^vcULAi&z}JSBw-Vso9)4)PbC&5kCpzs>$J8);y3+V2QF($ z!Rw{N{VOv*cV`45&k18JurJhpaAl9cZiOw%eH?k?#g|g#tM%Rcjm;uB1C=~!!oHrL zHQ4afapyj6hHnC;Pf}C1Q?JGXeGn3IIX$P*w5)+G!EDvaJGJI;ISM{-UQAz`|EyU1 zrQcbN`6~*H=#JVtf)5?cd*&-Xv~s&pgU-;0DI}I))AIc=&&c~m7k~7ukm4okYfUWq zLpvp(;8EUMm=VN71X7@dY1OJ#t8O-FMQgG^yMna}BWmQd-bw?*}d-{BjP zN8AZfe*f2r@9k}IU`H43@0s@HCHUN9ZVbN&!cS?trjKO}fnq0EwF>R4*RCZN>FYLb z%y`d>B;{O(N$^%y62CgI|9*n7R7@E0l~TX6L@J1^t$|Ab$Q{JC@CwRWI>5jG$3 z(1+ykKL$;RBKg-#zxq!LPKRi~xsXZH!wno-8-qhBDVIO(_)E2FgGFip4uM_H(?ExT z_^!pC>vn#F%hM1Jf1uCz1&35YiB-`KTZm#XKD!?Xt09d8<&%AKl77A7Zh@PXMGY-{|i!q0nvLsWo2N{mZj zy;0SFgz!he!+`9u@z)2OZEz0FtT3i>#ZO#>)g7a~xXmv*qB^HQL7@PMl|uB8#KxC$ z5hV|tUfk527f{L(3Ap*O_5YamS6tP(HZK4t9u^3|@QFPz!2_yIXc7ysZpd-A0lkSV z7x1O4^{Vj(Y3K!9?Jj+3>f2wQ+WOvt3dYktxz!Mi2XU&&fG{Ci-+N*iYlw)j-ki%? z`PV@jj@0GN8&OFR&v2YQU@!Uaks#R^PljXTyKB$3hU~<|mAKnL7AYF9od`|*Mk(gN zag+p8xR&Omvnbq<-& zu_%7(oIh|XE6N5$S_%c{FX1k8Hl`)enxzLaUbbtxCd&k4MyuY>o$1m4y7-?7aqz#M zfbVuSJ`1d+gv63jY7e!Eq+Nw4Ykv&v8$QTXeBF2G?)z_gB= z@Kj7X$6!T+U%!jpfp7dlaPa+oI`I!&8o{qSm*=#LB4E}a00LiW+<6I@;lqvJisrYlmjlj22+UuW)vO=WI$W%VFuYc?EKlFsp&u+dBrAZ->e|<%YLn&A8F@QwMkDT z*)H&Is$k==IMk|m-5@83>;F5E!k4&0;1QEV3ZKg&{Nkl2MK5>t^tfU57{C`f$Fd2? zGsuzflq_Oh6v8l@?09%@AY_R6>9RY2%-D+);H}NzNp{gzR4;@XJ{J}pNi!Cs%adX| zd=`2oulnETATQ&3nTQ~z=1fWl0lJBRL{9@pIB^$Hsw-$|7*{v+a_UYvdR9zP8;wYI z6U(vTM=c++pwS^d@mhK+99xN25r76GQ*d??W;dVo?0Sug1x^X#(|wIX-jg?8x~2PQ zlmRbDn!A+;+Q8<6v7A^<-zkiBKsZIE_2u1F9qW;{);BIUsu(9Qa}$6XC(XH`fE-;z zx#S>#RNx~08sZv)Qr|xL4{AVsvtQm@82Yz3EiX=cdU**Z>X9Z1BF*vY>$pdV9~@3C zh0E!axt=n!7k|7k+5TVWE4TbTm}Z47RKHyp(!7O2`Q4W5gI42d&J@P-{q@PrAX=G! z72SITUbS+HD_wEk23pHzrSvUvsfQEqVzmnl(2l|qcdQY{@C$r&)D=EQ=WpG>D87Q@ z#P5p(g8{J{Cb6keC zQ}~Fb)o6z_Y~L>7iaz;0S3N-%TyF3`jiwgBhhxK{A^zZ^rIGJYpLUyPd~R$iL)OWu z&rgrkbf=R~2ral5Hh+j**xUF`Pi1*R9vaVe2Wfns`cC|#Fbd(UIRlju>QIqwof(q) z(jnJD-sR*HUBMzMx(lx#T)Nm4d&EVTC6BQ)m$Fouz*_raY!qc(obx|1~`;+-Ia5x z=$z+0Wd`5Nd)Q7ZC@}I;vYpancbs~6M?&}^`;fY$88_N~q;ou0D@%vOVF5#65Eau) z0(l!QAL6AM!^)?{N@Y@c{?({m6lN7Tg+qDId^}^8mR?02V@4FX_=8pcw4hBF;s)N# z#8h?Wy{d)=w0lOm@T&Oh`u*`1N;eV1L4>W`iai4wN_e@Pl$2{A)9KU0R|qFxc-pau z-t>KD;Tg{ev6L72_udK`Z(CUaqg@tEEuc#w4*R<*26M%^xI7w8m}SfUlL~;$iJn|x z^sIH+a%_UHo?6yFf8PCwFMQ|ftA@KVyXxpC_GZLT_4Vh8;pUxiN+Z?&+4$Mgb&frC z|C;540sQ$#5w@LQTasA`rd9wxV7_+`3RJ);K2X+JPPlePlnLxWA zQ!c>Km~BeTGNHdy4HwyhQydS6BsDc|4?|545-u&1m~{aRWr*7|X!=3zv~cTy4hC!^ zIa|9XodnCQ@U&(vj;#g@Gk@Y*;43LCt{k?LrsyI*dbrzDoH;~KN)Q@Q&r_hy7HT|3 zjQa1C|Mg3+db0UQxdSS2@|XZ3g`X3@_Jfzy*|xc3>BDe>ze5~Dp!q~)CSvh()e}{3 zC{wUiN^EX}u3F9U6`J~5NTLz^)rsD12H+ogEs8@z0`Z(@iue&Xk`f<)nmMHF+;;c} z5myN;gZEI~!s%k2RKDj{I4Oe?EXBJ+uf^lK0c33^tsf5KASd;~&+>~i`fPz0QDeV2 zo6duNV-#%FK+n~Lz5ssm8L9NiS!{d)ji@Tj&(9-nVJn`bXFxz1q?leualWsG^#JHOQ*atH@%jC$B`d9Q{?@Hq7a<-4C#(dw zf%17ci!(tdbYJB;DxH+9<=vdmuj5RFqU|ma47d~&V&9_uAU>RE=;a44GVZgR>SxuN zyYl6kG=Qr@Q0YVsm41KVd1;u>_xS7!C}wA%*Aw;m3Tt0!pB;RdJl{n01Y)YzxVl7} zK~PRIAMVQ-u@Fo4w=+ltSpYX1Y{Rv&!MGDj5l8EM4K}5&O_Cl4|DV zJHzkg{5DJpjguBur+@-C=Zu31gdPYWHoYxlt zKxrGS2%yS=W7h9U`q#0`*?avq0!KKeOfRa)~@)ay&q<9gqgJG z1Cs&n4SjNfeDBf=XE)+0iFF_AS3PN0| z(Yj@^<>Bl;g!^H28KUr@xMGEmi*+1xo(pbpg1P8@P%`k!H5PlwLqO$;sK2|=>?7&L zv;PfVw*%zxZYK9d^|H_bDEj=z>6`O{KlL=E8E?88h0CD}=L&jO#k`!ITHGV-{{foq zlj&~DJ^xQULiFwBCCWs0IlvD4*#HRg`KNHwm!g1In0FqYN7C7~80FShy7*{qqd<9Uxkv7k7}}52WUJ0$3#i2;N|l4 zi4jj^%VdoJ4H>9lWywOj1t6dJ#r1^&>LPQUxZBX9R9uNj(B--Q^?dIRW^{fP#eYbE z)y|!3@eJ_6EMhKp`yaZbww$i0ub0Mh5*x9)`sVSk3u0$~GbVYsZ({Zbpc^PmLqkIx zKaI<5F4#=w`7QpfECMfM@4h&l!j|AqJk;3c!o}@2LLNm15t~JwqIy(fYd4FN=LZ|T zci(OMP_JLMV(8BF3yu=j1s59Ek7BnWb>;mso(jo$NbLZutIXIFzPhUu zy-}}DymANJ^BNFPv zRr~63GI|bQe#@MEF6%B3`iFzel1@r_NOrt;npfC+B~1Z$>M*K$f=>^XqV%z_reem$ zh<(Bmh5zY?`z$7tii#=)9-!*pg-Aph6?-6xLzeAWw-zdpql%X_80g{_zk->Bb}V3A z9gapK>KE~Y-DVG7V!Q@ahzQ!+a%s=yyF?sZllskZ4N^#Oj1x_azvoc7KW;3d(QkZD zi~!$VZ{DF6cBl}Os|tG(=_A*ixRN9CpfVZ8a+1%2dl6Aih4x!1_;Odnm>+SZ z4-6sXVJu4~Y zjJwcPX8S)lx#IaV`zL|-Rx3uu^ffVct0v4q2DcWY+e`xmACp1wEP2R4cS>iK|z5b zA*-eGpSB1Dq6Dl~-c=Y$KZhW&3ZDtIY9fgzc%Q@Xi99;v)cVMIvMW?ytSCSUXRq_Y zdZ?lHjkQlangkLDYP{8#zfu+5N-Z!5p+65FJh*6Gjf=z2KmA7Q?*DM(_orGTJ|rfd z9-eE1W?p2&_`xfmJ9vF}zd#UX+10X_e-_HOR2Og3591?4KABRGmMO0HQ`Wk|?c>4v z3QyCe2uK60`Dng*JAFzB z;<``A$tkD8+H<6(Y910b3mKvCZ#b8o z(leRPXOM~y-&*%L>N^pC^&yMEh&g*GaTTuS(vb2O_6U9D2q~;m4vr&LC__MNse8G~ z-v9|&=}bKrUoN-C+l;dt*#eb_;T*f;X8=IF1+R2@{Amu-$hL{UJDXH8@^eT@7swoV z8@tHlnv3m4`rC+P%Y-%i*Xg}~dtLRo{nXHt+Id9;vm;^F6E3j^h+PnV&TZ7yZ`Xr{ zFtSVc;|&jZPCTqZIx)q@c|9 z+yl`5Y-IHwvqofYK*1lUHNW0XM%^LbV-xe(4;`0sJ#^g9tvE992pjofM1*tnpaYk| zCvA{cQPf_vzW$r`oKwcl-@z}3{?yWu8U)r5A{q0jhrEUm)qN42o^8e5+{Kor*+i8P z9#+_GxJs6o{+_5*dA^kh@ti-6N|yMGg@uKg1}gv^+fG`3)OBk%?V&-*mR1wd?5_7W z5n>C4j6NAP)YJ|?99amPj@4{iYqCm67MK+X$uvO<^u1p?xHb0 zI~U@5BXt_locDX%;3$K*%lqv^tkzM(_NQP&UfwWe@6^xDPerwV5Cy zmPmmoq1;EA`t-K5=P*0_Jyj_XM|Pt=!9A={5A+ktqQKDs$h;2ka%rc7r@T;@FF?Yf z^QPE(im?4qRc|85ChNfK2Jj@{#q>})2=^qCaH>q$V9E3hPG3gGi>TSYKN8F#>1fES zt`=6C0NDNcpSXd^A|wH!+9C{o#y*dQ-(6;FqlB+FAt7OxTE>bFUSY622$rKT-HEg0 zl6pMNuy)ef!)+rooGHWhl1GkYI`J_$J~D$}g=#!6M(n^%_jN5||$zx?_ zf{A0(Y>{BtxwGur&Kz^Ye2l_VIRceb6|T{(TSbNmfOas#gziS9zBiLGFTk=yfTq?M zCqI<5k>70-Bt8_hw;$8L9=DJYO>^}TkxNfxM`8qv7)c9-n;uwAB}j~tJsEVA-RmS;QtDqsE0?1 zk&#gc%219EA^VUFwyFQK?*uRRB*J|%JW&v9m}+JQ9<`t{(zPAMU=%}Nn2(z zepXIyBszbQ(%)3X`2=_YaY2Be#5(XYFlv`{iKW_j^$G9}0tQ*1$2YEKWuZa+v*1LL-Cj$_XPjL3-Q;md-UKOqJzk3LNrM$26S^_y)A*GEJ_v2RmGfs4Q}Vf!R0 zmJU@GoDlUUwVyzgOY^DKqVE{zzhkZ|?>qz&TURmA4(CH3)N?a0)_5U~(V8VY(c z=6HDb7j#$e?Z-JO&oE3OgY)y(A4h%owEOiiw)fxy$Fc6}K!@^kO#v-)p8QQU^CDAe zOuFN{xgaSQ^|`%zugA!%bI%hJf;?AYesX2!OSOtfiR%yAg|(#{HNh$YvXXJjx5ua9 zs{h|Cij>j{ScsR_UOuj&!F3zQcGGLgvGH;C>NcF#@+VJ*YI)~jYW-))9bQRj8N6hN zAT@^)n>t!^9R@7WV^hBH*M+J0o%u4{+y5u-#RRir&OMBst9~o#gi5AGx7hn_xLg^` zIyRt0UAg0cD#JI%f3q}{b0u9C9*d|^9E^hj9}|n(aJv4fj7;|KLpV5IGmRkUzz9s! z2f_7IUy%{4}-|zQ4Kj&v$089t52trAMN4Mf0 zJHSHF0W|})LQzps)-}b!oH$IDLazA6D(=0NHhkh2N-dXf-Dp~2#?_`9_@QAr<;OLC zY~3^pXUW5_{Z(sRa^Rs3yDXAH4I}7P~SbsFt*ea%Ffgl zk+`r*D|LyQ3*qNMxf$=%94$!&sccj-FmOcXiz9!)WcW^FF<2iVsv@ zW5$=2-;PdXe1=W|F~RtYtKZ((-`QATyDRT`wHUaS`i8O(t6}b?-3lGnVZ+D+_1jhUj3Cr z1|`|=u-ZE~yZ{{L=vj{_*5bKFHf{;35bMk_^h^BrvXp1Z3!MqvPWJNeANKt6in|PT zCFI-`$ZUcc$dEgqV2A>uPLduX^|`S@waupeUIAmupym@LRKY>GRX2;Ios-Gsp$Qe3(%) zJrKzq+&(y9!5cmv>jGvToENk^I=L-L}-%C9J^~sO5)9lF>cgZ8ww|-+JFofX9Mq*pqEwW*!5D>8wsEOivy(Id@?{T#$qIfdS5 z#*Xqe=X=_Is*L9O&^{9!GK$mUQ&hpkMo;#DiSd21i(u+(`LujvitqR9pn1kXxh19s zzKNts*m25D1P=KuPT^P6t_PJP2x)L*CRMODMUsJ`PG}5Re0BXwTosd!z6TpkFnTUO z1c%O7imH};w~lQmRMcyoEC7eRB%B^yeF;B404%WM%i1BU3v5F#LF!WgFj^J?+e2(` z5Vqu?3Q&FcO|W4yX5vCJq2l~(0>``uv!RmI5qbuwA7L6;?qR6UM)pD=`R-eH?*8Td z=`Y;BwFAKE6WBDjH8Byz`Kwu`)EB#r3GPQOkRxq*O?>7?kFBoNS;?KooB9@FFyM z#8DrfUIekn^P3&+99<@tpL`B2Jd#pcW<9h?u_xjp)syqq0n*=jV1NfSOt9|#pBWTj z1J<{`2~c`>x1kjb@4yEyTK9S;vZ#KYLfbBb(5;as+x*jOGyryc?xX`Upm(tf@hgOh zm4c!oa4iV!z~+_)GO9_BIUscvcqhnQxaYJyr&?>RU5f~TkWT+fL%~LZ8*)L>^8XHQ zIEzq`W{Omm{y*Jn6 za)w*J%0-gcJjhd4#kJKq=YPs}6GY_<#CYAdzmM6-ZB3qy^4Qi~^vRXR2-8&PwfZGa z_#rCL6aEw1;2`5}W%OueT}0?%J?C2r$Y=ro_gNvXr5};OFi*Nk!8QS3F%-pLS@)2887pUcUTr=%lDYC+wM0yiT ztXPeuUy=~&_r&FY0@npezRP@fJ#Xi{C@zrOalXNbS6r@UM}enO^!&KCoZ?<~`K4!1 zt~7Nu`bVRIm0q6G=12r&OsrVkHMxEZdC`;z@T%u#)1Emf`TFIHev&F42#}=xQ6!}( zd_Ujw0ZHkXdWVU2$*J-!4mw#AZ0K*uFq?qTV$gW7f@$3Rwd7eH??#lc*9uIRXF7oa*N-z95(y zoR87aywV6eVZ+D>^?J_z7%;M%Kn2rZQ{zsK?}-OCKQe*>W0l_m)_1VX-W6YiR{_wB z0=5)j2)P}sQz4z8=LUqBBGy3g3n0Fp9O@-TQ*a6Zzs<%THMSbp`n1}R97QE1=6D6z zBL0!tpfZVH*M^6FVDcES9c1^~(E($QV8i*JKXLsfV>gGBaZitFDnA+Gx>Q5~oc!`W zhp8qdn&02i7+RHhPr!s3=1R=EQwR$O*32RtUnqpmDa`yq$>(R$?{+%xv!ZVbVtcU3 zyiCvzHUwTH#?Iu>&}KT-DVJ7#@0k}C!Y+mLEg^wyror>FmX;O^Ei)uth6B&g@6ix8}sTcTT|Blr-6M<3V)$4_5-cz3;TL*=h zkZu8$c5>zqZh5B0do>(rapXZ5f`kfzqiY$}eWp0dfi=oy2Yi7Hu-*c@FvQv(@f3vX zodSF~Itd?q6!P`}b~hb~i)|Q2@`n(U{$g(nyZJgun?~?{>#65}laST8OW?j`lLm$V zg&T&=BfFpsZAP)s-o->+QQK-?<0QvKf1NNsP5a)$;nGP_6IxWX6pkQk1@84|P|%}i zK2-d77DYV4|Hz_HVxs;3O`?dW`8$b%C0YZ;`_}t{muefU{G~y0g^3ctFG7P&e!FRA zu^S76!J*3=b)w+Lm$PQ~%Hcpqv30qLBn*R=8g{PqXNm0}1(t-$I5d82{g|>;wxC3O zOx(rT7yD8)XWTOO)ie4$neyJ;oI9n$m_MC6?+B~AKCk^?HzAxa+Z`qRWnr$z9bB=J zG9zV@UieDufe_!MZpU1EK&ZkRK2ou-_wza)lX~Yvt%$N%idfS0v~ta+#qs{^g&1FE zgK-HS!(RFi3GZ%D>=pJGNGKWJJ4^SB4Zj_-$M_@d@s9N8yR(`Ozx8JwD)ZK(sC3!4 ze-4%ts+KN%zhT0D>?&PVxE@t3{P=s+j0>wu@Mg5Qlxpy{Nqm89D2{bsj&3Chm~+U* zBZFVvGhlNeY;N1T2#XB>^Q+L)L%jwMaizt#wz$JX7!buVz=>=Ryt~<+hoFN9j)eNn zM1B$IIZ*jVWkeWK=G?Rnwgh_1e+EI z?h6E%(1?hurqxbNqZz`^j*c?GY>G`v!h#kSaG%tfO6Wmvr)67~^p!!;3@qC~!I4g2 zNwiE3vjRU5)*eDqKq16c*sNn+U#!3g@|B_hAz=*qkB93Q=3rC`3kvNd594rij;BOi z@1JoS2n%#p7!o%aueTsE9Z=FVkqY-`tn_W__N)iZ08100Vpj9KAA82BSD)fl<$T#5 z3PuItWcUa#dVFDq46GI5@2S8aePLV6AyF!?e|dSq)^IHgw%}BVT)Yor7&DbOaKvo( zPYgwyJ3%82+>lWP`jSU^cFVc4Ose#-U4W7M!u@v;Ie|F5sd6{0IcTOJs3QTrIKJd& zBoudn(!&?v{Z&ujDeKy(7!Q61B@*yHFTquU;VSG5!H0CncpI3~c&HRncYZh)Af$Y5JqwhsQ^JVxCoFDLhBypgF&CGTd$t{3;jhL z?~8mW&=jxDXG94xzx0 zrP_c1jzYn}^3^3_s9t}L<};Igh3QG;o;no5V2O&0j2h~1qy%!!0osEYw*o)I%t`Og z9iV{JinHKHNkL8au5!8EqnhqNe??~*NN6}2 z-j2DJyYl+*gc`}LnDw_9N@Vt`JY=->CivxH&lOl_Te^^cPyr>?G(z zj}mz=7lwl=*LwA!01`D{TR!BT4pA%#rDx;ud=6{0(d)gBpO|YUM}{@td)0+Y^7csx z8nw;BN1PQtnKYi;#36EB=eJHa#x0AtAm*MWJrgCF^~JDd_}#~Nwi_?fX4h=Dgkz|F z+Wgww7IM2fxv@f~O{8cEL7U8Xv%WX6=~%(_BX-g8u}~M+d-~nr`p>6muuU!45|0Z|OCdNsy4jN4|drMSxx_ zTC{wUPgKJjfLE9DC4jim>oYucBk$gFz9|@P(+wxrQVij~X%tW+D>o zTQ^l0_ShT7KM}{VU}rPoGBMl_#~qQ9dc*PfC2sFSWec=7q)+@LrGGAQ*Hkr0L{r;1 zy7pXi#X*}Lf~&AGn>3l~`qa#<@w5>}Nvn$8mzT}~TkUxR& zhHZ2oy!;S1-bK&h{k5ZmXKJ~w9I$tRM;_BH0`4eS039Y7qvXk1?J9G9D zV!d8|NDDy&ci=X_S+^msc@bibs#kr<$*AZAz_N4>z;Ue<78Gzx zG_x>GXu7fh5ik7p3P$gs;Dd8=yOY{hQ*7BiQQKbZq28D)k>?K>)@cLkD+CA0(_#5@ zxD@vAbTGNIUf*ck+FdoCl4gnLLqoQyEue=@7E&)SeZwBC`utG*xc$B07x?%{0F_k( zG$)1k9q;IO0E7;+=nzd#Xxjxf>BjOOh5RA~UoD_x9G#q$t)v1R0aP6|PUiw(Zz$oX z0&UW^@wk(z7}Sy=scQM6U17dGGg}(*nJyhXLWa-#c9}#((i-kTLy+w5cVh)~z5(a{ zHQ2)jyz^~@5+?LPV26tfT~+{yn}GB%=>rz2h8cCZGKN2EZmh+@FNBX@YSKfp{HqXv z7>WR)cm}PUVFMT3@R1T^fGLHE8*i<9PRVyhh1qA53)Q$S&f*yRjOZA>wepBXCk2CY@4_hz9(1C zCShW+H{mLdqd|9teidtW)&AMa>J!ewZwqa%vF|;WrEkAa5Sdbc)ZBGHoH|Br!g+(3 z)PKgSP+Iz#z^*j2$I1LXrSbV>yrBj4dmS`nbo`j^brX9Y35BCZt5bO0Ba}k}Xh*Mg z-AX+Ue(14c`*ywiwwDfq@l6fYPoll_mzcboF;YG*;r3~#K5xqwPfYc@XFJdLtj$-+ zuN4gpcKlDsz2ahDMy02ksw|9m}9(4QWK4_twj4}7`xdtV8~k5Gsy1Hgb#(*^^{vuv&qkz+2q=zdVo0q!fMxR=nZ z+v25e6Mk@24hR!My+$OHY-GHY`0GM2P(mmk5Y~;5@j%sw0DRDjmxVc|DzO{|Ona4> zK%xY)4S|KJu@ssrUrAF!Y_j!8aYT*Pd>IvW#sCmp}b$xKw|3Fb&;xGt}3}VwS>A za-H)Y&p&vnKbwXa*@NO0+0%jB@C~X@&042?$yk{EAzDY6_l!POppu6v6he2N^*+=! zZrX>v4&LsA95_0No4|!>$awovA*>9bb;wlFZ{GX$Ai=|@6QzV72yQWGhMt3# zAYEtT;6$!%^4BLAFeBl&5D!NK+}gjwP`Gv3iEyInB^sNTc11JKfI`)emjE7Xz%xYl zCAC6LVq_u)Thk7iR25J<09`fC$nyY#9&{O5u%Ch>Knu(!*gl@uZ8r9`{%A&7iwK6h0FmA%Dsa%LP6o zx*8Pi`vyA!7cM>D2(2hv>KYS+^Pk%l2Oeu%~fm6O~9wjvi1{Q*3mNPhv8($e|^ zc4@bYKW&#`e@}Daz6? zp?@VJ{SmR8ct-p^vFUv>25+_5wNV_;1EY~550uaE6-MTQx z8aHoTR4B&s9#L-HYdBc^De%q*{+PyKs1p_i^ZFWJ{2j4Dej$s~Y@;De*Vxi{KNCkm zVc_ZUiF#P3Zh1T+XT8`Hi*Cbr57Q-7SNR!*!Il+GFC$+ zYQGROsH?mfP3;PXOS<=7q2nrJhilt2-eHya@J7nvH=K8@k7C@*MVv)w;iOA5yiQK1 z+P|SkOpYGTs&|?yLiO>*(K<({$5O+#++ny?gW@%bNOM(n27}+{?HEo)ZB5l9 zn#Uh|WG7A@a7j&8zh8){>>}N2Q_NBBnc*1E99P9`t=w=9;C(-Nqq=yX@ISw1TAbF6}Q6E(~dx1u2V!j6UX~@$GgOa@5X@$Z&W%tLs zOk9@M?3|nn7~;8Md&ov4Dms%TnyA362?9`1+w@_HAvN!Ihlk-eAp$4`+W_B~{5}q^ zaUrmY1?X2>knDxq=tKhvFhCtH0XzWJ8OHZ0C1?iNX8J_v9R|FbUbLFZJH4j>HZ#F6 z2tlJG^-r`Izv~tI*HLjB@~4-oOC{0U~4@15eUc6s8mPr$6PX zTunM?ZVKUV#;K> znW{Rb>Z2B*+yzMu7l3(_Rj{?;)N7``vxeC%L0C9QaNyf~W`i{T_a_(Jlm2Q_p1Tss z&q?4;cRJF90KpI>ph4~qkr5Hb)-^Y}H)q4^cz%nXq*O)I!d*fl$e!8)h~5S7;lsu> z$ad<_Pu2+L3a+|5Yi0J}Lx^SmDed(S%K#O`L(|jqCie^r4<~s8VH45}oZp2AfZlL< zbPnLC%a%=b&?_J@?W8~SfU)j9nve!Op)xP~&JH8|cB6C2dH86gr{~cmTF1%`$OjtwCsI%1A~J?-F4Z01OWgK9GQMfJ0b)I!l|s z5*__8b$k(&huD}V5a(8Ug`f(~bGZ4Jrw>8WBILU9YRnP{>)7>J)7fCGkRs+OfJchx ztbxs2aQYyXKgeByEBuftz>O-4C`q<-_=5{kQm1<9_4mV6ZaRwntv@A5KiQhn+_9}^ z#=d7Bb#9wb#oqc;yEwt%l;`q`6js`&ziGCfI`_5Mm4*f;>ljtpdkGc9ZqgF|cWAQqXL~5Q+Z`-q3o3q2zuh5|^ zq(4pKYwJ7uI3G_yEN8i*UL~f~SA~7u;(ThlzHGnNaz{v$68D8KMY;`&-Zjs_&=)!q zJ&w(Rjn8E4d(Vt07UDC4R?j|qUnppuc6}Mt`n~^Qfgr=^k1;!esW%0eUJ* zqEBaiyoVc@L=HNQ$w}i#FjaVGzV1@Uruhy7VIJ)NtB?3^z}A6O_s@g)SwUWlkQ*Ss zO+Qbn`M0#5u)`Su!Ru3%ean~Akx~<$ZR>C&n8`rW^3#^r$vq7pq1gr?2vO5gWFxWC ztuE?W&A|RaB=`j@$vq}V^MQnjGx!ZcW9|4TLs58>+p4UjRWN z!Yvi3Ut65}$7y>WeHH_>r2A(n6evfqX=y5C1w43O1Jw|0%OC~Jeyp(p^GyU#2Mz!v z0C26#M93bk=ENYliIk@J?rsHi=CB}<280u9Tgi}X{ZiY@y87K-PuPMGd?Sb$)33SN zEJFdHiYTBj8GxNyz+UagkD9bfj!$VfL5~dwMIcmv+G4q|ASSA;(+2zxTZ2E`a@Q^$ ze}G%}&~T&pdGv*)cA zk%#&mk&S^T3}ZKasV08(;wx2OylFj=`0OeXvnm-NF|@;~`%v{6KD%a>6&0YZN7ACF zj_|;cdDC7!A~>c9_fB?7CaT=q|b1 zc1&LOq4QP}6cl`VaoHWFz~LY^HUQ*Wpe-43?*UC7`lfXBd}#Vhh5PsApuk1~1>t_P z519qI`v}(+y=?76lLYY~r`25juidS!k|_#`3SUghHu@(V5i?aS_B6Aen_|GnP>3C0 z2ACTM^1&mTb6(t-wb<#aTc|>|*jFl8gFlZWGN_}+3mz!JC73F>6_Jj z!Ki7#RU~?G3))U&IDrJ#(%`0nk{uc46u1}-oFk<&d}!>9hs&cyy3~M(lDU=gytkJK zaGRW`i5k!iZK;~M72Z~U+uO45%em~sKmFqa)zc@W+FBGVPbgYeQ}0_X@f81J%2-KG zdeeG~5+9oCkDiCz)C?TgO`fi8yLw%T*1O-57;^W``@*iG%l6Fx)2Mui-1d)qKaw@H zT zgDD$aw^3N4`z9Aq#fiI54eg%j0DH$#TV7(lmQ4Sq*1%iP@(wv$0yI@+pCvEO-Kmv zJmjRf$=hNJ&bFKq4W@?1?axY8Mn^b%39qIFXV3Kt!H)@{I4(Xe|53wnRXfBfk$~n< zsomIT$X4)rgkG`M1G=>2DEHK*Lu!;)sYC`&exdst{*~#R!;-W7PlFZ3HTTh*^PXm{ z>apfEn_AikZg3l|Q$Fs??k{Y?SZ(5uc1NQod4yF8Cs@s9N>p-6bs5_Jy>i~1$tM^( zZz}%qNGoHp546ic$6_2>qd3}27?g6MVoB-7E8UiJ^*dO+;ol3z zYSpeZ$a$A66Ze1j@1yyU(e;(=Rsz^`{CS|1XQw>qn3Lk|L{hiz3SwTc(Uxz<*~d1^IelgRB;LLx9_zNDMYF0xc}#R!b))hkOD z<8E)?@x;D#354Aaz4MP1bD0kvGX!tnz6}c~=+ks#5IO^2fnP-EHSP4w#Gq z=z@-pj;udO`v5T)F=#jtx+lN~X3kp-khB90Hys@vqzz!><8vx2;5FzWhHgOq0Hz5g zJ{F|ku4rchMGb;wT6RV8u`t{+LW+xvKV#xYe@A*_aF>H&J>WNRrf{;T(fF^pBW`eu zLQu$TiN*l5F`d4-Y?=h1b#FcMXb!wMAh}FH&joE(fE4JLf}jLdV19xAwfNEOTmBny zFg}HtCNNbxI4o0sv5C0m!B4{`16p}d`#F1fzl2fUDZeW)?bOBagd(zS>cU^NR*mya z?bg5k^$CuyErE5~ldrbC^sN1JyqV=s(+5{&-oAb7zj7=+ zM*9pRkK2K}10INhU>OHU6etyLI|o3LWKMt1C(w+40*8ppKPS~jhvGHPXxlRcbl!dT zUld}|3yX;{L+^H-bq{er@*+NMy+)p~0-rt@ZWZ{m!u1C*YLcw&dj~+`sdkJ_I}X9= zFwF&z8-TQ_0pD>ED?o^$kymcvf@%x#V9^h=uK>;}7^Rem;$dU6sU0_(9~{;1oXGub zN;%bm+oE4ZbJp+h((M z3Aa6TJYKi^NAw)+KEWlmrF5Imq1E8RWaxBMnEz%p{anUNmyy_>b6xvc5<#PlszL|N z8zX{0b#cL@f{*MOrNZQ?Zliw-(`?sr{zn%3W`0=7~46Rh+)pC&wdJdU>~RCgGWrD4@Vr)|E?tI^6k95Owheg#EuW=oz2^;jJKw9OX8g~E{XDIPPzFUa#PsnQT^W247M2%=>EwP=Grr|}vE>5a~LeJAI zF}!<*J3N9?di)CQ7gAO~iIL{2s7J~8ZheKJ(nd3HniK9Ous7fe}x zFs_HMoq)rQK+{6SilscHDjih18rC0#b}UH0)n$oiWMFtLyPU7Y!lCLpFEp6t=~~er z&OdZt|IQOq9w_3(1i!t1_l^}N^!l@|9VA}9%hn0U$(@6U&l0F9ZlD&MUtaH*R#6$X z)=*JJP)pAYkpkK>K{5+P&x7f}9D((2#~rzfwuK4~ptXUrzqq~X!e4M=n@~BS+^_20 zjELM56*AIhtU&*ulovfF=2I0v@HBkn&P3hE=A`=hE?+^cbwE`T03)tuJlvt(0A80b z(jg5B-rru}cG*9$w8dkRv4&u77&QGmhD>RbeEIATzvI~q%c!7iS zA3u&h`B--|kzz;3D4sgzFpL^V&Dj$_qC`;@Yna%ruSF+CdM(;S@ z(>m4QFIekS5!_Tqmg!XJXY~OM#u_NppP9QM>O(T@0=P2rw)O4x-)~2>e$O8!{CS0_k)W>tWaITPWzacDL|*gg;K1K4Tw{PFIK;Qb zV~-8YTu_uUwd9YFgcg>X>*+rNV?pi16LN4RGM$Dg8k|z8D^H+Sg)bg;CMv^0183U7 z#@D{8#G5=LqC<=E55=Xx^AoZghu;lM&Io^A;ejFjTZef47dpWm`*>NkG9&2-q1Q&SBBWQALTd0Hrzd{O z8j@jPC3h?=l%<(p%Wy5X8E_o0mfyr_f2hhAf<^8{M716rF13M87`ynpK}m{I5w+!* zEX_?Wm6~%g?WEnCBj|wFCSojCUh`*Pdl`jV@mCjr0KQi*MuYw)VvFZsgzmQ zmt5~XB*K&;!c=($JqRa48C811c zyAY`t;(7!%2O^(?S{*DAv7l3gPB26@-{iEbv$Jt;R!iRmdf5=@eQ>K_Fpw{sTL}#w zf@^s9OA}qAlou7cOaa9O@OE%A#8A8r-@8{9(4rVZvjaU)vT?>6OA0wR=2h-I$j}Q% zNir?J-irNZ=n*~OJ$glqzsz~dwR+>_ZE?j8OtG0qh66QqR7#?qXh-#y%s(}@h-tbo zj)RLUlDAN|DfU0$6Eq*GFY#_BYoj8it7@9s@mhb9vp)NP28FX*(%eHCZ*<8r)rboh zZ5J`hdDE6Na%5>jCIs(JlQnIlUhvUqq zZ})oUg$&fnm7O99p5Mvz)ylht#msRl2tp;s%}Cn+d{vMu9=B>T|8qF6)y396v|odZ z(sBNaIEF`>TT*QBr5G9PSel{9dz+%uGW4Tr9Im}BVq^RZqx1H(-=ysXs9qeUa) zi>{aX;ON~#o!4hPE4SLmqFdQ`N}=6oKJc@orz(@J@?+{=|DsN&$J;ISMWT?RMTPU* zcT@h9yX;Nna4wq;Jh>w8ZyCJ4>CR5}bCf*&5zy-y3|z8D+U;oo zi@TVrbmxLB(KVvoQWeWZ?2h3(-w6_F!1oe{KFZ&S+V5fEbcZr^eU-y6?OsKTyjU{d z%*DB(eS64wL`_(9u;0_Kw;3btk54GfYCoJe?(SW!T3_J~f=^?An1Q1SsE#!w9Y&TNi`mwDW$c`VNKVnt<1dZ@&l}mT$A#B6d@J=sPLNt z=XIUUYA)Ncd#N>UgTJSi$ZgiAB+}iV&lV?^dHh-w9NEFAUbE`&>wR9+{Rz|sS$s*- zB6J4d9DlXM{dTOkPQ;$-_qBanE^ImY%jA~LwfvU0)wd5=h#6}OS$EmpxcK^Vzpmkn zdhkeuz%c^@93Qt!3BBYCNetN8LZEYP0j)n0%?YmU%&j^B452GhVCOhhWgP*vDsYr= zVX%j!oSWjSuss9t2LNwKnrdjjf0x8W19O{w0VFuE^bvn=pu8ht1V6`%A5zIo1Qir; zKs34^;s^}h4zLN)fDRRj&HV8r4BRJPL*4)kuoJYdy#ipy7-XRH?sNd-fXTmfm=bGP z7k2h{BXNNzp!renA;@Ml`q*GzN-?yPu_0-Ap!=p zc!!B8WxH3z*Jvyro4?5V^6l&We^vy~$8 zj7yf*_0cE=ohQJdo7OeI`|nY)l39=?%rO4Qb&i&uBVS3Gu;|R7p5S}0o+YY9EQ+@>=9j&D)FqXHXU#~Zp00(c-k7QtWL$=lOwX1OnZj=+ir&uHDCAVtNfBG zT^=b5SyOs#x_`mL4r3a>-nXLjETmZ~a{9krCfU%ZFDE~LlBH+G=vv{3{Xm%Sl_;sLSi^%P0gmhgf(R}RSq+Tz4tny|eLFTy^t-ez+o6xz>6N1JG z*K*Ar7H!Ms-x$@HM#I6xjX*tSE8j2FRk7T2dzD{GB*S&vi&`)~1E+gu_v1D42%26A z`MgJicu+&L%%2+lxnD@9Lo1~>q_=mep3kHxKOwMQM^kgMTT&3P@G^@-RLMyUritaH zhtm#xWOO$}C5hS{JuKoB*j`b?$#!jcco=+^b0ZmENrN52-Sy|pQZ8~t#Z0(CtxRgS zq{ymhqobnU_Pa@ezpfOxF`L497_{sj69w)H2ptwTfI%jB#f?ake{)2 zZu^Iup<>|Nnbo`XQqNy=#Sk=zM=!6EdJk=$qGsYDka{V3* zEp{)l_G3^O?3LVl?LYik*-7i+y6_<-DmJA^YW=+sLRi5iq#9&3Sy&w-lRCPKLFje1 zM!T0s9r?^ulxFavz97!5jVc9l-%+uNIXq5meZDqtbN;*+T3z3&U)o^m4EG<~V!L_~ zs3->0`W!1!@Z6$Nwh<4mUg>$RaD_G|OuNWH%ZeP;+>B*fvbc8@t{d-G^LJPV&!Lx(f%=evqvdqEG+naisO zvPLI2HkI#l9Jh=Qtm0NWTz3r>GS)J860PMCi-420ZAdp9-sIxZzS{R>jNo_D;&jbN z1Ifh1t5_I>N^4~P3_MgPO?p?>&RyyKRQ$%ge`vuY7ITF$<6~lB>+DBn3M^vO71^U@ z>fkYA=mPij=q}oe52M4e_-IN)@)s|U8qZ?ObN|bm9K%a#H&%&gL&26-$a$JSGgy!v zFFz$vw!cN&W{>+KDuQ6HGk^LAJY3X2*tw#0?@85bO~@}E-zYs(V-g<6mEPy43&nNm zf}ZYa^4Fvcxvo`7O!pqUpAWFkKfXkt4RinODo0~=JHtJD6u0Qwf?wSIzAG2C(Z8mQ zb72tT>v+?pKf}tdZ@qM=TE0EK#H!FUf;(6($c>XxL~=8tjN2%&PnKXQ^Qs+h_{Ej= zn2QE`FUEaMX{OXQUfbNZZD-BbOuxm)oQuC3E>xzKC+RkUT#KUBi19Y>*rG4rlJOML zW1NA5bys#m{jb{o?+X9l|KC~@>levJ0b5;Chk8p2J-5{UCTJApP144?3WCaByT>IZbw5f!1K`vWu-Sf^*Y!3`_!R6y2MKNJ@sI5{88M?w;eBh z5`Wcd+!qfhDyj^$`#wU-^6~iWF76YxgX${}ZV1*4S#j6);#GE{8vMK9NiysexEzN4 zSr|_y2kwl2)fkzO9Um3S?iL@O#4E2*m{tYIwi~Ks_EMgHG5D}jUPXUZ??wRSd>Tpv*Tdwr#s6*SUo#zSOMu<3elQ3AlpKdv^pDci_HiOe0cZ`?YGAyIpw%C`C~(@nO>qjQOw2Lj=Vzyzo|Pcz?7fss5+&@S|4+BgT4?o5asoMw(c z+FsdLOB+|Sz}NKIqk9(OepOU5y7=UAr{CC0Dr=!rB?0wAakjF0_ZO!6vw-&6&-L~f zBo(J&-s%(I4K|;Tn#J8+c=`f&mvBZ}=}qxi?Jx6li@j*YwI`3ayWU>ByY9mtxrFxH zfsv|72fQt-wG!w_BVYbJU0u%7JW>19iy`E~Mc!4UmD`|NA~O3-HHq#iQq}*TKRk>~ zz3AB=v3HY=4?ha8s*7OrE6(a!-320!#|n1;hWhZ&O%Dvhpb)ZM5A`x^DKjSa30jHB z&^u!zL!u#Jay06fih@HW1*-;Ta-yqs0>Z3&4WT?ud8ho6qd|wR=g>L6%(z3q<#%BO zuh+`dB96;a*3hG3oeLfJxLcr#N^QP~g0n6IXRY78*!i{;c0K(AaRa(YcB~r1X^~sR zx?ev3bQVJ0XFoB5Ly#hC>!sRW$NQEsWya=#n~{@;HVmYy%VH@fk3YxT!Ey5-uJ zR9XFcxVJat_0_BHgG6bfseO?tab(71yZZUg6!(I=kA)(;PGAcvu*I*=HEPzhwXN-5 z!gdF1K5^z7=@+UPAEHC*A}HT_>kvKeO%LMfzyJPjh zSEwdE+-gHrYAQk=+b7(ndPZ9$-YcEbM6<2Pp0Mjm`n*?3{n_>$#x)Zof?F~TJ7RMM z!RkYcBN*3aOpNkwdQiI2-I5ly4ma;t0fy&dDQ@bdFr{1boNoAgQ_&srJUA#8Ncfe% zXheJFKg0B>W$2$f&SH*Fd@^%aossF&C8Tif*N-mqDdJH(hAlF#=nGTxqu`4cw?pwA z%C(C7Yj$|?Ncb+MXM|*|5R8NQX9IxO{Hza2VhZJBEjyNbU zTbxhJ1V0HmGC_byJ6_zeb+IHi#(nWw%E6mpw#*cV%{1J1HpBkojOPE`yHJh96*1cm z`x$N2p8C0yyOp4*u0V>l;9QdD{Y4Pm`HxNA#~3``2Im=NHkE===*o z(glLeTcaGAgK%yC$CmQFZT;8Re|?nyUK9Vm(r$9Vl=#;_sev{D{H8>J?1R|W zz(Bd%q>Lm^07Plv3V;CiV08;-Pzj$0y?>5ghhQ3Nu+*Xzad}=>Cqjks-+zOuPOz?1kp%MUa zE=TnMk^z~s4FW9~;(I=UX*3Y@h7tj8mHwa(Y(H@Cx&nT83D^JiLH_QIAwQPb3HkTufhS3NYNmxp3CnPiEuLwx!E7qDVj0LW^dmS$& z3pp<1gL5K6>0#Vq{^zUx<3qg)3@!8f*9Rnaj4|SQX?7K$U>LzbZl1Cf2@e;@IL zqyGD?U36!-`p;K$^Z$oq^!0yz>tEZL9WFeI|MY{r$3l zIszPdPa8bH86oKU7m}u>$w{#u4}M0#5QUH^24G{*bd~O{Xb^Zb(DA2o4tYBZ(*jQk zyz&gvuKVup+{slh00B-Y#5h6w2oB&3Y;0_E_21m-KZ5 zE-nJhB#IfwX|G;I0`ehKFb1;btKjht9)m9Kj{K7eHDW;8g#P&MJXJ4o{}5N=U1Tr? z@`-{2t6#J5Z~~|K8;G)jZv@6B%`g!ws(n3dZg;q$Yhr4;qzzeZCKAi0;5`xvWC2O= zY6GiM3U?bl0T6qG@lR<%#*+A#mt0x}8sMk#Ra;L0lslNmCJ%K9A&sYi4pMU%uU}VG zo`rWr@naY+x)1?Ypp?O8u0ub^*?-+h*_JF7jejgCJ3sKxH2@j|$`4o=S7By{I0sJ@ z1NG?$+$sSJ;02d-n%fTTMwvjpTLA3__#lUL*un%o+-5DqWia2Hto@L7y717{sx4HJfnh~m&@uJIBIEYj$Zff}kA)-N9< zt`wLW@KPe|S1`ng`)UbFk;07v)qI5h0#i8Ls;1pR5i9qx_mA_m%5xcf%gf74ttR;k zb?d$?yn^6z#8eX<1B1(F(VY&iW;?WYaIHCg=pjW0C?VLTnpV{VKb(XS7B_g0$^!AE z#OAAT#NrT`Ndux$R)3QfNFt;xN|L~zZiUp2qNV*`-%MdZ>YCnt~9BEJkW2T&kL zEgd^O|5;G1Ty3LUw|Nb+o&d-&thx-|@!%U>asn1n;Xv~#^{fH26woT#R9YeY285Le z3q!q_{PN`xB=h7a5HU*;A|Yfk85mN7ep?nOIcTVVj3+;2{6XxE5eB_FAqcFl9i745 z(5x_z8>^)QM|${O3=nOxPz~IEx#sRR+X=#Wkm16A{$c!&wanK%RV5ilmQ_mf;~PuasXdx@KQi_D)etkFf;&O zX9OTQ1zi?@v+H2K+y-Qth+Eb`%|tAB2OQV-J;3mp4Dm+-GUNsH3K;pK?hFq?OvyIz zQE+6{fl_MJhp_kH5&95*2MD2E?Z%9Zj9`c4kf$`zK!@fBAoc>eB^cQvF*dCrySi{_ z29FuA!bRKe1BZ_c(ZV(!FH_ewu&364Li2-pZO0}6R-Hf!vZDKQv~BFQH!LsYeR6Zw z_m4MTT#_bdFnG`aAZnel2qY}TFCDTY^cYIXQOFqqH$pnNI@bPA7s`HaG4>J-4O$EPglWu+I+vZYKqgc$}r;3`6(ex=X%TOfY~_S%Cb{u z2IK-Dyu;N%L!Rlocf$u$J5kp+2-ZraSGxKoHYtrDSfJ}$$xWYZ|3LFHgu07+g+lqJ3gNk+|qM-3rFfQ7Ih zTnh|ThCU85zr9Zv~cfk`y5 z!_CZ$giQIvvEfGFcON*&Fbu%~f2QBCzacEKx)6ACIk3#E=3oMb({2ohvot(};1O;Q zp2k)aC1@x+s2vJ*YDRKHDrex#ND5hAxYoQnHlQO)0nYy5&vX^+{}Ev(s9Qqt;O?Oi zr(=+CYlQ~vc{Fs0Rf97!GJbl_B2l()-o0Cd&Ftx+4O6xCgr)Wug$EBhK`GMl1vEi?V3P=H z8!S+wwt}T?iYuRE8WI$O@Q9BQWe?O9i1{NZuZ{Fp5pxNEhg4+SZVFg75(^3TWdA6pBnG&=u3zGcS*-lT>Bmr+MxXTQBVeD#&6JBgo) z)eaCP5my$gOxVB?y-$CH`m<;3q!#HQkg`79+Cr$6z;?WZLY`ERY+v2#y$P8c{wr^@ z?@|x#B2>u)H)Y1L>faP0YcQSht&krrF`IkYm@t@7Bzvs1#t|((SqtEWpx>3 zjTBv1@UajXjh{+nf=J}M4!l^|05C+r;0(woxj@|l>(i$-i2st5x#12GN zf|>`Cj*=vjHfGy&mh^QTP8Agpvb$ITr*jx$#lYyE#BbOWlw=L-6h>N5b>OxegV-!Mm}UUBs_hmH+Yv_X)BB*~u0FLDNf9~Yd9fgT#*Weuk0Cx@Fc z@Gihq_yV%Yf~_ic#!4w1gH^V$ahE{)k^jXa@UM9d4@W;m`T*>LsUM3#BbOrV%uYHO z6coe=TtIrCBSy%}!G($&A)P`99*AK~X4_5pQ=92H)bjj5>`IG>rAeR6LY)sRK$F%{ zxO0fxSZv=Ch{yu&7eD*2V(7VXC^R|?2q$cLJw`spbmfynOOv_b!8kDI`X}jQ+MNdQ zJbg7t%6YxVFDcDAWBBUa2yORf+&s!3Alj!gHz9N>)B|iJM|P${vmYcLKo~V zA?|D*i&27wxh)h=J|uA!jQtcn{54*jIi`$Idri>uzz&{wQx~c_vr|YGlVYH|!9yCq zatrPPh~aOW_<|dUX~s_oaRec-A*0`d5W!~SDL3T8O*j+MiJI?wL2Uw5a`1*`+>)+> zE!Jyy=;Z($gqrYD!;RjaRbn6zNS~ge)>Kl89J2(?_HtS@Xw6v2ZgkCf&cV*aWi_CO zOr?Rb%)gzIZ>6-)&%)A~&5aI@Y_A#g3BmFrV+Gj_Kzo*A{r^z*-tk!XfB*kML=-X` zQdE+ZRaPXcsi-899U4ZGY@w7y-H>!dlORj7V7tSb)KK=y3X^uzJL5~ zxAXQHojDxG`*@A#cs%aU-_Xd$-?lPue6s;V_P$PC--)mmu>q|CyU$rhCqclpZNF=1 zS|Xfaz4e0juHq3JwL8&b8=zDL{LlmD$w>4MYJXOn@*T(33BZ$M`M(ln_SIkA?+R$b zA9_>H+TY?=c>I^1v8ZiQ&VwtK?-97^zUoN9m05e?t?O^-Niw`YdHO7ByE;ORrvdjw zu~=k`LPy$@x9G~Bx~g3i-07IdIW#pDk^j`Mw{e)OZNpFDwWu5xKf7%r^33%^O2EzE zTa`i*puc~ibdAzxQoJI=_{=GEEj^ZfOz|nc*r$xQCwl{-p#^Vz`Mrd(hYgB-h^E>K zZlKOSMVfNheT<{zYQ*7U99|0PY>8|z$LSVy;6nCq!4ZIFezHRc4oM?ieeZthT@_oo z{H97wE^eK()2jWWqy7m0ji4J)kE@q*ni0ZK@+wz(s7Z82Fo3a|#o6yOa6@sey1Kd~ z(TO_wp;R16FtR<$-RB`h3cgbkp(NmJ8Lo%`)9{o(&JvbXMjJeEklbmvtNk_GDHs#B zC@&6Gu0yDq9k`#HDQ63#j5K$i; zi8{@Ev7fpe@_3`qFAtYjRIq*tz$M!n{|aWnbb6CPfqvUOL}L&IYg!z@I3(aw!6L^$ zOVx@I$ZD;xWQBC^I{gQ1ovfQXQize-GjB11N3qB3GYCLG(Tco zZF7Go%(o#y@fb;ycDb?UP4CH5r+j5Czz{Xh#wT>}sl!lp5P$9?DaDYA^QNX}Z>eIS zAcl_!7R5aEac*!HY|@ntWt%_Ab3#FW8~UbXuS>j<`>J2&7c5CD`0cE!DJV za215;4q&t{25*x%v~>o8q`nZV?(S~Xa?)Ftvj3n9|k4gT%c z{?R`*Hpby#Jo^hi_4c;5_>NTHqa7}BZG44`S=R<1D)nKH z5d#9I2sKAe!n?|)y?%@!D(G_|9YY?>=yKdfr$V^4%*Z-Fd^zJ0OkGg2PzJ3CTn%?j zX=LTvbuQ^buO^0IA)zL-M6|N5MAj!o|MRJ`D1SoK_gqWLPWYyGu`+kkr7L^pa*on2 zlyBj_Ey<)1tmr4ba4&#;IgixULDVQ$zEpMg91A?UdhJ)!$yv-Gos%wQ)Bo*wr}+b% zm%dxlqEovm@#{41F@#|6CHW#&6tz->Iqcn?jjr@Jiwx?IKGwK2q=UKuDnjfIE$79w zBmH;A1nu-z_JD04`5hh7UoxBpx|lH_JyKnUrfW3UOgXx^&)7ks3*!9oCsI_<&4F86 zQdwCU`>jU9GI~MCeC`Mw3&=Klp?tVLY~{M)axu9+aj}&5Whhy2eW^Ts=#W?aLPn!0 z20Zey`0qiA1LL+5dekUnV5EBzhFPjc>}!8LbA}~;#U5!tB_t_Y&AAIBIb>`PBV&k@ z{we!oe+jtVXA7&Us+=v-pUs4B?C-vaq{1KUSyUbeLNHf-$Ji9AHhT!@9qLSCmct}5 zeej^%d7TYMQ}gJu8OG zuefk>%r5Bp)r-tDjxF-5nN(O|DY&lCKX>~&Q z9j|4Kn;9r*ja-I`E#h1_W5{A+UG%8igVrw$$`yrnW5WlGvnLo)c?pC*fm{?R(~~ zoi{T*t%B6nqCj76RnA~f4>dLzw!*h-GNzp%9^loNLrCV1Jx5YqfEMSFr9ck5ILVBp zi(#nU?_Zbam6hc3^l%8yzjIN#iz>`xJ5%B1zPtyIUQ@lNigFJ3HaU4(W=h+YN#^E> zsV}=Lm?8S1xyg}tA|_(TzR`8wEc%rzzCaF@I@4aNFZtO#I{SDqm#J+W{p+USlRSm3 zolAGZ4hv@Gc#YS6JF8_q#B)8|wKO;G;?(j-)74JJ5>UK!rE}eXG+)(Qi00&{=+Nio zmApWSnB`b?eO~sX`+?%pxpC_|3|{nUat;@}98{MEWsbL{h1(Gm_}N$VfmSNC@2PgN zKB#vZ$`oTl!xpl@>vv;K_Ut77q3-vUU4~N^W=s+VD22&!+k=w_K(;r$#SE!LPvygu(veUYgtDADYku1{w zkoG?x#mUEnCM3tv8#y-_XLnY{qSKr|a%OFT?%|@Il1$oSakj)ePR5#>KSBb0=H-XXCY7C?8;M;K1*O#}Z6b=}L;HpD z5%f&fzGSansa#y)a{!e@e!4&$U$9_Qe9tE-dir!=WmqI`BkO0qw@Q0RF!* z>MV%nkUL_Fs=p3*xsO|e1Nr~Q9;xtw4NL*r8i-RucHUSR&eT-WrQVO21X?*piv4hU z<5;z1=O$#%i2PvlAu~WxV?3YkIw@-Hv=- zRK*v~9s#B*Ies~Sao+DuREWzQHn{{<>yS$()6Ctto0jP}o37`%(=qQy2OPeK4X+Y> z!9%k=B5^xDZ_}8osm#Hl*hvQ;{24GsciXo-aXZ<+M(MNtTc6}_V-`aqZ2p+G_r&h; zkuyJt)y&=ATqE_P(Q1F)!&Qm}kF6!jjH6}O&oAAOn$u6Ib|FHt?L5oe*d*&%as+E) zV?Knybtd*Dkk_M>&(Ax03twx18aNK7hNlxoXA9O}48GA7cx?Sb?^!|9;XOXpBT?sD z+n6c#Jcp`3uNd+gpnBXNB1u1ZYC&|Yuzb~TZ4QFLD;2u->*q=&^O+&F(BK*OpW=NY z?bQ2lY{Wk(0iR@llqm6mEzZpI>C-fo#IBG0B26W?TczJN%K zdQ%C|*P zs8DBMquQdIg+3J#WtuNwIL$a}-@ccLpE;JYlEqORUj7oaSA~X*Fho={FNn;k2W;D+ zg@dw)!f%$#f&^ugkkCpvU8p0Q39N2UsTs;~nDIw5Z?Nj}`|sCe;5*B&>!;H+k@@5`WYD-hCUacFA~t=MDGro2<=qqJAS%A%9&6p6xxQlY2ah$X6uNnp?q~X*;5hGz(^9EXZ-2w7 zA{M%?o7cO`dIk?C>@g_ruN0cvW-w(`k04{EtUqMOU62J*4kT(bGPMyNOYx$G8qJY1 z?zdkU6d_}2JNDsSO`Z24%}dRBu1QORfl~?RvTh%KRM6-i;$)eU%Wl2@g-@U!%QXjq zx94fc?(=il5S1IGDp^4f@9nw4pJcP~FMbtF3LY(ndH|wafryyxy61f5>Yg9e2w3jK zX&D?m5LUC$Vq2X1FOjL{h#R!1UV?qcSFKTi>@pffd{Rm! z9lZ3j_mBu0&y97HC69*pm7dZS&Nvj!e?#s4b?vTCAq1%eXf@&&_p=EHP%R#7(6NdH z!UidJF@Ybk94|u&o1k{`_0ZW{&B|IV%MleF{Ty9gi4vjyJK5?c|D;yo_}=?S60=0t z0D_eN`P$FXp(ZtSjo)-hZuargut&bSpoD8K#cCe(xOc*AwUkAl$w^au> zp;MkdI6veCMsHAbTMerZsyd{BdoMCs@X>I=B7l7O({A7jO6TolN32YaQM z0P`t9EXP3kzcD?*^eaRdn56w~Dn=UUC81q`SnAHjx@dB$96EBO7-2biRz=e@qn*rX z4rB16?5v{@tG5UkVkEk!+%<|tU&sLWC_+6Ukflkr^jy}JqNX#CKhRKDBTVYoRsAiK8%~Lc|YF7&UHPY|=86>j?oDr9V21ie&lfWci0)X2G8m3d|M(tHQs5 z-A#%;=D#u78q=y@pdW1BimaIMZ&kx(L%at`@(ub*sv`?Gw-EiEOCoC)((xP6dO3hl z(-56C=ea8wFTEeoK9piq=YRwdcf9bwb~%aN{s+0pA*ze&iCTH-{4LcNqCQf}G;ewN zKXX;@SlC>CG;q{GnquRmsrWR>EI@BKmagml4IZQEjqkk6zRx@yZN zovU*aIj-9BXGjX{Icv_+eG|_rX)IROZRD*)Ok6;<*lrRVl&7t zA!BA3o&~~NdRadDf33BlgH6;tC#G!l0)46_@RBKf!e7Z4_+`s>=) zBIqBant)hCBZF$PGCJmwnsFN{BL1S!P?3CxVt`gMAT$wWt_Tf}1`4?=_ssnuI4=>G zpVyG}K5cJA_MI@Y7fp$&@AZ{NIcU7<{jNIt9XuQh$BeUYuWk3Zd2{%{IM^wj0@R-#!Ojfpq&Q`x7)E@aH3qK*t z@Qkh^?L`zUOIy5z^XI4$oSn#@5eWDof=-hrGhzf}?ZI6lh-uN9l!L5*jFf?aqkZFB`c2%xf^WUpsfYje{i+$ zDE>+r0g?rdsY|(dU=u}cr#RmCjdrJqECAX@dZwEX+)a9006hxbMrSvmF?b*>DhtiP z3CNAuXOptntQ)%1r2g-l#?jy6CbIiHiYyokbM70c?QghEYo2(cFUR;<2SgQ=Iq_w} z+IbyjdySY)^tIb@GDDD6^+cKt(UqpZ_q}^%0J8vz-M>3*2qj;Vb)b!!Y5^gRc{b7o zvgSG*1~$ZXD^EMhphC||R0QYOb4L^nC>7Kfy zLW3kXz~M|qwdcYS;%3?Fe^DQSP)DJTmj_IRS&v`7%Q)pNWi5VhZFeYdJxjkZ9kW5< zsoIlnC%VL69aQoCL7<>$%287wi~sm-mC(ygl=wAT9IM?ekJp}>%oSC$gJ!k^n%Upi z79^MAnx+^4Tm5J;xc0(yu}k&n>qnW461k_BWkk0(@Te6sZ`-)eV%L7Z$2Vh(!Uwyg z7kaJdJ_k^D+QDRS*NVe*&&D?0Qh0{~J=F#6r0ba`jXFxIG6!sP9UMF1`N#|R8Lb*F z&E-~Y`YD&`a?DbP$0BwGtzWi(x@KTTjg_mu3F-;J(3PhE<#gqNLHXOsD&wc=*I@e; z1Kog*f)<+M7FKNz&s^0gr-W(U032bF*(_SOns7 zB=MM^WR@va#e*IznJ7+0LGR+$K9`@lA|^qAK0=-`Cd-Uz<8E$lvMRP2$Sn?_tV`Mh zSk@%Rmr${UhGF~m?P!FcHG3;DQ8+g*kMWuungh@f0ZoDaGB^!I?d{TrZ-5TQ??u1H z166pUimR=yeIr8#iQqS(mbAj})8Mr%yHOs>CX9=Wwja$dU^6l01NZ<6PE6Jus_k%g z`or5uC=4VV(PZO;s2KHMT#xy6EZ)k0=?)2LBKFn4cpWvFZK{Jc<>hV~f?#;S$j-R9 zxOiwzve{c%)5xMKPtWmj`{Wne_ zziC3|Sd>Y(9S{i`Tli! zf!+|wg1<=yZ+bs74l3M3{Y^lo)X3UhaXIHU+}XUm@5Z`Rcw0o*tR(fG98Hk<^kY&c z(PSC>uA#MFq`NeiIZeVm&ze~oTx@HTjcVVxVx68JP zsavmJy(*ax4037W!`B{}mXnpmUct|Bs^ee94dqWtq(5N)Fv?T#AhVx1!~Lth-Fye~ zCOTrybgXY~nOIXWFZM?MB$bF`+1vZ~y&d~J!&xCz=e$(AZWn80);snJVc0a`lG5U^ z3;DR--b;mhrG)Z%ZnsdA$?&0WszYT@Jn6rGyzzkm7Humzgl_faJu=VW?ipZkGC2lq z`wK~`@H?OcY^rb64ILOA5la?%!JvQ`E6z~j*xie(Z$o6CJm#}dyDf6`M^u;j>!5<1 zm2EaL!m|F(w0CLyD(c1_)2h6XR9{BK(z13SpOD>v-Xmm&mM!!=zkLc)#(k6X zKMcC=i7F>NfW2NH_Ik;vKlb{`|EIk^qojv7A^afRQo;3ke2zkU8SnYw@SMpHYZ6O| zZV%6X`1tHfT|27f9WfuyO2@1#UiS6+h96kr;&N;GckFTa6(03zuk*ddTb5T}(=T-j z+(Q#gR7W7`mH1x8sc*j0rB)|SZmVK^|0bFu;v?1iKOF5AuZg3*Dr}u7BBP${tC-tE zrW+eApoysWQ$1nks|uv5LlaW}+_mHZ88RZNQ1L5Qjd}5DEj)7XW4XlK4Sl3Jk9^|1 zbdP6kv&>xUm={OHOC!ZCe#@NOw@TSb-t>MiMTxMxr@X|l&!g8~s&?U&y&JdhPtE0Y z2F`I;gJ&yuQV#I8{-HxF(dN`_=Co$9=3Xhw?6>SZ>w@P=)L8r7RUf~2U;SRtH|bIB z#22@1+Uo-4_b3YG#RF4k$kP&UJ?Q>$=fc~=lT)k%^l(rV%Iy?RKj2Syu^9#)e0SQL z3=Z>f$=?v}eH?p7Ox)jjWLJ-@lB=N2l={V~F;GxW3qC(UL z;8^ASmUCHFPs)i&tVNSCX~(0eY=Mfx^>QhFoZVYZF3}WAp8Bc)$_#vJe|+u@+-ij+ zbHrc!2At^ z(j%drVyU*MpQu-~mK9(Bl@<8&NPP*6nJn_viI=^#z0^+XOdGutAN!WK$eohD+)Ju! zp?i#%M)lBm^v6mOjzCEYA>A;6DD;Bz&{Bj1M)1w&34$hnI1&Yj8R3+Wg%G;OSrjna|T)2l)R;$ zS*ys$E&RIHznbY+ka_abK&rpB*uHxSnK46FyyxGBJ_c8TuD zF`KolFkU9i&KerLa8wrQX_?XQOZOs`zm)7(Tg$hsowCOBNkE34!rQZEWqz#p6ZXg^ zH&wL{Jywb6_?z9K4tgO&r zPkdH`U9T^l*<2WX-XA|7yVc>jc|5hv2fA2ZyCVD_)KVRQJZlpNir= z2=ItcNm%_da!Uf@4uIQg`*E!G#)1w7|F=akn(s=wodM468s;|{ueP$IqG(Gy<|t}c zMDgXW9x2H+)jd{K{A>@`N7VUn=bA6g^7aILG5;+XCD*mfNg`4zNn!1f)RI@qSAR9#Lld3K z;w_aWyQ;E$S=JSKHXz)tP(TCGIt$+XEud%YQ{NU}lJTW3In}+1*U8YkJ#-!td*8JE|CM(;(-|FV*1g*~jE8D-h9iADBbar%E2^!S7VxlG|wC%!ae z*DRX)Q~s)?A^NHpj#3GKC%LxuY-b;*Hd!*1ne|kZ2)R=#ZXp_<^6@x#iG5v}^3USR z(;j!tUkpT{R=WPUY`OUG`@~m#dye{UT@q}yO$>FG^|4LZaBFi=c-N8VqIm#hDELqv}g^_O8I=Nn`FLudSQPaIJ_}Hw0|CJHH6q)wfg5Imz;NE>7A87{ELUYh0yxXTz!lKXv&t zzMU0-qYj9sf7(<`m+??P`_bM8<~U1U(|{0%pWiWWqF2YW==7y#XHfbX+vhaX>N_Wk z-yi!K|73{%=+>UQ;SH*#?UjWAuMD?W?)a+J^hyl*kVjg`g>#>{D(N&$438aMkWGz* zVKn&pQ=j3L{wA)nl)DzIZ{q;=5bKwsFTUTOv3a#w{O0-QAUTQP#(nLV=-~eq0KE0A z+yTLUkr;)wLcR&q6LS9#f0(d5ckN`ElR}N+=fdugjFOg=boozp@{_ic!x_IyH3Q4C z164#EP1$tjD=+7G4a`SveSKxs@?`+zjdbjBT+hJFEPUb}SH#N$&JP(5A6bOLli-X%Te?2<;r%cVhEq9cDEyQNbW^f!>=x04U=7 z3n6@=#6u~4LsB883hVl}oVp@^zq4&kbnGTn)eda>_R7`2nwO#f!Sb zGU0bVT@L&DdYfdrray=d3_?GT9IAeIGmRSt{IS{l&yQU6XWb^_qjqw$Zi0&2tCjxZ zyJJqi?V6oW0iJ?)pY}bti@EPAO4(R%g74nzAWedtfBusw z`2QnWVK6)_D~U)AkfObP=ODj;u!iy4-Jo+GjTH{dDAaN$oCP~%UtT!*DO0&$*TIv! zkHfQeFVg`lV?WlOAD8L^P9IJY-(U0jDE*K9)hXeR{w0X|&wCqZP_nfDKljUDf2el? z!l~S(L(1GH)c^c6|Kr0+!4U^nVL}I`q%P@=%rdpL|LYe1ufNT-DN<;Y-WIIcZ|8Q< z%j8j^oW;u#!1W*T`2XI#0z&Ys=m}sF%H4}RNB$4@<$vDSaW_S+|Hs)${z~ri|Mm_3 zAD_4fSLJ_x{MIrk9fsO-xS&i#;}I2ka95I>Mq#mNN;P9fuZ|J?R`dtRRD1VBUz;R1 z01!x;-jJ4HUWj*0Obqpg8-t&>lEK8tgD=!YuYy?V0pK%}bCAK48D;?WVxBVl;BUAi+;>Z7<*X(N^j9y8Zs4yGqn}Cs5L%&3(PK5K>~Wbtx3s z^8PYP=2RO)8)!Y$BuXZo5+O$GE>2%El7w_xAW28PraUw6;gKlK8<+)s|IijMFR%UR zWrPoiS^2BZ8ZzUhP(4<}AUp-xgt;%q?3GJ<3Y##jf2nmj@oPJP~3 z?I&?MVWgTwcQfs@jD5X=fz;adl!4f3qDY=<%xeUA^a#tOW@Z@VO$i21he1TvQtkh1UfjG zFvIyr257HlVYzSSq@jL$<=Wzw`Qj&}*Fy{n6@fd$M)TkMZ79igm~3){gF^(h^|*N( zd=~0HHA_|7(omNsCB5ri@e=$65)U?caU~`ysXK6S$<`W!3N@tr68$}b4z0+Bhk+se z>?)|KQBc+L%JyA&TG`=BMoz=;7Zdc|k)~Uqe|nFAK)I3gfJ4+)yH;zUFt_mYd-O^@ z64vi{F1O=*a?s+t*s3~w*~h6<&bTd_Km56id~M682KW5EO{G3Za(V;1dTG)wLDLrv zJBEzMyL4MdCgtHWMZvNVD${#tZJk}T+{a>6J*n*)%*@Am$BaMwTuq>U#^D4KJ$kb+UBrE`3^UH zPd)ys1l{tTrN+Rf!ce4wI>=6bF;Y{8Q%$GIab%`~2T|~CV6BOfi_Jc~HdRiL9v+CR z-?)iO4pVcl;J;;OiRK?&d4pEW6(qB3!%TYVBO@cbq~3g7HY64j|FNTX$ijHyGTRCd zfqYltV>AWp`j(zg&4&-<(Bl}uup3dd-|lYM8|b1nCEJ-jG)%|Yzm)t^-+vQ#)E1Yo zfoEFBVVCG0&g^{WwR$tU9n(e|ojzE_SI7?6*dN?5_vx*~46VPPFFP*^ zh6kW>p%6(ntZ}!2b-E_=R5xI%f8py^jU7{r?#jh)PJFy#a%(H>PJr9qQjEM(F|SH3 z5OwmR%~)k~qchL!VOe=-Q6B~bBx5=!!Kgy^FOx0+Rldg7(NI^;{+^kbTxjQ;AtB7^i63RpfARmM|#kZ1tnwTU4{#$9FfOo zv5(QREz2E6bH)rbdAQ{q+2tIkl9~$eyt> zyaffek##>^sq>cq#L4mLX%p%*Xv^2VQhR}Rvjfj0P9nwE8W;?x@ZwV;ghQCp%(<|H zTZP;#%|DY2OTg+o^+6kTS-l!jZ|(~hFi zwVauUAh@pQ<3!TFr74Od(ERq>w4lYBap1XMK$tJ|s1jep&AWG3hDFL%Ft) z2ckk)d_F}Tt1~VO{sNGs5Bd`T2Qc93B^HNYo?2K zq}?u$O)pmR5Rr$04M-P#FO~eLEsTKmYwzZo0orFo9ecnFwWbMyt(3 zJZW^~?>~4@G_(uGc!UcQvy%0{7r*ayI+1AI21cBrMKq$B}D z4|A!ZRo*lw){cB>SsAgb1_|pQK>L~Tv>hYs%(#O~x<$gh-Sgbkt0y!&wIhBZMRr=vhJaAGmDgHzWPz1P?^k#HThbB_-9gE2sMDK=#6! z{_u^Aiih7gl|9YynA$uvcoWjUcNeFJ9cJelPW`SLJ`Dl@y4WaEZJ!c9^r|jdLMyJm zxVm~3zQD1`$-W@F)+b#WC-RRR|}o3t(R^b+r2Uw&~yT^omi5@y=L zo5ne>rLiQaGn|9&L?lJF~MqC;YWV`Dpg#l+6(8+X?7h9z8}v zFg$ON)&WCay>8v5@i&jafIuq=+BHJ(Hc|8Q>*h3ZVDArkq93tGOzdG)b@P&qhrp$B zhQD3w&*@3Tjq7=PQ-<^WRU!R+ZVDlm%9gygz*q`+D>EDx5nFwN+`4EKkDjPFF)jHr z?@B5+jENzGs6B#yK%dh^W?DAh_3<$(Xos!_Lq+$WJNKl1_Y4*iTYl#C|6eo*};hK}*wHKNqmUMe{2aCs81hC~xmhYa)PVp--s zen?V*h22gH~wK;gh-LkpN25DFEKH+ z>Rvya&~R~9n}vW6h}#xpPBm}%dwCVYCG1yTekiVBVQ#Jqfe;y15#O(ZxR+evVc{Y0 zV3WZh@CVj>ABDxX7;I=7#S924Qq_GBEGZ${wCD)?urE&IwFjJnq6^iSjfZ|ZnI{!D zeF0d=dB;_izUYxF@jd8*ucZitmHUq#kx8X-3mrLj{^@k+cHhFG1g{`;Jy*J^)9!nc z9fj})@2pVb55O;5MbFIq9-%tXX2uN@C&C?Qz66-}A#h1oPm}xCek1WKZ zWD2L_#cA$_pVX8q6JHNIPkh*>xxkFifF_VKG+D#3m7rFX;^nC*7zj5vk~3<~LoC37 zYUFP~Ow(+I7?O;I*{PTv8oupskOl4R^Z#OKoK1Vxk45twaRf3Q4_5`4>EV5(rXc8) z13wD#oRtLALHV0N(HFvu!;E_;46;0ih>(P;afo5XCe6naEhxTt%RRNm#>SrtZv0kU zap1_+_8o81IHETis%=a|hO2sI=#a~f6)yQ64F%X$Mu;plhtu%|+api0aGX!OjROXO zvDP=!%j7E|ba5QB*x!y*-*}TaV3t8TOQGP*CId^d-o8DXYons9{7fxkpU^Pk(2PM_ zTj+1+;5$yp*zWhmjt$5eVnZd491783;OU#$cEScp1|{O!!xf3}y;gG3!IeGQ68BB= zujlD@n{b0-h=DO4GsO)n$Ov#z&e2DBf(Els7j&fxWLu#NKBfbFNfFFECo;^n<3>{u zgAx8oLVjffMvlO*dJxI3PkKVde|N|U;uQPd)6q)sf}B|2hsF6xA|EDcKBh_ifulf^ z5_quxx_0n5WpbdDtQG{7ar0u3AeHw`P&Z$J{1?+Iz3~|!t0OaaIE}a9GrY(38)~ML z2gCo&>YBeMA8ho^*t~o9qiLHehk>VdO%qUQsi&GAdv!NY>T>)MY#po~;-F917f}Ex z!}NAr^U>aVoXpau0BlLg{Q+=tkx77CV4k&(~|751qkiA$mLvGR~8BNz-g< z>jn%RF4ubI(79JQY`5B<)B0OZ_`FTyqr~We2x+XZGGvch>)-?q9-VB?9ny0e-cuDW zw|=AZ7!@&^C6D6Ck!l3NF^~{Js6`J)CE-=T;yY-ZrJ~K_>B2wIXtrwQN;2`m_vMY_ zEM@NhjWK$n=GtLB;W6Mqq{s72mS0UV=7#uBWSZ$I2wh6hquA19`fDOVGp>08p0kg6 zPIh6-4n$M=G&MCTwu3A~EM4(T2GoTJP(Q_;5%w*jLZDN-Z$VWg_z9UHW(1$8MBU?{ z!on$!h%o3AB&4i=EjnQC55w~KbLY+>qI?h#u#7U~D|0IJ)v+*B8;|O1?bt<0SN;9{ zYsG)VV#SQRYciC7?^(n>gwtYkMGz4ny1k})QA0%`vloTIJNX9Z_p$Lc6gX#=VHXtR zUkkaSkzNL=v?tD>PE)<1j=c4_jLkY?s}3JOegIqD;<$N;-zd~aZ}jpi$b`0gJFQSo z=nqPY_TEAH_Bo1iy;cR_Nm$hhOmo!l4p)#f3KlluYfioW{kNg`rV?>&#$G!Swm`;7 zn`h&MlPJ61n5b`731iWL*C(Tfj;mo+2OFrOpn%|=DZMN)Z)V-XPtC-}fgNpvm!QC) zjl4&MpU5SaXTVClm%>6X2S-SCOH|OR%NBR@EQ}NYFK{m_EpTWD7kDG|g$YcGVJ)@r zHf$CbKb6$}z%d8wPPWyYA_cYx5)AXnwPxQLn`G{!=NA$X3La_GkUWBY;!h&?XJwrp z1YBWKdAPtJ%r{HjhxK$vPCsnV<#(>FxenqOM{8+&wR}o;9y6|pcuS#Gkkwmj{rCQU zQwW0Au8rULK*!CLj7C8Q$*yYIXM5aiTAF*iKOw2$L1)VFnq7*(9{0sV(N0gmdEQ(# zi3waiaCn@LbZ0zzINk}90E=fxAScMqaOa24;lW9cm%o-k&{!jDpLEPg4xa{I^z-TZ zWHXTqvL#iCjzbF#A< z6L2A4meBc)qsby;8uzJ@g&B^!4V?ZQ8J59LY|<`2(^RCU;iAF;C~v+5MRLlwJ7%V) zDRzujR#qA@>O~{7i#X5vFYW-q>L7j=6T?+}jpgUMmJwfJf6fqYa*-=tg|a2@IaPE_ zMAiTnz5%C|s+^eNL@*?l7Z;HggV~g_$zQeO8TVRdWFX%<@^Vg(2XEKML-yN#Dw@6A z4+GSLU&(^kf~~K8cQm-M1mb>Qv)m#BgVD1_$-(uuI=y@5)6HtuI9c~=)!*k;iuZZ1 zDzO*ICrWu`mm3Jo@!c9>$wRJp0{_V2D(&~#pOJ?f z0B1?2{fM;j%FLLGk_Tp2U0pPVg%kOHM6fAa1C;l7pGSZwqW!QaS1j+v0e%lV#}JU4 zn6v4Z!r$BdXjnz=Tr`oJByehx(alhdb3BnGx^%n`rt3pZ?l^LQR!iz6_#1coq85Wk z@Oa)X&B3)82JX{FOTyjeQ&o%pl12*?%I;&T&SsrN?7>7a$nTtD~(+0 z9rhXRY!0p!l9>@jA&q{e>(9YgM9M|9v!+#Ra>3T3Zk!}6A^_}sAuqqeZ8jO|_9>?g zS5mX%wQIZKQT2enoLKXyh3U}!O_DLVyu<&AZNv=DLE4+u{WD9w{_^C^9FoM<4XtZ? z4|o5vDZ=_G_`MI2sF9&0k})D7TzT#<64$vae)wHv8XRobEY8|-7?OwzmPE4-ykrs= zuA%f3w=4v=I#LER+gvzf{y0;bMWH_;%n!ob;n<-`&(A_GG3@*7&5jp-4&Fc5xDLkl?;Y4kA#0fe)p>jJCqz}l@o?Gst4tc&7mbH3*cg@I zDL{&R8^5qCZ??m@73o@7{76&sX5w~3t#=gy($#5e`vZRYeQAX;f<9L|jp!^<|GGZX z<_&1!Gn_U&lh^FfLxdfCn(eKl4b3Yskru50A92`>9>WpUCUYIq4ZE zY{o`MC5jEQE{m{jI*$D;Pjr215*e&oJYLkAVE z=b{E1MiJCK*mhT(7X#DB*MBoCv09zGkli}ms%gItx*l=b;7nqdZG|XE1W$)R%cw_< z{qE+mmuWrz&~G=~;)%naVZs@~5%u_`6KT`g5f=I`(;UDvmAb|u0|6ffLeH~9OR>2> z6KFRS2$~nfh#0%mCX-Y5dwC9Kt1i)uQb_FL5{Inz?t01NNmd987+q@lYp+F%i6*{? zHam$;Ky3aS++ zgq^*##s&SgKrV#&a|nl;D}N&6q*cttG5Z47{~GD5TPVGaF0d%Ds)p``t@W+wX zKq{%H|C`T_}xFA57SfzHx}=9H~4%QAwwg_;=- zD&jF3o0?Jv7KeCi5o0Z)oA|9EA#BqzYy;x~$&Df~l+%PgnfME*)l_;IWdn6gELWIg zIn@-jsH~d(p`yYAWYwc)NGRW&%jO#kH;wg;zKX7sIA{~c{4ezf52VXMSPwRwAs$hD z6*oNNImJVv%;yJ)Ej;cgeQbf@pMDjogs5AjrvEniuc_xd>phHg) z>yu)__sVtaEMDxiJB=f4A4DP53?1C?;RHEo0#9UHb40Z+0&4uie zTFBm!1m0eCB@Ch2%MPGp0-u`U^Luzt{ulG={)(HEp<8ww(l7kNANDS1lsXZZ!?wV}r1+G*`#6C2a-5$F$%llFaU{sb*>@M#FdPZZ9;O2Vmw4gbk#h zNKXF-wLx4!?!{ajx1fe1Io5S=M((l)6HP$*v2odATp*5qj;8B4==Uv-DUsF#!WBXz z!Jx~+$=t&ANB^c~ArVfo0McncYAG}UKmF7ne%Cwks``QJtvr{Hcotl-`H$1~MaWIp z8i*ua@uU2q^5K;BNV)KTQn@Znu9K~0Ncqglg$_Fu+Jw-qMYlV%%_CHcJ%NZ(bk7H~?r2hY-hl`TKKCB@a}G-9ZaJbvh|YB<;c{YQTF~nxgW)BEx~8B z8a%CveiYM(#iUw3#5>lB($Zl7$z4^QnuqqZm~caN|K z?OLbpKFP6d68boJun@#~TQEBbu{H05Lq0DPYOT13>(t~qZdG8-PBYdeRva^2P+4~` zTHV-vrs->3#~ItNx`c}~(w1Gtc*fCDh8Q+b{_n+wY^0b2N)~#286Rh#v}%HjM6Mo& zpD(j36A&Fhgs*x(^!|Nt!70gFLtjMCnFZZRjrh>;GagU51pC4#0jYG&EujYj*CD|q zrsyfH>r6Yj7JWHQZ{!61^;Rg|&LuDx(dS90uqy2h(;r_T8;s=%E^ zjAnb2IT5k)3^QUj!w(qhs{Qnx=Om_gW@jgJ8Qwc^ zs*CEx`1~IiG~A?Fq26Bv4!a$+tw8BCd@qkIYK{PuO%^ijW{Ud^xELBsmvDx zQh5Hh`F0`vkP7OtLyp-&m3`*YTep@OeeOhA9ZF(Fco{@4eB1;lLQ-cHU43dw$`I41 z>oY^z5{HJ-~N|9tIohWIFI!;}{pZR*fM`nN{(U|wMxSY5uTLAy4 z7#WtU!@|I8=k;X3J0Xd==4Fz_^8|?>sB5RKp-9ZSaj~-U#jNMQsumme{pRNBhDGU1 zzozr0(UYF<2-@sI1*mR!)a_oWm21Ul2rUT~-yx=P4lsX!3Z(3JbbRX>REbalPIfoB zSE?O?RT28A!I2tb78me1xWvR1d~il!i}{xJ|4n=2^tG>gFahACsRcnY6L%hCYv7?R z9*KhpV`!0FAhsp&1oMJF)f!&2=O}$2A0JWw{69{@;)cwJZ3C&Nf7gV|RaWfc=fCX| zV|*O$CxYT^06ot9%$#mrUWn7YNTO5!(y>X2*2Ol*yl~H@D&lv+^G=y=GZcc(Y;sBn z_27uY2R|(t0}i#9qHDbKr+)wFX?P)Ne_beQwk>h~jeO^6GjHvrX5PwBwT|^GmvdgK zC@E3ebh3hRavzBcK7j}B>w;l0F}?KU6kvD{@ZJZS=Qyp zv5Zl7p!%z6cJTrQV}Z0AoUCWG2x6$vIK&%q!c2Dq5=!l*UEeeIWmf+o@s2n7en}Lp za6O^$o_aFnPndVtP7cUE`cv6}0sxhKZVblPPUqf_dn`JSOWIa5)O_hUalw^kW&p>q0pUB*xU#>@n7j~mk`Dv+>@8pq>)XWgl@3(vlN z{ec%SXKPB&hpPC99rW+qCGYM|Cq=T5e5^AUg+r4 zL6ZgFb93a>+4X6AbUW+oIgUg>% zw>I__LeVN;nC%syV?lq6wqq+wgFbhdH>P}`MJ7ukayM)Bs`FIo0d1=~WjI7CEO9Kggd3tqpei3y{Q9ttU7Q`ZXD z9DutV5U}~c{Xv~$}|MlG`$5x=fQMm8uar`fyU#`LDLTwi+ zDEc{%A4U%mVf-2q14`)biyo+W;I#;V0zt)2^5O7z z9ox%bCyDQ^nThpcVoz@@&W-;?AM`3~1{_e~P;Ae$q`n8?;WgnpFaV=~n-oe6Pnc~6 zrVIWdv4iS0l+U?|VxhBJ z|F+q!Xen%)6^F8FOxEf|#$QIYG7w?;gL5+^Z%h#R21wnjF`>%`MUUj&FJHdwni{O5 z+||=pwaD2#EthC!i@oF_^2h}9ph0bF!KZC}Ey@-L?92j*-LVSWs%n_8GjJHBFo0-3nq z7uD6VAx>2Ot#o^3a&yE=1_q-@St_kt9YQ`C6>CS2p~-$c?qJCs=FGAEz2)}%QNN?v zn0{@ft=Y8s@lG91JV{v5XXsAWjGuvgTyiSV~5_c=_EXx`Fyh$pRxNnOwu+h8=L#O zhs;tE_FcA~tzA(Z0$HbkMBtKZqoOQ7nYb<kyHmd03b#F@7V z*OMsmQjW}nmo`X|$?v-)PGF81%>zlmIIVqhEsAPXs-3)+6 zg-wM*dO;{Z;P37hxRNEW6U=BovEWnP(ir0zDKL79?*S6aZk+NYm_p}5n0~J@Gg^%f zEwKQLENT4S^n@rnek9yYgx9IMG7al7+Lz3GZ` zxB6mcMKGV@jzt|i(=OrY<4-TuZ`M*kdzKsHM~!T+61wjk82Ui`;~!#~MuS90gi<*v zi{fih13HZQx!_+@s|CVI?**Hk;32bfYFF6MQyE zZpn5*HQ^4<5N+qzLqqv)ijjq1KLm)PA&1J;?N!9c7p1JdXk}V1dsnTw;YfEDswLn z0|iDO)^3o)}c6{VsL4myG+d+N;HW3L?C8%u;N4dRwbr@pS%3Qnf)OFLxkMD2eDdR-4 z9_vyFR^=yxW5ibF)zy#1v}Yh6!w_sDY(=+iGi`|UksPXgv#+cwCw^MSe!3vLTtsSS z+rX%nMA)&PyHG*?h!y8whp~Q6!LC5L2mLTqiilg+;}lR(aO38NgCqF`4Fj%j)uu{N zpHUWWx>_}Ba4^um$?gw5OLt$|Z`HxqX~-gt5b-Bz37zcBL+X5zQzYQSEgVFAVqzx+ zjT!Bk(_-UVI-D-9?C|4KYfTWXnXY$e*|_VK>uIGK-*uCE21iHYB);0^?H$c!L+v_9 z_dOIOKPJp&IRPTn3-4nl0KniwBqO`*1_h- zTIoM}5@i+B1an=D*SR{sWY%nJU$$dp#C;>r_nQ}&db*tF9e!u3YJ|p{@0a|-!==-7{~v8{8CBJ{#SI@ilu}ASQji8wKpGSkL6AneQ5qzrK?zBNloF5zk?xWd z1f;u_4(Wz>?*DzpeeS)_=a&x-aR}#}z4uyc&R@+POEuy5pCTkBm{sx7{1i`Sid&B- zP?@2|vU-n$fd9pD{?XSd>5142>};R9oNSMK#JnxWcmVWyx7{a#6_d5&3AcX!YY#B zvb6xwgOG0!`&=>9XzX)l=wCjcb}TO=MQm_~Dm^}RuQFvL!MCY0^bd4IYy>c?qh!}b zPfss#Uw2V5EkH{lzKew;L{UY6M2gsi2shz%qQ|6nqFjY*2l-rXU!OAc6_Q%#aQv0e z52iQUIU$Wtvbg83dT;L-Z&<=D(C>fIZ-!AN%fnNI@DwROlnex^hykGgef2Brny|lR z&Nphf)z&q>wu=u57+L6Eai18o5Sx&qdcjJ z!>a&dH^7(?yJ20IL5u6xukFyB8svdJ{!=V-DES3F;yMDX4$U zM=#L44_WzHJN7dZ_1omlvlZ$?u>!LnQ{+2Uqe z_wb*w-3Tjzc-66i_lAAci^i?G+Jd{4ISh4z`~&vCM{EdQNkC?U%(b}7*!RQ!kgrmC z#3Uqeny!xnlJ(Q<_V)Jn7oS#mE|^$Y9^w<8!^V!16xPQ`k{cgh=423THV18x;IQwGGh|8ld=c^P{hr=x1eP%Rcaw*8kd4TB#@aTWlg=ik84r zet$o)knLnqn&9oV_N>QiU7WAyIil{N$h%hjf+Sx#Ry|}k=zb@CE-# z?PK;b#1T~g=k1;u5Ewi%Gh-p7x$A>a7cu%??_1%j#qwK{*Fn@e#4a3*ZjMb&;Ns#+ zf?5O6U2JGkK^;R2bfAsxjk_24%IV7dAwPxu=na4#Lmn%9e0)0~C6LJ%pahATRMiKy z3NYiQL?2eRHGyY~_G@2pXIfS+SOi<;Um$qp3P5RtoI+3v(t}eR#^fUNbdjC4U`NPh zBViavY;gQB=>yO|3buFU00GNZ-3(CV9P$U5D*uK0Viz| z2F0Tj-1WxZFv^CubLa>jH$o7)wjttZ2o)hEBLjFsrrrk0htGQwbQ6llzUe`4Q_YXx zzu#L%qep30546C&G!TKop^v)1#6Ujj6e z=haCc)Eq6ugBY~(PB#KlK4qn43#fLar8;=!9e>w8mGn)O+nxmsK%oGIt+KigOt_KRxq^wNp=0mg zBlW9$_+U(&b%*&dCFMjdtWpg6vKs{T8&#{k03enV-N%%A#}^;lWd;|xoi-BW$ipMv zmK|OXjOpCN@>7mU>TqbsC`(XK#2`A*HsQG}>KpIk1-?zab4N9ew?|GE)+ zFLUErj78ef9FO)9Cy8q%#G|pP#?voxAtDer7aAGh77=J18~CE)je3 ziWBYl)Ob-#z+uWGW!@5tp2Uz(Wx6KssL(N&PNVe71C8nOI?Hl%S30Lut@X|vy3|SV z(UgT0P^`DL49kLvAGAk<{*qfC&hCe4B!2XgAQtZFAp$@`MqVD#i7Kk8`GXve5Hjxg zP$xW{K;;GiYR-R5fEx&4YGve}y}G6*o)qyHbB{{j8^MgwoQ}IxAlBxOH^?4y1 zGnm_g-qO-Sdms|h;sncUtdYd7Xd+lvfMGh25XB& zj0Qy7(jd54tr3T72nYlPB8wwFB0U>Df;m_Bc9KRN$_g~{6VgP70 zYGj}fY)r5UDG>`jfNK~HN&Xpmc|R99Md9A{HJ!m>4$}hVh?4J65g;FK^st*~8)gXw zKxOTgP(Gi+iGmkKCJ?BpsrB(sYpWMknMwUErBfRgf23O;=unvpd`85u=Zw+Yn*f!7 zE?)C(GH93FJcDK{$j^1dYg=Kq!!$iq8-x3<)d|OCgxyj%p=;c#QV9QZ85b9Z1=3k%0H$@wFK>>xtWc>ce}r*FfDF=D4&_Oio3p`Ze-{_WwigK0_03Ce`8`{D zS7CdE5I01t;3Jkh4DD;W_$pWVL?3;d!u{*i&@BR0-3(e7kc}ayAe;6WFt91A?BFH& z;*M;ZlDlkEJFO475syUWh>sM>amst z6sz#ixN`REh*4)|_PluFbTl~ePsXcW}d^U4AUue0wh6Tz_# zgs|Kq$0M_HSRIA{|G|90MxgN1KevGdXfWr-%(SLw_a4El@RR)g7VoJT}vwW$t*YlR@ z%B2XU{)wOVWBo;PIW^ckc&OuyH-MJVOQuQcEVazU7}a7KPfz%-PEGi0Q*5^R>Gv27 zta{JBCtHp!-?}q(_J>pNTpXicVkB@pn#KhYt%Tw2Dw< zoYfbE8+G{Ax}N5y6@7?c^bwH|fAeE=qcQSezro^a;mmL`o z3#8Ohc7JuSZ1hTq%DJZ^F1(U`UA%=tT)c!raO&^++1tYWwkH4W5LTZ!x{Wp`+l}uP z<<7INUulUv?J*CyMM8*LX4R${q~2+2d~;c z-AE(DbwQ!;fi^Q&z>DVoka4j{gRq;enR-o&JJ@yZ?M(##M;4H&sVVn7j6|Wbtvkxt z9qTWcv20W_GWlw<4)91)?Z+Zm9CE#?U)~JsT#J)Gl z$jFNNf(Fl^J?@(ZvziZttpWrL6lbsat{}@<@pYLl~F$CS9i2`$((QuW{tIE4ZbwU*wOgX1KOgAIM!&v^{`;!)1T zWR&|dQJb^fsV5)+<#Ei?O1p021qH{uLMv5bP~Lp$L4=KBW$|5R$R>UZZi$Z5pc*R= z&m)j}IZlBw;GiP;Z7OSL!e5uW`&8QZ_nD~XT`d(n#r1`7qU!-#qNOXMASE>bm;kwI zKq{^78P+&RE+d!X$}W2Q>sYFJ_i%^oZ}3EDcC_a7FYEX%uR7G6Ao}~h*MlI6(_>Eu z!$M8d6WUAK&490CgxUS}dxo+6~)*2CCa*mCV) zTAcZ{gxhkSpns-4_yH%Eob|?%hG1!xhpolhe3tkD2Gp}+yTB=Bx9v*+#s>FFW3dcG zSg##VJ>aL5c|SRn8MJvl=ZpK>EwU*yjRs>+TH}?%T)!5C$xRmIJk>7}KTB<`p&0pa zl0&ei+hbNd3u)Dnded<518#5~=sGrn>ea8+6VWf~Nmty7$_r!i*$RHh_e$KfcyXCN zjb%0gjjg%_|7pH3%cbrdn+>(c&rS?%Jq{#opKOpZ5tNTf2caVI(fp&#JZd>K@I!Mp ztlpE9a|zLRdM(7Zg+4eb+>?GgY&zwXarwZTyWT)UbyP(O`-tO~?Yuei6XcF*N^TNg zWC_S8O6+*HJLQXDPfj9n-I?xq@Qx(X**YOBXh6QF!k9%+du3hdEvz5Hg(K8KV{ z4Vuu0%rh$r@58{19j!}>R{LYj1S#ikhlKtuT9qXAos+%v)KywKEUVCKPcL!j{t*a& zh~Z3eaNqg9WjU6?2mJK%CbL|$Ons<}*&Ql`dXp=-E{K)jomc@HeQ+sO<@JCxV2rh1 z)lu}aVB8bL5-}^j<*$u$1B!4e$*-GlDV&NH}VF zn<2yzsOLR1+>E!lQ}olpi3|iIpz*G_oD5mS2sD<|13>i`0Vw(YX(JsL6^PDcNWfKk z8yI78P4`#sGRx*7+Asomv=|3OmG93wTtJJcC-?o%V8K~hTAD7Opc;``B@9fnyi8+f zQtE(c?5O^j+3R}$um!FTI?Q>*1x;;R)5|)VWZ|G-g}*0c6ZSC8@k|H?U9od;=w_Ti z=Wi-FMQxGnG(|VYRl+Ic<*vbV$#yK|8ls`NT|jAhSnlc%6ijG6CqP*TjtoXl&L>(pz!rgJXyC|!$LW{M{pnnbH3v_5 zZm0SjanB#eyCGEDdIo#@7C$@(0}j6)^gg0{yq?;>{I>yvNRk*+bgTJlpHm?;;^Yn9 z*@KG-lOvywKk1C0zmk$d1CdZ5TzYEQXKjDg-FeUR?E8mL%?$&*9(7{c_#;QR(@2NA z$KQT^D@8fLec4khkX|TtL4Syi3>~w98D(Kt&8rhd9xnTlY;}K;No?E;t?cdonoRD- zI$P9XeEn7^=UrAVZsN3RX~sK}VOJ~NJgNmL{yaRPNci~5&4i5ly(>e7jQy;1l8VK5 zA{=gPtL!LrMklJ~H;1EXFD6*Cc<1+-aTH%Mpkz>gn_~qiN7+&ptl7~i`?>IOcrXd_ zN8YP`Apw9oMeiPnvPqed&Tl%41KHjJsQCE%^SyMk@xeC}^fcN=M@{8#@G3ZQQgR;C{34IPzsv$~4k8KiH z3)FvzvKwek2ABs2ojSv@hF^Mt z`i2vOD6GK*inwNwK_E5T9URK$G>Hlt1)=B!g-JH`-*Qx;-G2a-f1WyrP5qg(=yp33 zlJ^n0?+J>6B6CE2=(sx6Wo_{aI1JdTu9Z)`y!0@X5)C?Jz>8)ycS$~047Y7^};@6TCazRG+RHcMecfk1xEvj6On)tLzPyAr5#b;{CG+)wJBn78FWh0OHL<<@yMI z(6Ces37bKL%SAX{@XL@%Zo_NFh&vpH=5o0kM8(7eLQx!J@4mO<501{h)z>>vyc)wm zK^yPMb?_(|1H4*<6I2Ru zv0?$|EbJ{f9&QaV7WyyLgvgy>`G^PnbfitBKGcK+WlyHM-_}Jwvwq?v1}6j)Y=MA| zf$bL`z<=x>u>+d9xw&`A$q}$+jWN$GgT*QksHWP^NO(8*j{!!Qq=YGxwBD!o%PEtf zbSi|ZZh4G~np!*8K)HdISQyaH(O1l1#J66cfYTLuqTm_$TJi+8?lu6_!a*uA3;nVy zKzix%8z?*2m1U(+e)ONAlsp7UIvVP!=oThSoqe7AwF;k^nHffhM(yeuXFp9&6CXE5 zC<3RUUK=PZc&nV(@gb1~n9q){Qcw$II*Na~WgHM$n39@`4#-Fo!U4em2g!orPA|4; z8r>XAGwYaq@w1@TyHlT3l6w~2P4Ka3y$%i8F15iH9*7_+KoLCzz8|>=@EVF8yqQ4C zzS=e^*sA$u{6j$>CyXc(6NK-0TCf9|B6YwFyM8Qvoke$4uk{6p3m_O!?Y@@gFBtxt z!+G~7$+!80xP4%UzdF^Yz=Ef|W%S1JI^q;WwArC_610#^1od=o z{-ND-7z-B!O0MfJlTH+=si}6rMuYm)*c%#uz z+y5MBN=olZm-65i0i7UDn!?h8;^=nET_sj4@l?QL2Xf zeHh9^{QjsK(Fb)CrKWerc~gxs7p(6R6st8aqhl1w<(iC6p$zttCPMT>MIPMqnJE=H%Qt`oXXEAv=o%pQZ#C%*Z+H1(BBIL z^o&eQ={V`(@oWGX!tjA-uhW+byoxsf%#Z1))r9&S2e&)Pb6+!=?)$aN0d8F55VH{o z3_KJCQZy10EdY2g$?x}MbQ8brat*=u=fKcRrh%RJ*d>?pzw{fAw>eoV0&^B zu`x1AT^l}xWSyn{%!pl%4j{S=VNaHkllzG7APSW&Bf`Icf`Z0J?6r?Whx>B?mTmHF zp9%hUb6eZIgl<#$hkQ9E7*4o?y5pQ-bC`G(4u`A}2;usKg=~g|Jiu{Q6i7!1OAlPY zzMOWC;jMv+4Wb7=?f&{P?gauqI`_+8mj<#wuDQZmp|xFukrSLc6-Y?JyH@=g*TS`w zNT95*e=)eEKj1h8hi>f}gSN8rhjDtcMFS3((MQO9b0D$}ATpp&z7dF-S6r8_($PhL zbD2qaY_ILsphVLo&$HZkIAI|HVtM~z^3JvhZSc|e_m5MLMJ?RBAoS_3^V&bq=Va^? zIk}x9Uu23CNc89vhoNWj1&DCaRi!v%O=<8T0*s<3C8AVe4=srN@Aa}0iLOE;5oB4! zGu%@}Qw}=0e?FZ75^+y=aTo}D7o0GKAnNpho_|lP008#WgZhgW1U=NH)A!hV5HWr@ zsG*O+3^~?+Ava>+*moL~xu(;V5c6&#b+a3|=#!!zm^;{so?fDruNs__5`GaN zDgZWL&P(*v!j4&abjo4|kJ5TSBafnRU6T8T5a3>_$6HN+0qJr|TfLYKjQugG1|zTI@-V$(XEl z7lZ<0a>hC~I+_K>P@#nZ5ep8CqO;cb*;N7w2ZkSi+`t+}&?lHXn|)9lb3B#H7vEKy zl(};d86ZA9Je0nsF?evUrhD>Wua;uTIvI{N`H=P8p~dPc7f>P&0W|)oAGJn*H$oL& zQ2!CvEo)(66wAC4BKX|!80W-pv#6<^MHRiRd!5FOErZ8F&a^H_&wc4s2efizFG{Xv<`%{+IDh_ZdegQS)}O4C4`0gYY*JX z`JT79T>@%KE?Fd7<8ltCK*h=AS)q56n-OVql6BI%OD|5+greHp>z@Vh){*=unz7*g_Vo#_(p)Zz4G{7^z8&|F(pRR$>3kvliW?|eZQN*aPa02ua?9{pHks9r8l6YOPCla3m!q=(ZN=YfFC_Rbd^J^vZ& z3+FJCkdUT84Lo>A4}2$ahYu##7|(w9KLp#j#h^mc-OO_eOtgyw?~6d-{YQ;QDFA(i zB7suh>nPo-77G2e2=QQW^!(sMRcarsn3}^sF-3?XO~b3x30Ow+@V~Ph)Pn=Gp4z${ zY=l8Ro$Pqit7^6=S!Df;)Om_P-8OGe3%pkalp09>16UjTR=wuA?bx7`0o7_Nya>B> z;7X_cYJPdu=_-7Iq&P~PA2J~dmjcZ^aFlL+zr-^G4T60&0qUf{ z(((!zl95jUb75N%GbR8Qlk-OzU;lI_#PqzPtxKSWo7D?=$MPO{dQOhDFR*mfyq~0!w#vL)*A7aX1cA}tM={)T`qM$ z{OB$ZT;~&gFv`l4Kbg7fygE*SnC`^S4~@&?sIMbZ2V}mO6F0|)PoLOzsbT(cm?jVK za3Q9h(LemG5+iD%`!@i3t^yQFI^BlgQ$QpJYYfpll?a&AhRhr*1#J9YVtM_|y=8}i z=kBnvU5E7=6?K&D748Aby}!Ti6ICV@SGjD4gCKWkzU&&m1t~Zh*~C8YmD1=rNbG+y z8l}pio3+I27^AiAO7GE#cdlE`i2n;+lbfglHX%(AoPiz2iUAHM9HjU)XZzzt;-7Yo zGQc2p2!M5=^Sah(X3AXd-VxG1@99kb^%b`o9864^ z)mig%Kl=8f9w2bb)c?@tZi*y{7o>nv&A@L*Ah%Au`&t0lVHUTllXzB(VJ7w{pT>0g zM(u$fj44C|$8ymDB=048Y(K66K&;5P?Ugv<^oDz;ThznfQrWRpYYqqwVtTaPtIcKz zXVTvQ9LUSZcnlDjq4hFIGZw+I8BX7|g$yj{92ri!H9TvDo!3}b+l1@)_oxundkt92 zK^YyBf8WEyFaWu0*1-pl(5=X6gsUp60n#p(?`;&MG=PD-QBs zJ6&3<_IsjanK3%De&F$+ZI?7k{3^|)*Ew8VwspB6r8pO=cc$G~PVVcP?y&IYFzET2 z;keT?Zs*S$yeW_wW?4K6$`?D}p_fBc9h7WJ=6XV}SP5&r*-V&?02HGBi>?lN|@-TFGp{@vng`iE#j(}$N7{!srN z?5432(PUhILpL>q1u#Vr%BJ$26W%wRwHnd%+Rnvu=MvnD^NkxQ`VoDskAo~&*U=hI z%AFrtI7%jttpn2hZObm@Oitq^uU>5mhI0~k6Bwt)M6L_-M^EKT40E;Z01&FXx)%F*S4daKwM;^ft1z zYd%fIPM+eGU6R8I9%`zx@}t~Q=gR!uv8h+1==`!Cp0-b8W&{GQZ_51zZY|xuz9tjr zbO1lOED?7GV5Tq1v4VSHEs6JeGG;sh{~mE_P4ZofM%#k2+wbXkS7le?a3@95*q7t; z5!l_$E6UA-miju{+ffERSJD(&xnJRBMo;t{i9k3OD;F2sjBle>#1BY-=PAX1X3h!w zAZWjQS%X0v926Y9ZC<&(vx6|hPQk&!IsIL;RjeEw_cb-Ip`)WKYHGd(GxZyPfBxe| z&f>HH{$vrT|kWTy#hdfAo`&*$8 znP3|uxqf|pN1jfJH2n{}7nYCN>ixq}gBB0Gyu@KZJjmLB*#ZoPCwHfoH5i1c5A^hs zNSD4q0!(b|l9PMyA4@nj5VI#twq)&`7*ZbiQ{nU^t$$fjS=kTVCYqnKTa2X4GS1w+ zj1$^Gvfa_q@mW0=#L_T;ClIWOZprfq-!2wJb>{^!lGBykqYt)+2M-!9S z>#Gu=)T<#EaF$3FZyWzJQ0|2AT#p~)agtvGAAGvElM*OJPES4HI;Lc0;liCL+Q|dk zC%h{3)-XOk{?wz7vaLGvY!`~tC_Ym}5vdm^_zC7xGDK?to=#jYReUN+3Q z->;+cY|b@nE|=>bjxXGqc=c?rUhM2@;yFbw!ND$(-nsLxF%^G92s7vM2i$FqxfKNx zW$dhAb`HYif`;SQSR)>JGVGPlulnAs7Z2vyT*k95^(mnDCUf*ka+w9@8_Fk60VVZM zPxDDXtK%QeOXHO#a<=;B;s1_SdwlIX3W_S1xZDH+8G=2pdwyc9`BiGwFSia$2{c~U zJy#}%RuYJ0B}K#5lpV6AUUpo@wYXs#mfgQeU+2 z#^ZOfD509>A?}WA5N^hDy9`MtC3h1`Cihr4x?@xsm}OV{%dVnAH%Y==xFfaBj$|0j zh!ox^Gc*{K%{Ekqj83rZhS~_UsTh|Ck&wK<4K)koN1Ihz!JTzF-aAa!NrvdOWs1|i zSKm}(%Bm)Yi!+q7lZp%~c0(pkt18I_X}N!#g9wPahVv1kzW{tB=FIJ#Ed*=%LN9f} zwLh65YLl30f6xG?fg_!T5^h+HU-ZKTO$-)+M*LC_*{eFa@vYsRzq1qKdCuNcne}8B z9P8X(DHa3Q(Tc~KkhhJT(Eg`wt&x$lpko@_7?tTE23FQAJyVHXI>GSsFVziiAPLm$ zXg(bPpAC?g8-bexB^tw5PZJTgQ1-KHmTHi4I{wr4-JI1kR9ji~#C~OT9z)Nbyl2wh zP{B5Hv7+Zee-+5FPe6{nmaDh2=@z4<+cPR(YZYcMZ?ghgYfrn)FDy#W%paP)C(&$q zSiZS>8o3m6#qW7rX^RWd?!)TX5DKr_9|0Jef4*MF3{z%LUn<*4`LH<^-sf@c2>XfK zbD|_&PZ|jtR!Q|jC2~=0lzKdFdQ99Ulv~O6lqMNkxvv5>A(_GAa#6sl9V=$29eYcK zg$5zY9k2E0rMQw)ReYVOg)?l0WB=`_Qq!Wb->Y1ytuxhH-T@Et9-R!>jE8MdVTEB^e89G9U5$*S*5ZAp%NhFQ~8kXwuQbg(BILe`Zw{y!7dF zllNEK2)0Z64JITE=&t6IkAWNr9CpqM1GeHDTqi5Wzr)MQlwAUOV^JJ`Zm-2(nNuz* zuA2JMc(@MkDEIv`%PQ%6n;CDIF(n-s*gH)MQ(Rukq;1~k=c*PQoEx|JqQ=4d*_zTz zL<^Io;gjbpnGm_ggA*Pzhw2of+brM&gs)ddPzVUb-$F+Gfl>C4j}|=uq)_0XZlwr+*Z6oLQ?3hpeg~B_rpxPDkZC; zk$2ka{>{9bPOsHu(cTM(d<^rl5JT3@|L;48yAX1jG3d^ciVT~#whP?Kb>z^`7*x3$}o z>}uZobA_I-KD|~ul&T9glNfhon*)yrk9RWmLz*@z(A&>{vikN~HN9WM&$j;h;%Wy* zJOsN31+~A1V+;{o{Cw8uhv$bX)$*!mfw^od8BYx7iIJyDreL`G^2w(6h5KWHud{`RUzm=<3+ z&4wMrn~6?9QlC)EbZF^D^3dh&z2u?2f^<$4*Vb2# zwq%&;_k0Ff=AJ1q^KBEnxcY|*%ewGwh54)=Ja4R+GarQ}iK*v?vrZIsvLrA^4(MPX z;CJ7#GuA)QY?zvkWIM|rWL=5;ldt;(h1?A_E9vYh+O@^@02eha*V@}{g902>|2$y5 ze*zkfx#Gd@25iYYNQ?uFAJk!n;Fp=;339Dbx{c7cp|oODaB4yvTr9BQkV`zu%k>5r z&m^y7^Jkpoh!h&kr(yR7-n8k0Mz>htDqPM1PCK6ThnZ>|zxWw&tGh=m&uF16_5Lax z;4S}RQGht}r$OWEWV+}gA2v(_nG?J6opUo6Ze?S1JNZSK(Z7T4+g~p0pb(ff694FV zA{SqS6`5(LLFrppla}`(njM(*zX~=2->O9}F1Xp;6M5q9T|gSTySb?_vsx)e{U!3x z-*6I8X5H?qsm-qz>6M}?2_cjO?W;5@!ZR*Bz1-zNkb2#Dh!_dsb1F|d z3%aWau1wQYd~s~vyBYU_Y-95Zz}3epcjLUbD3tq8+U{o)TO=~bqd={3XX@w=r!=ab zj-#yS5$UZ;jVHCkkJ66aJxO?DDM=pvv;Rp`fRF~!A6wzVN_C8bj4~1E;JO1|G%k`v zb1=`@Kaz&HnOi=gpPK7g%9KDeG)W%|H*Y}?&Vy6MU2hfW} zKFPNxQ{f2^mwAl&w}UA2o0%;wJXdd8*yl2t{AGH+Mz;25VNUSzHl9DIP$RCJjK@K> zv+*0Yrg~PCS&{-hyVk?rmXY?LXgf-P1d*`C_T41cz2dlM6Zj29wm~(9zj^wS9Hppf zYW+<)8jT7IFq+1)kIa3%{;1GZlJy?4J%URSwZe{YDZ7^ zJS4R5(75`MR6Z$H?Hgv4=$#z?4Q*%2Isf%-DBOFwZsBUA`=<>UG>&<`0*+X2{?W<+ zWj}vTcd}rWC8_wKm+df92RIk$P2Pg+|;iW6F8i}LqskXAX=eIKCi$yc6 zDg2lopoq96{NSFANY7MmWfy%&``2av_n!kb$hCc=p5NeiEzxfJd0;v<`o5iM{sH=9gU`n2p)Zk{bGC+=V7`_wnsw7(pef8pMmBn#K56U; z2x>pj{1$%`i!4blTi`*wJ0of-jx=-+=hBxwp%1!vpy7~V{O_+S57rUP0o4yoCC_&L z(LbpevJ%1eJy2-C1xnz=&%=N&n9P|wC%c=r~??Lg2GAPyI} z{^qd`@05bn{T~&WhY8xqzTUZ#lgm|5uKe2&&B9zOJ&@#rUrDdd;YT!Q^4K@E*rD}l zLNP5Y22n+>+N6>G(uq_pwg#o%K9XmPRxb-Ef5v|bfC9I?6YAPnAzrjgW{w|*R2U>X ze*Te*+ph|DkdKjp_VV`FL){54_RQ@B^fhJ?jmhy&{mm`rnE*`N4&xfN2TFYp||<>>Eo-CJuf4=M?$Be{f%yB5lbylWb%rm8$sL z(?7j&&c=P};I%F~(Jzr6UmrDiuHKLoC%?RqTt17hL^^G)Ks{_$+z2YPHBEPcY~T$v z*lI#OF!;9j+6#Catw>0gGNnDT==$c+NytAti^vyF`DI@^_-<4jaj`d%q{x9qs{YBo z?3WK;8kv-rtYheKB|-R0b+d0XFKn3MwzRhor#VUcqQ@VUHHvh=~$VjptN`J0W?*|-s zXNljWJqZY5%?`dj;$eX-Ye4TSgQ#Zir@vqsj zukWpB{`&_QkF|}k`Kt0TPjY!vK(?n&jhzu7m2aZXYH=4)fB2s?y(p8)RWh(UvHMxY z=Sy(^-y71u{^G3-HCbmcls+c`HwziElu9D-8v4eoix~dr6KAy;;K}{Zi!(B&k_V%i z{Lin1Z@EY@+W+V0;ae?M?05flZT|VDRPi$X|6B%m8H*H3Nu=EV@2`P-u!;UZzc|a^ z2zBlM`Nc|?fVKVq=MA$1FHySI$L?|3CbTB18*rueT57-hdtrVz7A)hTsJB4`y*kmW zF-aST`-IvMdWy59xOWY+g?eYRdQt^SHdX4Z^tX$M;}amA&773Kf_yh{9tkd)th{+dTQ zPtW`b{9h|kouIJ5OSc>h&~2_$dXMHCL;DsVL4kn^-dn&Xq8HIn_CJvj2~~dGvVLNg z8#MpQt)}XnG}>Au!sWT+y&!e3ENW;eM&og!lzozAec_zfdScgW`zS)v0S1itHf=pnq`OGAO1wR)0nJ$rX733o!a72z&nP>w_Wc8h;${ z#7=m$Dr}fBh(W785Xl9mtl5Nm^>%ZO~Bja{B;;QN^<|wq#__`}B zo%_9bUb&ZvO@_*>DCp2+Rfu!?Jy%qz2P}M@@h<*iWx2hHmH8r6VXrQu{dADYi$Q!% z?ThD=j}v&Z(NU30Tu24z>S|(XPE)Fk=1 z8`+x0k&2e9QAw<5X1@clFexfTE64-Hx(uD&-WN4qlNN+zVL^?k}`M(RG$s@zsy`zHPSZ| z8jwXhXUB45!C~TVe!5?I!q;h^(?D6b zkgQn#K>HmN|HUvOLZOjgm(kPaDM`Z*4~!2?qR+ptBz(Rfq>e=*`OHU5(z-=o=y0a| z$*5}-3&&S?2MQGZt)FysT^sDy@!%4y`8$c1z;?%NLVft)ZHriwR;6z1ftT2-{fW-B z2=;?0v(ry{m}m_+x2r2m_K$7!X~}ImBC$k;n*9%Fr>~Cly9s=gl!#K@=+}gD2HZk8 zP+opczM|i=zAVr*?(0D2dKkh)jJ7L-|9w?30}Z8jvPBAJXgJ>G@SpSMgEx+3okK<_5AKNYBlMdh^GcY-UJx{;h;4D@A1-jIS+Nfe=Ni-G+w0j za9~M;GfT8|h4P#~C9&;8utS0D_levOIsw|&jM6Asf1mHNx9g^&R<~~VkYBSO|A67@ z^6)v-1Mj*J*AYOpwcI;jZ<|(J>@+=dm3waf>_HS33Vfu^HDQ@sO}^-(5Ls{#Sf7r4 z$ws$^j!aE`wCebZDEU(68!a{&&0@VH1`MQ$E5~yZ@q!9n?O;EWK;)C|?A~oKCVYFg z9@hcek=_JEyP0tus||zl}<(+E!iUiM-Vh zCw-M54h9Mr&zHYUPXtP?)Q&Q{{w$mbuhsa&d`VluJWAzgC}dvf>DJKG=QU+pbJNAd zw4Ynyv3sv+vRel?yrR$V7pkQ00{ zcx%q(=GM=OvaXjWBqaP;z`DWkvNVvu4fV4yGaG^v&q|sO605Pnty_3dd50=byPOc< zWGl-5Sb(W+P^s|0_02Xs^l&dDmDzdzknL+?`BmMkuX(d{DWSjJ%Knnb^rB6f%g^k8 zG||;pU`Z?>=BvS4v2=`v8Y!4PvCteVh3B_eT0=%`u zXaN`1!N&i?uMM_|_re55_fFS(g>6VxW9?5qU}V)0w=nD{CTTg*KtY3Ddg=b=Z{po2gweA1tNNL5;ktb!P*-YB0(tR1G<27tgn>1K-$+}s z`xCe@NoKT$&(4OhZ4jb0fER9Sz7c)-jE;zGrcCOsvgFLTL?XQD2P*>=DNi{86e`>^ z!5gA$kI5(vS>c^ozLCbb71O38?$|@%7i*Tj9Y)q5elgs7mExQGa*h)(s^$u?k&En`@L2KQ`B-(tqG9 zVLl=6X_Uf8bDbL#s2F3zT`N!fsu}ZtB!s-$56r7#GTURuDGH_|1GLFEAK8qyY{x+5 zm^bw?aL|4257GS+b~xD@2NM~4tDU}a-nE2g7%WK`fboHKFaUHocO?BaY}e?i3!d3Z zP5EDm-xza!5oo8=&n(N{we*Y!^()@$5M=GURhE8)I~h)q3lg8LSy>K(8z+-#UQ)Z7 z7QQ`nT@_m1`&(is%D->qoXfD@WVhsjMXQ%Y2s?55k6@njpRLxfuUORT)Ba?C^umXV zrL*O-wpj=P*r5`*?jUf5a;qQ;p;g=RUhNI?Fk^x&r*^l>jjxD6bFj*?##xJ2qa_WoD+h@a4(g~?f)Pi=JnPG8a9zg3T# zJ@8D*SDiusM+n1Onzp0<<0eZxK1L9ppTG5PEs+*9QA;GX{_v>nJRy${+D(xC0;l!X z0_T?&?72$3>>$Ez8X0GY3)ga~XZ^=5hCOk7Yr-lZNS&1wx4A}4as~lfZrFScT76u9F46H&XLbk~bg;{sa!lgkDW+%}z3cp2ri7-*ju}J$ zt@H$9N%+EK8@w|e?SAk^U&(Ia!k8=Ejd0-J(XG7wNUH#r)&*sMt7$^_mhjJO2|?hB z#Y7=`0L|A|6vuyk)$a@WE9dt9>HL(4DBgV4Bf*Zs6eT=YXOEoDzfk4=ZM2PJ&AHQL zMe<%(*8ohdh(49q^u~^N4Hj)M&mw#&^|`t zXG~bPg~LB)On6C4cd2D6BqH_%TtAf3E^S|~a-oRAf8L51^5kbqA7w)bnsIK6q)ohN zNxsHB;#h*aJW9qoq^;8`-;>?kMYq;elYPCHS!!{+?jDL|f1Bo)LuUgfn@?U=UnpOi z*!{k?;_60=tGcoaP~P2*vTe{1arn}3&DQN^peq3|j;}SzVvlCMG~W%nmpg(qVX{*V z54AxvhVMdX?Z3Mwd+MBr>z&B{0CwVNyvyHk0=Xt%61w+9&zDk*9G+e?L}SmZtfwIL zLxsuG4ygdB2aE#enUY2_^RhQ+9oZA3QtBB0_~-qicS0P5Hevdps9 z^r(C$p6EhheIsd`>>teS*(tavsJp_PbsU3-E3I!zp-xpP0^K?8XXhDjf6DFqZmDy? zus#`)W~U;YCuAeRn3_ROOpKBu{;WE*F=ogY2U&RrrCKSyFTkw`LPTZ7Pnla_;dtrb z@IEF6r+5@*0Q!S%9uhJnGy5ygtlB&h7d3&rhZv!hoE&@*bH13Y;=gzQe!97|QZs}a zBS?No3p#j?U^q`iGH~e3rEg`^{lFCgYCt6|iylI1TH0_JF$a+>!|v2DnqUTE6#9-q z49O%eo+NVf-%T!%wA9e%$}zUI$*41QY z%K6B@tH&3X=&80dij`2M;4=}58ByWD%8EiizpLbDeKW+KJ{P^xRIO-+BmLgHC~n8+ zk``IR2UsJo`*O5PIVd9ZW1$-=sgRuDFCjzG=Z*wHH&p1XU&o1S(=*D)c{WdD zQ{8vK=X-8}^@N;cdb6i3TqXVPCzZFSFIB?QNkTZf`lfX!O}HI!2^1OFW#+HsEsCB6 z+{YTR%lh0337nGN48}Uvu*pi5t4r}}PvCq%v#%M^OC@Q@l}NZJYa$sY_1dYIg>U8q zO&~tnQo`q4d(r&XgQb#xErBS#f`Y-rRMt5V9JGM+j*WmCbP;;qXHJ^LV{q~9zzWfs zp$n-wpfg2{V3Z&#F%OXF(@I#vtPIz+!Zu=B+70_UBxC`E6@{>zVwPnHXtS2!{G3cv}!U>kqv zfKRLCh1uq?PNcubLQQBAm8LHnWc3LZb_Jh@e>35>AR5K(JEp&>|1JePmN)7WBCPT~ zpRW7Faj4Iaa_I|JInkl5i^(%U$Ys|yT<8jQas?m><+l!QS@7k)imqo-lnW8rXLG!z z`ZNeVI6Rd6!EXy1G*IY!@?5O|^Xt^<@*S(x%}$`cu4U!qm?q_{?}k}Rjw^<4&I*N0 zA{~Os5y^=3$-Yi)^{n4T0wk5_ewbG#RWkl;;xP5kMrB~|d3?<<3D?gD9(iLR=y~JT z(tXRVM*YCvObd3B;scDIW}+~ErDz$s;B2lxU)(W*NhLNInI>oN2&lio04oo;mqFoW z`WRf~uaaLt=rwKT1y~pULUL5<=LQF#zkXPkV05Y-h#$uahyY8aCsXLP6kCmU@0isxB)qCO_n062j2P_ z^9_~ws0OG4c+=_+_?{pXYvWToWdhJYwf+4?>X-{;~V4q=RJ-+_F^#L$CLLx z=QXcji52fEEUy*bp!H3oeOAeq_3p1bJP1#_!_Q7qRAl>u>Q9pA%m;I?04E3xIyd^{ z*k_7csa}2eflK^X*1`4bNF*YJjmK^4ffE+^rZCi1CEkO!!&lcOLL=p5qJt)xVB885Ka z+6bO235bb<+%@J$r=Zj`Iz2u>oE4s0ZUM)k3NvD7p%(+bYN7Ty_CuTpnn0vv0V>f9 zFpi$k`V+-z0O<=l-g^|;tCk=b{#9tTnFzd75qJ^2OwryLX+YKFg8bcr)!Q`uhZta4Qb-nEGkdfXxCjg(k&;8NJbXZq5L`clXv zY`60<%-I}GH(rF`^$iG&bidO3N&>z_Kfzk4pfsM(nWe5>j;Zz}(Uf)3&CYf_B4j}Y z_;8goLFAScieOI(LIO}MW1t8iC>|#FjX(lLL;bDVV0tH?=nw23eZgT%ZbWL9t}L4sBPZ+!sE-`L{mz|W*<&&1J(4>D4|Bz25M(Y-gWRCR z6ydw}mKW5qb3xGE1Zcoj>=OqqoxfV&sL_M4bucF=4f|-;^%=ZM5i>8jIfUSgIKTqe zuC6>2+!^rMYiS;xA=%j{N8PysMQBWHR565%=Aj;kJozJHPhgdk}>}FL)m#4 zSr!(EsLgRRc;Iig*FE+udET%o@=>V%69Rg=x}}jdo-!?Wu*3)LJiv9prNUTFEXHGg zKv=R$>_>uO{NXpn^AEWEzw7k-LyNXb*BYgI8(~dex%)8a8HxYb+JhZ)!|*xP3*HwE zFVs6$Ive=QtzVYa^#&{Z74U*b@9xyuIW#2*hniw8CybSt z;!}An(2lx+)Q}(i>D~?lV*uQPfg*^w=|Zq3VtzTw4a8?~ql?rNIMba>6MF{ci~*ru zSh2ErHyjg$pfd-e5~zz1$y>*v+a`qld_|u?A>MSA5CoMq4Eje9ndKH!?k$hfcj)bc z3mz1z-?m)z>Op^*%ghO+ zY=K&kjRobTbpqI&BATm+e$WGAK#)WvA^y2@4e($Wd!Kk9x#d95O1@mHrmh|YenWGh z=82DO0JBIc5k$4B#P~!162hqV1Y=dZ_}>GTWr|BFz%4C)`U7(+ch?7tI0jSe0i z_BO+el8fAkXz+n_sL4FD4c+=~_hXJMMV4wl(+hcd{~Oz3rz*x&_U+aF4A`Ie{Of6*guS3+0w7SAV{F%FCE7Upz4u?(9H4uTE5^C`MeOzYiqaBE}s zt;_xAeX~CTSxg?iBr#qsX}Z?iOVv=mV)9(IuQy%QuJP!TsnL@{O*kWa<0(yxyZB6< z*N%R&ZQGH*Fet!-wk0;MOE*U;R=i=S&iL(b*VJ20`xP#Rs=w|#g%CE6($NpCc1Tq9 z;x*_cZtaRa4=-QCHjh!^OKCKTl`XW3xW_yPh1D#Gsz84&of0QPOWSCwxnpg?Q;>8AVAAMz zJ9QH1GuU*i@6X@nx_z7GMMkT&Sq5W!Nd(9RAt_|&{k;A8>_3N)mS2%qRy*Bd{#UkVrG}!rxNY zdO1Ja-0fPlrH%>lfAW!KN!!KZiu%g89lmgJ)KA5wkRsnuEirKn)sXXj_vaZxnYy5z zrdP@gcu>kd$1aUGV?GNqBWOW>Z*WPDaJ_-Sc0#WRRmBBaNf((b04+y<--}}ZD2z0SNT1 z%h+GK*LlK{)PaF}=)P}c8Z~`->e&+t=w6y=i2Dk0^v;!EMEh9g@G+L>C!p5V6hOq@ zKRBqu)Ex|RB$fNkZ&3Exnzx3q9vXgO)Da)<$D;=i zFu4!mH_DaO6`^=AN1nZlp}KcG-0YH2Y@WJZC0A~8a9I=^1s$S)uP*%tyvC7HcI$j^ zP~c!g?6&>-a{Q(*y6@xG_p?)03N-IaWwjLt12_7BK(iTh-pye}1}0N|+%GqxKc8$2 zjFj)6Xhy|Y;GwwNL$y@jkd0pjXN>7rHoqbtGIci(vfV%_>JDG+{viDFqtv}~=xOaWVl;ReNT1$Y(B}+{vIUaKt-y?I;#NHP)A`JXrmfq$Y4+0GB zb~f~lkxSE_yW%1|SumIH<);%>XL3B46hg2_Zdo*;Xn$Iiri*k1S$rO}F~{4Q`h|0n zivyZx_Nn|8#>mIN9>s!T2ZgY{-xh-!d>U|tLV9u#_US+Z$}RN7g8I_5vzNzBoVg@V z&siOrD=_W|8OVv!FcTkNKn0kuynp$(i2NnYXE2YLVbZM08v(@K+1G4Q0M=e!TG0~D z@aHBn=$3V^Z>HLRKgdW-y|`9F@XE8r0D#$K=A6$}M)Y7#N9SY-J55NA#PfS-4K-Z+ zgO+6Q(EoCE+1vLX#-yyTD!d6?PDY*t;wx{(^b&D_C%yiu=z^_Vv-6{=fjd{>X(@1U zud|`|DMPoLi21qUrPyEpL?YP3>qFrj_ck(!!Udtn8~2~2ps%R(@&PkI$a3XCG*5(J zH^VOfF0^qBicv0bq(Rfd=2pH;OhnYfG6X(>;cnnuk*;2|y9YXzBF{bB-_Bsd*18$% zD`cz{e4eDx3Op@9hbs!|lkp9RC06zBe@Ob^&+$oIkm7@dXp2i(Qb~+9&pMgGr!+*= zx!@h{P;O5}*~v_XW8CdvfGH)C++D8!4JVN8a9tEXiL|G6R5wbniqtqtK$S*R?Tc1x z+BFUYdcyn{C=n{UDPXN9fH8I^#zLYfN%jJ}oUIt^b z8kTe45UUN)5rD6UM7q*_G5u9OoX|x18&3gFf$B7cu3AgmE%ol6!9CVuJ4Q(N6dhf(jP!Nu;hFPCeBFD z`IlX@V&+;CKRF z#f*%MfV!uOP;YE(ToZm;$^8w0kd1+diZX>|+N^+0Ret>A{D}R~Oq}kn)#71O@JItd z3b>6w5Vujj0KRc9hNr+dU0TzZtaWQUJHJGB?kMLnbQb93F9DI~MFiGhm2$+5EH_XJ zfL|tJKivM^bAKi{Dmj?|<@O^=x(L!1Z;fk$S*GyOG8e#Fmu}s_Htp)&ao2FJN z0*3vVmNDmg7<<_GQHtRAmpguwTT7m}`OtCmMnX+g3o(*!;Zh|!N|R`gB$ zRhVnr)*qOS76FZ!B!kfibgYPdKx?-fbh;a$t*@+oub!tXES)zvRM|O*(TD;ca0T9% zHN3aLARgMhqNfK>-oATRopKg_=X(f5V-fJDDDVo>(S0`d#P|0_ta3o41}Jvf_`yAC z>AA9Dy{1cwf@p#M`p^EDr-x8rx+bEL?O>$zpVPVKG?V)N)8xrRP*om}xmUjTk-g)? zoOe>-TUYwNuS>+P=}HD;{dh9(hc2^c(5}NDK|_I)>vMgtO!YNP>1@9gAr~2>v(}AAQW#$fnk`rcJfKIO414QK* zO!LPAzKLd4@xRg6;1T){12E&pB_g7Rdw>3hEb@K5p@^a zqE598KNfTT@op_dI|_aT)^F~hchkSlmV7*r-4LBC@{hl(%r)(1W;hoym2>squj(JC zFZn-&vitwsEAjvTfK`fd|GJrMc4}E!92!uqcZ=M<$RzSfyQ|x*STr>rJinJnLVQvC z!GXoS><Ay_-lH1m(uk;n00M_Aoy zYBT;KjJn?SNUuHHD$-+eM7xXqY#MD@zq)zFbK?XjZL9RR^>#-TqtRN1RCZ^V!;wqs z5w)bIrDcJcyVDMn$=b-wZry4{x0uu3suiSEUqoC`VIu`<-XI#2kMd_>}J~1w$vSy@CVya^+%fUfO!} zJRLJLJ~^Zm!=Th3z+nqF$p1zxFd&vKL;sUE1mE$jI+h_5IB)^kY+#_v=m_eQ_=JV} z2A7q0I4tP%uy+9C>~ZS2kdI%F`N(uU=L7r5R#b`Oc=-^MZ>I_<(=l)a>kE}OmTvs> zUiQxWx^CylfDb8Bd|y$>iiM#(n8rSRU0+*E0d6JS@;6>_v+(egeLc^?rL)^5Jo z_5dE6+wZLSTI_q2kTF#Vu5krJYhX0-8GhBoPaa5yZcq_gO{yamE>d;k=jSffB0l_8 zKgr+(swc6(gGJ07VkQEf^^@iCTerb(Zw73Lo{+cATZq5{j#wjqo``AaTiQYXRuRCz z64cT3f!*QenF&!M&`aMq>}JPK_8f#%Va%OPYJR2uh7TgMGJ+oG8MQ2IwvZ^I16d1 zFzl)Kp(ElHJlXD7DbI(oq!HL|+K#__=YAKImk^s+!&(S|Nkx!S4mF>gpCudv_^#hq zRBV-|_PSm+31VSRXgUynB=m%~B65YpciQR=xyd-e%@7<(?>7M$7!Cz)`|Vf&?PtKx zumy2n&N-oW|3Ul|{4hhFKi{zMsw+z>%u<7$jE&@1xt+w~KCYgeE;>q*SZlnZq)~gb zzGUZur0Z`yx344kI*j^NKGfuY=tUOne1X{imZH)4Jgf+LoW3j&qkgozA^{@`NM+Lb zIr9|^O4(0@AeVPtn}+5#?| zEk+x-G80|7GSB%+6Fv-*|H8>_E7723M)|@muCYma{c}DKa=4IdMW6`rBha8CJ~x7A z$6KgnKi9R|t@DVnSK++K(h}T&d&rE~<9n?;tZGFaZeTozq#^<7>=_U+(!hxljtw4I z;x{nX)h+xvVAbsH&E#0xQNqFYpe+2+?4X&O zJ7Vz12JM4^$Vb$K)~x`_L5x-5L4GyXraRC(siJL%7bWpo#g*B_y9SoHn@dUcaQn*q+z7CFhDBo7m zf}r-@c^%vSlw#x7&ql@5EhPGwsN?mf3&;;eCMQoY9)V3qd@P06LVEB7cEsJ#u&`#t z!4Ehtq^KgpCTxT(VJoLGR9X+G;p6(N!mg0hHy^S(nh*83q=@H#b*Bq1Q7}CzF|3a? zR7+Q;r=hzj_Pl^7I(n9e{Pn1T@8B&ynXEza`Cm9mE&j8wZYnu#Gs@4;7iqEdVB*m8 zaT3o^_DyR@6uB994QEXm7N>1*TU@3V!In@aw!Vt5il&Wuvs7HNm3u$fz1zhyH#;(x z*qP-fD#j#Pa`k&%HWPa=4eA#CJ@NR+9)qLjT~I5gOMv8(xAu@%`0R00Od&Tp0i&=B zLDpl1jxpi4&UkbJPQFYu4NKc!`YtxyYRuQS=fDg^|Jg44gIdscGhbd+O;tQgT_S!2 zuUEIGvh-7nxnyJdGe&-If;c8mmq(w{Xe3>J`-ix!Sap6;_nKno_ofIsewsk&!5G7| z9~eZ(cMI3hW^CwM!cFZ0IJ{4MqNrmD@f902t>??S0?Ka^ar0Wx&(`g)>)t(A85ygo z#Q1bfBd;QH>+wJ3w{k}_Fae$bLNpH6MCICl@PMZ%CKi?DueTk^ewd`bbA`{VNO`)6P!+Ow#JJl1Jw-;o9ZxbU0{5b=`@ z%=i7kfVAAVl4LOzKGdDq7z5e`M;5RwJmT&6#Ik?TXvup-p&D1?Npjw2<1Pz{ti0!= zdfv=JsU+oL(QNhTao9y!yM)zS z%rBo-eYQ#pd|xi{{&#PwG zLn5cQGECWHm%yd=bgX4kHoYN|2c*RV!tCh~0`B%vuZ(@}^TKV1$g&|mAILj`dIK^o!NwQW za79ChmhjO>GeX&9s_da)$FD6f@JV?W+oMbis=(1GLD0QqyE-immBGln{|i!SfT9BY zX5%je=0ZXfA8tT=F~Vm8Vns0^c`#n0G67Q#g?g3e<-=iddr>S`FH;NrbRaX9;cQPi ziMXO|;iXr3mgl8=V4KMHf%KY3^UZKS@Qa7zj zacHd--e~-?`}^_4$9fx2)MW8N z|DSR#D$j5eF3mX3OvNII|#7(bCg1YZFeCeu)EPTl!W~rm{~8D)g7l$$=@Qo z#U-Yub_J3gY3g=)juHx?N9aZifucwR++e?A#6sa^mBA>U*-!gHQ9s$CeN*-6@iqA? zSJ<+yNhJ~ff^wRdCD)O}ys62L%-Dz)FH?UW3%8TEZwF*%uhpo14;v8_ck~*F*ywx>Y zhL06I&p=6g&isxGg^}l8PFfm7b8309$njL=#i;p18ge4WUTI%7s!g?l#k!1STe(lE*E3HR!TgcKWQ7 zSWP*l+D^4I_X5hSE{G8++5ae#yL&(HYXAndPne$T)SP!p3OYTUu#7}d#$aSRlnt5d zJz^C5+m|Bz)QBks(&q^8h!^xXYHEfUPvwix6laz=;GL%mfPQ+hLDXVobjnBKpo%5_ z0NF>z9p=(QUk1UZzWQAV*$Lvir%t}36L|eLWgN7u=$PkCN~NKBt9CK}Di>zfMSpV% zHTSA*7gL4v1c8C)LS8&cR85V)Idz7TbX_~|EJ~yJ`k(TSl>(z}AsV{o1sGO2@l!H^ zvpay`@`<;l;-oBL4N#G3gQ|Nd!4MAx&SLe)n{ue#d{qb^dR4b}oCqs;FFq2&5mob#tu|5G_I~-1Qi`0XMKOuEQ=rX#XLHoAcA2P9+D4PX$Uh zr0&tt(#}M)$^mM1L5U6|>riQh6kHWG`>3t#_T$_vn~A?O$K|Wln4g>Vd#B#Gmj2nVrrVp5#bf~Inqlfk?854!3DW!FLU7#tod1R5m zCQLPHHuzi#pnk^+A(T-jNg<2G)K;wdhyPKDHz1+KMc*3^NKHW^SUtDU1Cn_q1zs7`# zS@8AC_T*%;;ZOYo7Wc2&7YiAC>;y5<5YYSbOhUXc@#Y zz9`u#S5>2(J91{*vU2P)FQs&xZ1J`gMMVckh{|`r8QJagF%dIVe}#M3=VZRW9LIOS zVJ=E=nMvzW%IU$)QI|_Lygyb)!()_(E9FybTq8P)n*E-+m0bVNsze6_D7l+pp$fMV z4P|0#N-K0?^qU0sYw(Chp}5Jy_>^ueu5pCH4EmX)|nJc zJSBw>%q{MM5g>87Vrq=)B#c2)Jam}AUr*v=AsdwKFG3%m4R-O-vt>m`RSnhYNc+5Fw4|g3C?RLMVgbPpA)_EFM zuV>-}f2f#b!>6hl(M0F(s%&8xA4#0@4m2>ok_iC zkIG6KxE1*)xe0G+L^xUGEshhSO3H1VM^hz}x)|IPVV!;JZ1slapRy~2v~8h}7!wUh zr71C-z?ySj=*z-K(Lf`MbAbvIwGCn5y}i9W-(X({+IY+I1xOhqf%zrzweWiunqW_g z1d;v*B6=PtB!FP5c@`}ak&-?K_wp()#lqoCuvfjtWkvuuc7|tx83J&jbSo7KmFB^I zhK3~Z%G!AYn;9}xzd)4bjEf%Rh%`fP$hi6Z^*l$mIW6Ut8FPtVb8hI@#s1FR0C!68 zfYi)0E!AWJw5udK>D!hMl+DZ!YYr;yogKTKGR~L+oIcLF??=Dt;HaA(t+-pK?3f;0k-PWF^$Be2>&D*Oh z&dC6px!^|Lf>&;c{=@FIPW80|JaoLih_>(j_ay0Hd+OG2I6jVi(q*nS#OS3*MIa?* z+i{-Xsmj@`pj~Gu&d2MIO}23HqH?su z=B(?p1@rhPAL>_MeOvj_W4o+b*dO@rElufzN29kBn3)Yf*nrB-=(NGXHLW&TYD;kx+anpz}>n&Ly661F=3Gfp3tpcqSnsdOgjj@r_ zf1VKn@gCGr_hF-uxH+4jJXVBSLI6acet-=-3M=wCFZt$qD#^Y`+THrVq=x8#Qw+-; zQoLB%t06`oHI>w~@r)ganH--eCnkkMffb{QFTCkadAOU$=_X4~?3WfGsdY=iSh;xk zV)D+LLYf98O-Ax5O`QNo+uNctHd*SK`#)sdeZH7I6K3x=MkU^0JZEV0kla<87!x-8 zYr(>2ofbl&N{O$+(C zyAi6`r>Kk?|NKy2!#it~(2c{>_BVq)jQ!t;v5=Z^N`;p{TbJ#?=c zMwP_mNqZ3Z2hgMqPScj4Zimp7xUR%tMb4|S>)$#T@_R7p<{$n?=)khCZiaa;l9vO{ z*pMe}fTW^un@7gPLhGCUxciN8SI6Ctnl=b71>3VD`8Ctsb&kAp2;#CLIDF0T%mkj+ zm!MFG|AE_R1)9l&uhzUBCDnm}fmM|392`Y@sr2X2XOr7GA>W+<{EfG7X%>KP>ph!b zm^xD|By~}Vz?3_i;Ry*UcBj=0uGTq!alZ*mKZe(7G?>G5m$IE6u(3+Px*Sz!bu&y# zits2)s$e%qBpt7(+B38FC-VzBdDG3l@rf0M23+)hkwy&>Gl__3eQ8*x^r73SE>_A`g)Gu8ajj8IJOW;Azv_2-8%Wv_q`KFt(~HyR*ZY zTr--_z@Xq+>^GaKAf)?z&ueSGKFv9$p(<(;TzVt5koWeY-N!=Mi{dwvMtxR%yNvvb z-;OYlYkv0i6Yd^|mjGWrFaAd7m!+3omVv^GZ~Ry?yl_&{+ayDr$8hSD4-RO{74=6s z9!g=gvso9u*!rccrpM+fVzI828W;c1&`%p1P-Qu`KN8Lz=I&WfR4RL{0n=s2f(ke> zap);fNKpfFKE-unq)4I|I(92t=sU=K@v)oSd9|ZtFBCZjZ$F*FSwAXHpPc z*ZY=$qH=KqOuKIUok~UkASoy?_8|)ZiCZa5-5Y&OB1gw?^Ja%XMoI-P>aWynu)eOv zCW0V|+V#))B_}Nphj=$GikL$!Qyr7R@7k#Rt5YQ-M}bqxpocOKQV(V7F-xD*ZC;?Z-8^{R-tl9J z;-Y^KuZ;oM!JDr``Go=Pt@3)|6SPvtzG!We6??OXYN}~;0_0bI!N97W=3Vb^qQ2^~ z?vLj~Uiiyq`nBYQW$E8PCqDk}^Us*Oc{h0TuHfxtosi`zF%e%Q!Fly?v<*N6+A(Wq zy!MjOMSV*lhw0e1L*r2Ek*bw(qOs~Sxj3;vmFI12x^6~))M~g$CI67|a>C#7&1~dv zVqyXY1_sF9lSmQ60HKM@AeLnzdD?;{)ND5k5I zJ~(O#D_<@)YQeM6K_NhmH7WR}Be_kTDFP%YCE$J{@ma8M#9)26%l->s+nc7`{pQ?y z7$Z+_1%nc0+jX9VA`ISNM)^+(GhCj(6rlGqnfpCEn--oe#jB{?yiqn)MeuUmTqlU(LJi*6CTX3x6_bLi+h&Z6zVMQi1ffcd-luUj zyrEShA=g-&?}ajs=Jv%y|IqPUuEPpW3EZRrF24#VANnA4{IqRT{*S&22cs=p>)%aP zGL;b+Qim%YtSjXL(Eurv^dJzv{e3sZ57qV@K;nvM+Aj?H&yetN=kfC}V*qill(u%4X#tRo)`9)k z|K0(azK|mg5r!$6No>{Kzu?X|j_G!7Q>Bt?;n{8NF@*RsviB;qDm;@T2CYBr%*H1R z;=;B#?#rDs^-wdUy;g9P<7}OyaQ=+UsD9BB%_Qr+LZdZ@=n6xorkWP1tb(a%dJ#@# zC=qclEuk#+p= z4N-;aZ7<7SEe(BEEf{#(7nvT9C=vesFMAXbtKuegVdw1_S;Q^QV?Oiu=KwhK>A`$F zYI*c6EPOw2Q@fg;^Eq0%lfV}9Sw>DyLRR*LV#no6m!6?vU>l6{9$^$iTa^YgUqbc8 z9(OM2dYR6R*2oZhYhs&S_Nt#D?w3!By~|u zoxwdqDtC(!$aq`_)~Q?adkCO=pX!ZQmb6R{u>c=xR>k4{Me?>R_F=a(74shy+8KD5 zz-eR!Cam96en$3N&V-!;up$yP-m8FRJ+mAPE3NVWHb5x#66T(mx_k`dC4xkkpevIu)q zm?kEklTmOvqx@{t_^sk+M@f3%^_%n=62aJC+a6KV+Ki!+ykiLkHV5%^4hFcWxIWIW zerN3<0-e_L=RMp?yhYg`bRD*n3Qe;tyHCqycweeyifJD03ex_=af=G`e<1omEVGw} zmKF_M04WJ=%EAczX$%0q=vzvc2&UubA2SsOo^0QgWf>VT47=Y5^%Vg=a;TH5M1-jW ztyJGqo#&o-;V?*_Rmt9|3<`a@&B3A6e(|ajD_7vnl;qo2A?KlsgRto0DzVsgytc@wldV%awC_F|$-dD7Jc z-w%eKUdzQ&ySp1tukUpWA6yaMrZ-u}G87g|x~}}}yZ0r7ZdteXTt*Ug^kqtcAdFP+ z<=eF@3!H@I!rtI+Ur5Wxl78mP9~;H+pJpcG(;cWm-i`LEG9g)qdab_)axS^7Zoi2# z0(Qc+yDF#CzmW(x5Qyo&pC^N&uH$hvxjG3hF761#Y!tlP7{GQ;jIXgN^4M9tH(C#Y zS_mN>Nk>5-jyrcW;=ZynG2wmv`n3$NO%7(UFzh6NwHG$O^tW#@0j2=j2zc*9qH7%1 z5zqV~JVLr3W(zCPq4Kgn(wl`aS2nBn+gs1{%^6t~#0re>u8NydGyp@(au$+wMlz<08xSW*&GL zN>KbI=>fbjCP!rQutGVQ+_w3Y;jH_+S8#>$ln=^h%HqPKxc9YtZBD!X1iL{2b$jnv zd*W2unXt5FE$~0ZmEf7;FkbIeI!FJEs3iDO>+?&k4$E$KS|vl1YZpcze~q?&T)4O7 zeoQktw5JeEQFn5HS)pe4UFC(;1!3QbkKckgIf+Y#gN2wi(hTQ4A^F-#<#rfnH5d0_4w85`@|P;S9-Eiyj7wGo~iew0y(V;WD= zO!@e;ccGR%MxndHZFC`Y(O(qL;gWrg4rsUgBsO0$MPMOdN_|Q~gxTq~?2x^Eh!($+ zTPXcC$&R2hEL%NuVODN;v23~9g-em2G@OCFcj%!|xyDwU3b)-YO8X=!5?z9iT`tYK z%;lWMP1tQ2{e9`=G|eLGSo8)1Ji{}sH&2N!-pJS6ZSu9@qCI!bwN;mRT!QMnOlD{l z?+T{sStW~k_3-cJA$MxRr>r@u*IbV%acW;{TSJ5fHb$+XcFB^lv7v&(OnBtKH=d9X z@uENmgw@l#u{on(aIZZkhqnKc^qMDrOLBj=sJz0eH+2%tIb!CtmLESpUw&8TJ~EuG zV%YArC&3kyAf(Tw6 z&u&_6YrkSLm5bh&ZBoxEkC=as`XxTW-yA{873#$!HPegO=2D4*woltiOD`!oipl#)MgnQRmIHGwwVuf=lBVSbQrrOJoKboT8!zSKDdt&$Rs9Y35XuR~IdReFnM zO?jVXhlC)_-X&364I-(_Q%+nj?CPk&aJnRODx{aA?a%!X%TL$olj>Y7%L5S8?Llqq9uM*_}nQX9GEcC6Pdxd zI#ns@u`+W(D#R`9?&Fm3TYHlith`TY<_tv}lkqv{Hg2X@Xzv}>QEYe+_8U3*Xh;0B z!}Jy_MqZfJuLzzrT=?XRFC-F06?{bO>5ZO>qY$y3iMORWxK@15$4XHxA=U3)XbFpm z&-D}?nP?iBi!jGQp#Zq4orU`9r82N*P`+Vd1R)vclm*Mr6sGSVB=-xf4z1ei1)#1M zlIS{@E#wvkcdrCxt4ln7?)TN~85=*p`RKRTmLpbHwC;61fU(QU0PWOoc>Z%=Fm=H2 zyh>bG0_UYb5?zYYh}Cl5%9?7FRuC=kdF7lO40TEY&GGQ&!S64>33*O0n&Vu@&A5jl zVNKDQlcTicRI7>3M@HVGrhmcp@lBLy)}4l!NYtw?7s;}G3cQGZTxnfaimtA9=PaH? z!;(>hu!smv(>2lDsUj+JeNP_q*E}NBb8E+=Xm*MJ$kCktYp4^WOdRCkDQ)r?@zGr?Y;J|Ltve9#gsCo~kx~ zMqeL}E-w=gp>mD|jx%+zV-2uy`9lLqT8OS)x@ahWU+{)r=obTD1_7M7<)-acfs(LE zx%dG2ZwbP$X8vH@z6d$<|5t_YL}1P~?mx7-nP$OMaMhtH*wOxoZ6d*O@Ks|K}q7a~Qy@*$^SzRSjF+&g3`k7 z{r#iQvTk0RdWD|j`A*mGS=5j*7CBkjZcF0NKklc<9&N*DjgZraI|F=>Db>(9^CZM{&@#P+X+Jf2;P9`A@ab zU%yI7iDBZ@29{X@%;coSL1GxDwuJ%tZcPqCdfKsRfTU|>c+o_Ug|;LoRZ7kIL*}#=qR*MMgQ zb5+bigx3uIRNDV_f9NCHXwxzq87^5D0c^6R5x0ELriR%(QN*X}Auc4pQ1J<9;6{8! zt$Gy_IBEo4h(Pmgy*amI%H#H9ZZpz7)y}+m(y=0d5We5UI+KHSC7~vxe2Vvo$*oKO z5jC&iP|cdF#=xv!L^&Gj`l$YID;fcEiHgBqN{ZFR-TRe`^Mw0M;%)?}u@t@Ioh4bp zlTRsP{c0I$s!~>tZVufOtcFd-D5b%^@JHWAN)==0|5<(h{%TO+1!P>cvuUh&^g4E)J;l5!Am4ocK%{;&FnlnjeapO#OhZwo@si`F zLlbTe2WDxtPI(=nzKx~I8@oK$>CwR0cE?Qh=D~*#*Y}s7>fCO`jvdjMSsu(9e1`pL zwu@|nmy1%d6Ia(~{+ml}eLl87N}j#QNL!C!^Ho^;Q?!OobmkjoS{!U#9t|gLDDNHb z#EZF0lY8Me-_1mAJ#lC+;=^1fgFp8p9sGp+T37A%zSvKxl&>o@O8OC~1!!iRmb8+rOw z*56}YY3DzXxi`VjrR%yUG_X50x=pS~u?Y=OSH7(4k zI^8MsHl^35q(U{|1@Vt`?LU4*OnSJ-8JuN-#GF%tvt0(5^euzz6sv`e0c$9H;NDL zrkN^)ADkj6Z~lDb9`%PH;;+?-lHnRnCjIOFIeZUqID-6abB)~RbtnB)osS;jM08Vc z^yOk5@04VuIsYv9oy3mUFvZ4>mwSk{&z!6)#DnE?l6w)QG`#As7+sv%`@BGimPHx$ zrmB?jx=z+`2y(2IJK+x^O$n+}<13OY({HLCp-{SP)b+P)zG!96 zO2pNf__Hz!V(&9&=yKjA^*I_7v3;~u$2L zy)W8Vxl#Lvny*(m6zw)Y3rkQVyHKIxm(k2i;_uGp`^0tDBgz(b_n)bquQ>aJ?%Vp9 zq~R^$dD|6B2d0Qt6UkuA8jlnqCbUeIOcV-NSsUKgGMh=+onqBIDyr0g6~dR1&RzUG z159YPg#TiraoX*Le?ut5LoM9D(J^w+C2oCo{4r#!W^u9Teu=o=Y^>%)o?17g0eUhne1ch1f8^5EG5kRfOIiZs#`hz+a~Wu{HL`{o5xu9^t`(217{?O zGi0w^rnxk@nC2Bzd*GqO$70$(5NSVRFz2ILNcdp=mr?jvr)Df1IDmiu+YN%9si|=# zcF*cWHBb2Mn3}O@OMzZAK^E40J>z(*^Q7=C6YVgE>DqTKBYRQ?yU**ptKE_8eAS)(j|)nl4WEUdMM-YDxj*}-w9CjO zV~*@KOdpF_$RL=GU$gPkz&oGLI{rPVke3Im%=x4%qLFzq?IM;bLH>;$nqS7# z>;ii(6L57F+CF-IjgGV!I(`~fyJ@*#Yz$4OJuk0OlaHQ+hL>*76E!&!deiopx%YU9 zGzOhr6`%V}gVR(bb`R2cuf5ETFPaPO2E0b%-4ZKjo6w$)rsIlaPB(gp2%B5BBy3z-q8qTWNkLSNl*~ z&{7p$P}ry8cVeN7k$5?l!@GJ*J*XrZcuv{7hg8ew+P{7^tQa@hn+_U2W@R!CBdc|3 zVl*H0(vN=alpC^y?mIdEtZ7A}RFAOk#&*Y`%}!Ka;#U+C*-@^<^@P1bsR%|q4s$6d zr=z?gFa05Z*qy>F*ZPFK^x3Yym)=197x;(TZYOtjiM>+QX8rQrCAjaUqH3O@tiqSK z`X^*^)LwkqPhvCcscdT~s}~lE##eLmo@G|y3P+G+Xk$GTr<0eFF=T#nFtM6OqkSf; z*Zzb}LrI{`z5||br@_I-!QXa`1w4sFv9A{kv9F4)xtPlMGo@{+Y1ptX?>G~wSvE&p zLn|1iC`~!Fxh8a8x@{?%0XKRWKla`wl|bgcrDgpTt2EQ#c4+q(O+^CaE5fNGqBQi$ zxli41H@v^Oj-^VO1 z+zE@Y8`(P^*JkI^IA6P+{Zp=I7*jz@pc(Lco$opWkIU22>O#6NS(j`o%`BOI9y_wu zM#G{ERSfB=;~e9*x2{Fsj@L$IarGtGwdD=RIK3;fNMYe`2-80E3GL5>=XU=(>G)!` zQtyJKg1Gq(M&b3j5|ZJ}aeA@KzIW`X!cFAtt=^c9KbqOT&k=wdKUnHw{P^Kid}LoRM+U?;E6p^R>SX)?tVnB2X{Gx{#8#QWG|@CTs1rw|kFs&KTc6A435h#QQwYoOfQodp2|6vh{l^rGQGH?TT<1_2-KH2^}F*whhncH*Csi$QR?ZFI!?SYtT7b`78<3mfNFBLjZ#YsD^ zRdof$m18O_2Jk;Tzd3zfw%gk?JU@a}F}NR#<91W%3I$n@el~Tni};OydBd^eTO95! z{>buSz!~(+>m2E1f6O@Tuf->PBCcx;ryhCZJ>B|a)sv|CNLs+|l5dER5(Y=8YCm;w zucXKC;#lytXKZ=K+GHyA>Rqmk52$jEh?5|34ZOOzgD*LEuyEQ_Hr54BM*3#&;IP9m z44$6*z9d!Hx_YIDpASksvYqGvOX*DM2W|Z&R#qw}F>UId=8wyb6nEJlY;~WOC#R0q zyvt_&$jEknD~7wCzeyKlQ5V}QP@X#Py)BEGaZx`xVO|NM)ej8bkn#P;cmqG84r&o- zPl`3BcI@3ELju+WdiUUQ)Z|J`H7WV2JVULsJ&PJjp|x3VbNi zk-m_cRcT1W;(UgZ6tbIk%hiF(DM_R({YqBGp{2*%Li)=CE6#Fj>BltV+Gl7|dvv{B z_@wsLpkatv5)a*1Tx#GSnBVTlEyXqJ(7B)LD|u96%ojs_rWS17B(!cf9lV3dz1>q! zI*n^tiEiN~RC1R-b}3)K@=V*!MDOtLfwb<}W&AGYDBFt)i`f&)%?%(Dc3m3?H$B^# zgp-jXdh-r3jLq^@*T^D20W+9!ct$v3xRr_iq=iADfvb7rl~)})l~D%DmRQ^bs&oD4i#??jCf1%I%lI+j5+I@7AU^! z&G$3HC3}YU!saQF_e+wX&m|`>3+vo*x(tZc9ihAIf7HJkmD9h$O%-1#q02m;wJzKI ze6wt82Pd<5IxstRI-m&yu05@~k*Gh#iRipuV?pFUOJFm;3kj4NdLdRG?oS`%MgCmz zcI}tOOTh7g^l`7O;dY!+1j^~S-Hj5@VM~M%#Lj~1$q&KlSLQp+O|3!0rV4=^=1i4C zK?gOLEdp~Y2d%rFC0$9j5(M;l-G#%s5&VlpCId@19!`$?D!Ludg8H zkFI@wX#OKHH&>j;;ja`85qbp)d03_V#pS02iuBt`IfeV_Tu{q>OU)p*k~bVED;2=U z&fXsI2A|TpnNQD)cBtf_+3yuICTMt)+n$+H-RKDVaPPep##^G4id58275H3hZx8&E!+I_9Qsxo!IDZW9NH?8$ zEUcmNsXXaI-AV$`Mj=u$k8!>X?txErT031m5e1(O z*O@BHQ#R`4P!Ph#Af5BFksm~DlX?gYcf}p*{Bb5j4+Z>>&G18skMi=pt5L&77bvFl z4!O9x78ady*p_`&QdCpn__%YD$K}&kkM)7W^WB|v=e5hFugpW{B0M$@3=Xw+nT-p& zsO`(KhXZoUg$Q%*W;iR-?z+37l5DFbQLhi(DOKw+oZ)8i-LIb5wVb-qlqym_?7i-OC!z_@!`wu^vtpv=hl3EcEINp>M%ekp&C zPg&1uRN@cgCrFOxk$HP%1T%iNoa4OYA+bp0Ck2@YP361;cY)_ECjRU9zKshD7@F2j z(ioz6PoI(pK38&aztl|$5lcG!ktv_gmUnwjsN``I$|7eS+STcLu=j=zGnA0Spx<-x z;KpLjA=7Y@Av-jCa!u&d)_U4ru51DU-%t+GX|p@m>&~8lDw_EZP(4G0B%nit0SI4g z95IV>{N#vb^pkv%BKKuDyNOff2blyGY`6_wNNNvEwZoyqHvQTfx&B*)Y@s9i+UHcS zk&~-k%eF!}hUOP8ZiHRO8LCv3$*EpHZNTv)b;JMiJm>bBgDE%Xn!l8TV>Orm;ngOO z-R=yWdZhoD&>vl`O_L7KyBy*6EJeZUFxABIyzZU;>jFqt&$f?-%6Sk6IH64fzwd;h z7X6dzrLaK;q|xaYf)?|uHwHI9L)3WhoyTpG!1iZ3Xt$LBRRz(vmHrPb3Yl0o^HoZ> zJKM{FO1LYJB|jUjcT-pJp@D?5(p{+xGzd4>o(_^!()CNR!rxLDON+!pD`b>g3ZBs@ zw6>Ize(9&0@z!x%de@6XOwvGx?g$^Eec+LKcF*{6d}XL0GP`rrsT>q4i1SHr$IYbO zmjvS@Erc}|cI*uLpBxzWcA&S`X$a zHtCu1+~>~%X$f$uq|Yj{!`+*IFe+ilyPeQ;o>>nX%)p=lc?YrUv=ReC;;NS<_75P%;oIgA(yewWam-roAg$+g6 zMVgi(K_;T&!2|vK+hs&)&r@cUmfOC-9_oVep`K0w^RM1Bc((y$X2m!JQHa;^czL1S zpB-uXr_Szb+j8ZfF<)_9yzl^X`9sCjKAkY#Z?qt&SJzg=Pb@74OpT6=vRgG`k-$KN zwTLoUy!ii<5u_^%XXI3fre7!xKF5jW>SUw55{D$FBc4Xg7hk)I@qc((Vy zUXR$Egs>{Hat#mf`9VDWDuF4G7=}^h#!JTWXWsj~n69sg zSkU}HQp{b0UjKF0swb$0&*v$L3tr@4z(7Gk+Eux;vq@>uZXqWXWVtUM@U?=sftZ1e z1n>&QXa({UWwtLs2X5KK#49uZl}S_9(fwdJ{o1VeV;i0f!DLx)uWH?#+K=6YK|( zFlO@l!wW=U*|y|vnJ>=@ly0={B*%y@b#Gu5I#ys{~sHg4lbZZ}~#S3yC&2JB8w!WO zjUZsR5^*$_no_RU)Rb@dNEz83hMu5WZG>15ONVb)JHKb?MJJ3)j4I{eATRJ?ups8g zn-e@A5pz2TQP zECgP5jO-J09lj#xE9%C>=XSNV`h&-7k8yc8NKUMg3CRcxYJ!k{M8XPY3(Z8E3PQ1{ zY7&AwF;tgNVUi3AT`}z8{&jWkv*mOhpb47ih2z(Ra&#nb%KF4|&hYp+ePYX7Lz&%z z=5x0R3Yys$KyQTblHELGBgp&p-bwSi-3#QNn`RaW`6JkGLD+kn&ks*rs8QHel$(Oq z(D;UOEinH__Q>8msTk;0m#=K)F4;FYfBH{$`5gM6i)+*l#!b)ArM8$$b|fz;qc-`r zfaR0)dQb@5FBRU4i!^7;7;f?#xAYcyJd{8}cDL3$DH0AloM0@@AbL6!Gs9O>STr~c zA{!;zf;*IQo93SvlvQyn<~vsm1kP3af;x9Ly&1Z(mg^dyT*T2Qqk)+X8D>Y%jWtHs zWe#C|dw{Z*1J|YS%xhOsDPJ!zSlCg(3#B6Dn{mIq({LB&WaYv1(`UfsMA5%7cq@JG z5*EKRyRrS1X@1=C7ZNmpMT{icw>R!d2uPm7cCO))alL-ZJUvbAym}UX^vCsbseqMi zhXIN@PdzsmO_jUh>{nuPs0y87e0=hlN@)ppgbch9r1B;2>9@&TmZe|C&DlXr zCSZDyH(^%(;VP`HUqti@Mw++NBc{FT{cAC8*QR&r7V^R-%XR!BGMB4w6IkC+3rr~M zhJi2*Ap=4E*i%KCf4LJz^_LVzxcl~&Aoc>wXtYLFamGTQ@TsW9qz-6nwe-VrhG_gr&p;FUYzcpQczP0VgVv zaLnURpR5LYd&7dPl}tFp@NCjtZ0jLXD_&r3!LUANR2(i#@k-%?pmnvse~S_RY|AB2 z)^#K^9|`bm3JD2<%UOG=p7682Uj&QEjKMASyd|BXaiAXUUT|^Mb&|jcXE83o8bX|u zWHKK4on{ly#?NokJZmMs*BA3P8OQAw7`n3c)Cc*LL<@H5yIBJdtDB*zZ z;r#Z^JK+0E9;$-zKzafk7VGf`-Y2S3$iGvx5?=8^t?pk>hwF%22ntTQm$+VuKGXgP za%XAf?g`6U!%@Q0Oy}8&rBDQD05Ne=Oy)M?F7s9EplAN_2l#-o#@)63k2kB*fSWSb z_+W1@?}Tz#D;SftMB@U0pUW6kc)uQiiCA(w9(VsS60HXnw9wHqK))ki9k2hfQJT@w z7afnsF0XXGptXDX5$YY4jDNS0cT+QoeW40XF(nM;R|a@kDD~VVoM&X8X7|+2J4u;t zk7+TJV}nYr;Vo{AJmg!&F+@eOv*9cm8CoEqG;866;tiRtI}(fnh!kebx1fP<-TyDl|fqSBc0ymPL!80^NCo`i2xW5t7q7x)X25~G5o zHJPbSS!PKmAbk=b+tHi3>v#vICr=Q9=ShQ74&&y51N%G08E2yDHj+gmmg4sGR1wd; zLxzcpDx=bcgT3iav<71}oju+?!HM8FgvK@8DLErw`NqFL~;6#Jt{{MGm$ z+G!Oyq{mEMPVV82QA%MGF?UB5BT6)9JzhC4cI!%TklwFBbfk#yGczE8@)XtLH=K|F zq)uB);9quz2qfeIbfvW#J_QnI&Hnmvx}Re_-Kk8jL1xwk{-QSMf|z!~G?z4*Iff@C z+*Bqz8?bVsXJP$JtK~g+ZETR5@>Ti!-X6*Ea)Ers9RD{q%x+TsO(vi4qY{HNq!>h} zJ^2y1`QO=~KtN>@X+z6ts6<@1W!oX#SczVJr6rb&o9VWK7G8_r;}W6s_iuRRx+pik zqKAuq%*kH9J-amm6bn)$NJAV$+dgS*dvttY$@TT&WTbx!*ME9p!{t)HF~AlCG3QOj zyHTd|bMEu{7(*=uOfhXo_s5OJv>rPPs+U|@XEGA(z(-qcq5z=rcd!1WL;~llJa=P8 z@EFCkJy*lS+{ey11aOI)9`O`F)drji^Py5OCv@b=0Aw)I0H>r7jZF2>`F@^?e+MkV|T z4F7Q|axmQ1OIdX8nuvBo$Wr+%`ITE^#9!C&cZ|pU;8|Nj@DZ+|BYFM50$W%G!a(cj>CC8I%V-v4}cOXt$THVg4${}*KV(4Bx z`-Hc$B0LepbEn~jxYK#zqCEB+&Yi8 z-gab0Ay73Gn{x!e0n|i{1xI<%o@iNGy1EnpbvOQgM~n*V%=i%G-r`8@>$XFFdMn~W zv@gDUh3H1Y$vYB8^V3kdrRVu=F|V?|2mCjNzKYx12)mDz65bRN;5a2{2oQ%K2E7Ee zaxrBkB?ttt?m+%4ax`_%{rSb(C)tBK&4z}sKHI;m#`qxPs6fdZGc%zdX6kw=V9AQ+ z&b?=8<7h`?No*gxS=Lqn^J~MQV|)SLQ5Bb>_6bcBS`LNgTk5?u-7pjvT1~cPDM!}3g(-v2R#|Y9ftpY_{@dc60uR{k%VGKyI2*tXmffEj-#r_>Ia4rpAwvYpSNLWYnwf}AIA%2>g+*( zGxnbeWOi9*<2z79_Xl!llNT!9WOLB1xa&RKt#h+nrs z!tS1;eQXVWVEOA%JG-Qjo^F!xrSJtTm?p~gaMBjZLO*75%x>Qf?yngd#8_M(fmuG= zT?7_#d3*OU0v)>Dtg-{E1#Ry$lF*(CrKo0*grabbB6yS zpf(|&SXo<#r|@h&1`WPEG!_+ym~ zM+7YD{k;*1K~)HqiSxgH5YxHLf4X4*M6&;%oEIZD7ezfU)_rpsmQE@Pj2R9tp%tF47jXHbZ^|t}l_08QW_+S&PyuWnefos~)E|oT~B4{;bXT z@t%vJWcE9)d3jzD99!{xILsqz<4>7rJ0AV)S+2NW)B99SwJ%?-NJk9M>Xv<#bd3JF z_->|$LVCOOwj!kG!v?zP-M0y=pPGuK&zA6G4c>e0LS_I>k-b(dtlu-E%l zf+jsszH~HwCAi$sEz8k7HH9Dn555P`C!fVA4~6~*$;o(uq^UMS4pPTJAseUn_Qu>v zB&Kh7E&zE0$>e+z>%iC{vnR`#a%J|?%GvNKSA+YLQ#F7}IT`m_@jPf_r#zHM=4(0T z6_xC^Dk9SewHxC25PVV4IAt8E$;co^AD~75mZR{ckF9j_P(|v0Zj3mU3Rz}H_g>7A zjOo(>T3I5SH-LY-slY*-A~zZfa&nV}f`x3S=q1e3>dPe}^IKVW1O>5E4yg`rcG{O( zb>7s5z0t$2ap_Qvq$52(j8Tm|+I;D*&ji(~{qlp}d0kf8(2Aom{pE=_Qg-!5L7&5I zT@&i)u|d04)Zc6J-|siyc2;oOskzrT>kk>L>|2P7*T(HVppl14b^_`wXfzYxZ6TPv zdqRWw$zCrRfA(xRhKV~g3aU5HZ*}pv%-Tygo$U8w+6=Mv{M4?Y};96_i2a6RX6?$d1)Cgj)=YYQ4Xoae;V!V4T5YcXTc@4kNZi zV>E&cyI`+mR#8_Zq+xYD8RkmTfe2K!J=Elx*ku$=e6=ct4V4s$_~m;`Vc6;J;Bb?s zaHVN)HV(bZKsupnpVqtonWDc;bcc!kHGx05%2>hw8}0@wqM)y#VQ<~VgfG>vIb?C6 zClm|!2GO59Y*7!p@{|CRQI`WtwWR{;jMxg&>Kz(dcht$MhO&LfMG8703~{RYHtT1- zhY~Dc)BSW&o|?!WFP@)LIS>dKC1nj6DU86%6xPYGEiLWcLq#)Qq)JA5jb!kGfzWzl z=@j!wO@0IcfOZWRE}q2mIws2Z<{1|=)2ZfdEg@diO*Ff1a)VATZXY|3cJ~p7zBff4 zd;F37Jl@R=!>3VDkBH&BVi%=a2jR#&J7F(Y^Rd#o3DTw1;UJgD>0 zbQ8}{wMU}85rn)ClTi{9ShO_#s-|tRyiR4ja0qD4;_JZK`RU)C#nu)?* zum|gQ9c=4#m^4SeCk+4C;SpRee|l0Z__@^mJI=?aH_#S4j*h;KrF~|5g5zr{dlu-^ z`+Y&|rwozU)_P8VOrdbg+;@n%SfA94g~`)x<=bjxVN?*h~rt#qcU7 zua|F`zc868t0_%{bU~$;sm7@oT8Oa*Maq!(3`=EVF}Ye`0kidpGCG@Y4;fuRAm4UL z<{p++cpST!jIS)u(0Cfk-Z@*11=ZO!qWVctSrqMeHddbUeeZ&5OfFK4EB@3r; z7?7)jga8HLi~)Mt&dZ-BQ`&rVb926I4P4QNS9;3yVma&RTWqLO?1H73F;3=C@EUcCE-ac=w52;$gW4chv|eN~Ue&_Pr68+ZC7zUL&LRL6uO@95Pp zs_b99E@4!fAjDG)hHi`uNe-2Da{DOH*0}Hq5%Ti4x^w0K+T89(lsJhFMx#0~DxhoG zN7)|Y1@4d#v=Okw*~d{T^ma%|lqc;dPtd^$`O^IhQY-qC84@k;Q z4i=-<>*u@oqEK}YMM>^f^K~51wBcv))8}VyTAU1sxL>8bJLvU6hq0ZuH;}pDC{7C5 z2U@exp@wD$ulhM(onaBn1jJ_MkhAezm~G*zS2wvIKW9d>GckjDDODA<$I{wLMMwiu zR`{K;;oHU1Ug|jn5=qbXh?lV?L^pk*I~Pkj;6s%vXz zt{wLv^9nMkrSVJ&8wmounP+{T0DdKKMN``8dxWA;PWRiG;?kENbO*$(r4(988BWkJ zE(sF|upNp5C=)NP;wx8CFAoQp<7083mXD}`1xs2~(4lP|w*n}W)!d+(z}X$%IF3dY ztY;(*Cs`rkhg0`j$k1L-k8cY6ixD`fay5k>L1%6j47)i^vxewCbBSTDEVLn(*#EgH zUr~#|0+lIH4%>Bl0bIt?dEaGD72*c=OK1oH8z?n3A(t@fxS?_cHkvA#1gZCKL?VJ- zD#S!JrT|pWp7{E3D_^XnL+}GI)|eZ~98{PO>ME$Us{Rx(n&u5mkE}m_qG*7EKD44& z%i5~5Ay#ys+ALu`8BCt#jtY%zw#M1vC545xjc<|lM1HyoV&8pSOzp~P#F1cs$Y50( zS+A4Y;NE8*(&e9YAzip{n1@U|!@X4P%{f$59hO3k*>27VGS19+__#?~Zi`9dtjxQ< zY44eA+GIW`a7^<$+338n;$cQrm61T3R%z%6Hh*YR$nS%PJpPBP_Q7DaP4^F{Bu-~G>$4LJ1L3JsbV8}PavVi24Ac#&IoeM-pE=ozY{mxZWk!#i!-fugp+=(a+M zEw<<2MbwFC;0#v{)gGIz;W(bh54CK zl>pGp$^~3q*6Tb>w)O? z)~IN4(6ePB&ED%Es~^Fu$q-c8IfErKrDfOyfOERV;ey5#4e&lN1GV2+VFozhN)}Hl`dl<{_UUUG>-KUXdZ3p= z&h5!3(S2PYyLOc~mIvd)2Y-AT$9*)uo&(dYU8^eQ9>7l)k3&cR{|xesGEqL-qJ6J& z2D(yb;P(~Dp0T-e!M-s|VfUio05x7F1kymqoTWwT07AYpa)EQM2md6$m|)o_T_WQDV0UPMvs9Z66_?RiIZ9n$Cq67Hw7MN{u^jxSAoC%L>qkC4MC`C`knblZGwEbk&vu6 z6rrQz3VQ0^RNnK5vb44%hNc#Q26+rlvt8@Uzzp&C6&bIp=Cf2I^+ED< z6Kin0p9zTSUEt$5?Va3)_|Q75ZLR~ICKfykFg|wh1r=vo@+Pt3qQ_xje7vLFv%*3|a-XWe0Xx>cC(dP%dFnou;mQNj#6u3ID# zvzKChPYF*A0UHLKKp{?yDM$@eWo5rQg#hjytrmUQg$>y2V|^}b7B5^}C3d zf&6J3FGf6YW7ud1#dE$L)%FJob!%dr^Fb&Pk%ga$lKOiJmbwt*4tdi3Qz*;x?zf8m^R?Wf^cfhU+WZ&&8v_zB}r<89IsNCeF_*PSKaW_GGtQef%0 zoZZzBOFCc2DJDzKvo9*u#GgSFgzCgRFwt{`75n#J*^G`c5;q>g{9U`jt z7>X9>@g3YjB0M|Ne5eS{iKkHzuyu*XBAZc}^a~REFm=`x8Pcz#6-KKw<8^XxjcPWl z9kADUcXUE5*yAjeg6*FoFbfRywntkT=r`}ldR|IHY!(xyK`$^7#@j`h64F332FcR)_wdhtRtd=6i2sb-eLee$O3XAO)dY44o#O7=hlP>?dlLwyVg#ZDGld$} zUF8S9XoT?a_1DxsW)d`?U$m0uH0=Bi@%hMi{iKeypm{bHMbBN?M9mr$Qus8qaZg?R z&ED%TvQ4g?FRxE>q}Q{#+2i838m3x^yNfS==lf25sq+rlX5cLKpLmo9>Zwu^((_i zJ-2=J)3H8t9)PZ%96eUz3TbDV`TD&b0-gldR{JXL%_xn2L06qz_AZz3@y`S)Z^ue8 zJ$2y@^inB?!+aqnD0wh3&w7Idq!vr%Z_(YIb~b>Q&`S6pDfeF$ z$YI+@uABncqh~zFXFEvoX5%lo7xH|+!a{IE(JgiHU(vCB(86k-9Vvnxs+q9YHo)?X+g(8gR=`kP)AkiGw81h@MTqv*r1{!92?AGez?2V z;hpmjHFoQ$+;6BN)f~&~JFk=%wTw=7i48nHB~L+Z1i)eBzY6&EHdeP^ zy#mpF1YWlC3`>oj^U%oJXNf{@IvCuqwaaT>D!CEe-{XKJu1mCmnPJo$Fb!#?I}as9 z{vdDhJ@A%`Ynn!{m-B;>!Tow-S*a>kM8c@e4gDDQ&7`Pc-{OF> z%a=D-cu&bi9xuEX`NS3sIw(6ahJb?I;m+%d4i&klkR%p5H0#B8L=N|UtC5En{lYNn zO{_FflQL3WEhQu*?0FQs;u~#hQSXNiqcomjSWoDw=+mV%XJH&H@=@)U?;l)YKzKfS zkN{_ub~eU4Uip}j{Tdxj3mW?N5%}xoI#(XC>~9yQbV?asp1S;47fG>SrVuP8T|tOo z#Yv8RFwaM2(AD^sp4FO@ncn1%u42V34P61S8=Sl%II(R7tBp>|>{=t@jLN@C!Zi#1ouxFt=uFtF?O$G7r+6WW@ zMA;SP%Pf?Qxj`lR6%O%n)Vbv?C^=Z158`vUtGnxFzf$+B6CQb>P+d`d~>r{{79Q6_y5+z zMeJJh-oV;5f_1K%6d23EF=B}NBjok&8T&C-C?G<4SZOfhWUFlD|HyCtdP$pktw6%= z1%iv+$?NGO_}l9c3p9#9dMkf@N~{cub^m<{;9vjOjSEg=cbmwtO>7aI9Q$!Q_ikavC;lf+)}o;r@Fd z#*h0A|2nn!C`E|i65>Boj51dL%^E?Kj()LBbwlahfrIQ`-*f-GT~!3k6HLiuz2Os@ z=6fWFo@51idEI56Qxj^UWc2D+NjZKi7^6MqhO*<*L0FlmBA9-k7>G{e1bcTx`zMOM zs0IE~1A1O|JgW%VLv9ujSH=d1%vtVoZqng2w|)7VbtAhb`w00$yjBRU{hK-8x2`!d zs)rPxYzW3?g*(^BuNd5XupkPI;f+0eaI51vaiD5%#8?x(kR z5=*wo?Y7tEVRV@*{cwU&=TsJy-{+?eXIjbuJ1&3Ry`$ej9I!UGZHK`5-Oc(bu|zGg zMEm1xoT~>yP5x`fKE7+FmAkI-3I8=Ye!rR^;`9sZQr!NBQ=`s9pte-2`yCOmw6%?t zpxWX=Ji+r2R<>NeF!}Cyop5UUso1|I%UrSL(0`qTH*8yA){8YoCGAc5I?BfuCDM)0 zZhrx_RafFeU#Ta|Co>v~Qb!a0)|8?2-)Gl_7feN>A}}_r?0Tb#))|#K@89;3J!EJ_tuu{DM07f2+CV~Gj;aecXDw~{a+p&Yj&pDx5svP08CH#_#i9bhH~9u@rB0tcRRYe%o>l&x3@p0}&#>Wj{n9 zGt|>fGbxef{)UhcY#!g=C*TwSVbZ5CP2=^0%7aF!=pP5m16-ScB8pQ}a|x(x3dV-M zNe}h(pu5mxDwK80B!U?`wUax}$+C9)GvlA4a2{!#Xjgkiuskbd-(Yj&dZ0q`bQ@F< z!>7&7qLK7zirIG`IsmoRy-z7Mq^<~_=a80)|Cs40L|qIF3hw)ORmrH-ZrC)&MGWX0 z7_d>$Ap(lMVsCME+aV19I0NF!OGaJ0w@!^%3%BoK8u=ugJ$gfy-&TC{uWF`)If=Ct zGz3W^#K|3NASF7?LKEf51(}AXPB40Gw1Gq~LzUzA=q<5i_*LXc+w)GhBxZ74u28cd zvvbY4a1Yw=f(|BB4dgz3_`&``j=s}$^;0_5^mDMZH}%Tb*W1|jp=%l+NFa|&WIaM| zw4C3+$*nV+%y3oi&+s7v*QfZ@^_P)l{o}!<5D4IH93B;pKUe^)=?F&YT+y}w zjONjp0`VM*I=^$fNJ!nbS(U1a!K(R#e_`coql6+wS^ZQWp!tCfe z;{#k81_Gv7#UD+rlIwqY3%rQf)|_K|v%|jx7&BE*qbEt7tfHAsOxtQ(>Tw}}^Xx%H z=X+-R5Sl1ib{`Y>qJ&hH6=l_`MR+89DegDWI;fzf&Dg_htkNxk?s;2 zTh|l&K2F&Os)MlwTEehcQo}sEt&uQYgJ|W&s$Hey>6YHZ~gU)~ffF@5swre1|h*AnM{W zs74arWI-mg>cbb!#QfxW)y7LrJ?tJlT;>IL7wa>;5J>4Ps;j-O>tbibi;jJ6fSv-b zUZ<9jBmf5d0ZdlPzgcQ7s>iO1NNrEBr$+^nRjFLUdKASR?}y{?3F~U^qbbec<*4fd zgpePSMqayQ`IhCYH6QqUzRpaPoe(pi$Hpq@n@V7lxZH8`eA?OKbaxx@nyuoVueAEZ%mSov<099!H7H?G7)P<++YipZU*1>hsU_RWVd*29o-7&!EZf(_R-z57| za={ITczj%NZxA97OHt&yCCVCS`K9i@M07DT-vT*j&avfPLidgh^~t>D-sRD+9-nqy zgZMOI3T_-X%&~R)xg+bVC3jY9`i{*VYhSXUa2UQ%0jr-H;FA^=w0W0q7tFwE>u{>B z+#Z)52f_u(XQVwNj*FkE1K*~w=386nx_tys|8o+uInsyXVx z;_uFQjvLzdtrA^mMK1f(P?M!#;1*b!hS!E+l_MC#7!-2xj}r!j_k(BG2MI6ves4FR zcL9LTtt9~qD+VL3un83?_<$%PvS+l`L)NPEuG8q^E@ehXeG47>=6XMZAQN*2A)TpO z1^JT(7eHgdJ>VoD_|Isi3`3j;(Ihm)7;1rHEFWm2$Gy%yn1~RnL1cI5XHM%4V7gXl z`KtXE(x24@jRkkPQrQbvUUbn-O0gpp% zPOX%*tuM?}DE)!rmchic1&J|RU_t|7$`!0%@(&;4=lFpe z?5)n-a*u>xDN9fWrw=^c$3Y%sF!qY8ps@U`13emv6O19ii48%?<|TkU(x}utvCN-v zfWZ(rc9O3&cw?qo0t*4EZ2kU#hpxc>(nHeRdPd(`K$v-dZHtTm-aU!D`hAi5k)phl zv?Wv8(k-%{tdua&YCAC85`66zg9rdSP5p5C^x4l87@#s?`H&2&`r!$9&I%JU{L3P5 z`AAvCR=sX&jov>8yr>t2{sp>JOrJRrUkS*Y1M2H`r=LY~!9^I1%E<_PEg1PAOs7Rd0U8RAC@<4%`lm>Ta!z$Ra(+ z(;I|#3FiyvF%UZ(so!v-3g30lYKHyjGM#r+j4?x}iJZ^8^u7V{N4PO?y3sem-4-Ne zT_rSW6&iY*q5p&jn`#|kP}4`=MYiyb5JW{O~Gu5+?S}S9aL2oi@)7qK#AC9AAkUQyM5a7!0u!Gcc~--ruDiPIZk2 zEPue27V_@Olm1lS6kltHe1F@RPgpy=dNfv=wD<9?fVXmj9%5DbZ-yl10B$L+s(wG`-)v=a`OWh1Xk#4-v zp+wQ|{UOqq)l2QXgGx!EQtgDv0;X32(hu$vgD;(g$xIcZP8>1_Rljc}aqG!vr~-U7 zkd&P$ax$2g*28;p%^T3_ez-cI#C1}ywYiJ<8DQ1<7yKAw{sUn3`eME~S|v?AOeKw* zeF%m?#08Lhry9Y`hjooLXK597i`5>(EyJ+(9Xp|8mt`~4&sKw8tD9flQ{PhTZtS({vElWT{rH^#H$7@mb6JIK&LG6Y{F^zU5Tt_TC9 z$<$~5JuXcbcR7NSuS!h?jok@zlMD@r@WgWy$}Y1#KtyPf40 zrte)nc`V0UlQL}d0tAsOkmdbP-&ez=kT^=j#059ud`|m3qXDj0)JbrcFov#D0tq)M z1c>1LLJ}&Y%gv{%UbW*53IG}%^$oybK)e8=-Rm&L?Di>5Yjn#aDg->|v+AL9rNszJ z#BqK)<@Yzx8jV+4`%Qd|?*#HXHa?;|X@kTTNHxLajpB)=$@x!cO4rS4sr?K1BhZS# zKIskk0ckhZt0<}fOXimd&?3b+F^hC4NMUP0OBKY>Y}Betv|DXYKKkyZoZ!2oc%ac= zVI&cGqM%o(tIt2%1rOiuFO`j{xf0;W&6=z;W{VMhK7yQ||{fmcJf z#`hEK4!PXB?Cs^;^HcLvpV0!4Ss;}<$iWf;W0%x{nD@3*Tj!pp#zWVOIH7xJN)xqh zYe#~`SP=;(f*&n@PvFg4F)HzT!WAogv2k|G%d-@f%6-b>b!qbAV^p!Q*)-xMZIv=x zRp#iHKZJ~Tm_?70A)p^QRKquC1CNIfQPY7f0~ng+1Vr-B#rF;qwIJY#IljG^{MjC@ zZwgZqrN_MToqMpI-tz$kB45*hNKY)=d9hnKy4B6Sc%~fIu=TT?(^|64YgYOz+v>!p z?&Z|fB4`NRyb@eMKTM`<2gjojMUV`n(2I7+u@74k5CNb*-M!j+nI)U(@Z&4cS#Ju4 z4TISW9w}yrgDOB~K|s*FonEOH4%G5ys#miL4oZ_`RNPO98yqio1-#f=S{KC>(RfS1 z_Fr?JFNk@|f7F>~43aSQ+~o2}!4%4%U5;kp{lDf=DY<=5D?OH5${DIHE~)2ob6hM9w?BRrNXkaA{@V!b zyDZrh@3CIR=OkY#B&JT+cYx+#uOT)lIKtg!)2YyoE6h+ zSdA((S!MW{qJrn39?(%!QwN^Wm#a+X)Nb~0I569 ziYODqKfKK?6q>+=U0Q$Lv%FFHQjpRgy+WDIVJZE=Er4yUn*78<<@LSGtSMfUIWpJ1 zZu2R2vyS5Ds!J2Ms~%crPj!3}SM%}_q+_s9I`a)HpYSlI-m2yB$bJb9a+1AHln)Ov z$wVVY!v*UtTRAkIndT|v@Yf6yrxvee?ikIS%XuBS<;luc$5H!-LePGE8;KI83T@|h z0G19h-TOq;$04*qjJOa;&FzKzW7?D4^>OHezCekIiUO^E?p`U6nQ|K5;|{7AlNj|Z z+!(x`++%(7UndWEBOY_w+T@;ldeV~bXuxzUM*$2v)3H{lhP>a;)@*^C7nIl#XvWFE zgF&tYz#&ZRt(nj>ijPhdZ%!${F~M%gvRy6vFfN(RmP9AM^Ag@-y#89Hj+ebo7Y$fR9NAuWgUxcbQ6M@R*jU`devrOS;0c8;%BGaB0TdU5cp-+d@6> zSXZv?iygGi#%{y!IP5QH2`SZOoNi{ZZm;f)1$RuJTW7Tdy8&^m}9!(5GG^F7UZC9rr7F+iY-Kv4t z#6#7cm!?_E3oIov=M{>Oy5oW++h}7-5ga@fpzE4C-si@1AN@ss6Vm&u3lW{y8&nI^ zO;2*dnM-wR(SYb5$e*MeH+0lkDxD0;xSTj1ns3@5pH)j|LkG6E!_SNshcxK92?>1) z$@YIeSR3Jk0+e&fKTmbD4wRyaKUUBgs)_LgjjWbCtG56!7Gf!uHHG|VIf`oObN_cZdsTg5dX&|Ni;-1qn|J|>`hkIAu zo^Qu9X?@1;eQ;i|JjEbnJB-WGNrf#fwcc&_TZP9m9)kA&9QVf7=z3v336JP;22>U( zO&=B4>aXdh0+?rDOi9BAI(GI z(P`T^F_PIT0lr;Ei+oioK;(%^=s)mnJDr|n2yJA;;Xqr!fFH!s-UXE);t#=pjhP&S zDy&6^qCzWD4gWY^%7Rb|0ea-aPm$}8giSDiZ*~BQH7+jGwgcXY$bF;8VO$c=z%T*~ z(jjkw1@YdiiwB(ZZGz~=Gk8dv$0|IlLDG16unaS| zN|}E^QWRDV?F~_MgC5hMZ?xjz(bf*9d}S~+xp8!Q*V1OR^5q{H0>~>LXTwTVpj1nC z#=Hz`_j$OI%~v`Lo{sX4HQ%<;>xw0Sc#Ri{3u+LlM34K<=Zx;Ptpi@{9x~Pwru~&j zGla#{NCo;m_J`%UfCWFf$YnccR~l{K(t*PKXy0 z{{QgymSI_T`?~i{cS<+XARr*!9RdQfd=QC_!E**;e9`3pz5q5=>}A9$S5BzTSPMu-GeN)+-md^;t#4{B1Xqw$_ED)DPgls2uYhSUl zoL5vl497RNKFlC>O4E;Z9`lj-Q;68SBq=vWx9-q1sAM}ihS@4!$QG#G_(N8Ck)Qzj z4ZQ%BLVLe6rL-G(8y$ug4Fx(cm?nfu1wcEO5fc@{%!OBb{LFu_OvLQmw%G656ez!; zfJvzKhZK$>RCEtSXxhgep)WT5FX?8=y=E}vGE6$H9(z90J>-AP-aGBsbi5vp)5l0q z4Es|2qRYgT@^*Y|*{R{_f}ZGokDJ=*82kKg&nxP`aKmQ>i6~tz<3xgETHU@)3%2Z_TG^U6A#Ug49cRgIUMv)smZYN7J zr%_)Cs?+C^KKW`uMblCkvAB5<)eaw--JEBD16ikjf5rQ!V${Itu)GiX6_- zaC+)@fJ|C&S}`%9>4qF>Fx`4aJ))F?tp>07?6I>}z2t|1tsryG_dF@)r+`wm$9iQJ zxn3aCVBS*oIi7&WdTgh$e;eofLruC~Dasy_*57@`cFaA044NZ82z$}&4L^O9C#*T? zM8Zg|r7*y@jym#}3Obquq=gtZZ(d7-Hj|(*Ge%Pi8yl8oi7>@tWs5+5)H_!2{~OHT z!|plNnDucgy0d_M*;s_XbL$NCdn4L%nN-c%ZXfdy@GR=Q#F&pc^t&ateq23;as8P~ zoqSU7+Wp49rY=lR5eV*W9-3R%gk@~-B^BDLOuW4{CwBq=WYHFE7w@T&C%w|LVz zw|$kNG0^)7@eQ5Yj&Lwi*_C)))F&jVOMsD^>^4^zNj)2yrnv(HFHy=kCA>Uv%n zyYH#TVtxC^c-_NU`v!`C1MK?o6zMNyHBaw^#RD}fsWbmXO|7`_B{&%o^Iv3q%*-od z%He$fZujTnfWdvQ&$)2g+q5EQA6$#54)x3!#P{miOZTYaycu-7EV0Cd&&|h zTdIdbqj^|j7>DYJSrC39siCL0wW%i?n4Ou%4F!~fB@@qS>&l|mbL*Cn8+vQ@rvn6m z{(LyD=NwKTTSX)TI}}ElYwO zHg(x&Ee$@mEM%QZ4F7aD#;(c?oTm|??XQbJU3n_NL6%z%xeeu%86odF(55q^o(#Nhe zHXkq_pQ%9!YF9_@K1xNZFL&IFK);+X-slu1md}OAGVzh%+9^&EL0s%PDArTH#`g8x zu=r!}k~s0B{Iu0&2I#^L;9p7y-k|M{KHN%wa@zR%a^~~sgi;J?x*wkNN4U*lb!h0G zVAve_l$>CEu9kCJON|nVo%n(gEu)dzDrivB9>o{pNJg;cXqZY*ki?Qq(hrR)Ye5DB zblPWgz&B{m2{ZL8oU}2_fbcaBz5OSbfdWwBAe|1_MKY=9=dV~T8>V<_pH~rHZ>Gk8 z6n5NB69e+Z9kS=*z728<^aop2zIHRc`CHJln+7BpZ0Q)O)h#2_g{O*si4g**jiR=2 z!hfC$1DBypj7jpLf9RragM?Zlk(OYVV2OpyFv%kNY5R1XdblVjALtpkz#(r5;px*oINa@>ReC z**URQ_^dK2-Tx$J6ZCXN0ocNCC*}$F(uDycxrK>ozDKI{2bS>X?C(?m1@w9seK^ z5L;ZA>q0oiyn6Ll0yP3qq2}=6paLl}stX;`qo)?i_VeU$V$+G5rsgWXIFDGUf@mk! zgzy*XQ0x>XV*4=b7aZ67Zeq6(*gjaq@F=c~%2qT;c@rHb4^Abj?nPJFwDKG>#C?~$ zij5`RU>Q7Aq)I`s4gk^`$!+oai9van9G8=!9uagbIepMEzOWej z`*I0|pB40zV+9zls)ml3$^x?sumG6?8uD#$05mJ{TwnAoU01>Ar_zG974rMxj@p<_ zCSzS-RA`u_0LrHz111$v{tPcT4D(Q#Kk=NA<5|i2gZgeN=fqR&EX0L(y_tqEcXbSC zG|)6A%1uCNC}%T3Lnlk0ddu<(o(dExvklkaX(XO7C7{$Ds`ti<3qcK!I2^Rv0Qlhv zYH=sN3_A2JphLex3&i{^FZ^y8)^C;onR-wA%x8KZXQ%=5W?FNRE*$+iXu3HL@e$)1 zut1jWyqd2U>N=N<59^u!kR`Jj(*jE>B}NKYRW(BZHVkbE1E2Zw>LGV5^J+uq3D!t| z84Qvz`d)1F>F-G$Ut-&z{iA5^eFc)=^52DO_0}7R%_r;H?%vpVNqav)T>0`ajNSC% zqjHeO_-sWUF89Ov+w}%H9Q*bD?3Y|qq2pKP)V<#_C-Uu2eC*Jtdw$c&Zn=6Ha+y=H zj9DI%9cf-vPa~upI(r4B4OV_UzGfIWwdfqP@0O)n*+TbAA*yN9i|^g-EI+$~aU^pl z;#|#+%D3F}e*yD&?J4-+J;ore@3((38*32=$?1_av}y2L+%>RkH|3BPDIschDjnMw zwRxRaNg63T%#8wyUQjt~gaM+^H=x>-ZCfvk6dv+S*cxD5TH$EQ9(&23W5d#v=Wr3c zJSnuw_`Q8+FoFW@oM^rtBqr~Q&R1ASfx3bLcVR{-3g}!@mzkiIp|k0O^SejI?t|!x zBtBI|J&LEFsLWdqV?{I7^7&J|chQTbXARJDG`yz{PJ zfV9MSjboDTkn5K&F0cvqRGgm;N=UD`tmhHEd<21~&Xi_Q9^k4v;k}Jfkb!s7lHn=8U3PXM|3 z&e0ygZo<&*p<3)|sX^NBOijzboy+y@Ry**9vtf{tX;)XMc%*>pnHPpW$?psMjAc#M zbZX-_PIRkOs#vEt)YfGdP|J^Q7DY3dl|O5D-pf>yR9I?zC{&&BF!e(#<76mbh7=lQ zNAY;byLK5ndwAP>G`}?G9Lev#XJE_(CBITW(^n5?ix?1pR*l59gPFKkY;Jdy9I*8r z`f6aiE}B+JEmx%j+lGr*ZbmL1CBJi>DVFT=gFDwB1J$!tD0zo|awCJ~_TYOowQc`< zYi@3=E`rF!#$ZJGT;w$xgD+XLy)#(SC(szGej3I{nE=6NnKyn_f@9ylJIf&NIs zx83asJnihD_8I|(1k}=`zvohdu7z`5HT?=iK^4b&bTsII%D4yB{U$CDgR4Amn zLaXGZLV)#>fGfW$unLQ2$&Uhby~L4>HKkG%2r#e`d;cZT$*2O$2DL{=S z()6)sRB8T5d-Hsj|NgKoNS1&T87W(;V`35A&W-z(6cu>g)9}ed8V)QN5rD6J!%sSt zh`0VIE);_RSY4t_;@@ogzuXnwUfev${dGctKb(u7zy(dk)F_~k1FGVLMYRVVAR>zQ zNE#v$+PbMkwJy%rNPE0Ec_t1TEJ{{?MUOgRn+F0Md(9xUqQ~uf#fV#J!$vt+ zyjY9CzdIY7Y>oDnuAhDJ!8QBD+ghJpLR19g;ZmtY=t=knr7wERSks7VadQEi;l^%B z>}}XHqJ(hCVO_WOLMLF9iv_ZAX!IlmAT4t7yM{$ct7oU5$Fto_cQ-4*zSyyIcEw)J zMl;_z@{{%Ibuw7KZPVp{M?bQ1vV3XBqMkNPl8W?Hu76reSVJ#m624M(KUC9<>lkI! zb&<3hETY2aqdf?}1KfP!tR6KhE^(`iu!l}H<1roM%sMGDFB?_WB~La4zmIA}zm%PR zVUw}~FE!8#?Yiyrhc#?l9Fsn|Pxz-8ru5XPnr0OfZJ)^di2jv~*i^)!)W&=c^Oo`1 zS*<+kxaDC_jbB=s0Qc57Ruh*`l`Y-ky-@Xc1>+_dx5r(-fz!9WreQWN41?`1Z~Q)9 zuxRu(;|u9TRW3Xrn}C|xOx#_GLsk@D3Og5qzc97yn1L`F@xYDqwK~nm1WaS1@`;T_ z_MKN(3%GQQ!1neO|PfgvL5)Z_+1&9dURE&et;%xvC!Qk(6_d;OjC7Fs4o2o4Df~CA>R(I5Ira%)fb~9lC>Z28bGvVI_4u54UKMt5 zL#QvSNflSiM_`nv(hK4|2T2v;1jjnvO2&I+*d8EOm~LD@%Axt+}$IGMU-Z zKs&cpzY1n5g`H@WmQ+1)V);AkBoNaM2>H1_1t&@(-uyS9{GX&qXk_U2)qAyFusTe9rji$V6~v7c2yjbE&O#e%FaVhvy4@+q>~p zbv*9)tOB|^Nhq6w0{C%}Dr{cbSihfTkG?($EAR=byZouh>_`68>Ur4|)0js5#5+kXg|4;w%^&^s>%|V< zr~jh0{GEJ(a8UwGM27|IXG?ikWBIq~Wy_W!ePcyz$EAx>NsAeLBc(0o+~*3d!)3|o zbKUz#lS9b+54OpiQM^T=e{CX2jh378%DTtuQi!SrOBxTEDF*#PMWtDqwHtf|)LXu% zcK<_m((0eI5v?b2VToBxC-J2oXakD(c>|TvB1+!7gF_Z%9liZ7tnI6lekO<2@U!wc zP^CYrn|srnZpTMsJtxDa!rI^BJgBvHG_$6$JYF$_m4pSm99R%?b+K*bpyGhnvwl4n z^&T!IsNM9i$%Ju3$eyxilMbW)@Iwg0)t`z%0deA{rHhwz zber|{`8xXxTU(Bl1No9VNhn-Jgi?A2)5W#j(HrP=@}6jwpN$|8?z{76#j}kF;Nj0Q zD;2knhXOgQphD`_j;nYHwqYb^n2j3}B$<)eVJ40o8Ul`89ACHV4-o%EMY?bNDfV7I z2wwxO&5ks$gZY8;b3sbaGGI;>i$@-H%gPU>U4v|<*teeX_oVjU58(Zu^8T;UQ4k0w?D9jEV}>%4xKQt#M>{&t zqzgIrC%!L$BBB2?h5PTH`zRK0MdYIfq#mFwLj_JdwEgU4>8Zjw=T_QHY<3EQ(CWSA zI2;GIs(W-xK$4{1h z9SOzi8nvC+SdQQyq-a1a!k6O7q)VgY zjFt4Yt(FY3eB3X-XzP@OpmQ-jx^W~14>Y@!LUNJMlNpTa3p%&qAQHrPc99WMbO6w@ zbs-041aM~L``)li&6}CkVGt#>spY9Nw;!As`lulQfs#xms!lb!F-NxH2r6@(7I11v z6m)fV_M(J<&oMM9@7W(}Qko`r~E4sbmSNCbZUH~#L z;JOH8Z*&-uK@j?0;aH&0(yrmjG5ya4aDnjDT>G1f#DrV=|D1^wQ2f7m6guD4wNw!D z6Oq2$R{i2PrP*BV&b?7mpaHFCoBrw#MC))D2k!9-5F@6Wd!yO5l?{{h0J!L5!mq3l zh+%te61)E4U<9~UnN;;npv4Ig-Fhm+y|WBro8 zdV2m+C?Hk6w2TQ^;29^WXq^7>OJSgSh2edU&N)_HQ&s;5ouz3UfIPbDd`(_j+Bq2M zhM?f)aX*nwP3Z2Ur7-=ZfjK#!&YNR%leD>-f(MdiGa!tC0N6t)koIP%79*blI>Wr6 z+iUPz2+urrnzj;FrC-yaU$e3&?vwDA91fs}MT%yd>KdhJfEN3B;1mdspR?Q^H+rrg z7j&)}jz}#gfsE_Ei010WlR@I--cGwIYbgf55TxANimoNFFH3G|3@3-<5 zwswR5gHJJo;$pS{oL7Icj)wJb>%_UG`Hy8>+;9qZF7ENYF8eIHmpeTfPFrOu8-DZa z7lx9OI@9}ge9hA;RvM+>f&YSg=YO1{SwTGAJa_ru&r@@+Hl#yK{4W^~vLKGT$B6we*gW;tTxM9P)(*U?jD2R2^?!dwiKG zTb(X?U8Jl&hkL=}2`n^OfEDJG7b8HacO~Cyb8yWF%5@j&1JsbatKs*P`Fvc5^KqOf zh)I%sIb6-+Ufbj1TA7vG+TiEh3}czvt=Ly0kt6niI5cDYEP+i&Z3f-!i-^X%5FTX+ z3sE|3o=!yl!jf+_+C+&DR2y*Na^ITQz1#Wt0+24^x1XHSuFlK=dI{hH1=#3>bH07a z-Xs9J$g5vk4^BLkOxj;r``dMd50umR1+fo&>|QI~4lSNO7I9PpohIKFiSTJ#>h#2( zYOVzgupMkGWTC&q17cT+2*L&=AGJXMzQMfUysvLjYhYRPocuq6)h~ZhyR_h|fgm3v zvJ;``E};ZGd?*&5G-v*({*cnH@t2*!yQ>%UHZ9`{!$(VfhLkD)Tt0aO_H zH$oqzHo>uDR;+534bsu9B@$W;oB|INV0pTNe|RG<1#k^`YAe78G(>Od9|{u3x3^9K zz{cRH$z!e@@Bty^4L#AoD3ylieEx?VjejCpQ7_X}lYcmMv;0IuW5}qS+NN4{c+{E^ z-8@PQLgT!UseTz8@hxNf{*R^WRnyciUOerax=BdEU8Qi!F^qAKgt* zT|7;jS}OZf!rgOEuQN^LuddGTSsfE^Yn&u*SnWLfAC?Z znbL3xuI||51mv(c1}X$NnEVa&>J6cQfR|g%Yn}?weRISl16Bfl*3a#5{v)<<9(n5R z{)?03r_;8GtkLqn;E`IJ&7g%EovfAW^ZO4vexIn{?WO)Yk~;xy-F{e|E|1M9*a4D))^aAmrEQ0YfX&{+_$Kh}KWwDJnEzrU-2q%izM9XifjXJ?CmZivI5^p! zdp_=#HD|zAF@3?5oFW=f0HPw-x;tU7OVTANpGS;4mpnmhtwpc7imFR}YLV$|YdMDR zBHZ8aU47b^zr*qQF4Y>y>F{NI|5!OdW(tQmLm9L;U18ko+FWNnqn+2ZL|!@knPw(> z?Zt_w5Secm--4`L`fZA}Bx~DJ0I~y+oF~v62jC|l?u+13k=2v=^FNGOl?ak z4=8n4*r+W~+R#8zEM|%4E)cYiiFVYi;tYO)32U(xp8s4tRKvKV)n~UMIuWxu`bLlQ{8=r1M zbj*wW!jn@5mn4EH-h$gGhtCYIUmU6cFAV>)f#~aDCH`*XRNMKJ=~lM=vQAvGciO51 z+=iQd#rSO8Q=uhl3$Y39yf*_8FCgtqP1@rXsYzx*vUF0UR$A)IC+KQlhy^yY9j;AK zhws*7GC!y2^Iuq7yR~5qh+Msx%#jpzJUdF2Wqm$M#Pf*k?{nT_$joJ|pqIt+G#rEg32Y4>2 z6W@`H0YqtXkxTdHNb&CAp2)g*Pi)r#xLJu{LW&&z=zizTXEvn1P-c{dtK%e*|CYdg z8D(|X{4i3R4(En@trOcdj45uBh%C!bkXTnb6aRd^8}b zQY?}IA)>*z4=uf(P_VO5`a&EYG0mN7X#P;mGCHRoPN--aHCsS zY-|=pvF?@8ZTo|AAgB-=hF?k}tNiN!(0vn-nEKU~YEoW7qK)pyDWbprvpyeB8bT*W zYr1=qUq2%P7K+6++A#JN$)l?;vYVFNba7X)OFXCLwVf}nDJbj}_urE?evlv zA`l4A>Iw40=@d0WL5P_e-*NAb$7&c>5?XL|=EqcAj40g-C(B0qEefG=z1=!H*h!M| zA%ScEH0k??TEfCGPb|FEFi78v+(mNiLgV(~z}=Gx7i!;TA0!X*J3qid#yDcyX6HO;j<4=#iRyjW zwLJyQch6U|E78x{%2N6-68>|4+<7S?m8WMPQbZeu;HTp!-*Oni7Hok7nXtLv+1J50 z3l8ahSw5txjU&|7f>iYS+4IHA#Q<0L@9oCn&@TnWn)<-!c}1TaDJoh|`}k^ggoW{q z&xQ2AH!~NxxZ|g)U%plN3+mCE}wMz!x8L(K}iQbkW6>mR~&=aCU zD%-ORErwVHUHBE_uEIiSEYz#doW-Dyl45p`jugIQP5F3QQKe1KLm-;E1YroJjC=*X zc*f)HInP$kt}-E4y}6!u8;!Eyb&jutJRjgFF@^XK@b#423qQj)x3qz0QsB%NExHzA zR^$D2RNrg%V@Y6wp;(Y8*fctYZBAY~y8Efchz9m}Z`YaiHTI5>KEe!ni+1s(N$B(2 zl}q^yl%YuAhVq4d>DD&u2pg0`>kP#c|>mootDO+g z1@3WxW5Dn9MFdE|^~;=HPSIevk6a|^RM5Pc&0M?3OlN&ApkSa>t^Z9(cMf= zB11WuU|t3!savJNtJaP8*4GoP5D15D8%#gP`y&Z5y0%`99da%t$g9wb7B?5JSM4*i z$R(rxU7vrKPi@OIE7IM3OQvjqJ}fz_U@Onb6}5VK1p7~l67@trn?)2U3vf$!UYGfR7{o$$3XIJ2IOk7ZIDV` z`Sv!{vmT+)@2fKrwf37|2*HG|R43TK@{y5upp45f5nvgAq1A2+j4XF8aZj#An|`I- z<@gXvUetAHv-~GMLGeGg1B@?L4By(fISg5;1uU=cv;N|0CFX|Ki8>TO7EMBz>;jeIP0wc({kg@yJ)Pl~)I2u6eP&EA5$^X>gDn zV18|!Lb@OwQ+R5qWUYvfO`Gs>n~k4|N$GrOPW+2ksVlCHeq2*WRvo*TdulZ1!8byPX|o4N5`hk(trqKFHyzDe(~A8H>6 zgt+-}8L-4e><(-?hrW^N3NRzNR^fqDzPzB4=}%cAVoP@Teg>VH}fOj;ak|b zN%|!NpoS@Dc!@#A-rYY<1gdLkO|Ef!M{OOnLvg=b;Kg&ELjibd4iebu?e%2^*!QIN z3LTa^E74uhw`!nEzs7mWZ7h{UavYtM;&G;2-T`Pu;ogsL1s`5+OL!|y2Al-k!$T)b z9S8xZnBq;^Z$F?tvLL#zm_MwG{z1|_lAoz5u+?3;FN*7j{|7LD-vc(IuPR$0~ zQ(i|mU&H^U$)<0-Ll(gl+X(X1=e8lz@t0S&x9+?_jC*nRhNb5js^x40jBh&rm|YVo zn2a-9_sG9>C%w~WwVZ5?34G16jzmgjU_fq?ZYRI%>6MyYI_7!=xdE}*rn((L#f_$) z4sq?nO%he>!Ia(1$J|u@^1aPVakJW^;D5FT$qoo%Dz>i{5)$9FxbPfOkv$Eh{=K3* z4}-Sm&dvBr>j>YbxoVsRZF#liAb;-bpqK53Voi(RJ1^490xx3UFz^9|3f z#D+gj2|ME0s98WioGCMV!|X~HVbU?lji26u1LNw)(q(~Y;|&XU+TtvM1Bsuf2c5!$E{}T+Ba_{%rH^qnQYOf&K_9gWwZ+icveq+&W0e&@-v!ywKoa(ow#J)Y#}D( z?Ayqr0A7d)&(`o3&j(oH5 zH>~$#mcZ7l6+hYgh8YHWd9F3L!-$J^iD zCbLmHl|M{4B%oq(ML0r(NQU_c9pIZUe$8BU`xTa-%eZxw8(DmV6K`C++5f3u(v1M*td##-bvM*VwZdBoecC*JkI(U*qC3 zV~{ZJlCiNr@t7imqXl6CrsNnL)+^gUNbJcwwq7iN!OJv!yC?rM;AlGICW=@JE%uy~ zGqbD)S38bjM9Oa3c%)yW?!mYQTKB?ZgfBba7k43w0sJ{4M8GO2Wqq6`7QW1<@bnPyE6I|s>Qhf8{`&gWm?&iU%Q&uf{ zO5C$ZjW#wE5O7qVdww#X$gJs&3^d`e&TUs^mFF_HP!{+`Le@pZ`QS$%U z)qtXi3<$m5PLMjzauRO6w{Lu|R(7o_>7Wxv-KAz35gFJDUov+N;3I?V>pOfA9F)jV zL_&GacjHR~AVSU8x};Ru9E#E`kW8ee_Z5ESc^|&*(hX(J}Y#+WHl$A%2xQLmst1>biwr@i99vwV3C zriI?g%7(aV?lzA`dt4o3L?0fx=#i4zySe;0JQW?cGz_olh>yiQXS*lE2v+VH*mTSU z-|uq~d6pDPOEGTNyI$YIv$@co-09f-pnzu8YKVu}7t((BYOY zfOpMN();75=)s8@ECRDAJP^JR7*UbGwNJ?Ia5%8Z4{NGI2~>wk?GF5M_A%B8JSt$Z zEpP9u_0E4z4QbNIE?viTBFc0Uda5x^p)- zw<|o_g5VJ-EmUcHV^A-H76Uh^=OY9%2T4CKe()68iNpNcfbc0PQwE7xIxpBqOuFD6 zvV~Q?=dS`wk+-3Nnw`Q%M;P(lzpdbYejLd?aR=KEm3k48Y_Y7@k72*u!8h!b!u`%Y z`$_&HH5GB&A^YM9N9swmB2>uD?jVqj2g>hrVs2R917M+;tMFv^9vO8}g2o@kdITABRzf_=$V@@gh0+3#gS7@ArJq#l5sXD;@7J@zh!v zUnM__L*i)lMz9pRf{?D8zNDcx>W}fmHXq~Zkcfh>P0@9ZPm=VjGGWgw+CjXyJ7+b7 zBA^&yDS7ux(x0MzZjLzL`W2?5mgk|5GK_`I?WOVX5;+`1b8Kwl;_I&_x9q(cKS^&E zMCcRNMuKbnQY6CD8|Gx?iyx-|!@tx@_d;T2f4@6(GhK2n00!%l#&ptJzV!j#{MU+K z{>W1zC3tBq)3^`(IZkd=uTgarHh<+8Y;Z$-2=Y2O=Q>4ZnVF3*0MGn zc$zTlg@!WgC|#_gSY9z)P3JD2z~u~mC2lp22%L?X$~=jh>I7Jz*K&+De zB06Q#xKf2*NP^?A#Da7=?0 zMJ&7%w;Nn2Z z4Td#GgQx!Wh4(1vwJ69(4Ck~sI3^C8iVDRFFJX5}OVwd$D!yu2w#$H(b&Zc9ff@r9 zkTFy$e2)bd5T*oHmZF!DV59WSOffx>7=cD6$exTg$;qNgwxE^nhvs-1FMJ?R^OHc+ zbu?Z1F|oxfOOS1+&RWH8Bdv}okbV_JITeo|db5S_ao{Eno1OtvHhTE?! zK8)0;sOh7z^ruS{xsH77Ni+}&7iSf~cSy(8>UqdZlQ5H%>~qxmT)wsvrgS`8q#RRI z_MMRb@d{zQ^p@UP2XMhgnr9xz<0z-TK)1iNM~gPIjdrBWG@W8Oe=jC0JFdnFFIcHj zs#<3?TS2wcsK8%av}DGthg{Mvz&6$LBKsm6V`AG`0+x{P@|_8aT6b^&Ui?OYMP=H;Z2w z+hp$pcJ;^2G2?JsY;t)SKF~${T{<&IeHyda!g;DPy^c8y6WEJc#K)ig^lsDvfZevn zm2pVsjw(Dkj7N<)sXw>A3m}@$U#cX8OVT4d5pAX6C1fM~3chvN^T928)<{jlLf(AA z_ZI^R0lgYHdw#19_55LhycSNIaEy4{&gP3X&%oOzo>kBa-HTJ4ed!!eD(2_qwH!*( z{v{UkULmeOc$KL%n3)d$;DBY%?*xgHLgRk1sZYBC8${F%;G_#07J`BB)H+aACY2!; zrER$IO&rAn4S~Y|j=Pta^8Eq-4qwI0OrKs25wPs6Z)#LJPp=!dl>e1}Aa*fu`fjja z`BD)3>DFChgakKLY7|Qh?D1ksvY9jk$53!@m6!(3C#SOCXLOTe=5r;rc*Fff`e1*_ z1Cle(YH~gtp0q`MXEjnJq(%8IlBi!lCFI}JdhYCGX%wl%$7ZPZ1WIm|@)F%&R@~n@ z{Q1Q_K8Hl}y-%wKnJdWgJ;&;`(+1aZv8TEZ67aq2}4xhK?)7wAbz=04R9aI)Ve+D$8 zumpoThF%y(_RfOaxRVxGk>%8R6b=I-yH^?hI`%xskQWRLFX?C~Gjnj(SzFxaot$1( zF2x+|FuKUge;DoLtE8s%*m)IiRTID%p3F^o_=D5Xm_%2NrJVdN6})z~pmZ0*`ek-r zE7>~hy$;&&PKi1-T9K})oSX_1o2!}zpiny0jSAw|&&^GYSglPmo0FKE@}$%wQ~ls+uvru$1Xup{v65vSF)aO?%a~ZB1@|o=t0G-r4@iGQgod4`sCF1R zI2bl*68OgvPP-lu**tns&war@P-6s#juxD=!)+%v+k8!_7C{|@U~_u`Pi9agDJIL{ zFS4H7F@gn=e_XYZ`6?*gLYkZ!b@f?g?_oEc?hj7_y(o4;8~M3TT?RN3CQOM^(Kouf z!gqV$n;9?GwPlCOfclZ<#VJDhajO@lhk%j^;UXDAwQRpknh;EwfhX4IDHH&Jy3x_; zob`7l6DkhXRe3)YIAkR1=2FurkJ%l&S+)M>xoMBQ_svvW{yvu%@lF`Di84qj3)DBT zC9qBpuFeqhK<_xw)Q?kLF`>zL_e7#5~1B_Yl|`3t`}=ZtumFV*v(Zs#Wpw-);=7TO+emX z53(A{f^G1k6g;Y5j@S7o+N1wCuJjR?kzdY}L;;ntFlO$nu5MA`A6&jM_}XzuvcPfK z^pMOQa1=E(s9NiJOj3N zY9-kJaEK&U!0ez?8f5u|uzzds^f}XFa0=|0gRm56D}V$fSdnV=#^cUE;D;6(QGAZ} z$i!g~B!j*?@X#P0Av-!X7{qYnmW%fVHvx?;`I8|r>--^YR=b8z4Ex<)zQH=x2hFQL+ zxzG5dBoix+H!q_+?tkyvhHIM20H{OLd2RV^HSM#?#Ip9$nnUqVraZ~XhKh2blO|$2 z&DR0<8aS^G!46^}nn@=qdS9D~EckUWF-$m*qJ3ckch*$_Q_1-n7pxl*9L(geoIA%; z&5voGP39S%v-Zc5lI1x$ASWS=c+ojAgJkUOE#ppQI=L4Y!_@*v^w~4d>Yh(K(>k=Ve z;D^ECu*r9bKYc_+`Z=dUYy9Nt8Ptk&(9$#@lnIx`+uNXO7IDOVK>KXN`#yR4ZuuSp z(U9hEj3EnYDSv6L_u7(iW9f7u)_z9f=P#L?S?!56HcewG)Q9lK_UnGlW(k{Ytu_a7 zbAM}1wT7SFuVEtZ*B*n?S5_+8*RDsGX0ct#;6#;l4j3yxKGdqi_es5M7BoFp(*W5v zv^knoJs-crS&Q2!4`y^c4bnR612G7qdu5kcV>$!&G42gym}bF~DB$^8{lAK=1RzO( z*y^SDip_1(d^>94^dOOm7|4Hi9G3i=7fhQvYco`SoXwaF<}K0)gA-!MV^)V@UQkoB zcX195{(7cfq%A7*$+VhWApB!@m+o63TSD))JB2Z=F_YLA6FyKWnrhXUurn6h&dHeXp-mfH#$sdbvHtovLc$fMje#>;gtkIr6k$PxZA}|A+N(|pd zW>8^1Gb>caa@OmtP~D#&KFB0<8#JNlUp> z3GyVUhJx>4Ir6GrEK7(MzIJ}Tz1*S)?~ZMDHKRr|qc3w)-LVN2V#sFk+sL0HH~E%z z6!4{m|J-%q;}@x21(w-}P0ye6kbw2xKDXOdT;_vTyO%i-zL7&M->{AX{yXEOgc}M{ za(me^Gw-b;?t!LLyF7w29P%RBb#6af-R2)6Jl_$J@u|iWV0yuY|$T zUSx^)!K@J+X^CUP)VGGxrKt&?wUb?p7?|# z88A%ft`9O*UIyMBA#D|vFh<_gr!=KnN_^U}e=R`WPUMG3P1W*_MQ69kdsuBHas(?+ zz8C%dTUIt9b#?fHE;Rrb41rW9fn=r1QKLk?lV8e90GYKx6-^5wGIp_VK1+s(Y@t94 z(xISnEvI*^^aM36$t2^gV<{=c@SV>`2~bA}e)P(YTdo}JfN+?Gj|n}ys8P-)-F*P) z){#-+|U-ZqF?JdQjHn3!>23*h@23k!cRc6}gH7|;0WzOiu}KFP)PdwxK>66YzO zpE;DC3%Gx(l8Tszbp4c!gt^V6H(m_uubU4HUX@ioJS-6o6^s!=uFEEd*l%5L#)3+m z^LrFOk&kOPFIrZ5lr;vmKu%w4gQi;+0qrK;D~lA{(8PDVZSa7Njfcxg^z(XSA$)Ln z^ZM@x`Z*EYzq9_$aFElA+rDPOPbm#muItxvQkKb}t`AgtbI;~ZvhZ?tZS<{1QElX) zZl&r%&!KB4C#H}J4%~nPtXPjYrsU82$ptF-yq+I8=`SrcnSMKMruadj3*MqaG7N7o zL}a9uN9t{o8x;qNwJ7J-_3bH*oE()e4mU@aCh=U;cDQZC?sFBuDl}TGJ5*-klKj0i znOln+@AKOM&fZ_d-tzJ7n=I4IygZ+wp7l-XVs+ekY)-40lTR=@5rL&t(zGUi@I~gS zTYMeH`{aoF43((;`{-6h1eLx@6V);+Qg2mMWL%m*d`YGg2O$#=pAy>fbZ(R-ZaFAZ zj^7iM6zX;z6PoTBvsb-*Y-YL?$=7! zJ-ZP9*2%=!V(K;*%KI&fA3BTDlpPoyzF?aQiBHz0l2VGAj>G&wfZRaWp!nFU(WK%Urf}L9djUn+dU|=_;7flvAO7-~`WQjcy3qeMBJW(lJsC;3X%7R9~aKQ&0$E5F!IP$heJWOoWC3@AfHIvGzmq(?7?dO}2o3Mb0f{{*XAqGwF7*Owcs(n1>94)=vR{~TIy0g&(FJ))bL+J8}I(Hd?!Wtx1aM6 zlnnw#zFNV@<73T}HLvWtg10z(SDl?(rKQl2ao;oL3}#&=Oa`9^=yBg>bjV0K^GHwS z^;mn@Rqhq6=GgCX7dTP>AtIRSTCq>2XX6t;QsQxr3;3BBX{qmAniD!{&lD18KF0Gp zjh4Hh0B+VB8)jY7{T&BWRs%5G>Kj9c31~^?3bb1w<48<=4niME4P7K~+E)4r$JD|g zKp-vW=SLS8QZ{V`0B$3d7xd@2ePR;6Y>cp`Q0=MC4KhLy%E;fYCSig*b8&w=ahnI~ zJT-jd+t|8)l`cngiY+6@{t$9V758w-JbN`|%)#-Aox?MMIURuRm7x=wVON6Z3ZexR z!b;J=j^j4*sk}D6W6E@M#qm71rF<)A_}3`?Z>7H9p!AvV${o57N;#7e2?3Vo5rJSl zK|Jt#wt52p2X93>zjq%4fc3zf-=z*v{GP#~uwC~gXT@0fwcYsWx()^Ji?8e0g$DOJ zs+UNOu4|;!Hi-Y77CCdRJr@4eyYuOJ3K#+)=nxZD9Wp^M$QS6pm%ucUoQfL;rV@~} zx<4nvzjkDYWYlFsDe$}I?=w|tq~U8v$zXzFu(BPxI!k1Ay7ZFYAqU%r*6XB#rKyrf z0)@FcfM53%-0SY9+?_1GMcjS34b!~LCeOHE?kMkNpSFGq)B#9cyyw+UvOp9(0isf$ zuP}HrKJc;baNGL-aqQs7{Pt;{<=pqJ*mIT5pHbzR(9$mF48(&SmTHg@HLVW|F%N1j ztWL+j*#xC@^;>Uzz|~5XuxuXeboC0~Kuz9XVpN>_`3bTa_IyHwF5VV594vHO`f!ut z!^~_h>@3q#5>t_O*1${jRxI#F`?t=Lz3#;9XWBQ)btRx|rrLIC`|ct7d*M8iy_;Ky z!^qmV}rJ$kZBi;Oe zoV^89mFxEQz37q#K{`}WkPzt>0Z~$AFPlm-=~k(3Svr3C42>6FfQuD#FR z=bZh2zwdp&kKq^#9EfWzp69;jJ+JHf&Ch)66=N!(j9iNSTP2Mqy)gd+XuHJIJu;kA z5leQ@;mgXyAd<+)42U?P2g<;06gtis&h$wm84Cf}Vyv{Y*VQ5(qy5Eg9a?uyF4mKS-$snLfijfh=Ph93JL33T@fglztro!o#nElOv?LNV(UX?a zKVNPQMV6?)dM2PD^X8e4g2M22kJJk9b<6T_HulGxug`p|Mp27iWy+vgX$$%J-^<{A z)9(bnoPF`5dzxIT&rNB(H?;P6rL%^Z`OKf(j8iM}F*x(x!RKdm8Dvi-`+GAWZM>ZD z$1RZNfII{>#Imva_uA5*MF);Vy}QEGTgI%MlJhHz@Oinn^!m z)e+a;!<%ud>u~AhBBc2tr)r+93!Pvfs4O4n@=G;k@P|!q=?A_&m<*ZUgLTdNqG5{B z>+1vXd*FaB(KTAW1iD#anqTkN9;!=Rqw65f#ntLVTNnS}oHD+!4%!D-tEz=y^n>Rv zUw@d0M#~3<9;zr<|C7tMX<(}s8M;kzi|+G_$>QHC-UExp7t1Ut2p8(XEYm6d`J&I* zgZ+2lD1uzumh7xlhkoeP9lz`(cX~tuuTBPZk$~;-YVvhcQ`Xw~N0jT^ONBy#D}e3z z%mLe-?f6W^+?%AVdc~!^@Qx0tlME?nSfr-vFLfGU^20$EZ+JM&sK!M%DZXzsoL&JX zk$Cg>?tb&c$dym|OYM0Xyp;W8FFM|<|GfQWB!k{OZe5qbvOLjwZ_(b~kj~{LZF8IW zxgD9FFGl{yrvQSNY~>2yVzQd5A0t+<9D3Vsyf?R9Ju_b2yUm?e=w_ zjkgYAq<_naVypkksU2U6*X6ilJC-}ytzYrv%YInXPYQNrGy^y&3s_nj66OvX!)E90 z;Ba534m3_?@p=Zs_O7(BO6U!Rb)X=?6q5tDRD__i7G25BA_d`<%r$;-%-38BI>ieP9cI%g| zPZe#9HBI|##C_+VpmC&4?ffiwQ&M85Q_Ir7Q$-Ko%F=Ro_35wxRCrQWw?Dqx$W(i! zdg|EUhFz}&$9_wzwzAbDD~`MSI}3~*o-3;ZaZi4}ypd7he&f{q%PGc^n09n%fzCi0 ze^8O}^HyP-^Hc76A~v*(d-eOZqM}4%d{Z{#qGT$o@4K zY4^(D&>-&jITRub`pX87ZvV2G+u4DQVqh!s$1B2C3dj>y+_O02V+^6^~;Y?#K>!N1Tl><0u`|1`Q=9L#&EtDH;L?O>jjf9m-_LT!g~WZt;h3W6m>iU%Q$!lg}L@ zi;jY+WBsKDGp`E2UCTqiKUW31AWypa>oJ@_70rI|h8ORl?~dxQqhp&hd&L?J`^m`J zeVoy-nMLFFu1dp=$_;{%A~`r8W4zX-aUi!*lreZ*Fs+3*1q>Z_4E z#Pu@T95*9(zP)jFG}Q`a@5sr^Lqp6jE;_YBz33yOrh#?S&cxhYicC6u*8F4>z5C^q z6K31QhN}}vOwiO^!0Z85pe$Mjx2)c+ii?fPfTLP(jO%(%>m$NS*KnveYbXG)ktq|j zKzRB@e!cJNYdoafT|n@n10!0WP)c_^ag0}n#wv|4b%W!+Eg_k7XtznIAh*L8iICX? z=s^KL7quu03#J~lR#nB#Iq~tLM53zRZPps1>Rxqj9_vHXdXdhPlmUNulYWHz{1%{z z8@FBRGy}cBNd7)H&Q40<=PMqWtiuOC+Il~^5h5L&UYkMOV(0la`qyHys5j5JpPPIPCH}{Km+Rca)?CBZ0cV$TcU`L&&E_MR7!FSHdfYOH)^LQrD(UX%zefmXW9n%BVHWEB?l;Fs5W>AO=odA3c5+ukne1`H34!+h z2HgB=uRptg*K$ivO{J4i0rg30q`^S>HCE;E)#LoGuHk(4tBR?Y5CX^NUUAo!^Qow% z6Og?$XltB(=uT?G`Mc7+w@3zXan(xP(e_N%kbWLKjLQbJItIagPD zW_D2!r=J4Z%&!@%yRu^)Tpjkxr}$xQbKlwBH8E*F2984G*^<8{BpNVq%gBZQ@9FD!L5vX|h|szc>A#-u(6|t9^fUTA;Oo|(0c4!_h{MsoFy(aBaCPq|g+{uPxt-W}GQOdKSGrP<~otx@gt(g-z9akqKMUAP`z8zQ?1)BHIg3 zFYUrQ(3?2o=xEgO{cMdkk%3{cR_&Xh9l6r&lz1mqV=e}#<)5XN7I!ewZog(@%M zR7QZ!>V8yj*3|*jXiCBG!8zPA^C(^nj%Q&1Zcj?Ke|X!OiWppeWbYHR#LSDv2ve^2 zElGs(%O_3!m#q5tXgP4M zr(k;8Odr<12#kJ}vSfeE1^J!RcS|yr2N7gVivd7a2q0i3wMNlF{enCI%NZifan7!0-hDg$Q+8Gt;)_98#2u zg1>vM|9VS*U?VM!qzIGY=$uVQDRoq}Z}p#;*k-Hs&PetLux=VJ-$DIgq6x2tf5n&{ zX1WIO$EH`b(C&RMdXp4cR6ye?hS9hlyD6)eO`VAtuNFXqfP-YL4(`~b+0R+z6RsVm z`{aE#qd0zHMvgEvbG`H=TH88jDYA<28Sbq86nTgWTQ04I{j7$aM#jhte$9`33WtNE z&sBzwcfXzcEqo8kv0M%Nn%6kmJnc z8i|q#!^$M0d>zky8BEt-4aCJTE0qXpIJ?Xm&os!TMGLoW^ALw!!m``vO|JJM+4b-6 z5|@55_F(+Gqx0t;)oYFl$*mhK*o|tXFSseMK+K5Q(xs#vYi!`;dbZAU(^!4M6>$B# ziFzJqpm;G)j}qicfCwckJnd<7u`HjPpFOir!&fV-33{pZ{gYX?R7Px z(RT1-<7=t>!F1$B?ElvM=6e9)ouI~vL+;Ija3_ngRw=}_U zZ6RELu%p8j`1Td5;DcOQ6#LD*S1V_z{-)Bd$ot&a(*uPgH6eUDCTbG{qYohw)$_vs zQ7*<*11sTyo=P}*%gvZGV`I_wq+_OyHii_LMi(d5?dbvODl6&BBn46^1+LkI;@->l z-zvy&ogu?Kdlg!ovkB76?$fzUy8QgS@9qw7Ez-lq6GpMQW=A*@b|(qt26GuUZCMTj z$Gkx5=+E$n^phbcQv!hp1Ql83LteUzhu{!8T30yC$ycpQdCETv#>(dIkbK0HSBA({ zR#&F6^Og0Rk1O%ZccOToI-LBJ7jbtX7HiAc1N_qpn?mHRT3@7c+j2+x9Wwf8uO z2Qg;IJl$Jv9=7Wyb#GlMR%%cOzU*qF{Qhk{?0D6YH*dpN4qpHVaX#e|&&6V%(={C_ zx2?&;i!cnO!Ne0C(AdNF&Zd*RK>`odS&m;47a8|EEgieyEc?>NxX)qlJ*yU!PY z`Nue~6h6(Zn+V&)*O3<ydzX&cK3&t=7w!_Ca6kTpB9lN~PB0 zNRE8^9451$5s7b!gCH}h$$i6DJ|K6F7Eez7Zf;t^&4{*GPBv^o3M}fB!|vY;TOGK^ zwZ0+;a-{WTcjkgC)>mE^pKtY)P-G`=FLKJOur1uW8Qx+8!J}8%1i`_b|G3V=yUp0M zrLsdhR@=b!+MjC(+k-v675`y-_AN{4k|PfXu9%YJ_^$k~yPb)}3m9_0;%@5q%0DO% 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 z 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%0A?;J_&5@q; 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 PcXBY@6;O`c1q(R*@5SAMTSl&k5BtG(t%2);tS+ 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�U-!Oum)h~s$q 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

V#FTUU%%bsWX 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 z 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 literal 0 HcmV?d00001 diff --git a/images/readme/jobdetails.png b/images/readme/jobdetails.png new file mode 100755 index 0000000000000000000000000000000000000000..c20d9b45b072425e1159fbdadcca9f26de52cd7d GIT binary patch literal 400180 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