benchmark-location.ts 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
  1. import path from "path"
  2. import { Effect, Logger } from "effect"
  3. import { AppNodeBuilder } from "../src/effect/app-node-builder"
  4. import { LayerNode } from "@opencode-ai/util/effect/layer-node"
  5. import { Database } from "../src/database/database"
  6. import { Bus } from "../src/bus"
  7. import { SdkPlugins } from "../src/plugin/sdk"
  8. import { Location } from "../src/location"
  9. import { LocationServiceMap } from "../src/location-service-map"
  10. import { AbsolutePath } from "../src/schema"
  11. const args = process.argv.slice(2)
  12. const iterationsIndex = args.indexOf("--iterations")
  13. const iterations = iterationsIndex === -1 ? 10 : Number(args[iterationsIndex + 1])
  14. const directory = args.find((arg, index) => !arg.startsWith("--") && index !== iterationsIndex + 1) ?? process.cwd()
  15. if (!Number.isInteger(iterations) || iterations < 1) {
  16. console.error("--iterations must be a positive integer")
  17. process.exit(1)
  18. }
  19. const ref = Location.Ref.make({ directory: AbsolutePath.make(path.resolve(directory)) })
  20. const layer = AppNodeBuilder.build(
  21. LayerNode.group([Database.node, Bus.node, SdkPlugins.node, LocationServiceMap.node]),
  22. )
  23. const measure = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
  24. Effect.gen(function* () {
  25. const start = performance.now()
  26. yield* effect
  27. return performance.now() - start
  28. })
  29. const stats = (samples: ReadonlyArray<number>) => {
  30. const sorted = samples.toSorted((a, b) => a - b)
  31. const percentile = (value: number) => sorted[Math.min(Math.ceil(sorted.length * value) - 1, sorted.length - 1)]
  32. return {
  33. mean: samples.reduce((total, sample) => total + sample, 0) / samples.length,
  34. min: sorted[0] ?? 0,
  35. p50: percentile(0.5),
  36. p95: percentile(0.95),
  37. max: sorted.at(-1) ?? 0,
  38. }
  39. }
  40. const print = (name: string, samples: ReadonlyArray<number>) => {
  41. const result = stats(samples)
  42. console.log(
  43. `${name.padEnd(12)} mean ${result.mean.toFixed(2)} ms min ${result.min.toFixed(2)} ms p50 ${result.p50.toFixed(2)} ms p95 ${result.p95.toFixed(2)} ms max ${result.max.toFixed(2)} ms`,
  44. )
  45. }
  46. const program = Effect.gen(function* () {
  47. const locations = yield* LocationServiceMap.Service
  48. const load = locations.contextEffect(ref).pipe(Effect.scoped)
  49. const first = yield* measure(load)
  50. const cached = yield* Effect.forEach(Array.from({ length: iterations }), () => measure(load))
  51. const cold = yield* Effect.forEach(Array.from({ length: iterations }), () =>
  52. locations.invalidate(ref).pipe(Effect.andThen(measure(load))),
  53. )
  54. console.log(`Location: ${ref.directory}`)
  55. console.log(`Iterations: ${iterations}`)
  56. print("first", [first])
  57. print("cached", cached)
  58. print("cold", cold)
  59. }).pipe(Effect.scoped, Effect.provide(layer), Effect.provide(Logger.layer([])))
  60. await Effect.runPromise(program)