ioa-observe-sdk 1.0.27__py3-none-any.whl → 1.0.28__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.
@@ -2,6 +2,7 @@
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
 
4
4
  import sys
5
+ import threading
5
6
 
6
7
  from .http import HTTPClient
7
8
  from ioa_observe.sdk.version import __version__
@@ -51,21 +52,29 @@ class Client:
51
52
  class KVStore(object):
52
53
  """
53
54
  Key-Value Store for storing key-value pairs (Singleton).
55
+ Thread-safe implementation for concurrent access.
54
56
  """
55
57
 
56
58
  _instance = None
59
+ _instance_lock = threading.Lock()
57
60
 
58
61
  def __new__(cls):
59
62
  if cls._instance is None:
60
- cls._instance = super(KVStore, cls).__new__(cls)
61
- cls._instance.store = {}
63
+ with cls._instance_lock:
64
+ # Double-check locking pattern
65
+ if cls._instance is None:
66
+ cls._instance = super(KVStore, cls).__new__(cls)
67
+ cls._instance.store = {}
68
+ cls._instance._lock = threading.Lock()
62
69
  return cls._instance
63
70
 
64
71
  def set(self, key: str, value: str):
65
- self.store[key] = value
72
+ with self._lock:
73
+ self.store[key] = value
66
74
 
67
75
  def get(self, key: str):
68
- return self.store.get(key)
76
+ with self._lock:
77
+ return self.store.get(key)
69
78
 
70
79
 
71
80
  kv_store = KVStore()
@@ -67,6 +67,7 @@ APP_NAME = ""
67
67
 
68
68
  SESSION_IDLE_TIMEOUT_SECONDS = 300 # e.g. 5 minutes
69
69
  SESSION_WATCHER_INTERVAL_SECONDS = 30
70
+ MAX_PROCESSED_SPANS_SIZE = 100000 # Maximum size before cleanup
70
71
 
71
72
 
72
73
  def determine_reliability_score(span):
@@ -142,8 +143,10 @@ class TracerWrapper(object):
142
143
  obj.__image_uploader = image_uploader
143
144
  # {(agent_name): [success_count, total_count]}
144
145
  obj._agent_execution_counts = {}
146
+ obj._agent_execution_counts_lock = threading.Lock()
145
147
  # Track spans that have been processed to avoid duplicates
146
148
  obj._processed_spans = set()
149
+ obj._processed_spans_lock = threading.Lock()
147
150
  TracerWrapper.app_name = TracerWrapper.resource_attributes.get(
148
151
  "service.name", "observe"
149
152
  )
@@ -306,6 +309,11 @@ class TracerWrapper(object):
306
309
  expired[session_id] = last_ts
307
310
  del self._session_last_activity[session_id]
308
311
 
312
+ # Periodic cleanup of processed spans to prevent unbounded memory growth
313
+ with self._processed_spans_lock:
314
+ if len(self._processed_spans) > MAX_PROCESSED_SPANS_SIZE:
315
+ self._processed_spans.clear()
316
+
309
317
  if not expired:
310
318
  continue
311
319
 
@@ -313,7 +321,6 @@ class TracerWrapper(object):
313
321
 
314
322
  # Iterate over a snapshot and do *not* modify `expired` in the loop
315
323
  for session_id, _last_ts in list(expired.items()):
316
- print("ending session", session_id)
317
324
  with tracer.start_as_current_span("session.end") as span:
318
325
  span.set_attribute("session.id", session_id)
319
326
  workflow_name = get_value("workflow_name")
@@ -420,16 +427,19 @@ class TracerWrapper(object):
420
427
  # Added for avoid duplicate on_ending with manual triggers
421
428
  # from decorators (@tool, @workflow) in base.py
422
429
  span_id = span.context.span_id
423
- if span_id in self._processed_spans:
424
- # This span was already processed, skip to avoid duplicates
425
- return
430
+ with self._processed_spans_lock:
431
+ if span_id in self._processed_spans:
432
+ # This span was already processed, skip to avoid duplicates
433
+ return
426
434
 
427
- # Mark this span as processed
428
- self._processed_spans.add(span_id)
435
+ # Mark this span as processed
436
+ self._processed_spans.add(span_id)
429
437
 
430
438
  # update last activity per session
439
+ # Skip session.end spans to avoid infinite loop - these are cleanup spans
440
+ # that should not re-register the session as active
431
441
  session_id = span.attributes.get("session.id")
432
- if session_id:
442
+ if session_id and span.name != "session.end":
433
443
  with self._session_lock:
434
444
  self._session_last_activity[session_id] = time.time()
435
445
 
@@ -567,21 +577,23 @@ class TracerWrapper(object):
567
577
  return self.__tracer_provider.get_tracer(TRACER_NAME)
568
578
 
569
579
  def record_agent_execution(self, agent_name: str, success: bool):
570
- counts = self._agent_execution_counts.setdefault(agent_name, [0, 0])
571
- if success:
572
- counts[0] += 1 # success count
573
- counts[1] += 1 # total count
580
+ with self._agent_execution_counts_lock:
581
+ counts = self._agent_execution_counts.setdefault(agent_name, [0, 0])
582
+ if success:
583
+ counts[0] += 1 # success count
584
+ counts[1] += 1 # total count
574
585
 
575
586
  def _observe_agent_execution_success_rate(self, observer):
576
587
  measurements = []
577
- for agent_name, (
578
- success_count,
579
- total_count,
580
- ) in self._agent_execution_counts.items():
581
- rate = (success_count / total_count) if total_count > 0 else 0.0
582
- measurements.append(
583
- Observation(value=rate, attributes={"agent_name": agent_name})
584
- )
588
+ with self._agent_execution_counts_lock:
589
+ for agent_name, (
590
+ success_count,
591
+ total_count,
592
+ ) in self._agent_execution_counts.items():
593
+ rate = (success_count / total_count) if total_count > 0 else 0.0
594
+ measurements.append(
595
+ Observation(value=rate, attributes={"agent_name": agent_name})
596
+ )
585
597
  return measurements
586
598
 
587
599
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ioa-observe-sdk
3
- Version: 1.0.27
3
+ Version: 1.0.28
4
4
  Summary: IOA Observability SDK
5
5
  License-Expression: Apache-2.0
6
6
  Requires-Python: >=3.10
@@ -4,7 +4,7 @@ ioa_observe/sdk/instruments.py,sha256=cA5Yq1BYFovMrYUNYQXua-JXsMtMOa_YOn6yiJZNwL
4
4
  ioa_observe/sdk/telemetry.py,sha256=6wwaOYhZMjAZ6dXDdBA2LUWo3LLptTcy93BJqDdbqBM,3103
5
5
  ioa_observe/sdk/version.py,sha256=oriNAY8huVDPw5N_rv5F_PehFrcGo37FSGBCfZCM81M,121
6
6
  ioa_observe/sdk/client/__init__.py,sha256=V4Rt-Z1EHlM12Lx3hGd0Ew70V1JKAQZXNb9ABtdWHEI,224
7
- ioa_observe/sdk/client/client.py,sha256=6TVOo_E1ulE3WO_CYG7oPgeucs-qegOA09uTO3yQiyk,2112
7
+ ioa_observe/sdk/client/client.py,sha256=i4ZlYrhFcXfg5BTROLAKxhfAAsb-7RS0ch9gucsWXd0,2480
8
8
  ioa_observe/sdk/client/http.py,sha256=LdLYSQPFIhKN5BTB-N78jLO7ITl7jGjA0-qpewEIvO4,1724
9
9
  ioa_observe/sdk/config/__init__.py,sha256=8aVNaw0yRNLFPxlf97iOZLlJVcV81ivSDnudH2m1OIo,572
10
10
  ioa_observe/sdk/connectors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -35,15 +35,15 @@ ioa_observe/sdk/tracing/content_allow_list.py,sha256=1fAkpIwUQ7vDwCTkIVrqeltWQtr
35
35
  ioa_observe/sdk/tracing/context_manager.py,sha256=O0JEXYa9h8anhW78R8KKBuqS0j4by1E1KXxNIMPnLr8,400
36
36
  ioa_observe/sdk/tracing/context_utils.py,sha256=-sYS9vPLI87davV9ubneq5xqbV583CC_c0SmOQS1TAs,2933
37
37
  ioa_observe/sdk/tracing/manual.py,sha256=KS6WN-zw9vAACzXYmnMoJm9d1fenYMfvzeK1GrGDPDE,1937
38
- ioa_observe/sdk/tracing/tracing.py,sha256=7eJAEqaAEBZ2gjIXrdb7SyUidA8P10GnpBLzr8InLKI,50007
38
+ ioa_observe/sdk/tracing/tracing.py,sha256=ywvT5Z1DcaLYZQvUP30YKz6m6v76Idlvb2qszupcxS4,50774
39
39
  ioa_observe/sdk/tracing/transform_span.py,sha256=XTApi_gJxum7ynvhtcoCfDyK8VVOj91Q1DT6hAeLHA8,8419
40
40
  ioa_observe/sdk/utils/__init__.py,sha256=UPn182U-UblF_XwXaFpx8F-TmQTbm1LYf9y89uSp5Hw,704
41
41
  ioa_observe/sdk/utils/const.py,sha256=d67dUTAH9UpWvUV9GLBUqn1Sc2knJ55dy-e6YoLrvSo,1318
42
42
  ioa_observe/sdk/utils/in_memory_span_exporter.py,sha256=H_4TRaThMO1H6vUQ0OpQvzJk_fZH0OOsRAM1iZQXsR8,2112
43
43
  ioa_observe/sdk/utils/json_encoder.py,sha256=g4NQ0tTqgWssY6I1D7r4zo0G6PiUo61jhofTAw5-jno,639
44
44
  ioa_observe/sdk/utils/package_check.py,sha256=1d1MjxhwoEZIx9dumirT2pRsEWgn-m-SI4npDeEalew,576
45
- ioa_observe_sdk-1.0.27.dist-info/licenses/LICENSE.md,sha256=55VjUfgjWOS4vv3Cf55gfq-RxjPgRIO2vlgYPUuC5lA,11362
46
- ioa_observe_sdk-1.0.27.dist-info/METADATA,sha256=OrhfkqzR0Qj2qwSGXtSmL9rYjS1OMk-IYmTU5wHMHdE,7997
47
- ioa_observe_sdk-1.0.27.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
48
- ioa_observe_sdk-1.0.27.dist-info/top_level.txt,sha256=Yt-6Y1olZEDqCs2REeqI30WjYx0pLGQSVqzYmDd67N8,12
49
- ioa_observe_sdk-1.0.27.dist-info/RECORD,,
45
+ ioa_observe_sdk-1.0.28.dist-info/licenses/LICENSE.md,sha256=55VjUfgjWOS4vv3Cf55gfq-RxjPgRIO2vlgYPUuC5lA,11362
46
+ ioa_observe_sdk-1.0.28.dist-info/METADATA,sha256=xEbYDkX8xS0u72TgyF-DvEM9R7BxEA8wp3khv4MWHAE,7997
47
+ ioa_observe_sdk-1.0.28.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
48
+ ioa_observe_sdk-1.0.28.dist-info/top_level.txt,sha256=Yt-6Y1olZEDqCs2REeqI30WjYx0pLGQSVqzYmDd67N8,12
49
+ ioa_observe_sdk-1.0.28.dist-info/RECORD,,