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