/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.builder.profile; import com.android.annotations.NonNull; import com.android.annotations.Nullable; import com.android.annotations.VisibleForTesting; import com.android.utils.ILogger; import com.android.utils.StdLogger; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.io.ByteStreams; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.lang.management.GarbageCollectorMXBean; import java.lang.management.ManagementFactory; import java.net.HttpURLConnection; import java.net.URL; import java.util.List; import java.util.UUID; /** * Configures and creates instances of {@link ProcessRecorder}. * * There can be only one instance of {@link ProcessRecorder} per process (well class loader * to be exact). This instance can be configured initially before any calls to * {@link ThreadRecorder#get()} is made. An exception will be thrown if an attempt is made to * configure the instance of {@link ProcessRecorder} past this initialization window. * */ public class ProcessRecorderFactory { public static void shutdown() throws InterruptedException { synchronized (LOCK) { List<GarbageCollectorMXBean> garbageCollectorMXBeans = ManagementFactory .getGarbageCollectorMXBeans(); ThreadRecorder.get().record(ExecutionType.FINAL_METADATA, Recorder.EmptyBlock, new Recorder.Property("build_time", Long.toString(System.currentTimeMillis() - sINSTANCE.startTime)), new Recorder.Property("gc_count", Long.toString(garbageCollectorMXBeans.get(0).getCollectionCount() - sINSTANCE.gcCountAtStart)), new Recorder.Property("gc_time", Long.toString(garbageCollectorMXBeans.get(0).getCollectionTime() - sINSTANCE.gcTimeAtStart))); if (sINSTANCE.isInitialized()) { sINSTANCE.get().finish(); sINSTANCE.uploadData(); } sINSTANCE.processRecorder = null; } } public static void initialize( @NonNull ILogger logger, @NonNull File out, @NonNull List<Recorder.Property> properties) throws IOException { synchronized (LOCK) { if (sINSTANCE.isInitialized() || !isEnabled()) { return; } sINSTANCE.setLogger(logger); sINSTANCE.setOutputFile(out); sINSTANCE.setRecordWriter(new ProcessRecorder.JsonRecordWriter(new FileWriter(out))); sINSTANCE.get(); // Initialize the ProcessRecorder instance publishInitialRecords(properties); } } public static void publishInitialRecords(@NonNull List<Recorder.Property> properties) { List<Recorder.Property> propertyList = Lists.newArrayListWithExpectedSize( 6 + properties.size()); propertyList.add(new Recorder.Property( "build_id", UUID.randomUUID().toString())); propertyList.add(new Recorder.Property( "os_name", System.getProperty("os.name"))); propertyList.add(new Recorder.Property( "os_version", System.getProperty("os.version"))); propertyList.add(new Recorder.Property( "java_version", System.getProperty("java.version"))); propertyList.add(new Recorder.Property( "java_vm_version", System.getProperty("java.vm.version"))); propertyList.add(new Recorder.Property( "max_memory", Long.toString(Runtime.getRuntime().maxMemory()))); propertyList.addAll(properties); ThreadRecorder.get().record( ExecutionType.INITIAL_METADATA, Recorder.EmptyBlock, propertyList); } private static boolean sENABLED = !Strings.isNullOrEmpty(System.getenv("RECORD_SPANS")); private final long startTime; private final long gcCountAtStart; private final long gcTimeAtStart; ProcessRecorderFactory() { startTime = System.currentTimeMillis(); List<GarbageCollectorMXBean> garbageCollectorMXBeans = ManagementFactory .getGarbageCollectorMXBeans(); gcCountAtStart = garbageCollectorMXBeans.get(0).getCollectionCount(); gcTimeAtStart = garbageCollectorMXBeans.get(0).getCollectionTime(); } public static void initializeForTests(ProcessRecorder.ExecutionRecordWriter recordWriter) { sINSTANCE = new ProcessRecorderFactory(); ProcessRecorder.resetForTests(); setEnabled(true); sINSTANCE.setRecordWriter(recordWriter); sINSTANCE.get(); // Initialize the ProcessRecorder instance publishInitialRecords(ImmutableList.<Recorder.Property>of()); } static boolean isEnabled() { return sENABLED; } @VisibleForTesting static void setEnabled(boolean enabled) { sENABLED = enabled; } /** * Sets the {@link ProcessRecorder.JsonRecordWriter } * @param recordWriter */ public synchronized void setRecordWriter( @NonNull ProcessRecorder.ExecutionRecordWriter recordWriter) { assertRecorderNotCreated(); this.recordWriter = recordWriter; } public synchronized void setLogger(@NonNull ILogger iLogger) { assertRecorderNotCreated(); this.iLogger = iLogger; } public static ProcessRecorderFactory getFactory() { return sINSTANCE; } boolean isInitialized() { return processRecorder != null; } @SuppressWarnings("VariableNotUsedInsideIf") private void assertRecorderNotCreated() { if (isInitialized()) { throw new RuntimeException("ProcessRecorder already created."); } } static final Object LOCK = new Object(); static ProcessRecorderFactory sINSTANCE = new ProcessRecorderFactory(); @Nullable private ProcessRecorder processRecorder = null; @Nullable private ProcessRecorder.ExecutionRecordWriter recordWriter = null; @Nullable private ILogger iLogger = null; private File outputFile = null; private void setOutputFile(File outputFile) { this.outputFile = outputFile; } synchronized ProcessRecorder get() { if (processRecorder == null) { if (recordWriter == null) { throw new RuntimeException("recordWriter not configured."); } if (iLogger == null) { iLogger = new StdLogger(StdLogger.Level.INFO); } processRecorder = new ProcessRecorder(recordWriter, iLogger); } return processRecorder; } private void uploadData() { if (outputFile == null) { return; } try { URL u = new URL("http://android-devtools-logging.appspot.com/log/"); HttpURLConnection conn = null; conn = (HttpURLConnection) u.openConnection(); conn.setDoOutput(true); conn.setRequestMethod("POST"); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); conn.setRequestProperty("Content-Length", String.valueOf(outputFile.length())); InputStream is = null; try { is = new BufferedInputStream(new FileInputStream(outputFile)); OutputStream os = conn.getOutputStream(); ByteStreams.copy(is, os); os.close(); } finally { if (is != null) { is.close(); } } String line; BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream())); while ((line = reader.readLine()) != null) { if (iLogger != null) { iLogger.info("From POST : " + line); } } reader.close(); } catch(Exception e) { if (iLogger != null) { iLogger.warning("An exception while generated while uploading the profiler data"); iLogger.error(e, "Exception while uploading the profiler data"); } } } }