1#!/usr/bin/env python3 2 3import sys 4import argparse 5import urllib 6import json 7import urllib.request 8import urllib.parse 9import ssl 10import hashlib 11import copy 12 13BUCKET_NAME = "perfetto-ui-data" 14CURRENT_STATE_VERSION = 28 15 16 17def upgrade_15(old): 18 new = copy.deepcopy(old) 19 new["version"] = 16 20 new["flamegraphModalDismissed"] = False 21 return new 22 23 24def upgrade_16(old): 25 new = copy.deepcopy(old) 26 new["version"] = 17 27 new["nextId"] = max(old["nextId"], old["nextNoteId"], old["nextAreaId"]) 28 engines = old["engines"] 29 if len(engines) > 0: 30 new["currentEngineId"] = list(engines.values())[0]['id'] 31 return new 32 33 34def upgrade_17(old): 35 new = copy.deepcopy(old) 36 new["version"] = 18 37 # TODO(hjd): Update 38 return new 39 40 41def upgrade_18(old): 42 new = copy.deepcopy(old) 43 new["version"] = 19 44 # TODO(hjd): Update 45 return new 46 47 48def upgrade_19(old): 49 new = copy.deepcopy(old) 50 new["version"] = 20 51 # TODO(hjd): Update 52 return new 53 54 55def upgrade_20(old): 56 new = copy.deepcopy(old) 57 new["version"] = 21 58 # TODO(hjd): Update 59 return new 60 61 62def upgrade_20(old): 63 new = copy.deepcopy(old) 64 new["version"] = 22 65 # TODO(hjd): Update 66 return new 67 68 69def upgrade_21(old): 70 new = copy.deepcopy(old) 71 new["version"] = 22 72 # TODO(hjd): Update 73 return new 74 75 76def upgrade_22(old): 77 new = copy.deepcopy(old) 78 new["version"] = 23 79 new["logFilteringCriteria"] = { 80 "minimumLevel": 2, 81 } 82 return new 83 84 85def upgrade_23(old): 86 new = copy.deepcopy(old) 87 new["version"] = 24 88 current_engine_id = new["currentEngineId"] 89 new["engine"] = new["engines"][ 90 current_engine_id] if current_engine_id else None 91 del new["currentEngineId"] 92 del new["engines"] 93 return new 94 95 96def upgrade_24(old): 97 new = copy.deepcopy(old) 98 new["version"] = 25 99 new["omniboxState"] = new["frontendLocalState"]["omniboxState"] 100 del new["frontendLocalState"]["omniboxState"] 101 return new 102 103 104def upgrade_25(old): 105 new = copy.deepcopy(old) 106 new["version"] = 26 107 new["logFilteringCriteria"]["tags"] = [] 108 return new 109 110 111def upgrade_26(old): 112 new = copy.deepcopy(old) 113 new["version"] = 27 114 new["logFilteringCriteria"]["textEntry"] = "" 115 return new 116 117 118def upgrade_27(old): 119 new = copy.deepcopy(old) 120 new["version"] = 28 121 new["logFilteringCriteria"]["hideNonMatching"] = False 122 return new 123 124 125def bug_compatible_hash_mangling(hash): 126 pairs = [hash[i:i + 2] for i in range(0, len(hash), 2)] 127 return ''.join([pair.removeprefix("0") for pair in pairs]) 128 129 130def upload_state(state): 131 data = state.encode("utf-8") 132 hash = bug_compatible_hash_mangling(hashlib.sha256(data).hexdigest()) 133 134 try: 135 get(make_state_url(hash)) 136 except: 137 pass 138 else: 139 return hash 140 141 url = f"https://www.googleapis.com/upload/storage/v1/b/{BUCKET_NAME}/o?uploadType=media&name={hash}&predefinedAcl=publicRead" 142 request = urllib.request.Request(url, data=data) 143 request.add_header("Content-Type", "application/json; charset=utf-8") 144 response = urllib.request.urlopen(request) 145 return hash 146 147 148def make_state_url(id): 149 return f"https://storage.googleapis.com/{BUCKET_NAME}/{id}" 150 151 152def make_ui_url(id): 153 return f"https://ui.perfetto.dev/#!/?s={id}" 154 155 156def extract_state_uuid(url): 157 fragment = urllib.parse.urlparse(url).fragment 158 fragment = fragment.removeprefix("!/?") 159 return urllib.parse.parse_qs(fragment)["s"][0] 160 161 162def get(url): 163 context = ssl._create_unverified_context() 164 response = urllib.request.urlopen(url, context=context) 165 contents = response.read().decode() 166 return contents 167 168 169def post(url): 170 context = ssl._create_unverified_context() 171 response = urllib.request.urlopen(url, context=context) 172 contents = response.read().decode() 173 return contents 174 175 176def main(): 177 parser = argparse.ArgumentParser() 178 parser.add_argument("permalink", help="Permalink you wish to update") 179 parser.add_argument( 180 "--target-version", 181 help=f"Target state version (default: {CURRENT_STATE_VERSION})", 182 default=CURRENT_STATE_VERSION) 183 parser.add_argument( 184 "--verbose", help=f"Show debug information", action="store_true") 185 args = parser.parse_args() 186 187 permalink_url = args.permalink 188 old_uuid = extract_state_uuid(permalink_url) 189 old_state_url = make_state_url(old_uuid) 190 old_state = get(old_state_url) 191 old_json = json.loads(old_state) 192 193 old_state_version = old_json["version"] 194 new_state_version = args.target_version 195 196 print(json.dumps(old_json, sort_keys=True, indent=4)) 197 198 UPGRADE = { 199 15: upgrade_15, 200 16: upgrade_16, 201 17: upgrade_17, 202 18: upgrade_18, 203 19: upgrade_19, 204 20: upgrade_20, 205 21: upgrade_21, 206 22: upgrade_22, 207 23: upgrade_23, 208 24: upgrade_24, 209 25: upgrade_25, 210 26: upgrade_26, 211 27: upgrade_27, 212 } 213 214 new_json = old_json 215 for i in range(old_state_version, new_state_version): 216 new_json = UPGRADE[i](new_json) 217 218 new_state = json.dumps(new_json) 219 new_uuid = upload_state(new_state) 220 new_url = make_ui_url(new_uuid) 221 print(f"Your new permalink is accessible at:") 222 print(f"{new_url}") 223 return 0 224 225 226if __name__ == "__main__": 227 sys.exit(main()) 228