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

Combinator inference #935 #981

Merged
merged 10 commits into from Jul 26, 2020

Conversation

RaasAhsan
Copy link

This PR brings the next tracing addition to Cats Effect 2, combinator inference. In the current tracing implementation, only a particular set of primitive operations were traced and rendered. However, there is a large collection of derived combinators we should print out instead because we have that information via the stack trace.

Example

A sample program:

def program: IO[Unit] =
  for {
    _ <- IO(print("Hello ")) *> IO(println("world!"))
    _ <- (IO(1), IO(2), IO(3)).parMapN(_ + _ + _)
    _ <- IO.unit.bracket(_ => IO.unit)(_ => IO.unit)
    _ <- (IO(1), IO(2), IO(3)).parMapN(_ + _ + _)
    t <- IO.trace
    _ <- t.printFiberTrace()
  } yield ()

override def run(args: List[String]): IO[ExitCode] =
  program.as(ExitCode.Success)

The trace we would see before this PR:

IOTrace: 12 frames captured, 0 omitted
 ├ flatMap at org.simpleapp.examples.Main$.main (Main.scala:22)
 ├ map at org.simpleapp.examples.Main$.run (Main.scala:35)
 ├ flatMap at org.simpleapp.examples.Main$.program (Main.scala:26)
 ├ flatMap at org.simpleapp.examples.Main$.program (Main.scala:26)
 ├ flatMap at org.simpleapp.examples.Main$.program (Main.scala:27)
 ├ map at org.simpleapp.examples.Main$.program (Main.scala:27)
 ├ flatMap at org.simpleapp.examples.Main$.program (Main.scala:28)
 ├ flatMap at (...)
 ├ map at (...)
 ├ flatMap at org.simpleapp.examples.Main$.program (Main.scala:29)
 ├ map at org.simpleapp.examples.Main$.program (Main.scala:27)
 ╰ flatMap at org.simpleapp.examples.Main$.program (Main.scala:30)

The trace we would see after this PR:

IOTrace: 13 frames captured, 0 omitted
 ├ main$ at org.simpleapp.examples.Main$.main (Main.scala:22)
 ├ as at org.simpleapp.examples.Main$.run (Main.scala:35)
 ├ flatMap at org.simpleapp.examples.Main$.program (Main.scala:26)
 ├ $times$greater at org.simpleapp.examples.Main$.program (Main.scala:26)
 ├ flatMap at org.simpleapp.examples.Main$.program (Main.scala:27)
 ├ parMapN at org.simpleapp.examples.Main$.program (Main.scala:27)
 ├ flatMap at org.simpleapp.examples.Main$.program (Main.scala:28)
 ├ bracket at org.simpleapp.examples.Main$.program (Main.scala:28)
 ├ (...)
 ├ (...)
 ├ flatMap at org.simpleapp.examples.Main$.program (Main.scala:29)
 ├ parMapN at org.simpleapp.examples.Main$.program (Main.scala:27)
 ╰ flatMap at org.simpleapp.examples.Main$.program (Main.scala:30)

We now see a much richer trace that more accurately reflects the operation that was performed. Some observations:

  1. This trace was produced in the cached tracing mode. We have two calls to parMapN, but the corresponding code locations are the exact same. This is another limitation of the cached tracing mode in conjunction with the implementation of parMapN. Other derived combinators like parTraverse and as will exhibit this behavior as well. However, in full tracing mode, the appropriate code location will be printed.
  2. Symbolic functions like *> are rendered a textual form, but we should be able to recover the original name. We should fix this in future PRs.
  3. Sometimes trace lines (...) appear. This usually appears when some operation is traced after shifting thread pools, like bracket. We might be able to address this by explicitly not tracing those internal functions.

Note that the specific set of derived combinators that we can trace are currently restricted to those under the cats. package. This may be customizable in the future.

Closes #935.

@RaasAhsan
Copy link
Author

@djspiewak After this PR is merged, I think we should make another RC.

@@ -310,8 +310,8 @@ lazy val lawsJS = laws.js

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

lazy val tracingTests = project
.in(file("tracing-tests"))
lazy val runtimeTests = project
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

runtimeTests or systemTests or something else was what I had in mind

import cats.effect.IO
import cats.effect.internals.TracingPlatform.{isCachedStackTracing, isFullStackTracing}

private[effect] object IOAsync {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can probably move this into IO companion object, but I'm not sure what to name it

Copy link
Member

@djspiewak djspiewak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is exceptionally cool! Also I think we can probably just do unmangling even in this PR, since it's directly available in scala.reflect.NameTransformer.decode :-)

@djspiewak djspiewak merged commit 2e79db1 into typelevel:series/2.x Jul 26, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add combinator inference for tracing
2 participants