openlit 1.27.1__py3-none-any.whl → 1.28.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,208 @@
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
118
134
 
119
- for gpu in gpu_stats.gpus:
120
135
  attributes = {
121
136
  TELEMETRY_SDK_NAME: "openlit",
122
137
  SemanticConvetion.GEN_AI_APPLICATION_NAME: application_name,
123
138
  SemanticConvetion.GEN_AI_ENVIRONMENT: environment,
124
- SemanticConvetion.GPU_INDEX: gpu.index,
125
- SemanticConvetion.GPU_UUID: gpu.uuid,
126
- SemanticConvetion.GPU_NAME: gpu.name,
139
+ SemanticConvetion.GPU_INDEX: str(gpu_index),
140
+ SemanticConvetion.GPU_UUID: pynvml.nvmlDeviceGetUUID(handle).decode('utf-8'),
141
+ SemanticConvetion.GPU_NAME: pynvml.nvmlDeviceGetName(handle).decode('utf-8')
127
142
  }
128
-
129
- yield Observation(check_and_record(getattr(gpu, metric_name, 0)), attributes)
143
+ yield Observation(get_metric_value(handle, metric_name), attributes)
130
144
 
131
145
  except Exception as e:
132
146
  logger.error("Error in GPU metrics collection: %s", e)
147
+
148
+ def _collect_amd_metrics(self, environment, application_name,
149
+ metric_name,
150
+ options: CallbackOptions) -> Iterable[Observation]:
151
+ try:
152
+ import amdsmi
153
+ # Get the number of AMD GPUs
154
+ devices = amdsmi.amdsmi_get_processor_handles()
155
+ mega_bytes = 1024 * 1024
156
+ for device_handle in devices:
157
+
158
+ def get_metric_value(device_handle, metric_name):
159
+ try:
160
+ # pylint: disable=no-else-return
161
+ if metric_name == "temperature":
162
+ # pylint: disable=line-too-long
163
+ return amdsmi.amdsmi_get_temp_metric(device_handle,
164
+ amdsmi.AmdSmiTemperatureType.EDGE,
165
+ amdsmi.AmdSmiTemperatureMetric.CURRENT)
166
+ elif metric_name == "utilization":
167
+ # pylint: disable=line-too-long
168
+ return amdsmi.amdsmi_get_utilization_count(device_handle,
169
+ amdsmi.AmdSmiUtilizationCounterType.COARSE_GRAIN_GFX_ACTIVITY)
170
+ elif metric_name in ["utilization_enc", "utilization_dec"]:
171
+ return 0 # Placeholder if unsupported
172
+ elif metric_name == "fan_speed":
173
+ return amdsmi.amdsmi_get_gpu_fan_speed(device_handle, 0)
174
+ elif metric_name == "memory_available":
175
+ return (amdsmi.amdsmi_get_gpu_memory_total(device_handle) // mega_bytes)
176
+ elif metric_name == "memory_total":
177
+ return (amdsmi.amdsmi_get_gpu_memory_total(device_handle) // mega_bytes)
178
+ elif metric_name == "memory_used":
179
+ return (amdsmi.amdsmi_get_gpu_memory_usage(device_handle) // mega_bytes)
180
+ elif metric_name == "memory_free":
181
+ total_mem = (amdsmi.amdsmi_get_gpu_memory_total(device_handle) // mega_bytes)
182
+ used_mem = (amdsmi.amdsmi_get_gpu_memory_usage(device_handle) // mega_bytes)
183
+ return (total_mem - used_mem)
184
+ elif metric_name == "power_draw":
185
+ # pylint: disable=line-too-long
186
+ return (amdsmi.amdsmi_get_power_info(device_handle)['average_socket_power'] // 1000.0)
187
+ elif metric_name == "power_limit":
188
+ # pylint: disable=line-too-long
189
+ return (amdsmi.amdsmi_get_power_info(device_handle)['power_limit'] // 1000.0)
190
+ except Exception as e:
191
+ logger.error("Error collecting metric %s for AMD GPU %d: %s", metric_name,
192
+ amdsmi.amdsmi_get_xgmi_info(device_handle)['index'], e)
193
+ return 0
194
+
195
+ attributes = {
196
+ TELEMETRY_SDK_NAME: "openlit",
197
+ SemanticConvetion.GEN_AI_APPLICATION_NAME: application_name,
198
+ SemanticConvetion.GEN_AI_ENVIRONMENT: environment,
199
+ # pylint: disable=line-too-long
200
+ SemanticConvetion.GPU_INDEX: amdsmi.amdsmi_get_xgmi_info(device_handle)['index'],
201
+ # pylint: disable=line-too-long
202
+ SemanticConvetion.GPU_UUID: amdsmi.amdsmi_get_gpu_asic_info(device_handle)['market_name'],
203
+ SemanticConvetion.GPU_NAME: amdsmi.amdsmi_get_device_name(device_handle)
204
+ }
205
+ yield Observation(get_metric_value(device_handle, metric_name), attributes)
206
+
207
+ except Exception as e:
208
+ 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.28.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=wahEF66rCRVT8XafJkif6Yac8JcIMTMMm9fN1X2lwhI,10882
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.28.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
75
+ openlit-1.28.0.dist-info/METADATA,sha256=egvLD1EGcwE-1eRjIErX_kIqhRYTXRO6hm8EtwYOR0M,20806
76
+ openlit-1.28.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
77
+ openlit-1.28.0.dist-info/RECORD,,