15f9996aaSopenharmony_ci#!/usr/bin/env python 25f9996aaSopenharmony_ci# -*- coding: utf-8 -*- 35f9996aaSopenharmony_ci# Copyright (c) 2021 Huawei Device Co., Ltd. 45f9996aaSopenharmony_ci# Licensed under the Apache License, Version 2.0 (the "License"); 55f9996aaSopenharmony_ci# you may not use this file except in compliance with the License. 65f9996aaSopenharmony_ci# You may obtain a copy of the License at 75f9996aaSopenharmony_ci# 85f9996aaSopenharmony_ci# http://www.apache.org/licenses/LICENSE-2.0 95f9996aaSopenharmony_ci# 105f9996aaSopenharmony_ci# Unless required by applicable law or agreed to in writing, software 115f9996aaSopenharmony_ci# distributed under the License is distributed on an "AS IS" BASIS, 125f9996aaSopenharmony_ci# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 135f9996aaSopenharmony_ci# See the License for the specific language governing permissions and 145f9996aaSopenharmony_ci# limitations under the License. 155f9996aaSopenharmony_ci 165f9996aaSopenharmony_ciimport os 175f9996aaSopenharmony_ciimport sys 185f9996aaSopenharmony_ciimport argparse 195f9996aaSopenharmony_ciimport errno 205f9996aaSopenharmony_ciimport json 215f9996aaSopenharmony_ciimport datetime 225f9996aaSopenharmony_ciimport http.client as client 235f9996aaSopenharmony_ci 245f9996aaSopenharmony_cifrom http.server import BaseHTTPRequestHandler 255f9996aaSopenharmony_cifrom http.server import HTTPServer 265f9996aaSopenharmony_ci 275f9996aaSopenharmony_ciPYCACHE_PORT = 7970 # Ascii code for 'yp' 285f9996aaSopenharmony_ciLOCALHOST = '127.0.0.1' 295f9996aaSopenharmony_ciDEBUG = int(os.environ.get('PRINT_BUILD_EXPLANATIONS', 0)) 305f9996aaSopenharmony_ci 315f9996aaSopenharmony_ci 325f9996aaSopenharmony_ciclass PycacheDaemonRequestHandler(BaseHTTPRequestHandler): 335f9996aaSopenharmony_ci # Suppress logs 345f9996aaSopenharmony_ci def log_message(self, format, *args): # pylint: disable=redefined-builtin 355f9996aaSopenharmony_ci if DEBUG: 365f9996aaSopenharmony_ci super().log_message(format, *args) 375f9996aaSopenharmony_ci else: 385f9996aaSopenharmony_ci pass 395f9996aaSopenharmony_ci 405f9996aaSopenharmony_ci def do_cache_hit(self): 415f9996aaSopenharmony_ci self.server.hit_times += 1 425f9996aaSopenharmony_ci self.send_response(200) 435f9996aaSopenharmony_ci 445f9996aaSopenharmony_ci def do_cache_miss(self): 455f9996aaSopenharmony_ci self.server.miss_times += 1 465f9996aaSopenharmony_ci self.send_response(200) 475f9996aaSopenharmony_ci 485f9996aaSopenharmony_ci def do_cache_manage(self): 495f9996aaSopenharmony_ci self.send_response(200) 505f9996aaSopenharmony_ci self.server.cache_manage() 515f9996aaSopenharmony_ci 525f9996aaSopenharmony_ci def do_show_statistics(self): 535f9996aaSopenharmony_ci self.send_response(200) 545f9996aaSopenharmony_ci self.server.show_statistics() 555f9996aaSopenharmony_ci 565f9996aaSopenharmony_ci def do_stop_service(self): 575f9996aaSopenharmony_ci self.send_response(200) 585f9996aaSopenharmony_ci self.server.stop_service = True 595f9996aaSopenharmony_ci 605f9996aaSopenharmony_ci 615f9996aaSopenharmony_ciclass PycacheDaemon(HTTPServer): 625f9996aaSopenharmony_ci def __init__(self, *args, **kargs): 635f9996aaSopenharmony_ci self.hit_times = 0 645f9996aaSopenharmony_ci self.miss_times = 0 655f9996aaSopenharmony_ci self.stop_service = False 665f9996aaSopenharmony_ci self.pycache_dir = None 675f9996aaSopenharmony_ci self.pycache_config_file = None 685f9996aaSopenharmony_ci super().__init__(*args, **kargs) 695f9996aaSopenharmony_ci 705f9996aaSopenharmony_ci def serve_forever(self, poll_interval=0.5): 715f9996aaSopenharmony_ci while not self.stop_service: 725f9996aaSopenharmony_ci self.handle_request() 735f9996aaSopenharmony_ci os.unlink(self.pycache_config_file) 745f9996aaSopenharmony_ci 755f9996aaSopenharmony_ci def record_pycache_config(self, pycache_dir): 765f9996aaSopenharmony_ci root = os.path.realpath(pycache_dir) 775f9996aaSopenharmony_ci self.pycache_dir = root 785f9996aaSopenharmony_ci self.pycache_config_file = os.path.join(root, '.config') 795f9996aaSopenharmony_ci os.makedirs(root, exist_ok=True) 805f9996aaSopenharmony_ci host, port = self.server_address[:2] 815f9996aaSopenharmony_ci config = { 825f9996aaSopenharmony_ci 'root': root, 835f9996aaSopenharmony_ci 'config_file': self.pycache_config_file, 845f9996aaSopenharmony_ci 'debug': bool(DEBUG), 855f9996aaSopenharmony_ci 'host': host, 865f9996aaSopenharmony_ci 'port': port, 875f9996aaSopenharmony_ci } 885f9996aaSopenharmony_ci with open(self.pycache_config_file, 'w') as jsonfile: 895f9996aaSopenharmony_ci json.dump(config, jsonfile, indent=2, sort_keys=True) 905f9996aaSopenharmony_ci 915f9996aaSopenharmony_ci def cache_manage(self): 925f9996aaSopenharmony_ci now = datetime.datetime.now() 935f9996aaSopenharmony_ci days = 15 945f9996aaSopenharmony_ci earlier_time = (now - datetime.timedelta(days)).timestamp() 955f9996aaSopenharmony_ci # Pycache pool holds as much as 40GB or as long as 15 days 965f9996aaSopenharmony_ci # cache objects 975f9996aaSopenharmony_ci while days > 0: 985f9996aaSopenharmony_ci disk_usage = 0 995f9996aaSopenharmony_ci for root, _, files in os.walk(self.pycache_dir): 1005f9996aaSopenharmony_ci for file in files: 1015f9996aaSopenharmony_ci path = os.path.join(root, file) 1025f9996aaSopenharmony_ci stat = os.stat(path) 1035f9996aaSopenharmony_ci if stat.st_atime < int(earlier_time): 1045f9996aaSopenharmony_ci os.unlink(path) 1055f9996aaSopenharmony_ci else: 1065f9996aaSopenharmony_ci disk_usage += stat.st_size 1075f9996aaSopenharmony_ci if disk_usage >= 40 * 1024 * 1024 * 1024: 1085f9996aaSopenharmony_ci days -= 1 1095f9996aaSopenharmony_ci earlier_time = (now - datetime.timedelta(days)).timestamp() 1105f9996aaSopenharmony_ci else: 1115f9996aaSopenharmony_ci break 1125f9996aaSopenharmony_ci 1135f9996aaSopenharmony_ci def show_statistics(self): 1145f9996aaSopenharmony_ci actions = self.hit_times + self.miss_times 1155f9996aaSopenharmony_ci if actions != 0: 1165f9996aaSopenharmony_ci print('-' * 80) 1175f9996aaSopenharmony_ci print('pycache statistics:') 1185f9996aaSopenharmony_ci print('pycache hit targets: {}'.format(self.hit_times)) 1195f9996aaSopenharmony_ci print('pycache miss targets: {}'.format(self.miss_times)) 1205f9996aaSopenharmony_ci hit_rate = float(self.hit_times) / actions * 100 1215f9996aaSopenharmony_ci miss_rate = float(self.miss_times) / actions * 100 1225f9996aaSopenharmony_ci print('pycache hit rate: {:.2f}%'.format(hit_rate)) 1235f9996aaSopenharmony_ci print('pycache miss rate: {:.2f}%'.format(miss_rate)) 1245f9996aaSopenharmony_ci print('-' * 80) 1255f9996aaSopenharmony_ci else: 1265f9996aaSopenharmony_ci print('-' * 80) 1275f9996aaSopenharmony_ci print('No pycache actions in pycache, skip statistics') 1285f9996aaSopenharmony_ci print('-' * 80) 1295f9996aaSopenharmony_ci 1305f9996aaSopenharmony_ci 1315f9996aaSopenharmony_cidef start_server(host, port, root): 1325f9996aaSopenharmony_ci if root is None: 1335f9996aaSopenharmony_ci print('Warning: missing pycache root directory') 1345f9996aaSopenharmony_ci return 1355f9996aaSopenharmony_ci server_address = (host, port) 1365f9996aaSopenharmony_ci try: 1375f9996aaSopenharmony_ci pyd = PycacheDaemon(server_address, PycacheDaemonRequestHandler) 1385f9996aaSopenharmony_ci print('Starting pycache daemon at {}:{}'.format(host, port)) 1395f9996aaSopenharmony_ci pyd.record_pycache_config(root) 1405f9996aaSopenharmony_ci pyd.serve_forever() 1415f9996aaSopenharmony_ci except OSError as err: 1425f9996aaSopenharmony_ci if err.errno == errno.EADDRINUSE: 1435f9996aaSopenharmony_ci start_server(host, port + 2, root) 1445f9996aaSopenharmony_ci else: 1455f9996aaSopenharmony_ci print('Warning: Failed to start pycache daemon process') 1465f9996aaSopenharmony_ci 1475f9996aaSopenharmony_ci 1485f9996aaSopenharmony_cidef get_pyd(): 1495f9996aaSopenharmony_ci cache_dir = os.environ.get('PYCACHE_DIR') 1505f9996aaSopenharmony_ci daemon_config_file = '{}/.config'.format(cache_dir) 1515f9996aaSopenharmony_ci if not os.path.exists(daemon_config_file): 1525f9996aaSopenharmony_ci raise Exception('Warning: {} not exists'.format(daemon_config_file)) 1535f9996aaSopenharmony_ci with open(daemon_config_file, 'r') as jsonfile: 1545f9996aaSopenharmony_ci data = json.load(jsonfile) 1555f9996aaSopenharmony_ci return data.get('host'), data.get('port') 1565f9996aaSopenharmony_ci 1575f9996aaSopenharmony_ci 1585f9996aaSopenharmony_cidef stop_server(): 1595f9996aaSopenharmony_ci try: 1605f9996aaSopenharmony_ci host, port = get_pyd() 1615f9996aaSopenharmony_ci conn = client.HTTPConnection(host, port) 1625f9996aaSopenharmony_ci conn.request('stop_service', '/') 1635f9996aaSopenharmony_ci conn.close() 1645f9996aaSopenharmony_ci except: # noqa: E722 pylint: disable=bare-except 1655f9996aaSopenharmony_ci pass 1665f9996aaSopenharmony_ci 1675f9996aaSopenharmony_ci 1685f9996aaSopenharmony_cidef show_statistics(): 1695f9996aaSopenharmony_ci try: 1705f9996aaSopenharmony_ci host, port = get_pyd() 1715f9996aaSopenharmony_ci conn = client.HTTPConnection(host, port) 1725f9996aaSopenharmony_ci conn.request('show_statistics', '/') 1735f9996aaSopenharmony_ci conn.close() 1745f9996aaSopenharmony_ci except: # noqa: E722 pylint: disable=bare-except 1755f9996aaSopenharmony_ci pass 1765f9996aaSopenharmony_ci 1775f9996aaSopenharmony_ci 1785f9996aaSopenharmony_cidef manage_cache_contents(): 1795f9996aaSopenharmony_ci try: 1805f9996aaSopenharmony_ci host, port = get_pyd() 1815f9996aaSopenharmony_ci conn = client.HTTPConnection(host, port) 1825f9996aaSopenharmony_ci conn.request('cache_manage', '/') 1835f9996aaSopenharmony_ci conn.close() 1845f9996aaSopenharmony_ci except: # noqa: E722 pylint: disable=bare-except 1855f9996aaSopenharmony_ci pass 1865f9996aaSopenharmony_ci 1875f9996aaSopenharmony_ci 1885f9996aaSopenharmony_cidef main(args): 1895f9996aaSopenharmony_ci parser = argparse.ArgumentParser() 1905f9996aaSopenharmony_ci parser.add_argument('--root', help='path to pycache root directory') 1915f9996aaSopenharmony_ci parser.add_argument('--port', 1925f9996aaSopenharmony_ci default=PYCACHE_PORT, 1935f9996aaSopenharmony_ci help='which port to listen') 1945f9996aaSopenharmony_ci parser.add_argument('--start', 1955f9996aaSopenharmony_ci action='store_true', 1965f9996aaSopenharmony_ci help='start daemon process for pycache') 1975f9996aaSopenharmony_ci parser.add_argument('--stop', 1985f9996aaSopenharmony_ci action='store_true', 1995f9996aaSopenharmony_ci help='stop pycache daemon process') 2005f9996aaSopenharmony_ci parser.add_argument('--stat', 2015f9996aaSopenharmony_ci action='store_true', 2025f9996aaSopenharmony_ci help='report cache statistics') 2035f9996aaSopenharmony_ci parser.add_argument('--manage', 2045f9996aaSopenharmony_ci action='store_true', 2055f9996aaSopenharmony_ci help='manage pycache contents') 2065f9996aaSopenharmony_ci 2075f9996aaSopenharmony_ci options = parser.parse_args(args) 2085f9996aaSopenharmony_ci if options.start: 2095f9996aaSopenharmony_ci start_server(LOCALHOST, int(options.port), options.root) 2105f9996aaSopenharmony_ci if options.stop: 2115f9996aaSopenharmony_ci stop_server() 2125f9996aaSopenharmony_ci if options.stat: 2135f9996aaSopenharmony_ci show_statistics() 2145f9996aaSopenharmony_ci if options.manage: 2155f9996aaSopenharmony_ci manage_cache_contents() 2165f9996aaSopenharmony_ci 2175f9996aaSopenharmony_ci 2185f9996aaSopenharmony_ciif __name__ == '__main__': 2195f9996aaSopenharmony_ci main(sys.argv[1:]) 220