1"""
2"""
3
4# Created on 2014.03.15
5#
6# Author: Giovanni Cannata
7#
8# Copyright 2013 - 2020 Giovanni Cannata
9#
10# This file is part of ldap3.
11#
12# ldap3 is free software: you can redistribute it and/or modify
13# it under the terms of the GNU Lesser General Public License as published
14# by the Free Software Foundation, either version 3 of the License, or
15# (at your option) any later version.
16#
17# ldap3 is distributed in the hope that it will be useful,
18# but WITHOUT ANY WARRANTY; without even the implied warranty of
19# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20# GNU Lesser General Public License for more details.
21#
22# You should have received a copy of the GNU Lesser General Public License
23# along with ldap3 in the COPYING and COPYING.LESSER files.
24# If not, see <http://www.gnu.org/licenses/>.
25
26from datetime import datetime, timedelta
27from os import linesep
28
29from .exceptions import LDAPMetricsError
30from ..utils.log import log, log_enabled, ERROR, BASIC
31
32
33class ConnectionUsage(object):
34    """
35    Collect statistics on connection usage
36    """
37
38    def reset(self):
39        self.open_sockets = 0
40        self.closed_sockets = 0
41        self.wrapped_sockets = 0
42        self.bytes_transmitted = 0
43        self.bytes_received = 0
44        self.messages_transmitted = 0
45        self.messages_received = 0
46        self.operations = 0
47        self.abandon_operations = 0
48        self.add_operations = 0
49        self.bind_operations = 0
50        self.compare_operations = 0
51        self.delete_operations = 0
52        self.extended_operations = 0
53        self.modify_operations = 0
54        self.modify_dn_operations = 0
55        self.search_operations = 0
56        self.unbind_operations = 0
57        self.referrals_received = 0
58        self.referrals_followed = 0
59        self.referrals_connections = 0
60        self.restartable_failures = 0
61        self.restartable_successes = 0
62        self.servers_from_pool = 0
63        if log_enabled(BASIC):
64            log(BASIC, 'reset usage metrics')
65
66    def __init__(self):
67        self.initial_connection_start_time = None
68        self.open_socket_start_time = None
69        self.connection_stop_time = None
70        self.last_transmitted_time = None
71        self.last_received_time = None
72        self.open_sockets = 0
73        self.closed_sockets = 0
74        self.wrapped_sockets = 0
75        self.bytes_transmitted = 0
76        self.bytes_received = 0
77        self.messages_transmitted = 0
78        self.messages_received = 0
79        self.operations = 0
80        self.abandon_operations = 0
81        self.add_operations = 0
82        self.bind_operations = 0
83        self.compare_operations = 0
84        self.delete_operations = 0
85        self.extended_operations = 0
86        self.modify_operations = 0
87        self.modify_dn_operations = 0
88        self.search_operations = 0
89        self.unbind_operations = 0
90        self.referrals_received = 0
91        self.referrals_followed = 0
92        self.referrals_connections = 0
93        self.restartable_failures = 0
94        self.restartable_successes = 0
95        self.servers_from_pool = 0
96
97        if log_enabled(BASIC):
98            log(BASIC, 'instantiated Usage object')
99
100    def __repr__(self):
101        r = 'Connection Usage:' + linesep
102        r += '  Time: [elapsed:          ' + str(self.elapsed_time) + ']' + linesep
103        r += '    Initial start time:    ' + (str(self.initial_connection_start_time.isoformat()) if self.initial_connection_start_time else '') + linesep
104        r += '    Open socket time:      ' + (str(self.open_socket_start_time.isoformat()) if self.open_socket_start_time else '') + linesep
105        r += '    Last transmitted time: ' + (str(self.last_transmitted_time.isoformat()) if self.last_transmitted_time else '') + linesep
106        r += '    Last received time:    ' + (str(self.last_received_time.isoformat()) if self.last_received_time else '') + linesep
107        r += '    Close socket time:     ' + (str(self.connection_stop_time.isoformat()) if self.connection_stop_time else '') + linesep
108        r += '  Server:' + linesep
109        r += '    Servers from pool:     ' + str(self.servers_from_pool) + linesep
110        r += '    Sockets open:          ' + str(self.open_sockets) + linesep
111        r += '    Sockets closed:        ' + str(self.closed_sockets) + linesep
112        r += '    Sockets wrapped:       ' + str(self.wrapped_sockets) + linesep
113        r += '  Bytes:                   ' + str(self.bytes_transmitted + self.bytes_received) + linesep
114        r += '    Transmitted:           ' + str(self.bytes_transmitted) + linesep
115        r += '    Received:              ' + str(self.bytes_received) + linesep
116        r += '  Messages:                ' + str(self.messages_transmitted + self.messages_received) + linesep
117        r += '    Transmitted:           ' + str(self.messages_transmitted) + linesep
118        r += '    Received:              ' + str(self.messages_received) + linesep
119        r += '  Operations:              ' + str(self.operations) + linesep
120        r += '    Abandon:               ' + str(self.abandon_operations) + linesep
121        r += '    Bind:                  ' + str(self.bind_operations) + linesep
122        r += '    Add:                   ' + str(self.add_operations) + linesep
123        r += '    Compare:               ' + str(self.compare_operations) + linesep
124        r += '    Delete:                ' + str(self.delete_operations) + linesep
125        r += '    Extended:              ' + str(self.extended_operations) + linesep
126        r += '    Modify:                ' + str(self.modify_operations) + linesep
127        r += '    ModifyDn:              ' + str(self.modify_dn_operations) + linesep
128        r += '    Search:                ' + str(self.search_operations) + linesep
129        r += '    Unbind:                ' + str(self.unbind_operations) + linesep
130        r += '  Referrals:               ' + linesep
131        r += '    Received:              ' + str(self.referrals_received) + linesep
132        r += '    Followed:              ' + str(self.referrals_followed) + linesep
133        r += '    Connections:           ' + str(self.referrals_connections) + linesep
134        r += '  Restartable tries:       ' + str(self.restartable_failures + self.restartable_successes) + linesep
135        r += '    Failed restarts:       ' + str(self.restartable_failures) + linesep
136        r += '    Successful restarts:   ' + str(self.restartable_successes) + linesep
137        return r
138
139    def __str__(self):
140        return self.__repr__()
141
142    def __iadd__(self, other):
143        if not isinstance(other, ConnectionUsage):
144            raise LDAPMetricsError('unable to add to ConnectionUsage')
145
146        self.open_sockets += other.open_sockets
147        self.closed_sockets += other.closed_sockets
148        self.wrapped_sockets += other.wrapped_sockets
149        self.bytes_transmitted += other.bytes_transmitted
150        self.bytes_received += other.bytes_received
151        self.messages_transmitted += other.messages_transmitted
152        self.messages_received += other.messages_received
153        self.operations += other.operations
154        self.abandon_operations += other.abandon_operations
155        self.add_operations += other.add_operations
156        self.bind_operations += other.bind_operations
157        self.compare_operations += other.compare_operations
158        self.delete_operations += other.delete_operations
159        self.extended_operations += other.extended_operations
160        self.modify_operations += other.modify_operations
161        self.modify_dn_operations += other.modify_dn_operations
162        self.search_operations += other.search_operations
163        self.unbind_operations += other.unbind_operations
164        self.referrals_received += other.referrals_received
165        self.referrals_followed += other.referrals_followed
166        self.referrals_connections += other.referrals_connections
167        self.restartable_failures += other.restartable_failures
168        self.restartable_successes += other.restartable_successes
169        self.servers_from_pool += other.servers_from_pool
170        return self
171
172    def update_transmitted_message(self, message, length):
173        self.last_transmitted_time = datetime.now()
174        self.bytes_transmitted += length
175        self.operations += 1
176        self.messages_transmitted += 1
177        if message['type'] == 'abandonRequest':
178            self.abandon_operations += 1
179        elif message['type'] == 'addRequest':
180            self.add_operations += 1
181        elif message['type'] == 'bindRequest':
182            self.bind_operations += 1
183        elif message['type'] == 'compareRequest':
184            self.compare_operations += 1
185        elif message['type'] == 'delRequest':
186            self.delete_operations += 1
187        elif message['type'] == 'extendedReq':
188            self.extended_operations += 1
189        elif message['type'] == 'modifyRequest':
190            self.modify_operations += 1
191        elif message['type'] == 'modDNRequest':
192            self.modify_dn_operations += 1
193        elif message['type'] == 'searchRequest':
194            self.search_operations += 1
195        elif message['type'] == 'unbindRequest':
196            self.unbind_operations += 1
197        else:
198            if log_enabled(ERROR):
199                log(ERROR, 'unable to collect usage for unknown message type <%s>', message['type'])
200            raise LDAPMetricsError('unable to collect usage for unknown message type')
201
202    def update_received_message(self, length):
203        self.last_received_time = datetime.now()
204        self.bytes_received += length
205        self.messages_received += 1
206
207    def start(self, reset=True):
208        if reset:
209            self.reset()
210        self.open_socket_start_time = datetime.now()
211        self.connection_stop_time = None
212        if not self.initial_connection_start_time:
213            self.initial_connection_start_time = self.open_socket_start_time
214
215        if log_enabled(BASIC):
216            log(BASIC, 'start collecting usage metrics')
217
218    def stop(self):
219        if self.open_socket_start_time:
220            self.connection_stop_time = datetime.now()
221            if log_enabled(BASIC):
222                log(BASIC, 'stop collecting usage metrics')
223
224    @property
225    def elapsed_time(self):
226        if self.connection_stop_time:
227            return self.connection_stop_time - self.open_socket_start_time
228        else:
229            return (datetime.now() - self.open_socket_start_time) if self.open_socket_start_time else timedelta(0)
230