com.facebook.buck.util.trace.ChromeTraceParser.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.buck.util.trace.ChromeTraceParser.java

Source

/*
 * Copyright 2016-present Facebook, Inc.
 *
 * 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.facebook.buck.util.trace;

import com.facebook.buck.io.ProjectFilesystem;
import com.google.common.base.Preconditions;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

/**
 * Event-driven parser for
 * <a href="https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview">
 * Chrome traces</a>.
 */
public class ChromeTraceParser {

    /**
     * Extracts data of interest if it finds a Chrome trace event of the type it is looking for.
     */
    public interface ChromeTraceEventMatcher<T> {
        /**
         * Tests the specified event to see if it is a match. Its name has already been extracted, for
         * convenience. If it is a match, it should return the data of interest in the return value. If
         * not, then it should return {@link Optional#empty()}.
         */
        Optional<T> test(JsonObject event, String name);
    }

    /**
     * Tries to extract the command that was used to trigger the invocation of Buck that generated the
     * trace. If found, it returns the command as an opaque string.
     */
    public static final ChromeTraceEventMatcher<String> COMMAND = (json, name) -> {
        JsonElement argsEl = json.get("args");
        if (argsEl == null || !argsEl.isJsonObject() || argsEl.getAsJsonObject().get("command_args") == null
                || !argsEl.getAsJsonObject().get("command_args").isJsonPrimitive()) {
            return Optional.empty();
        }

        String commandArgs = argsEl.getAsJsonObject().get("command_args").getAsString();
        String command = "buck " + name + " " + commandArgs;

        return Optional.of(command);
    };

    private final ProjectFilesystem projectFilesystem;

    public ChromeTraceParser(ProjectFilesystem projectFilesystem) {
        this.projectFilesystem = projectFilesystem;
    }

    /**
     * Parses a Chrome trace and stops parsing once all of the specified matchers have been
     * satisfied. This method parses only one Chrome trace event at a time, which avoids loading the
     * entire trace into memory.
     * @param pathToTrace is a relative path [to the ProjectFilesystem] to a Chrome trace in the
     *     "JSON Array Format."
     * @param chromeTraceEventMatchers set of matchers this invocation of {@code parse()} is trying to
     *     satisfy. Once a matcher finds a match, it will not consider any other events in the trace.
     * @return a {@code Map} where every matcher that found a match will have an entry whose key is
     *     the matcher and whose value is the one returned by
     *     {@link ChromeTraceEventMatcher#test(JsonObject, String)} without the {@link Optional}
     *     wrapper.
     */
    public Map<ChromeTraceEventMatcher<?>, Object> parse(Path pathToTrace,
            Set<ChromeTraceEventMatcher<?>> chromeTraceEventMatchers) throws IOException {
        Set<ChromeTraceEventMatcher<?>> unmatchedMatchers = new HashSet<>(chromeTraceEventMatchers);
        Preconditions.checkArgument(!unmatchedMatchers.isEmpty(), "Must specify at least one matcher");
        Map<ChromeTraceEventMatcher<?>, Object> results = new HashMap<>();

        try (InputStream input = projectFilesystem.newFileInputStream(pathToTrace);
                JsonReader jsonReader = new JsonReader(new InputStreamReader(input))) {
            jsonReader.beginArray();
            Gson gson = new Gson();

            featureSearch: while (true) {
                // If END_ARRAY is the next token, then there are no more elements in the array.
                if (jsonReader.peek().equals(JsonToken.END_ARRAY)) {
                    break;
                }

                // Verify and extract the name property before invoking any of the matchers.
                JsonObject event = gson.fromJson(jsonReader, JsonObject.class);
                JsonElement nameEl = event.get("name");
                if (nameEl == null || !nameEl.isJsonPrimitive()) {
                    continue;
                }
                String name = nameEl.getAsString();

                // Prefer Iterator to Iterable+foreach so we can use remove().
                for (Iterator<ChromeTraceEventMatcher<?>> iter = unmatchedMatchers.iterator(); iter.hasNext();) {
                    ChromeTraceEventMatcher<?> chromeTraceEventMatcher = iter.next();
                    Optional<?> result = chromeTraceEventMatcher.test(event, name);
                    if (result.isPresent()) {
                        iter.remove();
                        results.put(chromeTraceEventMatcher, result.get());

                        if (unmatchedMatchers.isEmpty()) {
                            break featureSearch;
                        }
                    }
                }
            }
        }

        // We could throw if !unmatchedMatchers.isEmpty(), but that might be overbearing.
        return results;
    }

    /**
     * Designed for use with the result of {@link ChromeTraceParser#parse(Path, Set)}.
     * Helper function to avoid some distasteful casting logic.
     */
    @SuppressWarnings("unchecked")
    public static <T> Optional<T> getResultForMatcher(ChromeTraceEventMatcher<T> matcher,
            Map<ChromeTraceEventMatcher<?>, Object> results) {
        T result = (T) results.get(matcher);
        return Optional.ofNullable(result);
    }
}