| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright 2014 The Swarming Authors. All rights reserved. | 2 # Copyright 2014 The Swarming Authors. All rights reserved. |
| 3 # Use of this source code is governed by the Apache v2.0 license that can be | 3 # Use of this source code is governed by the Apache v2.0 license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """OS specific utility functions. | 6 """OS specific utility functions. |
| 7 | 7 |
| 8 Includes code: | 8 Includes code: |
| 9 - to declare the current system this code is running under. | 9 - to declare the current system this code is running under. |
| 10 - to run a command on user login. | 10 - to run a command on user login. |
| (...skipping 19 matching lines...) Expand all Loading... |
| 30 import socket | 30 import socket |
| 31 import string | 31 import string |
| 32 import subprocess | 32 import subprocess |
| 33 import sys | 33 import sys |
| 34 import tempfile | 34 import tempfile |
| 35 import threading | 35 import threading |
| 36 import time | 36 import time |
| 37 import urllib | 37 import urllib |
| 38 import urllib2 | 38 import urllib2 |
| 39 | 39 |
| 40 try: | 40 from utils import file_path |
| 41 # The reason for this try/except is so someone can copy this single file on a | 41 from utils import tools |
| 42 # new machine and execute it as-is to get the dimensions that would be set. | 42 from utils import zip_package |
| 43 from utils import file_path | |
| 44 from utils import tools | |
| 45 from utils import zip_package | |
| 46 | 43 |
| 47 THIS_FILE = os.path.abspath(zip_package.get_main_script_path() or __file__) | 44 THIS_FILE = os.path.abspath(zip_package.get_main_script_path() or __file__) |
| 48 except ImportError as e: | |
| 49 THIS_FILE = os.path.abspath(__file__) | |
| 50 tools = None | |
| 51 print >> sys.stderr, 'Failed to import tools: %s' % e | |
| 52 | |
| 53 | |
| 54 # Properties from an android device that should be kept as dimension. | |
| 55 ANDROID_DETAILS = frozenset( | |
| 56 [ | |
| 57 # Hardware details. | |
| 58 u'ro.board.platform', | |
| 59 u'ro.product.board', # or ro.product.device? | |
| 60 u'ro.product.cpu.abi', | |
| 61 u'ro.product.cpu.abi2', | |
| 62 | |
| 63 # OS details. | |
| 64 u'ro.build.id', | |
| 65 u'ro.build.tags', | |
| 66 u'ro.build.type', | |
| 67 u'ro.build.version.sdk', | |
| 68 ]) | |
| 69 | 45 |
| 70 | 46 |
| 71 # https://cloud.google.com/compute/pricing#machinetype | 47 # https://cloud.google.com/compute/pricing#machinetype |
| 72 GCE_MACHINE_COST_HOUR_US = { | 48 GCE_MACHINE_COST_HOUR_US = { |
| 73 u'n1-standard-1': 0.063, | 49 u'n1-standard-1': 0.063, |
| 74 u'n1-standard-2': 0.126, | 50 u'n1-standard-2': 0.126, |
| 75 u'n1-standard-4': 0.252, | 51 u'n1-standard-4': 0.252, |
| 76 u'n1-standard-8': 0.504, | 52 u'n1-standard-8': 0.504, |
| 77 u'n1-standard-16': 1.008, | 53 u'n1-standard-16': 1.008, |
| 78 u'f1-micro': 0.012, | 54 u'f1-micro': 0.012, |
| (...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 120 GCE_SSD_GB_COST_MONTH = 0.17 | 96 GCE_SSD_GB_COST_MONTH = 0.17 |
| 121 | 97 |
| 122 | 98 |
| 123 # https://cloud.google.com/compute/pricing#premiumoperatingsystems | 99 # https://cloud.google.com/compute/pricing#premiumoperatingsystems |
| 124 GCE_WINDOWS_COST_CORE_HOUR = 0.04 | 100 GCE_WINDOWS_COST_CORE_HOUR = 0.04 |
| 125 | 101 |
| 126 | 102 |
| 127 ### Private stuff. | 103 ### Private stuff. |
| 128 | 104 |
| 129 | 105 |
| 106 # Used to calculated Swarming bot uptime. |
| 130 _STARTED_TS = time.time() | 107 _STARTED_TS = time.time() |
| 131 | 108 |
| 109 |
| 110 # Cache of GCE OAuth2 token. |
| 132 _CACHED_OAUTH2_TOKEN_GCE = {} | 111 _CACHED_OAUTH2_TOKEN_GCE = {} |
| 133 _CACHED_OAUTH2_TOKEN_GCE_LOCK = threading.Lock() | 112 _CACHED_OAUTH2_TOKEN_GCE_LOCK = threading.Lock() |
| 134 | 113 |
| 135 _MONITORING_SCOPE = 'https://www.googleapis.com/auth/monitoring' | |
| 136 | |
| 137 | |
| 138 # Make cached a no-op when client/utils/tools.py is unavailable. | |
| 139 if not tools: | |
| 140 def cached(func): | |
| 141 return func | |
| 142 else: | |
| 143 cached = tools.cached | |
| 144 | |
| 145 | 114 |
| 146 def _write(filepath, content): | 115 def _write(filepath, content): |
| 147 """Writes out a file and returns True on success.""" | 116 """Writes out a file and returns True on success.""" |
| 148 logging.info('Writing in %s:\n%s', filepath, content) | 117 logging.info('Writing in %s:\n%s', filepath, content) |
| 149 try: | 118 try: |
| 150 with open(filepath, mode='wb') as f: | 119 with open(filepath, mode='wb') as f: |
| 151 f.write(content) | 120 f.write(content) |
| 152 return True | 121 return True |
| 153 except IOError as e: | 122 except IOError as e: |
| 154 logging.error('Failed to write %s: %s', filepath, e) | 123 logging.error('Failed to write %s: %s', filepath, e) |
| (...skipping 255 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 410 if dev_type.startswith('03'): | 379 if dev_type.startswith('03'): |
| 411 vendor = re_id.match(line[2]) | 380 vendor = re_id.match(line[2]) |
| 412 device = re_id.match(line[3]) | 381 device = re_id.match(line[3]) |
| 413 ven_id = vendor.group(2) | 382 ven_id = vendor.group(2) |
| 414 dimensions.add(unicode(ven_id)) | 383 dimensions.add(unicode(ven_id)) |
| 415 dimensions.add(u'%s:%s' % (ven_id, device.group(2))) | 384 dimensions.add(u'%s:%s' % (ven_id, device.group(2))) |
| 416 state.add(u'%s %s' % (vendor.group(1), device.group(1))) | 385 state.add(u'%s %s' % (vendor.group(1), device.group(1))) |
| 417 return sorted(dimensions), sorted(state) | 386 return sorted(dimensions), sorted(state) |
| 418 | 387 |
| 419 | 388 |
| 420 @cached | 389 @tools.cached |
| 421 def _get_SPDisplaysDataType_osx(): | 390 def _get_SPDisplaysDataType_osx(): |
| 422 """Returns an XML about the system display properties.""" | 391 """Returns an XML about the system display properties.""" |
| 423 import plistlib | 392 import plistlib |
| 424 sp = subprocess.check_output( | 393 sp = subprocess.check_output( |
| 425 ['system_profiler', 'SPDisplaysDataType', '-xml']) | 394 ['system_profiler', 'SPDisplaysDataType', '-xml']) |
| 426 return plistlib.readPlistFromString(sp)[0]['_items'] | 395 return plistlib.readPlistFromString(sp)[0]['_items'] |
| 427 | 396 |
| 428 | 397 |
| 429 def _get_gpu_osx(): | 398 def _get_gpu_osx(): |
| 430 """Returns video device as listed by 'system_profiler'. See get_gpu().""" | 399 """Returns video device as listed by 'system_profiler'. See get_gpu().""" |
| (...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 492 if match: | 461 if match: |
| 493 ven_id = match.group(1).lower() | 462 ven_id = match.group(1).lower() |
| 494 match = re.search(r'DEV_([0-9A-F]{4})', pnp_string) | 463 match = re.search(r'DEV_([0-9A-F]{4})', pnp_string) |
| 495 if match: | 464 if match: |
| 496 dev_id = match.group(1).lower() | 465 dev_id = match.group(1).lower() |
| 497 dimensions.add(unicode(ven_id)) | 466 dimensions.add(unicode(ven_id)) |
| 498 dimensions.add(u'%s:%s' % (ven_id, dev_id)) | 467 dimensions.add(u'%s:%s' % (ven_id, dev_id)) |
| 499 return sorted(dimensions), sorted(state) | 468 return sorted(dimensions), sorted(state) |
| 500 | 469 |
| 501 | 470 |
| 502 @cached | 471 @tools.cached |
| 503 def _get_mount_points_win(): | 472 def _get_mount_points_win(): |
| 504 """Returns the list of 'fixed' drives in format 'X:\\'.""" | 473 """Returns the list of 'fixed' drives in format 'X:\\'.""" |
| 505 ctypes.windll.kernel32.GetDriveTypeW.argtypes = (ctypes.c_wchar_p,) | 474 ctypes.windll.kernel32.GetDriveTypeW.argtypes = (ctypes.c_wchar_p,) |
| 506 ctypes.windll.kernel32.GetDriveTypeW.restype = ctypes.c_ulong | 475 ctypes.windll.kernel32.GetDriveTypeW.restype = ctypes.c_ulong |
| 507 DRIVE_FIXED = 3 | 476 DRIVE_FIXED = 3 |
| 508 # https://msdn.microsoft.com/library/windows/desktop/aa364939.aspx | 477 # https://msdn.microsoft.com/library/windows/desktop/aa364939.aspx |
| 509 return [ | 478 return [ |
| 510 letter + ':\\' | 479 letter + ':\\' |
| 511 for letter in string.lowercase | 480 for letter in string.lowercase |
| 512 if ctypes.windll.kernel32.GetDriveTypeW(letter + ':\\') == DRIVE_FIXED | 481 if ctypes.windll.kernel32.GetDriveTypeW(letter + ':\\') == DRIVE_FIXED |
| (...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 554 return dict( | 523 return dict( |
| 555 ( | 524 ( |
| 556 items[5], | 525 items[5], |
| 557 { | 526 { |
| 558 'free_mb': round(float(items[3]) / 1024., 1), | 527 'free_mb': round(float(items[3]) / 1024., 1), |
| 559 'size_mb': round(float(items[1]) / 1024., 1), | 528 'size_mb': round(float(items[1]) / 1024., 1), |
| 560 } | 529 } |
| 561 ) for items in _run_df()) | 530 ) for items in _run_df()) |
| 562 | 531 |
| 563 | 532 |
| 564 @cached | 533 @tools.cached |
| 565 def _get_metadata_gce(): | 534 def _get_metadata_gce(): |
| 566 """Returns the GCE metadata as a dict. | 535 """Returns the GCE metadata as a dict. |
| 567 | 536 |
| 568 Refs: | 537 Refs: |
| 569 https://cloud.google.com/compute/docs/metadata | 538 https://cloud.google.com/compute/docs/metadata |
| 570 https://cloud.google.com/compute/docs/machine-types | 539 https://cloud.google.com/compute/docs/machine-types |
| 571 | 540 |
| 572 To get the at the command line from a GCE VM, use: | 541 To get the at the command line from a GCE VM, use: |
| 573 curl --silent \ | 542 curl --silent \ |
| 574 http://metadata.google.internal/computeMetadata/v1/?recursive=true \ | 543 http://metadata.google.internal/computeMetadata/v1/?recursive=true \ |
| (...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 657 try: | 626 try: |
| 658 with open(filepath, 'rb') as f: | 627 with open(filepath, 'rb') as f: |
| 659 return f.read() | 628 return f.read() |
| 660 except (IOError, OSError): | 629 except (IOError, OSError): |
| 661 return None | 630 return None |
| 662 | 631 |
| 663 | 632 |
| 664 ### Public API. | 633 ### Public API. |
| 665 | 634 |
| 666 | 635 |
| 667 @cached | 636 @tools.cached |
| 668 def get_os_version_number(): | 637 def get_os_version_number(): |
| 669 """Returns the normalized OS version number as a string. | 638 """Returns the normalized OS version number as a string. |
| 670 | 639 |
| 671 Returns: | 640 Returns: |
| 672 The format depends on the OS: | 641 The format depends on the OS: |
| 673 - Windows: 5.1, 6.1, etc. There is no way to distinguish between Windows 7 | 642 - Windows: 5.1, 6.1, etc. There is no way to distinguish between Windows 7 |
| 674 and Windows Server 2008R2 since they both report 6.1. | 643 and Windows Server 2008R2 since they both report 6.1. |
| 675 - OSX: 10.7, 10.8, etc. | 644 - OSX: 10.7, 10.8, etc. |
| 676 - Ubuntu: 12.04, 10.04, etc. | 645 - Ubuntu: 12.04, 10.04, etc. |
| 677 Others will return None. | 646 Others will return None. |
| (...skipping 19 matching lines...) Expand all Loading... |
| 697 | 666 |
| 698 if sys.platform == 'linux2': | 667 if sys.platform == 'linux2': |
| 699 # On Ubuntu it will return a string like '12.04'. On Raspbian, it will look | 668 # On Ubuntu it will return a string like '12.04'. On Raspbian, it will look |
| 700 # like '7.6'. | 669 # like '7.6'. |
| 701 return unicode(platform.linux_distribution()[1]) | 670 return unicode(platform.linux_distribution()[1]) |
| 702 | 671 |
| 703 logging.error('Unable to determine platform version') | 672 logging.error('Unable to determine platform version') |
| 704 return None | 673 return None |
| 705 | 674 |
| 706 | 675 |
| 707 @cached | 676 @tools.cached |
| 708 def get_os_version_name(): | 677 def get_os_version_name(): |
| 709 """Returns the marketing name on Windows. | 678 """Returns the marketing name on Windows. |
| 710 | 679 |
| 711 Returns None on other OSes, since it's not problematic there. Having | 680 Returns None on other OSes, since it's not problematic there. Having |
| 712 dimensions like Trusty or Snow Leopard is not useful. | 681 dimensions like Trusty or Snow Leopard is not useful. |
| 713 """ | 682 """ |
| 714 if sys.platform == 'win32': | 683 if sys.platform == 'win32': |
| 715 return _get_os_version_name_win() | 684 return _get_os_version_name_win() |
| 716 return None | 685 return None |
| 717 | 686 |
| 718 | 687 |
| 719 @cached | 688 @tools.cached |
| 720 def get_os_name(): | 689 def get_os_name(): |
| 721 """Returns standardized OS name. | 690 """Returns standardized OS name. |
| 722 | 691 |
| 723 Defaults to sys.platform for OS not normalized. | 692 Defaults to sys.platform for OS not normalized. |
| 724 | 693 |
| 725 Returns: | 694 Returns: |
| 726 Windows, Mac, Ubuntu, Raspbian, etc. | 695 Windows, Mac, Ubuntu, Raspbian, etc. |
| 727 """ | 696 """ |
| 728 value = { | 697 value = { |
| 729 'cygwin': u'Windows', | 698 'cygwin': u'Windows', |
| (...skipping 10 matching lines...) Expand all Loading... |
| 740 content = _safe_read('/etc/os-release') | 709 content = _safe_read('/etc/os-release') |
| 741 if content: | 710 if content: |
| 742 os_release = dict(l.split('=', 1) for l in content.splitlines() if l) | 711 os_release = dict(l.split('=', 1) for l in content.splitlines() if l) |
| 743 os_id = os_release.get('ID').strip('"') | 712 os_id = os_release.get('ID').strip('"') |
| 744 # Uppercase the first letter for consistency with the other platforms. | 713 # Uppercase the first letter for consistency with the other platforms. |
| 745 return unicode(os_id[0].upper() + os_id[1:]) | 714 return unicode(os_id[0].upper() + os_id[1:]) |
| 746 | 715 |
| 747 return unicode(sys.platform) | 716 return unicode(sys.platform) |
| 748 | 717 |
| 749 | 718 |
| 750 @cached | 719 @tools.cached |
| 751 def get_cpu_type(): | 720 def get_cpu_type(): |
| 752 """Returns the type of processor: arm or x86.""" | 721 """Returns the type of processor: arm or x86.""" |
| 753 machine = platform.machine().lower() | 722 machine = platform.machine().lower() |
| 754 if machine in ('amd64', 'x86_64', 'i386'): | 723 if machine in ('amd64', 'x86_64', 'i386'): |
| 755 return u'x86' | 724 return u'x86' |
| 756 return unicode(machine) | 725 return unicode(machine) |
| 757 | 726 |
| 758 | 727 |
| 759 @cached | 728 @tools.cached |
| 760 def get_cpu_bitness(): | 729 def get_cpu_bitness(): |
| 761 """Returns the number of bits in the CPU architecture as a str: 32 or 64. | 730 """Returns the number of bits in the CPU architecture as a str: 32 or 64. |
| 762 | 731 |
| 763 Unless someone ported python to PDP-10 or 286. | 732 Unless someone ported python to PDP-10 or 286. |
| 764 | 733 |
| 765 Note: this function may return 32 bits on 64 bits OS in case of a 32 bits | 734 Note: this function may return 32 bits on 64 bits OS in case of a 32 bits |
| 766 python process. | 735 python process. |
| 767 """ | 736 """ |
| 768 if platform.machine().endswith('64'): | 737 if platform.machine().endswith('64'): |
| 769 return u'64' | 738 return u'64' |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 805 # happens on OSX. | 774 # happens on OSX. |
| 806 hostname = socket.gethostname() | 775 hostname = socket.gethostname() |
| 807 return unicode(hostname) | 776 return unicode(hostname) |
| 808 | 777 |
| 809 | 778 |
| 810 def get_hostname_short(): | 779 def get_hostname_short(): |
| 811 """Returns the base host name.""" | 780 """Returns the base host name.""" |
| 812 return get_hostname().split(u'.', 1)[0] | 781 return get_hostname().split(u'.', 1)[0] |
| 813 | 782 |
| 814 | 783 |
| 815 @cached | 784 @tools.cached |
| 816 def get_num_processors(): | 785 def get_num_processors(): |
| 817 """Returns the number of processors. | 786 """Returns the number of processors. |
| 818 | 787 |
| 819 Python on OSX 10.6 raises a NotImplementedError exception. | 788 Python on OSX 10.6 raises a NotImplementedError exception. |
| 820 """ | 789 """ |
| 821 try: | 790 try: |
| 822 # Multiprocessing | 791 # Multiprocessing |
| 823 return multiprocessing.cpu_count() | 792 return multiprocessing.cpu_count() |
| 824 except: # pylint: disable=W0702 | 793 except: # pylint: disable=W0702 |
| 825 try: | 794 try: |
| 826 # Mac OS 10.6 | 795 # Mac OS 10.6 |
| 827 return int(os.sysconf('SC_NPROCESSORS_ONLN')) # pylint: disable=E1101 | 796 return int(os.sysconf('SC_NPROCESSORS_ONLN')) # pylint: disable=E1101 |
| 828 except: | 797 except: |
| 829 # Returns non-zero, otherwise it could generate a divide by zero later | 798 # Returns non-zero, otherwise it could generate a divide by zero later |
| 830 # when doing calculations, leading to a crash. Saw it happens on Win2K8R2 | 799 # when doing calculations, leading to a crash. Saw it happens on Win2K8R2 |
| 831 # on python 2.7.5 on cygwin 1.7.28. | 800 # on python 2.7.5 on cygwin 1.7.28. |
| 832 logging.error('get_num_processors() failed to query number of cores') | 801 logging.error('get_num_processors() failed to query number of cores') |
| 833 # Return an improbable number to make it easier to catch. | 802 # Return an improbable number to make it easier to catch. |
| 834 return 5 | 803 return 5 |
| 835 | 804 |
| 836 | 805 |
| 837 @cached | 806 @tools.cached |
| 838 def get_physical_ram(): | 807 def get_physical_ram(): |
| 839 """Returns the amount of installed RAM in Mb, rounded to the nearest number. | 808 """Returns the amount of installed RAM in Mb, rounded to the nearest number. |
| 840 """ | 809 """ |
| 841 if sys.platform == 'win32': | 810 if sys.platform == 'win32': |
| 842 # https://msdn.microsoft.com/library/windows/desktop/aa366589.aspx | 811 # https://msdn.microsoft.com/library/windows/desktop/aa366589.aspx |
| 843 class MemoryStatusEx(ctypes.Structure): | 812 class MemoryStatusEx(ctypes.Structure): |
| 844 _fields_ = [ | 813 _fields_ = [ |
| 845 ('dwLength', ctypes.c_ulong), | 814 ('dwLength', ctypes.c_ulong), |
| 846 ('dwMemoryLoad', ctypes.c_ulong), | 815 ('dwMemoryLoad', ctypes.c_ulong), |
| 847 ('dwTotalPhys', ctypes.c_ulonglong), | 816 ('dwTotalPhys', ctypes.c_ulonglong), |
| (...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 886 return 0 | 855 return 0 |
| 887 | 856 |
| 888 | 857 |
| 889 def get_disks_info(): | 858 def get_disks_info(): |
| 890 """Returns a dict of dict of free and total disk space.""" | 859 """Returns a dict of dict of free and total disk space.""" |
| 891 if sys.platform == 'win32': | 860 if sys.platform == 'win32': |
| 892 return _get_disks_info_win() | 861 return _get_disks_info_win() |
| 893 return _get_disks_info_posix() | 862 return _get_disks_info_posix() |
| 894 | 863 |
| 895 | 864 |
| 896 @cached | 865 @tools.cached |
| 897 def get_gpu(): | 866 def get_gpu(): |
| 898 """Returns the installed video card(s) name. | 867 """Returns the installed video card(s) name. |
| 899 | 868 |
| 900 Returns: | 869 Returns: |
| 901 All the video cards detected. | 870 All the video cards detected. |
| 902 tuple(list(dimensions), list(state)). | 871 tuple(list(dimensions), list(state)). |
| 903 """ | 872 """ |
| 904 if sys.platform == 'darwin': | 873 if sys.platform == 'darwin': |
| 905 dimensions, state = _get_gpu_osx() | 874 dimensions, state = _get_gpu_osx() |
| 906 elif sys.platform == 'linux2': | 875 elif sys.platform == 'linux2': |
| 907 dimensions, state = _get_gpu_linux() | 876 dimensions, state = _get_gpu_linux() |
| 908 elif sys.platform == 'win32': | 877 elif sys.platform == 'win32': |
| 909 dimensions, state = _get_gpu_win() | 878 dimensions, state = _get_gpu_win() |
| 910 else: | 879 else: |
| 911 dimensions, state = None, None | 880 dimensions, state = None, None |
| 912 | 881 |
| 913 # 15ad is VMWare. It's akin not having a GPU card. | 882 # 15ad is VMWare. It's akin not having a GPU card. |
| 914 dimensions = dimensions or [u'none'] | 883 dimensions = dimensions or [u'none'] |
| 915 if '15ad' in dimensions: | 884 if '15ad' in dimensions: |
| 916 dimensions.append(u'none') | 885 dimensions.append(u'none') |
| 917 dimensions.sort() | 886 dimensions.sort() |
| 918 return dimensions, state | 887 return dimensions, state |
| 919 | 888 |
| 920 | 889 |
| 921 @cached | 890 @tools.cached |
| 922 def get_monitor_hidpi(): | 891 def get_monitor_hidpi(): |
| 923 """Returns True if there is an hidpi monitor detected.""" | 892 """Returns True if there is an hidpi monitor detected.""" |
| 924 if sys.platform == 'darwin': | 893 if sys.platform == 'darwin': |
| 925 return [_get_monitor_hidpi_osx()] | 894 return [_get_monitor_hidpi_osx()] |
| 926 return None | 895 return None |
| 927 | 896 |
| 928 | 897 |
| 929 @cached | 898 @tools.cached |
| 930 def get_cost_hour(): | 899 def get_cost_hour(): |
| 931 """Returns the cost in $USD/h as a floating point value if applicable.""" | 900 """Returns the cost in $USD/h as a floating point value if applicable.""" |
| 932 if _get_metadata_gce(): | 901 if _get_metadata_gce(): |
| 933 return _get_cost_hour_gce() | 902 return _get_cost_hour_gce() |
| 934 | 903 |
| 935 # Get an approximate cost trying to emulate GCE equivalent cost. | 904 # Get an approximate cost trying to emulate GCE equivalent cost. |
| 936 cores = get_num_processors() | 905 cores = get_num_processors() |
| 937 os_cost = 0. | 906 os_cost = 0. |
| 938 if sys.platform == 'darwin': | 907 if sys.platform == 'darwin': |
| 939 # Apple tax. It's 50% better, right? | 908 # Apple tax. It's 50% better, right? |
| 940 os_cost = GCE_WINDOWS_COST_CORE_HOUR * 1.5 * cores | 909 os_cost = GCE_WINDOWS_COST_CORE_HOUR * 1.5 * cores |
| 941 elif sys.platform == 'win32': | 910 elif sys.platform == 'win32': |
| 942 # MS tax. | 911 # MS tax. |
| 943 os_cost = GCE_WINDOWS_COST_CORE_HOUR * cores | 912 os_cost = GCE_WINDOWS_COST_CORE_HOUR * cores |
| 944 | 913 |
| 945 # Guess an equivalent machine_type. | 914 # Guess an equivalent machine_type. |
| 946 machine_cost = GCE_MACHINE_COST_HOUR_US.get(get_machine_type(), 0.) | 915 machine_cost = GCE_MACHINE_COST_HOUR_US.get(get_machine_type(), 0.) |
| 947 | 916 |
| 948 # Assume HDD for now, it's the cheapest. That's not true, we do have SSDs. | 917 # Assume HDD for now, it's the cheapest. That's not true, we do have SSDs. |
| 949 disk_gb_cost = 0. | 918 disk_gb_cost = 0. |
| 950 for disk in get_disks_info().itervalues(): | 919 for disk in get_disks_info().itervalues(): |
| 951 disk_gb_cost += disk['free_mb'] / 1024. * ( | 920 disk_gb_cost += disk['free_mb'] / 1024. * ( |
| 952 GCE_HDD_GB_COST_MONTH / 30. / 24.) | 921 GCE_HDD_GB_COST_MONTH / 30. / 24.) |
| 953 return machine_cost + os_cost + disk_gb_cost | 922 return machine_cost + os_cost + disk_gb_cost |
| 954 | 923 |
| 955 | 924 |
| 956 @cached | 925 @tools.cached |
| 957 def get_machine_type(): | 926 def get_machine_type(): |
| 958 """Returns a GCE-equivalent machine type. | 927 """Returns a GCE-equivalent machine type. |
| 959 | 928 |
| 960 If running on GCE, returns the right machine type. Otherwise tries to find the | 929 If running on GCE, returns the right machine type. Otherwise tries to find the |
| 961 'closest' one. | 930 'closest' one. |
| 962 """ | 931 """ |
| 963 if _get_metadata_gce(): | 932 if _get_metadata_gce(): |
| 964 return get_machine_type_gce() | 933 return get_machine_type_gce() |
| 965 | 934 |
| 966 ram_gb = get_physical_ram() / 1024. | 935 ram_gb = get_physical_ram() / 1024. |
| (...skipping 24 matching lines...) Expand all Loading... |
| 991 else: | 960 else: |
| 992 machine_type = u'n1-standard-1' | 961 machine_type = u'n1-standard-1' |
| 993 else: | 962 else: |
| 994 logging.info('Failed to find a fit: %s', machine_type) | 963 logging.info('Failed to find a fit: %s', machine_type) |
| 995 | 964 |
| 996 if machine_type not in GCE_MACHINE_COST_HOUR_US: | 965 if machine_type not in GCE_MACHINE_COST_HOUR_US: |
| 997 return None | 966 return None |
| 998 return machine_type | 967 return machine_type |
| 999 | 968 |
| 1000 | 969 |
| 1001 @cached | 970 @tools.cached |
| 1002 def can_send_metric(): | 971 def can_send_metric(): |
| 1003 """True if 'send_metric' really does something.""" | 972 """True if 'send_metric' really does something.""" |
| 1004 if _get_metadata_gce(): | 973 if _get_metadata_gce(): |
| 1005 return _MONITORING_SCOPE in _oauth2_available_scopes_gce() | 974 # Scope to use Cloud Monitoring. |
| 975 scope = 'https://www.googleapis.com/auth/monitoring' |
| 976 return scope in _oauth2_available_scopes_gce() |
| 1006 return False | 977 return False |
| 1007 | 978 |
| 1008 | 979 |
| 1009 def send_metric(name, value): | 980 def send_metric(name, value): |
| 1010 if _get_metadata_gce(): | 981 if _get_metadata_gce(): |
| 1011 return send_metric_gce(name, value) | 982 return send_metric_gce(name, value) |
| 1012 # Ignore on other platforms for now. | 983 # Ignore on other platforms for now. |
| 1013 | 984 |
| 1014 | 985 |
| 1015 ### Google Cloud Compute Engine. | 986 ### Google Cloud Compute Engine. |
| 1016 | 987 |
| 1017 | 988 |
| 1018 @cached | 989 @tools.cached |
| 1019 def get_zone_gce(): | 990 def get_zone_gce(): |
| 1020 """Returns the zone containing the GCE VM.""" | 991 """Returns the zone containing the GCE VM.""" |
| 1021 metadata = _get_metadata_gce() | 992 metadata = _get_metadata_gce() |
| 1022 if not metadata: | 993 if not metadata: |
| 1023 return None | 994 return None |
| 1024 # Format is projects/<id>/zones/<zone> | 995 # Format is projects/<id>/zones/<zone> |
| 1025 return unicode(metadata['instance']['zone'].rsplit('/', 1)[-1]) | 996 return unicode(metadata['instance']['zone'].rsplit('/', 1)[-1]) |
| 1026 | 997 |
| 1027 | 998 |
| 1028 @cached | 999 @tools.cached |
| 1029 def get_machine_type_gce(): | 1000 def get_machine_type_gce(): |
| 1030 """Returns the GCE machine type.""" | 1001 """Returns the GCE machine type.""" |
| 1031 metadata = _get_metadata_gce() | 1002 metadata = _get_metadata_gce() |
| 1032 if not metadata: | 1003 if not metadata: |
| 1033 return None | 1004 return None |
| 1034 # Format is projects/<id>/machineTypes/<machine_type> | 1005 # Format is projects/<id>/machineTypes/<machine_type> |
| 1035 return unicode(metadata['instance']['machineType'].rsplit('/', 1)[-1]) | 1006 return unicode(metadata['instance']['machineType'].rsplit('/', 1)[-1]) |
| 1036 | 1007 |
| 1037 | 1008 |
| 1038 @cached | 1009 @tools.cached |
| 1039 def get_tags_gce(): | 1010 def get_tags_gce(): |
| 1040 """Returns a list of instance tags or empty list if not GCE VM.""" | 1011 """Returns a list of instance tags or empty list if not GCE VM.""" |
| 1041 metadata = _get_metadata_gce() | 1012 metadata = _get_metadata_gce() |
| 1042 if not metadata: | 1013 if not metadata: |
| 1043 return [] | 1014 return [] |
| 1044 return metadata['instance']['tags'] | 1015 return metadata['instance']['tags'] |
| 1045 | 1016 |
| 1046 | 1017 |
| 1047 def send_metric_gce(name, value): | 1018 def send_metric_gce(name, value): |
| 1048 """Sets a lightweight custom metric. | 1019 """Sets a lightweight custom metric. |
| (...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1104 logging.debug(json.load(resp)) | 1075 logging.debug(json.load(resp)) |
| 1105 except urllib2.HTTPError as e: | 1076 except urllib2.HTTPError as e: |
| 1106 logging.error('send_metric failed: %s: %s' % (e, e.read())) | 1077 logging.error('send_metric failed: %s: %s' % (e, e.read())) |
| 1107 except IOError as e: | 1078 except IOError as e: |
| 1108 logging.error('send_metric failed: %s' % e) | 1079 logging.error('send_metric failed: %s' % e) |
| 1109 | 1080 |
| 1110 | 1081 |
| 1111 ### Windows. | 1082 ### Windows. |
| 1112 | 1083 |
| 1113 | 1084 |
| 1114 @cached | 1085 @tools.cached |
| 1115 def get_integrity_level_win(): | 1086 def get_integrity_level_win(): |
| 1116 """Returns the integrity level of the current process as a string. | 1087 """Returns the integrity level of the current process as a string. |
| 1117 | 1088 |
| 1118 TODO(maruel): It'd be nice to make it work on cygwin. The problem is that | 1089 TODO(maruel): It'd be nice to make it work on cygwin. The problem is that |
| 1119 ctypes.windll is unaccessible and it is not known to the author how to use | 1090 ctypes.windll is unaccessible and it is not known to the author how to use |
| 1120 stdcall convention through ctypes.cdll. | 1091 stdcall convention through ctypes.cdll. |
| 1121 """ | 1092 """ |
| 1122 if sys.platform != 'win32': | 1093 if sys.platform != 'win32': |
| 1123 return None | 1094 return None |
| 1124 if get_os_version_number() == u'5.1': | 1095 if get_os_version_number() == u'5.1': |
| (...skipping 103 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1228 token_info.Label.Sid, p_sid_size.contents.value - 1) | 1199 token_info.Label.Sid, p_sid_size.contents.value - 1) |
| 1229 value = res.contents.value | 1200 value = res.contents.value |
| 1230 return mapping.get(value) or u'0x%04x' % value | 1201 return mapping.get(value) or u'0x%04x' % value |
| 1231 finally: | 1202 finally: |
| 1232 ctypes.windll.kernel32.CloseHandle(token) | 1203 ctypes.windll.kernel32.CloseHandle(token) |
| 1233 | 1204 |
| 1234 | 1205 |
| 1235 ### Android. | 1206 ### Android. |
| 1236 | 1207 |
| 1237 | 1208 |
| 1238 def get_adb_list_devices(adb_path='adb'): | |
| 1239 """Returns the list of devices available. This includes emulators.""" | |
| 1240 output = subprocess.check_output([adb_path, 'devices']) | |
| 1241 devices = [] | |
| 1242 for line in output.splitlines(): | |
| 1243 if line.startswith(('*', 'List of')) or not line: | |
| 1244 continue | |
| 1245 # TODO(maruel): Handle 'offline', 'device', 'no device' and | |
| 1246 # 'unauthorized'. | |
| 1247 devices.append(line.split()[0]) | |
| 1248 return devices | |
| 1249 | |
| 1250 | |
| 1251 def get_adb_device_properties_raw(device_id, adb_path='adb'): | |
| 1252 """Returns the system properties for a device.""" | |
| 1253 output = subprocess.check_output( | |
| 1254 [adb_path, '-s', device_id, 'shell', 'cat', '/system/build.prop']) | |
| 1255 properties = {} | |
| 1256 for line in output.splitlines(): | |
| 1257 if line.startswith('#') or not line: | |
| 1258 continue | |
| 1259 key, value = line.split('=', 1) | |
| 1260 properties[key] = value | |
| 1261 return properties | |
| 1262 | |
| 1263 | |
| 1264 def get_dimensions_android(device_id, adb_path='adb'): | |
| 1265 """Returns the default dimensions for an android device. | |
| 1266 | |
| 1267 In this case, details are about the device, not about the host. | |
| 1268 """ | |
| 1269 properties = get_adb_device_properties_raw(device_id, adb_path) | |
| 1270 out = dict( | |
| 1271 (k, [v]) for k, v in properties.iteritems() if k in ANDROID_DETAILS) | |
| 1272 out[u'id'] = [device_id] | |
| 1273 return out | |
| 1274 | |
| 1275 | |
| 1276 def get_state_android(device_id, adb_path='adb'): | |
| 1277 """Returns state information about the device. | |
| 1278 | |
| 1279 It's a big speculating TODO. Would be temperature, device uptime, partition | |
| 1280 space, etc. | |
| 1281 """ | |
| 1282 # Unused argument - pylint: disable=W0613 | |
| 1283 return { | |
| 1284 u'device': { | |
| 1285 # TODO(maruel): Fill me. | |
| 1286 }, | |
| 1287 u'host': get_state(), | |
| 1288 } | |
| 1289 | 1209 |
| 1290 | 1210 |
| 1291 ### | 1211 ### |
| 1292 | 1212 |
| 1293 | 1213 |
| 1294 def get_dimensions(): | 1214 def get_dimensions(): |
| 1295 """Returns the default dimensions.""" | 1215 """Returns the default dimensions.""" |
| 1296 os_name = get_os_name() | 1216 os_name = get_os_name() |
| 1297 cpu_type = get_cpu_type() | 1217 cpu_type = get_cpu_type() |
| 1298 cpu_bitness = get_cpu_bitness() | 1218 cpu_bitness = get_cpu_bitness() |
| (...skipping 321 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1620 u'dimensions': get_dimensions(), | 1540 u'dimensions': get_dimensions(), |
| 1621 u'state': get_state(), | 1541 u'state': get_state(), |
| 1622 } | 1542 } |
| 1623 json.dump(data, sys.stdout, indent=2, sort_keys=True, separators=(',', ': ')) | 1543 json.dump(data, sys.stdout, indent=2, sort_keys=True, separators=(',', ': ')) |
| 1624 print('') | 1544 print('') |
| 1625 return 0 | 1545 return 0 |
| 1626 | 1546 |
| 1627 | 1547 |
| 1628 if __name__ == '__main__': | 1548 if __name__ == '__main__': |
| 1629 sys.exit(main()) | 1549 sys.exit(main()) |
| OLD | NEW |