arize-phoenix 3.25.0__py3-none-any.whl → 4.0.1__py3-none-any.whl

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 arize-phoenix might be problematic. Click here for more details.

Files changed (113) hide show
  1. {arize_phoenix-3.25.0.dist-info → arize_phoenix-4.0.1.dist-info}/METADATA +26 -4
  2. {arize_phoenix-3.25.0.dist-info → arize_phoenix-4.0.1.dist-info}/RECORD +80 -75
  3. phoenix/__init__.py +9 -5
  4. phoenix/config.py +109 -53
  5. phoenix/datetime_utils.py +18 -1
  6. phoenix/db/README.md +25 -0
  7. phoenix/db/__init__.py +4 -0
  8. phoenix/db/alembic.ini +119 -0
  9. phoenix/db/bulk_inserter.py +206 -0
  10. phoenix/db/engines.py +152 -0
  11. phoenix/db/helpers.py +47 -0
  12. phoenix/db/insertion/evaluation.py +209 -0
  13. phoenix/db/insertion/helpers.py +51 -0
  14. phoenix/db/insertion/span.py +142 -0
  15. phoenix/db/migrate.py +71 -0
  16. phoenix/db/migrations/env.py +121 -0
  17. phoenix/db/migrations/script.py.mako +26 -0
  18. phoenix/db/migrations/versions/cf03bd6bae1d_init.py +280 -0
  19. phoenix/db/models.py +371 -0
  20. phoenix/exceptions.py +5 -1
  21. phoenix/server/api/context.py +40 -3
  22. phoenix/server/api/dataloaders/__init__.py +97 -0
  23. phoenix/server/api/dataloaders/cache/__init__.py +3 -0
  24. phoenix/server/api/dataloaders/cache/two_tier_cache.py +67 -0
  25. phoenix/server/api/dataloaders/document_evaluation_summaries.py +152 -0
  26. phoenix/server/api/dataloaders/document_evaluations.py +37 -0
  27. phoenix/server/api/dataloaders/document_retrieval_metrics.py +98 -0
  28. phoenix/server/api/dataloaders/evaluation_summaries.py +151 -0
  29. phoenix/server/api/dataloaders/latency_ms_quantile.py +198 -0
  30. phoenix/server/api/dataloaders/min_start_or_max_end_times.py +93 -0
  31. phoenix/server/api/dataloaders/record_counts.py +125 -0
  32. phoenix/server/api/dataloaders/span_descendants.py +64 -0
  33. phoenix/server/api/dataloaders/span_evaluations.py +37 -0
  34. phoenix/server/api/dataloaders/token_counts.py +138 -0
  35. phoenix/server/api/dataloaders/trace_evaluations.py +37 -0
  36. phoenix/server/api/input_types/SpanSort.py +138 -68
  37. phoenix/server/api/routers/v1/__init__.py +11 -0
  38. phoenix/server/api/routers/v1/evaluations.py +275 -0
  39. phoenix/server/api/routers/v1/spans.py +126 -0
  40. phoenix/server/api/routers/v1/traces.py +82 -0
  41. phoenix/server/api/schema.py +112 -48
  42. phoenix/server/api/types/DocumentEvaluationSummary.py +1 -1
  43. phoenix/server/api/types/Evaluation.py +29 -12
  44. phoenix/server/api/types/EvaluationSummary.py +29 -44
  45. phoenix/server/api/types/MimeType.py +2 -2
  46. phoenix/server/api/types/Model.py +9 -9
  47. phoenix/server/api/types/Project.py +240 -171
  48. phoenix/server/api/types/Span.py +87 -131
  49. phoenix/server/api/types/Trace.py +29 -20
  50. phoenix/server/api/types/pagination.py +151 -10
  51. phoenix/server/app.py +263 -35
  52. phoenix/server/grpc_server.py +93 -0
  53. phoenix/server/main.py +75 -60
  54. phoenix/server/openapi/docs.py +218 -0
  55. phoenix/server/prometheus.py +23 -7
  56. phoenix/server/static/index.js +662 -643
  57. phoenix/server/telemetry.py +68 -0
  58. phoenix/services.py +4 -0
  59. phoenix/session/client.py +34 -30
  60. phoenix/session/data_extractor.py +8 -3
  61. phoenix/session/session.py +176 -155
  62. phoenix/settings.py +13 -0
  63. phoenix/trace/attributes.py +349 -0
  64. phoenix/trace/dsl/README.md +116 -0
  65. phoenix/trace/dsl/filter.py +660 -192
  66. phoenix/trace/dsl/helpers.py +24 -5
  67. phoenix/trace/dsl/query.py +562 -185
  68. phoenix/trace/fixtures.py +69 -7
  69. phoenix/trace/otel.py +44 -200
  70. phoenix/trace/schemas.py +14 -8
  71. phoenix/trace/span_evaluations.py +5 -2
  72. phoenix/utilities/__init__.py +0 -26
  73. phoenix/utilities/span_store.py +0 -23
  74. phoenix/version.py +1 -1
  75. phoenix/core/project.py +0 -773
  76. phoenix/core/traces.py +0 -96
  77. phoenix/datasets/dataset.py +0 -214
  78. phoenix/datasets/fixtures.py +0 -24
  79. phoenix/datasets/schema.py +0 -31
  80. phoenix/experimental/evals/__init__.py +0 -73
  81. phoenix/experimental/evals/evaluators.py +0 -413
  82. phoenix/experimental/evals/functions/__init__.py +0 -4
  83. phoenix/experimental/evals/functions/classify.py +0 -453
  84. phoenix/experimental/evals/functions/executor.py +0 -353
  85. phoenix/experimental/evals/functions/generate.py +0 -138
  86. phoenix/experimental/evals/functions/processing.py +0 -76
  87. phoenix/experimental/evals/models/__init__.py +0 -14
  88. phoenix/experimental/evals/models/anthropic.py +0 -175
  89. phoenix/experimental/evals/models/base.py +0 -170
  90. phoenix/experimental/evals/models/bedrock.py +0 -221
  91. phoenix/experimental/evals/models/litellm.py +0 -134
  92. phoenix/experimental/evals/models/openai.py +0 -453
  93. phoenix/experimental/evals/models/rate_limiters.py +0 -246
  94. phoenix/experimental/evals/models/vertex.py +0 -173
  95. phoenix/experimental/evals/models/vertexai.py +0 -186
  96. phoenix/experimental/evals/retrievals.py +0 -96
  97. phoenix/experimental/evals/templates/__init__.py +0 -50
  98. phoenix/experimental/evals/templates/default_templates.py +0 -472
  99. phoenix/experimental/evals/templates/template.py +0 -195
  100. phoenix/experimental/evals/utils/__init__.py +0 -172
  101. phoenix/experimental/evals/utils/threads.py +0 -27
  102. phoenix/server/api/routers/evaluation_handler.py +0 -110
  103. phoenix/server/api/routers/span_handler.py +0 -70
  104. phoenix/server/api/routers/trace_handler.py +0 -60
  105. phoenix/storage/span_store/__init__.py +0 -23
  106. phoenix/storage/span_store/text_file.py +0 -85
  107. phoenix/trace/dsl/missing.py +0 -60
  108. {arize_phoenix-3.25.0.dist-info → arize_phoenix-4.0.1.dist-info}/WHEEL +0 -0
  109. {arize_phoenix-3.25.0.dist-info → arize_phoenix-4.0.1.dist-info}/licenses/IP_NOTICE +0 -0
  110. {arize_phoenix-3.25.0.dist-info → arize_phoenix-4.0.1.dist-info}/licenses/LICENSE +0 -0
  111. /phoenix/{datasets → db/insertion}/__init__.py +0 -0
  112. /phoenix/{experimental → db/migrations}/__init__.py +0 -0
  113. /phoenix/{storage → server/openapi}/__init__.py +0 -0
phoenix/server/main.py CHANGED
@@ -1,24 +1,29 @@
1
1
  import atexit
2
2
  import logging
3
3
  import os
4
+ import warnings
4
5
  from argparse import ArgumentParser
5
6
  from pathlib import Path
6
- from random import random
7
7
  from threading import Thread
8
8
  from time import sleep, time
9
- from typing import Iterable, Optional, Protocol, TypeVar
9
+ from typing import List, Optional
10
10
 
11
11
  import pkg_resources
12
12
  from uvicorn import Config, Server
13
13
 
14
+ import phoenix.trace.v1 as pb
14
15
  from phoenix.config import (
15
16
  EXPORT_DIR,
17
+ get_env_database_connection_str,
18
+ get_env_enable_prometheus,
19
+ get_env_grpc_port,
16
20
  get_env_host,
17
21
  get_env_port,
18
22
  get_pids_path,
23
+ get_working_dir,
19
24
  )
20
25
  from phoenix.core.model_schema_adapter import create_model_from_datasets
21
- from phoenix.core.traces import Traces
26
+ from phoenix.db import get_printable_db_url
22
27
  from phoenix.inferences.fixtures import FIXTURES, get_datasets
23
28
  from phoenix.inferences.inferences import EMPTY_INFERENCES, Inferences
24
29
  from phoenix.pointcloud.umap_parameters import (
@@ -28,16 +33,17 @@ from phoenix.pointcloud.umap_parameters import (
28
33
  UMAPParameters,
29
34
  )
30
35
  from phoenix.server.app import create_app
31
- from phoenix.storage.span_store import SpanStore
36
+ from phoenix.settings import Settings
32
37
  from phoenix.trace.fixtures import (
33
38
  TRACES_FIXTURES,
34
- _download_traces_fixture,
35
- _get_trace_fixture_by_name,
39
+ download_traces_fixture,
36
40
  get_evals_from_fixture,
41
+ get_trace_fixture_by_name,
42
+ reset_fixture_span_ids_and_timestamps,
37
43
  )
38
44
  from phoenix.trace.otel import decode_otlp_span, encode_span_to_otlp
45
+ from phoenix.trace.schemas import Span
39
46
  from phoenix.trace.span_json_decoder import json_string_to_span
40
- from phoenix.utilities.span_store import get_span_store, load_traces_data_from_store
41
47
 
42
48
  logger = logging.getLogger(__name__)
43
49
 
@@ -48,7 +54,7 @@ _WELCOME_MESSAGE = """
48
54
  ██████╔╝███████║██║ ██║█████╗ ██╔██╗ ██║██║ ╚███╔╝
49
55
  ██╔═══╝ ██╔══██║██║ ██║██╔══╝ ██║╚██╗██║██║ ██╔██╗
50
56
  ██║ ██║ ██║╚██████╔╝███████╗██║ ╚████║██║██╔╝ ██╗
51
- ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═══╝╚═╝╚═╝ ╚═╝ v{0}
57
+ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═══╝╚═╝╚═╝ ╚═╝ v{version}
52
58
 
53
59
  |
54
60
  | 🌎 Join our Community 🌎
@@ -61,9 +67,11 @@ _WELCOME_MESSAGE = """
61
67
  | https://docs.arize.com/phoenix
62
68
  |
63
69
  | 🚀 Phoenix Server 🚀
64
- | Phoenix UI: http://{1}:{2}
65
- | Log traces: /v1/traces over HTTP
66
- |
70
+ | Phoenix UI: http://{host}:{port}
71
+ | Log traces:
72
+ | - gRPC: http://{host}:{grpc_port}
73
+ | - HTTP: http://{host}:{port}/v1/traces
74
+ | Storage: {storage}
67
75
  """
68
76
 
69
77
 
@@ -88,24 +96,6 @@ def _get_pid_file() -> Path:
88
96
  return get_pids_path() / str(os.getpid())
89
97
 
90
98
 
91
- _Item = TypeVar("_Item", contravariant=True)
92
-
93
-
94
- class _SupportsPut(Protocol[_Item]):
95
- def put(self, item: _Item) -> None: ...
96
-
97
-
98
- def _load_items(
99
- queue: _SupportsPut[_Item],
100
- items: Iterable[_Item],
101
- simulate_streaming: Optional[bool] = False,
102
- ) -> None:
103
- for item in items:
104
- if simulate_streaming:
105
- sleep(random())
106
- queue.put(item)
107
-
108
-
109
99
  DEFAULT_UMAP_PARAMS_STR = f"{DEFAULT_MIN_DIST},{DEFAULT_N_NEIGHBORS},{DEFAULT_N_SAMPLES}"
110
100
 
111
101
  if __name__ == "__main__":
@@ -118,10 +108,14 @@ if __name__ == "__main__":
118
108
  reference_dataset: Optional[Inferences] = None
119
109
  corpus_dataset: Optional[Inferences] = None
120
110
 
111
+ # Initialize the settings for the Server
112
+ Settings.log_migrations = True
113
+
121
114
  # automatically remove the pid file when the process is being gracefully terminated
122
115
  atexit.register(_remove_pid_file)
123
116
 
124
117
  parser = ArgumentParser()
118
+ parser.add_argument("--database-url", required=False)
125
119
  parser.add_argument("--export_path")
126
120
  parser.add_argument("--host", type=str, required=False)
127
121
  parser.add_argument("--port", type=int, required=False)
@@ -152,8 +146,10 @@ if __name__ == "__main__":
152
146
  )
153
147
  demo_parser.add_argument("--simulate-streaming", action="store_true")
154
148
  args = parser.parse_args()
149
+ db_connection_str = (
150
+ args.database_url if args.database_url else get_env_database_connection_str()
151
+ )
155
152
  export_path = Path(args.export_path) if args.export_path else EXPORT_DIR
156
- span_store: Optional[SpanStore] = None
157
153
  if args.command == "datasets":
158
154
  primary_dataset_name = args.primary
159
155
  reference_dataset_name = args.reference
@@ -189,33 +185,38 @@ if __name__ == "__main__":
189
185
  trace_dataset_name = args.trace_fixture
190
186
  simulate_streaming = args.simulate_streaming
191
187
 
188
+ host: Optional[str] = args.host or get_env_host()
189
+ display_host = host or "localhost"
190
+ # If the host is "::", the convention is to bind to all interfaces. However, uvicorn
191
+ # does not support this directly unless the host is set to None.
192
+ if host and ":" in host:
193
+ # format IPv6 hosts in brackets
194
+ display_host = f"[{host}]"
195
+ if host == "::":
196
+ # TODO(dustin): why is this necessary? it's not type compliant
197
+ host = None
198
+
199
+ port = args.port or get_env_port()
200
+
192
201
  model = create_model_from_datasets(
193
202
  primary_dataset,
194
203
  reference_dataset,
195
204
  )
196
- traces = Traces()
197
- if span_store := get_span_store():
198
- Thread(target=load_traces_data_from_store, args=(traces, span_store), daemon=True).start()
205
+
206
+ fixture_spans: List[Span] = []
207
+ fixture_evals: List[pb.Evaluation] = []
199
208
  if trace_dataset_name is not None:
200
- fixture_spans = list(
201
- # Apply `encode` here because legacy jsonl files contains UUIDs as strings.
202
- # `encode` removes the hyphens in the UUIDs.
203
- decode_otlp_span(encode_span_to_otlp(json_string_to_span(json_span)))
204
- for json_span in _download_traces_fixture(
205
- _get_trace_fixture_by_name(trace_dataset_name)
206
- )
209
+ fixture_spans, fixture_evals = reset_fixture_span_ids_and_timestamps(
210
+ (
211
+ # Apply `encode` here because legacy jsonl files contains UUIDs as strings.
212
+ # `encode` removes the hyphens in the UUIDs.
213
+ decode_otlp_span(encode_span_to_otlp(json_string_to_span(json_span)))
214
+ for json_span in download_traces_fixture(
215
+ get_trace_fixture_by_name(trace_dataset_name)
216
+ )
217
+ ),
218
+ get_evals_from_fixture(trace_dataset_name),
207
219
  )
208
- Thread(
209
- target=_load_items,
210
- args=(traces, fixture_spans, simulate_streaming),
211
- daemon=True,
212
- ).start()
213
- fixture_evals = list(get_evals_from_fixture(trace_dataset_name))
214
- Thread(
215
- target=_load_items,
216
- args=(traces, fixture_evals, simulate_streaming),
217
- daemon=True,
218
- ).start()
219
220
  umap_params_list = args.umap_params.split(",")
220
221
  umap_params = UMAPParameters(
221
222
  min_dist=float(umap_params_list[0]),
@@ -224,31 +225,45 @@ if __name__ == "__main__":
224
225
  )
225
226
  read_only = args.read_only
226
227
  logger.info(f"Server umap params: {umap_params}")
227
- if enable_prometheus := args.enable_prometheus:
228
+ if enable_prometheus := (get_env_enable_prometheus() or args.enable_prometheus):
229
+ if args.enable_prometheus:
230
+ warnings.warn(
231
+ "The --enable-prometheus command line argument is being deprecated "
232
+ "and will be removed in an upcoming release. "
233
+ "Please set the PHOENIX_ENABLE_PROMETHEUS environment variable to TRUE.",
234
+ DeprecationWarning,
235
+ stacklevel=2,
236
+ )
228
237
  from phoenix.server.prometheus import start_prometheus
229
238
 
230
239
  start_prometheus()
240
+
241
+ working_dir = get_working_dir().resolve()
231
242
  app = create_app(
243
+ database_url=db_connection_str,
232
244
  export_path=export_path,
233
245
  model=model,
234
246
  umap_params=umap_params,
235
- traces=traces,
236
247
  corpus=None if corpus_dataset is None else create_model_from_datasets(corpus_dataset),
237
248
  debug=args.debug,
238
249
  read_only=read_only,
239
- span_store=span_store,
240
250
  enable_prometheus=enable_prometheus,
251
+ initial_spans=fixture_spans,
252
+ initial_evaluations=fixture_evals,
241
253
  )
242
- host = args.host or get_env_host()
243
- port = args.port or get_env_port()
244
- server = Server(config=Config(app, host=host, port=port))
254
+ server = Server(config=Config(app, host=host, port=port)) # type: ignore
245
255
  Thread(target=_write_pid_file_when_ready, args=(server,), daemon=True).start()
246
256
 
247
257
  # Print information about the server
248
258
  phoenix_version = pkg_resources.get_distribution("arize-phoenix").version
249
- print(
250
- _WELCOME_MESSAGE.format(phoenix_version, host if host != "0.0.0.0" else "localhost", port)
251
- )
259
+ config = {
260
+ "version": phoenix_version,
261
+ "host": display_host,
262
+ "port": port,
263
+ "grpc_port": get_env_grpc_port(),
264
+ "storage": get_printable_db_url(db_connection_str),
265
+ }
266
+ print(_WELCOME_MESSAGE.format(**config))
252
267
 
253
268
  # Start the server
254
269
  server.run()
@@ -0,0 +1,218 @@
1
+ import json
2
+ from typing import Any, Dict, Optional
3
+
4
+ from starlette.responses import HTMLResponse
5
+
6
+ swagger_ui_default_parameters: Dict[str, Any] = {
7
+ "dom_id": "#swagger-ui",
8
+ "layout": "BaseLayout",
9
+ "deepLinking": True,
10
+ "showExtensions": True,
11
+ "showCommonExtensions": True,
12
+ }
13
+
14
+
15
+ def get_swagger_ui_html(
16
+ *,
17
+ openapi_url: str = "/schema",
18
+ title: str,
19
+ swagger_js_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.9.0/swagger-ui-bundle.js",
20
+ swagger_css_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.9.0/swagger-ui.css",
21
+ swagger_favicon_url: str = "/favicon.ico",
22
+ oauth2_redirect_url: Optional[str] = None,
23
+ init_oauth: Optional[str] = None,
24
+ swagger_ui_parameters: Optional[Dict[str, Any]] = None,
25
+ ) -> HTMLResponse:
26
+ """
27
+ Generate and return the HTML that loads Swagger UI for the interactive API
28
+ docs (normally served at `/docs`).
29
+ """
30
+ current_swagger_ui_parameters = swagger_ui_default_parameters.copy()
31
+ if swagger_ui_parameters:
32
+ current_swagger_ui_parameters.update(swagger_ui_parameters)
33
+
34
+ html = f"""
35
+ <!DOCTYPE html>
36
+ <html>
37
+ <head>
38
+ <link type="text/css" rel="stylesheet" href="{swagger_css_url}">
39
+ <link rel="shortcut icon" href="{swagger_favicon_url}">
40
+ <title>{title}</title>
41
+ </head>
42
+ <body>
43
+ <div id="swagger-ui">
44
+ </div>
45
+ <script src="{swagger_js_url}"></script>
46
+ <!-- `SwaggerUIBundle` is now available on the page -->
47
+ <script>
48
+ const ui = SwaggerUIBundle({{
49
+ url: '{openapi_url}',
50
+ """
51
+
52
+ for key, value in current_swagger_ui_parameters.items():
53
+ html += f"{json.dumps(key)}: {json.dumps(value)},\n"
54
+
55
+ if oauth2_redirect_url:
56
+ html += f"oauth2RedirectUrl: window.location.origin + '{oauth2_redirect_url}',"
57
+
58
+ html += """
59
+ presets: [
60
+ SwaggerUIBundle.presets.apis,
61
+ SwaggerUIBundle.SwaggerUIStandalonePreset
62
+ ],
63
+ })"""
64
+
65
+ if init_oauth:
66
+ html += f"""
67
+ ui.initOAuth({json.dumps(init_oauth)})
68
+ """
69
+
70
+ html += """
71
+ </script>
72
+ </body>
73
+ </html>
74
+ """
75
+ return HTMLResponse(html)
76
+
77
+
78
+ def get_redoc_html(
79
+ *,
80
+ openapi_url: str,
81
+ title: str,
82
+ redoc_js_url: str = "https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js",
83
+ redoc_favicon_url: str = "/favicon.ico",
84
+ with_google_fonts: bool = True,
85
+ ) -> HTMLResponse:
86
+ """
87
+ Generate and return the HTML response that loads ReDoc for the alternative
88
+ API docs (normally served at `/redoc`).
89
+
90
+
91
+ """
92
+ html = f"""
93
+ <!DOCTYPE html>
94
+ <html>
95
+ <head>
96
+ <title>{title}</title>
97
+ <!-- needed for adaptive design -->
98
+ <meta charset="utf-8"/>
99
+ <meta name="viewport" content="width=device-width, initial-scale=1">
100
+ """
101
+ if with_google_fonts:
102
+ html += """
103
+ <link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
104
+ """ # noqa: E501
105
+ html += f"""
106
+ <link rel="shortcut icon" href="{redoc_favicon_url}">
107
+ <!--
108
+ ReDoc doesn't change outer page styles
109
+ -->
110
+ <style>
111
+ body {{
112
+ margin: 0;
113
+ padding: 0;
114
+ }}
115
+ </style>
116
+ </head>
117
+ <body>
118
+ <noscript>
119
+ ReDoc requires Javascript to function. Please enable it to browse the documentation.
120
+ </noscript>
121
+ <redoc spec-url="{openapi_url}"></redoc>
122
+ <script src="{redoc_js_url}"> </script>
123
+ </body>
124
+ </html>
125
+ """
126
+ return HTMLResponse(html)
127
+
128
+
129
+ # Not needed now but copy-pasting for future reference
130
+ def get_swagger_ui_oauth2_redirect_html() -> HTMLResponse:
131
+ """
132
+ Generate the HTML response with the OAuth2 redirection for Swagger UI.
133
+
134
+ You normally don't need to use or change this.
135
+ """
136
+ # copied from https://github.com/swagger-api/swagger-ui/blob/v4.14.0/dist/oauth2-redirect.html
137
+ html = """
138
+ <!doctype html>
139
+ <html lang="en-US">
140
+ <head>
141
+ <title>Swagger UI: OAuth2 Redirect</title>
142
+ </head>
143
+ <body>
144
+ <script>
145
+ 'use strict';
146
+ function run () {
147
+ var oauth2 = window.opener.swaggerUIRedirectOauth2;
148
+ var sentState = oauth2.state;
149
+ var redirectUrl = oauth2.redirectUrl;
150
+ var isValid, qp, arr;
151
+
152
+ if (/code|token|error/.test(window.location.hash)) {
153
+ qp = window.location.hash.substring(1).replace('?', '&');
154
+ } else {
155
+ qp = location.search.substring(1);
156
+ }
157
+
158
+ arr = qp.split("&");
159
+ arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';});
160
+ qp = qp ? JSON.parse('{' + arr.join() + '}',
161
+ function (key, value) {
162
+ return key === "" ? value : decodeURIComponent(value);
163
+ }
164
+ ) : {};
165
+
166
+ isValid = qp.state === sentState;
167
+
168
+ if ((
169
+ oauth2.auth.schema.get("flow") === "accessCode" ||
170
+ oauth2.auth.schema.get("flow") === "authorizationCode" ||
171
+ oauth2.auth.schema.get("flow") === "authorization_code"
172
+ ) && !oauth2.auth.code) {
173
+ if (!isValid) {
174
+ oauth2.errCb({
175
+ authId: oauth2.auth.name,
176
+ source: "auth",
177
+ level: "warning",
178
+ message: "Authorization may be unsafe, passed state was changed in server. The passed state wasn't returned from auth server."
179
+ });
180
+ }
181
+
182
+ if (qp.code) {
183
+ delete oauth2.state;
184
+ oauth2.auth.code = qp.code;
185
+ oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
186
+ } else {
187
+ let oauthErrorMsg;
188
+ if (qp.error) {
189
+ oauthErrorMsg = "["+qp.error+"]: " +
190
+ (qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
191
+ (qp.error_uri ? "More info: "+qp.error_uri : "");
192
+ }
193
+
194
+ oauth2.errCb({
195
+ authId: oauth2.auth.name,
196
+ source: "auth",
197
+ level: "error",
198
+ message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server."
199
+ });
200
+ }
201
+ } else {
202
+ oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
203
+ }
204
+ window.close();
205
+ }
206
+
207
+ if (document.readyState !== 'loading') {
208
+ run();
209
+ } else {
210
+ document.addEventListener('DOMContentLoaded', function () {
211
+ run();
212
+ });
213
+ }
214
+ </script>
215
+ </body>
216
+ </html>
217
+ """ # noqa: E501
218
+ return HTMLResponse(content=html)
@@ -5,7 +5,7 @@ import psutil
5
5
  from prometheus_client import (
6
6
  Counter,
7
7
  Gauge,
8
- Histogram,
8
+ Summary,
9
9
  start_http_server,
10
10
  )
11
11
  from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
@@ -13,9 +13,9 @@ from starlette.requests import Request
13
13
  from starlette.responses import Response
14
14
  from starlette.routing import Match
15
15
 
16
- REQUESTS_PROCESSING_TIME = Histogram(
17
- name="starlette_requests_processing_time_seconds",
18
- documentation="Histogram of requests processing time by method and path (in seconds)",
16
+ REQUESTS_PROCESSING_TIME = Summary(
17
+ name="starlette_requests_processing_time_seconds_summary",
18
+ documentation="Summary of requests processing time by method and path (in seconds)",
19
19
  labelnames=["method", "path"],
20
20
  )
21
21
  EXCEPTIONS = Counter(
@@ -33,6 +33,22 @@ CPU_METRIC = Gauge(
33
33
  documentation="CPU usage percent",
34
34
  labelnames=["core"],
35
35
  )
36
+ BULK_LOADER_INSERTION_TIME = Summary(
37
+ name="bulk_loader_insertion_time_seconds_summary",
38
+ documentation="Summary of database insertion time (seconds)",
39
+ )
40
+ BULK_LOADER_SPAN_INSERTIONS = Counter(
41
+ name="bulk_loader_span_insertions_total",
42
+ documentation="Total count of bulk loader span insertions",
43
+ )
44
+ BULK_LOADER_EVALUATION_INSERTIONS = Counter(
45
+ name="bulk_loader_evaluation_insertions_total",
46
+ documentation="Total count of bulk loader evaluation insertions",
47
+ )
48
+ BULK_LOADER_EXCEPTIONS = Counter(
49
+ name="bulk_loader_exceptions_total",
50
+ documentation="Total count of bulk loader exceptions",
51
+ )
36
52
 
37
53
 
38
54
  class PrometheusMiddleware(BaseHTTPMiddleware):
@@ -51,14 +67,14 @@ class PrometheusMiddleware(BaseHTTPMiddleware):
51
67
  except BaseException as e:
52
68
  EXCEPTIONS.labels(method=method, path=path, exception_type=type(e).__name__).inc()
53
69
  raise
54
- stop_time = time.perf_counter()
55
- REQUESTS_PROCESSING_TIME.labels(method=method, path=path).observe(stop_time - start_time)
70
+ end_time = time.perf_counter()
71
+ REQUESTS_PROCESSING_TIME.labels(method=method, path=path).observe(end_time - start_time)
56
72
  return response
57
73
 
58
74
 
59
75
  def start_prometheus() -> None:
60
76
  Thread(target=gather_system_data, daemon=True).start()
61
- start_http_server(9090)
77
+ start_http_server(9090, addr="::")
62
78
 
63
79
 
64
80
  def gather_system_data() -> None: