bf54db0bcdfbf2f55773d395148de174399d626c
[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     def attach(self, duration):
56         """
57         Attach events to BPF.
58
59         :param duration: Trial duration.
60         :type duration: int
61         """
62         try:
63             for event in self.events:
64                 self.obj.attach_perf_event(
65                     ev_type=event[u"type"],
66                     ev_config=event[u"name"],
67                     fn_name=event[u"target"],
68                     sample_period=duration
69                 )
70         except AttributeError:
71             getLogger("console_stderr").error(u"Could not attach BPF events!")
72             sys.exit(Constants.err_linux_attach)
73
74     def detach(self):
75         """
76         Dettach events from BPF.
77         """
78         try:
79             for event in self.events:
80                 self.obj.detach_perf_event(
81                     ev_type=event[u"type"],
82                     ev_config=event[u"name"]
83                 )
84         except AttributeError:
85             getLogger("console_stderr").error(u"Could not detach BPF events!")
86             sys.exit(Constants.err_linux_detach)
87
88     def fetch_data(self):
89         """
90         Fetch data by invoking API calls to BPF.
91         """
92         self.serializer.create(metrics=self.metrics)
93
94         max_len = {"cpu": 3, "pid": 3, "name": 4, "value": 5}
95         text = ""
96         table_name = ""
97         item_list = []
98
99         for _, metric_list in self.metrics.items():
100             for metric in metric_list:
101                 for (key, val) in self.obj.get_table(metric[u"name"]).items():
102                     item = dict()
103                     labels = dict()
104                     item[u"name"] = metric[u"name"]
105                     item[u"value"] = val.value
106                     for label in metric[u"labelnames"]:
107                         labels[label] = getattr(key, label)
108                     item[u"labels"] = labels
109                     item[u'labels'][u'name'] = \
110                         item[u'labels'][u'name'].decode(u'utf-8')
111                     if item[u"labels"][u"name"] == u"python3":
112                         continue
113                     if len(str(item[u'labels'][u'cpu'])) > max_len["cpu"]:
114                         max_len["cpu"]= len(str(item[u'labels'][u'cpu']))
115                     if len(str(item[u'labels'][u'pid'])) > max_len[u"pid"]:
116                         max_len[u"pid"] = len(str(item[u'labels'][u'pid']))
117                     if len(str(item[u'labels'][u'name'])) > max_len[u"name"]:
118                         max_len[u"name"] = len(str(item[u'labels'][u'name']))
119                     if len(str(item[u'value'])) > max_len[u"value"]:
120                         max_len[u"value"] = len(str(item[u'value']))
121
122                     self.api_replies_list.append(item)
123                     item_list.append(item)
124
125         item_list = sorted(item_list, key=lambda x: x['labels']['cpu'])
126         item_list = sorted(item_list, key=lambda x: x['name'])
127
128         for it in item_list:
129             if table_name != it[u"name"]:
130                 table_name = it[u"name"]
131                 text += f"\n==={table_name}===\n" \
132                         f"cpu {u' ' * (max_len[u'cpu'] - 3)} " \
133                         f"pid {u' ' * (max_len[u'pid'] - 3)} " \
134                         f"name {u' ' * (max_len[u'name'] - 4)} " \
135                         f"value {u' ' * (max_len[u'value'] - 5)}\n"
136             text += (
137                 f"""{str(it[u'labels'][u'cpu']) + u' ' * 
138                      (max_len[u"cpu"] - len(str(it[u'labels'][u'cpu'])))}  """
139                 f"""{str(it[u'labels'][u'pid']) + u' ' * 
140                      (max_len[u"pid"] - len(str(it[u'labels'][u'pid'])))}  """
141                 f"""{str(it[u'labels'][u'name']) + u' ' * 
142                      (max_len[u"name"] - len(str(it[u'labels'][u'name'])))}  """
143                 f"""{str(it[u'value']) + u' ' * 
144                      (max_len[u"value"] - len(str(it[u'value'])))}\n""")
145         getLogger(u"console_stdout").info(text)
146
147     def process_data(self):
148         """
149         Post process API replies.
150         """
151         for item in self.api_replies_list:
152             self.serializer.serialize(
153                 metric=item[u"name"], labels=item[u"labels"], item=item
154             )