tracecast 0.3.1__tar.gz → 0.3.2__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.
- {tracecast-0.3.1/tracecast.egg-info → tracecast-0.3.2}/PKG-INFO +1 -1
- {tracecast-0.3.1 → tracecast-0.3.2}/pyproject.toml +1 -1
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/core/export_queue.py +70 -13
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/core/tracer.py +2 -2
- {tracecast-0.3.1 → tracecast-0.3.2/tracecast.egg-info}/PKG-INFO +1 -1
- {tracecast-0.3.1 → tracecast-0.3.2}/LICENSE +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/README.md +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/setup.cfg +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tests/test_aggregator_graph.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tests/test_async_export_path.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tests/test_context_propagation.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tests/test_cost_calculator.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tests/test_dashboard_mount.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tests/test_dashboard_pushdown.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tests/test_dashboard_sessions.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tests/test_decorators.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tests/test_e2e_langgraph.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tests/test_export_fallback.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tests/test_export_queue.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tests/test_export_retry.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tests/test_export_stats.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tests/test_exporters_db.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tests/test_http_ingest.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tests/test_ingest_auth.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tests/test_integration_frameworks.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tests/test_integration_langchain.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tests/test_json_exporter.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tests/test_langchain_callback.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tests/test_llm_wrappers.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tests/test_logging.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tests/test_metrics_totals.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tests/test_mongo_exporter.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tests/test_prompt_dashboard.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tests/test_prompts.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tests/test_retention.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tests/test_retention_mount.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tests/test_roundtrip.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tests/test_schema_v2.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tests/test_score.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tests/test_score_dashboard.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tests/test_scoring_api.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tests/test_serve.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tests/test_span.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tests/test_span_filter.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tests/test_streaming.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tests/test_token_counter.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tests/test_trace.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tests/test_trace_span.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tests/test_trace_summary.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tests/test_tracer.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/__init__.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/core/__init__.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/core/cost_calculator.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/core/export_retry.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/core/export_stats.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/core/ingest.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/core/ingest_auth.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/core/logger.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/core/payload.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/core/scoring.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/core/span_filter.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/core/token_counter.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/core/trace_summary.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/dashboard/__init__.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/dashboard/aggregator.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/dashboard/asgi_middleware.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/dashboard/blueprint.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/dashboard/eval_reader.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/dashboard/prompt_reader.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/dashboard/reader.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/dashboard/retention.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/dashboard/router.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/dashboard/score_reader.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/dashboard/standalone.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/dashboard/static/assets/index-DllJJzmE.css +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/dashboard/static/assets/index-WGkVmqjn.js +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/dashboard/static/index.html +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/decorators.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/eval/__init__.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/eval/cli.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/eval/compare.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/eval/dataset.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/eval/decorator.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/eval/judge.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/eval/metrics.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/eval/models.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/eval/online.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/eval/runner.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/eval/scorers.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/exporters/__init__.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/exporters/_eval_store.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/exporters/base.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/exporters/dict_exporter.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/exporters/http.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/exporters/json_file.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/exporters/mongo.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/exporters/postgres.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/exporters/query.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/instrument.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/instrumentors/__init__.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/instrumentors/_llamaindex_handler.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/instrumentors/_streaming.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/instrumentors/anthropic_inst.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/instrumentors/base.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/instrumentors/crewai_inst.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/instrumentors/gemini_inst.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/instrumentors/langchain_inst.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/instrumentors/llamaindex_inst.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/instrumentors/openai_inst.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/integrations/__init__.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/integrations/langchain.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/integrations/llm.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/middleware.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/models/__init__.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/models/score.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/models/span.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/models/trace.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/prompts/__init__.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/prompts/client.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/prompts/models.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast/serve.py +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast.egg-info/SOURCES.txt +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast.egg-info/dependency_links.txt +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast.egg-info/entry_points.txt +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast.egg-info/requires.txt +0 -0
- {tracecast-0.3.1 → tracecast-0.3.2}/tracecast.egg-info/top_level.txt +0 -0
|
@@ -112,11 +112,28 @@ class _FlushMarker:
|
|
|
112
112
|
self.event = threading.Event()
|
|
113
113
|
|
|
114
114
|
|
|
115
|
+
def default_overflow_queue_size(main_maxsize: int) -> int:
|
|
116
|
+
"""Secondary buffer for disk-spool handoff — still bounded, never blocks request path."""
|
|
117
|
+
raw = os.environ.get("TRACECAST_EXPORT_OVERFLOW")
|
|
118
|
+
if raw is not None and str(raw).strip() != "":
|
|
119
|
+
try:
|
|
120
|
+
return max(1, int(raw))
|
|
121
|
+
except ValueError:
|
|
122
|
+
pass
|
|
123
|
+
# At least as large as main queue (and ≥256) so brief export stalls
|
|
124
|
+
# hand off to spool thread without dropping; hard cap for RAM.
|
|
125
|
+
return max(1, min(5000, max(main_maxsize, 256)))
|
|
126
|
+
|
|
127
|
+
|
|
115
128
|
class ExportWorker:
|
|
116
|
-
"""Bounded
|
|
129
|
+
"""Bounded export buffer with **hard latency guarantee on request path**.
|
|
117
130
|
|
|
118
|
-
Request path only
|
|
119
|
-
|
|
131
|
+
Request / ASGI path does **only** ``queue.put_nowait``:
|
|
132
|
+
1) main export queue
|
|
133
|
+
2) if full and spool enabled → overflow queue (still put_nowait)
|
|
134
|
+
3) if overflow full → drop counter (never block the user message)
|
|
135
|
+
|
|
136
|
+
Serialize, disk spool I/O, HTTP/Mongo all run on daemon worker threads.
|
|
120
137
|
"""
|
|
121
138
|
|
|
122
139
|
def __init__(
|
|
@@ -130,6 +147,7 @@ class ExportWorker:
|
|
|
130
147
|
on_drop: Optional[Callable[[Any], None]] = None,
|
|
131
148
|
spool_path: Optional[str] = None,
|
|
132
149
|
spool_poll: float = 1.0,
|
|
150
|
+
overflow_maxsize: Optional[int] = None,
|
|
133
151
|
):
|
|
134
152
|
if maxsize < 1:
|
|
135
153
|
raise ValueError("export queue maxsize must be >= 1")
|
|
@@ -154,6 +172,16 @@ class ExportWorker:
|
|
|
154
172
|
self._spool_poll = max(0.2, float(spool_poll))
|
|
155
173
|
self._spool_stop = threading.Event()
|
|
156
174
|
self._spool_thread: Optional[threading.Thread] = None
|
|
175
|
+
# Overflow: hold Trace refs briefly until spool thread writes JSONL.
|
|
176
|
+
# Never serialize/disk on the calling (request) thread.
|
|
177
|
+
osize = (
|
|
178
|
+
overflow_maxsize
|
|
179
|
+
if overflow_maxsize is not None
|
|
180
|
+
else default_overflow_queue_size(maxsize)
|
|
181
|
+
)
|
|
182
|
+
self._overflow: Optional[queue.Queue] = (
|
|
183
|
+
queue.Queue(maxsize=max(1, osize)) if self._spool_path else None
|
|
184
|
+
)
|
|
157
185
|
if self._spool_path:
|
|
158
186
|
Path(self._spool_path).parent.mkdir(parents=True, exist_ok=True)
|
|
159
187
|
|
|
@@ -190,25 +218,35 @@ class ExportWorker:
|
|
|
190
218
|
self._atexit_registered = True
|
|
191
219
|
|
|
192
220
|
def enqueue(self, item: Any) -> bool:
|
|
193
|
-
"""Non-blocking.
|
|
221
|
+
"""Non-blocking. Never waits on disk, network, or locks held by exporters.
|
|
222
|
+
|
|
223
|
+
Latency budget on caller: a few microseconds for put_nowait.
|
|
224
|
+
"""
|
|
194
225
|
self.start()
|
|
195
226
|
try:
|
|
196
227
|
self._q.put_nowait(item)
|
|
197
228
|
return True
|
|
198
229
|
except queue.Full:
|
|
199
|
-
|
|
230
|
+
pass
|
|
231
|
+
# Main full → overflow handoff for async spool (still non-blocking).
|
|
232
|
+
if self._overflow is not None:
|
|
233
|
+
try:
|
|
234
|
+
self._overflow.put_nowait(item)
|
|
200
235
|
self._spooled += 1
|
|
201
236
|
return True
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
237
|
+
except queue.Full:
|
|
238
|
+
pass
|
|
239
|
+
self._dropped += 1
|
|
240
|
+
self._log_drop(item)
|
|
241
|
+
if self._on_drop is not None:
|
|
242
|
+
try:
|
|
243
|
+
self._on_drop(item)
|
|
244
|
+
except Exception:
|
|
245
|
+
pass
|
|
246
|
+
return False
|
|
210
247
|
|
|
211
248
|
def _append_spool(self, item: Any) -> bool:
|
|
249
|
+
"""Disk write — **worker thread only**, never request path."""
|
|
212
250
|
if not self._spool_path:
|
|
213
251
|
return False
|
|
214
252
|
try:
|
|
@@ -221,8 +259,25 @@ class ExportWorker:
|
|
|
221
259
|
_logger.warning("TraceCast: export spool write failed: %s", exc)
|
|
222
260
|
return False
|
|
223
261
|
|
|
262
|
+
def _flush_overflow_to_spool(self) -> None:
|
|
263
|
+
if self._overflow is None:
|
|
264
|
+
return
|
|
265
|
+
while True:
|
|
266
|
+
try:
|
|
267
|
+
item = self._overflow.get_nowait()
|
|
268
|
+
except queue.Empty:
|
|
269
|
+
break
|
|
270
|
+
if not self._append_spool(item):
|
|
271
|
+
# Last resort: try main queue again; else count drop.
|
|
272
|
+
try:
|
|
273
|
+
self._q.put_nowait(item)
|
|
274
|
+
except queue.Full:
|
|
275
|
+
self._dropped += 1
|
|
276
|
+
self._log_drop(item)
|
|
277
|
+
|
|
224
278
|
def _spool_loop(self) -> None:
|
|
225
279
|
while not self._spool_stop.wait(self._spool_poll):
|
|
280
|
+
self._flush_overflow_to_spool()
|
|
226
281
|
self._drain_spool_once()
|
|
227
282
|
|
|
228
283
|
def _drain_spool_once(self) -> None:
|
|
@@ -277,6 +332,7 @@ class ExportWorker:
|
|
|
277
332
|
def flush(self, timeout: Optional[float] = None) -> bool:
|
|
278
333
|
if not self._started:
|
|
279
334
|
return True
|
|
335
|
+
self._flush_overflow_to_spool()
|
|
280
336
|
self._drain_spool_once()
|
|
281
337
|
marker = _FlushMarker()
|
|
282
338
|
deadline = None if timeout is None else time.monotonic() + timeout
|
|
@@ -302,6 +358,7 @@ class ExportWorker:
|
|
|
302
358
|
self._spool_stop.set()
|
|
303
359
|
if self._spool_thread is not None:
|
|
304
360
|
self._spool_thread.join(timeout=min(2.0, timeout))
|
|
361
|
+
self._flush_overflow_to_spool()
|
|
305
362
|
self._drain_spool_once()
|
|
306
363
|
deadline = time.monotonic() + timeout
|
|
307
364
|
while True:
|
|
@@ -190,8 +190,8 @@ class Tracer:
|
|
|
190
190
|
return
|
|
191
191
|
if self._export_worker is not None:
|
|
192
192
|
self._schedule_online_eval(trace)
|
|
193
|
-
# Enqueue live Trace — serialize
|
|
194
|
-
# Request path
|
|
193
|
+
# Enqueue live Trace — serialize/HTTP/disk only on worker threads.
|
|
194
|
+
# Request path: put_nowait only (never blocks user message latency).
|
|
195
195
|
self._export_worker.enqueue(trace)
|
|
196
196
|
return
|
|
197
197
|
self._export(trace)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|