1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.cts.releaseparser; 18 19 import com.android.cts.releaseparser.ReleaseProto.*; 20 21 import java.io.File; 22 import java.io.FileInputStream; 23 import java.io.FileWriter; 24 import java.io.IOException; 25 import java.io.PrintWriter; 26 import java.util.ArrayList; 27 import java.util.HashMap; 28 import java.util.Map; 29 import java.util.TreeMap; 30 31 public class DepCsvPrinter { 32 private ReleaseContent mRelContent; 33 private PrintWriter mPWriter; 34 private HashMap<String, String> mLibMap; 35 private HashMap<String, String> mDepPathMap; 36 private TreeMap<String, Entry> mTreeEntryMap; 37 private ReleaseContent mBRelContent; 38 private int mBits; 39 private String mTitle; 40 private int mCurLevel; 41 private ArrayList<Entry> mEntryList; 42 getTitle(ReleaseContent relContent)43 private static String getTitle(ReleaseContent relContent) { 44 return relContent.getName() + relContent.getVersion() + relContent.getBuildNumber(); 45 } 46 DepCsvPrinter(ReleaseContent relContent)47 public DepCsvPrinter(ReleaseContent relContent) { 48 mRelContent = relContent; 49 mTitle = getTitle(relContent); 50 mBRelContent = null; 51 mTreeEntryMap = new TreeMap<String, Entry>(mRelContent.getEntries()); 52 } 53 writeDeltaDigraphs(ReleaseContent bRelContent, String dirName)54 public void writeDeltaDigraphs(ReleaseContent bRelContent, String dirName) { 55 mBRelContent = bRelContent; 56 mTitle = mTitle + "_vs_" + getTitle(bRelContent); 57 compareEntries(dirName); 58 } 59 compareEntries(String dirName)60 public void compareEntries(String dirName) { 61 for (Entry entry : mRelContent.getEntries().values()) { 62 if (entry.getType() == Entry.EntryType.EXE) { 63 String exeName = entry.getName(); 64 String fileName = String.format("%s/%s.csv", dirName, exeName); 65 writeCsv(entry, fileName); 66 } 67 } 68 } 69 writeDeltaDigraph(String exeName, ReleaseContent bRelContent, String fileName)70 public void writeDeltaDigraph(String exeName, ReleaseContent bRelContent, String fileName) { 71 mBRelContent = bRelContent; 72 mTitle = mTitle + "_vs_" + getTitle(bRelContent); 73 compareEntry(exeName, fileName); 74 } 75 compareEntry(String exeName, String fileName)76 public void compareEntry(String exeName, String fileName) { 77 for (Entry entry : mRelContent.getEntries().values()) { 78 if (entry.getName().equals(exeName)) { 79 writeCsv(entry, fileName); 80 break; 81 } 82 } 83 } 84 writeCsv(Entry entry, String fileName)85 public void writeCsv(Entry entry, String fileName) { 86 try { 87 String exeName = entry.getName(); 88 FileWriter fWriter = new FileWriter(fileName); 89 mPWriter = new PrintWriter(fWriter); 90 mLibMap = new HashMap<String, String>(); 91 mEntryList = new ArrayList<Entry>(); 92 mDepPathMap = new HashMap<String, String>(); 93 mBits = entry.getAbiBits(); 94 95 String sourceNode = getNodeName(exeName); 96 int delta = checkDelta(entry); 97 98 mCurLevel = 0; 99 mEntryList.add(entry); 100 printDep(entry, sourceNode); 101 102 mPWriter.println("id,label,value,delta"); 103 for (Map.Entry<String, String> node : mLibMap.entrySet()) { 104 mPWriter.println(String.format("%s,%s", node.getKey(), node.getValue())); 105 } 106 107 mPWriter.println("source,target,value,label"); 108 for (Map.Entry<String, String> link : mDepPathMap.entrySet()) { 109 mPWriter.println(String.format("%s,%s", link.getKey(), link.getValue())); 110 } 111 112 // Adjacency list 113 mPWriter.println("vertex,edge1,edge2,edge3,edge4,edge5,edge6,edge7,..."); 114 for (Entry e : mEntryList) { 115 mPWriter.println( 116 String.format( 117 "%s,%s", e.getName(), String.join(",", e.getDependenciesList()))); 118 // String.join(",", e.getDynamicLoadingDependenciesList()))); 119 } 120 121 mPWriter.flush(); 122 mPWriter.close(); 123 } catch (IOException e) { 124 System.err.println("IOException:" + e.getMessage()); 125 } 126 } 127 writeRcDeltaDigraphs(ReleaseContent bRelContent, String dirName)128 public void writeRcDeltaDigraphs(ReleaseContent bRelContent, String dirName) { 129 mBRelContent = bRelContent; 130 mTitle = mTitle + "_vs_" + getTitle(bRelContent); 131 compareRcEntries(dirName); 132 } 133 compareRcEntries(String dirName)134 public void compareRcEntries(String dirName) { 135 String fileName = String.format("%s/%s.csv", dirName, "RC-files"); 136 try { 137 FileWriter fWriter = new FileWriter(fileName); 138 mPWriter = new PrintWriter(fWriter); 139 String rootNode = "root"; 140 mPWriter.println("digraph {"); 141 mPWriter.println("rankdir=LR;"); 142 mPWriter.println("node [shape = box]"); 143 mPWriter.println(String.format("%s [label=\"%s\"]", rootNode, getNodeName(mTitle))); 144 145 for (Entry entry : mRelContent.getEntries().values()) { 146 if (entry.getType() == Entry.EntryType.RC) { 147 // only care if a RC starts Services 148 if (entry.getDependenciesList().size() > 0) { 149 writeRcDigraph(entry, rootNode); 150 } 151 } 152 } 153 154 mPWriter.println("}"); 155 mPWriter.flush(); 156 mPWriter.close(); 157 } catch (IOException e) { 158 System.err.println("IOException:" + e.getMessage()); 159 } 160 } 161 writeRcDigraph(Entry entry, String rootNode)162 public void writeRcDigraph(Entry entry, String rootNode) { 163 String rcName = entry.getRelativePath(); 164 String sourceNode = getNodeName(rcName); 165 mPWriter.printf(String.format("%s [label=\"%s\"", sourceNode, rcName)); 166 checkDelta(entry); 167 mPWriter.println("]"); 168 169 mPWriter.println(String.format("%s -> %s", rootNode, sourceNode)); 170 171 for (Service target : entry.getServicesList()) { 172 String targetFile = 173 target.getFile().replace("/system/", "SYSTEM/").replace("/vendor/", "VENDOR/"); 174 String targetNode = getNodeName(targetFile); 175 mPWriter.printf(String.format("%s [label=\"%s\"", targetNode, targetFile)); 176 Entry targetEntry = mRelContent.getEntries().get(targetFile); 177 if (targetEntry != null) { 178 checkDelta(targetEntry); 179 } 180 mPWriter.println("]"); 181 182 String depPath = String.format("%s -> %s", sourceNode, targetNode); 183 mPWriter.println(depPath); 184 } 185 } 186 checkDelta(Entry srcEntry)187 private int checkDelta(Entry srcEntry) { 188 // compare 189 if (mBRelContent != null) { 190 Entry bEntry = mBRelContent.getEntries().get(srcEntry.getRelativePath()); 191 if (bEntry == null) { 192 // New Entry 193 return -1; 194 } else { 195 if (srcEntry.getContentId().equals(bEntry.getContentId())) { 196 // Same 197 return 0; 198 } else { 199 // Different 200 return 1; 201 } 202 } 203 } 204 return -2; 205 } 206 getNodeName(String note)207 private String getNodeName(String note) { 208 return note; 209 } 210 printDep(Entry srcEntry, String sourceNode)211 private void printDep(Entry srcEntry, String sourceNode) { 212 mCurLevel += 1; 213 ArrayList<String> allDepList = new ArrayList<>(); 214 allDepList.addAll(srcEntry.getDependenciesList()); 215 int depCnt = allDepList.size(); 216 allDepList.addAll(srcEntry.getDynamicLoadingDependenciesList()); 217 int no = 0; 218 219 for (String dep : allDepList) { 220 boolean goFurther = false; 221 String targetNode; 222 Entry depEntry = null; 223 String filePath = dep; 224 225 // libEGL*.so to VENDOR/lib64/egl/libEGL_adreno.so 226 int idx = dep.indexOf("*.so"); 227 if (idx > -1) { 228 if (mBits == 32) { 229 filePath = "VENDOR/lib/egl/" + dep.substring(0, idx); 230 } else { 231 filePath = "VENDOR/lib64/egl/" + dep.substring(0, idx); 232 } 233 depEntry = mTreeEntryMap.tailMap(filePath, false).firstEntry().getValue(); 234 dep = depEntry.getName(); 235 } 236 targetNode = getNodeName(dep); 237 238 String depPath; 239 if (no < depCnt) { 240 depPath = String.format("%s,%s,black", sourceNode, targetNode); 241 } else { 242 // This is Dyanmic Loading 243 depPath = String.format("%s,%s,steelblue", sourceNode, targetNode); 244 } 245 246 if (mDepPathMap.get(depPath) == null) { 247 // Print path once only 248 mDepPathMap.put(depPath, String.format("%d", 1)); 249 } 250 251 if (depEntry == null) { 252 if (dep.startsWith("/system")) { 253 depEntry = mRelContent.getEntries().get(dep.replace("/system/", "SYSTEM/")); 254 } else if (dep.startsWith("/vendor")) { 255 depEntry = mRelContent.getEntries().get(dep.replace("/vendor/", "VENDOR/")); 256 } else { 257 if (mBits == 32) { 258 filePath = String.format("SYSTEM/lib/%s", dep); 259 } else { 260 filePath = String.format("SYSTEM/lib64/%s", dep); 261 } 262 263 depEntry = mRelContent.getEntries().get(filePath); 264 265 if (depEntry == null) { 266 // try Vendor 267 if (mBits == 32) { 268 filePath = String.format("VENDOR/lib/%s", dep); 269 } else { 270 filePath = String.format("VENDOR/lib64/%s", dep); 271 } 272 depEntry = mRelContent.getEntries().get(filePath); 273 } 274 275 if (depEntry == null) { 276 // try Vendor 277 if (mBits == 32) { 278 filePath = String.format("VENDOR/lib/%s", dep); 279 } else { 280 filePath = String.format("VENDOR/lib64/%s", dep); 281 } 282 depEntry = mRelContent.getEntries().get(filePath); 283 } 284 } 285 } 286 287 if (depEntry == null && dep.endsWith("libGLES_android.so")) { 288 // try Vendor 289 if (mBits == 32) { 290 filePath = "SYSTEM/lib/egl/libGLES_android.so"; 291 } else { 292 filePath = "SYSTEM/lib64/egl/libGLES_android.so"; 293 } 294 depEntry = mRelContent.getEntries().get(filePath); 295 } 296 297 if (depEntry != null) { 298 if (mLibMap.get(targetNode) == null) { 299 // Print Entry node once only 300 int delta = checkDelta(depEntry); 301 mLibMap.put( 302 targetNode, 303 String.format( 304 "%s,%d,%d", depEntry.getName(), depEntry.getSize(), delta)); 305 mEntryList.add(depEntry); 306 307 // Try to patch symbolic link to the target file 308 if (depEntry.getType() == Entry.EntryType.SYMBOLIC_LINK) { 309 filePath = depEntry.getParentFolder() + "/egl/" + depEntry.getName(); 310 Entry sDepEntry = mRelContent.getEntries().get(filePath); 311 if (sDepEntry == null) { 312 System.err.println( 313 "cannot find a target file for symbolic link: " 314 + depEntry.getRelativePath()); 315 } else { 316 depEntry = sDepEntry; 317 } 318 } 319 // Only visit once 320 goFurther = true; 321 } 322 323 if (goFurther) { 324 printDep(depEntry, targetNode); 325 } 326 } else { 327 System.err.println("cannot find: " + filePath); 328 } 329 no++; 330 } 331 mCurLevel -= 1; 332 } 333 334 private static final String USAGE_MESSAGE = 335 "Usage: java -cp releaseparser.jar com.android.cts.releaseparser.DepPrinter [-options]\n" 336 + " to compare A B builds dependency for X \n" 337 + "Options:\n" 338 + "\t-a A-Release.pb\t A release Content Protobuf file \n" 339 + "\t-b B-Release.pb\t B release Content Protobuf file \n" 340 + "\t-e Exe Name\t generates the Delta Dependency Digraph for the Execuable \n" 341 + "\t-r \t generates RC file Delta Dependency Digraphs \n" 342 + "\t \t without -e & -t, it will generate all Delta Dependency Digraphs \n"; 343 344 /** Get the argument or print out the usage and exit. */ printUsage()345 private static void printUsage() { 346 System.out.printf(USAGE_MESSAGE); 347 System.exit(1); 348 } 349 350 /** Get the argument or print out the usage and exit. */ getExpectedArg(String[] args, int index)351 private static String getExpectedArg(String[] args, int index) { 352 if (index < args.length) { 353 return args[index]; 354 } else { 355 printUsage(); 356 return null; // Never will happen because printUsage will call exit(1) 357 } 358 } 359 main(final String[] args)360 public static void main(final String[] args) { 361 String aPB = null; 362 String bPB = null; 363 String exeName = null; 364 boolean processRcOnly = false; 365 366 for (int i = 0; i < args.length; i++) { 367 if (args[i].startsWith("-")) { 368 if ("-a".equals(args[i])) { 369 aPB = getExpectedArg(args, ++i); 370 } else if ("-b".equals(args[i])) { 371 bPB = getExpectedArg(args, ++i); 372 } else if ("-e".equals(args[i])) { 373 exeName = getExpectedArg(args, ++i); 374 } else if ("-r".equals(args[i])) { 375 processRcOnly = true; 376 } else { 377 printUsage(); 378 } 379 } 380 } 381 if (aPB == null || bPB == null) { 382 printUsage(); 383 } 384 385 try { 386 ReleaseContent aRelContent = ReleaseContent.parseFrom(new FileInputStream(aPB)); 387 DepCsvPrinter depPrinter = new DepCsvPrinter(aRelContent); 388 ReleaseContent bRelContent = ReleaseContent.parseFrom(new FileInputStream(bPB)); 389 String dirName = 390 String.format("%s-vs-%s", getTitle(aRelContent), getTitle(bRelContent)); 391 File dir = new File(dirName); 392 dir.mkdir(); 393 if (processRcOnly) { 394 // General RC delta digraphs 395 depPrinter.writeRcDeltaDigraphs(bRelContent, dirName); 396 } else if (exeName == null) { 397 // General all execuable delta digraphs 398 depPrinter.writeDeltaDigraphs(bRelContent, dirName); 399 } else { 400 depPrinter.writeDeltaDigraph( 401 exeName, bRelContent, String.format("%s/%s.csv", dirName, exeName)); 402 } 403 404 } catch (Exception e) { 405 e.printStackTrace(); 406 System.err.println(e); 407 } 408 } 409 } 410