Skip to content

Commit 36a9603

Browse files
Updated client side metrics flow
1 parent e43721a commit 36a9603

16 files changed

Lines changed: 514 additions & 154 deletions

File tree

client/src/main/scala/org/apache/celeborn/client/ApplicationHeartbeater.scala

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ class ApplicationHeartbeater(
4949

5050
private var stopped = false
5151
private val reviseLostShuffles = conf.reviseLostShufflesEnabled
52+
private val appMetricLabels: util.Map[String, String] =
53+
conf.clientMetricsAppLabels.asJava
5254

5355
// Use independent app heartbeat threads to avoid being blocked by other operations.
5456
private val appHeartbeatIntervalMs = conf.appHeartbeatIntervalMs
@@ -89,7 +91,9 @@ class ApplicationHeartbeater(
8991
workerStatusTracker.getNeedCheckedWorkers().toList.asJava,
9092
ZERO_UUID,
9193
true,
92-
clientMetrics())
94+
if (appMetricLabels.isEmpty) new util.HashMap[String, ClientMetric]()
95+
else clientMetrics(),
96+
appMetricLabels)
9397
val response = requestHeartbeat(appHeartbeat)
9498
if (response.statusCode == StatusCode.SUCCESS) {
9599
logDebug("Successfully send app heartbeat.")

client/src/main/scala/org/apache/celeborn/client/CelebornClientSource.scala

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
package org.apache.celeborn.client
1919

20+
import java.util.concurrent.ConcurrentHashMap
21+
2022
import org.apache.celeborn.common.CelebornConf
2123
import org.apache.celeborn.common.metrics.{ClientMetric, MetricType}
2224
import org.apache.celeborn.common.metrics.source.{AbstractSource, Role}
@@ -29,6 +31,9 @@ class CelebornClientSource(conf: CelebornConf) extends AbstractSource(conf, Role
2931

3032
import CelebornClientSource._
3133

34+
// Tracks previous counter values so we can send deltas to the master.
35+
private val counterPrev = new ConcurrentHashMap[String, java.lang.Long]()
36+
3237
addCounter(REGISTER_SHUFFLE_COUNT)
3338
addCounter(REGISTER_SHUFFLE_FAIL_COUNT)
3439
addCounter(UNREGISTER_SHUFFLE_COUNT)
@@ -39,34 +44,35 @@ class CelebornClientSource(conf: CelebornConf) extends AbstractSource(conf, Role
3944
addCounter(SHUFFLE_DATA_LOST_COUNT)
4045

4146
def getMetricsSnapshot(): Map[String, ClientMetric] = {
42-
val counterMetrics = counters().map(c =>
43-
c.name -> ClientMetric(c.counter.getCount, MetricType.Counter))
47+
// Counters: compute delta since last snapshot
48+
val counterMetrics = counters().flatMap { c =>
49+
val current = c.counter.getCount
50+
val prev = Option(counterPrev.put(c.name, current)).map(_.longValue()).getOrElse(0L)
51+
val delta = current - prev
52+
if (delta > 0) Some(c.name -> ClientMetric(delta, MetricType.Counter))
53+
else None
54+
}
55+
// Gauges: send the latest value as-is.
4456
val gaugeMetrics = gauges().map(g =>
4557
g.name -> ClientMetric(g.gauge.getValue.asInstanceOf[Number].longValue(), MetricType.Gauge))
4658
(counterMetrics ++ gaugeMetrics).toMap
4759
}
4860

49-
// start cleaner thread
50-
startCleaner()
61+
def start(): Unit = startCleaner()
62+
63+
def stop(): Unit = metricsCleaner.shutdown()
5164
}
5265

5366
object CelebornClientSource {
54-
// worker health
5567
val EXCLUDED_WORKER_COUNT = "ClientExcludedWorkerCount"
5668
val SHUTTING_WORKER_COUNT = "ClientShuttingWorkerCount"
57-
58-
// shuffle lifecycle
5969
val ACTIVE_SHUFFLE_COUNT = "ClientActiveShuffleCount"
6070
val REGISTER_SHUFFLE_COUNT = "ClientRegisterShuffleCount"
6171
val REGISTER_SHUFFLE_FAIL_COUNT = "ClientRegisterShuffleFailCount"
6272
val UNREGISTER_SHUFFLE_COUNT = "ClientUnregisterShuffleCount"
63-
64-
// write path
6573
val REVIVE_REQUEST_COUNT = "ClientReviveRequestCount"
6674
val REVIVE_FAIL_COUNT = "ClientReviveFailCount"
6775
val SLOT_RESERVATION_FAIL_COUNT = "ClientSlotReservationFailCount"
68-
69-
// data integrity
7076
val SHUFFLE_FETCH_FAILURE_COUNT = "ClientShuffleFetchFailureCount"
7177
val SHUFFLE_DATA_LOST_COUNT = "ClientShuffleDataLostCount"
7278
}

client/src/main/scala/org/apache/celeborn/client/ChangePartitionManager.scala

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -282,11 +282,9 @@ class ChangePartitionManager(
282282
None,
283283
lifecycleManager.workerStatusTracker.workerAvailableByLocation(req.oldPartition))))
284284
}
285-
if (lifecycleManager.clientMetricsEnabled) {
286-
lifecycleManager.clientSource.incCounter(
287-
CelebornClientSource.REVIVE_FAIL_COUNT,
288-
changePartitions.size)
289-
}
285+
lifecycleManager.incClientMetric(
286+
CelebornClientSource.REVIVE_FAIL_COUNT,
287+
changePartitions.size)
290288
}
291289

292290
val candidates = new util.HashSet[WorkerInfo]()

client/src/main/scala/org/apache/celeborn/client/LifecycleManager.scala

Lines changed: 25 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -223,20 +223,25 @@ class LifecycleManager(val appUniqueId: String, val conf: CelebornConf) extends
223223
}
224224

225225
private val masterClient = new MasterClient(masterRpcEnvInUse, conf, false)
226-
val clientSource = new CelebornClientSource(conf)
227-
private[client] val clientMetricsEnabled = conf.metricsSystemEnable && conf.clientMetricsEnabled
226+
private val clientMetricsEnabled = conf.metricsSystemEnable && conf.clientMetricsEnabled
227+
val clientSource: Option[CelebornClientSource] =
228+
if (clientMetricsEnabled) Some(new CelebornClientSource(conf)) else None
229+
230+
@inline private[client] def incClientMetric(name: String, delta: Long = 1L): Unit =
231+
clientSource.foreach(_.incCounter(name, delta))
228232
val commitManager = new CommitManager(appUniqueId, conf, this)
229233
val workerStatusTracker = new WorkerStatusTracker(conf, this)
230-
if (clientMetricsEnabled) {
231-
clientSource.addGauge(CelebornClientSource.ACTIVE_SHUFFLE_COUNT) { () =>
234+
clientSource.foreach { source =>
235+
source.addGauge(CelebornClientSource.ACTIVE_SHUFFLE_COUNT) { () =>
232236
registeredShuffle.size
233237
}
234-
clientSource.addGauge(CelebornClientSource.EXCLUDED_WORKER_COUNT) { () =>
238+
source.addGauge(CelebornClientSource.EXCLUDED_WORKER_COUNT) { () =>
235239
workerStatusTracker.excludedWorkers.size
236240
}
237-
clientSource.addGauge(CelebornClientSource.SHUTTING_WORKER_COUNT) { () =>
241+
source.addGauge(CelebornClientSource.SHUTTING_WORKER_COUNT) { () =>
238242
workerStatusTracker.shuttingWorkers.size
239243
}
244+
source.start()
240245
}
241246
private val heartbeater =
242247
new ApplicationHeartbeater(
@@ -252,11 +257,8 @@ class LifecycleManager(val appUniqueId: String, val conf: CelebornConf) extends
252257
registeredShuffle,
253258
reason => cancelAllActiveStages(reason),
254259
() =>
255-
if (clientMetricsEnabled) {
256-
clientSource.getMetricsSnapshot().asJava
257-
} else {
258-
new util.HashMap[String, ClientMetric]()
259-
})
260+
clientSource.map(_.getMetricsSnapshot().asJava)
261+
.getOrElse(new util.HashMap[String, ClientMetric]()))
260262
private def resetFallbackCounts(counts: ConcurrentHashMap[String, java.lang.Long])
261263
: Map[String, java.lang.Long] = {
262264
val fallbackCounts = new util.HashMap[String, java.lang.Long]()
@@ -747,13 +749,9 @@ class LifecycleManager(val appUniqueId: String, val conf: CelebornConf) extends
747749

748750
// Reply to all RegisterShuffle request for current shuffle id.
749751
def replyRegisterShuffle(response: RegisterShuffleResponse): Unit = {
750-
if (clientMetricsEnabled) {
751-
if (response.status == StatusCode.SUCCESS) {
752-
clientSource.incCounter(CelebornClientSource.REGISTER_SHUFFLE_COUNT)
753-
} else {
754-
clientSource.incCounter(CelebornClientSource.REGISTER_SHUFFLE_FAIL_COUNT)
755-
}
756-
}
752+
incClientMetric(
753+
if (response.status == StatusCode.SUCCESS) CelebornClientSource.REGISTER_SHUFFLE_COUNT
754+
else CelebornClientSource.REGISTER_SHUFFLE_FAIL_COUNT)
757755
registeringShuffleRequest.synchronized {
758756
val serializedMsg: Option[ByteBuffer] = partitionType match {
759757
case PartitionType.REDUCE =>
@@ -915,15 +913,11 @@ class LifecycleManager(val appUniqueId: String, val conf: CelebornConf) extends
915913
serdeVersion: SerdeVersion): Unit = {
916914
val contextWrapper =
917915
ChangeLocationsCallContext(context, partitionIds.size(), serdeVersion)
918-
if (clientMetricsEnabled) {
919-
clientSource.incCounter(CelebornClientSource.REVIVE_REQUEST_COUNT, partitionIds.size())
920-
}
916+
incClientMetric(CelebornClientSource.REVIVE_REQUEST_COUNT, partitionIds.size())
921917
// If shuffle not registered, reply ShuffleNotRegistered and return
922918
if (!registeredShuffle.contains(shuffleId)) {
923919
logError(s"[handleRevive] shuffle $shuffleId not registered!")
924-
if (clientMetricsEnabled) {
925-
clientSource.incCounter(CelebornClientSource.REVIVE_FAIL_COUNT)
926-
}
920+
incClientMetric(CelebornClientSource.REVIVE_FAIL_COUNT, partitionIds.size())
927921
contextWrapper.reply(
928922
-1,
929923
StatusCode.SHUFFLE_UNREGISTERED,
@@ -935,9 +929,7 @@ class LifecycleManager(val appUniqueId: String, val conf: CelebornConf) extends
935929
s"[handleRevive] shuffle $shuffleId, $mapIds, $partitionIds, $oldEpochs, $oldPartitions, $causes")
936930
if (commitManager.isStageEnd(shuffleId)) {
937931
logError(s"[handleRevive] shuffle $shuffleId stage ended!")
938-
if (clientMetricsEnabled) {
939-
clientSource.incCounter(CelebornClientSource.REVIVE_FAIL_COUNT)
940-
}
932+
incClientMetric(CelebornClientSource.REVIVE_FAIL_COUNT, partitionIds.size())
941933
contextWrapper.reply(
942934
-1,
943935
StatusCode.STAGE_ENDED,
@@ -1151,9 +1143,7 @@ class LifecycleManager(val appUniqueId: String, val conf: CelebornConf) extends
11511143
if (invokeReportTaskShuffleFetchFailurePreCheck(taskId)) {
11521144
logInfo(s"handle fetch failure for appShuffleId $appShuffleId shuffleId $shuffleId")
11531145
ret = invokeAppShuffleTrackerCallback(appShuffleId)
1154-
if (ret && clientMetricsEnabled) {
1155-
clientSource.incCounter(CelebornClientSource.SHUFFLE_FETCH_FAILURE_COUNT)
1156-
}
1146+
if (ret) incClientMetric(CelebornClientSource.SHUFFLE_FETCH_FAILURE_COUNT)
11571147
shuffleIds.put(appShuffleIdentifier, (shuffleId, false))
11581148
} else {
11591149
logInfo(
@@ -1285,9 +1275,7 @@ class LifecycleManager(val appUniqueId: String, val conf: CelebornConf) extends
12851275
context.reply(MapperEndResponse(StatusCode.SUCCESS, serdeVersion))
12861276
case false =>
12871277
logError(s"Failed $message, reply ${StatusCode.SHUFFLE_DATA_LOST}.")
1288-
if (clientMetricsEnabled) {
1289-
clientSource.incCounter(CelebornClientSource.SHUFFLE_DATA_LOST_COUNT)
1290-
}
1278+
incClientMetric(CelebornClientSource.SHUFFLE_DATA_LOST_COUNT)
12911279
context.reply(MapperEndResponse(StatusCode.SHUFFLE_DATA_LOST, serdeVersion))
12921280
}
12931281
}
@@ -1653,9 +1641,7 @@ class LifecycleManager(val appUniqueId: String, val conf: CelebornConf) extends
16531641
// [[releasePartitionLocation]]. Now in the slots are all the successful partition
16541642
// locations.
16551643
logWarning(s"Reserve buffers for $shuffleId still fail after retrying, clear buffers.")
1656-
if (clientMetricsEnabled) {
1657-
clientSource.incCounter(CelebornClientSource.SLOT_RESERVATION_FAIL_COUNT)
1658-
}
1644+
incClientMetric(CelebornClientSource.SLOT_RESERVATION_FAIL_COUNT)
16591645
destroySlotsWithRetry(shuffleId, slots)
16601646
} else {
16611647
logInfo(s"Reserve buffer success for shuffleId $shuffleId")
@@ -1875,9 +1861,7 @@ class LifecycleManager(val appUniqueId: String, val conf: CelebornConf) extends
18751861
// if unregister shuffle not success, wait next turn
18761862
if (StatusCode.SUCCESS == StatusCode.fromValue(unregisterShuffleResponse.getStatus)) {
18771863
unregisterShuffleTime.remove(shuffleId)
1878-
if (clientMetricsEnabled) {
1879-
clientSource.incCounter(CelebornClientSource.UNREGISTER_SHUFFLE_COUNT)
1880-
}
1864+
incClientMetric(CelebornClientSource.UNREGISTER_SHUFFLE_COUNT)
18811865
}
18821866
}
18831867
} else {
@@ -1889,9 +1873,7 @@ class LifecycleManager(val appUniqueId: String, val conf: CelebornConf) extends
18891873
if (StatusCode.SUCCESS == StatusCode.fromValue(unregisterShuffleResponse.getStatus)) {
18901874
shuffleIdsToRemove.foreach { shuffleId: Integer =>
18911875
unregisterShuffleTime.remove(shuffleId)
1892-
if (clientMetricsEnabled) {
1893-
clientSource.incCounter(CelebornClientSource.UNREGISTER_SHUFFLE_COUNT)
1894-
}
1876+
incClientMetric(CelebornClientSource.UNREGISTER_SHUFFLE_COUNT)
18951877
}
18961878
}
18971879
}
@@ -2101,6 +2083,7 @@ class LifecycleManager(val appUniqueId: String, val conf: CelebornConf) extends
21012083
*/
21022084
override def stop(): Unit = {
21032085
heartbeater.stop()
2086+
clientSource.foreach(_.stop())
21042087
super.stop()
21052088
}
21062089

client/src/main/scala/org/apache/celeborn/client/commit/ReducePartitionCommitHandler.scala

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -222,9 +222,7 @@ class ReducePartitionCommitHandler(
222222
} else {
223223
logError(s"Failed to handle stageEnd for $shuffleId, lost file!")
224224
dataLostShuffleSet.add(shuffleId)
225-
if (conf.metricsSystemEnable && conf.clientMetricsEnabled) {
226-
lifecycleManager.clientSource.incCounter(CelebornClientSource.SHUFFLE_DATA_LOST_COUNT)
227-
}
225+
lifecycleManager.incClientMetric(CelebornClientSource.SHUFFLE_DATA_LOST_COUNT)
228226
// record in stageEndShuffleSet
229227
setStageEnd(shuffleId)
230228
}

client/src/test/scala/org/apache/celeborn/client/WorkerStatusTrackerSuite.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ class WorkerStatusTrackerSuite extends CelebornFunSuite {
167167
val lifecycleManager = new LifecycleManager("app-metrics-test", celebornConf)
168168
try {
169169
val statusTracker = lifecycleManager.workerStatusTracker
170-
val source = lifecycleManager.clientSource
170+
val source = lifecycleManager.clientSource.get
171171

172172
val failed = new ShuffleFailedWorkers()
173173
val now = System.currentTimeMillis()

common/src/main/proto/TransportMessages.proto

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,7 @@ message PbHeartbeatFromApplication {
502502
map<string, int64> applicationFallbackCounts = 10;
503503
PbHeartbeatInfo heartbeatInfo = 11;
504504
map<string, PbClientMetric> clientMetrics = 12;
505+
map<string, string> metricLabels = 13;
505506
}
506507

507508
enum PbMetricType {

common/src/main/scala/org/apache/celeborn/common/CelebornConf.scala

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -911,12 +911,17 @@ class CelebornConf(loadDefaults: Boolean) extends Cloneable with Logging with Se
911911
def metricsConf: Option[String] = get(METRICS_CONF)
912912
def metricsSystemEnable: Boolean = get(METRICS_ENABLED)
913913
def clientMetricsEnabled: Boolean = get(CLIENT_METRICS_ENABLED)
914+
def masterClientMetricsEnabled: Boolean = get(MASTER_CLIENT_METRICS_ENABLED)
915+
def masterClientMetricsRemovedAppRetentionMs: Long =
916+
get(MASTER_CLIENT_METRICS_REMOVED_APP_RETENTION)
914917
def metricsSampleRate: Double = get(METRICS_SAMPLE_RATE)
915918
def metricsSlidingWindowSize: Int = get(METRICS_SLIDING_WINDOW_SIZE)
916919
def metricsCollectCriticalEnabled: Boolean = get(METRICS_COLLECT_CRITICAL_ENABLED)
917920
def metricsCapacity: Int = get(METRICS_CAPACITY)
918921
def metricsExtraLabels: Map[String, String] =
919922
get(METRICS_EXTRA_LABELS).map(Utils.parseKeyValuePair).toMap
923+
def clientMetricsAppLabels: Map[String, String] =
924+
get(CLIENT_METRICS_APP_LABELS).map(Utils.parseKeyValuePair).toMap
920925
def metricsWorkerAppTopResourceConsumptionCount: Int =
921926
get(METRICS_WORKER_APP_TOP_RESOURCE_CONSUMPTION_COUNT)
922927
def metricsWorkerAppTopResourceConsumptionBytesWrittenThreshold: Long =
@@ -5919,6 +5924,25 @@ object CelebornConf extends Logging {
59195924
.booleanConf
59205925
.createWithDefault(false)
59215926

5927+
val MASTER_CLIENT_METRICS_ENABLED: ConfigEntry[Boolean] =
5928+
buildConf("celeborn.metrics.master.clientMetrics.enabled")
5929+
.categories("metrics")
5930+
.doc("When true, the master exposes client-side metrics forwarded in application " +
5931+
"heartbeats on its Prometheus endpoint.")
5932+
.version("0.7.0")
5933+
.booleanConf
5934+
.createWithDefault(false)
5935+
5936+
val MASTER_CLIENT_METRICS_REMOVED_APP_RETENTION: ConfigEntry[Long] =
5937+
buildConf("celeborn.metrics.master.clientMetrics.removedApp.retentionMs")
5938+
.categories("metrics")
5939+
.doc("How long to retain removed application IDs in the client metrics source to " +
5940+
"reject late heartbeats after an application is lost. Entries older than this are " +
5941+
"periodically evicted.")
5942+
.version("0.7.0")
5943+
.timeConf(TimeUnit.MILLISECONDS)
5944+
.createWithDefaultString("5min")
5945+
59225946
val METRICS_SAMPLE_RATE: ConfigEntry[Double] =
59235947
buildConf("celeborn.metrics.sample.rate")
59245948
.categories("metrics")
@@ -5965,6 +5989,20 @@ object CelebornConf extends Logging {
59655989
"Allowed pattern is: `<label1_key>=<label1_value>[,<label2_key>=<label2_value>]*`")
59665990
.createWithDefault(Seq.empty)
59675991

5992+
val CLIENT_METRICS_APP_LABELS: ConfigEntry[Seq[String]] =
5993+
buildConf("celeborn.client.metrics.appLabels")
5994+
.categories("client", "metrics")
5995+
.doc("Custom metric labels sent from the client in each application heartbeat and applied " +
5996+
"to client metrics exposed on the master's Prometheus endpoint. " +
5997+
"Labels' pattern is: `<label1_key>=<label1_value>[,<label2_key>=<label2_value>]*`; e.g. `env=prod,version=1`")
5998+
.version("0.7.0")
5999+
.stringConf
6000+
.toSequence
6001+
.checkValue(
6002+
labels => labels.map(_ => Try(Utils.parseKeyValuePair(_))).forall(_.isSuccess),
6003+
"Allowed pattern is: `<label1_key>=<label1_value>[,<label2_key>=<label2_value>]*`")
6004+
.createWithDefault(Seq.empty)
6005+
59686006
val METRICS_WORKER_APP_TOP_RESOURCE_CONSUMPTION_COUNT: ConfigEntry[Int] =
59696007
buildConf("celeborn.metrics.worker.app.topResourceConsumption.count")
59706008
.categories("metrics")

0 commit comments

Comments
 (0)