Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fiber tracing: asynchronous stack tracing #854

Merged
merged 80 commits into from
Jul 11, 2020
Merged
Show file tree
Hide file tree
Changes from 77 commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
72952a1
wip
Apr 26, 2020
5301fe9
Introduce global tracing flag
Apr 27, 2020
6c404dd
Capture traces in more functions
Apr 27, 2020
887a3d7
IOContext for threading state across asynchronous boundaries
Apr 27, 2020
3159e9e
Inspect tracing mode
Apr 27, 2020
2b5f569
wip
Apr 27, 2020
41f06b3
Add Example
Apr 27, 2020
f0a715f
Hold frames in IOContext
Apr 27, 2020
995ac9f
scalafmt
Apr 27, 2020
c8890f1
Clean up
Apr 27, 2020
b6bb1e5
disabled mode
Apr 27, 2020
ff2ec7e
wip
Apr 28, 2020
72d9723
Basic trace calculation
Apr 28, 2020
bf659ad
WIP
Apr 28, 2020
c23b4df
comment
Apr 29, 2020
4666088
Lexically scoped tracing via ThreadLocal
Apr 29, 2020
fe61325
Check flag
May 1, 2020
99eb9ca
WIP
May 2, 2020
09da37f
default to false
May 2, 2020
1cbe1f5
address pr feedback
May 10, 2020
3a2c39e
final class
May 10, 2020
0772c7e
isTracingEnabled
May 12, 2020
abce87f
Mutable, thread unsafe IOContext
May 16, 2020
7556b2c
Ring buffer to back trace frames
May 17, 2020
4f4dbd5
Hold trace in constructor argument
May 23, 2020
6e9ceab
Add trace to IOContext
May 23, 2020
91ab5b7
Don't trace Pure
May 24, 2020
3c792cf
Trace nodes
May 24, 2020
2131308
Pretty printing and demangling
May 24, 2020
e17a795
Trace tags
May 24, 2020
e36263d
Trace more instructions
May 24, 2020
bfa4e1b
Hybrid tracing
May 26, 2020
308789f
Remove Introspect instruction
May 28, 2020
77ccab3
Ring buffer tests
May 28, 2020
d234eec
Use private[this] in IOTracing
May 29, 2020
e52aee4
wip
May 29, 2020
74f7e3d
optimizations
May 29, 2020
2eb49f4
Use AnyRef to capture trace frame
Jun 2, 2020
7b980d8
Don't push to ring buffer when not in a tracing region
Jun 4, 2020
bf95848
wip
Jun 4, 2020
273a0e4
Replace global tracing boolean with global tracing mode
Jun 8, 2020
e6b629b
RunLoop context passing
Jun 9, 2020
a185509
Trace async, asyncF, cancelable
Jun 9, 2020
7c22a40
fix
Jun 9, 2020
c4630cb
TracingMode in java
Jun 9, 2020
e26cc66
comments
Jun 9, 2020
b1054e2
Move stack trace filtering to render function
Jun 9, 2020
1392be9
address pr feedback
Jun 17, 2020
bed2862
Fix JS cross build
Jun 19, 2020
f9131b3
Merge Tracing and TracingPlatform classes
Jun 19, 2020
1e4eb73
Tracing tests
Jun 19, 2020
b7a2f81
Split out rabbit and slug tracing tests
Jun 20, 2020
34e25aa
IOContextTests
Jun 20, 2020
0819d06
Start adding documentation
Jun 20, 2020
2b9124b
Explain use cases of tracing more
Jun 20, 2020
197a4d0
Remove map fusion.
Jun 21, 2020
1c42500
Merge remote-tracking branch 'origin/master' into fiber-tracing
Jun 21, 2020
58cc9f5
Format build.sbt
Jun 21, 2020
e5ce64d
More documentation and renaming
Jun 23, 2020
4f6a37f
Remove map fusion configuration
Jun 23, 2020
0e7aa80
RingBuffer improvements
Jul 9, 2020
442fe1b
address pr feedback
Jul 9, 2020
4db5554
wip
Jul 9, 2020
a277e17
doc
Jul 9, 2020
f6892fa
restore benchmarks
Jul 9, 2020
6cbda28
Declare ring buffer instance variables as private[this]
Jul 10, 2020
9f396b5
Remove TraceTag
Jul 10, 2020
1a3a946
docs
Jul 10, 2020
9adfe41
Add tracing on raiseError and docs update
Jul 10, 2020
895ccaa
doc updates
Jul 10, 2020
46a5142
fix tracing tests
Jul 10, 2020
fa90a3a
Only run tracing tests for JVM
Jul 10, 2020
10bd15f
Reorganize test directories
Jul 10, 2020
9fe4ece
Add mima filters
Jul 10, 2020
cb29e7e
Remove unused import in synciotests
Jul 10, 2020
c22f731
Refactor for IOEvent and better printers
Jul 11, 2020
b06b109
Printing options
Jul 11, 2020
42aef25
Fix tests
Jul 11, 2020
5267dd6
Merge branch 'master' into fiber-tracing
Jul 11, 2020
7ba1b11
fix build
Jul 11, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class MapCallsBenchmark {
import MapCallsBenchmark.test

@Benchmark
def one(): Long = test(12000, 1)
def one(): Long = test(1, 1)

@Benchmark
def batch30(): Long = test(12000 / 30, 30)
Expand Down
49 changes: 47 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,21 @@ val mimaSettings = Seq(
exclude[IncompatibleSignatureProblem]("cats.effect.Resource.evalTap"),
// change in encoding of value classes in generic methods https://github.com/lightbend/mima/issues/423
exclude[IncompatibleSignatureProblem]("cats.effect.Blocker.apply"),
exclude[IncompatibleSignatureProblem]("cats.effect.Blocker.fromExecutorService")
exclude[IncompatibleSignatureProblem]("cats.effect.Blocker.fromExecutorService"),
// Tracing - https://github.com/typelevel/cats-effect/pull/854
exclude[DirectMissingMethodProblem]("cats.effect.IO#Async.apply"),
exclude[DirectMissingMethodProblem]("cats.effect.IO#Bind.apply"),
exclude[IncompatibleResultTypeProblem]("cats.effect.IO#Async.k"),
exclude[DirectMissingMethodProblem]("cats.effect.IO#Async.copy"),
exclude[IncompatibleResultTypeProblem]("cats.effect.IO#Async.copy$default$1"),
exclude[DirectMissingMethodProblem]("cats.effect.IO#Async.this"),
exclude[DirectMissingMethodProblem]("cats.effect.IO#Bind.copy"),
exclude[DirectMissingMethodProblem]("cats.effect.IO#Bind.this"),
exclude[DirectMissingMethodProblem]("cats.effect.IO#Map.index"),
exclude[IncompatibleMethTypeProblem]("cats.effect.IO#Map.copy"),
exclude[IncompatibleResultTypeProblem]("cats.effect.IO#Map.copy$default$3"),
exclude[IncompatibleMethTypeProblem]("cats.effect.IO#Map.this"),
exclude[IncompatibleMethTypeProblem]("cats.effect.IO#Map.apply")
)
}
)
Expand Down Expand Up @@ -226,7 +240,7 @@ lazy val sharedSourcesSettings = Seq(
lazy val root = project
.in(file("."))
.disablePlugins(MimaPlugin)
.aggregate(coreJVM, coreJS, lawsJVM, lawsJS)
.aggregate(coreJVM, coreJS, lawsJVM, lawsJS, tracingTests)
.settings(skipOnPublishSettings)

lazy val core = crossProject(JSPlatform, JVMPlatform)
Expand Down Expand Up @@ -291,6 +305,37 @@ lazy val laws = crossProject(JSPlatform, JVMPlatform)
lazy val lawsJVM = laws.jvm
lazy val lawsJS = laws.js

lazy val FullTracingTest = config("fulltracing").extend(Test)

lazy val tracingTests = project
.in(file("tracing-tests"))
.dependsOn(coreJVM)
.settings(commonSettings ++ skipOnPublishSettings)
.settings(
libraryDependencies ++= Seq(
"org.typelevel" %%% "cats-laws" % CatsVersion,
"org.typelevel" %%% "discipline-scalatest" % DisciplineScalatestVersion % Test
)
)
.configs(FullTracingTest)
.settings(inConfig(FullTracingTest)(Defaults.testSettings): _*)
.settings(
unmanagedSourceDirectories in FullTracingTest += {
baseDirectory.value.getParentFile / "src" / "fulltracing" / "scala"
},
test in Test := (test in Test).dependsOn(test in FullTracingTest).value,
fork in Test := true,
fork in FullTracingTest := true,
javaOptions in Test ++= Seq(
"-Dcats.effect.tracing=true",
"-Dcats.effect.stackTracingMode=cached"
),
javaOptions in FullTracingTest ++= Seq(
"-Dcats.effect.tracing=true",
"-Dcats.effect.stackTracingMode=full"
)
)

lazy val benchmarksPrev = project
.in(file("benchmarks/vPrev"))
.settings(commonSettings ++ skipOnPublishSettings ++ sharedSourcesSettings)
Expand Down
9 changes: 0 additions & 9 deletions core/js/src/main/scala/cats/effect/internals/IOPlatform.scala
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,6 @@ private[effect] object IOPlatform {
}
}

/**
* Establishes the maximum stack depth for `IO#map` operations
* for JavaScript.
*
* The default for JavaScript is 32, from which we subtract 1
* as an optimization.
*/
final val fusionMaxStackDepth = 31

/** Returns `true` if the underlying platform is the JVM,
* `false` if it's JavaScript. */
final val isJVM = false
Expand Down
2 changes: 1 addition & 1 deletion core/js/src/main/scala/cats/effect/internals/IOTimer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ final private[internals] class IOTimer(ec: ExecutionContext) extends Timer[IO] {

def sleep(timespan: FiniteDuration): IO[Unit] =
IO.Async(new IOForkedStart[Unit] {
def apply(conn: IOConnection, cb: Either[Throwable, Unit] => Unit): Unit = {
def apply(conn: IOConnection, ctx: IOContext, cb: Either[Throwable, Unit] => Unit): Unit = {
val task = setTimeout(timespan.toMillis, ec, new ScheduledTick(conn, cb))
// On the JVM this would need a ForwardCancelable,
// but not on top of JS as we don't have concurrency
Expand Down
27 changes: 27 additions & 0 deletions core/js/src/main/scala/cats/effect/internals/TracingPlatform.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright (c) 2017-2019 The Typelevel Cats-effect Project Developers
*
* 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 cats.effect.internals

object TracingPlatform {
final val isCachedStackTracing: Boolean = false

final val isFullStackTracing: Boolean = false

final val isStackTracing: Boolean = isFullStackTracing || isCachedStackTracing

final val traceBufferSize: Int = 32
}
63 changes: 63 additions & 0 deletions core/jvm/src/main/java/cats/effect/internals/TracingPlatform.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright (c) 2017-2019 The Typelevel Cats-effect Project Developers
*
* 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 cats.effect.internals;

import java.util.Optional;

/**
* Holds platform-specific flags that control tracing behavior.
*
* The Scala compiler inserts a volatile bitmap access for module field accesses.
* Because the `tracingMode` flag is read in various IO combinators, we are opting
* to define it in a Java source file to avoid the volatile access.
*
* INTERNAL API.
*/
public final class TracingPlatform {

/**
* Sets stack tracing mode for a JVM process, which controls
* how much stack trace information is captured.
* Acceptable values are: NONE, CACHED, FULL.
RaasAhsan marked this conversation as resolved.
Show resolved Hide resolved
*/
private static final String stackTracingMode = Optional.ofNullable(System.getProperty("cats.effect.stackTracingMode"))
.filter(x -> !x.isEmpty())
.orElse("cached");

public static final boolean isCachedStackTracing = stackTracingMode.equalsIgnoreCase("cached");

public static final boolean isFullStackTracing = stackTracingMode.equalsIgnoreCase("full");

public static final boolean isStackTracing = isFullStackTracing || isCachedStackTracing;

/**
* The number of trace lines to retain during tracing. If more trace
* lines are produced, then the oldest trace lines will be discarded.
* Automatically rounded up to the nearest power of 2.
*/
public static final int traceBufferSize = Optional.ofNullable(System.getProperty("cats.effect.traceBufferSize"))
.filter(x -> !x.isEmpty())
.flatMap(x -> {
try {
return Optional.of(Integer.valueOf(x));
} catch (Exception e) {
return Optional.empty();
}
})
.orElse(128);

}
33 changes: 1 addition & 32 deletions core/jvm/src/main/scala/cats/effect/internals/IOPlatform.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import java.util.concurrent.locks.AbstractQueuedSynchronizer
import cats.effect.IO
import scala.concurrent.blocking
import scala.concurrent.duration.{Duration, FiniteDuration}
import scala.util.{Either, Try}
import scala.util.Either

private[effect] object IOPlatform {

Expand Down Expand Up @@ -73,37 +73,6 @@ private[effect] object IOPlatform {
}
}

/**
* Establishes the maximum stack depth for `IO#map` operations.
*
* The default is `128`, from which we subtract one as an
* optimization. This default has been reached like this:
*
* - according to official docs, the default stack size on 32-bits
* Windows and Linux was 320 KB, whereas for 64-bits it is 1024 KB
* - according to measurements chaining `Function1` references uses
* approximately 32 bytes of stack space on a 64 bits system;
* this could be lower if "compressed oops" is activated
* - therefore a "map fusion" that goes 128 in stack depth can use
* about 4 KB of stack space
*
* If this parameter becomes a problem, it can be tuned by setting
* the `cats.effect.fusionMaxStackDepth` system property when
* executing the Java VM:
*
* <pre>
* java -Dcats.effect.fusionMaxStackDepth=32 \
* ...
* </pre>
*/
final val fusionMaxStackDepth =
Option(System.getProperty("cats.effect.fusionMaxStackDepth", ""))
.filter(s => s != null && s.nonEmpty)
.flatMap(s => Try(s.toInt).toOption)
.filter(_ > 0)
.map(_ - 1)
.getOrElse(127)

/**
* Returns `true` if the underlying platform is the JVM,
* `false` if it's JavaScript.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ final private[internals] class IOTimer private (ec: ExecutionContext, sc: Schedu

override def sleep(timespan: FiniteDuration): IO[Unit] =
IO.Async(new IOForkedStart[Unit] {
def apply(conn: IOConnection, cb: T[Unit]): Unit = {
def apply(conn: IOConnection, ctx: IOContext, cb: T[Unit]): Unit = {
// Doing what IO.cancelable does
val ref = ForwardCancelable()
conn.push(ref.cancel)
Expand Down
Loading