Home | History | Annotate | Download | only in runner
      1 /*
      2  * Copyright (C) 2011 Google Inc.
      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.google.caliper.runner;
     18 
     19 import static java.util.logging.Level.SEVERE;
     20 import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE;
     21 
     22 import com.google.caliper.api.ResultProcessor;
     23 import com.google.caliper.config.InvalidConfigurationException;
     24 import com.google.caliper.config.ResultProcessorConfig;
     25 import com.google.caliper.model.Trial;
     26 import com.google.common.base.Optional;
     27 import com.google.common.base.Strings;
     28 import com.google.common.collect.ImmutableList;
     29 import com.google.gson.Gson;
     30 
     31 import com.sun.jersey.api.client.Client;
     32 import com.sun.jersey.api.client.ClientHandlerException;
     33 import com.sun.jersey.api.client.UniformInterfaceException;
     34 import com.sun.jersey.api.client.WebResource;
     35 
     36 import java.io.PrintWriter;
     37 import java.net.URI;
     38 import java.net.URISyntaxException;
     39 import java.util.UUID;
     40 import java.util.logging.Logger;
     41 
     42 import javax.annotation.Nullable;
     43 
     44 /**
     45  * {@link ResultProcessor} implementation that uploads the JSON-serialized results to the Caliper
     46  * webapp.
     47  */
     48 abstract class ResultsUploader implements ResultProcessor {
     49   private static final Logger logger = Logger.getLogger(ResultsUploader.class.getName());
     50   private static final String POST_PATH = "/data/trials";
     51   private static final String RESULTS_PATH_PATTERN = "/runs/%s";
     52 
     53   private final PrintWriter stdout;
     54   private final Client client;
     55   private final Gson gson;
     56   private final Optional<UUID> apiKey;
     57   private final Optional<URI> uploadUri;
     58   private Optional<UUID> runId = Optional.absent();
     59   private boolean failure = false;
     60 
     61   ResultsUploader(PrintWriter stdout, Gson gson, Client client,
     62       ResultProcessorConfig resultProcessorConfig) throws InvalidConfigurationException {
     63     this.stdout = stdout;
     64     this.client = client;
     65     this.gson = gson;
     66     @Nullable String apiKeyString = resultProcessorConfig.options().get("key");
     67     Optional<UUID> apiKey = Optional.absent();
     68     if (Strings.isNullOrEmpty(apiKeyString)) {
     69       logger.info("No api key specified. Uploading results anonymously.");
     70     } else {
     71       try {
     72         apiKey = Optional.of(UUID.fromString(apiKeyString));
     73       } catch (IllegalArgumentException e) {
     74         throw new InvalidConfigurationException(String.format(
     75             "The specified API key (%s) is not valid. API keys are UUIDs and should look like %s.",
     76                 apiKeyString, new UUID(0L,  0L)));
     77       }
     78     }
     79     this.apiKey = apiKey;
     80 
     81     @Nullable String urlString = resultProcessorConfig.options().get("url");
     82     if (Strings.isNullOrEmpty(urlString)) {
     83       logger.info("No upload URL was specified. Results will not be uploaded.");
     84       this.uploadUri = Optional.absent();
     85     } else {
     86       try {
     87         this.uploadUri = Optional.of(new URI(urlString).resolve(POST_PATH));
     88       } catch (URISyntaxException e) {
     89         throw new InvalidConfigurationException(urlString + " is an invalid upload url", e);
     90       }
     91     }
     92   }
     93 
     94   @Override public final void processTrial(Trial trial) {
     95     if (uploadUri.isPresent()) {
     96       WebResource resource = client.resource(uploadUri.get());
     97       if (apiKey.isPresent()) {
     98         resource = resource.queryParam("key", apiKey.get().toString());
     99       }
    100       boolean threw = true;
    101       try {
    102         // TODO(gak): make the json part happen automagically
    103         resource.type(APPLICATION_JSON_TYPE).post(gson.toJson(ImmutableList.of(trial)));
    104         // only set the run id if a result has been successfully uploaded
    105         runId = Optional.of(trial.run().id());
    106         threw = false;
    107       } catch (ClientHandlerException e) {
    108         logUploadFailure(trial, e);
    109       } catch (UniformInterfaceException e) {
    110         logUploadFailure(trial, e);
    111         logger.fine("Failed upload response: " + e.getResponse().getStatus());
    112       } finally {
    113         failure |= threw;
    114       }
    115     }
    116   }
    117 
    118   private static void logUploadFailure(Trial trial, Exception e) {
    119     logger.log(SEVERE, String.format(
    120         "Could not upload trial %s. Consider uploading it manually.", trial.id()), e);
    121   }
    122 
    123   @Override public final void close() {
    124     if (uploadUri.isPresent()) {
    125       if (runId.isPresent()) {
    126         stdout.printf("Results have been uploaded. View them at: %s%n",
    127             uploadUri.get().resolve(String.format(RESULTS_PATH_PATTERN, runId.get())));
    128       }
    129       if (failure) {
    130         // TODO(gak): implement some retry
    131         stdout.println("Some trials failed to upload. Consider uploading them manually.");
    132       }
    133     } else {
    134       logger.fine("No upload URL was provided, so results were not uploaded.");
    135     }
    136   }
    137 }
    138