4e2e7a330fcac11ae02c5b1f74d96fac803e0a39
1 # -*- coding: utf-8 -*-
3 from __future__
import absolute_import
7 class MonitorEntry(object):
8 """Entity object for one monitoring "event"
10 This class encapsulates all data that's measured in
11 one single monitoring event.
13 def __init__(self
, cmdline
, inbytes
, outbytes
, timestamp
=None):
14 """Creates a new MonitorEntry object
16 If the parameter timestamp is omitted, it will default to
17 "now" (i.e. the measurement has taken place right now).
19 @param cmdline: The command line of the measured process
20 @param inbytes: The current incoming total data, in bytes
21 @param outbytes: The current outgoing total data, in bytes
22 @param timestamp: Unix timestamp (UTC) of the event
25 timestamp
= time
.time()
27 self
.cmdline
= cmdline
28 self
.inbytes
= int(inbytes
)
29 self
.outbytes
= int(outbytes
)
30 self
.timestamp
= timestamp
33 """Create a string representation of this entry
35 This is mostl used for debugging, and will simply
36 return all the values in a user-readable string.
38 @return: The string representation of this object
40 return '<%s cmd="%s" in=%d out=%d time=%d>' % (self
.__class
__.__name
__,
46 class MonitorEntryCollection(object):
47 """A (linear) collection of multiple MonitorEntry objects
49 This object is used by the monitor and the aggregator to
50 combine several single MonitorEntry objects and provide
51 a unified view on the current state of the objects.
55 def __init__(self
, update_frequency
, get_app
=lambda x
: x
):
56 """Create a new MonitorEntryCollection object
58 The update_frequency should match the update frequency
59 of the "calling object" (either a simple Monitor or the
60 aggregator itself), as it's used for calculations that
61 will be exposed to the calling object.
63 @param update_frequency: Update frequency (in seconds)
64 @param get_app: Callback to determine the app from a command
68 self
.update_frequency
= update_frequency
69 self
.get_app
= get_app
72 """Compact the internal state (expire old entries)
74 Remove all the measurement data that's too old and
75 has already been processed. This will usually be
76 called internally and remove all items older than
77 the "TIMEOUT" value for this class.
79 cutoff
= time
.time() - self
.TIMEOUT
80 d
= filter(lambda e
: e
.timestamp
>= cutoff
, self
._data
)
82 for key
in self
._latest
.keys():
83 current
, previous
= self
._latest
[key
]
84 if current
is not None and current
.timestamp
< cutoff
:
87 def get_last_bytes(self
, cmdline
):
88 """Return a (inbytes, outbytes, timestamp) for a command
90 In case no measurement for the given command is
91 available, a (0, 0, 0) three-tuple will be returned.
93 Otherwise, the cumulative traffic data (inbytes, outbytes
94 and a UTC unix timestamp) will be returned, as of the
97 @return: A three-tuple or (0, 0, 0)
99 (current
, previous
) = self
._latest
.get(cmdline
, (None, None))
101 if current
is not None:
102 return (current
.inbytes
, current
.outbytes
, current
.timestamp
)
106 def get_bandwidth(self
, cmdline
):
107 """Return a (inbytespersec, outbytespersec, timestamp) for a command
109 The semantics of this command are the same as the
110 get_last_bytes() command, but it returns the CURRENT
111 bandwidth usage (in bytes) instead of the cumulative
114 When the current bandwidth usage cannot be determined,
115 this function returns (0, 0, 0). This is the case when
116 not enough measurements are available (< 2) or the
117 command is not monitored at all by the installed monitors.
119 @return: A three-tuple or (0, 0, 0)
121 (current
, previous
) = self
._latest
.get(cmdline
, (None, None))
122 if current
is not None and previous
is not None:
123 d_time
= float(current
.timestamp
- previous
.timestamp
)
124 d_in
= float(current
.inbytes
- previous
.inbytes
)
125 d_out
= float(current
.outbytes
- previous
.outbytes
)
126 return (d_in
/d_time
, d_out
/d_time
, current
.timestamp
)
130 def add(self
, entry
):
131 """Add a new entry to this collection
133 This adds a new MonitorEntry to this collection and
134 makes sure that it is picked up from the aggregation
137 @param entry: A MonitorEntry object to be added
139 entry
.cmdline
= self
.get_app(entry
.cmdline
)
140 (current
, previous
) = self
._latest
.get(entry
.cmdline
, (None, None))
142 # throttle comparison; don't always take the lastest two
143 #prev = current if current and (entry.timestamp - current.timestamp) else previous
144 #self._latest[entry.cmdline] = (entry, prev)
146 self
._latest
[entry
.cmdline
] = (entry
, current
)
147 self
._data
.append(entry
)
149 def get_history(self
, cmdline
):
150 """Return the bandwidth history for a given command
152 @param cmdline: The command line / app name to be queried
157 if e
.cmdline
== cmdline
:
158 if last_inbytes
== -1:
159 last_inbytes
= e
.inbytes
160 x
.append(e
.inbytes
-last_inbytes
)
161 last_inbytes
= e
.inbytes
164 def get_datapoints(self
):
169 items
= [self
.get_history(cmdline
) for bin
, bout
, cmdline
in self
.get_usage()]
175 items
= filter(check_items
, items
)
177 return range(len(min(items
, key
=len))), items
179 def get_traffic(self
):
180 """Get the current view on the traffic
182 This returns a generator object that will yield
183 (bytes_in, bytes_out, cmdline) tuples describing
184 the current traffic on a per-command/-app basis.
186 @return: Generator yielding (bytes_in, bytes_out, cmdline)
188 for cmdline
in self
._latest
:
189 bytes_in
, bytes_out
, timestamp
= self
.get_last_bytes(cmdline
)
190 yield (bytes_in
, bytes_out
, cmdline
)
193 """Get the current view on the bandwidth (usage)
195 This returns a generator object that will yield
196 (bytes_in, bytes_out, cmdline) tuples describing
197 the current bandwidth usage on a per-command/-app
200 @return: Generator yielding (bytes_in, bytes_out, cmdline)
202 cutoff
= time
.time() - self
.update_frequency
203 for cmdline
in self
._latest
:
204 bytes_in
, bytes_out
, timestamp
= self
.get_bandwidth(cmdline
)
205 if timestamp
> cutoff
:
206 yield (bytes_in
, bytes_out
, cmdline
)