| OLD | NEW |
| 1 # -*- coding: utf-8 -*- |
| 1 # Copyright 2012 Google Inc. All Rights Reserved. | 2 # Copyright 2012 Google Inc. All Rights Reserved. |
| 2 # | 3 # |
| 3 # Licensed under the Apache License, Version 2.0 (the "License"); | 4 # Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 # you may not use this file except in compliance with the License. | 5 # you may not use this file except in compliance with the License. |
| 5 # You may obtain a copy of the License at | 6 # You may obtain a copy of the License at |
| 6 # | 7 # |
| 7 # http://www.apache.org/licenses/LICENSE-2.0 | 8 # http://www.apache.org/licenses/LICENSE-2.0 |
| 8 # | 9 # |
| 9 # Unless required by applicable law or agreed to in writing, software | 10 # Unless required by applicable law or agreed to in writing, software |
| 10 # distributed under the License is distributed on an "AS IS" BASIS, | 11 # distributed under the License is distributed on an "AS IS" BASIS, |
| 11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 # See the License for the specific language governing permissions and | 13 # See the License for the specific language governing permissions and |
| 13 # limitations under the License. | 14 # limitations under the License. |
| 14 | |
| 15 """Contains the perfdiag gsutil command.""" | 15 """Contains the perfdiag gsutil command.""" |
| 16 | 16 |
| 17 # Get the system logging module, not our local logging module. | |
| 18 from __future__ import absolute_import | 17 from __future__ import absolute_import |
| 19 | 18 |
| 20 import calendar | 19 import calendar |
| 21 from collections import defaultdict | 20 from collections import defaultdict |
| 22 import contextlib | 21 import contextlib |
| 22 import cStringIO |
| 23 import datetime | 23 import datetime |
| 24 import httplib |
| 24 import json | 25 import json |
| 25 import logging | 26 import logging |
| 26 import math | 27 import math |
| 27 import multiprocessing | 28 import multiprocessing |
| 28 import os | 29 import os |
| 29 import random | 30 import random |
| 30 import re | 31 import re |
| 31 import socket | 32 import socket |
| 32 import string | 33 import string |
| 33 import subprocess | 34 import subprocess |
| 34 import tempfile | 35 import tempfile |
| 35 import time | 36 import time |
| 36 | 37 |
| 38 from apiclient import errors as apiclient_errors |
| 37 import boto | 39 import boto |
| 38 from boto.utils import compute_md5 | |
| 39 import boto.gs.connection | 40 import boto.gs.connection |
| 40 | 41 |
| 41 import gslib | 42 import gslib |
| 43 from gslib.cloud_api import NotFoundException |
| 44 from gslib.cloud_api import ServiceException |
| 45 from gslib.cloud_api_helper import GetDownloadSerializationDict |
| 42 from gslib.command import Command | 46 from gslib.command import Command |
| 43 from gslib.command import COMMAND_NAME | |
| 44 from gslib.command import COMMAND_NAME_ALIASES | |
| 45 from gslib.command import DummyArgChecker | 47 from gslib.command import DummyArgChecker |
| 46 from gslib.command import FILE_URIS_OK | |
| 47 from gslib.command import MAX_ARGS | |
| 48 from gslib.command import MIN_ARGS | |
| 49 from gslib.command import PROVIDER_URIS_OK | |
| 50 from gslib.command import SUPPORTED_SUB_ARGS | |
| 51 from gslib.command import URIS_START_ARG | |
| 52 #from gslib.command_runner import CommandRunner | |
| 53 from gslib.commands import config | 48 from gslib.commands import config |
| 49 from gslib.cs_api_map import ApiSelector |
| 54 from gslib.exception import CommandException | 50 from gslib.exception import CommandException |
| 55 from gslib.help_provider import HELP_NAME | 51 from gslib.hashing_helper import CalculateB64EncodedMd5FromContents |
| 56 from gslib.help_provider import HELP_NAME_ALIASES | 52 from gslib.storage_url import StorageUrlFromString |
| 57 from gslib.help_provider import HELP_ONE_LINE_SUMMARY | 53 from gslib.third_party.storage_apitools import storage_v1_messages as apitools_m
essages |
| 58 from gslib.help_provider import HELP_TEXT | 54 from gslib.util import GetCloudApiInstance |
| 59 from gslib.help_provider import HELP_TYPE | 55 from gslib.util import GetMaxRetryDelay |
| 60 from gslib.help_provider import HelpType | |
| 61 from gslib.util import GetBotoConfigFileList | |
| 62 from gslib.util import HumanReadableToBytes | 56 from gslib.util import HumanReadableToBytes |
| 63 from gslib.util import IS_LINUX | 57 from gslib.util import IS_LINUX |
| 64 from gslib.util import MakeBitsHumanReadable | 58 from gslib.util import MakeBitsHumanReadable |
| 65 from gslib.util import MakeHumanReadable | 59 from gslib.util import MakeHumanReadable |
| 66 from gslib.util import Percentile | 60 from gslib.util import Percentile |
| 61 from gslib.util import ResumableThreshold |
| 67 | 62 |
| 68 _detailed_help_text = (""" | 63 _DETAILED_HELP_TEXT = (""" |
| 69 <B>SYNOPSIS</B> | 64 <B>SYNOPSIS</B> |
| 70 gsutil perfdiag [-i in.json] [-o out.json] [-n iterations] [-c processes] | 65 gsutil perfdiag [-i in.json] [-o out.json] [-n iterations] [-c processes] |
| 71 [-k threads] [-s size] [-t tests] uri... | 66 [-k threads] [-s size] [-t tests] url... |
| 72 | 67 |
| 73 | 68 |
| 74 <B>DESCRIPTION</B> | 69 <B>DESCRIPTION</B> |
| 75 The perfdiag command runs a suite of diagnostic tests for a given Google | 70 The perfdiag command runs a suite of diagnostic tests for a given Google |
| 76 Storage bucket. | 71 Storage bucket. |
| 77 | 72 |
| 78 The 'uri' parameter must name an existing bucket (e.g. gs://foo) to which | 73 The 'url' parameter must name an existing bucket (e.g. gs://foo) to which |
| 79 the user has write permission. Several test files will be uploaded to and | 74 the user has write permission. Several test files will be uploaded to and |
| 80 downloaded from this bucket. All test files will be deleted at the completion | 75 downloaded from this bucket. All test files will be deleted at the completion |
| 81 of the diagnostic if it finishes successfully. | 76 of the diagnostic if it finishes successfully. |
| 82 | 77 |
| 83 gsutil performance can be impacted by many factors at the client, server, | 78 gsutil performance can be impacted by many factors at the client, server, |
| 84 and in-between, such as: CPU speed; available memory; the access path to the | 79 and in-between, such as: CPU speed; available memory; the access path to the |
| 85 local disk; network bandwidth; contention and error rates along the path | 80 local disk; network bandwidth; contention and error rates along the path |
| 86 between gsutil and Google; operating system buffering configuration; and | 81 between gsutil and Google; operating system buffering configuration; and |
| 87 firewalls and other network elements. The perfdiag command is provided so | 82 firewalls and other network elements. The perfdiag command is provided so |
| 88 that customers can run a known measurement suite when troubleshooting | 83 that customers can run a known measurement suite when troubleshooting |
| (...skipping 26 matching lines...) Expand all Loading... |
| 115 | 110 |
| 116 -t Sets the list of diagnostic tests to perform. The default is to | 111 -t Sets the list of diagnostic tests to perform. The default is to |
| 117 run all diagnostic tests. Must be a comma-separated list | 112 run all diagnostic tests. Must be a comma-separated list |
| 118 containing one or more of the following: | 113 containing one or more of the following: |
| 119 | 114 |
| 120 lat | 115 lat |
| 121 Runs N iterations (set with -n) of writing the file, | 116 Runs N iterations (set with -n) of writing the file, |
| 122 retrieving its metadata, reading the file, and deleting | 117 retrieving its metadata, reading the file, and deleting |
| 123 the file. Records the latency of each operation. | 118 the file. Records the latency of each operation. |
| 124 | 119 |
| 120 list |
| 121 Write N (set with -n) objects to the bucket, record how long |
| 122 it takes for the eventually consistent listing call to return |
| 123 the N objects in its result, delete the N objects, then record |
| 124 how long it takes listing to stop returning the N objects. |
| 125 This test is off by default. |
| 126 |
| 125 rthru | 127 rthru |
| 126 Runs N (set with -n) read operations, with at most C | 128 Runs N (set with -n) read operations, with at most C |
| 127 (set with -c) reads outstanding at any given time. | 129 (set with -c) reads outstanding at any given time. |
| 128 | 130 |
| 129 wthru | 131 wthru |
| 130 Runs N (set with -n) write operations, with at most C | 132 Runs N (set with -n) write operations, with at most C |
| 131 (set with -c) writes outstanding at any given time. | 133 (set with -c) writes outstanding at any given time. |
| 132 | 134 |
| 133 -m Adds metadata to the result JSON file. Multiple -m values can be | 135 -m Adds metadata to the result JSON file. Multiple -m values can be |
| 134 specified. Example: | 136 specified. Example: |
| (...skipping 21 matching lines...) Expand all Loading... |
| 156 | 158 |
| 157 Note that HTTP responses are only recorded when the request was made in a | 159 Note that HTTP responses are only recorded when the request was made in a |
| 158 single process. When using multiple processes or threads, read and write | 160 single process. When using multiple processes or threads, read and write |
| 159 throughput measurements are performed in an external process, so the | 161 throughput measurements are performed in an external process, so the |
| 160 availability numbers reported won't include the throughput measurements. | 162 availability numbers reported won't include the throughput measurements. |
| 161 | 163 |
| 162 | 164 |
| 163 <B>NOTE</B> | 165 <B>NOTE</B> |
| 164 The perfdiag command collects system information. It collects your IP address, | 166 The perfdiag command collects system information. It collects your IP address, |
| 165 executes DNS queries to Google servers and collects the results, and collects | 167 executes DNS queries to Google servers and collects the results, and collects |
| 166 network statistics information from the output of netstat -s. None of this | 168 network statistics information from the output of netstat -s. It will also |
| 167 information will be sent to Google unless you choose to send it. | 169 attempt to connect to your proxy server if you have one configured. None of |
| 170 this information will be sent to Google unless you choose to send it. |
| 168 """) | 171 """) |
| 169 | 172 |
| 170 def _DownloadKey(cls, key): | 173 |
| 171 key.get_contents_to_file(cls.devnull, **cls.get_contents_to_file_args) | 174 class Error(Exception): |
| 172 | 175 """Base exception class for this module.""" |
| 173 def _UploadKey(cls, key): | 176 pass |
| 174 return key.set_contents_from_string(cls.file_contents[cls.thru_local_file], | 177 |
| 175 md5=cls.file_md5s[cls.thru_local_file]) | 178 |
| 176 | 179 class InvalidArgument(Error): |
| 180 """Raised on invalid arguments to functions.""" |
| 181 pass |
| 182 |
| 183 |
| 184 def _DownloadWrapper(cls, arg, thread_state=None): |
| 185 cls.Download(arg, thread_state=thread_state) |
| 186 |
| 187 |
| 188 def _UploadWrapper(cls, arg, thread_state=None): |
| 189 cls.Upload(arg, thread_state=thread_state) |
| 190 |
| 191 |
| 192 def _DeleteWrapper(cls, arg, thread_state=None): |
| 193 cls.Delete(arg, thread_state=thread_state) |
| 194 |
| 195 |
| 177 def _PerfdiagExceptionHandler(cls, e): | 196 def _PerfdiagExceptionHandler(cls, e): |
| 178 """Simple exception handler to allow post-completion status.""" | 197 """Simple exception handler to allow post-completion status.""" |
| 179 cls.logger.error(str(e)) | 198 cls.logger.error(str(e)) |
| 180 | 199 |
| 200 |
| 201 def _DummyTrackerCallback(_): |
| 202 pass |
| 203 |
| 181 | 204 |
| 182 class DummyFile(object): | 205 class DummyFile(object): |
| 183 """A dummy, file-like object that throws away everything written to it.""" | 206 """A dummy, file-like object that throws away everything written to it.""" |
| 184 | 207 |
| 185 def write(self, *args, **kwargs): # pylint: disable-msg=C6409 | 208 def write(self, *args, **kwargs): # pylint: disable=invalid-name |
| 186 pass | 209 pass |
| 187 | 210 |
| 188 | 211 |
| 189 class PerfDiagCommand(Command): | 212 class PerfDiagCommand(Command): |
| 190 """Implementation of gsutil perfdiag command.""" | 213 """Implementation of gsutil perfdiag command.""" |
| 191 | 214 |
| 192 # Command specification (processed by parent class). | 215 # Command specification. See base class for documentation. |
| 193 command_spec = { | 216 command_spec = Command.CreateCommandSpec( |
| 194 # Name of command. | 217 'perfdiag', |
| 195 COMMAND_NAME: 'perfdiag', | 218 command_name_aliases=['diag', 'diagnostic', 'perf', 'performance'], |
| 196 # List of command name aliases. | 219 min_args=0, |
| 197 COMMAND_NAME_ALIASES: ['diag', 'diagnostic', 'perf', 'performance'], | 220 max_args=1, |
| 198 # Min number of args required by this command. | 221 supported_sub_args='n:c:k:s:t:m:i:o:', |
| 199 MIN_ARGS: 0, | 222 file_url_ok=False, |
| 200 # Max number of args required by this command, or NO_MAX. | 223 provider_url_ok=False, |
| 201 MAX_ARGS: 1, | 224 urls_start_arg=0, |
| 202 # Getopt-style string specifying acceptable sub args. | 225 gs_api_support=[ApiSelector.XML, ApiSelector.JSON], |
| 203 SUPPORTED_SUB_ARGS: 'n:c:k:s:t:m:i:o:', | 226 gs_default_api=ApiSelector.JSON, |
| 204 # True if file URIs acceptable for this command. | 227 ) |
| 205 FILE_URIS_OK: False, | 228 # Help specification. See help_provider.py for documentation. |
| 206 # True if provider-only URIs acceptable for this command. | 229 help_spec = Command.HelpSpec( |
| 207 PROVIDER_URIS_OK: False, | 230 help_name='perfdiag', |
| 208 # Index in args of first URI arg. | 231 help_name_aliases=[], |
| 209 URIS_START_ARG: 0, | 232 help_type='command_help', |
| 210 } | 233 help_one_line_summary='Run performance diagnostic', |
| 211 help_spec = { | 234 help_text=_DETAILED_HELP_TEXT, |
| 212 # Name of command or auxiliary help info for which this help applies. | 235 subcommand_help_text={}, |
| 213 HELP_NAME: 'perfdiag', | 236 ) |
| 214 # List of help name aliases. | |
| 215 HELP_NAME_ALIASES: [], | |
| 216 # Type of help: | |
| 217 HELP_TYPE: HelpType.COMMAND_HELP, | |
| 218 # One line summary of this help. | |
| 219 HELP_ONE_LINE_SUMMARY: 'Run performance diagnostic', | |
| 220 # The full help text. | |
| 221 HELP_TEXT: _detailed_help_text, | |
| 222 } | |
| 223 | 237 |
| 224 # Byte sizes to use for testing files. | 238 # Byte sizes to use for latency testing files. |
| 225 # TODO: Consider letting the user specify these sizes with a configuration | 239 # TODO: Consider letting the user specify these sizes with a configuration |
| 226 # parameter. | 240 # parameter. |
| 227 test_file_sizes = ( | 241 test_file_sizes = ( |
| 228 0, # 0 bytes | 242 0, # 0 bytes |
| 229 1024, # 1 KB | 243 1024, # 1 KB |
| 230 102400, # 100 KB | 244 102400, # 100 KB |
| 231 1048576, # 1MB | 245 1048576, # 1MB |
| 232 ) | 246 ) |
| 233 | 247 |
| 248 # Test names. |
| 249 RTHRU = 'rthru' |
| 250 WTHRU = 'wthru' |
| 251 LAT = 'lat' |
| 252 LIST = 'list' |
| 253 |
| 234 # List of all diagnostic tests. | 254 # List of all diagnostic tests. |
| 235 ALL_DIAG_TESTS = ('rthru', 'wthru', 'lat') | 255 ALL_DIAG_TESTS = (RTHRU, WTHRU, LAT, LIST) |
| 256 # List of diagnostic tests to run by default. |
| 257 DEFAULT_DIAG_TESTS = (RTHRU, WTHRU, LAT) |
| 236 | 258 |
| 237 # Google Cloud Storage API endpoint host. | 259 # Google Cloud Storage XML API endpoint host. |
| 238 GOOGLE_API_HOST = boto.gs.connection.GSConnection.DefaultHost | 260 XML_API_HOST = boto.config.get( |
| 261 'Credentials', 'gs_host', boto.gs.connection.GSConnection.DefaultHost) |
| 262 # Google Cloud Storage XML API endpoint port. |
| 263 XML_API_PORT = boto.config.get('Credentials', 'gs_port', 80) |
| 239 | 264 |
| 240 # Maximum number of times to retry requests on 5xx errors. | 265 # Maximum number of times to retry requests on 5xx errors. |
| 241 MAX_SERVER_ERROR_RETRIES = 5 | 266 MAX_SERVER_ERROR_RETRIES = 5 |
| 242 # Maximum number of times to retry requests on more serious errors like | 267 # Maximum number of times to retry requests on more serious errors like |
| 243 # the socket breaking. | 268 # the socket breaking. |
| 244 MAX_TOTAL_RETRIES = 10 | 269 MAX_TOTAL_RETRIES = 10 |
| 245 | 270 |
| 246 # The default buffer size in boto's Key object is set to 8KB. This becomes a | 271 # The default buffer size in boto's Key object is set to 8KB. This becomes a |
| 247 # bottleneck at high throughput rates, so we increase it. | 272 # bottleneck at high throughput rates, so we increase it. |
| 248 KEY_BUFFER_SIZE = 16384 | 273 KEY_BUFFER_SIZE = 16384 |
| 249 | 274 |
| 250 # The maximum number of bytes to generate pseudo-randomly before beginning | 275 # The maximum number of bytes to generate pseudo-randomly before beginning |
| 251 # to repeat bytes. This number was chosen as the next prime larger than 5 MB. | 276 # to repeat bytes. This number was chosen as the next prime larger than 5 MB. |
| 252 MAX_UNIQUE_RANDOM_BYTES = 5242883 | 277 MAX_UNIQUE_RANDOM_BYTES = 5242883 |
| 253 | 278 |
| 279 # Maximum amount of time, in seconds, we will wait for object listings to |
| 280 # reflect what we expect in the listing tests. |
| 281 MAX_LISTING_WAIT_TIME = 60.0 |
| 282 |
| 254 def _Exec(self, cmd, raise_on_error=True, return_output=False, | 283 def _Exec(self, cmd, raise_on_error=True, return_output=False, |
| 255 mute_stderr=False): | 284 mute_stderr=False): |
| 256 """Executes a command in a subprocess. | 285 """Executes a command in a subprocess. |
| 257 | 286 |
| 258 Args: | 287 Args: |
| 259 cmd: List containing the command to execute. | 288 cmd: List containing the command to execute. |
| 260 raise_on_error: Whether or not to raise an exception when a process exits | 289 raise_on_error: Whether or not to raise an exception when a process exits |
| 261 with a non-zero return code. | 290 with a non-zero return code. |
| 262 return_output: If set to True, the return value of the function is the | 291 return_output: If set to True, the return value of the function is the |
| 263 stdout of the process. | 292 stdout of the process. |
| 264 mute_stderr: If set to True, the stderr of the process is not printed to | 293 mute_stderr: If set to True, the stderr of the process is not printed to |
| 265 the console. | 294 the console. |
| 266 | 295 |
| 267 Returns: | 296 Returns: |
| 268 The return code of the process or the stdout if return_output is set. | 297 The return code of the process or the stdout if return_output is set. |
| 269 | 298 |
| 270 Raises: | 299 Raises: |
| 271 Exception: If raise_on_error is set to True and any process exits with a | 300 Exception: If raise_on_error is set to True and any process exits with a |
| 272 non-zero return code. | 301 non-zero return code. |
| 273 """ | 302 """ |
| 274 self.logger.debug('Running command: %s', cmd) | 303 self.logger.debug('Running command: %s', cmd) |
| 275 stderr = subprocess.PIPE if mute_stderr else None | 304 stderr = subprocess.PIPE if mute_stderr else None |
| 276 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=stderr) | 305 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=stderr) |
| 277 (stdoutdata, stderrdata) = p.communicate() | 306 (stdoutdata, _) = p.communicate() |
| 278 if raise_on_error and p.returncode: | 307 if raise_on_error and p.returncode: |
| 279 raise CommandException("Received non-zero return code (%d) from " | 308 raise CommandException("Received non-zero return code (%d) from " |
| 280 "subprocess '%s'." % (p.returncode, ' '.join(cmd))) | 309 "subprocess '%s'." % (p.returncode, ' '.join(cmd))) |
| 281 return stdoutdata if return_output else p.returncode | 310 return stdoutdata if return_output else p.returncode |
| 282 | 311 |
| 283 def _SetUp(self): | 312 def _SetUp(self): |
| 284 """Performs setup operations needed before diagnostics can be run.""" | 313 """Performs setup operations needed before diagnostics can be run.""" |
| 285 | 314 |
| 286 # Stores test result data. | 315 # Stores test result data. |
| 287 self.results = {} | 316 self.results = {} |
| (...skipping 14 matching lines...) Expand all Loading... |
| 302 # Total number of socket errors. | 331 # Total number of socket errors. |
| 303 self.connection_breaks = 0 | 332 self.connection_breaks = 0 |
| 304 | 333 |
| 305 def _MakeFile(file_size): | 334 def _MakeFile(file_size): |
| 306 """Creates a temporary file of the given size and returns its path.""" | 335 """Creates a temporary file of the given size and returns its path.""" |
| 307 fd, fpath = tempfile.mkstemp(suffix='.bin', prefix='gsutil_test_file', | 336 fd, fpath = tempfile.mkstemp(suffix='.bin', prefix='gsutil_test_file', |
| 308 text=False) | 337 text=False) |
| 309 self.file_sizes[fpath] = file_size | 338 self.file_sizes[fpath] = file_size |
| 310 random_bytes = os.urandom(min(file_size, self.MAX_UNIQUE_RANDOM_BYTES)) | 339 random_bytes = os.urandom(min(file_size, self.MAX_UNIQUE_RANDOM_BYTES)) |
| 311 total_bytes = 0 | 340 total_bytes = 0 |
| 312 file_contents = "" | 341 file_contents = '' |
| 313 while total_bytes < file_size: | 342 while total_bytes < file_size: |
| 314 num_bytes = min(self.MAX_UNIQUE_RANDOM_BYTES, file_size - total_bytes) | 343 num_bytes = min(self.MAX_UNIQUE_RANDOM_BYTES, file_size - total_bytes) |
| 315 file_contents += random_bytes[:num_bytes] | 344 file_contents += random_bytes[:num_bytes] |
| 316 total_bytes += num_bytes | 345 total_bytes += num_bytes |
| 317 self.file_contents[fpath] = file_contents | 346 self.file_contents[fpath] = file_contents |
| 318 with os.fdopen(fd, 'wb') as f: | 347 with os.fdopen(fd, 'wb') as f: |
| 319 f.write(self.file_contents[fpath]) | 348 f.write(self.file_contents[fpath]) |
| 320 with open(fpath, 'rb') as f: | 349 with open(fpath, 'rb') as f: |
| 321 self.file_md5s[fpath] = compute_md5(f) | 350 self.file_md5s[fpath] = CalculateB64EncodedMd5FromContents(f) |
| 322 return fpath | 351 return fpath |
| 323 | 352 |
| 324 # Create files for latency tests. | 353 # Create files for latency tests. |
| 325 for file_size in self.test_file_sizes: | 354 for file_size in self.test_file_sizes: |
| 326 fpath = _MakeFile(file_size) | 355 fpath = _MakeFile(file_size) |
| 327 self.latency_files.append(fpath) | 356 self.latency_files.append(fpath) |
| 328 | 357 |
| 329 # Creating a file for warming up the TCP connection. | 358 # Creating a file for warming up the TCP connection. |
| 330 self.tcp_warmup_file = _MakeFile(5 * 1024 * 1024) # 5 Megabytes. | 359 self.tcp_warmup_file = _MakeFile(5 * 1024 * 1024) # 5 Megabytes. |
| 331 # Remote file to use for TCP warmup. | 360 # Remote file to use for TCP warmup. |
| 332 self.tcp_warmup_remote_file = (str(self.bucket_uri) + | 361 self.tcp_warmup_remote_file = (str(self.bucket_url) + |
| 333 os.path.basename(self.tcp_warmup_file)) | 362 os.path.basename(self.tcp_warmup_file)) |
| 334 | 363 |
| 335 # Local file on disk for write throughput tests. | 364 # Local file on disk for write throughput tests. |
| 336 self.thru_local_file = _MakeFile(self.thru_filesize) | 365 self.thru_local_file = _MakeFile(self.thru_filesize) |
| 337 # Remote file to write/read from during throughput tests. | 366 # Remote file to write/read from during throughput tests. |
| 338 self.thru_remote_file = (str(self.bucket_uri) + | 367 self.thru_remote_file = (str(self.bucket_url) + |
| 339 os.path.basename(self.thru_local_file)) | 368 os.path.basename(self.thru_local_file)) |
| 340 # Dummy file buffer to use for downloading that goes nowhere. | 369 # Dummy file buffer to use for downloading that goes nowhere. |
| 341 self.devnull = DummyFile() | 370 self.discard_sink = DummyFile() |
| 342 | 371 |
| 343 def _TearDown(self): | 372 def _TearDown(self): |
| 344 """Performs operations to clean things up after performing diagnostics.""" | 373 """Performs operations to clean things up after performing diagnostics.""" |
| 345 for fpath in self.latency_files + [self.thru_local_file, | 374 for fpath in self.latency_files + [self.thru_local_file, |
| 346 self.tcp_warmup_file]: | 375 self.tcp_warmup_file]: |
| 347 try: | 376 try: |
| 348 os.remove(fpath) | 377 os.remove(fpath) |
| 349 except OSError: | 378 except OSError: |
| 350 pass | 379 pass |
| 351 | 380 |
| 352 cleanup_files = [self.thru_local_file, self.tcp_warmup_file] | 381 if self.LAT in self.diag_tests or self.WTHRU in self.diag_tests: |
| 353 for f in cleanup_files: | 382 cleanup_files = [self.thru_local_file, self.tcp_warmup_file] |
| 383 for f in cleanup_files: |
| 354 | 384 |
| 355 def _Delete(): | 385 def _Delete(): |
| 356 k = self.bucket.key_class(self.bucket) | 386 try: |
| 357 k.name = os.path.basename(f) | 387 self.gsutil_api.DeleteObject(self.bucket_url.bucket_name, |
| 358 try: | 388 os.path.basename(f), |
| 359 k.delete() | 389 provider=self.provider) |
| 360 except boto.exception.BotoServerError as e: | 390 except NotFoundException: |
| 361 # Ignore not found errors since it's already gone. | 391 pass |
| 362 if e.status != 404: | |
| 363 raise | |
| 364 | 392 |
| 365 self._RunOperation(_Delete) | 393 self._RunOperation(_Delete) |
| 366 | 394 |
| 367 @contextlib.contextmanager | 395 @contextlib.contextmanager |
| 368 def _Time(self, key, bucket): | 396 def _Time(self, key, bucket): |
| 369 """A context manager that measures time. | 397 """A context manager that measures time. |
| 370 | 398 |
| 371 A context manager that prints a status message before and after executing | 399 A context manager that prints a status message before and after executing |
| 372 the inner command and times how long the inner command takes. Keeps track of | 400 the inner command and times how long the inner command takes. Keeps track of |
| 373 the timing, aggregated by the given key. | 401 the timing, aggregated by the given key. |
| 374 | 402 |
| 375 Args: | 403 Args: |
| (...skipping 16 matching lines...) Expand all Loading... |
| 392 Args: | 420 Args: |
| 393 func: The function to run. | 421 func: The function to run. |
| 394 | 422 |
| 395 Returns: | 423 Returns: |
| 396 True if the operation succeeds, False if aborted. | 424 True if the operation succeeds, False if aborted. |
| 397 """ | 425 """ |
| 398 # We retry on httplib exceptions that can happen if the socket was closed | 426 # We retry on httplib exceptions that can happen if the socket was closed |
| 399 # by the remote party or the connection broke because of network issues. | 427 # by the remote party or the connection broke because of network issues. |
| 400 # Only the BotoServerError is counted as a 5xx error towards the retry | 428 # Only the BotoServerError is counted as a 5xx error towards the retry |
| 401 # limit. | 429 # limit. |
| 402 exceptions = list(self.bucket.connection.http_exceptions) | |
| 403 exceptions.append(boto.exception.BotoServerError) | |
| 404 | |
| 405 success = False | 430 success = False |
| 406 server_error_retried = 0 | 431 server_error_retried = 0 |
| 407 total_retried = 0 | 432 total_retried = 0 |
| 408 i = 0 | 433 i = 0 |
| 434 return_val = None |
| 409 while not success: | 435 while not success: |
| 410 next_sleep = random.random() * (2 ** i) + 1 | 436 next_sleep = min(random.random() * (2 ** i) + 1, GetMaxRetryDelay()) |
| 411 try: | 437 try: |
| 412 func() | 438 return_val = func() |
| 413 self.total_requests += 1 | 439 self.total_requests += 1 |
| 414 success = True | 440 success = True |
| 415 except tuple(exceptions) as e: | 441 except tuple(self.exceptions) as e: |
| 416 total_retried += 1 | 442 total_retried += 1 |
| 417 if total_retried > self.MAX_TOTAL_RETRIES: | 443 if total_retried > self.MAX_TOTAL_RETRIES: |
| 418 self.logger.info('Reached maximum total retries. Not retrying.') | 444 self.logger.info('Reached maximum total retries. Not retrying.') |
| 419 break | 445 break |
| 420 if isinstance(e, boto.exception.BotoServerError): | 446 if (isinstance(e, apiclient_errors.HttpError) or |
| 421 if e.status >= 500: | 447 isinstance(e, ServiceException)): |
| 422 self.error_responses_by_code[e.status] += 1 | 448 if isinstance(e, apiclient_errors.HttpError): |
| 449 status = e.resp.status |
| 450 else: |
| 451 status = e.status |
| 452 if status >= 500: |
| 453 self.error_responses_by_code[status] += 1 |
| 423 self.total_requests += 1 | 454 self.total_requests += 1 |
| 424 self.request_errors += 1 | 455 self.request_errors += 1 |
| 425 server_error_retried += 1 | 456 server_error_retried += 1 |
| 426 time.sleep(next_sleep) | 457 time.sleep(next_sleep) |
| 427 else: | 458 else: |
| 428 raise | 459 raise |
| 429 if server_error_retried > self.MAX_SERVER_ERROR_RETRIES: | 460 if server_error_retried > self.MAX_SERVER_ERROR_RETRIES: |
| 430 self.logger.info( | 461 self.logger.info( |
| 431 'Reached maximum server error retries. Not retrying.') | 462 'Reached maximum server error retries. Not retrying.') |
| 432 break | 463 break |
| 433 else: | 464 else: |
| 434 self.connection_breaks += 1 | 465 self.connection_breaks += 1 |
| 435 return success | 466 return return_val |
| 436 | 467 |
| 437 def _RunLatencyTests(self): | 468 def _RunLatencyTests(self): |
| 438 """Runs latency tests.""" | 469 """Runs latency tests.""" |
| 439 # Stores timing information for each category of operation. | 470 # Stores timing information for each category of operation. |
| 440 self.results['latency'] = defaultdict(list) | 471 self.results['latency'] = defaultdict(list) |
| 441 | 472 |
| 442 for i in range(self.num_iterations): | 473 for i in range(self.num_iterations): |
| 443 self.logger.info('\nRunning latency iteration %d...', i+1) | 474 self.logger.info('\nRunning latency iteration %d...', i+1) |
| 444 for fpath in self.latency_files: | 475 for fpath in self.latency_files: |
| 445 basename = os.path.basename(fpath) | 476 basename = os.path.basename(fpath) |
| 446 gsbucket = str(self.bucket_uri) | 477 url = self.bucket_url.Clone() |
| 447 gsuri = gsbucket + basename | 478 url.object_name = basename |
| 448 file_size = self.file_sizes[fpath] | 479 file_size = self.file_sizes[fpath] |
| 449 readable_file_size = MakeHumanReadable(file_size) | 480 readable_file_size = MakeHumanReadable(file_size) |
| 450 | 481 |
| 451 self.logger.info( | 482 self.logger.info( |
| 452 "\nFile of size %(size)s located on disk at '%(fpath)s' being " | 483 "\nFile of size %s located on disk at '%s' being diagnosed in the " |
| 453 "diagnosed in the cloud at '%(gsuri)s'." | 484 "cloud at '%s'.", readable_file_size, fpath, url) |
| 454 % {'size': readable_file_size, 'fpath': fpath, 'gsuri': gsuri}) | |
| 455 | 485 |
| 456 k = self.bucket.key_class(self.bucket) | 486 upload_target = StorageUrlToUploadObjectMetadata(url) |
| 457 k.BufferSize = self.KEY_BUFFER_SIZE | |
| 458 k.key = basename | |
| 459 | 487 |
| 460 def _Upload(): | 488 def _Upload(): |
| 489 io_fp = cStringIO.StringIO(self.file_contents[fpath]) |
| 461 with self._Time('UPLOAD_%d' % file_size, self.results['latency']): | 490 with self._Time('UPLOAD_%d' % file_size, self.results['latency']): |
| 462 k.set_contents_from_string(self.file_contents[fpath], | 491 self.gsutil_api.UploadObject( |
| 463 md5=self.file_md5s[fpath]) | 492 io_fp, upload_target, size=file_size, provider=self.provider, |
| 493 fields=['name']) |
| 464 self._RunOperation(_Upload) | 494 self._RunOperation(_Upload) |
| 465 | 495 |
| 466 def _Metadata(): | 496 def _Metadata(): |
| 467 with self._Time('METADATA_%d' % file_size, self.results['latency']): | 497 with self._Time('METADATA_%d' % file_size, self.results['latency']): |
| 468 k.exists() | 498 return self.gsutil_api.GetObjectMetadata( |
| 469 self._RunOperation(_Metadata) | 499 url.bucket_name, url.object_name, |
| 500 provider=self.provider, fields=['name', 'contentType', |
| 501 'mediaLink', 'size']) |
| 502 # Download will get the metadata first if we don't pass it in. |
| 503 download_metadata = self._RunOperation(_Metadata) |
| 504 serialization_dict = GetDownloadSerializationDict(download_metadata) |
| 505 serialization_data = json.dumps(serialization_dict) |
| 470 | 506 |
| 471 def _Download(): | 507 def _Download(): |
| 472 with self._Time('DOWNLOAD_%d' % file_size, self.results['latency']): | 508 with self._Time('DOWNLOAD_%d' % file_size, self.results['latency']): |
| 473 k.get_contents_to_file(self.devnull, | 509 self.gsutil_api.GetObjectMedia( |
| 474 **self.get_contents_to_file_args) | 510 url.bucket_name, url.object_name, self.discard_sink, |
| 511 provider=self.provider, serialization_data=serialization_data) |
| 475 self._RunOperation(_Download) | 512 self._RunOperation(_Download) |
| 476 | 513 |
| 477 def _Delete(): | 514 def _Delete(): |
| 478 with self._Time('DELETE_%d' % file_size, self.results['latency']): | 515 with self._Time('DELETE_%d' % file_size, self.results['latency']): |
| 479 k.delete() | 516 self.gsutil_api.DeleteObject(url.bucket_name, url.object_name, |
| 517 provider=self.provider) |
| 480 self._RunOperation(_Delete) | 518 self._RunOperation(_Delete) |
| 481 | 519 |
| 520 class _CpFilter(logging.Filter): |
| 482 | 521 |
| 483 class _CpFilter(logging.Filter): | |
| 484 def filter(self, record): | 522 def filter(self, record): |
| 485 # Used to prevent cp._LogCopyOperation from spewing output from | 523 # Used to prevent cp._LogCopyOperation from spewing output from |
| 486 # subprocesses about every iteration. | 524 # subprocesses about every iteration. |
| 487 msg = record.getMessage() | 525 msg = record.getMessage() |
| 488 return not (('Copying file:///' in msg) or ('Copying gs://' in msg)) | 526 return not (('Copying file:///' in msg) or ('Copying gs://' in msg) or |
| 527 ('Computing CRC' in msg)) |
| 528 |
| 529 def _PerfdiagExceptionHandler(self, e): |
| 530 """Simple exception handler to allow post-completion status.""" |
| 531 self.logger.error(str(e)) |
| 489 | 532 |
| 490 def _RunReadThruTests(self): | 533 def _RunReadThruTests(self): |
| 491 """Runs read throughput tests.""" | 534 """Runs read throughput tests.""" |
| 492 self.results['read_throughput'] = {'file_size': self.thru_filesize, | 535 self.results['read_throughput'] = {'file_size': self.thru_filesize, |
| 493 'num_times': self.num_iterations, | 536 'num_times': self.num_iterations, |
| 494 'processes': self.processes, | 537 'processes': self.processes, |
| 495 'threads': self.threads} | 538 'threads': self.threads} |
| 496 | 539 |
| 497 # Copy the TCP warmup file. | 540 # Copy the TCP warmup file. |
| 498 warmup_key = self.bucket.key_class(self.bucket) | 541 warmup_url = self.bucket_url.Clone() |
| 499 warmup_key.key = os.path.basename(self.tcp_warmup_file) | 542 warmup_url.object_name = os.path.basename(self.tcp_warmup_file) |
| 543 warmup_target = StorageUrlToUploadObjectMetadata(warmup_url) |
| 500 | 544 |
| 501 def _Upload1(): | 545 def _Upload1(): |
| 502 warmup_key.set_contents_from_string( | 546 self.gsutil_api.UploadObject( |
| 503 self.file_contents[self.tcp_warmup_file], | 547 cStringIO.StringIO(self.file_contents[self.tcp_warmup_file]), |
| 504 md5=self.file_md5s[self.tcp_warmup_file]) | 548 warmup_target, provider=self.provider, fields=['name']) |
| 505 self._RunOperation(_Upload1) | 549 self._RunOperation(_Upload1) |
| 506 | 550 |
| 507 # Copy the file to remote location before reading. | 551 # Copy the file to remote location before reading. |
| 508 k = self.bucket.key_class(self.bucket) | 552 thru_url = self.bucket_url.Clone() |
| 509 k.BufferSize = self.KEY_BUFFER_SIZE | 553 thru_url.object_name = os.path.basename(self.thru_local_file) |
| 510 k.key = os.path.basename(self.thru_local_file) | 554 thru_target = StorageUrlToUploadObjectMetadata(thru_url) |
| 555 thru_target.md5Hash = self.file_md5s[self.thru_local_file] |
| 511 | 556 |
| 557 # Get the mediaLink here so that we can pass it to download. |
| 512 def _Upload2(): | 558 def _Upload2(): |
| 513 k.set_contents_from_string(self.file_contents[self.thru_local_file], | 559 return self.gsutil_api.UploadObject( |
| 514 md5=self.file_md5s[self.thru_local_file]) | 560 cStringIO.StringIO(self.file_contents[self.thru_local_file]), |
| 515 self._RunOperation(_Upload2) | 561 thru_target, provider=self.provider, size=self.thru_filesize, |
| 562 fields=['name', 'mediaLink', 'size']) |
| 563 |
| 564 # Get the metadata for the object so that we are just measuring performance |
| 565 # on the actual bytes transfer. |
| 566 download_metadata = self._RunOperation(_Upload2) |
| 567 serialization_dict = GetDownloadSerializationDict(download_metadata) |
| 568 serialization_data = json.dumps(serialization_dict) |
| 516 | 569 |
| 517 if self.processes == 1 and self.threads == 1: | 570 if self.processes == 1 and self.threads == 1: |
| 518 | 571 |
| 519 # Warm up the TCP connection. | 572 # Warm up the TCP connection. |
| 520 def _Warmup(): | 573 def _Warmup(): |
| 521 warmup_key.get_contents_to_file(self.devnull, | 574 self.gsutil_api.GetObjectMedia(warmup_url.bucket_name, |
| 522 **self.get_contents_to_file_args) | 575 warmup_url.object_name, |
| 576 self.discard_sink, |
| 577 provider=self.provider) |
| 523 self._RunOperation(_Warmup) | 578 self._RunOperation(_Warmup) |
| 524 | 579 |
| 525 times = [] | 580 times = [] |
| 526 | 581 |
| 527 def _Download(): | 582 def _Download(): |
| 528 t0 = time.time() | 583 t0 = time.time() |
| 529 k.get_contents_to_file(self.devnull, **self.get_contents_to_file_args) | 584 self.gsutil_api.GetObjectMedia( |
| 585 thru_url.bucket_name, thru_url.object_name, self.discard_sink, |
| 586 provider=self.provider, serialization_data=serialization_data) |
| 530 t1 = time.time() | 587 t1 = time.time() |
| 531 times.append(t1 - t0) | 588 times.append(t1 - t0) |
| 532 for _ in range(self.num_iterations): | 589 for _ in range(self.num_iterations): |
| 533 self._RunOperation(_Download) | 590 self._RunOperation(_Download) |
| 534 time_took = sum(times) | 591 time_took = sum(times) |
| 535 else: | 592 else: |
| 536 args = [k] * self.num_iterations | 593 args = ([(thru_url.bucket_name, thru_url.object_name, serialization_data)] |
| 594 * self.num_iterations) |
| 537 self.logger.addFilter(self._CpFilter()) | 595 self.logger.addFilter(self._CpFilter()) |
| 538 | 596 |
| 539 t0 = time.time() | 597 t0 = time.time() |
| 540 self.Apply(_DownloadKey, | 598 self.Apply(_DownloadWrapper, |
| 541 args, | 599 args, |
| 542 _PerfdiagExceptionHandler, | 600 _PerfdiagExceptionHandler, |
| 543 arg_checker=DummyArgChecker, | 601 arg_checker=DummyArgChecker, |
| 544 parallel_operations_override=True, | 602 parallel_operations_override=True, |
| 545 process_count=self.processes, | 603 process_count=self.processes, |
| 546 thread_count=self.threads) | 604 thread_count=self.threads) |
| 547 t1 = time.time() | 605 t1 = time.time() |
| 548 time_took = t1 - t0 | 606 time_took = t1 - t0 |
| 549 | 607 |
| 550 total_bytes_copied = self.thru_filesize * self.num_iterations | 608 total_bytes_copied = self.thru_filesize * self.num_iterations |
| 551 bytes_per_second = total_bytes_copied / time_took | 609 bytes_per_second = total_bytes_copied / time_took |
| 552 | 610 |
| 553 self.results['read_throughput']['time_took'] = time_took | 611 self.results['read_throughput']['time_took'] = time_took |
| 554 self.results['read_throughput']['total_bytes_copied'] = total_bytes_copied | 612 self.results['read_throughput']['total_bytes_copied'] = total_bytes_copied |
| 555 self.results['read_throughput']['bytes_per_second'] = bytes_per_second | 613 self.results['read_throughput']['bytes_per_second'] = bytes_per_second |
| 556 | 614 |
| 557 def _RunWriteThruTests(self): | 615 def _RunWriteThruTests(self): |
| 558 """Runs write throughput tests.""" | 616 """Runs write throughput tests.""" |
| 559 self.results['write_throughput'] = {'file_size': self.thru_filesize, | 617 self.results['write_throughput'] = {'file_size': self.thru_filesize, |
| 560 'num_copies': self.num_iterations, | 618 'num_copies': self.num_iterations, |
| 561 'processes': self.processes, | 619 'processes': self.processes, |
| 562 'threads': self.threads} | 620 'threads': self.threads} |
| 563 | 621 |
| 564 k = self.bucket.key_class(self.bucket) | 622 warmup_url = self.bucket_url.Clone() |
| 565 k.BufferSize = self.KEY_BUFFER_SIZE | 623 warmup_url.object_name = os.path.basename(self.tcp_warmup_file) |
| 566 k.key = os.path.basename(self.thru_local_file) | 624 warmup_target = StorageUrlToUploadObjectMetadata(warmup_url) |
| 625 |
| 626 thru_url = self.bucket_url.Clone() |
| 627 thru_url.object_name = os.path.basename(self.thru_local_file) |
| 628 thru_target = StorageUrlToUploadObjectMetadata(thru_url) |
| 629 thru_tuples = [] |
| 630 for i in xrange(self.num_iterations): |
| 631 # Create a unique name for each uploaded object. Otherwise, |
| 632 # the XML API would fail when trying to non-atomically get metadata |
| 633 # for the object that gets blown away by the overwrite. |
| 634 thru_tuples.append(UploadObjectTuple( |
| 635 thru_target.bucket, thru_target.name + str(i), |
| 636 filepath=self.thru_local_file)) |
| 637 |
| 567 if self.processes == 1 and self.threads == 1: | 638 if self.processes == 1 and self.threads == 1: |
| 568 # Warm up the TCP connection. | 639 # Warm up the TCP connection. |
| 569 warmup_key = self.bucket.key_class(self.bucket) | |
| 570 warmup_key.key = os.path.basename(self.tcp_warmup_file) | |
| 571 | |
| 572 def _Warmup(): | 640 def _Warmup(): |
| 573 warmup_key.set_contents_from_string( | 641 self.gsutil_api.UploadObject( |
| 574 self.file_contents[self.tcp_warmup_file], | 642 cStringIO.StringIO(self.file_contents[self.tcp_warmup_file]), |
| 575 md5=self.file_md5s[self.tcp_warmup_file]) | 643 warmup_target, provider=self.provider, size=self.thru_filesize, |
| 644 fields=['name']) |
| 576 self._RunOperation(_Warmup) | 645 self._RunOperation(_Warmup) |
| 577 | 646 |
| 578 times = [] | 647 times = [] |
| 579 | 648 |
| 580 def _Upload(): | 649 for i in xrange(self.num_iterations): |
| 581 t0 = time.time() | 650 thru_tuple = thru_tuples[i] |
| 582 k.set_contents_from_string(self.file_contents[self.thru_local_file], | 651 def _Upload(): |
| 583 md5=self.file_md5s[self.thru_local_file]) | 652 """Uploads the write throughput measurement object.""" |
| 584 t1 = time.time() | 653 upload_target = apitools_messages.Object( |
| 585 times.append(t1 - t0) | 654 bucket=thru_tuple.bucket_name, name=thru_tuple.object_name, |
| 586 for _ in range(self.num_iterations): | 655 md5Hash=thru_tuple.md5) |
| 656 io_fp = cStringIO.StringIO(self.file_contents[self.thru_local_file]) |
| 657 t0 = time.time() |
| 658 if self.thru_filesize < ResumableThreshold(): |
| 659 self.gsutil_api.UploadObject( |
| 660 io_fp, upload_target, provider=self.provider, |
| 661 size=self.thru_filesize, fields=['name']) |
| 662 else: |
| 663 self.gsutil_api.UploadObjectResumable( |
| 664 io_fp, upload_target, provider=self.provider, |
| 665 size=self.thru_filesize, fields=['name'], |
| 666 tracker_callback=_DummyTrackerCallback) |
| 667 |
| 668 t1 = time.time() |
| 669 times.append(t1 - t0) |
| 670 |
| 587 self._RunOperation(_Upload) | 671 self._RunOperation(_Upload) |
| 588 time_took = sum(times) | 672 time_took = sum(times) |
| 589 | 673 |
| 590 else: | 674 else: |
| 591 args = [k] * self.num_iterations | 675 args = thru_tuples |
| 592 t0 = time.time() | 676 t0 = time.time() |
| 593 self.Apply(_UploadKey, | 677 self.Apply(_UploadWrapper, |
| 594 args, | 678 args, |
| 595 _PerfdiagExceptionHandler, | 679 _PerfdiagExceptionHandler, |
| 596 arg_checker=DummyArgChecker, | 680 arg_checker=DummyArgChecker, |
| 597 parallel_operations_override=True, | 681 parallel_operations_override=True, |
| 598 process_count=self.processes, | 682 process_count=self.processes, |
| 599 thread_count=self.threads) | 683 thread_count=self.threads) |
| 600 t1 = time.time() | 684 t1 = time.time() |
| 601 time_took = t1 - t0 | 685 time_took = t1 - t0 |
| 602 | 686 |
| 603 total_bytes_copied = self.thru_filesize * self.num_iterations | 687 total_bytes_copied = self.thru_filesize * self.num_iterations |
| 604 bytes_per_second = total_bytes_copied / time_took | 688 bytes_per_second = total_bytes_copied / time_took |
| 605 | 689 |
| 606 self.results['write_throughput']['time_took'] = time_took | 690 self.results['write_throughput']['time_took'] = time_took |
| 607 self.results['write_throughput']['total_bytes_copied'] = total_bytes_copied | 691 self.results['write_throughput']['total_bytes_copied'] = total_bytes_copied |
| 608 self.results['write_throughput']['bytes_per_second'] = bytes_per_second | 692 self.results['write_throughput']['bytes_per_second'] = bytes_per_second |
| 609 | 693 |
| 694 def _RunListTests(self): |
| 695 """Runs eventual consistency listing latency tests.""" |
| 696 self.results['listing'] = {'num_files': self.num_iterations} |
| 697 |
| 698 # Generate N random object names to put in the bucket. |
| 699 list_prefix = 'gsutil-perfdiag-list-' |
| 700 list_objects = [] |
| 701 for _ in xrange(self.num_iterations): |
| 702 list_objects.append( |
| 703 u'%s%s' % (list_prefix, os.urandom(20).encode('hex'))) |
| 704 |
| 705 # Add the objects to the bucket. |
| 706 self.logger.info( |
| 707 '\nWriting %s objects for listing test...', self.num_iterations) |
| 708 empty_md5 = CalculateB64EncodedMd5FromContents(cStringIO.StringIO('')) |
| 709 args = [ |
| 710 UploadObjectTuple(self.bucket_url.bucket_name, name, md5=empty_md5, |
| 711 contents='') for name in list_objects] |
| 712 self.Apply(_UploadWrapper, args, _PerfdiagExceptionHandler, |
| 713 arg_checker=DummyArgChecker) |
| 714 |
| 715 list_latencies = [] |
| 716 files_seen = [] |
| 717 total_start_time = time.time() |
| 718 expected_objects = set(list_objects) |
| 719 found_objects = set() |
| 720 |
| 721 def _List(): |
| 722 """Lists and returns objects in the bucket. Also records latency.""" |
| 723 t0 = time.time() |
| 724 objects = list(self.gsutil_api.ListObjects( |
| 725 self.bucket_url.bucket_name, prefix=list_prefix, delimiter='/', |
| 726 provider=self.provider, fields=['items/name'])) |
| 727 t1 = time.time() |
| 728 list_latencies.append(t1 - t0) |
| 729 return set([obj.data.name for obj in objects]) |
| 730 |
| 731 self.logger.info( |
| 732 'Listing bucket %s waiting for %s objects to appear...', |
| 733 self.bucket_url.bucket_name, self.num_iterations) |
| 734 while expected_objects - found_objects: |
| 735 def _ListAfterUpload(): |
| 736 names = _List() |
| 737 found_objects.update(names & expected_objects) |
| 738 files_seen.append(len(found_objects)) |
| 739 self._RunOperation(_ListAfterUpload) |
| 740 if expected_objects - found_objects: |
| 741 if time.time() - total_start_time > self.MAX_LISTING_WAIT_TIME: |
| 742 self.logger.warning('Maximum time reached waiting for listing.') |
| 743 break |
| 744 total_end_time = time.time() |
| 745 |
| 746 self.results['listing']['insert'] = { |
| 747 'num_listing_calls': len(list_latencies), |
| 748 'list_latencies': list_latencies, |
| 749 'files_seen_after_listing': files_seen, |
| 750 'time_took': total_end_time - total_start_time, |
| 751 } |
| 752 |
| 753 self.logger.info( |
| 754 'Deleting %s objects for listing test...', self.num_iterations) |
| 755 self.Apply(_DeleteWrapper, args, _PerfdiagExceptionHandler, |
| 756 arg_checker=DummyArgChecker) |
| 757 |
| 758 self.logger.info( |
| 759 'Listing bucket %s waiting for %s objects to disappear...', |
| 760 self.bucket_url.bucket_name, self.num_iterations) |
| 761 list_latencies = [] |
| 762 files_seen = [] |
| 763 total_start_time = time.time() |
| 764 found_objects = set(list_objects) |
| 765 while found_objects: |
| 766 def _ListAfterDelete(): |
| 767 names = _List() |
| 768 found_objects.intersection_update(names) |
| 769 files_seen.append(len(found_objects)) |
| 770 self._RunOperation(_ListAfterDelete) |
| 771 if found_objects: |
| 772 if time.time() - total_start_time > self.MAX_LISTING_WAIT_TIME: |
| 773 self.logger.warning('Maximum time reached waiting for listing.') |
| 774 break |
| 775 total_end_time = time.time() |
| 776 |
| 777 self.results['listing']['delete'] = { |
| 778 'num_listing_calls': len(list_latencies), |
| 779 'list_latencies': list_latencies, |
| 780 'files_seen_after_listing': files_seen, |
| 781 'time_took': total_end_time - total_start_time, |
| 782 } |
| 783 |
| 784 def Upload(self, thru_tuple, thread_state=None): |
| 785 gsutil_api = GetCloudApiInstance(self, thread_state) |
| 786 |
| 787 md5hash = thru_tuple.md5 |
| 788 contents = thru_tuple.contents |
| 789 if thru_tuple.filepath: |
| 790 md5hash = self.file_md5s[thru_tuple.filepath] |
| 791 contents = self.file_contents[thru_tuple.filepath] |
| 792 |
| 793 upload_target = apitools_messages.Object( |
| 794 bucket=thru_tuple.bucket_name, name=thru_tuple.object_name, |
| 795 md5Hash=md5hash) |
| 796 file_size = len(contents) |
| 797 if file_size < ResumableThreshold(): |
| 798 gsutil_api.UploadObject( |
| 799 cStringIO.StringIO(contents), upload_target, |
| 800 provider=self.provider, size=file_size, fields=['name']) |
| 801 else: |
| 802 gsutil_api.UploadObjectResumable( |
| 803 cStringIO.StringIO(contents), upload_target, |
| 804 provider=self.provider, size=file_size, fields=['name'], |
| 805 tracker_callback=_DummyTrackerCallback) |
| 806 |
| 807 def Download(self, download_tuple, thread_state=None): |
| 808 """Downloads a file. |
| 809 |
| 810 Args: |
| 811 download_tuple: (bucket name, object name, serialization data for object). |
| 812 thread_state: gsutil Cloud API instance to use for the download. |
| 813 """ |
| 814 gsutil_api = GetCloudApiInstance(self, thread_state) |
| 815 gsutil_api.GetObjectMedia( |
| 816 download_tuple[0], download_tuple[1], self.discard_sink, |
| 817 provider=self.provider, serialization_data=download_tuple[2]) |
| 818 |
| 819 def Delete(self, thru_tuple, thread_state=None): |
| 820 gsutil_api = thread_state or self.gsutil_api |
| 821 gsutil_api.DeleteObject( |
| 822 thru_tuple.bucket_name, thru_tuple.object_name, provider=self.provider) |
| 823 |
| 610 def _GetDiskCounters(self): | 824 def _GetDiskCounters(self): |
| 611 """Retrieves disk I/O statistics for all disks. | 825 """Retrieves disk I/O statistics for all disks. |
| 612 | 826 |
| 613 Adapted from the psutil module's psutil._pslinux.disk_io_counters: | 827 Adapted from the psutil module's psutil._pslinux.disk_io_counters: |
| 614 http://code.google.com/p/psutil/source/browse/trunk/psutil/_pslinux.py | 828 http://code.google.com/p/psutil/source/browse/trunk/psutil/_pslinux.py |
| 615 | 829 |
| 616 Originally distributed under under a BSD license. | 830 Originally distributed under under a BSD license. |
| 617 Original Copyright (c) 2009, Jay Loden, Dave Daeschler, Giampaolo Rodola. | 831 Original Copyright (c) 2009, Jay Loden, Dave Daeschler, Giampaolo Rodola. |
| 618 | 832 |
| 619 Returns: | 833 Returns: |
| (...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 695 result['tcp_retransmit'] = None | 909 result['tcp_retransmit'] = None |
| 696 result['tcp_received'] = None | 910 result['tcp_received'] = None |
| 697 result['tcp_sent'] = None | 911 result['tcp_sent'] = None |
| 698 | 912 |
| 699 return result | 913 return result |
| 700 | 914 |
| 701 def _CollectSysInfo(self): | 915 def _CollectSysInfo(self): |
| 702 """Collects system information.""" | 916 """Collects system information.""" |
| 703 sysinfo = {} | 917 sysinfo = {} |
| 704 | 918 |
| 705 # All exceptions that might be thrown from socket module calls. | 919 # All exceptions that might be raised from socket module calls. |
| 706 socket_errors = ( | 920 socket_errors = ( |
| 707 socket.error, socket.herror, socket.gaierror, socket.timeout) | 921 socket.error, socket.herror, socket.gaierror, socket.timeout) |
| 708 | 922 |
| 709 # Find out whether HTTPS is enabled in Boto. | 923 # Find out whether HTTPS is enabled in Boto. |
| 710 sysinfo['boto_https_enabled'] = boto.config.get('Boto', 'is_secure', True) | 924 sysinfo['boto_https_enabled'] = boto.config.get('Boto', 'is_secure', True) |
| 925 |
| 926 # Look up proxy info. |
| 927 proxy_host = boto.config.get('Boto', 'proxy', None) |
| 928 proxy_port = boto.config.getint('Boto', 'proxy_port', 0) |
| 929 sysinfo['using_proxy'] = bool(proxy_host) |
| 930 |
| 931 if boto.config.get('Boto', 'proxy_rdns', False): |
| 932 self.logger.info('DNS lookups are disallowed in this environment, so ' |
| 933 'some information is not included in this perfdiag run.') |
| 934 |
| 711 # Get the local IP address from socket lib. | 935 # Get the local IP address from socket lib. |
| 712 try: | 936 try: |
| 713 sysinfo['ip_address'] = socket.gethostbyname(socket.gethostname()) | 937 sysinfo['ip_address'] = socket.gethostbyname(socket.gethostname()) |
| 714 except socket_errors: | 938 except socket_errors: |
| 715 sysinfo['ip_address'] = '' | 939 sysinfo['ip_address'] = '' |
| 716 # Record the temporary directory used since it can affect performance, e.g. | 940 # Record the temporary directory used since it can affect performance, e.g. |
| 717 # when on a networked filesystem. | 941 # when on a networked filesystem. |
| 718 sysinfo['tempdir'] = tempfile.gettempdir() | 942 sysinfo['tempdir'] = tempfile.gettempdir() |
| 719 | 943 |
| 720 # Produces an RFC 2822 compliant GMT timestamp. | 944 # Produces an RFC 2822 compliant GMT timestamp. |
| 721 sysinfo['gmt_timestamp'] = time.strftime('%a, %d %b %Y %H:%M:%S +0000', | 945 sysinfo['gmt_timestamp'] = time.strftime('%a, %d %b %Y %H:%M:%S +0000', |
| 722 time.gmtime()) | 946 time.gmtime()) |
| 723 | 947 |
| 724 # Execute a CNAME lookup on Google DNS to find what Google server | 948 # Execute a CNAME lookup on Google DNS to find what Google server |
| 725 # it's routing to. | 949 # it's routing to. |
| 726 cmd = ['nslookup', '-type=CNAME', self.GOOGLE_API_HOST] | 950 cmd = ['nslookup', '-type=CNAME', self.XML_API_HOST] |
| 727 try: | 951 try: |
| 728 nslookup_cname_output = self._Exec(cmd, return_output=True) | 952 nslookup_cname_output = self._Exec(cmd, return_output=True) |
| 729 m = re.search(r' = (?P<googserv>[^.]+)\.', nslookup_cname_output) | 953 m = re.search(r' = (?P<googserv>[^.]+)\.', nslookup_cname_output) |
| 730 sysinfo['googserv_route'] = m.group('googserv') if m else None | 954 sysinfo['googserv_route'] = m.group('googserv') if m else None |
| 731 except OSError: | 955 except (CommandException, OSError): |
| 732 sysinfo['googserv_route'] = '' | 956 sysinfo['googserv_route'] = '' |
| 733 | 957 |
| 958 # Try to determine the latency of a DNS lookup for the Google hostname |
| 959 # endpoint. Note: we don't piggyback on gethostbyname_ex below because |
| 960 # the _ex version requires an extra RTT. |
| 961 try: |
| 962 t0 = time.time() |
| 963 socket.gethostbyname(self.XML_API_HOST) |
| 964 t1 = time.time() |
| 965 sysinfo['google_host_dns_latency'] = t1 - t0 |
| 966 except socket_errors: |
| 967 pass |
| 968 |
| 734 # Look up IP addresses for Google Server. | 969 # Look up IP addresses for Google Server. |
| 735 try: | 970 try: |
| 736 (hostname, aliaslist, ipaddrlist) = socket.gethostbyname_ex( | 971 (hostname, _, ipaddrlist) = socket.gethostbyname_ex(self.XML_API_HOST) |
| 737 self.GOOGLE_API_HOST) | |
| 738 sysinfo['googserv_ips'] = ipaddrlist | 972 sysinfo['googserv_ips'] = ipaddrlist |
| 739 except socket_errors: | 973 except socket_errors: |
| 974 ipaddrlist = [] |
| 740 sysinfo['googserv_ips'] = [] | 975 sysinfo['googserv_ips'] = [] |
| 741 | 976 |
| 742 # Reverse lookup the hostnames for the Google Server IPs. | 977 # Reverse lookup the hostnames for the Google Server IPs. |
| 743 sysinfo['googserv_hostnames'] = [] | 978 sysinfo['googserv_hostnames'] = [] |
| 744 for googserv_ip in ipaddrlist: | 979 for googserv_ip in ipaddrlist: |
| 745 try: | 980 try: |
| 746 (hostname, aliaslist, ipaddrlist) = socket.gethostbyaddr(googserv_ip) | 981 (hostname, _, ipaddrlist) = socket.gethostbyaddr(googserv_ip) |
| 747 sysinfo['googserv_hostnames'].append(hostname) | 982 sysinfo['googserv_hostnames'].append(hostname) |
| 748 except socket_errors: | 983 except socket_errors: |
| 749 pass | 984 pass |
| 750 | 985 |
| 751 # Query o-o to find out what the Google DNS thinks is the user's IP. | 986 # Query o-o to find out what the Google DNS thinks is the user's IP. |
| 752 try: | 987 try: |
| 753 cmd = ['nslookup', '-type=TXT', 'o-o.myaddr.google.com.'] | 988 cmd = ['nslookup', '-type=TXT', 'o-o.myaddr.google.com.'] |
| 754 nslookup_txt_output = self._Exec(cmd, return_output=True) | 989 nslookup_txt_output = self._Exec(cmd, return_output=True) |
| 755 m = re.search(r'text\s+=\s+"(?P<dnsip>[\.\d]+)"', nslookup_txt_output) | 990 m = re.search(r'text\s+=\s+"(?P<dnsip>[\.\d]+)"', nslookup_txt_output) |
| 756 sysinfo['dns_o-o_ip'] = m.group('dnsip') if m else None | 991 sysinfo['dns_o-o_ip'] = m.group('dnsip') if m else None |
| 757 except OSError: | 992 except (CommandException, OSError): |
| 758 sysinfo['dns_o-o_ip'] = '' | 993 sysinfo['dns_o-o_ip'] = '' |
| 759 | 994 |
| 995 # Try to determine the latency of connecting to the Google hostname |
| 996 # endpoint. |
| 997 sysinfo['google_host_connect_latencies'] = {} |
| 998 for googserv_ip in ipaddrlist: |
| 999 try: |
| 1000 sock = socket.socket() |
| 1001 t0 = time.time() |
| 1002 sock.connect((googserv_ip, self.XML_API_PORT)) |
| 1003 t1 = time.time() |
| 1004 sysinfo['google_host_connect_latencies'][googserv_ip] = t1 - t0 |
| 1005 except socket_errors: |
| 1006 pass |
| 1007 |
| 1008 # If using a proxy, try to determine the latency of a DNS lookup to resolve |
| 1009 # the proxy hostname and the latency of connecting to the proxy. |
| 1010 if proxy_host: |
| 1011 proxy_ip = None |
| 1012 try: |
| 1013 t0 = time.time() |
| 1014 proxy_ip = socket.gethostbyname(proxy_host) |
| 1015 t1 = time.time() |
| 1016 sysinfo['proxy_dns_latency'] = t1 - t0 |
| 1017 except socket_errors: |
| 1018 pass |
| 1019 |
| 1020 try: |
| 1021 sock = socket.socket() |
| 1022 t0 = time.time() |
| 1023 sock.connect((proxy_ip or proxy_host, proxy_port)) |
| 1024 t1 = time.time() |
| 1025 sysinfo['proxy_host_connect_latency'] = t1 - t0 |
| 1026 except socket_errors: |
| 1027 pass |
| 1028 |
| 760 # Try and find the number of CPUs in the system if available. | 1029 # Try and find the number of CPUs in the system if available. |
| 761 try: | 1030 try: |
| 762 sysinfo['cpu_count'] = multiprocessing.cpu_count() | 1031 sysinfo['cpu_count'] = multiprocessing.cpu_count() |
| 763 except NotImplementedError: | 1032 except NotImplementedError: |
| 764 sysinfo['cpu_count'] = None | 1033 sysinfo['cpu_count'] = None |
| 765 | 1034 |
| 766 # For *nix platforms, obtain the CPU load. | 1035 # For *nix platforms, obtain the CPU load. |
| 767 try: | 1036 try: |
| 768 sysinfo['load_avg'] = list(os.getloadavg()) | 1037 sysinfo['load_avg'] = list(os.getloadavg()) |
| 769 except (AttributeError, OSError): | 1038 except (AttributeError, OSError): |
| (...skipping 125 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 895 print 'Read Throughput'.center(78) | 1164 print 'Read Throughput'.center(78) |
| 896 print '-' * 78 | 1165 print '-' * 78 |
| 897 read_thru = self.results['read_throughput'] | 1166 read_thru = self.results['read_throughput'] |
| 898 print 'Copied a %s file %d times for a total transfer size of %s.' % ( | 1167 print 'Copied a %s file %d times for a total transfer size of %s.' % ( |
| 899 MakeHumanReadable(read_thru['file_size']), | 1168 MakeHumanReadable(read_thru['file_size']), |
| 900 read_thru['num_times'], | 1169 read_thru['num_times'], |
| 901 MakeHumanReadable(read_thru['total_bytes_copied'])) | 1170 MakeHumanReadable(read_thru['total_bytes_copied'])) |
| 902 print 'Read throughput: %s/s.' % ( | 1171 print 'Read throughput: %s/s.' % ( |
| 903 MakeBitsHumanReadable(read_thru['bytes_per_second'] * 8)) | 1172 MakeBitsHumanReadable(read_thru['bytes_per_second'] * 8)) |
| 904 | 1173 |
| 1174 if 'listing' in self.results: |
| 1175 print |
| 1176 print '-' * 78 |
| 1177 print 'Listing'.center(78) |
| 1178 print '-' * 78 |
| 1179 |
| 1180 listing = self.results['listing'] |
| 1181 insert = listing['insert'] |
| 1182 delete = listing['delete'] |
| 1183 print 'After inserting %s objects:' % listing['num_files'] |
| 1184 print (' Total time for objects to appear: %.2g seconds' % |
| 1185 insert['time_took']) |
| 1186 print ' Number of listing calls made: %s' % insert['num_listing_calls'] |
| 1187 print (' Individual listing call latencies: [%s]' % |
| 1188 ', '.join('%.2gs' % lat for lat in insert['list_latencies'])) |
| 1189 print (' Files reflected after each call: [%s]' % |
| 1190 ', '.join(map(str, insert['files_seen_after_listing']))) |
| 1191 |
| 1192 print 'After deleting %s objects:' % listing['num_files'] |
| 1193 print (' Total time for objects to appear: %.2g seconds' % |
| 1194 delete['time_took']) |
| 1195 print ' Number of listing calls made: %s' % delete['num_listing_calls'] |
| 1196 print (' Individual listing call latencies: [%s]' % |
| 1197 ', '.join('%.2gs' % lat for lat in delete['list_latencies'])) |
| 1198 print (' Files reflected after each call: [%s]' % |
| 1199 ', '.join(map(str, delete['files_seen_after_listing']))) |
| 1200 |
| 905 if 'sysinfo' in self.results: | 1201 if 'sysinfo' in self.results: |
| 906 print | 1202 print |
| 907 print '-' * 78 | 1203 print '-' * 78 |
| 908 print 'System Information'.center(78) | 1204 print 'System Information'.center(78) |
| 909 print '-' * 78 | 1205 print '-' * 78 |
| 910 info = self.results['sysinfo'] | 1206 info = self.results['sysinfo'] |
| 911 print 'IP Address: \n %s' % info['ip_address'] | 1207 print 'IP Address: \n %s' % info['ip_address'] |
| 912 print 'Temporary Directory: \n %s' % info['tempdir'] | 1208 print 'Temporary Directory: \n %s' % info['tempdir'] |
| 913 print 'Bucket URI: \n %s' % self.results['bucket_uri'] | 1209 print 'Bucket URI: \n %s' % self.results['bucket_uri'] |
| 914 print 'gsutil Version: \n %s' % self.results.get('gsutil_version', | 1210 print 'gsutil Version: \n %s' % self.results.get('gsutil_version', |
| (...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 982 print | 1278 print |
| 983 | 1279 |
| 984 if 'tcp_proc_values' in info: | 1280 if 'tcp_proc_values' in info: |
| 985 print 'TCP /proc values:\n', | 1281 print 'TCP /proc values:\n', |
| 986 for item in info['tcp_proc_values'].iteritems(): | 1282 for item in info['tcp_proc_values'].iteritems(): |
| 987 print ' %s = %s' % item | 1283 print ' %s = %s' % item |
| 988 | 1284 |
| 989 if 'boto_https_enabled' in info: | 1285 if 'boto_https_enabled' in info: |
| 990 print 'Boto HTTPS Enabled: \n %s' % info['boto_https_enabled'] | 1286 print 'Boto HTTPS Enabled: \n %s' % info['boto_https_enabled'] |
| 991 | 1287 |
| 1288 if 'using_proxy' in info: |
| 1289 print 'Requests routed through proxy: \n %s' % info['using_proxy'] |
| 1290 |
| 1291 if 'google_host_dns_latency' in info: |
| 1292 print ('Latency of the DNS lookup for Google Storage server (ms): ' |
| 1293 '\n %.1f' % (info['google_host_dns_latency'] * 1000.0)) |
| 1294 |
| 1295 if 'google_host_connect_latencies' in info: |
| 1296 print 'Latencies connecting to Google Storage server IPs (ms):' |
| 1297 for ip, latency in info['google_host_connect_latencies'].iteritems(): |
| 1298 print ' %s = %.1f' % (ip, latency * 1000.0) |
| 1299 |
| 1300 if 'proxy_dns_latency' in info: |
| 1301 print ('Latency of the DNS lookup for the configured proxy (ms): ' |
| 1302 '\n %.1f' % (info['proxy_dns_latency'] * 1000.0)) |
| 1303 |
| 1304 if 'proxy_host_connect_latency' in info: |
| 1305 print ('Latency connecting to the configured proxy (ms): \n %.1f' % |
| 1306 (info['proxy_host_connect_latency'] * 1000.0)) |
| 1307 |
| 992 if 'request_errors' in self.results and 'total_requests' in self.results: | 1308 if 'request_errors' in self.results and 'total_requests' in self.results: |
| 993 print | 1309 print |
| 994 print '-' * 78 | 1310 print '-' * 78 |
| 995 print 'In-Process HTTP Statistics'.center(78) | 1311 print 'In-Process HTTP Statistics'.center(78) |
| 996 print '-' * 78 | 1312 print '-' * 78 |
| 997 total = int(self.results['total_requests']) | 1313 total = int(self.results['total_requests']) |
| 998 numerrors = int(self.results['request_errors']) | 1314 numerrors = int(self.results['request_errors']) |
| 999 numbreaks = int(self.results['connection_breaks']) | 1315 numbreaks = int(self.results['connection_breaks']) |
| 1000 availability = (((total - numerrors) / float(total)) * 100 | 1316 availability = (((total - numerrors) / float(total)) * 100 |
| 1001 if total > 0 else 100) | 1317 if total > 0 else 100) |
| (...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1043 """Parses arguments for perfdiag command.""" | 1359 """Parses arguments for perfdiag command.""" |
| 1044 # From -n. | 1360 # From -n. |
| 1045 self.num_iterations = 5 | 1361 self.num_iterations = 5 |
| 1046 # From -c. | 1362 # From -c. |
| 1047 self.processes = 1 | 1363 self.processes = 1 |
| 1048 # From -k. | 1364 # From -k. |
| 1049 self.threads = 1 | 1365 self.threads = 1 |
| 1050 # From -s. | 1366 # From -s. |
| 1051 self.thru_filesize = 1048576 | 1367 self.thru_filesize = 1048576 |
| 1052 # From -t. | 1368 # From -t. |
| 1053 self.diag_tests = self.ALL_DIAG_TESTS | 1369 self.diag_tests = self.DEFAULT_DIAG_TESTS |
| 1054 # From -o. | 1370 # From -o. |
| 1055 self.output_file = None | 1371 self.output_file = None |
| 1056 # From -i. | 1372 # From -i. |
| 1057 self.input_file = None | 1373 self.input_file = None |
| 1058 # From -m. | 1374 # From -m. |
| 1059 self.metadata_keys = {} | 1375 self.metadata_keys = {} |
| 1060 | 1376 |
| 1061 if self.sub_opts: | 1377 if self.sub_opts: |
| 1062 for o, a in self.sub_opts: | 1378 for o, a in self.sub_opts: |
| 1063 if o == '-n': | 1379 if o == '-n': |
| (...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1102 self.results = json.load(f) | 1418 self.results = json.load(f) |
| 1103 self.logger.info("Read input file: '%s'.", self.input_file) | 1419 self.logger.info("Read input file: '%s'.", self.input_file) |
| 1104 except ValueError: | 1420 except ValueError: |
| 1105 raise CommandException("Could not decode input file (-i): '%s'." % | 1421 raise CommandException("Could not decode input file (-i): '%s'." % |
| 1106 a) | 1422 a) |
| 1107 return | 1423 return |
| 1108 | 1424 |
| 1109 if not self.args: | 1425 if not self.args: |
| 1110 raise CommandException('Wrong number of arguments for "perfdiag" ' | 1426 raise CommandException('Wrong number of arguments for "perfdiag" ' |
| 1111 'command.') | 1427 'command.') |
| 1112 self.bucket_uri = self.suri_builder.StorageUri(self.args[0]) | 1428 |
| 1113 if not self.bucket_uri.names_bucket(): | 1429 self.bucket_url = StorageUrlFromString(self.args[0]) |
| 1114 raise CommandException('The perfdiag command requires a URI that ' | 1430 self.provider = self.bucket_url.scheme |
| 1431 if not (self.bucket_url.IsCloudUrl() and self.bucket_url.IsBucket()): |
| 1432 raise CommandException('The perfdiag command requires a URL that ' |
| 1115 'specifies a bucket.\n"%s" is not ' | 1433 'specifies a bucket.\n"%s" is not ' |
| 1116 'valid.' % self.bucket_uri) | 1434 'valid.' % self.args[0]) |
| 1117 self.bucket = self.bucket_uri.get_bucket() | 1435 # Ensure the bucket exists. |
| 1118 | 1436 self.gsutil_api.GetBucket(self.bucket_url.bucket_name, |
| 1119 # TODO: Add MD5 argument support to get_contents_to_file() | 1437 provider=self.bucket_url.scheme, |
| 1120 # and pass the file md5 as a parameter to avoid any unnecessary | 1438 fields=['id']) |
| 1121 # computation. | 1439 self.exceptions = [httplib.HTTPException, socket.error, socket.gaierror, |
| 1122 self.get_contents_to_file_args = {} | 1440 httplib.BadStatusLine, ServiceException] |
| 1123 if self.bucket_uri.scheme == 'gs': | |
| 1124 self.get_contents_to_file_args = {'hash_algs': {}} | |
| 1125 | 1441 |
| 1126 # Command entry point. | 1442 # Command entry point. |
| 1127 def RunCommand(self): | 1443 def RunCommand(self): |
| 1128 """Called by gsutil when the command is being invoked.""" | 1444 """Called by gsutil when the command is being invoked.""" |
| 1129 self._ParseArgs() | 1445 self._ParseArgs() |
| 1130 | 1446 |
| 1131 if self.input_file: | 1447 if self.input_file: |
| 1132 self._DisplayResults() | 1448 self._DisplayResults() |
| 1133 return 0 | 1449 return 0 |
| 1134 | 1450 |
| 1135 # We turn off retries in the underlying boto library because the | 1451 # We turn off retries in the underlying boto library because the |
| 1136 # _RunOperation function handles errors manually so it can count them. | 1452 # _RunOperation function handles errors manually so it can count them. |
| 1137 boto.config.set('Boto', 'num_retries', '0') | 1453 boto.config.set('Boto', 'num_retries', '0') |
| 1138 | 1454 |
| 1139 self.logger.info( | 1455 self.logger.info( |
| 1140 'Number of iterations to run: %d\n' | 1456 'Number of iterations to run: %d\n' |
| 1141 'Base bucket URI: %s\n' | 1457 'Base bucket URI: %s\n' |
| 1142 'Number of processes: %d\n' | 1458 'Number of processes: %d\n' |
| 1143 'Number of threads: %d\n' | 1459 'Number of threads: %d\n' |
| 1144 'Throughput file size: %s\n' | 1460 'Throughput file size: %s\n' |
| 1145 'Diagnostics to run: %s', | 1461 'Diagnostics to run: %s', |
| 1146 self.num_iterations, | 1462 self.num_iterations, |
| 1147 self.bucket_uri, | 1463 self.bucket_url, |
| 1148 self.processes, | 1464 self.processes, |
| 1149 self.threads, | 1465 self.threads, |
| 1150 MakeHumanReadable(self.thru_filesize), | 1466 MakeHumanReadable(self.thru_filesize), |
| 1151 (', '.join(self.diag_tests))) | 1467 (', '.join(self.diag_tests))) |
| 1152 | 1468 |
| 1153 try: | 1469 try: |
| 1154 self._SetUp() | 1470 self._SetUp() |
| 1155 | 1471 |
| 1156 # Collect generic system info. | 1472 # Collect generic system info. |
| 1157 self._CollectSysInfo() | 1473 self._CollectSysInfo() |
| 1158 # Collect netstat info and disk counters before tests (and again later). | 1474 # Collect netstat info and disk counters before tests (and again later). |
| 1159 self.results['sysinfo']['netstat_start'] = self._GetTcpStats() | 1475 self.results['sysinfo']['netstat_start'] = self._GetTcpStats() |
| 1160 if IS_LINUX: | 1476 if IS_LINUX: |
| 1161 self.results['sysinfo']['disk_counters_start'] = self._GetDiskCounters() | 1477 self.results['sysinfo']['disk_counters_start'] = self._GetDiskCounters() |
| 1162 # Record bucket URI. | 1478 # Record bucket URL. |
| 1163 self.results['bucket_uri'] = str(self.bucket_uri) | 1479 self.results['bucket_uri'] = str(self.bucket_url) |
| 1164 self.results['json_format'] = 'perfdiag' | 1480 self.results['json_format'] = 'perfdiag' |
| 1165 self.results['metadata'] = self.metadata_keys | 1481 self.results['metadata'] = self.metadata_keys |
| 1166 | 1482 |
| 1167 if 'lat' in self.diag_tests: | 1483 if self.LAT in self.diag_tests: |
| 1168 self._RunLatencyTests() | 1484 self._RunLatencyTests() |
| 1169 if 'rthru' in self.diag_tests: | 1485 if self.RTHRU in self.diag_tests: |
| 1170 self._RunReadThruTests() | 1486 self._RunReadThruTests() |
| 1171 if 'wthru' in self.diag_tests: | 1487 if self.WTHRU in self.diag_tests: |
| 1172 self._RunWriteThruTests() | 1488 self._RunWriteThruTests() |
| 1489 if self.LIST in self.diag_tests: |
| 1490 self._RunListTests() |
| 1173 | 1491 |
| 1174 # Collect netstat info and disk counters after tests. | 1492 # Collect netstat info and disk counters after tests. |
| 1175 self.results['sysinfo']['netstat_end'] = self._GetTcpStats() | 1493 self.results['sysinfo']['netstat_end'] = self._GetTcpStats() |
| 1176 if IS_LINUX: | 1494 if IS_LINUX: |
| 1177 self.results['sysinfo']['disk_counters_end'] = self._GetDiskCounters() | 1495 self.results['sysinfo']['disk_counters_end'] = self._GetDiskCounters() |
| 1178 | 1496 |
| 1179 self.results['total_requests'] = self.total_requests | 1497 self.results['total_requests'] = self.total_requests |
| 1180 self.results['request_errors'] = self.request_errors | 1498 self.results['request_errors'] = self.request_errors |
| 1181 self.results['error_responses_by_code'] = self.error_responses_by_code | 1499 self.results['error_responses_by_code'] = self.error_responses_by_code |
| 1182 self.results['connection_breaks'] = self.connection_breaks | 1500 self.results['connection_breaks'] = self.connection_breaks |
| 1183 self.results['gsutil_version'] = gslib.VERSION | 1501 self.results['gsutil_version'] = gslib.VERSION |
| 1184 self.results['boto_version'] = boto.__version__ | 1502 self.results['boto_version'] = boto.__version__ |
| 1185 | 1503 |
| 1186 self._DisplayResults() | 1504 self._DisplayResults() |
| 1187 finally: | 1505 finally: |
| 1188 self._TearDown() | 1506 self._TearDown() |
| 1189 | 1507 |
| 1190 return 0 | 1508 return 0 |
| 1509 |
| 1510 |
| 1511 class UploadObjectTuple(object): |
| 1512 """Picklable tuple with necessary metadata for an insert object call.""" |
| 1513 |
| 1514 def __init__(self, bucket_name, object_name, filepath=None, md5=None, |
| 1515 contents=None): |
| 1516 """Create an upload tuple. |
| 1517 |
| 1518 Args: |
| 1519 bucket_name: Name of the bucket to upload to. |
| 1520 object_name: Name of the object to upload to. |
| 1521 filepath: A file path located in self.file_contents and self.file_md5s. |
| 1522 md5: The MD5 hash of the object being uploaded. |
| 1523 contents: The contents of the file to be uploaded. |
| 1524 |
| 1525 Note: (contents + md5) and filepath are mutually exlusive. You may specify |
| 1526 one or the other, but not both. |
| 1527 Note: If one of contents or md5 are specified, they must both be specified. |
| 1528 |
| 1529 Raises: |
| 1530 InvalidArgument: if the arguments are invalid. |
| 1531 """ |
| 1532 self.bucket_name = bucket_name |
| 1533 self.object_name = object_name |
| 1534 self.filepath = filepath |
| 1535 self.md5 = md5 |
| 1536 self.contents = contents |
| 1537 if filepath and (md5 or contents is not None): |
| 1538 raise InvalidArgument( |
| 1539 'Only one of filepath or (md5 + contents) may be specified.') |
| 1540 if not filepath and (not md5 or contents is None): |
| 1541 raise InvalidArgument( |
| 1542 'Both md5 and contents must be specified.') |
| 1543 |
| 1544 |
| 1545 def StorageUrlToUploadObjectMetadata(storage_url): |
| 1546 if storage_url.IsCloudUrl() and storage_url.IsObject(): |
| 1547 upload_target = apitools_messages.Object() |
| 1548 upload_target.name = storage_url.object_name |
| 1549 upload_target.bucket = storage_url.bucket_name |
| 1550 return upload_target |
| 1551 else: |
| 1552 raise CommandException('Non-cloud URL upload target %s was created in ' |
| 1553 'perfdiag implemenation.' % storage_url) |
| OLD | NEW |