Skip to content

Commit ccfe957

Browse files
committed
Profiling fix
1 parent b242752 commit ccfe957

File tree

9 files changed

+81
-74
lines changed

9 files changed

+81
-74
lines changed

build.mill

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ object sls extends CommonScalaModule {
7070

7171
def moduleDeps: Seq[JavaModule] = Seq(slsSmithy, profilingRuntime)
7272

73+
override def forkArgs: T[Seq[String]] = Seq(
74+
"-Dcats.effect.trackFiberContext=true"
75+
)
76+
7377
def ivyDeps = Agg(
7478
ivy"org.typelevel::cats-effect:3.6.3",
7579
ivy"co.fs2::fs2-io:3.13.0-M2",
@@ -79,15 +83,14 @@ object sls extends CommonScalaModule {
7983
ivy"ch.qos.logback:logback-classic:1.4.14",
8084
ivy"com.lihaoyi::os-lib:0.11.4",
8185
ivy"org.polyvariant.smithy4s-bsp::bsp4s:0.5.0",
82-
ivy"org.scalameta:mtags-interfaces:1.5.1",
86+
ivy"org.scalameta:mtags-interfaces:1.6.1-SNAPSHOT",
8387
ivy"com.evolution::scache:5.1.2",
8488
ivy"org.typelevel::cats-parse:1.1.0",
8589
ivy"io.get-coursier:interface:1.0.28",
8690
)
8791

8892
def javacOptions = Seq(
8993
"-Dotel.service.name=simple-language-server",
90-
"-Dcats.effect.trackFiberContext=true"
9194
)
9295

9396
object test extends ScalaTests {

profilingRuntime/src/org/scala/abusers/profiling/runtime/LocalSpan.scala

Lines changed: 0 additions & 25 deletions
This file was deleted.

profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingExecutionContext.scala

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,34 @@
1-
package org.scala.abusers.profiling.runtime
1+
package cats.effect.org.scala.abusers.profiling.runtime
22

33
import scala.concurrent.ExecutionContext
44
import io.pyroscope.vendor.one.profiler.AsyncProfiler
5+
import org.typelevel.otel4s.sdk.context.Context
6+
import org.typelevel.otel4s.sdk.trace.SdkContextKeys
7+
import org.typelevel.otel4s.sdk.context.TraceContext
58

69
object ProfilingExecutionContext {
710

8-
def wrapExecutionContext(ec: ExecutionContext, profiler: AsyncProfiler, localSpan: LocalSpan): ExecutionContext = {
11+
def wrapExecutionContext(ec: ExecutionContext, profiler: AsyncProfiler, localContext: ThreadLocal[Context]): ExecutionContext = {
912
new ExecutionContext {
1013
def execute(runnable: Runnable): Unit = {
11-
val spanOpt = localSpan.get()
1214

1315
ec.execute(new Runnable {
16+
val ctx = localContext.get()
1417
def run(): Unit = {
1518

19+
val spanOpt = ctx
20+
.get(SdkContextKeys.SpanContextKey)
21+
.filter(_.isValid)
22+
.map { ctx =>
23+
TraceContext(
24+
ctx.traceId,
25+
ctx.spanId,
26+
ctx.isSampled
27+
)
28+
}
29+
1630
spanOpt.foreach { span =>
17-
profiler.setTracingContext(span.spanId, 0)
31+
profiler.setTracingContext(span.spanId.toLong(signed = false), 0)
1832
}
1933

2034
try {

profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingIOApp.scala

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,18 @@ import cats.effect.unsafe.IORuntimeBuilder
1919
import org.typelevel.otel4s.experimental.metrics._
2020
import org.typelevel.otel4s.trace.Tracer
2121
import org.typelevel.otel4s.metrics.Meter
22+
import org.typelevel.otel4s.context.LocalProvider
23+
import org.typelevel.otel4s.sdk.context.Context
24+
import cats.effect.org.scala.abusers.profiling.runtime.ProfilingExecutionContext
2225

26+
object ProfilingIOAppSettings {
27+
lazy val isEnabled: Boolean = sys.env.get("SLS_PROFILING").contains("true") || sys.props.get("sls.profiling").contains("true")
28+
}
2329

2430
trait ProfilingIOApp extends IOApp {
2531
private lazy val profiler = PyroscopeAsyncProfiler.getAsyncProfiler()
26-
private lazy val localSpan0: IOLocal[Option[TraceSpan]] = IOLocal[Option[TraceSpan]](None)
32+
33+
private given localCtx: IOLocal[Context] = IOLocal[Context](Context.root)
2734
.syncStep(100)
2835
.flatMap(
2936
_.leftMap(_ =>
@@ -34,28 +41,29 @@ trait ProfilingIOApp extends IOApp {
3441
)
3542
.unsafeRunSync()
3643

44+
private val threadLocal = localCtx.unsafeThreadLocal()
3745

38-
private val localSpan = new LocalSpan(() => localSpan0)
39-
private val _runtime = {
46+
private lazy val _runtime =
4047
IORuntimeBuilder()
41-
.transformCompute { ec => ProfilingExecutionContext.wrapExecutionContext(ec, profiler, localSpan) }
42-
.transformBlocking { ec => ProfilingExecutionContext.wrapExecutionContext(ec, profiler, localSpan) }
48+
.transformCompute { ec => ProfilingExecutionContext.wrapExecutionContext(ec, profiler, threadLocal) }
49+
.transformBlocking { ec => ProfilingExecutionContext.wrapExecutionContext(ec, profiler, threadLocal) }
4350
.build()
44-
}
4551

46-
override protected def runtime: IORuntime = _runtime
52+
override protected def runtime: IORuntime = if ProfilingIOAppSettings.isEnabled then _runtime else null /* Null is the default value */
53+
54+
def program(using meter: Meter[IO], tracer: Tracer[IO]): Resource[IO, Unit]
55+
def applicationName: String
4756

4857
override def run(args: List[String]): IO[ExitCode] =
4958
OpenTelemetrySdk
50-
.autoConfigured[IO]( // register OTLP exporters configurer
59+
.autoConfigured[IO](
5160
_.addExportersConfigurer(OtlpExportersAutoConfigure[IO])
52-
.addTracerProviderCustomizer((builder, _) =>
53-
builder.addSpanProcessor(new ProfilingSpanProcessor(localSpan)
54-
))
61+
.addTracerProviderCustomizer((builder, _) => builder.addSpanProcessor(new ProfilingSpanProcessor))
5562
)
56-
.flatTap(_ => registerPyroscope()) // register Pyroscope agent
63+
.flatTap(_ => registerPyroscope())
5764
.use { autoConfigured =>
5865
val sdk = autoConfigured.sdk
66+
5967
val app = for {
6068
given Meter[IO] <- sdk.meterProvider.get(applicationName).toResource
6169
given Tracer[IO] <- sdk.tracerProvider.get(applicationName).toResource
@@ -66,9 +74,6 @@ trait ProfilingIOApp extends IOApp {
6674
app.useForever
6775
}.as(ExitCode.Success)
6876

69-
def program(using meter: Meter[IO], tracer: Tracer[IO]): Resource[IO, Unit]
70-
def applicationName: String
71-
7277
private def registerPyroscope(): Resource[IO, Unit] = {
7378
val acquire = IO.delay {
7479
PyroscopeAgent.start(
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package org.scala.abusers.profiling.runtime
2+
3+
4+
import org.typelevel.otel4s.trace.SpanOps
5+
import org.typelevel.otel4s.trace.Span
6+
import cats.effect.IO
7+
8+
object ProfilingOps {
9+
extension (ops: SpanOps[IO]) {
10+
def profilingUse[A](f: Span[IO] => IO[A]): IO[A] =
11+
if ProfilingIOAppSettings.isEnabled then
12+
ops.use(span => (IO.cede *> f(span)).guarantee(IO.cede))
13+
else ops.use(f)
14+
15+
inline def profilingSurround[A](fa: IO[A]): IO[A] = profilingUse(_ => fa)
16+
}
17+
}

profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingSpanProcessor.scala

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,19 @@ import org.typelevel.otel4s.sdk.trace.SpanRef
99
import org.typelevel.otel4s.sdk.trace.data.SpanData
1010
import org.typelevel.otel4s.Attribute
1111

12-
class ProfilingSpanProcessor(localSpan: LocalSpan) extends SpanProcessor[IO] {
12+
class ProfilingSpanProcessor extends SpanProcessor[IO] {
1313

1414
override def name: String = "ProfilingSpanProcessor"
1515

1616
override def onStart: OnStart[IO] = new OnStart[IO] {
1717
override def apply(parentContext: Option[SpanContext], span: SpanRef[IO]): IO[Unit] = {
1818
val spanIdStr = span.context.spanIdHex
19-
val spanId = java.lang.Long.parseUnsignedLong(spanIdStr, 16)
20-
for {
21-
_ <- span.addAttributes(Seq(Attribute("pyroscope.profile.id", spanIdStr)))
22-
_ <- IO(localSpan.set(Some(TraceSpan(spanId))))
23-
} yield ()
19+
span.addAttributes(Seq(Attribute("pyroscope.profile.id", spanIdStr)))
2420
}
2521
}
2622

2723
override def onEnd: OnEnd[IO] = new OnEnd[IO] {
28-
override def apply(span: SpanData): IO[Unit] = IO(localSpan.set(None))
24+
override def apply(span: SpanData): IO[Unit] = IO.unit
2925
}
3026

3127
override def forceFlush: IO[Unit] = IO.unit

sls/src/org/scala/abusers/sls/ComputationQueue.scala

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,19 @@ package org.scala.abusers.sls
22

33
import cats.effect.IO
44
import cats.effect.std.Semaphore
5-
import org.typelevel.otel4s.trace.Tracer
65

76
sealed trait SynchronizedState
87

98
trait ComputationQueue {
109
protected given SynchronizedState = new SynchronizedState {}
11-
def synchronously[A](computation: SynchronizedState ?=> IO[A])(using Tracer[IO]): IO[A]
10+
def synchronously[A](computation: SynchronizedState ?=> IO[A]): IO[A]
1211
}
1312

1413
class ComputationQueueImpl(semaphore: Semaphore[IO]) extends ComputationQueue {
15-
def synchronously[A](computation: SynchronizedState ?=> IO[A])(using Tracer[IO]): IO[A] = {
16-
semaphore.permit.use { _ =>
17-
(computation)
18-
}
19-
}
14+
def synchronously[A](computation: SynchronizedState ?=> IO[A]): IO[A] =
15+
semaphore.permit.surround(computation)
2016
}
2117

2218
object ComputationQueue {
23-
def instance: IO[ComputationQueue] =
24-
Semaphore[IO](1).map(ComputationQueueImpl.apply)
19+
def instance: IO[ComputationQueue] = Semaphore[IO](1).map(ComputationQueueImpl.apply)
2520
}

sls/src/org/scala/abusers/sls/ServerImpl.scala

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import LoggingUtils.*
3535
import ScalaBuildTargetInformation.*
3636
import org.typelevel.otel4s.trace.Tracer
3737
import org.typelevel.otel4s.metrics.Meter
38+
import org.scala.abusers.profiling.runtime.ProfilingOps.*
3839

3940

4041
class ServerImpl(
@@ -85,23 +86,23 @@ class ServerImpl(
8586
computationQueue.synchronously { bspStateManager.importBuild }
8687

8788
def textDocumentCompletionOp(params: lsp.CompletionParams): IO[lsp.TextDocumentCompletionOpOutput] =
88-
Tracer[IO].span("completion").surround(handleCompletion(params))
89+
Tracer[IO].span("completion").profilingSurround(handleCompletion(params))
8990
def textDocumentDefinitionOp(params: lsp.DefinitionParams): IO[lsp.TextDocumentDefinitionOpOutput] =
90-
Tracer[IO].span("go-to-defintion").surround(handleDefinition(params))
91+
Tracer[IO].span("go-to-defintion").profilingSurround(handleDefinition(params))
9192
def textDocumentDidChange(params: lsp.DidChangeTextDocumentParams): IO[Unit] =
92-
Tracer[IO].span("did-change").surround(handleDidChange(params))
93+
Tracer[IO].span("did-change").profilingSurround(handleDidChange(params))
9394
def textDocumentDidClose(params: lsp.DidCloseTextDocumentParams): IO[Unit] =
94-
Tracer[IO].span("did-close").surround(handleDidClose(params))
95+
Tracer[IO].span("did-close").profilingSurround(handleDidClose(params))
9596
def textDocumentDidOpen(params: lsp.DidOpenTextDocumentParams): IO[Unit] =
96-
Tracer[IO].span("did-open").surround(handleDidOpen(params))
97+
Tracer[IO].span("did-open").profilingSurround(handleDidOpen(params))
9798
def textDocumentDidSave(params: lsp.DidSaveTextDocumentParams): IO[Unit] =
98-
Tracer[IO].span("did-save").surround(handleDidSave(params))
99+
Tracer[IO].span("did-save").profilingSurround(handleDidSave(params))
99100
def textDocumentHoverOp(params: lsp.HoverParams): IO[lsp.TextDocumentHoverOpOutput] =
100-
Tracer[IO].span("hover").surround(handleHover(params))
101+
Tracer[IO].span("hover").profilingSurround(handleHover(params))
101102
def textDocumentInlayHintOp(params: lsp.InlayHintParams): IO[lsp.TextDocumentInlayHintOpOutput] =
102-
Tracer[IO].span("inlay-hints").surround(handleInlayHints(params))
103+
Tracer[IO].span("inlay-hints").profilingSurround(handleInlayHints(params))
103104
def textDocumentSignatureHelpOp(params: lsp.SignatureHelpParams): IO[lsp.TextDocumentSignatureHelpOpOutput] =
104-
Tracer[IO].span("signature-help").surround(handleSignatureHelp(params))
105+
Tracer[IO].span("signature-help").profilingSurround(handleSignatureHelp(params))
105106

106107
// // TODO: goto type definition with container types
107108
def handleCompletion(params: lsp.CompletionParams) =

sls/src/org/scala/abusers/sls/SimpleLanguageServer.scala

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package org.scala.abusers.sls
22

3-
import cats.effect.*
43
import cats.syntax.all.*
54
import jsonrpclib.fs2.*
65
import jsonrpclib.smithy4sinterop.ClientStub
@@ -9,6 +8,8 @@ import org.scala.abusers.pc.IOCancelTokens
98
import org.scala.abusers.pc.PresentationCompilerProvider
109
import org.typelevel.otel4s.trace.Tracer
1110
import org.typelevel.otel4s.metrics.Meter
11+
import org.scala.abusers.profiling.runtime.ProfilingIOApp
12+
import cats.effect.*
1213

1314
case class BuildServer(
1415
generic: bsp.BuildServer[IO],
@@ -41,7 +42,7 @@ object LSPCancelRequest {
4142
)
4243
}
4344

44-
object SimpleScalaServer extends org.scala.abusers.profiling.runtime.ProfilingIOApp {
45+
object SimpleScalaServer extends ProfilingIOApp {
4546
import jsonrpclib.smithy4sinterop.ServerEndpoints
4647

4748
override def applicationName: String = "simple-language-server"
@@ -70,7 +71,7 @@ object SimpleScalaServer extends org.scala.abusers.profiling.runtime.ProfilingIO
7071
)
7172
.compile
7273
.drain
73-
.background
74+
.toResource
7475
} yield ()
7576

7677
private def server(lspClient: SlsLanguageClient[IO])(using Tracer[IO], Meter[IO]): Resource[IO, ServerImpl] =

0 commit comments

Comments
 (0)