telemetry: linux telemetry with perf-stat
[csit.git] / resources / tools / telemetry / bundle_bpf.py
1 # Copyright (c) 2022 Cisco and/or its affiliates.
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at:
5 #
6 #     http://www.apache.org/licenses/LICENSE-2.0
7 #
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
13
14 """BPF performance bundle."""
15
16 from logging import getLogger
17 import sys
18
19 from bcc import BPF
20 from .constants import Constants
21
22
23 class BundleBpf:
24     """
25     Creates a BPF object. This is the main object for defining a BPF program,
26     and interacting with its output.
27
28     Syntax: BPF({text=BPF_program | src_file=filename}
29                 [, usdt_contexts=[USDT_object, ...]]
30                 [, cflags=[arg1, ...]] [, debug=int]
31             )
32
33     Exactly one of text or src_file must be supplied (not both).
34     """
35     def __init__(self, program, serializer, hook):
36         """Initialize Bundle BPF Perf event class.
37
38         :param program: BPF C code.
39         :param serializer: Metric serializer.
40         :param hook: Process ID.
41         :type program: dict
42         :type serializer: Serializer
43         :type hook: int
44         """
45         self.obj = None
46         self.code = program[u"code"]
47         self.metrics = program[u"metrics"]
48         self.events = program[u"events"]
49         self.api_replies_list = list()
50         self.serializer = serializer
51         self.hook = hook
52
53         self.obj = BPF(text=self.code)
54
55
56     def attach(self, sample_period):
57         """
58         Attach events to BPF.
59
60         :param sample_period:  A "sampling" event is one that generates
61         an overflow notification every N events, where N is given by
62         sample_period.
63         :type sample_period: int
64         """
65         try:
66             for event in self.events:
67                 self.obj.attach_perf_event(
68                     ev_type=event[u"type"],
69                     ev_config=event[u"name"],
70                     fn_name=event[u"target"],
71                     sample_period=sample_period
72                 )
73         except AttributeError:
74             getLogger("console_stderr").error(f"Could not attach BPF event: "
75                                               f"{event[u'name']}")
76             sys.exit(Constants.err_linux_attach)
77
78     def detach(self):
79         """
80         Detach events from BPF.
81         """
82         try:
83             for event in self.events:
84                 self.obj.detach_perf_event(
85                     ev_type=event[u"type"],
86                     ev_config=event[u"name"]
87                 )
88         except AttributeError:
89             getLogger("console_stderr").error(u"Could not detach BPF events!")
90             sys.exit(Constants.err_linux_detach)
91
92     def fetch_data(self):
93         """
94         Fetch data by invoking API calls to BPF.
95         """
96         self.serializer.create(metrics=self.metrics)
97
98         max_len = {"cpu": 3, "pid": 3, "name": 4, "value": 5}
99         text = ""
100         table_name = ""
101         item_list = []
102
103         for _, metric_list in self.metrics.items():
104             for metric in metric_list:
105                 if table_name != metric[u"name"]:
106                     table_name = metric[u"name"]
107                     text += f"{table_name}\n"
108                 for (key, val) in self.obj.get_table(metric[u"name"]).items():
109                     item = dict()
110                     labels = dict()
111                     item[u"name"] = metric[u"name"]
112                     item[u"value"] = val.value
113                     for label in metric[u"labelnames"]:
114                         labels[label] = getattr(key, label)
115                     item[u"labels"] = labels
116                     item[u'labels'][u'name'] = \
117                         item[u'labels'][u'name'].decode(u'utf-8')
118                     if item[u"labels"][u"name"] == u"python3":
119                         continue
120                     if len(str(item[u'labels'][u'cpu'])) > max_len["cpu"]:
121                         max_len["cpu"] = len(str(item[u'labels'][u'cpu']))
122                     if len(str(item[u'labels'][u'pid'])) > max_len[u"pid"]:
123                         max_len[u"pid"] = len(str(item[u'labels'][u'pid']))
124                     if len(str(item[u'labels'][u'name'])) > max_len[u"name"]:
125                         max_len[u"name"] = len(str(item[u'labels'][u'name']))
126                     if len(str(item[u'value'])) > max_len[u"value"]:
127                         max_len[u"value"] = len(str(item[u'value']))
128
129                     self.api_replies_list.append(item)
130                     item_list.append(item)
131
132         item_list = sorted(item_list, key=lambda x: x['labels']['cpu'])
133         item_list = sorted(item_list, key=lambda x: x['name'])
134
135         for itl in item_list:
136             if table_name != itl[u"name"]:
137                 table_name = itl[u"name"]
138                 text += f"\n==={table_name}===\n" \
139                         f"cpu {u' ' * (max_len[u'cpu'] - 3)} " \
140                         f"pid {u' ' * (max_len[u'pid'] - 3)} " \
141                         f"name {u' ' * (max_len[u'name'] - 4)} " \
142                         f"value {u' ' * (max_len[u'value'] - 5)}\n"
143             text += (
144                 f"""{str(itl[u'labels'][u'cpu']) + u' ' *
145                      (max_len[u"cpu"] - len(str(itl[u'labels'][u'cpu'])))} """
146                 f"""{str(itl[u'labels'][u'pid']) + u' ' *
147                      (max_len[u"pid"] - len(str(itl[u'labels'][u'pid'])))} """
148                 f"""{str(itl[u'labels'][u'name']) + u' ' *
149                      (max_len[u"name"] - len(str(itl[u'labels'][u'name'])))} """
150                 f"""{str(itl[u'value']) + u' ' *
151                      (max_len[u"value"] - len(str(itl[u'value'])))}\n""")
152         getLogger(u"console_stdout").info(text)
153
154     def process_data(self):
155         """
156         Post process API replies.
157         """
158         for item in self.api_replies_list:
159             self.serializer.serialize(
160                 metric=item[u"name"], labels=item[u"labels"], item=item
161             )