diff options
| -rwxr-xr-x | bitbake/bin/bitbake-hashclient | 16 | ||||
| -rw-r--r-- | bitbake/lib/hashserv/client.py | 5 | ||||
| -rw-r--r-- | bitbake/lib/hashserv/server.py | 5 | ||||
| -rw-r--r-- | bitbake/lib/hashserv/sqlalchemy.py | 14 | ||||
| -rw-r--r-- | bitbake/lib/hashserv/sqlite.py | 37 | ||||
| -rw-r--r-- | bitbake/lib/hashserv/tests.py | 9 |
6 files changed, 86 insertions, 0 deletions
diff --git a/bitbake/bin/bitbake-hashclient b/bitbake/bin/bitbake-hashclient index cfbc197e52..5d65c7bc56 100755 --- a/bitbake/bin/bitbake-hashclient +++ b/bitbake/bin/bitbake-hashclient | |||
| @@ -161,6 +161,19 @@ def main(): | |||
| 161 | r = client.delete_user(args.username) | 161 | r = client.delete_user(args.username) |
| 162 | print_user(r) | 162 | print_user(r) |
| 163 | 163 | ||
| 164 | def handle_get_db_usage(args, client): | ||
| 165 | usage = client.get_db_usage() | ||
| 166 | print(usage) | ||
| 167 | tables = sorted(usage.keys()) | ||
| 168 | print("{name:20}| {rows:20}".format(name="Table name", rows="Rows")) | ||
| 169 | print(("-" * 20) + "+" + ("-" * 20)) | ||
| 170 | for t in tables: | ||
| 171 | print("{name:20}| {rows:<20}".format(name=t, rows=usage[t]["rows"])) | ||
| 172 | print() | ||
| 173 | |||
| 174 | total_rows = sum(t["rows"] for t in usage.values()) | ||
| 175 | print(f"Total rows: {total_rows}") | ||
| 176 | |||
| 164 | parser = argparse.ArgumentParser(description='Hash Equivalence Client') | 177 | parser = argparse.ArgumentParser(description='Hash Equivalence Client') |
| 165 | parser.add_argument('--address', default=DEFAULT_ADDRESS, help='Server address (default "%(default)s")') | 178 | parser.add_argument('--address', default=DEFAULT_ADDRESS, help='Server address (default "%(default)s")') |
| 166 | parser.add_argument('--log', default='WARNING', help='Set logging level') | 179 | parser.add_argument('--log', default='WARNING', help='Set logging level') |
| @@ -223,6 +236,9 @@ def main(): | |||
| 223 | delete_user_parser.add_argument("--username", "-u", help="Username", required=True) | 236 | delete_user_parser.add_argument("--username", "-u", help="Username", required=True) |
| 224 | delete_user_parser.set_defaults(func=handle_delete_user) | 237 | delete_user_parser.set_defaults(func=handle_delete_user) |
| 225 | 238 | ||
| 239 | db_usage_parser = subparsers.add_parser('get-db-usage', help="Database Usage") | ||
| 240 | db_usage_parser.set_defaults(func=handle_get_db_usage) | ||
| 241 | |||
| 226 | args = parser.parse_args() | 242 | args = parser.parse_args() |
| 227 | 243 | ||
| 228 | logger = logging.getLogger('hashserv') | 244 | logger = logging.getLogger('hashserv') |
diff --git a/bitbake/lib/hashserv/client.py b/bitbake/lib/hashserv/client.py index 4457f8e50d..5e0a462b1c 100644 --- a/bitbake/lib/hashserv/client.py +++ b/bitbake/lib/hashserv/client.py | |||
| @@ -186,6 +186,10 @@ class AsyncClient(bb.asyncrpc.AsyncClient): | |||
| 186 | self.saved_become_user = username | 186 | self.saved_become_user = username |
| 187 | return result | 187 | return result |
| 188 | 188 | ||
| 189 | async def get_db_usage(self): | ||
| 190 | await self._set_mode(self.MODE_NORMAL) | ||
| 191 | return (await self.invoke({"get-db-usage": {}}))["usage"] | ||
| 192 | |||
| 189 | 193 | ||
| 190 | class Client(bb.asyncrpc.Client): | 194 | class Client(bb.asyncrpc.Client): |
| 191 | def __init__(self, username=None, password=None): | 195 | def __init__(self, username=None, password=None): |
| @@ -214,6 +218,7 @@ class Client(bb.asyncrpc.Client): | |||
| 214 | "new_user", | 218 | "new_user", |
| 215 | "delete_user", | 219 | "delete_user", |
| 216 | "become_user", | 220 | "become_user", |
| 221 | "get_db_usage", | ||
| 217 | ) | 222 | ) |
| 218 | 223 | ||
| 219 | def _get_async_client(self): | 224 | def _get_async_client(self): |
diff --git a/bitbake/lib/hashserv/server.py b/bitbake/lib/hashserv/server.py index ca419a1abf..c5b9797e4e 100644 --- a/bitbake/lib/hashserv/server.py +++ b/bitbake/lib/hashserv/server.py | |||
| @@ -249,6 +249,7 @@ class ServerClient(bb.asyncrpc.AsyncServerConnection): | |||
| 249 | "get-outhash": self.handle_get_outhash, | 249 | "get-outhash": self.handle_get_outhash, |
| 250 | "get-stream": self.handle_get_stream, | 250 | "get-stream": self.handle_get_stream, |
| 251 | "get-stats": self.handle_get_stats, | 251 | "get-stats": self.handle_get_stats, |
| 252 | "get-db-usage": self.handle_get_db_usage, | ||
| 252 | # Not always read-only, but internally checks if the server is | 253 | # Not always read-only, but internally checks if the server is |
| 253 | # read-only | 254 | # read-only |
| 254 | "report": self.handle_report, | 255 | "report": self.handle_report, |
| @@ -567,6 +568,10 @@ class ServerClient(bb.asyncrpc.AsyncServerConnection): | |||
| 567 | oldest = datetime.now() - timedelta(seconds=-max_age) | 568 | oldest = datetime.now() - timedelta(seconds=-max_age) |
| 568 | return {"count": await self.db.clean_unused(oldest)} | 569 | return {"count": await self.db.clean_unused(oldest)} |
| 569 | 570 | ||
| 571 | @permissions(DB_ADMIN_PERM) | ||
| 572 | async def handle_get_db_usage(self, request): | ||
| 573 | return {"usage": await self.db.get_usage()} | ||
| 574 | |||
| 570 | # The authentication API is always allowed | 575 | # The authentication API is always allowed |
| 571 | async def handle_auth(self, request): | 576 | async def handle_auth(self, request): |
| 572 | username = str(request["username"]) | 577 | username = str(request["username"]) |
diff --git a/bitbake/lib/hashserv/sqlalchemy.py b/bitbake/lib/hashserv/sqlalchemy.py index bfd8a8446e..818b51951b 100644 --- a/bitbake/lib/hashserv/sqlalchemy.py +++ b/bitbake/lib/hashserv/sqlalchemy.py | |||
| @@ -27,6 +27,7 @@ from sqlalchemy import ( | |||
| 27 | and_, | 27 | and_, |
| 28 | delete, | 28 | delete, |
| 29 | update, | 29 | update, |
| 30 | func, | ||
| 30 | ) | 31 | ) |
| 31 | import sqlalchemy.engine | 32 | import sqlalchemy.engine |
| 32 | from sqlalchemy.orm import declarative_base | 33 | from sqlalchemy.orm import declarative_base |
| @@ -401,3 +402,16 @@ class Database(object): | |||
| 401 | async with self.db.begin(): | 402 | async with self.db.begin(): |
| 402 | result = await self.db.execute(statement) | 403 | result = await self.db.execute(statement) |
| 403 | return result.rowcount != 0 | 404 | return result.rowcount != 0 |
| 405 | |||
| 406 | async def get_usage(self): | ||
| 407 | usage = {} | ||
| 408 | async with self.db.begin() as session: | ||
| 409 | for name, table in Base.metadata.tables.items(): | ||
| 410 | statement = select(func.count()).select_from(table) | ||
| 411 | self.logger.debug("%s", statement) | ||
| 412 | result = await self.db.execute(statement) | ||
| 413 | usage[name] = { | ||
| 414 | "rows": result.scalar(), | ||
| 415 | } | ||
| 416 | |||
| 417 | return usage | ||
diff --git a/bitbake/lib/hashserv/sqlite.py b/bitbake/lib/hashserv/sqlite.py index 414ee8ffb8..dfdccbbaa0 100644 --- a/bitbake/lib/hashserv/sqlite.py +++ b/bitbake/lib/hashserv/sqlite.py | |||
| @@ -120,6 +120,18 @@ class Database(object): | |||
| 120 | self.db = sqlite3.connect(self.dbname) | 120 | self.db = sqlite3.connect(self.dbname) |
| 121 | self.db.row_factory = sqlite3.Row | 121 | self.db.row_factory = sqlite3.Row |
| 122 | 122 | ||
| 123 | with closing(self.db.cursor()) as cursor: | ||
| 124 | cursor.execute("SELECT sqlite_version()") | ||
| 125 | |||
| 126 | version = [] | ||
| 127 | for v in cursor.fetchone()[0].split("."): | ||
| 128 | try: | ||
| 129 | version.append(int(v)) | ||
| 130 | except ValueError: | ||
| 131 | version.append(v) | ||
| 132 | |||
| 133 | self.sqlite_version = tuple(version) | ||
| 134 | |||
| 123 | async def __aenter__(self): | 135 | async def __aenter__(self): |
| 124 | return self | 136 | return self |
| 125 | 137 | ||
| @@ -362,3 +374,28 @@ class Database(object): | |||
| 362 | ) | 374 | ) |
| 363 | self.db.commit() | 375 | self.db.commit() |
| 364 | return cursor.rowcount != 0 | 376 | return cursor.rowcount != 0 |
| 377 | |||
| 378 | async def get_usage(self): | ||
| 379 | usage = {} | ||
| 380 | with closing(self.db.cursor()) as cursor: | ||
| 381 | if self.sqlite_version >= (3, 33): | ||
| 382 | table_name = "sqlite_schema" | ||
| 383 | else: | ||
| 384 | table_name = "sqlite_master" | ||
| 385 | |||
| 386 | cursor.execute( | ||
| 387 | f""" | ||
| 388 | SELECT name FROM {table_name} WHERE type = 'table' AND name NOT LIKE 'sqlite_%' | ||
| 389 | """ | ||
| 390 | ) | ||
| 391 | for row in cursor.fetchall(): | ||
| 392 | cursor.execute( | ||
| 393 | """ | ||
| 394 | SELECT COUNT() FROM %s | ||
| 395 | """ | ||
| 396 | % row["name"], | ||
| 397 | ) | ||
| 398 | usage[row["name"]] = { | ||
| 399 | "rows": cursor.fetchone()[0], | ||
| 400 | } | ||
| 401 | return usage | ||
diff --git a/bitbake/lib/hashserv/tests.py b/bitbake/lib/hashserv/tests.py index 311b7b7772..9d5bec2454 100644 --- a/bitbake/lib/hashserv/tests.py +++ b/bitbake/lib/hashserv/tests.py | |||
| @@ -767,6 +767,15 @@ class HashEquivalenceCommonTests(object): | |||
| 767 | with self.auth_perms("@user-admin") as client: | 767 | with self.auth_perms("@user-admin") as client: |
| 768 | become = client.become_user(client.username) | 768 | become = client.become_user(client.username) |
| 769 | 769 | ||
| 770 | def test_get_db_usage(self): | ||
| 771 | usage = self.client.get_db_usage() | ||
| 772 | |||
| 773 | self.assertTrue(isinstance(usage, dict)) | ||
| 774 | for name in usage.keys(): | ||
| 775 | self.assertTrue(isinstance(usage[name], dict)) | ||
| 776 | self.assertIn("rows", usage[name]) | ||
| 777 | self.assertTrue(isinstance(usage[name]["rows"], int)) | ||
| 778 | |||
| 770 | 779 | ||
| 771 | class TestHashEquivalenceUnixServer(HashEquivalenceTestSetup, HashEquivalenceCommonTests, unittest.TestCase): | 780 | class TestHashEquivalenceUnixServer(HashEquivalenceTestSetup, HashEquivalenceCommonTests, unittest.TestCase): |
| 772 | def get_server_addr(self, server_idx): | 781 | def get_server_addr(self, server_idx): |
