Skip to content

Commit df82bb0

Browse files
Merge pull request #97 from bugout-dev/add-metrics
Add init metrics version.
2 parents e0a1715 + 547fcee commit df82bb0

4 files changed

Lines changed: 251 additions & 6 deletions

File tree

python/humbug/report.py

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from dataclasses import dataclass, field
88
from enum import Enum
99
from functools import wraps
10+
import json
1011

1112
import logging
1213
import os
@@ -17,14 +18,30 @@
1718
from typing import Any, Callable, Dict, List, Optional
1819
import uuid
1920

20-
import requests
21+
import requests # type: ignore
2122

23+
24+
from . import utils
2225
from .consent import HumbugConsent
2326
from .system_information import (
2427
SystemInformation,
2528
generate as generate_system_information,
2629
)
2730

31+
psutil = None
32+
33+
try:
34+
import psutil # type: ignore
35+
except ImportError:
36+
pass
37+
38+
GPUtil = None
39+
40+
try:
41+
import GPUtil # type: ignore
42+
except ImportError:
43+
pass
44+
2845

2946
DEFAULT_URL = "https://spire.bugout.dev"
3047

@@ -96,6 +113,9 @@ def __init__(
96113

97114
self.blacklist_fn = blacklist_fn
98115

116+
self.psutil_exists = psutil is not None
117+
self.gputil_exists = GPUtil is not None
118+
99119
def wait(self) -> None:
100120
concurrent.futures.wait(
101121
self.report_futures, timeout=float(self.timeout_seconds)
@@ -524,6 +544,65 @@ def showtraceback(*args, **kwargs):
524544
ipython_shell.showtraceback = showtraceback
525545
self.setup_excepthook(publish=True, tags=tags)
526546

547+
def metrics_report(
548+
self,
549+
cpu: bool = True,
550+
gpu: bool = True,
551+
memory: bool = True,
552+
disk: bool = True,
553+
network: bool = True,
554+
open_files_flag: bool = True,
555+
num_threads_flag: bool = True,
556+
processes_flag: bool = True,
557+
tags: Optional[List[str]] = None,
558+
publish: bool = False,
559+
wait: bool = False,
560+
) -> Report:
561+
title = "Metrics report"
562+
563+
metrics: Dict[str, Any] = {}
564+
565+
if self.gputil_exists:
566+
metrics["gpu"] = utils.get_gpu_metrics()
567+
568+
if self.psutil_exists:
569+
if cpu:
570+
metrics["cpu"] = utils.get_cpu_metrics()
571+
572+
if memory:
573+
metrics["memory"] = utils.get_memory_metrics()
574+
575+
if disk:
576+
metrics["disk"] = utils.get_disk_metrics()
577+
578+
if network:
579+
metrics["network"] = utils.get_network_metrics()
580+
581+
if open_files_flag:
582+
metrics["open_files"] = utils.get_open_files_metrics()
583+
584+
if num_threads_flag:
585+
metrics["num_threads"] = utils.get_thread_metrics()
586+
587+
if processes_flag:
588+
metrics["processes"] = utils.get_processes_metrics()
589+
590+
tags = tags if tags is not None else []
591+
592+
tags.append("type:metrics")
593+
tags.extend(self.system_tags())
594+
595+
report = Report(
596+
title=title,
597+
tags=tags,
598+
content=f"```\n{json.dumps(metrics, indent=4, sort_keys=True)}\n```",
599+
)
600+
601+
if publish:
602+
self.publish(report, wait=wait)
603+
604+
return report
605+
527606

528607
class Reporter(HumbugReporter):
529608
"""

python/humbug/utils.py

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
import os
2+
import types
3+
import sys
4+
5+
6+
def get_cpu_metrics():
7+
cpu_metrics = {}
8+
9+
import psutil
10+
11+
cpu_metrics["cpu_count"] = psutil.cpu_count()
12+
cpu_metrics["cpu_count_logical"] = psutil.cpu_count(logical=True)
13+
cpu_metrics["cpu_count_physical"] = psutil.cpu_count(logical=False)
14+
cpu_metrics["cpu_percent"] = psutil.cpu_percent()
15+
cpu_metrics["cpu_percent_per_core"] = psutil.cpu_percent(percpu=True)
16+
current_pid = os.getpid()
17+
current_process = psutil.Process(current_pid)
18+
cpu_metrics["cpu_load_by_process"] = current_process.cpu_percent()
19+
return cpu_metrics
20+
21+
22+
def get_gpu_metrics():
23+
gpu_metrics = {}
24+
25+
import GPUtil
26+
27+
gpus = GPUtil.getGPUs()
28+
for gpu_unit in gpus:
29+
gpu_id = gpu_unit.id
30+
gpu_metrics[f"gpu_{gpu_id}_uuid"] = gpu_unit.uuid
31+
gpu_metrics[f"gpu_{gpu_id}_load"] = gpu_unit.load
32+
gpu_metrics[f"gpu_{gpu_id}_memory_util_%"] = gpu_unit.memoryUtil
33+
gpu_metrics[f"gpu_{gpu_id}_memory_total_MB"] = gpu_unit.memoryTotal
34+
gpu_metrics[f"gpu_{gpu_id}_memory_used_MB"] = gpu_unit.memoryUsed
35+
gpu_metrics[f"gpu_{gpu_id}_memory_free_MB"] = gpu_unit.memoryFree
36+
gpu_metrics[f"gpu_{gpu_id}_driver"] = gpu_unit.driver
37+
gpu_metrics[f"gpu_{gpu_id}_name"] = gpu_unit.name
38+
gpu_metrics[f"gpu_{gpu_id}_serial"] = gpu_unit.serial
39+
gpu_metrics[f"gpu_{gpu_id}_display_mode"] = gpu_unit.display_mode
40+
gpu_metrics[f"gpu_{gpu_id}_display_active"] = gpu_unit.display_active
41+
gpu_metrics[f"gpu_{gpu_id}_temperature_C"] = gpu_unit.temperature
42+
gpu_metrics["gpu_count"] = len(gpus)
43+
return gpu_metrics
44+
45+
46+
def get_memory_metrics():
47+
memory_metrics = {}
48+
49+
import psutil
50+
51+
system_memory = psutil.virtual_memory()
52+
memory_metrics["total_MB"] = round(system_memory.total / 1024 / 1024, 2)
53+
memory_metrics["available_MB"] = round(system_memory.available / 1024 / 1024, 2)
54+
memory_metrics["percent_%"] = system_memory.percent
55+
memory_metrics["used_MB"] = round(system_memory.used / 1024 / 1024, 2)
56+
memory_metrics["free_MB"] = round(system_memory.free / 1024 / 1024, 2)
57+
swap = psutil.swap_memory()
58+
memory_metrics["swap_total_MB"] = round(swap.total / 1024 / 1024, 2)
59+
memory_metrics["swap_used_MB"] = round(swap.used / 1024 / 1024, 2)
60+
memory_metrics["swap_free_MB"] = round(swap.free / 1024 / 1024, 2)
61+
memory_metrics["swap_percent_%"] = swap.percent
62+
return memory_metrics
63+
64+
65+
def get_disk_metrics():
66+
disk_metrics = {}
67+
68+
import psutil
69+
70+
for disk_partition in psutil.disk_partitions():
71+
disk_usage = psutil.disk_usage(disk_partition.mountpoint)
72+
disk_metrics[str(disk_partition.mountpoint)] = {}
73+
disk_metrics[str(disk_partition.mountpoint)]["total_MB"] = round(
74+
disk_usage.total / 1024 / 1024, 2
75+
) # in MB
76+
disk_metrics[str(disk_partition.mountpoint)]["used_MB"] = round(
77+
disk_usage.used / 1024 / 1024, 2
78+
) # in MB
79+
disk_metrics[str(disk_partition.mountpoint)]["free_MB"] = round(
80+
disk_usage.free / 1024 / 1024, 2
81+
) # in MB
82+
disk_metrics[str(disk_partition.mountpoint)]["percent_%"] = disk_usage.percent
83+
84+
disk_io = psutil.disk_io_counters()
85+
disk_metrics["read_count"] = disk_io.read_count
86+
disk_metrics["write_count"] = disk_io.write_count
87+
disk_metrics["read_MB"] = round(disk_io.read_bytes / 1024 / 1024, 2) # in MB
88+
disk_metrics["write_MB"] = round(disk_io.write_bytes / 1024 / 1024, 2) # in MB
89+
disk_metrics["read_time_s"] = disk_io.read_time / 1000 # in seconds
90+
disk_metrics["write_time_s"] = disk_io.write_time / 1000 # in seconds
91+
disk_metrics["read_merged_count"] = disk_io.read_merged_count
92+
disk_metrics["write_merged_count"] = disk_io.write_merged_count
93+
disk_metrics["busy_time_s"] = round(disk_io.busy_time, 2) / 1000 # in seconds
94+
return disk_metrics
95+
96+
97+
def get_network_metrics():
98+
network_metrics = {}
99+
100+
import psutil
101+
102+
network_io = psutil.net_io_counters()
103+
network_metrics["MB_sent"] = round(network_io.bytes_sent / 1024 / 1024, 2) # in MB
104+
network_metrics["MB_recv"] = round(network_io.bytes_recv / 1024 / 1024, 2) # in MB
105+
network_metrics["packets_sent"] = network_io.packets_sent
106+
network_metrics["packets_recv"] = network_io.packets_recv
107+
network_metrics["errin"] = network_io.errin
108+
network_metrics["errout"] = network_io.errout
109+
network_metrics["dropin"] = network_io.dropin
110+
network_metrics["dropout"] = network_io.dropout
111+
112+
return network_metrics
113+
114+
115+
def get_open_files_metrics():
116+
files_metrics = {}
117+
118+
import psutil
119+
120+
open_files = psutil.Process().open_files()
121+
files_metrics["total"] = len(open_files)
122+
123+
return files_metrics
124+
125+
126+
def get_thread_metrics():
127+
threads_metrics = {}
128+
129+
import psutil
130+
131+
threads = psutil.Process().threads()
132+
threads_metrics["total"] = len(threads)
133+
134+
return threads_metrics
135+
136+
137+
def get_processes_metrics():
138+
process_metrics = {}
139+
140+
import psutil
141+
142+
# get all processes memory usage
143+
144+
processes = psutil.process_iter()
145+
146+
# Iterate through each process and get its CPU and memory utilization
147+
for process in processes:
148+
cpu_percent = process.cpu_percent()
149+
mem_info = process.memory_info()
150+
if process.name() not in process_metrics:
151+
process_metrics[f"{process.name()}"] = {
152+
"cpu_percent": cpu_percent,
153+
"memory_MB": round(mem_info.rss / 1024 / 1024, 2), # in MB
154+
"amount_of_processes": 1,
155+
}
156+
else:
157+
process_metrics[f"{process.name()}"]["cpu_percent"] += cpu_percent
158+
process_metrics[f"{process.name()}"]["memory_MB"] += round(
159+
mem_info.rss / 1024 / 1024, 2
160+
)
161+
process_metrics[f"{process.name()}"]["amount_of_processes"] += 1
162+
163+
return process_metrics

python/mypy.ini

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[mypy]
2+
3+
[mypy-GPUtil.*]
4+
ignore_missing_imports = True

python/setup.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,10 @@
66

77
setup(
88
name="humbug",
9-
version="0.2.8",
9+
version="0.3.0",
1010
packages=find_packages(),
1111
package_data={"humbug": ["py.typed"]},
12-
install_requires=[
13-
"requests",
14-
"dataclasses; python_version=='3.6'"
15-
],
12+
install_requires=["requests", "dataclasses; python_version=='3.6'"],
1613
extras_require={
1714
"dev": [
1815
"black",
@@ -21,8 +18,10 @@
2118
"types-pkg_resources",
2219
"types-requests",
2320
"types-dataclasses",
21+
"types-psutil",
2422
],
2523
"distribute": ["setuptools", "twine", "wheel"],
24+
"profile": ["psutil", "GPUtil", "types-psutil"],
2625
},
2726
description="Humbug: Do you build developer tools? Humbug helps you know your users.",
2827
long_description=long_description,

0 commit comments

Comments
 (0)