statsd timing refactor

Change-Id: I99d9ddfbcad0f88e75c49235c8317ea97237d4e4
This commit is contained in:
Michael Barton 2012-11-06 02:44:43 -08:00 committed by Darrell Bishop
parent f66248b973
commit 3586f829b0
6 changed files with 182 additions and 229 deletions

View File

@ -477,35 +477,36 @@ Metric Name Description
Metrics for `account-server` ("Not Found" is not considered an error and requests Metrics for `account-server` ("Not Found" is not considered an error and requests
which increment `errors` are not included in the timing data): which increment `errors` are not included in the timing data):
================================= ==================================================== ======================================== =======================================================
Metric Name Description Metric Name Description
--------------------------------- ---------------------------------------------------- ---------------------------------------- -------------------------------------------------------
`account-server.DELETE.errors` Count of errors handling DELETE requests: bad `account-server.DELETE.errors.timing` Timing data for each DELETE request resulting in an
request, not mounted, missing timestamp. error: bad request, not mounted, missing timestamp.
`account-server.DELETE.timing` Timing data for each DELETE request not resulting in `account-server.DELETE.timing` Timing data for each DELETE request not resulting in
an error. an error.
`account-server.PUT.errors` Count of errors handling PUT requests: bad request, `account-server.PUT.errors.timing` Timing data for each PUT request resulting in an error:
not mounted, conflict. bad request, not mounted, conflict, recently-deleted.
`account-server.PUT.timing` Timing data for each PUT request not resulting in an `account-server.PUT.timing` Timing data for each PUT request not resulting in an
error. error.
`account-server.HEAD.errors` Count of errors handling HEAD requests: bad request, `account-server.HEAD.errors.timing` Timing data for each HEAD request resulting in an
not mounted. error: bad request, not mounted.
`account-server.HEAD.timing` Timing data for each HEAD request not resulting in `account-server.HEAD.timing` Timing data for each HEAD request not resulting in
an error. an error.
`account-server.GET.errors` Count of errors handling GET requests: bad request, `account-server.GET.errors.timing` Timing data for each GET request resulting in an
not mounted, bad delimiter, account listing limit error: bad request, not mounted, bad delimiter,
too high, bad accept header. account listing limit too high, bad accept header.
`account-server.GET.timing` Timing data for each GET request not resulting in `account-server.GET.timing` Timing data for each GET request not resulting in
an error. an error.
`account-server.REPLICATE.errors` Count of errors handling REPLICATE requests: bad `account-server.REPLICATE.errors.timing` Timing data for each REPLICATE request resulting in an
request, not mounted. error: bad request, not mounted.
`account-server.REPLICATE.timing` Timing data for each REPLICATE request not resulting `account-server.REPLICATE.timing` Timing data for each REPLICATE request not resulting
in an error. in an error.
`account-server.POST.errors` Count of errors handling POST requests: bad request, `account-server.POST.errors.timing` Timing data for each POST request resulting in an
bad or missing timestamp, not mounted. error: bad request, bad or missing timestamp, not
`account-server.POST.timing` Timing data for each POST request not resulting in mounted.
an error. `account-server.POST.timing` Timing data for each POST request not resulting in
================================= ==================================================== an error.
======================================== =======================================================
Metrics for `account-replicator`: Metrics for `account-replicator`:
@ -584,34 +585,34 @@ Metric Name Description
Metrics for `container-server` ("Not Found" is not considered an error and requests Metrics for `container-server` ("Not Found" is not considered an error and requests
which increment `errors` are not included in the timing data): which increment `errors` are not included in the timing data):
=================================== ==================================================== ========================================== ====================================================
Metric Name Description Metric Name Description
----------------------------------- ---------------------------------------------------- ------------------------------------------ ----------------------------------------------------
`container-server.DELETE.errors` Count of errors handling DELETE requests: bad `container-server.DELETE.errors.timing` Timing data for DELETE request errors: bad request,
request, not mounted, missing timestamp, conflict. not mounted, missing timestamp, conflict.
`container-server.DELETE.timing` Timing data for each DELETE request not resulting in `container-server.DELETE.timing` Timing data for each DELETE request not resulting in
an error. an error.
`container-server.PUT.errors` Count of errors handling PUT requests: bad request, `container-server.PUT.errors.timing` Timing data for PUT request errors: bad request,
missing timestamp, not mounted, conflict. missing timestamp, not mounted, conflict.
`container-server.PUT.timing` Timing data for each PUT request not resulting in an `container-server.PUT.timing` Timing data for each PUT request not resulting in an
error. error.
`container-server.HEAD.errors` Count of errors handling HEAD requests: bad request, `container-server.HEAD.errors.timing` Timing data for HEAD request errors: bad request,
not mounted. not mounted.
`container-server.HEAD.timing` Timing data for each HEAD request not resulting in `container-server.HEAD.timing` Timing data for each HEAD request not resulting in
an error. an error.
`container-server.GET.errors` Count of errors handling GET requests: bad request, `container-server.GET.errors.timing` Timing data for GET request errors: bad request,
not mounted, parameters not utf8, bad accept header. not mounted, parameters not utf8, bad accept header.
`container-server.GET.timing` Timing data for each GET request not resulting in `container-server.GET.timing` Timing data for each GET request not resulting in
an error. an error.
`container-server.REPLICATE.errors` Count of errors handling REPLICATE requests: bad `container-server.REPLICATE.errors.timing` Timing data for REPLICATE request errors: bad
request, not mounted. request, not mounted.
`container-server.REPLICATE.timing` Timing data for each REPLICATE request not resulting `container-server.REPLICATE.timing` Timing data for each REPLICATE request not resulting
in an error. in an error.
`container-server.POST.errors` Count of errors handling POST requests: bad request, `container-server.POST.errors.timing` Timing data for POST request errors: bad request,
bad x-container-sync-to, not mounted. bad x-container-sync-to, not mounted.
`container-server.POST.timing` Timing data for each POST request not resulting in `container-server.POST.timing` Timing data for each POST request not resulting in
an error. an error.
=================================== ==================================================== ========================================== ====================================================
Metrics for `container-sync`: Metrics for `container-sync`:
@ -700,47 +701,49 @@ Metric Name Description
Metrics for `object-server`: Metrics for `object-server`:
================================ ==================================================== ======================================= ====================================================
Metric Name Description Metric Name Description
-------------------------------- ---------------------------------------------------- --------------------------------------- ----------------------------------------------------
`object-server.quarantines` Count of objects (files) found bad and moved to `object-server.quarantines` Count of objects (files) found bad and moved to
quarantine. quarantine.
`object-server.async_pendings` Count of container updates saved as async_pendings `object-server.async_pendings` Count of container updates saved as async_pendings
(may result from PUT or DELETE requests). (may result from PUT or DELETE requests).
`object-server.POST.errors` Count of errors handling POST requests: bad request, `object-server.POST.errors.timing` Timing data for POST request errors: bad request,
missing timestamp, delete-at in past, not mounted. missing timestamp, delete-at in past, not mounted.
`object-server.POST.timing` Timing data for each POST request not resulting in `object-server.POST.timing` Timing data for each POST request not resulting in
an error. an error.
`object-server.PUT.errors` Count of errors handling PUT requests: bad request, `object-server.PUT.errors.timing` Timing data for PUT request errors: bad request,
not mounted, missing timestamp, object creation not mounted, missing timestamp, object creation
constraint violation, delete-at in past. constraint violation, delete-at in past.
`object-server.PUT.timeouts` Count of object PUTs which exceeded max_upload_time. `object-server.PUT.timeouts` Count of object PUTs which exceeded max_upload_time.
`object-server.PUT.timing` Timing data for each PUT request not resulting in an `object-server.PUT.timing` Timing data for each PUT request not resulting in an
error. error.
`object-server.GET.errors` Count of errors handling GET requests: bad request, `object-server.GET.errors.timing` Timing data for GET request errors: bad request,
not mounted, header timestamps before the epoch. not mounted, header timestamps before the epoch,
File errors resulting in a quarantine are not precondition failed.
counted here. File errors resulting in a quarantine are not
`object-server.GET.timing` Timing data for each GET request not resulting in an counted here.
error. Includes requests which couldn't find the `object-server.GET.timing` Timing data for each GET request not resulting in an
object (including disk errors resulting in file error. Includes requests which couldn't find the
quarantine). object (including disk errors resulting in file
`object-server.HEAD.errors` Count of errors handling HEAD requests: bad request, quarantine).
not mounted. `object-server.HEAD.errors.timing` Timing data for HEAD request errors: bad request,
`object-server.HEAD.timing` Timing data for each HEAD request not resulting in not mounted.
an error. Includes requests which couldn't find the `object-server.HEAD.timing` Timing data for each HEAD request not resulting in
object (including disk errors resulting in file an error. Includes requests which couldn't find the
quarantine). object (including disk errors resulting in file
`object-server.DELETE.errors` Count of errors handling DELETE requests: bad quarantine).
request, missing timestamp, not mounted. Includes `object-server.DELETE.errors.timing` Timing data for DELETE request errors: bad request,
requests which couldn't find or match the object. missing timestamp, not mounted, precondition
`object-server.DELETE.timing` Timing data for each DELETE request not resulting failed. Includes requests which couldn't find or
in an error. match the object.
`object-server.REPLICATE.errors` Count of errors handling REPLICATE requests: bad `object-server.DELETE.timing` Timing data for each DELETE request not resulting
request, not mounted. in an error.
`object-server.REPLICATE.timing` Timing data for each REPLICATE request not resulting `object-server.REPLICATE.errors.timing` Timing data for REPLICATE request errors: bad
in an error. request, not mounted.
================================ ==================================================== `object-server.REPLICATE.timing` Timing data for each REPLICATE request not resulting
in an error.
======================================= ====================================================
Metrics for `object-updater`: Metrics for `object-updater`:

View File

@ -27,7 +27,7 @@ import swift.common.db
from swift.common.db import AccountBroker from swift.common.db import AccountBroker
from swift.common.utils import get_logger, get_param, hash_path, public, \ from swift.common.utils import get_logger, get_param, hash_path, public, \
normalize_timestamp, split_path, storage_directory, config_true_value, \ normalize_timestamp, split_path, storage_directory, config_true_value, \
validate_device_partition, json validate_device_partition, json, timing_stats
from swift.common.constraints import ACCOUNT_LISTING_LIMIT, \ from swift.common.constraints import ACCOUNT_LISTING_LIMIT, \
check_mount, check_float, check_utf8, FORMAT2CONTENT_TYPE check_mount, check_float, check_utf8, FORMAT2CONTENT_TYPE
from swift.common.db_replicator import ReplicatorRpc from swift.common.db_replicator import ReplicatorRpc
@ -63,46 +63,39 @@ class AccountController(object):
return AccountBroker(db_path, account=account, logger=self.logger) return AccountBroker(db_path, account=account, logger=self.logger)
@public @public
@timing_stats
def DELETE(self, req): def DELETE(self, req):
"""Handle HTTP DELETE request.""" """Handle HTTP DELETE request."""
start_time = time.time()
try: try:
drive, part, account = split_path(unquote(req.path), 3) drive, part, account = split_path(unquote(req.path), 3)
validate_device_partition(drive, part) validate_device_partition(drive, part)
except ValueError, err: except ValueError, err:
self.logger.increment('DELETE.errors')
return HTTPBadRequest(body=str(err), content_type='text/plain', return HTTPBadRequest(body=str(err), content_type='text/plain',
request=req) request=req)
if self.mount_check and not check_mount(self.root, drive): if self.mount_check and not check_mount(self.root, drive):
self.logger.increment('DELETE.errors')
return HTTPInsufficientStorage(drive=drive, request=req) return HTTPInsufficientStorage(drive=drive, request=req)
if 'x-timestamp' not in req.headers or \ if 'x-timestamp' not in req.headers or \
not check_float(req.headers['x-timestamp']): not check_float(req.headers['x-timestamp']):
self.logger.increment('DELETE.errors')
return HTTPBadRequest(body='Missing timestamp', request=req, return HTTPBadRequest(body='Missing timestamp', request=req,
content_type='text/plain') content_type='text/plain')
broker = self._get_account_broker(drive, part, account) broker = self._get_account_broker(drive, part, account)
if broker.is_deleted(): if broker.is_deleted():
self.logger.timing_since('DELETE.timing', start_time)
return HTTPNotFound(request=req) return HTTPNotFound(request=req)
broker.delete_db(req.headers['x-timestamp']) broker.delete_db(req.headers['x-timestamp'])
self.logger.timing_since('DELETE.timing', start_time)
return HTTPNoContent(request=req) return HTTPNoContent(request=req)
@public @public
@timing_stats
def PUT(self, req): def PUT(self, req):
"""Handle HTTP PUT request.""" """Handle HTTP PUT request."""
start_time = time.time()
try: try:
drive, part, account, container = split_path(unquote(req.path), drive, part, account, container = split_path(unquote(req.path),
3, 4) 3, 4)
validate_device_partition(drive, part) validate_device_partition(drive, part)
except ValueError, err: except ValueError, err:
self.logger.increment('PUT.errors')
return HTTPBadRequest(body=str(err), content_type='text/plain', return HTTPBadRequest(body=str(err), content_type='text/plain',
request=req) request=req)
if self.mount_check and not check_mount(self.root, drive): if self.mount_check and not check_mount(self.root, drive):
self.logger.increment('PUT.errors')
return HTTPInsufficientStorage(drive=drive, request=req) return HTTPInsufficientStorage(drive=drive, request=req)
broker = self._get_account_broker(drive, part, account) broker = self._get_account_broker(drive, part, account)
if container: # put account container if container: # put account container
@ -114,13 +107,11 @@ class AccountController(object):
req.headers.get('x-timestamp') or time.time())) req.headers.get('x-timestamp') or time.time()))
if req.headers.get('x-account-override-deleted', 'no').lower() != \ if req.headers.get('x-account-override-deleted', 'no').lower() != \
'yes' and broker.is_deleted(): 'yes' and broker.is_deleted():
self.logger.timing_since('PUT.timing', start_time)
return HTTPNotFound(request=req) return HTTPNotFound(request=req)
broker.put_container(container, req.headers['x-put-timestamp'], broker.put_container(container, req.headers['x-put-timestamp'],
req.headers['x-delete-timestamp'], req.headers['x-delete-timestamp'],
req.headers['x-object-count'], req.headers['x-object-count'],
req.headers['x-bytes-used']) req.headers['x-bytes-used'])
self.logger.timing_since('PUT.timing', start_time)
if req.headers['x-delete-timestamp'] > \ if req.headers['x-delete-timestamp'] > \
req.headers['x-put-timestamp']: req.headers['x-put-timestamp']:
return HTTPNoContent(request=req) return HTTPNoContent(request=req)
@ -132,13 +123,11 @@ class AccountController(object):
broker.initialize(timestamp) broker.initialize(timestamp)
created = True created = True
elif broker.is_status_deleted(): elif broker.is_status_deleted():
self.logger.timing_since('PUT.timing', start_time)
return HTTPForbidden(request=req, body='Recently deleted') return HTTPForbidden(request=req, body='Recently deleted')
else: else:
created = broker.is_deleted() created = broker.is_deleted()
broker.update_put_timestamp(timestamp) broker.update_put_timestamp(timestamp)
if broker.is_deleted(): if broker.is_deleted():
self.logger.increment('PUT.errors')
return HTTPConflict(request=req) return HTTPConflict(request=req)
metadata = {} metadata = {}
metadata.update((key, (value, timestamp)) metadata.update((key, (value, timestamp))
@ -146,13 +135,13 @@ class AccountController(object):
if key.lower().startswith('x-account-meta-')) if key.lower().startswith('x-account-meta-'))
if metadata: if metadata:
broker.update_metadata(metadata) broker.update_metadata(metadata)
self.logger.timing_since('PUT.timing', start_time)
if created: if created:
return HTTPCreated(request=req) return HTTPCreated(request=req)
else: else:
return HTTPAccepted(request=req) return HTTPAccepted(request=req)
@public @public
@timing_stats
def HEAD(self, req): def HEAD(self, req):
"""Handle HTTP HEAD request.""" """Handle HTTP HEAD request."""
# TODO(refactor): The account server used to provide a 'account and # TODO(refactor): The account server used to provide a 'account and
@ -161,24 +150,20 @@ class AccountController(object):
# container servers directly so this is no longer needed. We should # container servers directly so this is no longer needed. We should
# refactor out the container existence check here and retest # refactor out the container existence check here and retest
# everything. # everything.
start_time = time.time()
try: try:
drive, part, account, container = split_path(unquote(req.path), drive, part, account, container = split_path(unquote(req.path),
3, 4) 3, 4)
validate_device_partition(drive, part) validate_device_partition(drive, part)
except ValueError, err: except ValueError, err:
self.logger.increment('HEAD.errors')
return HTTPBadRequest(body=str(err), content_type='text/plain', return HTTPBadRequest(body=str(err), content_type='text/plain',
request=req) request=req)
if self.mount_check and not check_mount(self.root, drive): if self.mount_check and not check_mount(self.root, drive):
self.logger.increment('HEAD.errors')
return HTTPInsufficientStorage(drive=drive, request=req) return HTTPInsufficientStorage(drive=drive, request=req)
broker = self._get_account_broker(drive, part, account) broker = self._get_account_broker(drive, part, account)
if not container: if not container:
broker.pending_timeout = 0.1 broker.pending_timeout = 0.1
broker.stale_reads_ok = True broker.stale_reads_ok = True
if broker.is_deleted(): if broker.is_deleted():
self.logger.timing_since('HEAD.timing', start_time)
return HTTPNotFound(request=req) return HTTPNotFound(request=req)
info = broker.get_info() info = broker.get_info()
headers = { headers = {
@ -203,31 +188,26 @@ class AccountController(object):
'text/xml'], 'text/xml'],
default_match='text/plain') default_match='text/plain')
except AssertionError, err: except AssertionError, err:
self.logger.increment('HEAD.errors')
return HTTPBadRequest(body='bad accept header: %s' % req.accept, return HTTPBadRequest(body='bad accept header: %s' % req.accept,
content_type='text/plain', request=req) content_type='text/plain', request=req)
self.logger.timing_since('HEAD.timing', start_time)
return HTTPNoContent(request=req, headers=headers, charset='utf-8') return HTTPNoContent(request=req, headers=headers, charset='utf-8')
@public @public
@timing_stats
def GET(self, req): def GET(self, req):
"""Handle HTTP GET request.""" """Handle HTTP GET request."""
start_time = time.time()
try: try:
drive, part, account = split_path(unquote(req.path), 3) drive, part, account = split_path(unquote(req.path), 3)
validate_device_partition(drive, part) validate_device_partition(drive, part)
except ValueError, err: except ValueError, err:
self.logger.increment('GET.errors')
return HTTPBadRequest(body=str(err), content_type='text/plain', return HTTPBadRequest(body=str(err), content_type='text/plain',
request=req) request=req)
if self.mount_check and not check_mount(self.root, drive): if self.mount_check and not check_mount(self.root, drive):
self.logger.increment('GET.errors')
return HTTPInsufficientStorage(drive=drive, request=req) return HTTPInsufficientStorage(drive=drive, request=req)
broker = self._get_account_broker(drive, part, account) broker = self._get_account_broker(drive, part, account)
broker.pending_timeout = 0.1 broker.pending_timeout = 0.1
broker.stale_reads_ok = True broker.stale_reads_ok = True
if broker.is_deleted(): if broker.is_deleted():
self.logger.timing_since('GET.timing', start_time)
return HTTPNotFound(request=req) return HTTPNotFound(request=req)
info = broker.get_info() info = broker.get_info()
resp_headers = { resp_headers = {
@ -244,14 +224,12 @@ class AccountController(object):
delimiter = get_param(req, 'delimiter') delimiter = get_param(req, 'delimiter')
if delimiter and (len(delimiter) > 1 or ord(delimiter) > 254): if delimiter and (len(delimiter) > 1 or ord(delimiter) > 254):
# delimiters can be made more flexible later # delimiters can be made more flexible later
self.logger.increment('GET.errors')
return HTTPPreconditionFailed(body='Bad delimiter') return HTTPPreconditionFailed(body='Bad delimiter')
limit = ACCOUNT_LISTING_LIMIT limit = ACCOUNT_LISTING_LIMIT
given_limit = get_param(req, 'limit') given_limit = get_param(req, 'limit')
if given_limit and given_limit.isdigit(): if given_limit and given_limit.isdigit():
limit = int(given_limit) limit = int(given_limit)
if limit > ACCOUNT_LISTING_LIMIT: if limit > ACCOUNT_LISTING_LIMIT:
self.logger.increment('GET.errors')
return HTTPPreconditionFailed(request=req, return HTTPPreconditionFailed(request=req,
body='Maximum limit is %d' % body='Maximum limit is %d' %
ACCOUNT_LISTING_LIMIT) ACCOUNT_LISTING_LIMIT)
@ -259,7 +237,6 @@ class AccountController(object):
end_marker = get_param(req, 'end_marker') end_marker = get_param(req, 'end_marker')
query_format = get_param(req, 'format') query_format = get_param(req, 'format')
except UnicodeDecodeError, err: except UnicodeDecodeError, err:
self.logger.increment('GET.errors')
return HTTPBadRequest(body='parameters not utf8', return HTTPBadRequest(body='parameters not utf8',
content_type='text/plain', request=req) content_type='text/plain', request=req)
if query_format: if query_format:
@ -271,7 +248,6 @@ class AccountController(object):
'text/xml'], 'text/xml'],
default_match='text/plain') default_match='text/plain')
except AssertionError, err: except AssertionError, err:
self.logger.increment('GET.errors')
return HTTPBadRequest(body='bad accept header: %s' % req.accept, return HTTPBadRequest(body='bad accept header: %s' % req.accept,
content_type='text/plain', request=req) content_type='text/plain', request=req)
account_list = broker.list_containers_iter(limit, marker, end_marker, account_list = broker.list_containers_iter(limit, marker, end_marker,
@ -301,66 +277,56 @@ class AccountController(object):
account_list = '\n'.join(output_list) account_list = '\n'.join(output_list)
else: else:
if not account_list: if not account_list:
self.logger.timing_since('GET.timing', start_time)
return HTTPNoContent(request=req, headers=resp_headers) return HTTPNoContent(request=req, headers=resp_headers)
account_list = '\n'.join(r[0] for r in account_list) + '\n' account_list = '\n'.join(r[0] for r in account_list) + '\n'
ret = Response(body=account_list, request=req, headers=resp_headers) ret = Response(body=account_list, request=req, headers=resp_headers)
ret.content_type = out_content_type ret.content_type = out_content_type
ret.charset = 'utf-8' ret.charset = 'utf-8'
self.logger.timing_since('GET.timing', start_time)
return ret return ret
@public @public
@timing_stats
def REPLICATE(self, req): def REPLICATE(self, req):
""" """
Handle HTTP REPLICATE request. Handle HTTP REPLICATE request.
Handler for RPC calls for account replication. Handler for RPC calls for account replication.
""" """
start_time = time.time()
try: try:
post_args = split_path(unquote(req.path), 3) post_args = split_path(unquote(req.path), 3)
drive, partition, hash = post_args drive, partition, hash = post_args
validate_device_partition(drive, partition) validate_device_partition(drive, partition)
except ValueError, err: except ValueError, err:
self.logger.increment('REPLICATE.errors')
return HTTPBadRequest(body=str(err), content_type='text/plain', return HTTPBadRequest(body=str(err), content_type='text/plain',
request=req) request=req)
if self.mount_check and not check_mount(self.root, drive): if self.mount_check and not check_mount(self.root, drive):
self.logger.increment('REPLICATE.errors')
return HTTPInsufficientStorage(drive=drive, request=req) return HTTPInsufficientStorage(drive=drive, request=req)
try: try:
args = json.load(req.environ['wsgi.input']) args = json.load(req.environ['wsgi.input'])
except ValueError, err: except ValueError, err:
self.logger.increment('REPLICATE.errors')
return HTTPBadRequest(body=str(err), content_type='text/plain') return HTTPBadRequest(body=str(err), content_type='text/plain')
ret = self.replicator_rpc.dispatch(post_args, args) ret = self.replicator_rpc.dispatch(post_args, args)
ret.request = req ret.request = req
self.logger.timing_since('REPLICATE.timing', start_time)
return ret return ret
@public @public
@timing_stats
def POST(self, req): def POST(self, req):
"""Handle HTTP POST request.""" """Handle HTTP POST request."""
start_time = time.time()
try: try:
drive, part, account = split_path(unquote(req.path), 3) drive, part, account = split_path(unquote(req.path), 3)
validate_device_partition(drive, part) validate_device_partition(drive, part)
except ValueError, err: except ValueError, err:
self.logger.increment('POST.errors')
return HTTPBadRequest(body=str(err), content_type='text/plain', return HTTPBadRequest(body=str(err), content_type='text/plain',
request=req) request=req)
if 'x-timestamp' not in req.headers or \ if 'x-timestamp' not in req.headers or \
not check_float(req.headers['x-timestamp']): not check_float(req.headers['x-timestamp']):
self.logger.increment('POST.errors')
return HTTPBadRequest(body='Missing or bad timestamp', return HTTPBadRequest(body='Missing or bad timestamp',
request=req, request=req,
content_type='text/plain') content_type='text/plain')
if self.mount_check and not check_mount(self.root, drive): if self.mount_check and not check_mount(self.root, drive):
self.logger.increment('POST.errors')
return HTTPInsufficientStorage(drive=drive, request=req) return HTTPInsufficientStorage(drive=drive, request=req)
broker = self._get_account_broker(drive, part, account) broker = self._get_account_broker(drive, part, account)
if broker.is_deleted(): if broker.is_deleted():
self.logger.timing_since('POST.timing', start_time)
return HTTPNotFound(request=req) return HTTPNotFound(request=req)
timestamp = normalize_timestamp(req.headers['x-timestamp']) timestamp = normalize_timestamp(req.headers['x-timestamp'])
metadata = {} metadata = {}
@ -369,7 +335,6 @@ class AccountController(object):
if key.lower().startswith('x-account-meta-')) if key.lower().startswith('x-account-meta-'))
if metadata: if metadata:
broker.update_metadata(metadata) broker.update_metadata(metadata)
self.logger.timing_since('POST.timing', start_time)
return HTTPNoContent(request=req) return HTTPNoContent(request=req)
def __call__(self, env, start_response): def __call__(self, env, start_response):

View File

@ -52,6 +52,7 @@ utf8_decoder = codecs.getdecoder('utf-8')
utf8_encoder = codecs.getencoder('utf-8') utf8_encoder = codecs.getencoder('utf-8')
from swift.common.exceptions import LockTimeout, MessageTimeout from swift.common.exceptions import LockTimeout, MessageTimeout
from swift.common.http import is_success, is_redirection, HTTP_NOT_FOUND
# logging doesn't import patched as cleanly as one would like # logging doesn't import patched as cleanly as one would like
from logging.handlers import SysLogHandler from logging.handlers import SysLogHandler
@ -463,6 +464,27 @@ class StatsdClient(object):
sample_rate) sample_rate)
def timing_stats(func):
"""
Decorator that logs timing events or errors for public methods in swift's
wsgi server controllers, based on response code.
"""
method = func.func_name
@functools.wraps(func)
def _timing_stats(ctrl, *args, **kwargs):
start_time = time.time()
resp = func(ctrl, *args, **kwargs)
if is_success(resp.status_int) or is_redirection(resp.status_int) or \
resp.status_int == HTTP_NOT_FOUND:
ctrl.logger.timing_since(method + '.timing', start_time)
else:
ctrl.logger.timing_since(method + '.errors.timing', start_time)
return resp
return _timing_stats
# double inheritance to support property with setter # double inheritance to support property with setter
class LogAdapter(logging.LoggerAdapter, object): class LogAdapter(logging.LoggerAdapter, object):
""" """

View File

@ -28,7 +28,7 @@ import swift.common.db
from swift.common.db import ContainerBroker from swift.common.db import ContainerBroker
from swift.common.utils import get_logger, get_param, hash_path, public, \ from swift.common.utils import get_logger, get_param, hash_path, public, \
normalize_timestamp, storage_directory, split_path, validate_sync_to, \ normalize_timestamp, storage_directory, split_path, validate_sync_to, \
config_true_value, validate_device_partition, json config_true_value, validate_device_partition, json, timing_stats
from swift.common.constraints import CONTAINER_LISTING_LIMIT, \ from swift.common.constraints import CONTAINER_LISTING_LIMIT, \
check_mount, check_float, check_utf8, FORMAT2CONTENT_TYPE check_mount, check_float, check_utf8, FORMAT2CONTENT_TYPE
from swift.common.bufferedhttp import http_connect from swift.common.bufferedhttp import http_connect
@ -141,24 +141,21 @@ class ContainerController(object):
return None return None
@public @public
@timing_stats
def DELETE(self, req): def DELETE(self, req):
"""Handle HTTP DELETE request.""" """Handle HTTP DELETE request."""
start_time = time.time()
try: try:
drive, part, account, container, obj = split_path( drive, part, account, container, obj = split_path(
unquote(req.path), 4, 5, True) unquote(req.path), 4, 5, True)
validate_device_partition(drive, part) validate_device_partition(drive, part)
except ValueError, err: except ValueError, err:
self.logger.increment('DELETE.errors')
return HTTPBadRequest(body=str(err), content_type='text/plain', return HTTPBadRequest(body=str(err), content_type='text/plain',
request=req) request=req)
if 'x-timestamp' not in req.headers or \ if 'x-timestamp' not in req.headers or \
not check_float(req.headers['x-timestamp']): not check_float(req.headers['x-timestamp']):
self.logger.increment('DELETE.errors')
return HTTPBadRequest(body='Missing timestamp', request=req, return HTTPBadRequest(body='Missing timestamp', request=req,
content_type='text/plain') content_type='text/plain')
if self.mount_check and not check_mount(self.root, drive): if self.mount_check and not check_mount(self.root, drive):
self.logger.increment('DELETE.errors')
return HTTPInsufficientStorage(drive=drive, request=req) return HTTPInsufficientStorage(drive=drive, request=req)
broker = self._get_container_broker(drive, part, account, container) broker = self._get_container_broker(drive, part, account, container)
if account.startswith(self.auto_create_account_prefix) and obj and \ if account.startswith(self.auto_create_account_prefix) and obj and \
@ -166,25 +163,20 @@ class ContainerController(object):
broker.initialize(normalize_timestamp( broker.initialize(normalize_timestamp(
req.headers.get('x-timestamp') or time.time())) req.headers.get('x-timestamp') or time.time()))
if not os.path.exists(broker.db_file): if not os.path.exists(broker.db_file):
self.logger.timing_since('DELETE.timing', start_time)
return HTTPNotFound() return HTTPNotFound()
if obj: # delete object if obj: # delete object
broker.delete_object(obj, req.headers.get('x-timestamp')) broker.delete_object(obj, req.headers.get('x-timestamp'))
self.logger.timing_since('DELETE.timing', start_time)
return HTTPNoContent(request=req) return HTTPNoContent(request=req)
else: else:
# delete container # delete container
if not broker.empty(): if not broker.empty():
self.logger.increment('DELETE.errors')
return HTTPConflict(request=req) return HTTPConflict(request=req)
existed = float(broker.get_info()['put_timestamp']) and \ existed = float(broker.get_info()['put_timestamp']) and \
not broker.is_deleted() not broker.is_deleted()
broker.delete_db(req.headers['X-Timestamp']) broker.delete_db(req.headers['X-Timestamp'])
if not broker.is_deleted(): if not broker.is_deleted():
self.logger.increment('DELETE.errors')
return HTTPConflict(request=req) return HTTPConflict(request=req)
resp = self.account_update(req, account, container, broker) resp = self.account_update(req, account, container, broker)
self.logger.timing_since('DELETE.timing', start_time)
if resp: if resp:
return resp return resp
if existed: if existed:
@ -192,30 +184,26 @@ class ContainerController(object):
return HTTPNotFound() return HTTPNotFound()
@public @public
@timing_stats
def PUT(self, req): def PUT(self, req):
"""Handle HTTP PUT request.""" """Handle HTTP PUT request."""
start_time = time.time()
try: try:
drive, part, account, container, obj = split_path( drive, part, account, container, obj = split_path(
unquote(req.path), 4, 5, True) unquote(req.path), 4, 5, True)
validate_device_partition(drive, part) validate_device_partition(drive, part)
except ValueError, err: except ValueError, err:
self.logger.increment('PUT.errors')
return HTTPBadRequest(body=str(err), content_type='text/plain', return HTTPBadRequest(body=str(err), content_type='text/plain',
request=req) request=req)
if 'x-timestamp' not in req.headers or \ if 'x-timestamp' not in req.headers or \
not check_float(req.headers['x-timestamp']): not check_float(req.headers['x-timestamp']):
self.logger.increment('PUT.errors')
return HTTPBadRequest(body='Missing timestamp', request=req, return HTTPBadRequest(body='Missing timestamp', request=req,
content_type='text/plain') content_type='text/plain')
if 'x-container-sync-to' in req.headers: if 'x-container-sync-to' in req.headers:
err = validate_sync_to(req.headers['x-container-sync-to'], err = validate_sync_to(req.headers['x-container-sync-to'],
self.allowed_sync_hosts) self.allowed_sync_hosts)
if err: if err:
self.logger.increment('PUT.errors')
return HTTPBadRequest(err) return HTTPBadRequest(err)
if self.mount_check and not check_mount(self.root, drive): if self.mount_check and not check_mount(self.root, drive):
self.logger.increment('PUT.errors')
return HTTPInsufficientStorage(drive=drive, request=req) return HTTPInsufficientStorage(drive=drive, request=req)
timestamp = normalize_timestamp(req.headers['x-timestamp']) timestamp = normalize_timestamp(req.headers['x-timestamp'])
broker = self._get_container_broker(drive, part, account, container) broker = self._get_container_broker(drive, part, account, container)
@ -224,12 +212,10 @@ class ContainerController(object):
not os.path.exists(broker.db_file): not os.path.exists(broker.db_file):
broker.initialize(timestamp) broker.initialize(timestamp)
if not os.path.exists(broker.db_file): if not os.path.exists(broker.db_file):
self.logger.timing_since('PUT.timing', start_time)
return HTTPNotFound() return HTTPNotFound()
broker.put_object(obj, timestamp, int(req.headers['x-size']), broker.put_object(obj, timestamp, int(req.headers['x-size']),
req.headers['x-content-type'], req.headers['x-content-type'],
req.headers['x-etag']) req.headers['x-etag'])
self.logger.timing_since('PUT.timing', start_time)
return HTTPCreated(request=req) return HTTPCreated(request=req)
else: # put container else: # put container
if not os.path.exists(broker.db_file): if not os.path.exists(broker.db_file):
@ -239,7 +225,6 @@ class ContainerController(object):
created = broker.is_deleted() created = broker.is_deleted()
broker.update_put_timestamp(timestamp) broker.update_put_timestamp(timestamp)
if broker.is_deleted(): if broker.is_deleted():
self.logger.increment('PUT.errors')
return HTTPConflict(request=req) return HTTPConflict(request=req)
metadata = {} metadata = {}
metadata.update( metadata.update(
@ -255,7 +240,6 @@ class ContainerController(object):
broker.set_x_container_sync_points(-1, -1) broker.set_x_container_sync_points(-1, -1)
broker.update_metadata(metadata) broker.update_metadata(metadata)
resp = self.account_update(req, account, container, broker) resp = self.account_update(req, account, container, broker)
self.logger.timing_since('PUT.timing', start_time)
if resp: if resp:
return resp return resp
if created: if created:
@ -264,25 +248,22 @@ class ContainerController(object):
return HTTPAccepted(request=req) return HTTPAccepted(request=req)
@public @public
@timing_stats
def HEAD(self, req): def HEAD(self, req):
"""Handle HTTP HEAD request.""" """Handle HTTP HEAD request."""
start_time = time.time()
try: try:
drive, part, account, container, obj = split_path( drive, part, account, container, obj = split_path(
unquote(req.path), 4, 5, True) unquote(req.path), 4, 5, True)
validate_device_partition(drive, part) validate_device_partition(drive, part)
except ValueError, err: except ValueError, err:
self.logger.increment('HEAD.errors')
return HTTPBadRequest(body=str(err), content_type='text/plain', return HTTPBadRequest(body=str(err), content_type='text/plain',
request=req) request=req)
if self.mount_check and not check_mount(self.root, drive): if self.mount_check and not check_mount(self.root, drive):
self.logger.increment('HEAD.errors')
return HTTPInsufficientStorage(drive=drive, request=req) return HTTPInsufficientStorage(drive=drive, request=req)
broker = self._get_container_broker(drive, part, account, container) broker = self._get_container_broker(drive, part, account, container)
broker.pending_timeout = 0.1 broker.pending_timeout = 0.1
broker.stale_reads_ok = True broker.stale_reads_ok = True
if broker.is_deleted(): if broker.is_deleted():
self.logger.timing_since('HEAD.timing', start_time)
return HTTPNotFound(request=req) return HTTPNotFound(request=req)
info = broker.get_info() info = broker.get_info()
headers = { headers = {
@ -305,32 +286,27 @@ class ContainerController(object):
'text/xml'], 'text/xml'],
default_match='text/plain') default_match='text/plain')
except AssertionError, err: except AssertionError, err:
self.logger.increment('HEAD.errors')
return HTTPBadRequest(body='bad accept header: %s' % req.accept, return HTTPBadRequest(body='bad accept header: %s' % req.accept,
content_type='text/plain', request=req) content_type='text/plain', request=req)
self.logger.timing_since('HEAD.timing', start_time)
return HTTPNoContent(request=req, headers=headers, charset='utf-8') return HTTPNoContent(request=req, headers=headers, charset='utf-8')
@public @public
@timing_stats
def GET(self, req): def GET(self, req):
"""Handle HTTP GET request.""" """Handle HTTP GET request."""
start_time = time.time()
try: try:
drive, part, account, container, obj = split_path( drive, part, account, container, obj = split_path(
unquote(req.path), 4, 5, True) unquote(req.path), 4, 5, True)
validate_device_partition(drive, part) validate_device_partition(drive, part)
except ValueError, err: except ValueError, err:
self.logger.increment('GET.errors')
return HTTPBadRequest(body=str(err), content_type='text/plain', return HTTPBadRequest(body=str(err), content_type='text/plain',
request=req) request=req)
if self.mount_check and not check_mount(self.root, drive): if self.mount_check and not check_mount(self.root, drive):
self.logger.increment('GET.errors')
return HTTPInsufficientStorage(drive=drive, request=req) return HTTPInsufficientStorage(drive=drive, request=req)
broker = self._get_container_broker(drive, part, account, container) broker = self._get_container_broker(drive, part, account, container)
broker.pending_timeout = 0.1 broker.pending_timeout = 0.1
broker.stale_reads_ok = True broker.stale_reads_ok = True
if broker.is_deleted(): if broker.is_deleted():
self.logger.timing_since('GET.timing', start_time)
return HTTPNotFound(request=req) return HTTPNotFound(request=req)
info = broker.get_info() info = broker.get_info()
resp_headers = { resp_headers = {
@ -363,7 +339,6 @@ class ContainerController(object):
body='Maximum limit is %d' % CONTAINER_LISTING_LIMIT) body='Maximum limit is %d' % CONTAINER_LISTING_LIMIT)
query_format = get_param(req, 'format') query_format = get_param(req, 'format')
except UnicodeDecodeError, err: except UnicodeDecodeError, err:
self.logger.increment('GET.errors')
return HTTPBadRequest(body='parameters not utf8', return HTTPBadRequest(body='parameters not utf8',
content_type='text/plain', request=req) content_type='text/plain', request=req)
if query_format: if query_format:
@ -375,7 +350,6 @@ class ContainerController(object):
'text/xml'], 'text/xml'],
default_match='text/plain') default_match='text/plain')
except AssertionError, err: except AssertionError, err:
self.logger.increment('GET.errors')
return HTTPBadRequest(body='bad accept header: %s' % req.accept, return HTTPBadRequest(body='bad accept header: %s' % req.accept,
content_type='text/plain', request=req) content_type='text/plain', request=req)
container_list = broker.list_objects_iter(limit, marker, end_marker, container_list = broker.list_objects_iter(limit, marker, end_marker,
@ -421,70 +395,59 @@ class ContainerController(object):
''.join(xml_output), '</container>']) ''.join(xml_output), '</container>'])
else: else:
if not container_list: if not container_list:
self.logger.timing_since('GET.timing', start_time)
return HTTPNoContent(request=req, headers=resp_headers) return HTTPNoContent(request=req, headers=resp_headers)
container_list = '\n'.join(r[0] for r in container_list) + '\n' container_list = '\n'.join(r[0] for r in container_list) + '\n'
ret = Response(body=container_list, request=req, headers=resp_headers) ret = Response(body=container_list, request=req, headers=resp_headers)
ret.content_type = out_content_type ret.content_type = out_content_type
ret.charset = 'utf-8' ret.charset = 'utf-8'
self.logger.timing_since('GET.timing', start_time)
return ret return ret
@public @public
@timing_stats
def REPLICATE(self, req): def REPLICATE(self, req):
""" """
Handle HTTP REPLICATE request (json-encoded RPC calls for replication.) Handle HTTP REPLICATE request (json-encoded RPC calls for replication.)
""" """
start_time = time.time()
try: try:
post_args = split_path(unquote(req.path), 3) post_args = split_path(unquote(req.path), 3)
drive, partition, hash = post_args drive, partition, hash = post_args
validate_device_partition(drive, partition) validate_device_partition(drive, partition)
except ValueError, err: except ValueError, err:
self.logger.increment('REPLICATE.errors')
return HTTPBadRequest(body=str(err), content_type='text/plain', return HTTPBadRequest(body=str(err), content_type='text/plain',
request=req) request=req)
if self.mount_check and not check_mount(self.root, drive): if self.mount_check and not check_mount(self.root, drive):
self.logger.increment('REPLICATE.errors')
return HTTPInsufficientStorage(drive=drive, request=req) return HTTPInsufficientStorage(drive=drive, request=req)
try: try:
args = json.load(req.environ['wsgi.input']) args = json.load(req.environ['wsgi.input'])
except ValueError, err: except ValueError, err:
self.logger.increment('REPLICATE.errors')
return HTTPBadRequest(body=str(err), content_type='text/plain') return HTTPBadRequest(body=str(err), content_type='text/plain')
ret = self.replicator_rpc.dispatch(post_args, args) ret = self.replicator_rpc.dispatch(post_args, args)
ret.request = req ret.request = req
self.logger.timing_since('REPLICATE.timing', start_time)
return ret return ret
@public @public
@timing_stats
def POST(self, req): def POST(self, req):
"""Handle HTTP POST request.""" """Handle HTTP POST request."""
start_time = time.time()
try: try:
drive, part, account, container = split_path(unquote(req.path), 4) drive, part, account, container = split_path(unquote(req.path), 4)
validate_device_partition(drive, part) validate_device_partition(drive, part)
except ValueError, err: except ValueError, err:
self.logger.increment('POST.errors')
return HTTPBadRequest(body=str(err), content_type='text/plain', return HTTPBadRequest(body=str(err), content_type='text/plain',
request=req) request=req)
if 'x-timestamp' not in req.headers or \ if 'x-timestamp' not in req.headers or \
not check_float(req.headers['x-timestamp']): not check_float(req.headers['x-timestamp']):
self.logger.increment('POST.errors')
return HTTPBadRequest(body='Missing or bad timestamp', return HTTPBadRequest(body='Missing or bad timestamp',
request=req, content_type='text/plain') request=req, content_type='text/plain')
if 'x-container-sync-to' in req.headers: if 'x-container-sync-to' in req.headers:
err = validate_sync_to(req.headers['x-container-sync-to'], err = validate_sync_to(req.headers['x-container-sync-to'],
self.allowed_sync_hosts) self.allowed_sync_hosts)
if err: if err:
self.logger.increment('POST.errors')
return HTTPBadRequest(err) return HTTPBadRequest(err)
if self.mount_check and not check_mount(self.root, drive): if self.mount_check and not check_mount(self.root, drive):
self.logger.increment('POST.errors')
return HTTPInsufficientStorage(drive=drive, request=req) return HTTPInsufficientStorage(drive=drive, request=req)
broker = self._get_container_broker(drive, part, account, container) broker = self._get_container_broker(drive, part, account, container)
if broker.is_deleted(): if broker.is_deleted():
self.logger.timing_since('POST.timing', start_time)
return HTTPNotFound(request=req) return HTTPNotFound(request=req)
timestamp = normalize_timestamp(req.headers['x-timestamp']) timestamp = normalize_timestamp(req.headers['x-timestamp'])
metadata = {} metadata = {}
@ -499,7 +462,6 @@ class ContainerController(object):
broker.metadata['X-Container-Sync-To'][0]: broker.metadata['X-Container-Sync-To'][0]:
broker.set_x_container_sync_points(-1, -1) broker.set_x_container_sync_points(-1, -1)
broker.update_metadata(metadata) broker.update_metadata(metadata)
self.logger.timing_since('POST.timing', start_time)
return HTTPNoContent(request=req) return HTTPNoContent(request=req)
def __call__(self, env, start_response): def __call__(self, env, start_response):

View File

@ -33,7 +33,7 @@ from eventlet import sleep, Timeout, tpool
from swift.common.utils import mkdirs, normalize_timestamp, public, \ from swift.common.utils import mkdirs, normalize_timestamp, public, \
storage_directory, hash_path, renamer, fallocate, fsync, \ storage_directory, hash_path, renamer, fallocate, fsync, \
split_path, drop_buffer_cache, get_logger, write_pickle, \ split_path, drop_buffer_cache, get_logger, write_pickle, \
config_true_value, validate_device_partition config_true_value, validate_device_partition, timing_stats
from swift.common.bufferedhttp import http_connect from swift.common.bufferedhttp import http_connect
from swift.common.constraints import check_object_creation, check_mount, \ from swift.common.constraints import check_object_creation, check_mount, \
check_float, check_utf8 check_float, check_utf8
@ -511,36 +511,31 @@ class ObjectController(object):
host, partition, contdevice, headers_out, objdevice) host, partition, contdevice, headers_out, objdevice)
@public @public
@timing_stats
def POST(self, request): def POST(self, request):
"""Handle HTTP POST requests for the Swift Object Server.""" """Handle HTTP POST requests for the Swift Object Server."""
start_time = time.time()
try: try:
device, partition, account, container, obj = \ device, partition, account, container, obj = \
split_path(unquote(request.path), 5, 5, True) split_path(unquote(request.path), 5, 5, True)
validate_device_partition(device, partition) validate_device_partition(device, partition)
except ValueError, err: except ValueError, err:
self.logger.increment('POST.errors')
return HTTPBadRequest(body=str(err), request=request, return HTTPBadRequest(body=str(err), request=request,
content_type='text/plain') content_type='text/plain')
if 'x-timestamp' not in request.headers or \ if 'x-timestamp' not in request.headers or \
not check_float(request.headers['x-timestamp']): not check_float(request.headers['x-timestamp']):
self.logger.increment('POST.errors')
return HTTPBadRequest(body='Missing timestamp', request=request, return HTTPBadRequest(body='Missing timestamp', request=request,
content_type='text/plain') content_type='text/plain')
new_delete_at = int(request.headers.get('X-Delete-At') or 0) new_delete_at = int(request.headers.get('X-Delete-At') or 0)
if new_delete_at and new_delete_at < time.time(): if new_delete_at and new_delete_at < time.time():
self.logger.increment('POST.errors')
return HTTPBadRequest(body='X-Delete-At in past', request=request, return HTTPBadRequest(body='X-Delete-At in past', request=request,
content_type='text/plain') content_type='text/plain')
if self.mount_check and not check_mount(self.devices, device): if self.mount_check and not check_mount(self.devices, device):
self.logger.increment('POST.errors')
return HTTPInsufficientStorage(drive=device, request=request) return HTTPInsufficientStorage(drive=device, request=request)
file = DiskFile(self.devices, device, partition, account, container, file = DiskFile(self.devices, device, partition, account, container,
obj, self.logger, disk_chunk_size=self.disk_chunk_size) obj, self.logger, disk_chunk_size=self.disk_chunk_size)
if 'X-Delete-At' in file.metadata and \ if 'X-Delete-At' in file.metadata and \
int(file.metadata['X-Delete-At']) <= time.time(): int(file.metadata['X-Delete-At']) <= time.time():
self.logger.timing_since('POST.timing', start_time)
return HTTPNotFound(request=request) return HTTPNotFound(request=request)
if file.is_deleted(): if file.is_deleted():
response_class = HTTPNotFound response_class = HTTPNotFound
@ -568,36 +563,30 @@ class ObjectController(object):
container, obj, request.headers, device) container, obj, request.headers, device)
with file.mkstemp() as (fd, tmppath): with file.mkstemp() as (fd, tmppath):
file.put(fd, tmppath, metadata, extension='.meta') file.put(fd, tmppath, metadata, extension='.meta')
self.logger.timing_since('POST.timing', start_time)
return response_class(request=request) return response_class(request=request)
@public @public
@timing_stats
def PUT(self, request): def PUT(self, request):
"""Handle HTTP PUT requests for the Swift Object Server.""" """Handle HTTP PUT requests for the Swift Object Server."""
start_time = time.time()
try: try:
device, partition, account, container, obj = \ device, partition, account, container, obj = \
split_path(unquote(request.path), 5, 5, True) split_path(unquote(request.path), 5, 5, True)
validate_device_partition(device, partition) validate_device_partition(device, partition)
except ValueError, err: except ValueError, err:
self.logger.increment('PUT.errors')
return HTTPBadRequest(body=str(err), request=request, return HTTPBadRequest(body=str(err), request=request,
content_type='text/plain') content_type='text/plain')
if self.mount_check and not check_mount(self.devices, device): if self.mount_check and not check_mount(self.devices, device):
self.logger.increment('PUT.errors')
return HTTPInsufficientStorage(drive=device, request=request) return HTTPInsufficientStorage(drive=device, request=request)
if 'x-timestamp' not in request.headers or \ if 'x-timestamp' not in request.headers or \
not check_float(request.headers['x-timestamp']): not check_float(request.headers['x-timestamp']):
self.logger.increment('PUT.errors')
return HTTPBadRequest(body='Missing timestamp', request=request, return HTTPBadRequest(body='Missing timestamp', request=request,
content_type='text/plain') content_type='text/plain')
error_response = check_object_creation(request, obj) error_response = check_object_creation(request, obj)
if error_response: if error_response:
self.logger.increment('PUT.errors')
return error_response return error_response
new_delete_at = int(request.headers.get('X-Delete-At') or 0) new_delete_at = int(request.headers.get('X-Delete-At') or 0)
if new_delete_at and new_delete_at < time.time(): if new_delete_at and new_delete_at < time.time():
self.logger.increment('PUT.errors')
return HTTPBadRequest(body='X-Delete-At in past', request=request, return HTTPBadRequest(body='X-Delete-At in past', request=request,
content_type='text/plain') content_type='text/plain')
file = DiskFile(self.devices, device, partition, account, container, file = DiskFile(self.devices, device, partition, account, container,
@ -674,23 +663,20 @@ class ObjectController(object):
'x-trans-id': request.headers.get('x-trans-id', '-')}, 'x-trans-id': request.headers.get('x-trans-id', '-')},
device) device)
resp = HTTPCreated(request=request, etag=etag) resp = HTTPCreated(request=request, etag=etag)
self.logger.timing_since('PUT.timing', start_time)
return resp return resp
@public @public
@timing_stats
def GET(self, request): def GET(self, request):
"""Handle HTTP GET requests for the Swift Object Server.""" """Handle HTTP GET requests for the Swift Object Server."""
start_time = time.time()
try: try:
device, partition, account, container, obj = \ device, partition, account, container, obj = \
split_path(unquote(request.path), 5, 5, True) split_path(unquote(request.path), 5, 5, True)
validate_device_partition(device, partition) validate_device_partition(device, partition)
except ValueError, err: except ValueError, err:
self.logger.increment('GET.errors')
return HTTPBadRequest(body=str(err), request=request, return HTTPBadRequest(body=str(err), request=request,
content_type='text/plain') content_type='text/plain')
if self.mount_check and not check_mount(self.devices, device): if self.mount_check and not check_mount(self.devices, device):
self.logger.increment('GET.errors')
return HTTPInsufficientStorage(drive=device, request=request) return HTTPInsufficientStorage(drive=device, request=request)
file = DiskFile(self.devices, device, partition, account, container, file = DiskFile(self.devices, device, partition, account, container,
obj, self.logger, keep_data_fp=True, obj, self.logger, keep_data_fp=True,
@ -700,54 +686,45 @@ class ObjectController(object):
('X-Delete-At' in file.metadata and ('X-Delete-At' in file.metadata and
int(file.metadata['X-Delete-At']) <= time.time()): int(file.metadata['X-Delete-At']) <= time.time()):
if request.headers.get('if-match') == '*': if request.headers.get('if-match') == '*':
self.logger.timing_since('GET.timing', start_time)
return HTTPPreconditionFailed(request=request) return HTTPPreconditionFailed(request=request)
else: else:
self.logger.timing_since('GET.timing', start_time)
return HTTPNotFound(request=request) return HTTPNotFound(request=request)
try: try:
file_size = file.get_data_file_size() file_size = file.get_data_file_size()
except (DiskFileError, DiskFileNotExist): except (DiskFileError, DiskFileNotExist):
file.quarantine() file.quarantine()
self.logger.timing_since('GET.timing', start_time)
return HTTPNotFound(request=request) return HTTPNotFound(request=request)
if request.headers.get('if-match') not in (None, '*') and \ if request.headers.get('if-match') not in (None, '*') and \
file.metadata['ETag'] not in request.if_match: file.metadata['ETag'] not in request.if_match:
file.close() file.close()
self.logger.timing_since('GET.timing', start_time)
return HTTPPreconditionFailed(request=request) return HTTPPreconditionFailed(request=request)
if request.headers.get('if-none-match') is not None: if request.headers.get('if-none-match') is not None:
if file.metadata['ETag'] in request.if_none_match: if file.metadata['ETag'] in request.if_none_match:
resp = HTTPNotModified(request=request) resp = HTTPNotModified(request=request)
resp.etag = file.metadata['ETag'] resp.etag = file.metadata['ETag']
file.close() file.close()
self.logger.timing_since('GET.timing', start_time)
return resp return resp
try: try:
if_unmodified_since = request.if_unmodified_since if_unmodified_since = request.if_unmodified_since
except (OverflowError, ValueError): except (OverflowError, ValueError):
# catches timestamps before the epoch # catches timestamps before the epoch
self.logger.increment('GET.errors')
return HTTPPreconditionFailed(request=request) return HTTPPreconditionFailed(request=request)
if if_unmodified_since and \ if if_unmodified_since and \
datetime.fromtimestamp( datetime.fromtimestamp(
float(file.metadata['X-Timestamp']), UTC) > \ float(file.metadata['X-Timestamp']), UTC) > \
if_unmodified_since: if_unmodified_since:
file.close() file.close()
self.logger.timing_since('GET.timing', start_time)
return HTTPPreconditionFailed(request=request) return HTTPPreconditionFailed(request=request)
try: try:
if_modified_since = request.if_modified_since if_modified_since = request.if_modified_since
except (OverflowError, ValueError): except (OverflowError, ValueError):
# catches timestamps before the epoch # catches timestamps before the epoch
self.logger.increment('GET.errors')
return HTTPPreconditionFailed(request=request) return HTTPPreconditionFailed(request=request)
if if_modified_since and \ if if_modified_since and \
datetime.fromtimestamp( datetime.fromtimestamp(
float(file.metadata['X-Timestamp']), UTC) < \ float(file.metadata['X-Timestamp']), UTC) < \
if_modified_since: if_modified_since:
file.close() file.close()
self.logger.timing_since('GET.timing', start_time)
return HTTPNotModified(request=request) return HTTPNotModified(request=request)
response = Response(app_iter=file, response = Response(app_iter=file,
request=request, conditional_response=True) request=request, conditional_response=True)
@ -768,38 +745,33 @@ class ObjectController(object):
if 'Content-Encoding' in file.metadata: if 'Content-Encoding' in file.metadata:
response.content_encoding = file.metadata['Content-Encoding'] response.content_encoding = file.metadata['Content-Encoding']
response.headers['X-Timestamp'] = file.metadata['X-Timestamp'] response.headers['X-Timestamp'] = file.metadata['X-Timestamp']
self.logger.timing_since('GET.timing', start_time)
return request.get_response(response) return request.get_response(response)
@public @public
@timing_stats
def HEAD(self, request): def HEAD(self, request):
"""Handle HTTP HEAD requests for the Swift Object Server.""" """Handle HTTP HEAD requests for the Swift Object Server."""
start_time = time.time()
try: try:
device, partition, account, container, obj = \ device, partition, account, container, obj = \
split_path(unquote(request.path), 5, 5, True) split_path(unquote(request.path), 5, 5, True)
validate_device_partition(device, partition) validate_device_partition(device, partition)
except ValueError, err: except ValueError, err:
self.logger.increment('HEAD.errors')
resp = HTTPBadRequest(request=request) resp = HTTPBadRequest(request=request)
resp.content_type = 'text/plain' resp.content_type = 'text/plain'
resp.body = str(err) resp.body = str(err)
return resp return resp
if self.mount_check and not check_mount(self.devices, device): if self.mount_check and not check_mount(self.devices, device):
self.logger.increment('HEAD.errors')
return HTTPInsufficientStorage(drive=device, request=request) return HTTPInsufficientStorage(drive=device, request=request)
file = DiskFile(self.devices, device, partition, account, container, file = DiskFile(self.devices, device, partition, account, container,
obj, self.logger, disk_chunk_size=self.disk_chunk_size) obj, self.logger, disk_chunk_size=self.disk_chunk_size)
if file.is_deleted() or \ if file.is_deleted() or \
('X-Delete-At' in file.metadata and ('X-Delete-At' in file.metadata and
int(file.metadata['X-Delete-At']) <= time.time()): int(file.metadata['X-Delete-At']) <= time.time()):
self.logger.timing_since('HEAD.timing', start_time)
return HTTPNotFound(request=request) return HTTPNotFound(request=request)
try: try:
file_size = file.get_data_file_size() file_size = file.get_data_file_size()
except (DiskFileError, DiskFileNotExist): except (DiskFileError, DiskFileNotExist):
file.quarantine() file.quarantine()
self.logger.timing_since('HEAD.timing', start_time)
return HTTPNotFound(request=request) return HTTPNotFound(request=request)
response = Response(request=request, conditional_response=True) response = Response(request=request, conditional_response=True)
response.headers['Content-Type'] = file.metadata.get( response.headers['Content-Type'] = file.metadata.get(
@ -815,28 +787,24 @@ class ObjectController(object):
response.content_length = file_size response.content_length = file_size
if 'Content-Encoding' in file.metadata: if 'Content-Encoding' in file.metadata:
response.content_encoding = file.metadata['Content-Encoding'] response.content_encoding = file.metadata['Content-Encoding']
self.logger.timing_since('HEAD.timing', start_time)
return response return response
@public @public
@timing_stats
def DELETE(self, request): def DELETE(self, request):
"""Handle HTTP DELETE requests for the Swift Object Server.""" """Handle HTTP DELETE requests for the Swift Object Server."""
start_time = time.time()
try: try:
device, partition, account, container, obj = \ device, partition, account, container, obj = \
split_path(unquote(request.path), 5, 5, True) split_path(unquote(request.path), 5, 5, True)
validate_device_partition(device, partition) validate_device_partition(device, partition)
except ValueError, e: except ValueError, e:
self.logger.increment('DELETE.errors')
return HTTPBadRequest(body=str(e), request=request, return HTTPBadRequest(body=str(e), request=request,
content_type='text/plain') content_type='text/plain')
if 'x-timestamp' not in request.headers or \ if 'x-timestamp' not in request.headers or \
not check_float(request.headers['x-timestamp']): not check_float(request.headers['x-timestamp']):
self.logger.increment('DELETE.errors')
return HTTPBadRequest(body='Missing timestamp', request=request, return HTTPBadRequest(body='Missing timestamp', request=request,
content_type='text/plain') content_type='text/plain')
if self.mount_check and not check_mount(self.devices, device): if self.mount_check and not check_mount(self.devices, device):
self.logger.increment('DELETE.errors')
return HTTPInsufficientStorage(drive=device, request=request) return HTTPInsufficientStorage(drive=device, request=request)
response_class = HTTPNoContent response_class = HTTPNoContent
file = DiskFile(self.devices, device, partition, account, container, file = DiskFile(self.devices, device, partition, account, container,
@ -844,7 +812,6 @@ class ObjectController(object):
if 'x-if-delete-at' in request.headers and \ if 'x-if-delete-at' in request.headers and \
int(request.headers['x-if-delete-at']) != \ int(request.headers['x-if-delete-at']) != \
int(file.metadata.get('X-Delete-At') or 0): int(file.metadata.get('X-Delete-At') or 0):
self.logger.timing_since('DELETE.timing', start_time)
return HTTPPreconditionFailed( return HTTPPreconditionFailed(
request=request, request=request,
body='X-If-Delete-At and X-Delete-At do not match') body='X-If-Delete-At and X-Delete-At do not match')
@ -869,33 +836,29 @@ class ObjectController(object):
'x-trans-id': request.headers.get('x-trans-id', '-')}, 'x-trans-id': request.headers.get('x-trans-id', '-')},
device) device)
resp = response_class(request=request) resp = response_class(request=request)
self.logger.timing_since('DELETE.timing', start_time)
return resp return resp
@public @public
@timing_stats
def REPLICATE(self, request): def REPLICATE(self, request):
""" """
Handle REPLICATE requests for the Swift Object Server. This is used Handle REPLICATE requests for the Swift Object Server. This is used
by the object replicator to get hashes for directories. by the object replicator to get hashes for directories.
""" """
start_time = time.time()
try: try:
device, partition, suffix = split_path( device, partition, suffix = split_path(
unquote(request.path), 2, 3, True) unquote(request.path), 2, 3, True)
validate_device_partition(device, partition) validate_device_partition(device, partition)
except ValueError, e: except ValueError, e:
self.logger.increment('REPLICATE.errors')
return HTTPBadRequest(body=str(e), request=request, return HTTPBadRequest(body=str(e), request=request,
content_type='text/plain') content_type='text/plain')
if self.mount_check and not check_mount(self.devices, device): if self.mount_check and not check_mount(self.devices, device):
self.logger.increment('REPLICATE.errors')
return HTTPInsufficientStorage(drive=device, request=request) return HTTPInsufficientStorage(drive=device, request=request)
path = os.path.join(self.devices, device, DATADIR, partition) path = os.path.join(self.devices, device, DATADIR, partition)
if not os.path.exists(path): if not os.path.exists(path):
mkdirs(path) mkdirs(path)
suffixes = suffix.split('-') if suffix else [] suffixes = suffix.split('-') if suffix else []
_junk, hashes = tpool_reraise(get_hashes, path, recalculate=suffixes) _junk, hashes = tpool_reraise(get_hashes, path, recalculate=suffixes)
self.logger.timing_since('REPLICATE.timing', start_time)
return Response(body=pickle.dumps(hashes)) return Response(body=pickle.dumps(hashes))
def __call__(self, env, start_response): def __call__(self, env, start_response):

View File

@ -40,6 +40,7 @@ from eventlet import sleep
from swift.common.exceptions import (Timeout, MessageTimeout, from swift.common.exceptions import (Timeout, MessageTimeout,
ConnectionTimeout) ConnectionTimeout)
from swift.common import utils from swift.common import utils
from swift.common.swob import Response
class MockOs(): class MockOs():
@ -994,6 +995,43 @@ class TestStatsdLogging(unittest.TestCase):
payload = mock_socket.sent[0][0] payload = mock_socket.sent[0][0]
self.assertTrue(payload.endswith("|@0.5")) self.assertTrue(payload.endswith("|@0.5"))
def test_timing_stats(self):
class MockController(object):
def __init__(self, status):
self.status = status
self.logger = self
self.args = ()
self.called = 'UNKNOWN'
def timing_since(self, *args):
self.called = 'timing'
self.args = args
@utils.timing_stats
def METHOD(controller):
return Response(status=controller.status)
mock_controller = MockController(200)
METHOD(mock_controller)
self.assertEquals(mock_controller.called, 'timing')
self.assertEquals(len(mock_controller.args), 2)
self.assertEquals(mock_controller.args[0], 'METHOD.timing')
self.assert_(mock_controller.args[1] > 0)
mock_controller = MockController(404)
METHOD(mock_controller)
self.assertEquals(len(mock_controller.args), 2)
self.assertEquals(mock_controller.called, 'timing')
self.assertEquals(mock_controller.args[0], 'METHOD.timing')
self.assert_(mock_controller.args[1] > 0)
mock_controller = MockController(401)
METHOD(mock_controller)
self.assertEquals(len(mock_controller.args), 2)
self.assertEquals(mock_controller.called, 'timing')
self.assertEquals(mock_controller.args[0], 'METHOD.errors.timing')
self.assert_(mock_controller.args[1] > 0)
class TestStatsdLoggingDelegation(unittest.TestCase): class TestStatsdLoggingDelegation(unittest.TestCase):
def setUp(self): def setUp(self):