openlit 1.27.1__py3-none-any.whl → 1.29.0__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.
openlit/__init__.py CHANGED
@@ -46,7 +46,7 @@ from openlit.instrumentation.pinecone import PineconeInstrumentor
46
46
  from openlit.instrumentation.qdrant import QdrantInstrumentor
47
47
  from openlit.instrumentation.milvus import MilvusInstrumentor
48
48
  from openlit.instrumentation.transformers import TransformersInstrumentor
49
- from openlit.instrumentation.gpu import NvidiaGPUInstrumentor
49
+ from openlit.instrumentation.gpu import GPUInstrumentor
50
50
  import openlit.guard
51
51
  import openlit.evals
52
52
 
@@ -313,7 +313,7 @@ def init(environment="default", application_name="default", tracer=None, otlp_en
313
313
  disabled_instrumentors, module_name_map)
314
314
 
315
315
  if not disable_metrics and collect_gpu_stats:
316
- NvidiaGPUInstrumentor().instrument(
316
+ GPUInstrumentor().instrument(
317
317
  environment=config.environment,
318
318
  application_name=config.application_name,
319
319
  )
@@ -1,132 +1,213 @@
1
- # pylint: disable=useless-return, bad-staticmethod-argument, duplicate-code, import-outside-toplevel, broad-exception-caught, unused-argument
1
+ # pylint: disable=useless-return, bad-staticmethod-argument, duplicate-code, import-outside-toplevel, broad-exception-caught, unused-argument, import-error, too-many-return-statements, superfluous-parens
2
2
  """Initializer of Auto Instrumentation of GPU Metrics"""
3
3
 
4
4
  from typing import Collection, Iterable
5
5
  import logging
6
6
  from functools import partial
7
-
8
7
  from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
9
8
  from opentelemetry.sdk.resources import TELEMETRY_SDK_NAME
10
9
  from opentelemetry.metrics import get_meter, CallbackOptions, Observation
11
-
12
10
  from openlit.semcov import SemanticConvetion
13
11
 
14
12
  # Initialize logger for logging potential issues and operations
15
13
  logger = logging.getLogger(__name__)
16
14
 
17
- class NvidiaGPUInstrumentor(BaseInstrumentor):
15
+ class GPUInstrumentor(BaseInstrumentor):
18
16
  """
19
- An instrumentor for collecting NVIDIA GPU metrics.
17
+ An instrumentor for collecting GPU metrics.
20
18
  """
21
19
 
22
20
  def instrumentation_dependencies(self) -> Collection[str]:
23
21
  return []
24
22
 
25
23
  def _instrument(self, **kwargs):
26
-
27
24
  application_name = kwargs.get("application_name", "default")
28
25
  environment = kwargs.get("environment", "default")
29
-
26
+ # pylint: disable=attribute-defined-outside-init
27
+ self.gpu_type = self._get_gpu_type()
30
28
  meter = get_meter(
31
29
  __name__,
32
30
  "0.1.0",
33
31
  schema_url="https://opentelemetry.io/schemas/1.11.0",
34
32
  )
35
33
 
36
- def check_and_record(value):
37
- return value if value is not None else 0
38
-
39
- meter.create_observable_gauge(
40
- name=SemanticConvetion.GPU_UTILIZATION,
41
- callbacks=[partial(self._collect_metric, environment,
42
- application_name, check_and_record, "utilization")],
43
- description="GPU Utilization",
44
- )
45
- meter.create_observable_gauge(
46
- name=SemanticConvetion.GPU_UTILIZATION_ENC,
47
- callbacks=[partial(self._collect_metric, environment,
48
- application_name, check_and_record, "utilization_enc")],
49
- description="GPU Encoder Utilization",
50
- )
51
- meter.create_observable_gauge(
52
- name=SemanticConvetion.GPU_UTILIZATION_DEC,
53
- callbacks=[partial(self._collect_metric, environment,
54
- application_name, check_and_record, "utilization_dec")],
55
- description="GPU Decoder Utilization",
56
- )
57
- meter.create_observable_gauge(
58
- name=SemanticConvetion.GPU_TEMPERATURE,
59
- callbacks=[partial(self._collect_metric, environment,
60
- application_name, check_and_record, "temperature")],
61
- description="GPU Temperature",
62
- )
63
- meter.create_observable_gauge(
64
- name=SemanticConvetion.GPU_FAN_SPEED,
65
- callbacks=[partial(self._collect_metric, environment,
66
- application_name, check_and_record, "fan_speed")],
67
- description="GPU Fan Speed",
68
- )
69
- meter.create_observable_gauge(
70
- name=SemanticConvetion.GPU_MEMORY_AVAILABLE,
71
- callbacks=[partial(self._collect_metric, environment,
72
- application_name, check_and_record, "memory_available")],
73
- description="GPU Memory Available",
74
- )
75
- meter.create_observable_gauge(
76
- name=SemanticConvetion.GPU_MEMORY_TOTAL,
77
- callbacks=[partial(self._collect_metric, environment,
78
- application_name, check_and_record, "memory_total")],
79
- description="GPU Memory Total",
80
- )
81
- meter.create_observable_gauge(
82
- name=SemanticConvetion.GPU_MEMORY_USED,
83
- callbacks=[partial(self._collect_metric, environment,
84
- application_name, check_and_record, "memory_used")],
85
- description="GPU Memory Used",
86
- )
87
- meter.create_observable_gauge(
88
- name=SemanticConvetion.GPU_MEMORY_FREE,
89
- callbacks=[partial(self._collect_metric, environment,
90
- application_name, check_and_record, "memory_free")],
91
- description="GPU Memory Free",
92
- )
93
- meter.create_observable_gauge(
94
- name=SemanticConvetion.GPU_POWER_DRAW,
95
- callbacks=[partial(self._collect_metric, environment,
96
- application_name, check_and_record, "power_draw")],
97
- description="GPU Power Draw",
98
- )
99
- meter.create_observable_gauge(
100
- name=SemanticConvetion.GPU_POWER_LIMIT,
101
- callbacks=[partial(self._collect_metric, environment,
102
- application_name, check_and_record, "power_limit")],
103
- description="GPU Power Limit",
104
- )
34
+ if not self.gpu_type:
35
+ logger.error(
36
+ "OpenLIT GPU Instrumentation Error: No supported GPUs found."
37
+ "If this is a non-GPU host, set `collect_gpu_stats=False` to disable GPU stats."
38
+ )
39
+ return
40
+
41
+ metric_names = [
42
+ ("GPU_UTILIZATION", "utilization"),
43
+ ("GPU_UTILIZATION_ENC", "utilization_enc"),
44
+ ("GPU_UTILIZATION_DEC", "utilization_dec"),
45
+ ("GPU_TEMPERATURE", "temperature"),
46
+ ("GPU_FAN_SPEED", "fan_speed"),
47
+ ("GPU_MEMORY_AVAILABLE", "memory_available"),
48
+ ("GPU_MEMORY_TOTAL", "memory_total"),
49
+ ("GPU_MEMORY_USED", "memory_used"),
50
+ ("GPU_MEMORY_FREE", "memory_free"),
51
+ ("GPU_POWER_DRAW", "power_draw"),
52
+ ("GPU_POWER_LIMIT", "power_limit"),
53
+ ]
54
+
55
+ for semantic_name, internal_name in metric_names:
56
+ meter.create_observable_gauge(
57
+ name=getattr(SemanticConvetion, semantic_name),
58
+ callbacks=[partial(self._collect_metric,
59
+ environment, application_name, internal_name)],
60
+ description=f"GPU {internal_name.replace('_', ' ').title()}",
61
+ )
105
62
 
106
63
  def _uninstrument(self, **kwargs):
107
64
  # Proper uninstrumentation logic to revert patched methods
108
65
  pass
109
66
 
67
+ def _get_gpu_type(self) -> str:
68
+ try:
69
+ import pynvml
70
+ pynvml.nvmlInit()
71
+ return "nvidia"
72
+ except Exception:
73
+ try:
74
+ import amdsmi
75
+ amdsmi.amdsmi_init()
76
+ return "amd"
77
+ except Exception:
78
+ return None
79
+
80
+
110
81
  def _collect_metric(self, environment, application_name,
111
- check_and_record, metric_name,
82
+ metric_name,
112
83
  options: CallbackOptions) -> Iterable[Observation]:
84
+ # pylint: disable=no-else-return
85
+ if self.gpu_type == "nvidia":
86
+ return self._collect_nvidia_metrics(environment, application_name, metric_name, options)
87
+ elif self.gpu_type == "amd":
88
+ return self._collect_amd_metrics(environment, application_name, metric_name, options)
89
+ return []
113
90
 
114
- import gpustat
115
-
91
+ def _collect_nvidia_metrics(self, environment, application_name,
92
+ metric_name,
93
+ options: CallbackOptions) -> Iterable[Observation]:
116
94
  try:
117
- gpu_stats = gpustat.GPUStatCollection.new_query()
95
+ import pynvml
96
+ gpu_count = pynvml.nvmlDeviceGetCount()
97
+ mega_bytes = 1024 * 1024
98
+ gpu_index = 0
99
+ for gpu_index in range(gpu_count):
100
+ handle = pynvml.nvmlDeviceGetHandleByIndex(gpu_index)
101
+
102
+ def get_metric_value(handle, metric_name):
103
+ try:
104
+ # pylint: disable=no-else-return
105
+ if metric_name == "temperature":
106
+ return pynvml.nvmlDeviceGetTemperature(handle,
107
+ pynvml.NVML_TEMPERATURE_GPU)
108
+ elif metric_name == "utilization":
109
+ return pynvml.nvmlDeviceGetUtilizationRates(handle).gpu
110
+ elif metric_name == "utilization_enc":
111
+ return pynvml.nvmlDeviceGetEncoderUtilization(handle)[0]
112
+ elif metric_name == "utilization_dec":
113
+ return pynvml.nvmlDeviceGetDecoderUtilization(handle)[0]
114
+ elif metric_name == "fan_speed":
115
+ return 0
116
+ elif metric_name == "memory_available":
117
+ memory_info = pynvml.nvmlDeviceGetMemoryInfo(handle)
118
+ return (memory_info.free // mega_bytes) # Assuming reserved memory is 0
119
+ elif metric_name == "memory_total":
120
+ return (pynvml.nvmlDeviceGetMemoryInfo(handle).total // mega_bytes)
121
+ elif metric_name == "memory_used":
122
+ return (pynvml.nvmlDeviceGetMemoryInfo(handle).used // mega_bytes)
123
+ elif metric_name == "memory_free":
124
+ return (pynvml.nvmlDeviceGetMemoryInfo(handle).free // mega_bytes)
125
+ elif metric_name == "power_draw":
126
+ return (pynvml.nvmlDeviceGetPowerUsage(handle) // 1000.0)
127
+ elif metric_name == "power_limit":
128
+ return (pynvml.nvmlDeviceGetEnforcedPowerLimit(handle) // 1000.0)
129
+ except Exception as e:
130
+ # pylint: disable=cell-var-from-loop
131
+ logger.error("Error collecting metric %s for GPU %d: %s", metric_name,
132
+ gpu_index, e)
133
+ return 0
134
+
135
+ def safe_decode(byte_string):
136
+ if isinstance(byte_string, bytes):
137
+ return byte_string.decode('utf-8')
138
+ return byte_string
118
139
 
119
- for gpu in gpu_stats.gpus:
120
140
  attributes = {
121
141
  TELEMETRY_SDK_NAME: "openlit",
122
142
  SemanticConvetion.GEN_AI_APPLICATION_NAME: application_name,
123
143
  SemanticConvetion.GEN_AI_ENVIRONMENT: environment,
124
- SemanticConvetion.GPU_INDEX: gpu.index,
125
- SemanticConvetion.GPU_UUID: gpu.uuid,
126
- SemanticConvetion.GPU_NAME: gpu.name,
144
+ SemanticConvetion.GPU_INDEX: str(gpu_index),
145
+ SemanticConvetion.GPU_UUID: safe_decode(pynvml.nvmlDeviceGetUUID(handle)),
146
+ SemanticConvetion.GPU_NAME: safe_decode(pynvml.nvmlDeviceGetName(handle))
127
147
  }
128
-
129
- yield Observation(check_and_record(getattr(gpu, metric_name, 0)), attributes)
148
+ yield Observation(get_metric_value(handle, metric_name), attributes)
130
149
 
131
150
  except Exception as e:
132
151
  logger.error("Error in GPU metrics collection: %s", e)
152
+
153
+ def _collect_amd_metrics(self, environment, application_name,
154
+ metric_name,
155
+ options: CallbackOptions) -> Iterable[Observation]:
156
+ try:
157
+ import amdsmi
158
+ # Get the number of AMD GPUs
159
+ devices = amdsmi.amdsmi_get_processor_handles()
160
+ mega_bytes = 1024 * 1024
161
+ for device_handle in devices:
162
+
163
+ def get_metric_value(device_handle, metric_name):
164
+ try:
165
+ # pylint: disable=no-else-return
166
+ if metric_name == "temperature":
167
+ # pylint: disable=line-too-long
168
+ return amdsmi.amdsmi_get_temp_metric(device_handle,
169
+ amdsmi.AmdSmiTemperatureType.EDGE,
170
+ amdsmi.AmdSmiTemperatureMetric.CURRENT)
171
+ elif metric_name == "utilization":
172
+ # pylint: disable=line-too-long
173
+ return amdsmi.amdsmi_get_utilization_count(device_handle,
174
+ amdsmi.AmdSmiUtilizationCounterType.COARSE_GRAIN_GFX_ACTIVITY)
175
+ elif metric_name in ["utilization_enc", "utilization_dec"]:
176
+ return 0 # Placeholder if unsupported
177
+ elif metric_name == "fan_speed":
178
+ return amdsmi.amdsmi_get_gpu_fan_speed(device_handle, 0)
179
+ elif metric_name == "memory_available":
180
+ return (amdsmi.amdsmi_get_gpu_memory_total(device_handle) // mega_bytes)
181
+ elif metric_name == "memory_total":
182
+ return (amdsmi.amdsmi_get_gpu_memory_total(device_handle) // mega_bytes)
183
+ elif metric_name == "memory_used":
184
+ return (amdsmi.amdsmi_get_gpu_memory_usage(device_handle) // mega_bytes)
185
+ elif metric_name == "memory_free":
186
+ total_mem = (amdsmi.amdsmi_get_gpu_memory_total(device_handle) // mega_bytes)
187
+ used_mem = (amdsmi.amdsmi_get_gpu_memory_usage(device_handle) // mega_bytes)
188
+ return (total_mem - used_mem)
189
+ elif metric_name == "power_draw":
190
+ # pylint: disable=line-too-long
191
+ return (amdsmi.amdsmi_get_power_info(device_handle)['average_socket_power'] // 1000.0)
192
+ elif metric_name == "power_limit":
193
+ # pylint: disable=line-too-long
194
+ return (amdsmi.amdsmi_get_power_info(device_handle)['power_limit'] // 1000.0)
195
+ except Exception as e:
196
+ logger.error("Error collecting metric %s for AMD GPU %d: %s", metric_name,
197
+ amdsmi.amdsmi_get_xgmi_info(device_handle)['index'], e)
198
+ return 0
199
+
200
+ attributes = {
201
+ TELEMETRY_SDK_NAME: "openlit",
202
+ SemanticConvetion.GEN_AI_APPLICATION_NAME: application_name,
203
+ SemanticConvetion.GEN_AI_ENVIRONMENT: environment,
204
+ # pylint: disable=line-too-long
205
+ SemanticConvetion.GPU_INDEX: amdsmi.amdsmi_get_xgmi_info(device_handle)['index'],
206
+ # pylint: disable=line-too-long
207
+ SemanticConvetion.GPU_UUID: amdsmi.amdsmi_get_gpu_asic_info(device_handle)['market_name'],
208
+ SemanticConvetion.GPU_NAME: amdsmi.amdsmi_get_device_name(device_handle)
209
+ }
210
+ yield Observation(get_metric_value(device_handle, metric_name), attributes)
211
+
212
+ except Exception as e:
213
+ logger.error("Error in AMD GPU metrics collection: %s", e)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: openlit
3
- Version: 1.27.1
3
+ Version: 1.29.0
4
4
  Summary: OpenTelemetry-native Auto instrumentation library for monitoring LLM Applications and GPUs, facilitating the integration of observability into your GenAI-driven projects
5
5
  Home-page: https://github.com/openlit/openlit/tree/main/openlit/python
6
6
  Keywords: OpenTelemetry,otel,otlp,llm,tracing,openai,anthropic,claude,cohere,llm monitoring,observability,monitoring,gpt,Generative AI,chatGPT,gpu
@@ -16,7 +16,6 @@ Classifier: Programming Language :: Python :: 3.13
16
16
  Requires-Dist: anthropic (>=0.21.0,<0.22.0)
17
17
  Requires-Dist: boto3 (>=1.34.0,<2.0.0)
18
18
  Requires-Dist: botocore (>=1.34.0,<2.0.0)
19
- Requires-Dist: gpustat (>=1.1.1,<2.0.0)
20
19
  Requires-Dist: openai (>=1.1.1,<2.0.0)
21
20
  Requires-Dist: opentelemetry-api (>=1.27.0,<2.0.0)
22
21
  Requires-Dist: opentelemetry-exporter-otlp (>=1.27.0,<2.0.0)
@@ -26,6 +25,7 @@ Requires-Dist: pydantic (>=2.0.0,<3.0.0)
26
25
  Requires-Dist: requests (>=2.26.0,<3.0.0)
27
26
  Requires-Dist: schedule (>=1.2.2,<2.0.0)
28
27
  Requires-Dist: tiktoken (>=0.7.0,<0.8.0)
28
+ Requires-Dist: xmltodict (>=0.13.0,<0.14.0)
29
29
  Project-URL: Repository, https://github.com/openlit/openlit/tree/main/openlit/python
30
30
  Description-Content-Type: text/markdown
31
31
 
@@ -65,8 +65,8 @@ This project proudly follows and maintains the [Semantic Conventions](https://gi
65
65
 
66
66
  | LLMs | Vector DBs | Frameworks | GPUs |
67
67
  |--------------------------------------------------------------------------|----------------------------------------------|----------------------------------------------|---------------|
68
- | [✅ OpenAI](https://docs.openlit.io/latest/integrations/openai) | [✅ ChromaDB](https://docs.openlit.io/latest/integrations/chromadb) | [✅ Langchain](https://docs.openlit.io/latest/integrations/langchain) | [✅ NVIDIA GPUs](https://docs.openlit.io/latest/integrations/nvidia-gpu) |
69
- | [✅ Ollama](https://docs.openlit.io/latest/integrations/ollama) | [✅ Pinecone](https://docs.openlit.io/latest/integrations/pinecone) | [✅ LiteLLM](https://docs.openlit.io/latest/integrations/litellm) | |
68
+ | [✅ OpenAI](https://docs.openlit.io/latest/integrations/openai) | [✅ ChromaDB](https://docs.openlit.io/latest/integrations/chromadb) | [✅ Langchain](https://docs.openlit.io/latest/integrations/langchain) | [✅ NVIDIA](https://docs.openlit.io/latest/integrations/nvidia-gpu) |
69
+ | [✅ Ollama](https://docs.openlit.io/latest/integrations/ollama) | [✅ Pinecone](https://docs.openlit.io/latest/integrations/pinecone) | [✅ LiteLLM](https://docs.openlit.io/latest/integrations/litellm) | [✅ AMD](#) |
70
70
  | [✅ Anthropic](https://docs.openlit.io/latest/integrations/anthropic) | [✅ Qdrant](https://docs.openlit.io/latest/integrations/qdrant) | [✅ LlamaIndex](https://docs.openlit.io/latest/integrations/llama-index) | |
71
71
  | [✅ GPT4All](https://docs.openlit.io/latest/integrations/gpt4all) | [✅ Milvus](https://docs.openlit.io/latest/integrations/milvus) | [✅ Haystack](https://docs.openlit.io/latest/integrations/haystack) | |
72
72
  | [✅ Cohere](https://docs.openlit.io/latest/integrations/cohere) | | [✅ EmbedChain](https://docs.openlit.io/latest/integrations/embedchain) | |
@@ -1,5 +1,5 @@
1
1
  openlit/__helpers.py,sha256=H-8uJKs_CP9Y2HL4lz5n0AgN60wwZ675hlWHMDO143A,5936
2
- openlit/__init__.py,sha256=dboS_trymEQ2zubonTuMSRqiFIhy0A9tK0PAktEHc08,19329
2
+ openlit/__init__.py,sha256=y0YPACLr3feXqIQJDcHw6Yc1c43mmc8AgQmUrOTG59U,19317
3
3
  openlit/evals/__init__.py,sha256=nJe99nuLo1b5rf7pt9U9BCdSDedzbVi2Fj96cgl7msM,380
4
4
  openlit/evals/all.py,sha256=oWrue3PotE-rB5WePG3MRYSA-ro6WivkclSHjYlAqGs,7154
5
5
  openlit/evals/bias_detection.py,sha256=mCdsfK7x1vX7S3psC3g641IMlZ-7df3h-V6eiICj5N8,8154
@@ -34,7 +34,7 @@ openlit/instrumentation/google_ai_studio/async_google_ai_studio.py,sha256=20MHsp
34
34
  openlit/instrumentation/google_ai_studio/google_ai_studio.py,sha256=vIJjzl5Fkgsf3vfaqmxhtSFvOpXK-wGC-JFhEXGP50M,13636
35
35
  openlit/instrumentation/gpt4all/__init__.py,sha256=-59CP2B3-HGZJ_vC-fI9Dt-0BuQXRhSCWCjnaGeU15Q,1802
36
36
  openlit/instrumentation/gpt4all/gpt4all.py,sha256=dbxqZeuTrv_y6wyDOIEmC8-Dc4iCGgLpj3l5JiodLMI,18787
37
- openlit/instrumentation/gpu/__init__.py,sha256=Dj2MLar0DB20-t6W3pfR-3jfR_mwg4SYwhzIrH_n9sU,5596
37
+ openlit/instrumentation/gpu/__init__.py,sha256=niMQS-tmVcHSFPGC39JWOnQK306Ve6GhsOUCMneOC88,11076
38
38
  openlit/instrumentation/groq/__init__.py,sha256=uW_0G6HSanQyK2dIXYhzR604pDiyPQfybzc37DsfSew,1911
39
39
  openlit/instrumentation/groq/async_groq.py,sha256=myob-d9V66YiNmkFd9rtmMaXjlLiSMVHi_US16L-rZw,20011
40
40
  openlit/instrumentation/groq/groq.py,sha256=m4gFPbYzjUUIgjXZ0Alu2Zy1HcO5takCFA2XFnkcGVo,19975
@@ -71,7 +71,7 @@ openlit/instrumentation/vllm/vllm.py,sha256=lDzM7F5pgxvh8nKL0dcKB4TD0Mc9wXOWeXOs
71
71
  openlit/otel/metrics.py,sha256=FYAk4eBAmNtFKUIp4hbRbpdq4LME6MapyCQOIeuhmEg,4337
72
72
  openlit/otel/tracing.py,sha256=2kSj7n7uXSkRegcGFDC8IbnDOxqWTA8dGODs__Yn_yA,3719
73
73
  openlit/semcov/__init__.py,sha256=xPsw1aPonDSGYVuga-ZdoGt4yyA16wNFi5AEc7_xIrQ,8114
74
- openlit-1.27.1.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
75
- openlit-1.27.1.dist-info/METADATA,sha256=A5el-uDDg_yZPRFEuYiNrpl1vTJX7tT2ETY6rio1GEA,20790
76
- openlit-1.27.1.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
77
- openlit-1.27.1.dist-info/RECORD,,
74
+ openlit-1.29.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
75
+ openlit-1.29.0.dist-info/METADATA,sha256=8uukJaCeePRMe0rFpdEQWACsNdD0SakudmVc6wPxkU0,20806
76
+ openlit-1.29.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
77
+ openlit-1.29.0.dist-info/RECORD,,