写在前面:Spark2.1.2 SparkSQL执行过程中涉及的类;后续手撕Spark2.2.0源码.
/**
*一个简单的实用工具,用于跟踪查询规划中的运行时和相关统计信息
*我们追踪两个不同的概念:
*1阶段:这些是查询规划中的大范围阶段,如下所示,即分析、优化和物理规划(仅规划)。
*2规则:这些是我们跟踪的单个催化剂规则。除了时间,我们还跟踪调用的数量和有效调用。
*/
伴生对象:
object QueryPlanningTracker {
val PARSING = "parsing"
val ANALYSIS = "analysis"
val OPTIMIZATION = "optimization"
val PLANNING = "planning"
//规则摘要类
class RuleSummary(
var totalTimeNs: Long, //在此规则中所花费的总时间(以纳米秒为单位)
var numInvocations: Long, //调用规则的次数
var numEffectiveInvocations: Long //调用规则并导致计划更改的次数。
) {
def this() = this(totalTimeNs = 0, numInvocations = 0, numEffectiveInvocations = 0)
override def toString: String = {
s"RuleSummary($totalTimeNs, $numInvocations, $numEffectiveInvocations)"
}
}
//阶段摘要类: 包含开始时间和结束时间,以便我们可以构建一个时间轴。
class PhaseSummary(val startTimeMs: Long, val endTimeMs: Long) {
def durationMs: Long = endTimeMs - startTimeMs
override def toString: String = {
s"PhaseSummary($startTimeMs, $endTimeMs)"
}
}
//一个线程局部变量,隐式地传递跟踪器。
//这假设查询规划器是单线程的,并避免在每个函数调用中传递相同的跟踪程序上下文。
private val localTracker = new ThreadLocal[QueryPlanningTracker]() {
override def initialValue: QueryPlanningTracker = null
}
//根据线程局部变量 返回 作用域中的当前跟踪程序
def get: Option[QueryPlanningTracker] = Option(localTracker.get())
//为函数f的执行设置当前跟踪器。我们假设f是单线程的。
def withTracker[T](tracker: QueryPlanningTracker)(f: => T): T = {
//得到当前线程的局部变量
val originalTracker = localTracker.get()
//设置当前线程绑定tracker变量
localTracker.set(tracker)
//运行f函数,设置当前线程绑定原来的变量
try f finally { localTracker.set(originalTracker) }
}
}
伴生类
class QueryPlanningTracker {
import QueryPlanningTracker._
//从规则的名称映射到规则的摘要。使用Java HashMap可以减少开销。
private val rulesMap = new java.util.HashMap[String, RuleSummary]
//从一个阶段到它的开始时间和结束时间,单位为ms
private val phasesMap = new java.util.HashMap[String, PhaseSummary]
//测量一个阶段的开始和结束时间。
//请注意,如果在同一阶段多次调用此函数,则记录的开始时间将是第一次调用的开始时间,
//记录的结束时间将是最后一次调用的结束时间。
def measurePhase[T](phase: String)(f: => T): T = {
//这三个参数获取当前的执行时间
val startTime = System.currentTimeMillis()
val ret = f
val endTime = System.currentTimeMillis
//判断是否是第一次调用这个方法,如果是就创建新的Phase,并赋值以前的初始时间,当前的结束时间,并将其put到phasesMap中
//否则创建一个新的Phase,并赋值当前启动和结束时间,将其put到phaseMap中
if (phasesMap.containsKey(phase)) {
val oldSummary = phasesMap.get(phase)
phasesMap.put(phase, new PhaseSummary(oldSummary.startTimeMs, endTime))
} else {
phasesMap.put(phase, new PhaseSummary(startTime, endTime))
}
ret
}
//记录规则的特定调用
def recordRuleInvocation(rule: String, //规则名称
timeNs: Long, //运行此调用所需时间
effective: Boolean //调用是否导致计划更改
): Unit = {
//判断rule是否被rulesMap记录,如果没有记录,就记录下来
var s = rulesMap.get(rule)
if (s eq null) {
s = new RuleSummary
rulesMap.put(rule, s)
}
//记录规则所花费的总时间,调用次数,调用规则导致计划变更次数
s.totalTimeNs += timeNs
s.numInvocations += 1
s.numEffectiveInvocations += (if (effective) 1 else 0)
}
// ------------ reporting functions below ------------
//将java类型转换为scala类型
def rules: Map[String, RuleSummary] = rulesMap.asScala.toMap
def phases: Map[String, PhaseSummary] = phasesMap.asScala.toMap
//返回前k个最昂贵的规则(按时间度量)。
//如果k大于目前看到的规则,则返回所有规则。如果到目前为止没有看到规则或k<=0,则返回空seq
def topRulesByTime(k: Int): Seq[(String, RuleSummary)] = {
if (k <= 0) {
Seq.empty
} else {
//取元组中的第二个元素的totalTimeNs,作为排序规则
val orderingByTime: Ordering[(String, RuleSummary)] = Ordering.by(e => e._2.totalTimeNs)
//第一个参数是取前k个,第二个参数是隐式排序规则,即上面定义的
val q = new BoundedPriorityQueue(k)(orderingByTime)
//调用BoundedPriorityQueue中的+=函数
rulesMap.asScala.foreach(q.+=)
//疑问:这里的-r是什么意思?
q.toSeq.sortBy(r => -r._2.totalTimeNs)
}
}
}