This is an example of running an R version of Google Datalab
Google Datalab is a service that lets you easily interact with your data in the Google Cloud. This document is an excercise in trying to replicate the same functionality:
- Runs on Google Cloud infrastructure using
googleComputeEngineR
within its own Docker container
- Uses RStudio and its RMarkdown Notebooks to replicate the Jupyter/iPython functionality
- Auto authentication with the Google cloud services to work with BigQuery and Cloud Storage data
- Cross language support of python, SQL and bash via R Notebooks
- Python data analysis libraries pandas and NumPy
- Visualisation via R libraries such as the htmlwidgets family
- Installation of Tensorflow and RStudio’s tensorflow package
- Installation of
tensorflow
helper library tflearn
- Installation of feather to help R and python share data nicely.
Setup
library(googleAuthR)
## this reuses the authentication of the GCE instance we are on
gar_gce_auth()
library(bigQueryR)
## list authenticated projects
myproject <- bqr_list_projects()
library(googleCloudStorageR)
## list Cloud Storage buckets
gcs_list_buckets(myproject$id[[1]])
Demo of running python in same document:
hiss = 'sssssssss'
print "Pythons go %s." % hiss
Also works with SQL
and bash
pip freeze
Transfer data between R and Python with feather
From the example intro blogpost for feather:
library(feather)
df <- mtcars
path <- "my_data.feather"
write_feather(df, path)
import feather
path = 'my_data.feather'
df = feather.read_dataframe(path)
df.head
Tensorflow
Hello world Python
from __future__ import print_function
import tensorflow as tf
# Simple hello world using TensorFlow
# Create a Constant op
# The op is added as a node to the default graph.
#
# The value returned by the constructor represents the output
# of the Constant op.
hello = tf.constant('Hello, TensorFlow!')
# Start tf session
sess = tf.Session()
# Run the op
print(sess.run(hello))
Hello world R
library(tensorflow)
sess = tf$Session()
hello <- tf$constant('Hello, TensorFlow!')
sess$run(hello)
tflearn Titanic example
From the tflearn quickstart
from __future__ import print_function
import numpy as np
import tflearn
# Download the Titanic dataset
from tflearn.datasets import titanic
titanic.download_dataset('titanic_dataset.csv')
# Load CSV file, indicate that the first column represents labels
from tflearn.data_utils import load_csv
data, labels = load_csv('titanic_dataset.csv', target_column=0,
categorical_labels=True, n_classes=2)
# Preprocessing function
def preprocess(data, columns_to_ignore):
# Sort by descending id and delete columns
for id in sorted(columns_to_ignore, reverse=True):
[r.pop(id) for r in data]
for i in range(len(data)):
# Converting 'sex' field to float (id is 1 after removing labels column)
data[i][1] = 1. if data[i][1] == 'female' else 0.
return np.array(data, dtype=np.float32)
# Ignore 'name' and 'ticket' columns (id 1 & 6 of data array)
to_ignore=[1, 6]
# Preprocess data
data = preprocess(data, to_ignore)
# Build neural network
net = tflearn.input_data(shape=[None, 6])
net = tflearn.fully_connected(net, 32)
net = tflearn.fully_connected(net, 32)
net = tflearn.fully_connected(net, 2, activation='softmax')
net = tflearn.regression(net)
# Define model
model = tflearn.DNN(net)
# Start training (apply gradient descent algorithm)
model.fit(data, labels, n_epoch=10, batch_size=16, show_metric=True)
# Let's create some data for DiCaprio and Winslet
dicaprio = [3, 'Jack Dawson', 'male', 19, 0, 0, 'N/A', 5.0000]
winslet = [1, 'Rose DeWitt Bukater', 'female', 17, 1, 2, 'N/A', 100.0000]
# Preprocess data
dicaprio, winslet = preprocess([dicaprio, winslet], to_ignore)
# Predict surviving chances (class 1 results)
pred = model.predict([dicaprio, winslet])
print("DiCaprio Surviving Rate:", pred[0][1])
print("Winslet Surviving Rate:", pred[1][1])
Build details
This was run in a local R session to start up this RStudio instance with the right libraries installed:
library(googleComputeEngineR)
## make an RStudio instance to base upon
vm <- gce_vm(template = "rstudio",
name = "r-datalab-build",
username = "mark", password = "mark1234",
predefined_type = "n1-standard-1")
## once RStudio loaded at the IP, build the Dockerfile below on instance
## this takes a while
docker_build(vm, dockerfolder = get_dockerfolder("cloudDataLabR"), new_image = "r-datalab")
## send to the Container Registry
gce_push_registry(vm, save_name = "datalab-r-image", image_name = "r-datalab")
## Can now launch instances using this image via:
vm2 <- gce_vm(template = "rstudio",
name = "r-datalab",
predefined_type = "n1-standard-1",
dynamic_image = gce_tag_container("datalab-r"),
username = "mark", password = "mark1234")
The Dockerfile used is below:
FROM rocker/hadleyverse
MAINTAINER Mark Edmondson (r@sunholo.com)
# install cron and nano and tensorflow and tflearn
RUN apt-get update && apt-get install -y \
cron nano \
python-pip python-dev \
&& pip install numpy \
&& pip install pandas \
&& export TF_BINARY_URL=https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-0.11.0-cp27-none-linux_x86_64.whl \
&& pip install --upgrade $TF_BINARY_URL \
&& pip install git+https://github.com/tflearn/tflearn.git \
&& pip install cython \
&& pip install feather-format \
## clean up
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/ \
&& rm -rf /tmp/downloaded_packages/ /tmp/*.rds
## Install packages from CRAN
RUN install2.r --error \
-r 'http://cran.rstudio.com' \
googleAuthR googleAnalyticsR searchConsoleR googleCloudStorageR bigQueryR htmlwidgets feather rPython \
## install Github packages
&& Rscript -e "devtools::install_github(c('MarkEdmondson1234/youtubeAnalyticsR', 'MarkEdmondson1234/googleID', 'MarkEdmondson1234/googleAuthR'))" \
&& Rscript -e "devtools::install_github(c('bnosac/cronR'))" \
&& Rscript -e "devtools::install_github(c('rstudio/tensorflow'))" \
## clean up
&& rm -rf /tmp/downloaded_packages/ /tmp/*.rds \
LS0tCnRpdGxlOiAiUiBEYXRhbGFiIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDogZGVmYXVsdAogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQKLS0tCgpUaGlzIGlzIGFuIGV4YW1wbGUgb2YgcnVubmluZyBhbiBSIHZlcnNpb24gb2YgW0dvb2dsZSBEYXRhbGFiXShodHRwczovL2Nsb3VkLmdvb2dsZS5jb20vZGF0YWxhYi8pCgpHb29nbGUgRGF0YWxhYiBpcyBhIHNlcnZpY2UgdGhhdCBsZXRzIHlvdSBlYXNpbHkgaW50ZXJhY3Qgd2l0aCB5b3VyIGRhdGEgaW4gdGhlIEdvb2dsZSBDbG91ZC4gIFRoaXMgZG9jdW1lbnQgaXMgYW4gZXhjZXJjaXNlIGluIHRyeWluZyB0byByZXBsaWNhdGUgdGhlIHNhbWUgZnVuY3Rpb25hbGl0eToKCiogUnVucyBvbiBHb29nbGUgQ2xvdWQgaW5mcmFzdHJ1Y3R1cmUgdXNpbmcgYGdvb2dsZUNvbXB1dGVFbmdpbmVSYCB3aXRoaW4gaXRzIG93biBEb2NrZXIgY29udGFpbmVyCiogVXNlcyBSU3R1ZGlvIGFuZCBpdHMgW1JNYXJrZG93biBOb3RlYm9va3NdKGh0dHA6Ly9ybWFya2Rvd24ucnN0dWRpby5jb20vcl9ub3RlYm9va3MuaHRtbCkgdG8gcmVwbGljYXRlIHRoZSBKdXB5dGVyL2lQeXRob24gZnVuY3Rpb25hbGl0eQoqIEF1dG8gYXV0aGVudGljYXRpb24gd2l0aCB0aGUgR29vZ2xlIGNsb3VkIHNlcnZpY2VzIHRvIHdvcmsgd2l0aCBCaWdRdWVyeSBhbmQgQ2xvdWQgU3RvcmFnZSBkYXRhCiogQ3Jvc3MgbGFuZ3VhZ2Ugc3VwcG9ydCBvZiBweXRob24sIFNRTCBhbmQgYmFzaCB2aWEgUiBOb3RlYm9va3MKKiBQeXRob24gZGF0YSBhbmFseXNpcyBsaWJyYXJpZXMgW3BhbmRhc10oaHR0cDovL3BhbmRhcy5weWRhdGEub3JnLykgYW5kIFtOdW1QeV0oaHR0cDovL3d3dy5udW1weS5vcmcvKQoqIFZpc3VhbGlzYXRpb24gdmlhIFIgbGlicmFyaWVzIHN1Y2ggYXMgdGhlIFtodG1sd2lkZ2V0cyBmYW1pbHldKGh0dHA6Ly9nYWxsZXJ5Lmh0bWx3aWRnZXRzLm9yZy8pCiogSW5zdGFsbGF0aW9uIG9mIFtUZW5zb3JmbG93XShodHRwczovL3d3dy50ZW5zb3JmbG93Lm9yZykgYW5kIFJTdHVkaW8ncyBbdGVuc29yZmxvd10oaHR0cHM6Ly9yc3R1ZGlvLmdpdGh1Yi5pby90ZW5zb3JmbG93LykgcGFja2FnZQoqIEluc3RhbGxhdGlvbiBvZiBgdGVuc29yZmxvd2AgaGVscGVyIGxpYnJhcnkgW2B0ZmxlYXJuYF0oaHR0cDovL3RmbGVhcm4ub3JnKQoqIEluc3RhbGxhdGlvbiBvZiBbZmVhdGhlcl0oaHR0cHM6Ly9ibG9nLnJzdHVkaW8ub3JnLzIwMTYvMDMvMjkvZmVhdGhlci8pIHRvIGhlbHAgUiBhbmQgcHl0aG9uIHNoYXJlIGRhdGEgbmljZWx5LgoKIyMgU2V0dXAKCmBgYHtyLCBlY2hvPVRSVUV9CmxpYnJhcnkoZ29vZ2xlQXV0aFIpCiMjIHRoaXMgcmV1c2VzIHRoZSBhdXRoZW50aWNhdGlvbiBvZiB0aGUgR0NFIGluc3RhbmNlIHdlIGFyZSBvbgpnYXJfZ2NlX2F1dGgoKQoKbGlicmFyeShiaWdRdWVyeVIpCiMjIGxpc3QgYXV0aGVudGljYXRlZCBwcm9qZWN0cwpteXByb2plY3QgPC0gYnFyX2xpc3RfcHJvamVjdHMoKQoKbGlicmFyeShnb29nbGVDbG91ZFN0b3JhZ2VSKQojIyBsaXN0IENsb3VkIFN0b3JhZ2UgYnVja2V0cwpnY3NfbGlzdF9idWNrZXRzKG15cHJvamVjdCRpZFtbMV1dKQoKYGBgCgpEZW1vIG9mIHJ1bm5pbmcgcHl0aG9uIGluIHNhbWUgZG9jdW1lbnQ6CgpgYGB7cHl0aG9uLCBlY2hvPVRSVUV9Cmhpc3MgPSAnc3Nzc3Nzc3NzJwpwcmludCAiUHl0aG9ucyBnbyAlcy4iICUgaGlzcwpgYGAKCkFsc28gd29ya3Mgd2l0aCBgU1FMYCBhbmQgYGJhc2hgCgpgYGB7YmFzaCwgZWNobz1UUlVFfQpwaXAgZnJlZXplCmBgYAoKIyMgVHJhbnNmZXIgZGF0YSBiZXR3ZWVuIFIgYW5kIFB5dGhvbiB3aXRoIGZlYXRoZXIKCkZyb20gdGhlIFtleGFtcGxlIGludHJvIGJsb2dwb3N0XShodHRwczovL2Jsb2cucnN0dWRpby5vcmcvMjAxNi8wMy8yOS9mZWF0aGVyLykgZm9yIGZlYXRoZXI6CgpgYGB7ciwgZWNobz1UUlVFfQpsaWJyYXJ5KGZlYXRoZXIpCmRmIDwtIG10Y2FycwpwYXRoIDwtICJteV9kYXRhLmZlYXRoZXIiCndyaXRlX2ZlYXRoZXIoZGYsIHBhdGgpCmBgYAoKCmBgYHtweXRob24sIGVjaG89VFJVRX0KaW1wb3J0IGZlYXRoZXIKcGF0aCA9ICdteV9kYXRhLmZlYXRoZXInCmRmID0gZmVhdGhlci5yZWFkX2RhdGFmcmFtZShwYXRoKQpkZi5oZWFkCmBgYAoKIyMgVGVuc29yZmxvdwoKIyMjIEhlbGxvIHdvcmxkIFB5dGhvbgoKYGBge3B5dGhvbiwgZWNobz1UUlVFfQpmcm9tIF9fZnV0dXJlX18gaW1wb3J0IHByaW50X2Z1bmN0aW9uCgppbXBvcnQgdGVuc29yZmxvdyBhcyB0ZgoKIyBTaW1wbGUgaGVsbG8gd29ybGQgdXNpbmcgVGVuc29yRmxvdwoKIyBDcmVhdGUgYSBDb25zdGFudCBvcAojIFRoZSBvcCBpcyBhZGRlZCBhcyBhIG5vZGUgdG8gdGhlIGRlZmF1bHQgZ3JhcGguCiMKIyBUaGUgdmFsdWUgcmV0dXJuZWQgYnkgdGhlIGNvbnN0cnVjdG9yIHJlcHJlc2VudHMgdGhlIG91dHB1dAojIG9mIHRoZSBDb25zdGFudCBvcC4KaGVsbG8gPSB0Zi5jb25zdGFudCgnSGVsbG8sIFRlbnNvckZsb3chJykKCiMgU3RhcnQgdGYgc2Vzc2lvbgpzZXNzID0gdGYuU2Vzc2lvbigpCgojIFJ1biB0aGUgb3AKcHJpbnQoc2Vzcy5ydW4oaGVsbG8pKQpgYGAKCiMjIyBIZWxsbyB3b3JsZCBSCgpgYGB7ciwgZWNobz1UUlVFfQpsaWJyYXJ5KHRlbnNvcmZsb3cpCnNlc3MgPSB0ZiRTZXNzaW9uKCkKaGVsbG8gPC0gdGYkY29uc3RhbnQoJ0hlbGxvLCBUZW5zb3JGbG93IScpCnNlc3MkcnVuKGhlbGxvKQpgYGAKCiMjIHRmbGVhcm4gVGl0YW5pYyBleGFtcGxlCgpGcm9tIHRoZSBbdGZsZWFybiBxdWlja3N0YXJ0XShodHRwOi8vdGZsZWFybi5vcmcvdHV0b3JpYWxzL3F1aWNrc3RhcnQuaHRtbCkKCmBgYHtweXRob24sIGVjaG8gPSBUUlVFfQpmcm9tIF9fZnV0dXJlX18gaW1wb3J0IHByaW50X2Z1bmN0aW9uCgppbXBvcnQgbnVtcHkgYXMgbnAKaW1wb3J0IHRmbGVhcm4KCiMgRG93bmxvYWQgdGhlIFRpdGFuaWMgZGF0YXNldApmcm9tIHRmbGVhcm4uZGF0YXNldHMgaW1wb3J0IHRpdGFuaWMKdGl0YW5pYy5kb3dubG9hZF9kYXRhc2V0KCd0aXRhbmljX2RhdGFzZXQuY3N2JykKCiMgTG9hZCBDU1YgZmlsZSwgaW5kaWNhdGUgdGhhdCB0aGUgZmlyc3QgY29sdW1uIHJlcHJlc2VudHMgbGFiZWxzCmZyb20gdGZsZWFybi5kYXRhX3V0aWxzIGltcG9ydCBsb2FkX2NzdgpkYXRhLCBsYWJlbHMgPSBsb2FkX2NzdigndGl0YW5pY19kYXRhc2V0LmNzdicsIHRhcmdldF9jb2x1bW49MCwKICAgICAgICAgICAgICAgICAgICAgICAgY2F0ZWdvcmljYWxfbGFiZWxzPVRydWUsIG5fY2xhc3Nlcz0yKQoKIyBQcmVwcm9jZXNzaW5nIGZ1bmN0aW9uCmRlZiBwcmVwcm9jZXNzKGRhdGEsIGNvbHVtbnNfdG9faWdub3JlKToKICAgICMgU29ydCBieSBkZXNjZW5kaW5nIGlkIGFuZCBkZWxldGUgY29sdW1ucwogICAgZm9yIGlkIGluIHNvcnRlZChjb2x1bW5zX3RvX2lnbm9yZSwgcmV2ZXJzZT1UcnVlKToKICAgICAgICBbci5wb3AoaWQpIGZvciByIGluIGRhdGFdCiAgICBmb3IgaSBpbiByYW5nZShsZW4oZGF0YSkpOgogICAgICAjIENvbnZlcnRpbmcgJ3NleCcgZmllbGQgdG8gZmxvYXQgKGlkIGlzIDEgYWZ0ZXIgcmVtb3ZpbmcgbGFiZWxzIGNvbHVtbikKICAgICAgZGF0YVtpXVsxXSA9IDEuIGlmIGRhdGFbaV1bMV0gPT0gJ2ZlbWFsZScgZWxzZSAwLgogICAgcmV0dXJuIG5wLmFycmF5KGRhdGEsIGR0eXBlPW5wLmZsb2F0MzIpCgojIElnbm9yZSAnbmFtZScgYW5kICd0aWNrZXQnIGNvbHVtbnMgKGlkIDEgJiA2IG9mIGRhdGEgYXJyYXkpCnRvX2lnbm9yZT1bMSwgNl0KCiMgUHJlcHJvY2VzcyBkYXRhCmRhdGEgPSBwcmVwcm9jZXNzKGRhdGEsIHRvX2lnbm9yZSkKCiMgQnVpbGQgbmV1cmFsIG5ldHdvcmsKbmV0ID0gdGZsZWFybi5pbnB1dF9kYXRhKHNoYXBlPVtOb25lLCA2XSkKbmV0ID0gdGZsZWFybi5mdWxseV9jb25uZWN0ZWQobmV0LCAzMikKbmV0ID0gdGZsZWFybi5mdWxseV9jb25uZWN0ZWQobmV0LCAzMikKbmV0ID0gdGZsZWFybi5mdWxseV9jb25uZWN0ZWQobmV0LCAyLCBhY3RpdmF0aW9uPSdzb2Z0bWF4JykKbmV0ID0gdGZsZWFybi5yZWdyZXNzaW9uKG5ldCkKCiMgRGVmaW5lIG1vZGVsCm1vZGVsID0gdGZsZWFybi5ETk4obmV0KQojIFN0YXJ0IHRyYWluaW5nIChhcHBseSBncmFkaWVudCBkZXNjZW50IGFsZ29yaXRobSkKbW9kZWwuZml0KGRhdGEsIGxhYmVscywgbl9lcG9jaD0xMCwgYmF0Y2hfc2l6ZT0xNiwgc2hvd19tZXRyaWM9VHJ1ZSkKCiMgTGV0J3MgY3JlYXRlIHNvbWUgZGF0YSBmb3IgRGlDYXByaW8gYW5kIFdpbnNsZXQKZGljYXByaW8gPSBbMywgJ0phY2sgRGF3c29uJywgJ21hbGUnLCAxOSwgMCwgMCwgJ04vQScsIDUuMDAwMF0Kd2luc2xldCA9IFsxLCAnUm9zZSBEZVdpdHQgQnVrYXRlcicsICdmZW1hbGUnLCAxNywgMSwgMiwgJ04vQScsIDEwMC4wMDAwXQojIFByZXByb2Nlc3MgZGF0YQpkaWNhcHJpbywgd2luc2xldCA9IHByZXByb2Nlc3MoW2RpY2FwcmlvLCB3aW5zbGV0XSwgdG9faWdub3JlKQojIFByZWRpY3Qgc3Vydml2aW5nIGNoYW5jZXMgKGNsYXNzIDEgcmVzdWx0cykKcHJlZCA9IG1vZGVsLnByZWRpY3QoW2RpY2FwcmlvLCB3aW5zbGV0XSkKcHJpbnQoIkRpQ2FwcmlvIFN1cnZpdmluZyBSYXRlOiIsIHByZWRbMF1bMV0pCnByaW50KCJXaW5zbGV0IFN1cnZpdmluZyBSYXRlOiIsIHByZWRbMV1bMV0pCmBgYAoKIyMgQnVpbGQgZGV0YWlscwoKVGhpcyB3YXMgcnVuIGluIGEgbG9jYWwgUiBzZXNzaW9uIHRvIHN0YXJ0IHVwIHRoaXMgUlN0dWRpbyBpbnN0YW5jZSB3aXRoIHRoZSByaWdodCBsaWJyYXJpZXMgaW5zdGFsbGVkOgoKYGBgcgpsaWJyYXJ5KGdvb2dsZUNvbXB1dGVFbmdpbmVSKQoKIyMgbWFrZSBhbiBSU3R1ZGlvIGluc3RhbmNlIHRvIGJhc2UgdXBvbgp2bSA8LSBnY2Vfdm0odGVtcGxhdGUgPSAicnN0dWRpbyIsIAogICAgICAgICAgICAgbmFtZSA9ICJyLWRhdGFsYWItYnVpbGQiLCAKICAgICAgICAgICAgIHVzZXJuYW1lID0gIm1hcmsiLCBwYXNzd29yZCA9ICJtYXJrMTIzNCIsIAogICAgICAgICAgICAgcHJlZGVmaW5lZF90eXBlID0gIm4xLXN0YW5kYXJkLTEiKQoKIyMgb25jZSBSU3R1ZGlvIGxvYWRlZCBhdCB0aGUgSVAsIGJ1aWxkIHRoZSBEb2NrZXJmaWxlIGJlbG93IG9uIGluc3RhbmNlCiMjIHRoaXMgdGFrZXMgYSB3aGlsZQpkb2NrZXJfYnVpbGQodm0sIGRvY2tlcmZvbGRlciA9IGdldF9kb2NrZXJmb2xkZXIoImNsb3VkRGF0YUxhYlIiKSwgbmV3X2ltYWdlID0gInItZGF0YWxhYiIpCgoKIyMgc2VuZCB0byB0aGUgQ29udGFpbmVyIFJlZ2lzdHJ5CmdjZV9wdXNoX3JlZ2lzdHJ5KHZtLCBzYXZlX25hbWUgPSAiZGF0YWxhYi1yLWltYWdlIiwgaW1hZ2VfbmFtZSA9ICJyLWRhdGFsYWIiKQoKIyMgQ2FuIG5vdyBsYXVuY2ggaW5zdGFuY2VzIHVzaW5nIHRoaXMgaW1hZ2UgdmlhOgp2bTIgPC0gZ2NlX3ZtKHRlbXBsYXRlID0gInJzdHVkaW8iLCAKICAgICAgICAgICAgICBuYW1lID0gInItZGF0YWxhYiIsIAogICAgICAgICAgICAgIHByZWRlZmluZWRfdHlwZSA9ICJuMS1zdGFuZGFyZC0xIiwgCiAgICAgICAgICAgICAgZHluYW1pY19pbWFnZSA9IGdjZV90YWdfY29udGFpbmVyKCJkYXRhbGFiLXIiKSwgCiAgICAgICAgICAgICAgdXNlcm5hbWUgPSAibWFyayIsIHBhc3N3b3JkID0gIm1hcmsxMjM0IikKCmBgYAoKVGhlIERvY2tlcmZpbGUgdXNlZCBpcyBiZWxvdzoKCmBgYApGUk9NIHJvY2tlci9oYWRsZXl2ZXJzZQpNQUlOVEFJTkVSIE1hcmsgRWRtb25kc29uIChyQHN1bmhvbG8uY29tKQoKIyBpbnN0YWxsIGNyb24gYW5kIG5hbm8gYW5kIHRlbnNvcmZsb3cgYW5kIHRmbGVhcm4KUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSBcCiAgICBjcm9uIG5hbm8gXAogICAgcHl0aG9uLXBpcCBweXRob24tZGV2IFwKICAgICYmIHBpcCBpbnN0YWxsIG51bXB5IFwKICAgICYmIHBpcCBpbnN0YWxsIHBhbmRhcyBcCiAgICAmJiBleHBvcnQgVEZfQklOQVJZX1VSTD1odHRwczovL3N0b3JhZ2UuZ29vZ2xlYXBpcy5jb20vdGVuc29yZmxvdy9saW51eC9jcHUvdGVuc29yZmxvdy0wLjExLjAtY3AyNy1ub25lLWxpbnV4X3g4Nl82NC53aGwgXAogICAgJiYgcGlwIGluc3RhbGwgLS11cGdyYWRlICRURl9CSU5BUllfVVJMIFwKICAgICYmIHBpcCBpbnN0YWxsIGdpdCtodHRwczovL2dpdGh1Yi5jb20vdGZsZWFybi90ZmxlYXJuLmdpdCBcCiAgICAmJiBwaXAgaW5zdGFsbCBjeXRob24gXAogICAgJiYgcGlwIGluc3RhbGwgZmVhdGhlci1mb3JtYXQgXAogICAgIyMgY2xlYW4gdXAKICAgICYmIGFwdC1nZXQgY2xlYW4gXCAKICAgICYmIHJtIC1yZiAvdmFyL2xpYi9hcHQvbGlzdHMvIFwgCiAgICAmJiBybSAtcmYgL3RtcC9kb3dubG9hZGVkX3BhY2thZ2VzLyAvdG1wLyoucmRzCiAgICAKIyMgSW5zdGFsbCBwYWNrYWdlcyBmcm9tIENSQU4KUlVOIGluc3RhbGwyLnIgLS1lcnJvciBcIAogICAgLXIgJ2h0dHA6Ly9jcmFuLnJzdHVkaW8uY29tJyBcCiAgICBnb29nbGVBdXRoUiBnb29nbGVBbmFseXRpY3NSIHNlYXJjaENvbnNvbGVSIGdvb2dsZUNsb3VkU3RvcmFnZVIgYmlnUXVlcnlSIGh0bWx3aWRnZXRzIGZlYXRoZXIgclB5dGhvbiBcCiAgICAjIyBpbnN0YWxsIEdpdGh1YiBwYWNrYWdlcwogICAgJiYgUnNjcmlwdCAtZSAiZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKGMoJ01hcmtFZG1vbmRzb24xMjM0L3lvdXR1YmVBbmFseXRpY3NSJywgJ01hcmtFZG1vbmRzb24xMjM0L2dvb2dsZUlEJywgJ01hcmtFZG1vbmRzb24xMjM0L2dvb2dsZUF1dGhSJykpIiBcCiAgICAmJiBSc2NyaXB0IC1lICJkZXZ0b29sczo6aW5zdGFsbF9naXRodWIoYygnYm5vc2FjL2Nyb25SJykpIiBcCiAgICAmJiBSc2NyaXB0IC1lICJkZXZ0b29sczo6aW5zdGFsbF9naXRodWIoYygncnN0dWRpby90ZW5zb3JmbG93JykpIiBcCiAgICAjIyBjbGVhbiB1cAogICAgJiYgcm0gLXJmIC90bXAvZG93bmxvYWRlZF9wYWNrYWdlcy8gL3RtcC8qLnJkcyBcCgpgYGAKCgo=