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