-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Bundle Analysis: Routing report and comparisons #447
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,12 +3,14 @@ | |
import os | ||
import sqlite3 | ||
import tempfile | ||
from collections import defaultdict, deque | ||
from typing import Any, Dict, Iterator, List, Optional, Set, Tuple | ||
|
||
import sentry_sdk | ||
from sqlalchemy import asc, desc, text | ||
from sqlalchemy.exc import OperationalError | ||
from sqlalchemy.orm import Session as DbSession | ||
from sqlalchemy.orm import aliased | ||
from sqlalchemy.orm.query import Query | ||
from sqlalchemy.sql import func | ||
from sqlalchemy.sql.functions import coalesce | ||
|
@@ -21,6 +23,7 @@ | |
AssetType, | ||
Bundle, | ||
Chunk, | ||
DynamicImport, | ||
Metadata, | ||
MetadataKey, | ||
Module, | ||
|
@@ -114,6 +117,57 @@ def routes(self) -> Optional[List[str]]: | |
|
||
return list(routes) | ||
|
||
def dynamically_imported_assets(self) -> List["AssetReport"]: | ||
""" | ||
Returns all dynamically imported assets of the current Asset. | ||
This is retrieving by querying all unique Assets in the DynamicImport | ||
model for each Chunk of the current Asset. | ||
""" | ||
with get_db_session(self.db_path) as session: | ||
# Reattach self.asset to the current session to avoid DetachedInstanceError | ||
asset = session.merge(self.asset) | ||
|
||
# Alias the chunks table for the Asset.chunks relationship | ||
asset_chunks = aliased(Chunk) | ||
|
||
assets = ( | ||
session.query(Asset) | ||
.distinct() | ||
.join(DynamicImport, DynamicImport.asset_id == Asset.id) | ||
.join(Chunk, DynamicImport.chunk_id == Chunk.id) | ||
.join(asset_chunks, asset_chunks.id == DynamicImport.chunk_id) | ||
.filter(asset_chunks.id.in_([chunk.id for chunk in asset.chunks])) | ||
) | ||
|
||
return ( | ||
AssetReport(self.db_path, asset, self.bundle_info) | ||
for asset in assets.all() | ||
) | ||
|
||
|
||
class BundleRouteReport: | ||
""" | ||
Report wrapper for asset route analytics. Mainly used for BundleRouteComparison | ||
Stores a dictionary | ||
keys: all routes of the bundle | ||
values: a list of distinct Assets (as AssetReports) that is associated with the route | ||
""" | ||
|
||
def __init__(self, db_path: str, data: Dict[str, List[AssetReport]]): | ||
self.db_path = db_path | ||
self.data = data | ||
|
||
def get_size(self) -> Dict[str, int]: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thoughts on renaming like |
||
results = {} | ||
for route, asset_reports in self.data.items(): | ||
results[route] = sum([asset.size for asset in asset_reports]) | ||
return results | ||
|
||
def get_size_by_route(self, route: str) -> Optional[int]: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: can consider dropping "by_route" in the function name since the argument |
||
if route not in self.data: | ||
return None | ||
return sum([asset.size for asset in self.data[route]]) | ||
|
||
|
||
class BundleReport: | ||
""" | ||
|
@@ -243,6 +297,45 @@ def is_cached(self) -> bool: | |
result = session.query(Bundle).filter(Bundle.id == self.bundle.id).first() | ||
return result.is_cached | ||
|
||
def routes(self) -> Dict[str, List[AssetReport]]: | ||
""" | ||
Returns a mapping of routes and all Assets (as AssetReports) that belongs to it | ||
Note that this ignores dynamically imported Assets (ie only the direct asset) | ||
""" | ||
route_map = defaultdict(list) | ||
for asset_report in self.asset_reports(): | ||
for route in asset_report.routes(): | ||
route_map[route].append(asset_report) | ||
return route_map | ||
|
||
@sentry_sdk.trace | ||
def full_route_report(self) -> BundleRouteReport: | ||
""" | ||
A more powerful routes function that will additionally associate dynamically | ||
imported Assets into the belonging route. Also this function returns a | ||
BundleRouteReport object as this will be used for comparison and additional | ||
data manipulation. | ||
""" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. neat! |
||
return_data = defaultdict(list) # typing: Dict[str, List[AssetReport]] | ||
for route, asset_reports in self.routes().items(): | ||
# Implements a graph traversal algorithm to get all nodes (Asset) linked by edges | ||
# represented as DynamicImport. | ||
visited_asset_ids = set() | ||
unique_assets = [] | ||
|
||
# For each Asset get all the dynamic imported Asset that we will need to traverse into | ||
to_be_processed_asset = deque(asset_reports) | ||
while to_be_processed_asset: | ||
current_asset = to_be_processed_asset.popleft() | ||
if current_asset.id not in visited_asset_ids: | ||
visited_asset_ids.add(current_asset.id) | ||
unique_assets.append(current_asset) | ||
to_be_processed_asset += current_asset.dynamically_imported_assets() | ||
|
||
# Add all the assets found to the route we were processing | ||
return_data[route] = unique_assets | ||
return BundleRouteReport(self.db_path, return_data) | ||
|
||
|
||
class BundleAnalysisReport: | ||
""" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to add the case where base_sizes[route_name] is 0 to avoid a potential divide by 0 issue at line 240? Can 0 even happen - maybe with rounding?