
Gearman message actions need to change to maintain backward compatibility. PING will revert back to STATS, and METRICS will replace the STATS message. Worker message documentation is also updated. Change-Id: I1aa7478abcf29ffe5dbd512b2175dc5633548064
168 lines
5.9 KiB
Python
168 lines
5.9 KiB
Python
# Copyright 2013 Hewlett-Packard Development Company, L.P.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import datetime
|
|
import os.path
|
|
import simplejson
|
|
|
|
from libra.openstack.common import log
|
|
|
|
LOG = log.getLogger(__name__)
|
|
|
|
|
|
class StatisticsManager(object):
|
|
"""
|
|
Class for managing statistics storage.
|
|
|
|
Since HAProxy statistics are reset whenever the haproxy process is
|
|
restarted, we need a reliable way of maintaining these values across
|
|
restarts. This class attempts to manage the storage of the values.
|
|
|
|
There are two types of statistics we record:
|
|
|
|
* Unreported stats
|
|
These are stats that we need to save because a state change in
|
|
the HAProxy service is causing it to restart. Since HAProxy stores
|
|
its stats in memory, they would otherwise be lost. We save them here
|
|
for consideration in the next METRICS request.
|
|
|
|
* Last queried stats
|
|
These are total bytes out as reported from HAProxy the last time we
|
|
queried it for that information.
|
|
"""
|
|
|
|
START_FIELD = 'start'
|
|
END_FIELD = 'end'
|
|
|
|
# UNREPORTED_* values are for unreported statistics due to a restart
|
|
UNREPORTED_TCP_BYTES_FIELD = 'unreported_tcp_bytes_out'
|
|
UNREPORTED_HTTP_BYTES_FIELD = 'unreported_http_bytes_out'
|
|
|
|
# LAST_* values are for values from our last query
|
|
LAST_TCP_BYTES_FIELD = 'last_tcp_bytes_out'
|
|
LAST_HTTP_BYTES_FIELD = 'last_http_bytes_out'
|
|
|
|
def __init__(self, filename):
|
|
self.filename = filename
|
|
self._object = {}
|
|
self.read()
|
|
|
|
def _do_save(self, obj):
|
|
with open(self.filename, "w") as fp:
|
|
simplejson.dump(obj, fp)
|
|
|
|
def _format_timestamp(self, ts):
|
|
return datetime.datetime.strptime(ts, '%Y-%m-%d %H:%M:%S.%f')
|
|
|
|
def save(self, start, end, tcp_bytes=0, http_bytes=0,
|
|
unreported_tcp_bytes=0, unreported_http_bytes=0):
|
|
"""
|
|
Save HAProxy statistics values, overwriting any existing data.
|
|
|
|
start
|
|
Start timestamp from our last report.
|
|
|
|
end
|
|
End timestamp from our last report.
|
|
|
|
tcp_bytes
|
|
TOTAL bytes out of the TCP backend, as reported by haproxy,
|
|
when we last reported them back.
|
|
|
|
http_bytes
|
|
TOTAL bytes out of the HTTP backend, as reported by haproxy,
|
|
when we last reported them back.
|
|
|
|
unreported_tcp_bytes
|
|
TOTAL bytes out of the TCP backend, as reported by haproxy,
|
|
when the service was stopped or restarted.
|
|
|
|
unreported_http_bytes
|
|
TOTAL bytes out of the HTTP backend, as reported by haproxy,
|
|
when the service was stopped or restarted.
|
|
"""
|
|
if None in [start, end]:
|
|
raise Exception('Cannot save None value for timestamps')
|
|
|
|
if type(start) != datetime.datetime or type(end) != datetime.datetime:
|
|
raise TypeError('Timestamps must be datetime.datetime')
|
|
|
|
obj = {
|
|
self.START_FIELD: str(start),
|
|
self.END_FIELD: str(end),
|
|
self.LAST_TCP_BYTES_FIELD: tcp_bytes,
|
|
self.LAST_HTTP_BYTES_FIELD: http_bytes,
|
|
self.UNREPORTED_TCP_BYTES_FIELD: unreported_tcp_bytes,
|
|
self.UNREPORTED_HTTP_BYTES_FIELD: unreported_http_bytes
|
|
}
|
|
LOG.debug("Saving statistics: %s" % obj)
|
|
self._do_save(obj)
|
|
|
|
def read(self):
|
|
""" Read the current values from the file """
|
|
if not os.path.exists(self.filename):
|
|
return
|
|
with open(self.filename, "r") as fp:
|
|
self._object = simplejson.load(fp)
|
|
|
|
def get_start(self):
|
|
""" Return last start timestamp as datetime object """
|
|
if self.START_FIELD in self._object:
|
|
return self._format_timestamp(self._object[self.START_FIELD])
|
|
return None
|
|
|
|
def get_end(self):
|
|
""" Return last end timestamp as datetime object """
|
|
if self.END_FIELD in self._object:
|
|
return self._format_timestamp(self._object[self.END_FIELD])
|
|
return None
|
|
|
|
def get_unreported_tcp_bytes(self):
|
|
""" Return TCP unreported bytes out """
|
|
if self.UNREPORTED_TCP_BYTES_FIELD in self._object:
|
|
return int(self._object[self.UNREPORTED_TCP_BYTES_FIELD])
|
|
return 0
|
|
|
|
def get_unreported_http_bytes(self):
|
|
""" Return HTTP unreported bytes out """
|
|
if self.UNREPORTED_HTTP_BYTES_FIELD in self._object:
|
|
return int(self._object[self.UNREPORTED_HTTP_BYTES_FIELD])
|
|
return 0
|
|
|
|
def get_last_tcp_bytes(self):
|
|
""" Return TCP last reported bytes out """
|
|
if self.LAST_TCP_BYTES_FIELD in self._object:
|
|
return int(self._object[self.LAST_TCP_BYTES_FIELD])
|
|
return 0
|
|
|
|
def get_last_http_bytes(self):
|
|
""" Return HTTP last reported bytes out """
|
|
if self.LAST_HTTP_BYTES_FIELD in self._object:
|
|
return int(self._object[self.LAST_HTTP_BYTES_FIELD])
|
|
return 0
|
|
|
|
def calculate_new_start(self):
|
|
"""
|
|
Calculate a new start value for our reporting time range,
|
|
which should be just after the last reported end value. If
|
|
there is no start value, then we haven't recorded one yet
|
|
(i.e., haven't reported any stats yet) so use the current time.
|
|
"""
|
|
new_start = self.get_end()
|
|
if new_start is None:
|
|
new_start = datetime.datetime.utcnow()
|
|
else:
|
|
new_start = new_start + datetime.timedelta(microseconds=1)
|
|
return new_start
|