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

Adjust tracing to use IORuntimeConfig #2147

Merged
merged 6 commits into from
Jul 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
25 changes: 1 addition & 24 deletions core/jvm/src/main/java/cats/effect/tracing/TracingConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,35 +24,12 @@ class TracingConstants {
* Sets stack tracing mode for a JVM process, which controls how much stack
* trace information is captured. Acceptable values are: NONE, CACHED, FULL.
*/
private static final String stackTracingMode = Optional.ofNullable(System.getProperty("cats.effect.stackTracingMode"))
private static final String stackTracingMode = Optional.ofNullable(System.getProperty("cats.effect.tracing.mode"))
.filter(x -> !x.isEmpty()).orElse("cached");
djspiewak marked this conversation as resolved.
Show resolved Hide resolved

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

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

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.
*/
static final int traceBufferLogSize = Optional.ofNullable(System.getProperty("cats.effect.traceBufferLogSize"))
.filter(x -> !x.isEmpty()).flatMap(x -> {
try {
return Optional.of(Integer.valueOf(x));
} catch (Exception e) {
return Optional.empty();
}
}).orElse(4);

/**
* Sets the enhanced exceptions flag, which controls whether or not the stack
* traces of IO exceptions are augmented to include async stack trace
* information. Stack tracing must be enabled in order to use this feature. This
* flag is enabled by default.
*/
static final boolean enhancedExceptions = Optional.ofNullable(System.getProperty("cats.effect.enhancedExceptions"))
.map(Boolean::valueOf).orElse(true);
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,27 @@
package cats.effect
package unsafe

import scala.util.Try

private[unsafe] abstract class IORuntimeConfigCompanionPlatform { this: IORuntimeConfig.type =>

// TODO make the cancelation and auto-yield properties have saner names
protected final val Default: IORuntimeConfig = {
val cancelationCheckThreshold =
System.getProperty("cats.effect.cancelation.check.threshold", "512").toInt
Try(System.getProperty("cats.effect.cancelation.check.threshold").toInt).getOrElse(512)
Copy link
Member

Choose a reason for hiding this comment

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

My favorite stdlib monad.

Copy link
Member Author

Choose a reason for hiding this comment

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

"monad"

Copy link
Member

Choose a reason for hiding this comment

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

Exactly.


val autoYieldThreshold =
Try(System.getProperty("cats.effect.auto.yield.threshold.multiplier").toInt)
.getOrElse(2) * cancelationCheckThreshold

val enhancedExceptions =
Try(System.getProperty("cats.effect.tracing.exceptions.enhanced").toBoolean)
.getOrElse(DefaultEnhancedExceptions)

val traceBufferSize =
Try(System.getProperty("cats.effect.tracing.buffer.size").toInt)
.getOrElse(DefaultTraceBufferSize)

apply(
cancelationCheckThreshold,
System
.getProperty("cats.effect.auto.yield.threshold.multiplier", "2")
.toInt * cancelationCheckThreshold)
apply(cancelationCheckThreshold, autoYieldThreshold, enhancedExceptions, traceBufferSize)
}
}
5 changes: 3 additions & 2 deletions core/shared/src/main/scala/cats/effect/IOFiber.scala
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,8 @@ private final class IOFiber[A](
private[this] val cancelationCheckThreshold: Int = runtime.config.cancelationCheckThreshold
private[this] val autoYieldThreshold: Int = runtime.config.autoYieldThreshold

private[this] val tracingEvents: RingBuffer = RingBuffer.empty
private[this] val tracingEvents: RingBuffer =
RingBuffer.empty(runtime.config.traceBufferLogSize)

override def run(): Unit = {
// insert a read barrier after every async boundary
Expand Down Expand Up @@ -1266,7 +1267,7 @@ private final class IOFiber[A](
}

private[this] def runTerminusFailureK(t: Throwable): IO[Any] = {
Tracing.augmentThrowable(t, tracingEvents)
Tracing.augmentThrowable(runtime.config.enhancedExceptions, t, tracingEvents)
done(Outcome.Errored(t))
IOEndFiber
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,5 @@ private[effect] final class RingBuffer private (logSize: Int) {
}

private[effect] object RingBuffer {
import TracingConstants.traceBufferLogSize

def empty: RingBuffer = new RingBuffer(traceBufferLogSize)
def empty(logSize: Int): RingBuffer = new RingBuffer(logSize)
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ private[effect] object Tracing extends ClassValue[TracingEvent] {
"scala."
)

def augmentThrowable(t: Throwable, events: RingBuffer): Unit = {
def augmentThrowable(enhancedExceptions: Boolean, t: Throwable, events: RingBuffer): Unit = {
def applyRunLoopFilter(ste: StackTraceElement): Boolean = {
val name = ste.getClassName
var i = 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,68 @@ package unsafe

final case class IORuntimeConfig private (
val cancelationCheckThreshold: Int,
val autoYieldThreshold: Int)
val autoYieldThreshold: Int,
val enhancedExceptions: Boolean,
val traceBufferSize: Int) {

def this(cancelationCheckThreshold: Int, autoYieldThreshold: Int) =
this(
cancelationCheckThreshold,
autoYieldThreshold,
IORuntimeConfig.DefaultEnhancedExceptions,
IORuntimeConfig.DefaultTraceBufferSize)
djspiewak marked this conversation as resolved.
Show resolved Hide resolved

def copy(
cancelationCheckThreshold: Int = this.cancelationCheckThreshold,
autoYieldThreshold: Int = this.autoYieldThreshold,
enhancedExceptions: Boolean = this.enhancedExceptions,
traceBufferSize: Int = this.traceBufferSize): IORuntimeConfig =
new IORuntimeConfig(
cancelationCheckThreshold,
autoYieldThreshold,
enhancedExceptions,
traceBufferSize)

// shim for binary compat
private[unsafe] def copy(
cancelationCheckThreshold: Int,
autoYieldThreshold: Int): IORuntimeConfig =
new IORuntimeConfig(
cancelationCheckThreshold,
autoYieldThreshold,
enhancedExceptions,
traceBufferSize)

private[effect] val traceBufferLogSize: Int =
Math.round(Math.log(traceBufferSize.toDouble) / Math.log(2)).toInt
}

object IORuntimeConfig extends IORuntimeConfigCompanionPlatform {

// these have to be defs because we forward-reference them from the companion platform
private[unsafe] def DefaultEnhancedExceptions = true
private[unsafe] def DefaultTraceBufferSize = 16

def apply(): IORuntimeConfig = Default

def apply(cancelationCheckThreshold: Int, autoYieldThreshold: Int): IORuntimeConfig = {
def apply(cancelationCheckThreshold: Int, autoYieldThreshold: Int): IORuntimeConfig =
apply(
cancelationCheckThreshold,
autoYieldThreshold,
DefaultEnhancedExceptions,
DefaultTraceBufferSize)

def apply(
cancelationCheckThreshold: Int,
autoYieldThreshold: Int,
enhancedExceptions: Boolean,
traceBufferSize: Int): IORuntimeConfig = {
if (autoYieldThreshold % cancelationCheckThreshold == 0)
new IORuntimeConfig(cancelationCheckThreshold, autoYieldThreshold)
new IORuntimeConfig(
cancelationCheckThreshold,
autoYieldThreshold,
enhancedExceptions,
1 << Math.round(Math.log(traceBufferSize.toDouble) / Math.log(2)).toInt)
else
throw new AssertionError(
s"Auto yield threshold $autoYieldThreshold must be a multiple of cancelation check threshold $cancelationCheckThreshold")
Expand Down
21 changes: 10 additions & 11 deletions docs/tracing.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ enabled before deploying it to a production environment!

### Configuration
The stack tracing mode of an application is configured by the system property
`cats.effect.stackTracingMode`. There are 3 stack tracing modes: `none`,
`cats.effect.tracing.mode`. There are 3 stack tracing modes: `none`,
`cached` and `full`. These values are case-insensitive. The default tracing mode
is `cached`, which uses a global cache to avoid expensive recomputation of stack
tracing information.
Expand All @@ -91,15 +91,14 @@ To prevent unbounded memory usage, stack tracing information for a given fiber
is accumulated in an internal buffer as execution proceeds. If more traces are
collected than the buffer can retain, the oldest trace information will be
overwritten. The default size of the buffer is 16. This value can be configured
via the system property `cats.effect.traceBufferLogSize`. Keep in mind that the
provided value is a logarithm of the actual size of the buffer (i.e. the actual
size of the tracing information buffer is
2<sup>`cats.effect.traceBufferLogSize`</sup>).
via the system property `cats.effect.tracing.buffer.size`. Keep in mind that the
value configured by the system property must be a power of two and will be rounded to the nearest power if not.

For example, to enable full stack tracing and a trace buffer of size 1024,
specify the following system properties:

```
-Dcats.effect.stackTracingMode=full -Dcats.effect.traceBufferLogSize=10
-Dcats.effect.tracing.mode=full -Dcats.effect.tracing.buffer.size=1024
```

## Stack tracing modes
Expand Down Expand Up @@ -239,23 +238,23 @@ deeply nested recursive `IO` programs like the one on this page, even with a
fairly small buffer. The example was generated using the following stack tracing
configuration:
```
-Dcats.effect.stackTracingMode=full -Dcats.effect.traceBufferLogSize=6
-Dcats.effect.tracing.mode=full -Dcats.effect.tracing.buffer.size=64
```

The enhanced exceptions feature is controlled by the system property
`cats.effect.enhancedExceptions`. It is enabled by default.
`cats.effect.tracing.exceptions.enhanced`. It is enabled by default.

It can be disabled with the following configuration:
```
-Dcats.effect.enhancedExceptions=false
-Dcats.effect.tracing.exceptions.enhanced=false
```

### Complete code
Here is the code snippet that was used to generate the above examples:
```scala mdoc
// Pass the following system property to your JVM:
// -Dcats.effect.stackTracingMode=full
// -Dcats.effect.traceBufferLogSize=6
// -Dcats.effect.tracing.mode=full
// -Dcats.effect.tracing.buffer.size=64

import cats.effect.{IO, IOApp}

Expand Down