115a4d2ffd04dc6c70f2cd17dae12ac6bc14c69abKenny Root/* 215a4d2ffd04dc6c70f2cd17dae12ac6bc14c69abKenny Root * Copyright (C) 2009 The Android Open Source Project 315a4d2ffd04dc6c70f2cd17dae12ac6bc14c69abKenny Root * 415a4d2ffd04dc6c70f2cd17dae12ac6bc14c69abKenny Root * Licensed under the Apache License, Version 2.0 (the "License"); 515a4d2ffd04dc6c70f2cd17dae12ac6bc14c69abKenny Root * you may not use this file except in compliance with the License. 615a4d2ffd04dc6c70f2cd17dae12ac6bc14c69abKenny Root * You may obtain a copy of the License at 715a4d2ffd04dc6c70f2cd17dae12ac6bc14c69abKenny Root * 815a4d2ffd04dc6c70f2cd17dae12ac6bc14c69abKenny Root * http://www.apache.org/licenses/LICENSE-2.0 915a4d2ffd04dc6c70f2cd17dae12ac6bc14c69abKenny Root * 1015a4d2ffd04dc6c70f2cd17dae12ac6bc14c69abKenny Root * Unless required by applicable law or agreed to in writing, software 1115a4d2ffd04dc6c70f2cd17dae12ac6bc14c69abKenny Root * distributed under the License is distributed on an "AS IS" BASIS, 1215a4d2ffd04dc6c70f2cd17dae12ac6bc14c69abKenny Root * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1315a4d2ffd04dc6c70f2cd17dae12ac6bc14c69abKenny Root * See the License for the specific language governing permissions and 1415a4d2ffd04dc6c70f2cd17dae12ac6bc14c69abKenny Root * limitations under the License. 1515a4d2ffd04dc6c70f2cd17dae12ac6bc14c69abKenny Root */ 1615a4d2ffd04dc6c70f2cd17dae12ac6bc14c69abKenny Root 17e540833fdff4d58e37c9ba859388e24e2945ed45Bob Leepackage com.android.internal.os; 18e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee 19db35269ae4e6a6572156e39557a993d5b730c167Jesse Wilsonimport android.content.pm.PackageInfo; 20db35269ae4e6a6572156e39557a993d5b730c167Jesse Wilsonimport android.os.Build; 21db35269ae4e6a6572156e39557a993d5b730c167Jesse Wilsonimport android.os.SystemProperties; 22db35269ae4e6a6572156e39557a993d5b730c167Jesse Wilsonimport android.util.Log; 23ed7e00729f78d11107f0e9a7f0559dfe4be6b560Brian Carlstromimport dalvik.system.profiler.BinaryHprofWriter; 24a9602db3d5a6c9bc5a7a31b4fe3cc141235ad332Brian Carlstromimport dalvik.system.profiler.SamplingProfiler; 251751086360056bc60d00f2ed2988bc82be9e7bd9Brian Carlstromimport java.io.BufferedOutputStream; 26e540833fdff4d58e37c9ba859388e24e2945ed45Bob Leeimport java.io.File; 27e540833fdff4d58e37c9ba859388e24e2945ed45Bob Leeimport java.io.FileOutputStream; 28e540833fdff4d58e37c9ba859388e24e2945ed45Bob Leeimport java.io.IOException; 29ed7e00729f78d11107f0e9a7f0559dfe4be6b560Brian Carlstromimport java.io.OutputStream; 301751086360056bc60d00f2ed2988bc82be9e7bd9Brian Carlstromimport java.io.PrintStream; 31ed7e00729f78d11107f0e9a7f0559dfe4be6b560Brian Carlstromimport java.util.Date; 32e540833fdff4d58e37c9ba859388e24e2945ed45Bob Leeimport java.util.concurrent.Executor; 33e540833fdff4d58e37c9ba859388e24e2945ed45Bob Leeimport java.util.concurrent.Executors; 34def41ec2e88a70e63590117c93476276f8d0bf4cBrian Carlstromimport java.util.concurrent.ThreadFactory; 35db35269ae4e6a6572156e39557a993d5b730c167Jesse Wilsonimport java.util.concurrent.atomic.AtomicBoolean; 36db35269ae4e6a6572156e39557a993d5b730c167Jesse Wilsonimport libcore.io.IoUtils; 37e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee 38e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee/** 39e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee * Integrates the framework with Dalvik's sampling profiler. 40e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee */ 41e540833fdff4d58e37c9ba859388e24e2945ed45Bob Leepublic class SamplingProfilerIntegration { 42e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee 43e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee private static final String TAG = "SamplingProfilerIntegration"; 44e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee 45bde75706592c77379fb6546283e733abaca6fe04Sen Hu public static final String SNAPSHOT_DIR = "/data/snapshots"; 46bde75706592c77379fb6546283e733abaca6fe04Sen Hu 47e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee private static final boolean enabled; 48e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee private static final Executor snapshotWriter; 49def41ec2e88a70e63590117c93476276f8d0bf4cBrian Carlstrom private static final int samplingProfilerMilliseconds; 50def41ec2e88a70e63590117c93476276f8d0bf4cBrian Carlstrom private static final int samplingProfilerDepth; 51bde75706592c77379fb6546283e733abaca6fe04Sen Hu 52bde75706592c77379fb6546283e733abaca6fe04Sen Hu /** Whether or not a snapshot is being persisted. */ 53bde75706592c77379fb6546283e733abaca6fe04Sen Hu private static final AtomicBoolean pending = new AtomicBoolean(false); 54bde75706592c77379fb6546283e733abaca6fe04Sen Hu 55e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee static { 56def41ec2e88a70e63590117c93476276f8d0bf4cBrian Carlstrom samplingProfilerMilliseconds = SystemProperties.getInt("persist.sys.profiler_ms", 0); 57def41ec2e88a70e63590117c93476276f8d0bf4cBrian Carlstrom samplingProfilerDepth = SystemProperties.getInt("persist.sys.profiler_depth", 4); 58def41ec2e88a70e63590117c93476276f8d0bf4cBrian Carlstrom if (samplingProfilerMilliseconds > 0) { 59def41ec2e88a70e63590117c93476276f8d0bf4cBrian Carlstrom File dir = new File(SNAPSHOT_DIR); 60def41ec2e88a70e63590117c93476276f8d0bf4cBrian Carlstrom dir.mkdirs(); 61def41ec2e88a70e63590117c93476276f8d0bf4cBrian Carlstrom // the directory needs to be writable to anybody to allow file writing 62def41ec2e88a70e63590117c93476276f8d0bf4cBrian Carlstrom dir.setWritable(true, false); 63def41ec2e88a70e63590117c93476276f8d0bf4cBrian Carlstrom // the directory needs to be executable to anybody to allow file creation 64def41ec2e88a70e63590117c93476276f8d0bf4cBrian Carlstrom dir.setExecutable(true, false); 65def41ec2e88a70e63590117c93476276f8d0bf4cBrian Carlstrom if (dir.isDirectory()) { 66def41ec2e88a70e63590117c93476276f8d0bf4cBrian Carlstrom snapshotWriter = Executors.newSingleThreadExecutor(new ThreadFactory() { 67def41ec2e88a70e63590117c93476276f8d0bf4cBrian Carlstrom public Thread newThread(Runnable r) { 68def41ec2e88a70e63590117c93476276f8d0bf4cBrian Carlstrom return new Thread(r, TAG); 69def41ec2e88a70e63590117c93476276f8d0bf4cBrian Carlstrom } 70def41ec2e88a70e63590117c93476276f8d0bf4cBrian Carlstrom }); 71def41ec2e88a70e63590117c93476276f8d0bf4cBrian Carlstrom enabled = true; 72def41ec2e88a70e63590117c93476276f8d0bf4cBrian Carlstrom Log.i(TAG, "Profiling enabled. Sampling interval ms: " 73def41ec2e88a70e63590117c93476276f8d0bf4cBrian Carlstrom + samplingProfilerMilliseconds); 74def41ec2e88a70e63590117c93476276f8d0bf4cBrian Carlstrom } else { 75def41ec2e88a70e63590117c93476276f8d0bf4cBrian Carlstrom snapshotWriter = null; 76def41ec2e88a70e63590117c93476276f8d0bf4cBrian Carlstrom enabled = true; 77def41ec2e88a70e63590117c93476276f8d0bf4cBrian Carlstrom Log.w(TAG, "Profiling setup failed. Could not create " + SNAPSHOT_DIR); 78def41ec2e88a70e63590117c93476276f8d0bf4cBrian Carlstrom } 79e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee } else { 80e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee snapshotWriter = null; 81bde75706592c77379fb6546283e733abaca6fe04Sen Hu enabled = false; 82def41ec2e88a70e63590117c93476276f8d0bf4cBrian Carlstrom Log.i(TAG, "Profiling disabled."); 83e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee } 84e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee } 85e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee 86ed7e00729f78d11107f0e9a7f0559dfe4be6b560Brian Carlstrom private static SamplingProfiler samplingProfiler; 87ed7e00729f78d11107f0e9a7f0559dfe4be6b560Brian Carlstrom private static long startMillis; 881751086360056bc60d00f2ed2988bc82be9e7bd9Brian Carlstrom 89e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee /** 90e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee * Is profiling enabled? 91e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee */ 92e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee public static boolean isEnabled() { 93e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee return enabled; 94e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee } 95e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee 96e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee /** 97e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee * Starts the profiler if profiling is enabled. 98e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee */ 99e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee public static void start() { 1001751086360056bc60d00f2ed2988bc82be9e7bd9Brian Carlstrom if (!enabled) { 1011751086360056bc60d00f2ed2988bc82be9e7bd9Brian Carlstrom return; 1021751086360056bc60d00f2ed2988bc82be9e7bd9Brian Carlstrom } 103ed7e00729f78d11107f0e9a7f0559dfe4be6b560Brian Carlstrom if (samplingProfiler != null) { 104ed7e00729f78d11107f0e9a7f0559dfe4be6b560Brian Carlstrom Log.e(TAG, "SamplingProfilerIntegration already started at " + new Date(startMillis)); 105ed7e00729f78d11107f0e9a7f0559dfe4be6b560Brian Carlstrom return; 106ed7e00729f78d11107f0e9a7f0559dfe4be6b560Brian Carlstrom } 107ed7e00729f78d11107f0e9a7f0559dfe4be6b560Brian Carlstrom 1081751086360056bc60d00f2ed2988bc82be9e7bd9Brian Carlstrom ThreadGroup group = Thread.currentThread().getThreadGroup(); 1091751086360056bc60d00f2ed2988bc82be9e7bd9Brian Carlstrom SamplingProfiler.ThreadSet threadSet = SamplingProfiler.newThreadGroupTheadSet(group); 110ed7e00729f78d11107f0e9a7f0559dfe4be6b560Brian Carlstrom samplingProfiler = new SamplingProfiler(samplingProfilerDepth, threadSet); 111ed7e00729f78d11107f0e9a7f0559dfe4be6b560Brian Carlstrom samplingProfiler.start(samplingProfilerMilliseconds); 112ed7e00729f78d11107f0e9a7f0559dfe4be6b560Brian Carlstrom startMillis = System.currentTimeMillis(); 113e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee } 114e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee 115e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee /** 116bde75706592c77379fb6546283e733abaca6fe04Sen Hu * Writes a snapshot if profiling is enabled. 117e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee */ 118bde75706592c77379fb6546283e733abaca6fe04Sen Hu public static void writeSnapshot(final String processName, final PackageInfo packageInfo) { 1191751086360056bc60d00f2ed2988bc82be9e7bd9Brian Carlstrom if (!enabled) { 1201751086360056bc60d00f2ed2988bc82be9e7bd9Brian Carlstrom return; 1211751086360056bc60d00f2ed2988bc82be9e7bd9Brian Carlstrom } 122ed7e00729f78d11107f0e9a7f0559dfe4be6b560Brian Carlstrom if (samplingProfiler == null) { 123ed7e00729f78d11107f0e9a7f0559dfe4be6b560Brian Carlstrom Log.e(TAG, "SamplingProfilerIntegration is not started"); 124ed7e00729f78d11107f0e9a7f0559dfe4be6b560Brian Carlstrom return; 125ed7e00729f78d11107f0e9a7f0559dfe4be6b560Brian Carlstrom } 126e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee 127eec2f41d607c3eacba4f7d9cc098b335c7310d23Bob Lee /* 128bde75706592c77379fb6546283e733abaca6fe04Sen Hu * If we're already writing a snapshot, don't bother enqueueing another 129eec2f41d607c3eacba4f7d9cc098b335c7310d23Bob Lee * request right now. This will reduce the number of individual 130eec2f41d607c3eacba4f7d9cc098b335c7310d23Bob Lee * snapshots and in turn the total amount of memory consumed (one big 131eec2f41d607c3eacba4f7d9cc098b335c7310d23Bob Lee * snapshot is smaller than N subset snapshots). 132eec2f41d607c3eacba4f7d9cc098b335c7310d23Bob Lee */ 133bde75706592c77379fb6546283e733abaca6fe04Sen Hu if (pending.compareAndSet(false, true)) { 134e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee snapshotWriter.execute(new Runnable() { 135e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee public void run() { 136e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee try { 137def41ec2e88a70e63590117c93476276f8d0bf4cBrian Carlstrom writeSnapshotFile(processName, packageInfo); 138e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee } finally { 139bde75706592c77379fb6546283e733abaca6fe04Sen Hu pending.set(false); 140e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee } 141e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee } 142e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee }); 143e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee } 144e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee } 145e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee 146e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee /** 147e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee * Writes the zygote's snapshot to internal storage if profiling is enabled. 148e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee */ 149e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee public static void writeZygoteSnapshot() { 1501751086360056bc60d00f2ed2988bc82be9e7bd9Brian Carlstrom if (!enabled) { 1511751086360056bc60d00f2ed2988bc82be9e7bd9Brian Carlstrom return; 1521751086360056bc60d00f2ed2988bc82be9e7bd9Brian Carlstrom } 153def41ec2e88a70e63590117c93476276f8d0bf4cBrian Carlstrom writeSnapshotFile("zygote", null); 154ed7e00729f78d11107f0e9a7f0559dfe4be6b560Brian Carlstrom samplingProfiler.shutdown(); 155ed7e00729f78d11107f0e9a7f0559dfe4be6b560Brian Carlstrom samplingProfiler = null; 156ed7e00729f78d11107f0e9a7f0559dfe4be6b560Brian Carlstrom startMillis = 0; 157e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee } 158e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee 159bde75706592c77379fb6546283e733abaca6fe04Sen Hu /** 160bde75706592c77379fb6546283e733abaca6fe04Sen Hu * pass in PackageInfo to retrieve various values for snapshot header 161bde75706592c77379fb6546283e733abaca6fe04Sen Hu */ 162def41ec2e88a70e63590117c93476276f8d0bf4cBrian Carlstrom private static void writeSnapshotFile(String processName, PackageInfo packageInfo) { 1631751086360056bc60d00f2ed2988bc82be9e7bd9Brian Carlstrom if (!enabled) { 164e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee return; 165e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee } 166ed7e00729f78d11107f0e9a7f0559dfe4be6b560Brian Carlstrom samplingProfiler.stop(); 167e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee 168e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee /* 169ed7e00729f78d11107f0e9a7f0559dfe4be6b560Brian Carlstrom * We use the global start time combined with the process name 170ed7e00729f78d11107f0e9a7f0559dfe4be6b560Brian Carlstrom * as a unique ID. We can't use a counter because processes 171ed7e00729f78d11107f0e9a7f0559dfe4be6b560Brian Carlstrom * restart. This could result in some overlap if we capture 172ed7e00729f78d11107f0e9a7f0559dfe4be6b560Brian Carlstrom * two snapshots in rapid succession. 173e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee */ 174bde75706592c77379fb6546283e733abaca6fe04Sen Hu String name = processName.replaceAll(":", "."); 175ed7e00729f78d11107f0e9a7f0559dfe4be6b560Brian Carlstrom String path = SNAPSHOT_DIR + "/" + name + "-" + startMillis + ".snapshot"; 176ed7e00729f78d11107f0e9a7f0559dfe4be6b560Brian Carlstrom long start = System.currentTimeMillis(); 177ed7e00729f78d11107f0e9a7f0559dfe4be6b560Brian Carlstrom OutputStream outputStream = null; 178e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee try { 179ed7e00729f78d11107f0e9a7f0559dfe4be6b560Brian Carlstrom outputStream = new BufferedOutputStream(new FileOutputStream(path)); 180ed7e00729f78d11107f0e9a7f0559dfe4be6b560Brian Carlstrom PrintStream out = new PrintStream(outputStream); 181db35269ae4e6a6572156e39557a993d5b730c167Jesse Wilson generateSnapshotHeader(name, packageInfo, out); 182db35269ae4e6a6572156e39557a993d5b730c167Jesse Wilson if (out.checkError()) { 183db35269ae4e6a6572156e39557a993d5b730c167Jesse Wilson throw new IOException(); 184db35269ae4e6a6572156e39557a993d5b730c167Jesse Wilson } 185ed7e00729f78d11107f0e9a7f0559dfe4be6b560Brian Carlstrom BinaryHprofWriter.write(samplingProfiler.getHprofData(), outputStream); 186bde75706592c77379fb6546283e733abaca6fe04Sen Hu } catch (IOException e) { 187db35269ae4e6a6572156e39557a993d5b730c167Jesse Wilson Log.e(TAG, "Error writing snapshot to " + path, e); 188c9ad7c6dbb1d70b831cd79416cbe493ade50ed2cBrian Carlstrom return; 189bde75706592c77379fb6546283e733abaca6fe04Sen Hu } finally { 190ed7e00729f78d11107f0e9a7f0559dfe4be6b560Brian Carlstrom IoUtils.closeQuietly(outputStream); 191bde75706592c77379fb6546283e733abaca6fe04Sen Hu } 192bde75706592c77379fb6546283e733abaca6fe04Sen Hu // set file readable to the world so that SamplingProfilerService 193bde75706592c77379fb6546283e733abaca6fe04Sen Hu // can put it to dropbox 194bde75706592c77379fb6546283e733abaca6fe04Sen Hu new File(path).setReadable(true, false); 195e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee 196bde75706592c77379fb6546283e733abaca6fe04Sen Hu long elapsed = System.currentTimeMillis() - start; 197ed7e00729f78d11107f0e9a7f0559dfe4be6b560Brian Carlstrom Log.i(TAG, "Wrote snapshot " + path + " in " + elapsed + "ms."); 198ed7e00729f78d11107f0e9a7f0559dfe4be6b560Brian Carlstrom samplingProfiler.start(samplingProfilerMilliseconds); 199bde75706592c77379fb6546283e733abaca6fe04Sen Hu } 200bde75706592c77379fb6546283e733abaca6fe04Sen Hu 201bde75706592c77379fb6546283e733abaca6fe04Sen Hu /** 202ed7e00729f78d11107f0e9a7f0559dfe4be6b560Brian Carlstrom * generate header for snapshots, with the following format 203ed7e00729f78d11107f0e9a7f0559dfe4be6b560Brian Carlstrom * (like an HTTP header but without the \r): 204bde75706592c77379fb6546283e733abaca6fe04Sen Hu * 205bde75706592c77379fb6546283e733abaca6fe04Sen Hu * Version: <version number of profiler>\n 206bde75706592c77379fb6546283e733abaca6fe04Sen Hu * Process: <process name>\n 207bde75706592c77379fb6546283e733abaca6fe04Sen Hu * Package: <package name, if exists>\n 208bde75706592c77379fb6546283e733abaca6fe04Sen Hu * Package-Version: <version number of the package, if exists>\n 209bde75706592c77379fb6546283e733abaca6fe04Sen Hu * Build: <fingerprint>\n 210bde75706592c77379fb6546283e733abaca6fe04Sen Hu * \n 211bde75706592c77379fb6546283e733abaca6fe04Sen Hu * <the actual snapshot content begins here...> 212bde75706592c77379fb6546283e733abaca6fe04Sen Hu */ 213bde75706592c77379fb6546283e733abaca6fe04Sen Hu private static void generateSnapshotHeader(String processName, PackageInfo packageInfo, 214c9ad7c6dbb1d70b831cd79416cbe493ade50ed2cBrian Carlstrom PrintStream out) { 215bde75706592c77379fb6546283e733abaca6fe04Sen Hu // profiler version 216ed7e00729f78d11107f0e9a7f0559dfe4be6b560Brian Carlstrom out.println("Version: 3"); 217c9ad7c6dbb1d70b831cd79416cbe493ade50ed2cBrian Carlstrom out.println("Process: " + processName); 218c9ad7c6dbb1d70b831cd79416cbe493ade50ed2cBrian Carlstrom if (packageInfo != null) { 219c9ad7c6dbb1d70b831cd79416cbe493ade50ed2cBrian Carlstrom out.println("Package: " + packageInfo.packageName); 220c9ad7c6dbb1d70b831cd79416cbe493ade50ed2cBrian Carlstrom out.println("Package-Version: " + packageInfo.versionCode); 221e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee } 222c9ad7c6dbb1d70b831cd79416cbe493ade50ed2cBrian Carlstrom out.println("Build: " + Build.FINGERPRINT); 223bde75706592c77379fb6546283e733abaca6fe04Sen Hu // single blank line means the end of snapshot header. 224c9ad7c6dbb1d70b831cd79416cbe493ade50ed2cBrian Carlstrom out.println(); 225e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee } 226e540833fdff4d58e37c9ba859388e24e2945ed45Bob Lee} 227