Passed
Pull Request — master (#9)
by
unknown
02:47
created

build.main   B

Complexity

Total Complexity 44

Size/Duplication

Total Lines 230
Duplicated Lines 0 %

Test Coverage

Coverage 73.88%

Importance

Changes 0
Metric Value
wmc 44
eloc 153
dl 0
loc 230
ccs 99
cts 134
cp 0.7388
rs 8.8798
c 0
b 0
f 0

19 Methods

Rating   Name   Duplication   Size   Complexity  
B Main.table_stats_by_dpid_table_id() 0 17 6
A Main.shutdown() 0 2 1
A Main.setup() 0 9 1
A Main.execute() 0 2 1
A Main.flow_stats() 0 10 2
A Main.flow_stats_by_dpid_flow_id() 0 16 4
A Main.table_stats() 0 12 2
A Main.flow_from_id() 0 3 1
A Main.handle_stats_received() 0 5 2
A Main.bytes_count() 0 18 3
A Main.on_table_stats_received() 0 4 1
A Main.bytes_count_per_flow() 0 8 1
A Main.handle_table_stats_reply_received() 0 7 3
A Main.packet_count() 0 18 3
A Main.handle_table_stats_received() 0 4 1
A Main.handle_stats_reply_received() 0 3 1
A Main.on_stats_received() 0 4 1
A Main.packet_count_per_flow() 0 8 1
C Main.flows_counters() 0 37 9

How to fix   Complexity   

Complexity

Complex classes like build.main often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
"""Main module of amlight/kytos_stats Kytos Network Application.
2
3
This NApp does operations with flows not covered by Kytos itself.
4
"""
5
# pylint: disable=too-many-return-statements,too-many-instance-attributes
6
# pylint: disable=too-many-arguments,too-many-branches,too-many-statements
7
8 1
from kytos.core import KytosNApp, log, rest
9 1
from kytos.core.helpers import listen_to
10 1
from kytos.core.rest_api import HTTPException, JSONResponse, Request
11
12
13
# pylint: disable=too-many-public-methods
14 1
class Main(KytosNApp):
15
    """Main class of amlight/kytos_stats NApp.
16
    This class is the entry point for this napp.
17
    """
18
19 1
    def setup(self):
20
        """Replace the '__init__' method for the KytosNApp subclass.
21
        The setup method is automatically called by the controller when your
22
        application is loaded.
23
        So, if you have any setup routine, insert it here.
24
        """
25 1
        log.info('Starting Kytos/Amlight flow manager')
26 1
        self.flows_stats_dict = {}
27 1
        self.tables_stats_dict = {}
28
29 1
    def execute(self):
30
        """This method is executed right after the setup method execution.
31
        You can also use this method in loop mode if you add to the above setup
32
        method a line like the following example:
33
            self.execute_as_loop(30)  # 30-second interval.
34
        """
35
36 1
    def shutdown(self):
37
        """This method is executed when your napp is unloaded.
38
        If you have some cleanup procedure, insert it here.
39
        """
40
41 1
    def flow_from_id(self, flow_id):
42
        """Flow from given flow_id."""
43 1
        return self.flows_stats_dict.get(flow_id)
44
45 1
    def flow_stats_by_dpid_flow_id(self, dpids):
46
        """ Auxiliar funcion for v1/flow/stats endpoint implementation.
47
        """
48 1
        flow_stats_by_id = {}
49 1
        flows_stats_dict_copy = self.flows_stats_dict.copy()
50 1
        for flow_id, flow in flows_stats_dict_copy.items():
51
            dpid = flow.switch.dpid
52
            if dpid in dpids:
53
                if dpid not in flow_stats_by_id:
54
                    flow_stats_by_id[dpid] = {}
55
                info_flow_as_dict = flow.stats.as_dict()
56
                info_flow_as_dict.update({"cookie": flow.cookie})
57
                info_flow_as_dict.update({"priority": flow.priority})
58
                info_flow_as_dict.update({"match": flow.match.as_dict()})
59
                flow_stats_by_id[dpid].update({flow_id: info_flow_as_dict})
60 1
        return flow_stats_by_id
61
62 1
    def table_stats_by_dpid_table_id(self, dpids, table_ids):
63
        """ Auxiliar funcion for v1/table/stats endpoint implementation.
64
        """
65
        table_stats_by_id = {}
66
        tables_stats_dict_copy = self.tables_stats_dict.copy()
67
        for dpid, tables in tables_stats_dict_copy.items():
68
            if dpid not in dpids:
69
                continue
70
            table_stats_by_id[dpid] = {}
71
            if len(table_ids) == 0:
72
                table_ids = list(tables.keys())
73
            for table_id, table in tables.items():
74
                if table_id in table_ids:
75
                    table_dict = table.as_dict()
76
                    del table_dict['switch']
77
                    table_stats_by_id[dpid][table_id] = table_dict
78
        return table_stats_by_id
79
80 1
    @rest('v1/flow/stats')
81 1
    def flow_stats(self, request: Request) -> JSONResponse:
82
        """Return the flows stats by dpid.
83
        Return the stats of all flows if dpid is None
84
        """
85 1
        dpids = request.query_params.getlist("dpid")
86 1
        if len(dpids) == 0:
87 1
            dpids = [sw.dpid for sw in self.controller.switches.values()]
88 1
        flow_stats_by_id = self.flow_stats_by_dpid_flow_id(dpids)
89 1
        return JSONResponse(flow_stats_by_id)
90
91 1
    @rest('v1/table/stats')
92 1
    def table_stats(self, request: Request) -> JSONResponse:
93
        """Return the table stats by dpid,
94
        and optionally by table_id.
95
        """
96 1
        dpids = request.query_params.getlist("dpid")
97 1
        if len(dpids) == 0:
98 1
            dpids = [sw.dpid for sw in self.controller.switches.values()]
99 1
        table_ids = request.query_params.getlist("table")
100 1
        table_ids = list(map(int, table_ids))
101 1
        table_stats_dpid = self.table_stats_by_dpid_table_id(dpids, table_ids)
102 1
        return JSONResponse(table_stats_dpid)
103
104 1
    @rest('v1/packet_count/{flow_id}')
105 1
    def packet_count(self, request: Request) -> JSONResponse:
106
        """Packet count of an specific flow."""
107 1
        flow_id = request.path_params["flow_id"]
108 1
        flow = self.flow_from_id(flow_id)
109 1
        if flow is None:
110 1
            raise HTTPException(404, detail="Flow does not exist")
111 1
        try:
112 1
            packet_per_second = \
113
                flow.stats.packet_count / flow.stats.duration_sec
114
        except ZeroDivisionError:
115
            packet_per_second = 0
116 1
        packet_stats = {
117
            'flow_id': flow_id,
118
            'packet_counter': flow.stats.packet_count,
119
            'packet_per_second': packet_per_second
120
            }
121 1
        return JSONResponse(packet_stats)
122
123 1
    @rest('v1/bytes_count/{flow_id}')
124 1
    def bytes_count(self, request: Request) -> JSONResponse:
125
        """Bytes count of an specific flow."""
126 1
        flow_id = request.path_params["flow_id"]
127 1
        flow = self.flow_from_id(flow_id)
128 1
        if flow is None:
129 1
            raise HTTPException(404, detail="Flow does not exist")
130 1
        try:
131 1
            bits_per_second = \
132
                flow.stats.byte_count * 8 / flow.stats.duration_sec
133
        except ZeroDivisionError:
134
            bits_per_second = 0
135 1
        bytes_stats = {
136
            'flow_id': flow_id,
137
            'bytes_counter': flow.stats.byte_count,
138
            'bits_per_second': bits_per_second
139
            }
140 1
        return JSONResponse(bytes_stats)
141
142 1
    @rest('v1/packet_count/per_flow/{dpid}')
143 1
    def packet_count_per_flow(self, request: Request) -> JSONResponse:
144
        """Per flow packet count."""
145 1
        dpid = request.path_params["dpid"]
146 1
        return self.flows_counters('packet_count',
147
                                   dpid,
148
                                   counter='packet_counter',
149
                                   rate='packet_per_second')
150
151 1
    @rest('v1/bytes_count/per_flow/{dpid}')
152 1
    def bytes_count_per_flow(self, request: Request) -> JSONResponse:
153
        """Per flow bytes count."""
154 1
        dpid = request.path_params["dpid"]
155 1
        return self.flows_counters('byte_count',
156
                                   dpid,
157
                                   counter='bytes_counter',
158
                                   rate='bits_per_second')
159
160 1
    def flows_counters(self, field, dpid, counter=None, rate=None,
161
                       total=False) -> JSONResponse:
162
        """Calculate flows statistics.
163
        The returned statistics are both per flow and for the sum of flows
164
        """
165
166 1
        if total:
167
            count_flows = 0
168
        else:
169 1
            count_flows = []
170 1
            if not counter:
171
                counter = field
172 1
            if not rate:
173
                rate = field
174
175
        # We don't have statistics persistence yet, so for now this only works
176
        # for start and end equals to zero
177 1
        flows = self.flow_stats_by_dpid_flow_id([dpid])
178 1
        flows = flows.get(dpid)
179
180 1
        if flows is None:
181 1
            return JSONResponse(count_flows)
182 1
        for flow_id, stats in flows.items():
183 1
            count = stats[field]
184 1
            if total:
185
                count_flows += count
186
            else:
187 1
                try:
188 1
                    per_second = count / stats['duration_sec']
189 1
                except ZeroDivisionError:
190 1
                    per_second = 0
191 1
                if rate.startswith('bits'):
192 1
                    per_second *= 8
193 1
                count_flows.append({'flow_id': flow_id,
194
                                    counter: count,
195
                                    rate: per_second})
196 1
        return JSONResponse(count_flows)
197
198 1
    @listen_to('kytos/of_core.flow_stats.received')
199 1
    def on_stats_received(self, event):
200
        """Capture flow stats messages for OpenFlow 1.3."""
201
        self.handle_stats_received(event)
202
203 1
    def handle_stats_received(self, event):
204
        """Handle flow stats messages for OpenFlow 1.3."""
205 1
        if 'replies_flows' in event.content:
206 1
            replies_flows = event.content['replies_flows']
207 1
            self.handle_stats_reply_received(replies_flows)
208
209 1
    def handle_stats_reply_received(self, replies_flows):
210
        """Update the set of flows stats"""
211 1
        self.flows_stats_dict.update({flow.id: flow for flow in replies_flows})
212
213 1
    @listen_to('kytos/of_core.table_stats.received')
214 1
    def on_table_stats_received(self, event):
215
        """Capture table stats messages for OpenFlow 1.3."""
216
        self.handle_table_stats_received(event)
217
218 1
    def handle_table_stats_received(self, event):
219
        """Handle table stats messages for OpenFlow 1.3."""
220
        replies_tables = event.content['replies_tables']
221
        self.handle_table_stats_reply_received(replies_tables)
222
223 1
    def handle_table_stats_reply_received(self, replies_tables):
224
        """Update the set of tables stats"""
225 1
        for table in replies_tables:
226 1
            switch_id = table.switch.id
227 1
            if switch_id not in self.tables_stats_dict:
228 1
                self.tables_stats_dict[switch_id] = {}
229
            self.tables_stats_dict[switch_id][table.table_id] = table
230