{"action":"created","comment":{"url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/pulls/comments/198692534","pull_request_review_id":132666406,"id":198692534,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDE5ODY5MjUzNA==","diff_hunk":"@@ -0,0 +1,709 @@\n+:author: Adam Richie-Halford\n+:email: richiehalford@gmail.com\n+:institution: University of Washington, Seattle, WA\n+\n+:author: Ariel Rokem\n+:email: arokem@gmail.com\n+:institution: University of Washington, Seattle, WA\n+\n+:bibliography: mybib\n+\n+------------------------------------------------------------------\n+Cloudknot: A Python Library to Run your Existing Code on AWS Batch\n+------------------------------------------------------------------\n+\n+.. class:: abstract\n+\n+   We introduce Cloudknot, a software library that simplifies\n+   cloud-based distributed computing by programmatically executing\n+   user-defined functions (UDFs) in AWS Batch. It takes as input\n+   a Python function, packages it as a container, creates all the\n+   necessary AWS constituent resources to submit jobs, monitors their\n+   execution and gathers the results, all from within the Python\n+   environment. Cloudknot minimizes the cognitive load of learning a\n+   new API by introducing only one new object and using the familiar\n+   :code:`map` method. It overcomes limitations of previous similar\n+   libraries, such as Pywren, that runs UDFs on AWS Lambda, because most\n+   data science workloads exceed the AWS Lambda limits on execution\n+   time, RAM, and local storage.\n+\n+.. class:: keywords\n+\n+   Cloud computing, Amazon AWS\n+\n+\n+Introduction\n+------------\n+\n+In the quest to minimize time-to-first-result, data scientists are\n+increasingly turning to cloud-based distributed computing with\n+commercial vendors like Amazon Web Services (AWS). Cloud computing\n+platforms have the advantage of linear scalability: users can access\n+limitless computing resources to meet the demands of their computational\n+workloads. At the same time they offer elasticity: resources are\n+provisioned as-needed and can be decomissioned when they are no\n+longer needed. In data-intensive research scenarios in which large\n+computational workloads are coupled with large amounts of data this\n+could, in principle, offer substantial speedups.\n+\n+But the complexity and learning curve associated with a transition to\n+cloud computing make it inaccessible to beginners. This transition cost\n+has been improving. For example, Dask :cite:`Rocklin2015-ra` used to\n+be difficult to run in parallel but it is now more accessible, thanks\n+in part to tools such as dask-ec2 :cite:`dask-ec2` and kubernetes/helm\n+:cite:`helm`. Yet despite these improvements, the cloud remains\n+inaccessible to many researchers who have not had previous exposure to\n+distributed computing.\n+\n+A number of Python libraries have sought to close this gap\n+by allowing users to interact seamlessly with AWS resources\n+from within their Python environment. For example, Cottoncandy\n+allows users to store and access numpy array data on Amazon S3\n+:cite:`anwar_o_nunez_elizalde_2017_1034342`. Pywren :cite:`jonas2017`\n+enables users to run their existing Python code on AWS Lambda,\n+providing convenient distributed execution for jobs that fall within\n+the limits of this service [#]_. These limitations are impractical\n+for many data-oriented workloads, which require more RAM and local\n+storage, longer compute times, and complex dependencies. The AWS Batch\n+service offers a platform for workloads with these requirements.\n+Batch dynamically provisions AWS resources based on the volume and\n+requirements of user-submitted jobs. Instead of provisioning and\n+managing their own batch computing jobs, users specify job constraints,\n+such as the amount of memory required for a single job, and the number\n+of jobs. AWS Batch manages the job distribution to satisfy those\n+constraints. The user can optionally constrain the cost by using\n+Amazon EC2 Spot Instances :cite:`ec2-spot-instances` and specifying a\n+bid percentage [#]_.\n+\n+.. [#] Current limits include a maximum of 300 seconds of execution\n+       time, 1.5 GB of RAM, 512 MB of local storage, and no root access.\n+\n+.. [#] The bid percentage is the maximum price, expressed as a percentage\n+       of the on-demand EC2 instance price, with which to bid on unused\n+       EC2 capacity.\n+\n+One of the main advantages of Batch, relative to the provisioning of\n+your own compute instances is that it abstracts away the exact details\n+of the infrastructure that is needed, offering instead relatively\n+straight-forward abstractions:\n+\n+- a *job*, which is an atomic, independent task to repeat on multiple\n+  inputs, encapsulated in a linux executable, a bash script or a Docker\n+  container,\n+\n+- a *job definition*, which connects the job with the compute resources\n+  it requires,\n+\n+- a *compute environment*, which defines the configuration of the\n+  computational resources needed, such as number of processors, or\n+  amount of RAM,\n+\n+- a *job queue*, where jobs reside until they are run in a compute\n+  environment.\n+\n+While Batch provides useful functional abstractions for processing data\n+in bulk, the user interface provided through the AWS web console still\n+resists automation, requires learning many of the terms that control\n+its execution and does not facilitate scripting and/or reproducibility\n+:cite:`batch-getting-started`. The AWS Python API offers a programming\n+interface that can control the execution of computational tasks in AWS\n+Batch, but it is not currently designed to offer an accessible single\n+point of access to these resources.\n+\n+Here, we introduce a new Python library with support for Python 2.7\n+and 3.5+: Cloudknot :cite:`cloudknot-docs` :cite:`cloudknot-repo`,\n+that launches Python functions as jobs on the AWS Batch service,\n+thereby lifting these limitations. Rather than introducing its own\n+set of terms and abstractions, Cloudknot attempts to mimic Pythons\n+concurrent futures’ :code:`Executor` objects. Users of Cloudknot have to\n+familiarize themselves with one new object: the :code:`Knot`. While some\n+of its functionality will initially be new to users of Cloudknot (e.g.,\n+the way that resources on AWS are managed), its :code:`map` method\n+should be familiar to most Python users.\n+\n+The next section discusses Cloudknot's approach to parallelism and the\n+API section describes Cloudknot's user interface. In the Examples\n+section, we demonstrate a few of Cloudknot's use cases, including\n+examples with data in the hundreds of GB to TB range. We then summarize\n+the the trade-offs between performance and accessibility in the Conclusion.\n+\n+\n+Design\n+------\n+\n+The primary object in Cloudknot is the :code:`Knot`, which employs the\n+single program, multiple data (SPMD) paradigm to achieve parallelism.\n+In this section, we describe Cloudknot's approach to establishing the\n+single program (SP) and managing the multiple data (MD). :code:`Knot`'s\n+user-facing API and interactions with cloud-based resources are depicted\n+in Figure :ref:`fig.workflow`.\n+\n+.. figure:: figures/cloudknot_workflow.pdf\n+\n+   Cloudknot's SPMD workflow. The left two columns depict steps\n+   Cloudknot takes to create the single program (SP). The right column\n+   depicts Cloudknot's management of the multiple data (MD). Blue\n+   rounded squares represent components of Cloudknot's user-facing\n+   API. Yellow circles represent AWS resources. Grey document shapes\n+   represent containers, templates, or data used to communicate with\n+   cloud resources.\n+   :label:`fig.workflow`\n+\n+\n+Single Program (SP)\n+~~~~~~~~~~~~~~~~~~~\n+\n+:code:`Knot` creates the single program on initialization, taking a\n+user-defined function (UDF) as input and wrapping it in a command line\n+interface (CLI), which downloads data from an Amazon Simple Storage\n+Service (S3) bucket specified by an input URL. The UDF is also wrapped in\n+a Python decorator that sends its output back to an S3 bucket. So in\n+total, the resulting command line program downloads input data from S3,\n+executes the UDF, and sends output back to S3. :code:`Knot` then packages\n+the CLI, along with its dependencies, into a Docker container. The\n+container is uploaded into the Amazon Elastic Container Registry (ECR).\n+Cloudknot's use of Docker allows it to handle non-trivial software\n+and data dependencies (see examples below). This is because Docker\n+provides a consistent and isolated environment, allowing complete\n+control over the software dependencies of a particular application, and\n+near-immediate deployment of these dependencies :cite:`Boettiger14`.\n+\n+Separately, :code:`Knot` uses an AWS CloudFormation template to create\n+the AWS resources required by AWS Batch [#]_. :code:`Knot` passes the\n+location of the Docker container on AWS ECR to its job definition\n+so that all jobs execute the SP. The user may ask :code:`Knot` to\n+restrict its compute environment to only certain instance types (e.g.\n+``c4.2xlarge``) or may choose a specific Amazon Machine Image (AMI)\n+to be loaded on each compute resource. Or they may simply request a\n+minimum, desired, and maximum number of virtual CPUs and let AWS Batch\n+select and manage the EC2 instances.\n+\n+.. [#] The required resources are\n+\n+       - AWS Identity and Access Management (IAM) Roles\n+\n+         - a batch service IAM role to allow AWS Batch to make calls to\n+           other AWS services on the user's behalf;\n+\n+         - an Elastic Container Service (ECS) instance role to be\n+           attached to each container instance when it is launched;\n+\n+         - an Elastic Cloud Compute (EC2) Spot Fleet role to allow Spot\n+           Fleet to bid on, launch, and terminate instances if the user\n+           chooses to use Spot Fleet instances instead of dedicated EC2\n+           instances;\n+\n+       - an AWS Virtual Private Cloud (VPC) with subnets and a security\n+         group;\n+\n+       - an AWS Batch job definition specifying the job to be run;\n+\n+       - an AWS Batch job queue that holds jobs until scheduled into a\n+         compute environment;\n+\n+       - and an AWS Batch compute environment, which is a set of compute\n+         resources that will be used to run jobs.\n+\n+:code:`Knot` uses job definition and compute environment\n+defaults conservative enough to run most simple jobs, with the goal of\n+minimizing errors due to insufficient resources. The casual user may\n+never need to concern themselves with selecting an instance type or\n+specifying an AMI. Users who want to minimize costs by specifying the\n+minimum sufficient resources or users who need additional resources for\n+intensive jobs can control their jobs' memory requirements, instance\n+types, or AMIs. This might be necessary if the jobs require special\n+hardware (e.g. GPGPU computing) or if the user wants more fine-grained\n+control over which resources are launched.\n+\n+One of the most complex aspects of AWS is its permissions model. Here,\n+we assume that the user has the permissions needed to run AWS Batch\n+in the console. We also provide users with the minimal necessary\n+permissions in the documentation.\n+\n+Finally, :code:`Knot` exposes AWS resource tags :cite:`aws-tags` to\n+the user, allowing the user to assign metadata key-value pairs to each\n+created resource. This facilitates management of Cloudknot generated\n+resources and allows the user to quickly recognize Cloudknot resources\n+in the AWS console.\n+\n+\n+Multiple Data (MD)\n+~~~~~~~~~~~~~~~~~~\n+\n+To operate on the MD, the :code:`Knot.map()` method uses a simple for\n+loop to iterate over the outer-most dimension of the input array and\n+assign each element to a separate AWS Batch job. It serializes each\n+element and sends it to S3, organizing the data in a schema that is\n+internally consistent with the expectations of the CLI. It then launches\n+an AWS Batch array job (or optionally, separate individual Batch jobs)\n+to execute the program over these data. When run, each batch job selects\n+its own input, executes the UDF, and returns its serialized output to\n+S3.\n+\n+If the instances and S3 bucket are in the same region, then users do not\n+pay for transfer from S3 to the EC2 instances and back. They pay only\n+for transfer out of the data center (i.e. from their local machine to\n+S3 and back). Transfer speed within the data center also outperforms\n+transfer speed between data centers. So it is both less costly and more\n+performant to colocate the Cloudknot S3 bucket with the EC2 instances.\n+Cloudknot includes utility functions to change regions and S3 buckets\n+for this purpose.\n+\n+In the last step, :code:`Knot.map()` downloads the output from S3\n+and returns it to the user. Since AWS Batch allows arbitrarily long\n+execution times, :code:`Knot.map()` returns a list of futures for\n+the results, mimicking Python's concurrent futures' :code:`Executor`\n+objects. If the results are too large to fit on the local machine, the\n+user may augment their UDF to write results to S3 or some other remote\n+storage and then simply return the address at which to retrieve the\n+result.\n+\n+Under the hood, :code:`Knot.map()` creates a\n+:code:`concurrent.futures.ThreadPoolExecutor` instance where each\n+thread intermittently queries S3 for its returned output. The results\n+are encapsulated in :code:`concurrent.futures.Future` objects, allowing\n+asynchronous execution. The user can use :code:`Future` methods such\n+as :code:`done()` and :code:`result()` to test for success or view the\n+results. This also allows them to attach callbacks to the results using\n+the :code:`add_done_callback()` method. For example a user may want to\n+perform a local reduction on results generated on AWS Batch.\n+\n+\n+API\n+---\n+\n+The above interactions with AWS resources are hidden from the user.\n+The advanced and/or curious user can customize the Docker container or\n+CloudFormation template. But for most use cases, the user interacts\n+only with the :code:`Knot` object. This section provides an example\n+calculating the value of :math:`\\pi` as a pedagogical introduction to\n+the Cloudknot API.\n+\n+We first import Cloudknot and define the function that we would like to\n+run on AWS Batch. Cloudknot uses the `pipreqs` :cite:`pipreqs` package\n+to generate the requirements file used to install dependencies in the\n+Docker container on AWS ECR. So all required packages must be imported\n+in the source code of the UDF itself.\n+\n+.. code-block:: python\n+\n+   import cloudknot as ck\n+\n+   def monte_pi_count(b):\n+       import numpy as np\n+       x = np.random.rand(n)\n+       y = np.random.rand(n)\n+       return np.count_nonzero(x * x + y * y <= 1.0)\n+\n+Next, we create a :code:`Knot` instance and pass the UDF using the\n+:code:`func` argument. The :code:`name` argument affects the names of\n+resources created on AWS. For example, in this case, the created job\n+definition would be named ``pi-calc-cloudknot-job-definition``:\n+\n+.. code-block:: python\n+\n+   knot = ck.Knot(name='pi-calc', func=monte_pi_count)\n+\n+We submit jobs with the :code:`Knot.map()` method:\n+\n+.. code-block:: python\n+\n+   import numpy as np  # for np.linspace\n+   n_jobs, n_samples = 1000, 100000000\n+   args = np.ones(n_jobs, dtype=np.int32) * n_samples\n+   future = knot.map(args)\n+\n+This will launch an AWS Batch array job with 20 child jobs, one for each\n+element of the input array. Cloudknot can accomodate functions with\n+multiple inputs by passing the :code:`map()` method a sequence of tuples\n+of input arguments and the :code:`starmap=True` argument. For example,\n+if the UDF signature were :code:`def udf(arg0, arg1)`, one could execute\n+:code:`udf` over all combinations of :code:`arg0` in ``[1, 2, 3]`` and\n+:code:`arg1` in ``['a', 'b', 'c']`` by calling\n+\n+.. code-block:: python\n+\n+   args = list(itertools.product([1, 2, 3],\n+                                 ['a', 'b', 'c']))\n+   future = knot.map(args, starmap=True)\n+\n+We can then query the result status using :code:`future.done()`\n+and retrieve the results using :code:`future.result()`, which\n+will block until results are returned unless the user passes an\n+optional :code:`timeout` argument. We can also check the status\n+of all the jobs that have been submitted with this :code:`Knot`\n+instance by inspecting the :code:`knot.jobs` property, which returns\n+a list of :code:`cloudknot.BatchJob` instances, each of which\n+has its own :code:`done` property and :code:`result()` method.\n+So in the example above, :code:`future.done()` is equivalent to\n+:code:`knot.jobs[-1].done` and :code:`future.result()` is equivalent to\n+:code:`knot.jobs[-1].result()`. In this way, users have access to AWS\n+Batch job results that they have run in past sessions.\n+\n+In this pedagogical example, we are estimating :math:`\\pi` using the\n+Monte Carlo method. :code:`Knot.map()` returns a future for an array\n+of counts of random points that fall within the circle enclosed by the\n+unit square. To get the final estimate of :math:`\\pi`, we need to sum\n+all the elements of this array and divide by four, a simple use case for\n+:code:`future.add_done_callback()`:\n+\n+.. code-block:: python\n+\n+   PI = 0.0\n+   n_total = n_samples * n_jobs\n+   def pi_from_future(future):\n+       global PI\n+       PI = 4.0 * np.sum(future.result()) / n_total\n+\n+   future.add_done_callback(pi_from_future)\n+\n+Lastly, without navigating to the AWS console, we can get a quick\n+summary of the status of all jobs submitted with this :code:`Knot` using\n+\n+.. code-block:: python\n+\n+   >>> knot.view_jobs()\n+   Job ID          Name           Status\n+   ----------------------------------------\n+   fcd2a14b...     pi-calc-0      PENDING\n+\n+\n+Examples\n+--------\n+\n+In this section, we will present a few real use cases of Cloudknot,\n+including real life uses of the software in data analysis. We will\n+start with examples that have minimal software and data dependencies,\n+and increase the complexity by adding first data dependencies and\n+subsequently complex software and resource dependencies. These and other\n+examples are available in Jupyter Notebooks in the Cloudknot repository\n+:cite:`cloudknot-examples`.\n+\n+\n+Solving differential equations\n+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n+\n+Simulations executed with Cloudknot do not have to comply with any\n+particular memory or time limitations. This is in contrast to Pywren's\n+limitations, which stem from the use of the AWS Lambda service. On\n+the other hand, Cloudknot's use of AWS Batch increases the overhead\n+associated with creating AWS resources and uploading a Docker container\n+to ECR. While this infrastructure setup time can be minimized by reusing\n+AWS resources that were created in a previous session, this setup time\n+suits use-cases for which execution time is much greater than the time\n+required to create the necessary resources on AWS.\n+\n+To demonstrate this, we used Cloudknot and Pywren to find the steady-state\n+solution to the two-dimensional heat equation by the Gauss-Seidel method\n+:cite:`templates-linear-sys`. The method chosen is suboptimal, as is the\n+specific implementation of the method, and serves only as a benchmarking tool.\n+In this unrealistic example, we wish to parallelize execution both over a range\n+of different boundary conditions and over a range of grid sizes.\n+\n+First, we hold the grid size constant at 10 x 10 and parallelize over\n+different temperature constraints on one edge of the simulation grid.\n+We investigate the scaling of job execution time as a function of\n+the size of the argument array. In Figure :ref:`fig.nargsscaling` we\n+show the execution time as a function of :math:`n_\\mathrm{args}`, the\n+length of the argument array (with both on :math:`\\log_2` scales). We\n+tested scaling using Cloudknot's default parameters and also using\n+custom parameters [#]_. Regardless of the :code:`Knot` parameters,\n+Pywren outperformed Cloudknot at all argument array sizes. Indeed,\n+Pywren appears to achieve constant scaling between :math:`2^2 \\le\n+n_\\mathrm{args} \\le 2^9`, revealing AWS Lambda's capabilities for\n+massively parallel computation. For :math:`n_\\mathrm{args} > 2^9`,\n+Pywren appears to conform to linear scaling with a constant of roughly\n+0.25. By contrast, Cloudknot exhibits noisy linear scaling for\n+:math:`n_\\mathrm{args} \\gtrapprox 2^5`, with constants of roughly 2 for\n+the custom configuration and roughly 4 for the default configuration.\n+Precise determination of these scaling constants would require more data\n+for a larger range of argument sizes.\n+\n+.. [#] Default settings are :code:`min_vcpus=0`,\n+   :code:`desired_vcpus=8`, and :code:`max_vcpus=256`. Custom settings\n+   are :code:`desired_vcpus=2048`, :code:`max_vcpus=4096`, and\n+   :code:`min_vcpus=512`. Both default and custom Cloudknot cases were also\n+   limited by the EC2 service limits for our region and account, which\n+   vary by instance type but never exceeded 200 instances.\n+\n+.. figure:: figures/nargsscaling.png\n+\n+   Execution time to find solutions of the 2D heat equation for many\n+   different temperature constraints on a 10 x 10 grid. We show\n+   execution time scaling as a function of the number of constraints\n+   for Pywren, the default Cloudknot configuration, and a Cloudknot\n+   configuration with more available vCPUs. Pywren outperforms Cloudknot\n+   in all cases. We posit that the additional overhead associated with\n+   building the Docker image, along with EC2 service limits limited\n+   Cloudknot's throughput.\n+   :label:`fig.nargsscaling`\n+\n+For the data in Figure :ref:`fig.syssizescaling`, we still parallelized\n+over only five different temperature constraints, but we did so\n+for increasing grid sizes. Grid sizes beyond 125 x 125 required an\n+individual job execution time that exceeded the AWS Lambda execution\n+limit of 300s. So Pywren was unable to compute on the larger grid sizes.\n+There is a crossover point around 80 x 80 where Cloudknot outperforms\n+Pywren. Before this point, AWS Lambda's fast triggering and continuous\n+scaling surpass the AWS Batch queueing system. Conversely, past this\n+point the compute power of each individual EC2 instance launched by\n+AWS Batch is enough to compensate for the difference in queueing\n+performance.\n+\n+.. figure:: figures/syssizescaling.png\n+\n+   Execution time to find five solutions to the 2D heat equation\n+   as a function of grid size. Grid sizes above 125 x 125 exceed\n+   Pywren's limit on execution time of 300 sec. The cross-over point at\n+   around 80 x 80 occurs when it is more beneficial to have the more\n+   powerful EC2 instances provided by Cloudknot with AWS Batch than the\n+   massively parallel execution provided by Pywren with AWS Lambda.\n+   :label:`fig.syssizescaling`\n+\n+Taken together, Figures :ref:`fig.nargsscaling` and\n+:ref:`fig.syssizescaling` indicate that if a UDF can be executed within\n+AWS Lambda's five minute execution time and 1.5 GB memory limitations\n+and does not have software and data dependencies that would prohibit\n+using Pywren, it should be parallelized on AWS using Pywren rather than\n+Cloudknot. However, when simulations are too large or complicated to\n+fit well into Pywren's framework, Cloudknot is the appropriate tool to\n+simplify their distributed execution on AWS. Pywren's authors note that\n+the AWS Lambda limits are not fixed and are likely to improve. We agree\n+and note only that EC2 and AWS Batch limitations are likely to improve\n+as well. So long as there exists a computational regime between the two\n+sets of limitations, Cloudknot can offer researchers a simple platform\n+with which to execute their scientific workloads.\n+\n+\n+Data Dependencies: Analysis of magnetic resonance imaging data\n+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n+\n+Because Cloudknot is run on the standard AWS infrastructure, it allows\n+specification of complex and large data dependencies. Dependency of\n+individual tasks on data can be addressed by preloading the data into\n+object storage on S3, and then downloading of individual bits of data\n+needed to complete each task into the individual worker machines.\n+\n+As an example, we implemented a pipeline for analysis of human MRI\n+data. Human MRI data is a good use-case for a system such as Cloudknot\n+because much of the analysis proceeds in a parallel manner. Even for\n+large datasets with multiple subjects, a large part of the analysis is\n+conducted first at the level of each individual brain. Aggregation of\n+information across brains is typically done after many preprocessing and\n+analysis stages at the level of each individual subject.\n+\n+For example, diffusion MRI (dMRI) is a method that measures the\n+properties of the connections between different regions of the brain.\n+Over the last few decades, this method has been used to establish the\n+role of these connections in many different cognitive and behavioral\n+properties of the human brain, and to delineate the role that the\n+biology of these connections plays in neurological and psychiatric\n+disorders :cite:`Wandell2016-ms`. Because of the interest in these\n+connections, several large consortium efforts for data collection have\n+aggregated large datasets of human dMRI data from multiple different\n+subjects :cite:`Glasser2016-qk`.\n+\n+In the analysis of dMRI data, the first few steps are done at the\n+individual level. For example, the selection of regions of interest\n+within each image and the denoising and initial modeling of the data\n+can all be completed at the individual level in parallel. In a previous\n+study, we implemented a dMRI analysis pipeline that contained these\n+steps and we used it to compare several Big Data systems as a basis for\n+efficient scientific image processing :cite:`mehta2017comparative`.\n+Here, we reused this pipeline. This allows us to compare the performance\n+of Cloudknot directly against the performance of several alternative\n+systems for distributed computing that were studied in our previous\n+work: Spark :cite:`Zaharia2010-rp`, Myria :cite:`Halperin2014-vu` and\n+Dask.\n+\n+In Cloudknot, we used the reference implementation from this\n+previous study written in Python and using methods from Dipy\n+:cite:`Garyfallidis2014`, which are implemented in Python and Cython.\n+In contrast to the other systems, essentially no changes had to be made\n+to the reference implementation when using Cloudknot, except to download\n+the part of the data required for an individual job from S3 into the\n+individual instances. Parallelization was implemented only at the level\n+of individual subjects, and a naive serial approach was taken at the\n+level of each individual.\n+\n+We found that with a small number of subjects this reference\n+implementation is significantly slower with Cloudknot compared with the\n+parallelized implementation in these other systems. But the relative\n+advantage of these systems diminshes substantially as the number of\n+subjects grows larger (Figure :ref:`fig.mribenchmark`), and the benefits\n+of parallelization across subjects starts to be more substantial. With\n+the largest number of subjects used, Cloudknot processed 25 subjectss\n+10% slower than Spark and Myria; however, it was 25% slower than Dask,\n+the fastest of the tools that we previously benchmarked.\n+\n+There are two important caveats to this analysis: the first is that\n+the analysis with the other systems was conducted on a cluster with a\n+fixed allocation of 16 nodes (each node was an AWS r3.2xlarge instance\n+with 8 vCPUs). The benchmark code does run faster with more nodes added\n+to the cluster :cite:`mehta2017comparative`. The largest amount of\n+data that was benchmarked was for 25 subjects, corresponding to 105 GB\n+of input data and a maximum of 210 GB of intermediate data. Notably,\n+even for this amount of data, Cloudknot deployed only two instances\n+of the r4.16xlarge type -- each with 64 vCPUs and 488 GB of RAM. In\n+terms of RAM, this is the equivalent of a 16 node cluster of r3.2xlarge\n+instances, but the number of CPUs deployed to the task is about half. As\n+shown above, additional scaling can be reached in Cloudknot by expanding\n+the cluster with :code:`min_vcpus`. The second caveat to these results\n+is that that the comparison timing data for the other systems is from\n+early 2017, and these systems may have evolved and improved since.\n+\n+.. figure:: figures/mri_benchmark.png\n+\n+   MRI analysis pipeline with data requirements. A comparison of\n+   Cloudknot performance to other parallel computing systems: Dask,\n+   Spark and Myria, based on a previous benchmark\n+   :cite:`mehta2017comparative`. Cloudknot is orders of magnitude\n+   slower for small amounts of data, but reaches within 10-25 %\n+   of these systems' performance for large amounts of data.\n+   :label:`fig.mribenchmark`\n+\n+\n+Data and software dependencies: analysis of microscopy data\n+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n+\n+The MRI example demonstrates the use of a large and rather complex\n+dataset. In addition, Cloudknot can manage complex software\n+dependencies. Researchers in cell biology, molecular engineering and\n+nano-engineering are also increasingly relying on methods that generate\n+large amounts of data and on analysis that requires large amounts\n+of computing power. For example, in experiments that evaluate the\n+mobility of synthetically designed nano-particles in biological tissue\n+:cite:`Nance2017-xp`, :cite:`Nance2012-nu`, researchers may record\n+movies of microscopic images of the tissue at high spatial and temporal\n+resolution and with a wide field of view, resulting in large amounts of\n+image data, often stored in multiple large files. These collections often\n+reach several TB in size.\n+\n+To analyze these experiments, researchers rely on software implemented\n+in ImageJ for particle segmentation and tracking, such as TrackMate\n+:cite:`Tinevez2017-ti`. However, when applied to large amounts of data,\n+using TrackMate serially in each experiment can be prohibitively time\n+consuming. One solution is to divide the movies spatially into smaller\n+field of view movies, and analyze them in parallel.\n+\n+ImageJ and Trackmate are written in Java and can be scripted using\n+Jython. This implies complex software dependencies, because the software\n+requires installation of the ImageJ Jython runtime. Because Cloudknot\n+relies on docker, this installation can be managed using the command\n+line interface (i.e. :code:`wget`). Once a docker image is created that\n+contains the software dependencies for a particular analysis, Python\n+code can be written on top of it to execute system calls that will run\n+the analysis. This approach was recently implemented in :cite:`Curtis2018`.\n+\n+Additional complexity in this use-case is caused by the volume of data.\n+Because of the data size in this case, a custom AMI had to be created\n+from the AWS Batch AMI, that includes a larger volume (Batch AMI volumes\n+are limited to 30 GB of disk-space).\n+\n+Conclusion\n+----------\n+\n+Cloudknot simplifies cloud-based distributed computing by\n+programmatically executing UDFs in AWS Batch. This lowers the barrier to\n+cloud computing and allows users to launch massive workloads at\n+scale from within their Python environment.\n+\n+We have demonstrated Cloudknot's ability to handle complicated data and\n+software dependencies using real-world examples from neuroimaging and\n+microscopy. And we've included analyses that show Cloudknot's performance\n+compared to other distributed computing frameworks. On one hand, scaling\n+charts like the ones in Figures :ref:`fig.nargsscaling`,\n+:ref:`fig.syssizescaling`, and :ref:`fig.mribenchmark` are important because\n+they show potential users the relative cost in execution time of using\n+Cloudknot in comparison to other distributed computing platforms.\n+\n+On the other hand, the timing results in this paper, indeed most\n+benchmark results in general, measure the bare execution time, capturing\n+only partial information about the time that it takes to reach a\n+computational result. This is because all the distributed systems\n+currently available require some amount of systems administration and\n+often incur non-trivial setup time. In addition, most of the existing\n+systems currently require some amount of rewriting of the original\n+code :cite:`mehta2017comparative`. We believe an increase in execution\n+time may be acceptable in some situations, if it can reduce the time\n+spent on systems administration, setup and particularly on rewriting","path":"papers/adam_richie-halford/adam_richie-halford.rst","position":629,"original_position":629,"commit_id":"df20742f793f8e1043cae5946d8e93051ae5941e","original_commit_id":"df20742f793f8e1043cae5946d8e93051ae5941e","user":{"login":"dalippa","id":7816645,"node_id":"MDQ6VXNlcjc4MTY2NDU=","avatar_url":"https://avatars0.githubusercontent.com/u/7816645?v=4","gravatar_id":"","url":"https://api.github.com/users/dalippa","html_url":"https://github.com/dalippa","followers_url":"https://api.github.com/users/dalippa/followers","following_url":"https://api.github.com/users/dalippa/following{/other_user}","gists_url":"https://api.github.com/users/dalippa/gists{/gist_id}","starred_url":"https://api.github.com/users/dalippa/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dalippa/subscriptions","organizations_url":"https://api.github.com/users/dalippa/orgs","repos_url":"https://api.github.com/users/dalippa/repos","events_url":"https://api.github.com/users/dalippa/events{/privacy}","received_events_url":"https://api.github.com/users/dalippa/received_events","type":"User","site_admin":false},"body":"remove \"particularly on\". \"on\" is already implied from the earlier list","created_at":"2018-06-28T02:25:24Z","updated_at":"2018-06-28T02:30:50Z","html_url":"https://github.com/scipy-conference/scipy_proceedings/pull/406#discussion_r198692534","pull_request_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/pulls/406","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/scipy-conference/scipy_proceedings/pulls/comments/198692534"},"html":{"href":"https://github.com/scipy-conference/scipy_proceedings/pull/406#discussion_r198692534"},"pull_request":{"href":"https://api.github.com/repos/scipy-conference/scipy_proceedings/pulls/406"}}},"pull_request":{"url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/pulls/406","id":190054250,"node_id":"MDExOlB1bGxSZXF1ZXN0MTkwMDU0MjUw","html_url":"https://github.com/scipy-conference/scipy_proceedings/pull/406","diff_url":"https://github.com/scipy-conference/scipy_proceedings/pull/406.diff","patch_url":"https://github.com/scipy-conference/scipy_proceedings/pull/406.patch","issue_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/issues/406","number":406,"state":"open","locked":false,"title":"Paper: Cloudknot: A Python Library to Run your Existing Code on AWS Batch","user":{"login":"richford","id":7978135,"node_id":"MDQ6VXNlcjc5NzgxMzU=","avatar_url":"https://avatars0.githubusercontent.com/u/7978135?v=4","gravatar_id":"","url":"https://api.github.com/users/richford","html_url":"https://github.com/richford","followers_url":"https://api.github.com/users/richford/followers","following_url":"https://api.github.com/users/richford/following{/other_user}","gists_url":"https://api.github.com/users/richford/gists{/gist_id}","starred_url":"https://api.github.com/users/richford/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/richford/subscriptions","organizations_url":"https://api.github.com/users/richford/orgs","repos_url":"https://api.github.com/users/richford/repos","events_url":"https://api.github.com/users/richford/events{/privacy}","received_events_url":"https://api.github.com/users/richford/received_events","type":"User","site_admin":false},"body":"This is the first draft of \"Cloudknot: A Python Library to Run your Existing Code on AWS Batch\" for the Scipy 2018 Proceedings.","created_at":"2018-05-23T17:44:18Z","updated_at":"2018-06-28T02:30:50Z","closed_at":null,"merged_at":null,"merge_commit_sha":"5bed68ddb02c638308de2251a38067793abf6a08","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[{"id":902774949,"node_id":"MDU6TGFiZWw5MDI3NzQ5NDk=","url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/labels/paper","name":"paper","color":"78dfe2","default":false},{"id":229680784,"node_id":"MDU6TGFiZWwyMjk2ODA3ODQ=","url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/labels/pending-comment","name":"pending-comment","color":"bfe5bf","default":false}],"milestone":null,"commits_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/pulls/406/commits","review_comments_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/pulls/406/comments","review_comment_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/pulls/comments{/number}","comments_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/issues/406/comments","statuses_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/statuses/df20742f793f8e1043cae5946d8e93051ae5941e","head":{"label":"richford:2018-cloudknot","ref":"2018-cloudknot","sha":"df20742f793f8e1043cae5946d8e93051ae5941e","user":{"login":"richford","id":7978135,"node_id":"MDQ6VXNlcjc5NzgxMzU=","avatar_url":"https://avatars0.githubusercontent.com/u/7978135?v=4","gravatar_id":"","url":"https://api.github.com/users/richford","html_url":"https://github.com/richford","followers_url":"https://api.github.com/users/richford/followers","following_url":"https://api.github.com/users/richford/following{/other_user}","gists_url":"https://api.github.com/users/richford/gists{/gist_id}","starred_url":"https://api.github.com/users/richford/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/richford/subscriptions","organizations_url":"https://api.github.com/users/richford/orgs","repos_url":"https://api.github.com/users/richford/repos","events_url":"https://api.github.com/users/richford/events{/privacy}","received_events_url":"https://api.github.com/users/richford/received_events","type":"User","site_admin":false},"repo":{"id":129521741,"node_id":"MDEwOlJlcG9zaXRvcnkxMjk1MjE3NDE=","name":"scipy_proceedings","full_name":"richford/scipy_proceedings","owner":{"login":"richford","id":7978135,"node_id":"MDQ6VXNlcjc5NzgxMzU=","avatar_url":"https://avatars0.githubusercontent.com/u/7978135?v=4","gravatar_id":"","url":"https://api.github.com/users/richford","html_url":"https://github.com/richford","followers_url":"https://api.github.com/users/richford/followers","following_url":"https://api.github.com/users/richford/following{/other_user}","gists_url":"https://api.github.com/users/richford/gists{/gist_id}","starred_url":"https://api.github.com/users/richford/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/richford/subscriptions","organizations_url":"https://api.github.com/users/richford/orgs","repos_url":"https://api.github.com/users/richford/repos","events_url":"https://api.github.com/users/richford/events{/privacy}","received_events_url":"https://api.github.com/users/richford/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/richford/scipy_proceedings","description":"Tools used to generate the SciPy conference proceedings","fork":true,"url":"https://api.github.com/repos/richford/scipy_proceedings","forks_url":"https://api.github.com/repos/richford/scipy_proceedings/forks","keys_url":"https://api.github.com/repos/richford/scipy_proceedings/keys{/key_id}","collaborators_url":"https://api.github.com/repos/richford/scipy_proceedings/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/richford/scipy_proceedings/teams","hooks_url":"https://api.github.com/repos/richford/scipy_proceedings/hooks","issue_events_url":"https://api.github.com/repos/richford/scipy_proceedings/issues/events{/number}","events_url":"https://api.github.com/repos/richford/scipy_proceedings/events","assignees_url":"https://api.github.com/repos/richford/scipy_proceedings/assignees{/user}","branches_url":"https://api.github.com/repos/richford/scipy_proceedings/branches{/branch}","tags_url":"https://api.github.com/repos/richford/scipy_proceedings/tags","blobs_url":"https://api.github.com/repos/richford/scipy_proceedings/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/richford/scipy_proceedings/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/richford/scipy_proceedings/git/refs{/sha}","trees_url":"https://api.github.com/repos/richford/scipy_proceedings/git/trees{/sha}","statuses_url":"https://api.github.com/repos/richford/scipy_proceedings/statuses/{sha}","languages_url":"https://api.github.com/repos/richford/scipy_proceedings/languages","stargazers_url":"https://api.github.com/repos/richford/scipy_proceedings/stargazers","contributors_url":"https://api.github.com/repos/richford/scipy_proceedings/contributors","subscribers_url":"https://api.github.com/repos/richford/scipy_proceedings/subscribers","subscription_url":"https://api.github.com/repos/richford/scipy_proceedings/subscription","commits_url":"https://api.github.com/repos/richford/scipy_proceedings/commits{/sha}","git_commits_url":"https://api.github.com/repos/richford/scipy_proceedings/git/commits{/sha}","comments_url":"https://api.github.com/repos/richford/scipy_proceedings/comments{/number}","issue_comment_url":"https://api.github.com/repos/richford/scipy_proceedings/issues/comments{/number}","contents_url":"https://api.github.com/repos/richford/scipy_proceedings/contents/{+path}","compare_url":"https://api.github.com/repos/richford/scipy_proceedings/compare/{base}...{head}","merges_url":"https://api.github.com/repos/richford/scipy_proceedings/merges","archive_url":"https://api.github.com/repos/richford/scipy_proceedings/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/richford/scipy_proceedings/downloads","issues_url":"https://api.github.com/repos/richford/scipy_proceedings/issues{/number}","pulls_url":"https://api.github.com/repos/richford/scipy_proceedings/pulls{/number}","milestones_url":"https://api.github.com/repos/richford/scipy_proceedings/milestones{/number}","notifications_url":"https://api.github.com/repos/richford/scipy_proceedings/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/richford/scipy_proceedings/labels{/name}","releases_url":"https://api.github.com/repos/richford/scipy_proceedings/releases{/id}","deployments_url":"https://api.github.com/repos/richford/scipy_proceedings/deployments","created_at":"2018-04-14T13:55:37Z","updated_at":"2018-06-27T23:37:43Z","pushed_at":"2018-06-27T23:37:40Z","git_url":"git://github.com/richford/scipy_proceedings.git","ssh_url":"git@github.com:richford/scipy_proceedings.git","clone_url":"https://github.com/richford/scipy_proceedings.git","svn_url":"https://github.com/richford/scipy_proceedings","homepage":"","size":193531,"stargazers_count":0,"watchers_count":0,"language":"Jupyter Notebook","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":0,"license":{"key":"other","name":"Other","spdx_id":null,"url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":0,"open_issues":0,"watchers":0,"default_branch":"2018-cloudknot"}},"base":{"label":"scipy-conference:2018","ref":"2018","sha":"2e43deb55d94b1876aabfa5d8a4c9367e2c0ada0","user":{"login":"scipy-conference","id":4884274,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ4ODQyNzQ=","avatar_url":"https://avatars2.githubusercontent.com/u/4884274?v=4","gravatar_id":"","url":"https://api.github.com/users/scipy-conference","html_url":"https://github.com/scipy-conference","followers_url":"https://api.github.com/users/scipy-conference/followers","following_url":"https://api.github.com/users/scipy-conference/following{/other_user}","gists_url":"https://api.github.com/users/scipy-conference/gists{/gist_id}","starred_url":"https://api.github.com/users/scipy-conference/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/scipy-conference/subscriptions","organizations_url":"https://api.github.com/users/scipy-conference/orgs","repos_url":"https://api.github.com/users/scipy-conference/repos","events_url":"https://api.github.com/users/scipy-conference/events{/privacy}","received_events_url":"https://api.github.com/users/scipy-conference/received_events","type":"Organization","site_admin":false},"repo":{"id":1825629,"node_id":"MDEwOlJlcG9zaXRvcnkxODI1NjI5","name":"scipy_proceedings","full_name":"scipy-conference/scipy_proceedings","owner":{"login":"scipy-conference","id":4884274,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ4ODQyNzQ=","avatar_url":"https://avatars2.githubusercontent.com/u/4884274?v=4","gravatar_id":"","url":"https://api.github.com/users/scipy-conference","html_url":"https://github.com/scipy-conference","followers_url":"https://api.github.com/users/scipy-conference/followers","following_url":"https://api.github.com/users/scipy-conference/following{/other_user}","gists_url":"https://api.github.com/users/scipy-conference/gists{/gist_id}","starred_url":"https://api.github.com/users/scipy-conference/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/scipy-conference/subscriptions","organizations_url":"https://api.github.com/users/scipy-conference/orgs","repos_url":"https://api.github.com/users/scipy-conference/repos","events_url":"https://api.github.com/users/scipy-conference/events{/privacy}","received_events_url":"https://api.github.com/users/scipy-conference/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/scipy-conference/scipy_proceedings","description":"Tools used to generate the SciPy conference proceedings","fork":false,"url":"https://api.github.com/repos/scipy-conference/scipy_proceedings","forks_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/forks","keys_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/keys{/key_id}","collaborators_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/teams","hooks_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/hooks","issue_events_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/issues/events{/number}","events_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/events","assignees_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/assignees{/user}","branches_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/branches{/branch}","tags_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/tags","blobs_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/git/refs{/sha}","trees_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/git/trees{/sha}","statuses_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/statuses/{sha}","languages_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/languages","stargazers_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/stargazers","contributors_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/contributors","subscribers_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/subscribers","subscription_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/subscription","commits_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/commits{/sha}","git_commits_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/git/commits{/sha}","comments_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/comments{/number}","issue_comment_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/issues/comments{/number}","contents_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/contents/{+path}","compare_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/compare/{base}...{head}","merges_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/merges","archive_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/downloads","issues_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/issues{/number}","pulls_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/pulls{/number}","milestones_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/milestones{/number}","notifications_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/labels{/name}","releases_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/releases{/id}","deployments_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/deployments","created_at":"2011-05-31T10:26:48Z","updated_at":"2018-06-25T01:50:03Z","pushed_at":"2018-06-28T00:34:58Z","git_url":"git://github.com/scipy-conference/scipy_proceedings.git","ssh_url":"git@github.com:scipy-conference/scipy_proceedings.git","clone_url":"https://github.com/scipy-conference/scipy_proceedings.git","svn_url":"https://github.com/scipy-conference/scipy_proceedings","homepage":"","size":192531,"stargazers_count":111,"watchers_count":111,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":248,"mirror_url":null,"archived":false,"open_issues_count":71,"license":{"key":"other","name":"Other","spdx_id":null,"url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":248,"open_issues":71,"watchers":111,"default_branch":"2018"}},"_links":{"self":{"href":"https://api.github.com/repos/scipy-conference/scipy_proceedings/pulls/406"},"html":{"href":"https://github.com/scipy-conference/scipy_proceedings/pull/406"},"issue":{"href":"https://api.github.com/repos/scipy-conference/scipy_proceedings/issues/406"},"comments":{"href":"https://api.github.com/repos/scipy-conference/scipy_proceedings/issues/406/comments"},"review_comments":{"href":"https://api.github.com/repos/scipy-conference/scipy_proceedings/pulls/406/comments"},"review_comment":{"href":"https://api.github.com/repos/scipy-conference/scipy_proceedings/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/scipy-conference/scipy_proceedings/pulls/406/commits"},"statuses":{"href":"https://api.github.com/repos/scipy-conference/scipy_proceedings/statuses/df20742f793f8e1043cae5946d8e93051ae5941e"}},"author_association":"NONE"}}
{"actor":{"display_login":"dalippa"}}