langgraph-api 0.4.47__tar.gz → 0.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of langgraph-api might be problematic. Click here for more details.

Files changed (144) hide show
  1. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/PKG-INFO +5 -5
  2. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/benchmark/Makefile +5 -7
  3. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/benchmark/benchmark-runners/assistant.js +11 -7
  4. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/benchmark/capacity_k6.js +6 -5
  5. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/benchmark/capacity_runner.mjs +114 -109
  6. langgraph_api-0.5.0/benchmark/capacity_urls.mjs +8 -0
  7. langgraph_api-0.5.0/benchmark/clean.js +150 -0
  8. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/benchmark/ramp.js +14 -3
  9. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/benchmark/reporting/dd_reporting.py +9 -8
  10. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/constraints.txt +2 -2
  11. langgraph_api-0.5.0/langgraph_api/__init__.py +1 -0
  12. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/config.py +46 -0
  13. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/grpc_ops/generated/core_api_pb2_grpc.py +2 -2
  14. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/js/package.json +1 -1
  15. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/js/yarn.lock +4 -4
  16. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/serde.py +55 -11
  17. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/pyproject.toml +11 -9
  18. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/uv.lock +91 -91
  19. langgraph_api-0.4.47/benchmark/capacity_urls.mjs +0 -18
  20. langgraph_api-0.4.47/benchmark/clean.js +0 -87
  21. langgraph_api-0.4.47/langgraph_api/__init__.py +0 -1
  22. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/.gitignore +0 -0
  23. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/LICENSE +0 -0
  24. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/Makefile +0 -0
  25. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/README.md +0 -0
  26. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/benchmark/.gitignore +0 -0
  27. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/benchmark/README.md +0 -0
  28. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/benchmark/benchmark-runners/benchmark-runner.js +0 -0
  29. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/benchmark/benchmark-runners/benchmarks.js +0 -0
  30. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/benchmark/benchmark-runners/stream_write.js +0 -0
  31. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/benchmark/benchmark-runners/wait_write.js +0 -0
  32. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/benchmark/burst.js +0 -0
  33. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/benchmark/graphs.js +0 -0
  34. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/benchmark/package.json +0 -0
  35. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/benchmark/update-revision.js +0 -0
  36. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/benchmark/weather.js +0 -0
  37. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/forbidden.txt +0 -0
  38. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/healthcheck.py +0 -0
  39. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/api/__init__.py +0 -0
  40. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/api/a2a.py +0 -0
  41. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/api/assistants.py +0 -0
  42. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/api/mcp.py +0 -0
  43. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/api/meta.py +0 -0
  44. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/api/openapi.py +0 -0
  45. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/api/runs.py +0 -0
  46. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/api/store.py +0 -0
  47. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/api/threads.py +0 -0
  48. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/api/ui.py +0 -0
  49. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/asgi_transport.py +0 -0
  50. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/asyncio.py +0 -0
  51. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/auth/__init__.py +0 -0
  52. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/auth/custom.py +0 -0
  53. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/auth/langsmith/__init__.py +0 -0
  54. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/auth/langsmith/backend.py +0 -0
  55. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/auth/langsmith/client.py +0 -0
  56. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/auth/middleware.py +0 -0
  57. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/auth/noop.py +0 -0
  58. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/auth/studio_user.py +0 -0
  59. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/cli.py +0 -0
  60. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/command.py +0 -0
  61. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/cron_scheduler.py +0 -0
  62. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/errors.py +0 -0
  63. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/executor_entrypoint.py +0 -0
  64. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/feature_flags.py +0 -0
  65. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/graph.py +0 -0
  66. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/grpc_ops/__init__.py +0 -0
  67. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/grpc_ops/client.py +0 -0
  68. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/grpc_ops/generated/__init__.py +0 -0
  69. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/grpc_ops/generated/core_api_pb2.py +0 -0
  70. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/grpc_ops/generated/core_api_pb2.pyi +0 -0
  71. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/grpc_ops/ops.py +0 -0
  72. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/http.py +0 -0
  73. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/http_metrics.py +0 -0
  74. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/http_metrics_utils.py +0 -0
  75. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/js/.gitignore +0 -0
  76. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/js/.prettierrc +0 -0
  77. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/js/__init__.py +0 -0
  78. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/js/base.py +0 -0
  79. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/js/build.mts +0 -0
  80. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/js/client.http.mts +0 -0
  81. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/js/client.mts +0 -0
  82. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/js/errors.py +0 -0
  83. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/js/global.d.ts +0 -0
  84. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/js/remote.py +0 -0
  85. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/js/schema.py +0 -0
  86. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/js/src/graph.mts +0 -0
  87. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/js/src/load.hooks.mjs +0 -0
  88. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/js/src/preload.mjs +0 -0
  89. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/js/src/utils/files.mts +0 -0
  90. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/js/src/utils/importMap.mts +0 -0
  91. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/js/src/utils/pythonSchemas.mts +0 -0
  92. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/js/src/utils/serde.mts +0 -0
  93. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/js/sse.py +0 -0
  94. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/js/traceblock.mts +0 -0
  95. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/js/tsconfig.json +0 -0
  96. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/js/ui.py +0 -0
  97. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/logging.py +0 -0
  98. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/metadata.py +0 -0
  99. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/middleware/__init__.py +0 -0
  100. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/middleware/http_logger.py +0 -0
  101. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/middleware/private_network.py +0 -0
  102. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/middleware/request_id.py +0 -0
  103. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/models/__init__.py +0 -0
  104. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/models/run.py +0 -0
  105. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/patch.py +0 -0
  106. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/queue_entrypoint.py +0 -0
  107. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/route.py +0 -0
  108. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/schema.py +0 -0
  109. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/self_hosted_logs.py +0 -0
  110. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/self_hosted_metrics.py +0 -0
  111. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/server.py +0 -0
  112. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/sse.py +0 -0
  113. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/state.py +0 -0
  114. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/store.py +0 -0
  115. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/stream.py +0 -0
  116. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/thread_ttl.py +0 -0
  117. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/traceblock.py +0 -0
  118. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/tunneling/cloudflare.py +0 -0
  119. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/utils/__init__.py +0 -0
  120. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/utils/cache.py +0 -0
  121. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/utils/config.py +0 -0
  122. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/utils/errors.py +0 -0
  123. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/utils/future.py +0 -0
  124. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/utils/headers.py +0 -0
  125. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/utils/retriable_client.py +0 -0
  126. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/utils/stream_codec.py +0 -0
  127. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/utils/uuids.py +0 -0
  128. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/validation.py +0 -0
  129. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/webhook.py +0 -0
  130. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_api/worker.py +0 -0
  131. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_license/__init__.py +0 -0
  132. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_license/validation.py +0 -0
  133. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_runtime/__init__.py +0 -0
  134. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_runtime/checkpoint.py +0 -0
  135. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_runtime/database.py +0 -0
  136. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_runtime/lifespan.py +0 -0
  137. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_runtime/metrics.py +0 -0
  138. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_runtime/ops.py +0 -0
  139. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_runtime/queue.py +0 -0
  140. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_runtime/retry.py +0 -0
  141. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/langgraph_runtime/store.py +0 -0
  142. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/logging.json +0 -0
  143. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/openapi.json +0 -0
  144. {langgraph_api-0.4.47 → langgraph_api-0.5.0}/scripts/create_license.py +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langgraph-api
3
- Version: 0.4.47
4
- Author-email: Nuno Campos <nuno@langchain.dev>, Will Fu-Hinthorn <will@langchain.dev>
3
+ Version: 0.5.0
4
+ Author-email: Will Fu-Hinthorn <will@langchain.dev>, Josh Rogers <josh@langchain.dev>, Parker Rule <parker@langchain.dev>
5
5
  License: Elastic-2.0
6
6
  License-File: LICENSE
7
7
  Requires-Python: >=3.11
@@ -12,10 +12,10 @@ Requires-Dist: grpcio<2.0.0,>=1.75.0
12
12
  Requires-Dist: httpx>=0.25.0
13
13
  Requires-Dist: jsonschema-rs<0.30,>=0.20.0
14
14
  Requires-Dist: langchain-core>=0.3.64
15
- Requires-Dist: langgraph-checkpoint>=2.0.23
16
- Requires-Dist: langgraph-runtime-inmem<0.15.0,>=0.14.0
15
+ Requires-Dist: langgraph-checkpoint<4,>=3
16
+ Requires-Dist: langgraph-runtime-inmem<0.16.0,>=0.15.0
17
17
  Requires-Dist: langgraph-sdk>=0.2.0
18
- Requires-Dist: langgraph>=0.4.0
18
+ Requires-Dist: langgraph<2,>=0.4.10
19
19
  Requires-Dist: langsmith>=0.3.45
20
20
  Requires-Dist: opentelemetry-api>=1.37.0
21
21
  Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.37.0
@@ -1,16 +1,15 @@
1
1
  # Benchmark commands
2
- BASE_URL ?= https://ifh-core-api-dr-benchmark-048d364b548f5d9790082d8ba4fb44d8.us.langgraph.app
3
- RAMP_START ?= 10
2
+ BASE_URL ?= https://benchmark-dr-s-2799835ad04b501a95044223ae72ced7.staging.langgraph.app
3
+ RAMP_START ?= 40
4
4
  RAMP_END ?= 1000
5
5
  RAMP_MULTIPLIER ?= 2
6
6
  WAIT_SECONDS ?= 60
7
- SUCCESS_THRESHOLD ?= 0.99
8
7
  CLEAR_BETWEEN_STEPS ?= true
9
8
  CLEAR_DELAY_SECONDS ?= 5
10
9
  DATA_SIZE ?= 1000
11
10
  DELAY ?= 0
12
- EXPAND ?= 10
13
- STEPS ?= 10
11
+ EXPAND ?= 1
12
+ STEPS ?= 100
14
13
 
15
14
  benchmark-burst:
16
15
  make benchmark-reset
@@ -21,14 +20,13 @@ benchmark-ramp:
21
20
  k6 run --out json=raw_data_$(shell date +%Y-%m-%dT%H-%M-%S).json --system-tags=[] ramp.js
22
21
 
23
22
  benchmark-capacity:
24
- rm -f capacity_summary_t*.json capacity_report_*.json capacity_raw_t*.json capacity_hist_*.png capacity_pie_*.png
23
+ rm -f capacity_summary_t*.json capacity_raw_t*.json capacity_histogram_*.json
25
24
  npm install
26
25
  BASE_URL=$(BASE_URL) \
27
26
  RAMP_START=$(RAMP_START) \
28
27
  RAMP_END=$(RAMP_END) \
29
28
  RAMP_MULTIPLIER=$(RAMP_MULTIPLIER) \
30
29
  WAIT_SECONDS=$(WAIT_SECONDS) \
31
- SUCCESS_THRESHOLD=$(SUCCESS_THRESHOLD) \
32
30
  CLEAR_BETWEEN_STEPS=$(CLEAR_BETWEEN_STEPS) \
33
31
  CLEAR_DELAY_SECONDS=$(CLEAR_DELAY_SECONDS) \
34
32
  DATA_SIZE=$(DATA_SIZE) \
@@ -9,7 +9,7 @@ export class Assistant extends BenchmarkRunner {
9
9
  */
10
10
  static run(baseUrl, requestParams) {
11
11
  const graph_id = 'benchmark';
12
- let metadata = { description: `Test benchmark assistant ${crypto.randomUUID()}` };
12
+ let metadata = { description: `Test benchmark assistant ${crypto.randomUUID()}`, created_by: 'benchmark' };
13
13
 
14
14
  // Create an assistant
15
15
  const createPayload = JSON.stringify({ graph_id, metadata });
@@ -36,7 +36,8 @@ export class Assistant extends BenchmarkRunner {
36
36
  const countResponse = http.post(`${baseUrl}/assistants/count`, countPayload, requestParams);
37
37
 
38
38
  // Delete the assistant
39
- http.del(`${baseUrl}/assistants/${assistantId}`, requestParams);
39
+ const deleteResponse = http.del(`${baseUrl}/assistants/${assistantId}`, "{}", requestParams);
40
+
40
41
  return {
41
42
  assistantId,
42
43
  searchResponse,
@@ -44,6 +45,7 @@ export class Assistant extends BenchmarkRunner {
44
45
  patchResponse,
45
46
  getResponse2,
46
47
  countResponse,
48
+ deleteResponse,
47
49
  };
48
50
  }
49
51
 
@@ -57,22 +59,24 @@ export class Assistant extends BenchmarkRunner {
57
59
  'Patch response contains the correct assistant': (r) => r.patchResponse.json().assistant_id === result.assistantId,
58
60
  'Get response 2 contains the correct assistant': (r) => r.getResponse2.json().assistant_id === result.assistantId,
59
61
  'Get response 2 contains the new description': (r) => r.getResponse2.json().metadata.description != result.getResponse.json().metadata.description && result.getResponse2.json().metadata.description === result.patchResponse.json().metadata.description,
62
+ 'Get response 2 contains the correct created_by': (r) => r.getResponse2.json().metadata.created_by === 'josh',
60
63
  'Count response contains the correct number of assistants': (r) => parseInt(r.countResponse.json()) === 1,
64
+ 'Delete response is successful': (r) => r.deleteResponse.status === 204,
61
65
  });
62
66
  } catch (error) {
63
67
  console.log(`Unknown error checking response: ${error.message}`);
64
68
  }
65
69
 
66
70
  if (!success) {
67
- if (result.searchResponse.status >= 500 || result.getResponse.status >= 500 || result.patchResponse.status >= 500 || result.getResponse2.status >= 500 || result.countResponse.status >= 500) {
71
+ if (result.searchResponse.status >= 500 || result.getResponse.status >= 500 || result.patchResponse.status >= 500 || result.getResponse2.status >= 500 || result.countResponse.status >= 500 || result.deleteResponse.status >= 500) {
68
72
  errorMetrics.server_errors.add(1);
69
- console.log(`Server error: ${result.searchResponse.status}, ${result.getResponse.status}, ${result.patchResponse.status}, ${result.getResponse2.status}, ${result.countResponse.status}`);
70
- } else if (result.searchResponse.status === 408 || result.getResponse.status === 408 || result.patchResponse.status === 408 || result.getResponse2.status === 408 || result.countResponse.status === 408) {
73
+ console.log(`Server error: ${result.searchResponse.status}, ${result.getResponse.status}, ${result.patchResponse.status}, ${result.getResponse2.status}, ${result.countResponse.status}, ${result.deleteResponse.status}`);
74
+ } else if (result.searchResponse.status === 408 || result.getResponse.status === 408 || result.patchResponse.status === 408 || result.getResponse2.status === 408 || result.countResponse.status === 408 || result.deleteResponse.status === 408) {
71
75
  errorMetrics.timeout_errors.add(1);
72
- console.log(`Timeout error: ${result.searchResponse.error}, ${result.getResponse.error}, ${result.patchResponse.error}, ${result.getResponse2.error}, ${result.countResponse.error}`);
76
+ console.log(`Timeout error: ${result.searchResponse.error}, ${result.getResponse.error}, ${result.patchResponse.error}, ${result.getResponse2.error}, ${result.countResponse.error}, ${result.deleteResponse.error}`);
73
77
  } else {
74
78
  errorMetrics.other_errors.add(1);
75
- console.log(`Other error: ${result.searchResponse.body}, ${result.getResponse.body}, ${result.patchResponse.body}, ${result.getResponse2.body}, ${result.countResponse.body}`);
79
+ console.log(`Other error: ${result.searchResponse.body}, ${result.getResponse.body}, ${result.patchResponse.body}, ${result.getResponse2.body}, ${result.countResponse.body}, ${result.deleteResponse.body}`);
76
80
  }
77
81
  }
78
82
  return success;
@@ -19,7 +19,6 @@ const LANGSMITH_API_KEY = __ENV.LANGSMITH_API_KEY;
19
19
 
20
20
  const TARGET = parseInt(__ENV.TARGET || '10');
21
21
  const WAIT_SECONDS = parseInt(__ENV.WAIT_SECONDS || '60');
22
- const SUCCESS_THRESHOLD = parseFloat(__ENV.SUCCESS_THRESHOLD || '0.99');
23
22
 
24
23
  // Agent params
25
24
  const DATA_SIZE = parseInt(__ENV.DATA_SIZE || '1000');
@@ -37,9 +36,6 @@ export const options = {
37
36
  maxDuration: `${Math.max(WAIT_SECONDS + 120, 150)}s`,
38
37
  },
39
38
  },
40
- thresholds: {
41
- 'capacity_success_rate': [`rate>=${SUCCESS_THRESHOLD}`],
42
- },
43
39
  };
44
40
 
45
41
  function headers() {
@@ -171,7 +167,12 @@ export function handleSummary(data) {
171
167
  const successRate = total > 0 ? (succ / total) * 100 : 0;
172
168
 
173
169
  function withStats(metric) {
174
- if (!data.metrics[metric]?.values) return {};
170
+ if (!data.metrics[metric]?.values) return {
171
+ avg: null,
172
+ p50: null,
173
+ p95: null,
174
+ max: null,
175
+ };
175
176
  const vals = data.metrics[metric].values;
176
177
  return {
177
178
  avg: vals.avg ? vals.avg / 1000 : null,
@@ -32,6 +32,11 @@ function envFloat(name, def) {
32
32
  return Number.isFinite(n) ? n : def;
33
33
  }
34
34
 
35
+ function slugifyTimestamp(ts) {
36
+ const raw = ts ?? new Date().toISOString();
37
+ return raw.replace(/:/g, '-').replace(/\..+/, '');
38
+ }
39
+
35
40
  const BASE_URL = process.env.BASE_URL;
36
41
  const LANGSMITH_API_KEY = process.env.LANGSMITH_API_KEY;
37
42
 
@@ -39,7 +44,6 @@ const RAMP_START = envInt('RAMP_START', 10);
39
44
  const RAMP_END = envInt('RAMP_END', 1000);
40
45
  const RAMP_MULTIPLIER = envFloat('RAMP_MULTIPLIER', 10);
41
46
  const WAIT_SECONDS = envInt('WAIT_SECONDS', 60);
42
- const SUCCESS_THRESHOLD = envFloat('SUCCESS_THRESHOLD', 0.99);
43
47
  const CLEAR_BETWEEN_STEPS = envBool('CLEAR_BETWEEN_STEPS', true);
44
48
  const CLEAR_DELAY_SECONDS = envInt('CLEAR_DELAY_SECONDS', 5);
45
49
 
@@ -107,7 +111,6 @@ function runK6(target) {
107
111
  LANGSMITH_API_KEY,
108
112
  TARGET: String(target),
109
113
  WAIT_SECONDS: String(WAIT_SECONDS),
110
- SUCCESS_THRESHOLD: String(SUCCESS_THRESHOLD),
111
114
  DATA_SIZE: String(DATA_SIZE),
112
115
  DELAY: String(DELAY),
113
116
  EXPAND: String(EXPAND),
@@ -140,89 +143,95 @@ function loadSummaryForTarget(target) {
140
143
 
141
144
  async function main() {
142
145
  let n = RAMP_START;
143
- let lastSuccess = null; // { target, avgDurationSeconds, successRate }
144
- let failedStep = null; // { target, successRate }
145
146
 
146
147
  while (n <= RAMP_END) {
147
- console.log(`\n=== Capacity step: N=${n} ===`);
148
+ const currentTarget = n;
149
+ console.log(`\n=== Capacity step: N=${currentTarget} ===`);
148
150
  if (CLEAR_BETWEEN_STEPS) {
149
151
  console.log('Clearing threads before step...');
150
152
  await cleanThreads();
151
153
  }
152
154
 
155
+ let stageTimestamp = null;
156
+ let runDurationHistogram = null;
157
+ let histogramSampleCount = 0;
158
+
153
159
  try {
154
- const { rawOut, ts } = runK6(n);
160
+ const { rawOut, ts } = runK6(currentTarget);
161
+ stageTimestamp = ts;
155
162
  try {
156
- await generateHistogramsForStage(rawOut, n, ts);
163
+ const histogramResult = await generateHistogramsForStage(rawOut);
164
+ runDurationHistogram = histogramResult?.runDurationHistogramSeconds ?? null;
165
+ histogramSampleCount = histogramResult?.sampleCount ?? 0;
157
166
  } catch (e) {
158
- console.error(`Failed to generate histograms for N=${n}:`, e?.message || e);
167
+ console.error(`Failed to generate histograms for N=${currentTarget}:`, e?.message || e);
159
168
  }
160
169
  } catch (e) {
161
- console.error(`k6 run failed at N=${n}:`, e?.message || e);
170
+ console.error(`k6 run failed at N=${currentTarget}:`, e?.message || e);
162
171
  // Treat as failure for this step
163
172
  }
164
173
 
165
- let successRate = 0;
166
- let avgDurationSeconds = null;
174
+ let summary = null;
167
175
  try {
168
- const summary = loadSummaryForTarget(n);
169
- const s = summary?.metrics?.successRate; // percent
170
- successRate = Number.isFinite(s) ? s / 100 : 0;
171
- avgDurationSeconds = summary?.metrics?.runDuration?.avg ?? null;
172
- console.log(`Step N=${n} successRate=${(successRate * 100).toFixed(2)}% avgDur=${avgDurationSeconds ?? 'n/a'}s`);
176
+ summary = loadSummaryForTarget(currentTarget);
173
177
  } catch (e) {
174
- console.error(`Failed to read summary for N=${n}:`, e?.message || e);
178
+ console.error(`Failed to read summary for N=${currentTarget}:`, e?.message || e);
175
179
  }
176
180
 
177
- if (successRate >= SUCCESS_THRESHOLD) {
178
- lastSuccess = { target: n, avgDurationSeconds, successRate };
179
- // next n
180
- const next = Math.floor(n * RAMP_MULTIPLIER);
181
- if (next <= n) {
182
- console.log('Next ramp value would not increase; stopping.');
183
- break;
184
- }
185
- n = next;
181
+ const metrics = summary?.metrics ?? {};
182
+ const successRatePercent = metrics?.successRate ?? null;
183
+ const avgDurationSeconds = metrics?.runDuration?.avg ?? null;
184
+
185
+ const hasSuccessRate = Number.isFinite(successRatePercent);
186
+ const hasAvgDuration = Number.isFinite(avgDurationSeconds);
187
+ if (hasSuccessRate && hasAvgDuration) {
188
+ console.log(`Step N=${currentTarget} successRate=${successRatePercent.toFixed(2)}% avgDur=${avgDurationSeconds.toFixed(3)}s`);
186
189
  } else {
187
- failedStep = { target: n, avgDurationSeconds, successRate };
190
+ console.log(`Step N=${currentTarget} summary incomplete`);
191
+ }
192
+
193
+ const effectiveTimestamp = summary?.timestamp ?? stageTimestamp ?? slugifyTimestamp();
194
+
195
+ if (Array.isArray(runDurationHistogram) && runDurationHistogram.length > 0) {
196
+ const histogramLog = {
197
+ timestamp: effectiveTimestamp,
198
+ baseUrl: BASE_URL,
199
+ baseUrlName: baseUrlToBaseUrlName[BASE_URL],
200
+ target: currentTarget,
201
+ waitSeconds: WAIT_SECONDS,
202
+ dataSize: DATA_SIZE,
203
+ delay: DELAY,
204
+ expand: EXPAND,
205
+ steps: STEPS,
206
+ metric: 'run_duration_seconds',
207
+ histogram: {
208
+ unit: 'seconds',
209
+ buckets: runDurationHistogram,
210
+ sampleCount: histogramSampleCount,
211
+ },
212
+ };
213
+ const histogramFile = `capacity_histogram_t${currentTarget}_${slugifyTimestamp(effectiveTimestamp)}.json`;
214
+ writeFileSync(join(process.cwd(), histogramFile), JSON.stringify(histogramLog, null, 2));
215
+ console.log(JSON.stringify(histogramLog));
216
+ }
217
+
218
+ if (currentTarget >= RAMP_END) {
219
+ console.log('Reached final ramp target; stopping.');
188
220
  break;
189
221
  }
190
- }
191
222
 
192
- const ts = new Date().toISOString().replace(/:/g, '-').replace(/\..+/, '');
193
- const report = {
194
- baseUrl: BASE_URL,
195
- baseUrlName: baseUrlToBaseUrlName[BASE_URL],
196
- timestamp: ts,
197
- ramp: { start: RAMP_START, end: RAMP_END, multiplier: RAMP_MULTIPLIER },
198
- waitSeconds: WAIT_SECONDS,
199
- threshold: SUCCESS_THRESHOLD,
200
- last_success_target: lastSuccess?.target ?? 0,
201
- last_success_avg_duration_seconds: lastSuccess?.avgDurationSeconds ?? null,
202
- last_success_rate: lastSuccess?.successRate ?? null,
203
- failed_target: failedStep?.target ?? null,
204
- failed_success_rate: failedStep?.successRate ?? null,
205
- failed_avg_duration_seconds: failedStep?.avgDurationSeconds ?? null,
206
- };
207
- const fname = `capacity_report_${ts}.json`;
208
- writeFileSync(join(process.cwd(), fname), JSON.stringify(report, null, 2));
223
+ const next = Math.floor(currentTarget * RAMP_MULTIPLIER);
224
+ if (next > RAMP_END) {
225
+ n = RAMP_END;
226
+ } else {
227
+ n = next;
228
+ }
209
229
 
210
- // Export selected fields as GitHub Action outputs if available
211
- if (process.env.GITHUB_OUTPUT) {
212
- const out = [
213
- `last_success_target=${report.last_success_target}`,
214
- `last_success_avg_duration_seconds=${report.last_success_avg_duration_seconds}`,
215
- `failed_target=${report.failed_target}`,
216
- `failed_success_rate=${report.failed_success_rate}`,
217
- `failed_avg_duration_seconds=${report.failed_avg_duration_seconds}`,
218
- ].join('\n');
219
- writeFileSync(process.env.GITHUB_OUTPUT, `${out}\n`, { flag: 'a' });
230
+ if (n <= currentTarget) {
231
+ console.log('Next ramp value would not increase; stopping.');
232
+ break;
233
+ }
220
234
  }
221
-
222
- console.log('=== Capacity Benchmark Report ===');
223
- console.log(`Last successful step: ${report.last_success_target}`);
224
- console.log(`Average duration (s) at success: ${report.last_success_avg_duration_seconds}`);
225
- console.log(`Failed step: ${report.failed_target} with success rate: ${report.failed_success_rate}`);
226
235
  }
227
236
 
228
237
  main().catch((e) => {
@@ -230,8 +239,47 @@ main().catch((e) => {
230
239
  process.exit(1);
231
240
  });
232
241
 
242
+ function buildHistogramBuckets(valuesSeconds, bucketCount = 12) {
243
+ if (!Array.isArray(valuesSeconds) || valuesSeconds.length === 0) return [];
244
+ const min = Math.min(...valuesSeconds);
245
+ const max = Math.max(...valuesSeconds);
246
+ if (!Number.isFinite(min) || !Number.isFinite(max)) return [];
247
+ if (min === max) {
248
+ return [{
249
+ start: Number(min.toFixed(6)),
250
+ end: Number(max.toFixed(6)),
251
+ count: valuesSeconds.length,
252
+ }];
253
+ }
254
+ const width = (max - min) / bucketCount;
255
+ if (width === 0) {
256
+ return [{
257
+ start: Number(min.toFixed(6)),
258
+ end: Number(max.toFixed(6)),
259
+ count: valuesSeconds.length,
260
+ }];
261
+ }
262
+ const buckets = Array.from({ length: bucketCount }, (_, i) => ({
263
+ start: min + i * width,
264
+ end: i === bucketCount - 1 ? max : min + (i + 1) * width,
265
+ count: 0,
266
+ }));
267
+ for (const v of valuesSeconds) {
268
+ if (!Number.isFinite(v)) continue;
269
+ let idx = Math.floor((v - min) / width);
270
+ if (idx < 0) idx = 0;
271
+ if (idx >= bucketCount) idx = bucketCount - 1;
272
+ buckets[idx].count += 1;
273
+ }
274
+ return buckets.map((b, i) => ({
275
+ start: Number(b.start.toFixed(6)),
276
+ end: Number(b.end.toFixed(6)),
277
+ count: b.count,
278
+ }));
279
+ }
280
+
233
281
  // Build and save histogram charts for one stage from raw K6 JSON
234
- async function generateHistogramsForStage(rawFile, target, ts) {
282
+ async function generateHistogramsForStage(rawFile) {
235
283
  // Parse streaming JSONL from k6 --out json
236
284
  const metrics = {
237
285
  run_duration: [],
@@ -261,52 +309,9 @@ async function generateHistogramsForStage(rawFile, target, ts) {
261
309
  rl.on('error', reject);
262
310
  });
263
311
 
264
- // Build pie chart for component breakdown based on average seconds
265
- const avg = (arr) => (arr.length ? arr.reduce((a, b) => a + b, 0) / arr.length : 0);
266
- const avgInsertionS = avg(metrics.run_insertion_duration) / 1000;
267
- const avgPickupS = avg(metrics.run_pickup_duration) / 1000;
268
- const avgOssS = avg(metrics.run_oss_duration) / 1000;
269
- const avgReturnS = avg(metrics.run_return_duration) / 1000;
270
- const parts = [avgInsertionS, avgPickupS, avgOssS, avgReturnS];
271
- const totalParts = parts.reduce((a, b) => a + b, 0);
272
- if (totalParts > 0) {
273
- const chart = new QuickChart();
274
- chart.setWidth(700);
275
- chart.setHeight(500);
276
- chart.setFormat('png');
277
- chart.setConfig({
278
- type: 'pie',
279
- data: {
280
- labels: ['Insertion', 'Pickup', 'OSS', 'Return'],
281
- datasets: [{
282
- label: `Breakdown of Run Duration (N=${target})`,
283
- data: parts.map((v) => Number(v.toFixed(4))),
284
- backgroundColor: [
285
- 'rgba(255, 99, 132, 0.6)',
286
- 'rgba(54, 162, 235, 0.6)',
287
- 'rgba(255, 206, 86, 0.6)',
288
- 'rgba(75, 192, 192, 0.6)',
289
- ],
290
- borderColor: [
291
- 'rgba(255, 99, 132, 1)',
292
- 'rgba(54, 162, 235, 1)',
293
- 'rgba(255, 206, 86, 1)',
294
- 'rgba(75, 192, 192, 1)',
295
- ],
296
- borderWidth: 1,
297
- }],
298
- },
299
- options: {
300
- plugins: {
301
- title: { display: true, text: `Run Duration Breakdown (s) — N=${target}` },
302
- legend: { position: 'right' },
303
- tooltip: { callbacks: { label: (ctx) => `${ctx.label}: ${ctx.parsed.toFixed(3)}s` } },
304
- },
305
- },
306
- });
307
- const pieBuf = await chart.toBinary();
308
- const piePath = join(process.cwd(), `capacity_pie_breakdown_t${target}_${ts}.png`);
309
- writeFileSync(piePath, pieBuf);
310
- console.log(`Saved pie chart: ${piePath}`);
311
- }
312
+ const runDurationSeconds = metrics.run_duration.map((v) => v / 1000);
313
+ return {
314
+ runDurationHistogramSeconds: buildHistogramBuckets(runDurationSeconds),
315
+ sampleCount: runDurationSeconds.length,
316
+ };
312
317
  }
@@ -0,0 +1,8 @@
1
+ export const baseUrlToBaseUrlName = {
2
+ 'https://benchmark-s-6d17010be8a25023a9eab0a688d29c05.staging.langgraph.app': 's',
3
+ 'https://benchmark-m-fd2e770a72f55324b6c59f2664a56d32.staging.langgraph.app': 'm',
4
+ 'https://benchmark-l-d655833b3cba5fc8a703c95f20045831.staging.langgraph.app': 'l',
5
+ 'https://benchmark-dr-s-2799835ad04b501a95044223ae72ced7.staging.langgraph.app': 'dr-s',
6
+ 'https://benchmark-dr-m-ec079ea9f20e5655ab35a4ebc1a0ade8.staging.langgraph.app': 'dr-m',
7
+ 'https://benchmark-dr-l-e996bbdcfbf15c9e8f547ab74fae93d2.staging.langgraph.app': 'dr-l'
8
+ };
@@ -0,0 +1,150 @@
1
+ /*
2
+ * Delete all threads and runs from the last benchmark run for consistent tests
3
+ * The default benchmark server has a thread TTL of one hour that should clean things up too so this doesn't run too long.
4
+ */
5
+
6
+ // URL of your LangGraph server
7
+ const BASE_URL = process.env.BASE_URL || 'http://localhost:9123';
8
+ // LangSmith API key only needed with a custom server endpoint
9
+ const LANGSMITH_API_KEY = process.env.LANGSMITH_API_KEY;
10
+
11
+ async function clean() {
12
+ try {
13
+ await cleanAssistants();
14
+ await cleanThreads();
15
+ } catch (error) {
16
+ console.error('Fatal error during cleanup:', error.message);
17
+ process.exit(1);
18
+ }
19
+ }
20
+
21
+ async function cleanAssistants() {
22
+ const headers = { 'Content-Type': 'application/json' };
23
+ if (LANGSMITH_API_KEY) {
24
+ headers['x-api-key'] = LANGSMITH_API_KEY;
25
+ }
26
+
27
+ const searchUrl = `${BASE_URL}/assistants/search`;
28
+ let totalDeleted = 0;
29
+
30
+ console.log('Starting assistant cleanup...');
31
+
32
+ while (true) {
33
+ // Get the next page of assistants
34
+ console.log('Searching for assistants...');
35
+ const searchResponse = await fetch(searchUrl, {
36
+ method: 'POST',
37
+ headers,
38
+ body: JSON.stringify({
39
+ limit: 1000,
40
+ metadata: {
41
+ created_by: 'benchmark' // NOTE: Super important to not clean up the assistants created by the system
42
+ }
43
+ })
44
+ });
45
+
46
+ if (!searchResponse.ok) {
47
+ throw new Error(`Search request failed: ${searchResponse.status} ${searchResponse.statusText}`);
48
+ }
49
+
50
+ const assistants = await searchResponse.json();
51
+
52
+ // If no assistants found, we're done
53
+ if (!assistants || assistants.length === 0) {
54
+ console.log('No more assistants found.');
55
+ break;
56
+ }
57
+
58
+ console.log(`Found ${assistants.length} assistants to delete`);
59
+
60
+ // Delete each assistant
61
+ for (const assistant of assistants) {
62
+ try {
63
+ const deleteUrl = `${BASE_URL}/assistants/${assistant.assistant_id}`;
64
+ const deleteResponse = await fetch(deleteUrl, {
65
+ method: 'DELETE',
66
+ headers
67
+ });
68
+
69
+ if (!deleteResponse.ok) {
70
+ console.error(`Failed to delete assistant ${assistant.assistant_id}: ${deleteResponse.status} ${deleteResponse.statusText}`);
71
+ } else {
72
+ totalDeleted++;
73
+ }
74
+ } catch (deleteError) {
75
+ console.error(`Error deleting assistant ${assistant.assistant_id}:`, deleteError.message);
76
+ }
77
+ }
78
+
79
+ console.log(`Deleted ${assistants.length} assistants in this batch`);
80
+ }
81
+
82
+ console.log(`Assistant cleanup completed. Total assistants deleted: ${totalDeleted}`);
83
+ }
84
+
85
+
86
+ async function cleanThreads() {
87
+ const headers = { 'Content-Type': 'application/json' };
88
+ if (LANGSMITH_API_KEY) {
89
+ headers['x-api-key'] = LANGSMITH_API_KEY;
90
+ }
91
+
92
+ const searchUrl = `${BASE_URL}/threads/search`;
93
+ let totalDeleted = 0;
94
+
95
+ console.log('Starting thread cleanup...');
96
+
97
+ while (true) {
98
+ // Get the next page of threads
99
+ console.log('Searching for threads...');
100
+ const searchResponse = await fetch(searchUrl, {
101
+ method: 'POST',
102
+ headers,
103
+ body: JSON.stringify({
104
+ limit: 1000
105
+ })
106
+ });
107
+
108
+ if (!searchResponse.ok) {
109
+ throw new Error(`Search request failed: ${searchResponse.status} ${searchResponse.statusText}`);
110
+ }
111
+
112
+ const threads = await searchResponse.json();
113
+
114
+ // If no threads found, we're done
115
+ if (!threads || threads.length === 0) {
116
+ console.log('No more threads found.');
117
+ break;
118
+ }
119
+
120
+ console.log(`Found ${threads.length} threads to delete`);
121
+
122
+ // Delete each thread
123
+ for (const thread of threads) {
124
+ try {
125
+ const deleteUrl = `${BASE_URL}/threads/${thread.thread_id}`;
126
+ const deleteResponse = await fetch(deleteUrl, {
127
+ method: 'DELETE',
128
+ headers
129
+ });
130
+
131
+ if (!deleteResponse.ok) {
132
+ console.error(`Failed to delete thread ${thread.thread_id}: ${deleteResponse.status} ${deleteResponse.statusText}`);
133
+ } else {
134
+ totalDeleted++;
135
+ }
136
+ } catch (deleteError) {
137
+ console.error(`Error deleting thread ${thread.thread_id}:`, deleteError.message);
138
+ }
139
+ }
140
+
141
+ console.log(`Deleted ${threads.length} threads in this batch`);
142
+ }
143
+
144
+ console.log(`Thread cleanup completed. Total threads deleted: ${totalDeleted}`);
145
+ }
146
+
147
+ clean().catch(error => {
148
+ console.error('Unhandled error:', error.message);
149
+ process.exit(1);
150
+ });
@@ -31,7 +31,8 @@ const LOAD_SIZE = parseInt(__ENV.LOAD_SIZE || '500');
31
31
  const LEVELS = parseInt(__ENV.LEVELS || '2');
32
32
  const PLATEAU_DURATION = parseInt(__ENV.PLATEAU_DURATION || '300');
33
33
  const BENCHMARK_TYPE = __ENV.BENCHMARK_TYPE || 'wait_write';
34
- const STATEFUL = __ENV.STATEFUL === 'true';
34
+ const STATEFUL = __ENV.STATEFUL === 'true'; // Should the runner be stateful if possible?
35
+ const P95_RUN_DURATION = __ENV.P95_RUN_DURATION; // Expected P95 run duration in milliseconds
35
36
 
36
37
  // Params for the agent
37
38
  const DATA_SIZE = parseInt(__ENV.DATA_SIZE || '1000');
@@ -53,6 +54,15 @@ const p95_run_duration = {
53
54
  'single': 1500,
54
55
  }
55
56
 
57
+ function getP95RunDuration(mode) {
58
+ return P95_RUN_DURATION ? parseInt(P95_RUN_DURATION) : p95_run_duration[mode];
59
+ }
60
+
61
+ function getSuccessfulRunsThreshold(mode) {
62
+ // Number of expected successful runs per user worst case during plateau * max number of users * 2 cause that feels about right
63
+ return (PLATEAU_DURATION / (getP95RunDuration(mode) / 1000)) * LOAD_SIZE * LEVELS * 2;
64
+ }
65
+
56
66
  // Test configuration
57
67
  export let options = {
58
68
  scenarios: {
@@ -64,8 +74,8 @@ export let options = {
64
74
  },
65
75
  },
66
76
  thresholds: {
67
- 'run_duration': [`p(95)<${p95_run_duration[MODE]}`],
68
- 'successful_runs': [`count>${(PLATEAU_DURATION / (p95_run_duration[MODE] / 1000)) * LOAD_SIZE * LEVELS * 2}`], // Number of expected successful runs per user worst case during plateau * max number of users * 2 cause that feels about right
77
+ 'run_duration': [`p(95)<${getP95RunDuration(MODE)}`],
78
+ 'successful_runs': [`count>${getSuccessfulRunsThreshold(MODE)}`],
69
79
  'http_req_failed': ['rate<0.01'], // Error rate should be less than 1%
70
80
  },
71
81
  };
@@ -129,6 +139,7 @@ export function setup() {
129
139
  console.log(`Running on pod: ${__ENV.POD_NAME || 'local'}`);
130
140
  console.log(`Running with the following ramp config: load size ${LOAD_SIZE}, levels ${LEVELS}, plateau duration ${PLATEAU_DURATION}, stateful ${STATEFUL}`);
131
141
  console.log(`Running with the following agent config: data size ${DATA_SIZE}, delay ${DELAY}, expand ${EXPAND}, mode ${MODE}`);
142
+ console.log(`Running with the following thresholds: p95 run duration ${getP95RunDuration(MODE)}ms, successful runs threshold ${getSuccessfulRunsThreshold(MODE)}, error rate < 1%`);
132
143
 
133
144
  return { startTime: new Date().toISOString().replace(/:/g, '-').replace(/\..+/, '') };
134
145
  }
@@ -34,7 +34,7 @@ def send_benchmark_results(
34
34
  benchmark_data["labels"] = common_labels
35
35
 
36
36
  log_item = HTTPLogItem(
37
- ddsource="benchmark-3",
37
+ ddsource="benchmark-6",
38
38
  ddtags="env:benchmarking" if common_labels else "env:benchmarking",
39
39
  hostname=os.getenv("HOSTNAME", "localhost"),
40
40
  message=json.dumps(benchmark_data),
@@ -77,11 +77,12 @@ if __name__ == "__main__":
77
77
 
78
78
  DD_SITE = os.getenv("DD_SITE", "us5.datadoghq.com")
79
79
 
80
- common_labels = {"base_url": os.getenv("BASE_URL")}
81
- if "report" in args.benchmark_file:
82
- common_labels["type"] = "summary"
83
- else:
84
- common_labels["type"] = "individual_run"
85
-
86
80
  for file in glob.glob(args.benchmark_file):
87
- process_benchmark_file(file, common_labels, DD_SITE, DD_API_KEY)
81
+ labels = {"base_url": os.getenv("BASE_URL")}
82
+ basename = os.path.basename(file)
83
+ lowered = basename.lower()
84
+ if "histogram" in lowered:
85
+ labels["type"] = "histogram"
86
+ else:
87
+ labels["type"] = "individual_run"
88
+ process_benchmark_file(file, labels, DD_SITE, DD_API_KEY)