Skip to content

Commit 05c8ee3

Browse files
committed
More consistent usage of cost center in causal profiler
1 parent 79a2867 commit 05c8ee3

File tree

10 files changed

+121
-70
lines changed

10 files changed

+121
-70
lines changed

build.sbt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ inThisBuild(
1515
)
1616
)
1717

18-
addCommandAlias("compileSources", "core/Test/compile; taggingPlugin/compile; taggingPluginTests/compile; examples/compile; benchmarks/compiile;")
18+
addCommandAlias(
19+
"compileSources",
20+
"core/Test/compile; taggingPlugin/compile; taggingPluginTests/compile; examples/compile; benchmarks/compile;"
21+
)
1922
addCommandAlias("testAll", "core/test; taggingPluginTests/test")
2023

2124
addCommandAlias("check", "fixCheck; fmtCheck")
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package zio.profiling
2+
3+
import zio.test._
4+
import zio.{Runtime, ZLayer}
5+
6+
abstract class BaseSpec extends ZIOSpecDefault {
7+
override val bootstrap: ZLayer[Any, Any, TestEnvironment] = testEnvironment ++ Runtime.removeDefaultLoggers
8+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package zio.profiling.causal
2+
3+
import zio._
4+
import zio.profiling.{BaseSpec, CostCenter, ProfilerExamples}
5+
import zio.test.Assertion.isTrue
6+
import zio.test._
7+
8+
object PluginSamplingProfilerSpec extends BaseSpec {
9+
10+
def spec = suite("PluginCausalProfiler")(
11+
test("Should correctly profile simple example program") {
12+
val profiler = CausalProfiler(
13+
iterations = 10,
14+
warmUpPeriod = 0.seconds,
15+
minExperimentDuration = 1.second,
16+
experimentTargetSamples = 1
17+
)
18+
val program = (ProfilerExamples.zioProgram *> CausalProfiler.progressPoint("done")).forever
19+
Live.live(profiler.profile(program)).map { result =>
20+
println(result)
21+
22+
def isSlowEffect(location: CostCenter) =
23+
location.hasParentMatching("zio\\.profiling\\.ProfilerExamples\\.slow\\(.*\\)".r)
24+
def isFastEffect(location: CostCenter) =
25+
location.hasParentMatching("zio\\.profiling\\.ProfilerExamples\\.fast\\(.*\\)".r)
26+
27+
val hasSlow = result.experiments.exists(e => isSlowEffect(e.selected))
28+
val hasFast = result.experiments.exists(e => isFastEffect(e.selected))
29+
30+
assert(hasSlow)(isTrue) && assert(hasFast)(isTrue)
31+
}
32+
}
33+
)
34+
}

zio-profiling-tagging-plugin-tests/src/test/scala/zio/profiling/sampling/PluginSamplingProfilerSpec.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
package zio.profiling.sampling
22

3-
import zio.profiling.{CostCenter, ProfilerExamples}
3+
import zio.Scope
4+
import zio.profiling.{BaseSpec, CostCenter, ProfilerExamples}
45
import zio.test.Assertion.{hasSize, isGreaterThanEqualTo}
56
import zio.test._
6-
import zio.Scope
77

8-
object PluginSamplingProfilerSpec extends ZIOSpecDefault {
8+
object PluginSamplingProfilerSpec extends BaseSpec {
99

1010
def spec: Spec[Environment with TestEnvironment with Scope, Any] = suite("PluginSamplingProfiler")(
1111
test("Should correctly profile simple example program") {

zio-profiling/src/main/scala/zio/profiling/CostCenter.scala

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ import scala.util.matching.Regex
1919
sealed trait CostCenter { self =>
2020
import CostCenter._
2121

22+
final def render: String =
23+
self match {
24+
case Root => ""
25+
case Child(Root, name) => name
26+
case Child(parent, name) => s"${parent.render};$name"
27+
}
28+
2229
final def location: Option[String] = self match {
2330
case Root => None
2431
case Child(_, current) => Some(current)
@@ -41,6 +48,11 @@ sealed trait CostCenter { self =>
4148
Child(self, location)
4249
}
4350

51+
final def isChildOf(other: CostCenter): Boolean = self == other || (self match {
52+
case Root => false
53+
case Child(parent, _) => parent.isChildOf(other)
54+
})
55+
4456
/**
4557
* Check whether this cost center has a parent with a given name.
4658
*
@@ -60,7 +72,7 @@ sealed trait CostCenter { self =>
6072
*/
6173
final def hasParentMatching(regex: Regex): Boolean = self match {
6274
case Root => false
63-
case Child(parent, current) => regex.matches(current) || parent.hasParentMatching(regex)
75+
case Child(parent, current) => regex.findFirstIn(current).nonEmpty || parent.hasParentMatching(regex)
6476
}
6577
}
6678

zio-profiling/src/main/scala/zio/profiling/causal/CausalProfiler.scala

Lines changed: 44 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -192,56 +192,55 @@ final case class CausalProfiler(
192192
val iterator = fibers.values().iterator()
193193

194194
while (experiment == null && iterator.hasNext()) {
195-
val fiber = iterator.next()
196-
if (fiber.running) {
197-
fiber.costCenter.location.filter(candidateSelector).foreach { candidate =>
198-
results match {
199-
case previous :: _ =>
200-
// with a speedup of 100%, we expect the program to take twice as long
201-
def compensateSpeedup(original: Int): Int = (original * (2 - previous.speedup)).toInt
202-
203-
val minDelta =
204-
if (previous.throughputData.isEmpty) 0
205-
else compensateSpeedup(previous.throughputData.map(_.delta).min)
206-
207-
val nextDuration =
208-
if (minDelta < experimentTargetSamples) {
209-
previous.duration * 2
210-
} else if (
211-
minDelta >= experimentTargetSamples * 2 && previous.duration >= minExperimentDurationNanos * 2
212-
) {
213-
previous.duration / 2
214-
} else {
215-
previous.duration
216-
}
217-
218-
experiment = new Experiment(
219-
candidate,
220-
now,
221-
nextDuration,
222-
selectSpeedUp(),
223-
new ConcurrentHashMap[String, Int](),
224-
new ConcurrentHashMap[String, Int](),
225-
new ConcurrentHashMap[String, Int]()
226-
)
227-
case Nil =>
228-
experiment = new Experiment(
229-
candidate,
230-
now,
231-
minExperimentDurationNanos,
232-
selectSpeedUp(),
233-
new ConcurrentHashMap[String, Int](),
234-
new ConcurrentHashMap[String, Int](),
235-
new ConcurrentHashMap[String, Int]()
236-
)
237-
}
195+
val fiber = iterator.next()
196+
val candidate = fiber.costCenter
197+
if (fiber.running && !candidate.isRoot && candidate.location.fold(false)(candidateSelector)) {
198+
results match {
199+
case previous :: _ =>
200+
// with a speedup of 100%, we expect the program to take twice as long
201+
def compensateSpeedup(original: Int): Int = (original * (2 - previous.speedup)).toInt
202+
203+
val minDelta =
204+
if (previous.throughputData.isEmpty) 0
205+
else compensateSpeedup(previous.throughputData.map(_.delta).min)
206+
207+
val nextDuration =
208+
if (minDelta < experimentTargetSamples) {
209+
previous.duration * 2
210+
} else if (
211+
minDelta >= experimentTargetSamples * 2 && previous.duration >= minExperimentDurationNanos * 2
212+
) {
213+
previous.duration / 2
214+
} else {
215+
previous.duration
216+
}
217+
218+
experiment = new Experiment(
219+
candidate,
220+
now,
221+
nextDuration,
222+
selectSpeedUp(),
223+
new ConcurrentHashMap[String, Int](),
224+
new ConcurrentHashMap[String, Int](),
225+
new ConcurrentHashMap[String, Int]()
226+
)
227+
case Nil =>
228+
experiment = new Experiment(
229+
candidate,
230+
now,
231+
minExperimentDurationNanos,
232+
selectSpeedUp(),
233+
new ConcurrentHashMap[String, Int](),
234+
new ConcurrentHashMap[String, Int](),
235+
new ConcurrentHashMap[String, Int]()
236+
)
238237
}
239238
}
240239
}
241240
if (experiment != null) {
242241
samplingState = SamplingState.ExperimentInProgress(experiment, iteration, results)
243242
logMessage(
244-
s"Starting experiment $iteration (costCenter: ${experiment.candidate}, speedUp: ${experiment.speedUp}, duration: ${experiment.duration}ns)"
243+
s"Starting experiment $iteration (costCenter: ${experiment.candidate.render}, speedUp: ${experiment.speedUp}, duration: ${experiment.duration}ns)"
245244
)
246245
}
247246

@@ -261,7 +260,7 @@ final case class CausalProfiler(
261260
val iterator = fibers.values.iterator()
262261
while (iterator.hasNext()) {
263262
val fiber = iterator.next()
264-
if (fiber.running && fiber.costCenter.hasParent(experiment.candidate)) {
263+
if (fiber.running && fiber.costCenter.isChildOf(experiment.candidate)) {
265264
val delayAmount = (experiment.speedUp * samplingPeriodNanos).toLong
266265
fiber.localDelay.addAndGet(delayAmount)
267266
globalDelay += delayAmount

zio-profiling/src/main/scala/zio/profiling/causal/Experiment.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@
1616

1717
package zio.profiling.causal
1818

19+
import zio.profiling.CostCenter
20+
1921
import java.util.concurrent.ConcurrentHashMap
2022
import scala.jdk.CollectionConverters._
2123

2224
final private class Experiment(
23-
val candidate: String,
25+
val candidate: CostCenter,
2426
val startTime: Long,
2527
val duration: Long,
2628
val speedUp: Float,

zio-profiling/src/main/scala/zio/profiling/causal/ExperimentResult.scala

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616

1717
package zio.profiling.causal
1818

19+
import zio.profiling.CostCenter
20+
1921
final case class ExperimentResult(
20-
selected: String,
22+
selected: CostCenter,
2123
speedup: Float,
2224
duration: Long,
2325
effectiveDuration: Long,
@@ -26,8 +28,8 @@ final case class ExperimentResult(
2628
latencyData: List[LatencyData]
2729
) {
2830

29-
lazy val render: List[String] =
30-
s"experiment\tselected=$selected\tspeedup=$speedup\tduration=$effectiveDuration\tselected-samples=$selectedSamples" ::
31+
def render: List[String] =
32+
s"experiment\tselected=${selected.render}\tspeedup=$speedup\tduration=$effectiveDuration\tselected-samples=$selectedSamples" ::
3133
throughputData.map(_.render) ++
3234
latencyData.map(_.render)
3335
}

zio-profiling/src/main/scala/zio/profiling/sampling/ProfilingResult.scala

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,8 @@ final case class ProfilingResult(entries: List[ProfilingResult.Entry]) {
1212
*
1313
* Example command: `flamegraph.pl profile.folded > profile.svg`
1414
*/
15-
def stackCollapse: List[String] = {
16-
def renderCostCenter(costCenter: CostCenter): String = {
17-
import CostCenter._
18-
costCenter match {
19-
case Root => ""
20-
case Child(Root, name) => name
21-
case Child(parent, name) => s"${renderCostCenter(parent)};$name"
22-
}
23-
}
24-
25-
entries.map { entry =>
26-
s"${renderCostCenter(entry.costCenter)} ${entry.samples}"
27-
}
28-
}
15+
def stackCollapse: List[String] =
16+
entries.map(entry => s"${entry.costCenter.render} ${entry.samples}")
2917

3018
/**
3119
* Convenience method to render the result using `stackCollapse` and write it to a file.
Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package zio.profiling
22

3-
import zio.test.ZIOSpecDefault
3+
import zio.test._
4+
import zio.{Runtime, ZLayer}
45

5-
abstract class BaseSpec extends ZIOSpecDefault
6+
abstract class BaseSpec extends ZIOSpecDefault {
7+
override val bootstrap: ZLayer[Any, Any, TestEnvironment] = testEnvironment ++ Runtime.removeDefaultLoggers
8+
}

0 commit comments

Comments
 (0)