diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 00000000..4b279e97
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,7 @@
+# GalaxyBenchmarker generated files
+logs/
+results/
+
+# Python stuff
+venv/
+.venv/
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index f31affe7..ab55a5b4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,8 @@ tools.yml
*.html
results/*
tool_test_output.*
+.vscode
+evaluation/*.png
### Autogenerated: ###
# Byte-compiled / optimized / DLL files
diff --git a/Dockerfile b/Dockerfile
index cc544d3e..d2d48373 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,8 +1,28 @@
-FROM python:3.7
+FROM python:3.10
-COPY requirements.txt ./
-RUN pip install --no-cache-dir -r requirements.txt
-RUN pip uninstall bioblend -y
-RUN git clone https://github.com/galaxyproject/bioblend.git && cd bioblend && python setup.py install
+ENV PIP_NO_CACHE_DIR=1 \
+ PIP_DISABLE_PIP_VERSION_CHECK=1
-RUN cd / && git clone https://github.com/usegalaxy-eu/workflow-testing.git
\ No newline at end of file
+RUN \
+ apt-get update \
+ && apt-get install -y \
+ rsync \
+ tini \
+ && apt-get clean \
+ && rm -rf /var/lib/apt/lists/* /tmp/*
+
+RUN pip install poetry
+
+WORKDIR /src
+COPY poetry.lock pyproject.toml /src/
+
+RUN poetry config virtualenvs.create false \
+ && poetry install --no-dev --no-interaction --no-ansi
+
+RUN git clone https://github.com/usegalaxy-eu/workflow-testing.git /workflow-testing
+
+COPY . /src/
+
+COPY scripts/entrypoint.sh /entrypoint.sh
+
+ENTRYPOINT ["/usr/bin/tini", "-v", "--", "/entrypoint.sh"]
diff --git a/Makefile b/Makefile
new file mode 100644
index 00000000..9734302d
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,36 @@
+run:
+ docker-compose up -d
+ docker-compose logs -f benchmarker
+
+run_sudo:
+ sudo -E docker-compose up -d
+ sudo docker-compose logs -f benchmarker
+
+.PHONY: run_cron
+run_cron:
+ /usr/local/bin/docker-compose up -d
+
+stop:
+ echo "Gracefull shutdown. Benchmarker has 30 seconds to save results"
+ docker-compose stop -t 30 benchmarker
+
+stop_sudo:
+ echo "Gracefull shutdown. Benchmarker has 30 seconds to save results"
+ sudo docker-compose stop -t 30 benchmarker
+
+build:
+ docker-compose build
+
+build_sudo:
+ sudo docker-compose build
+
+lint: lint_isort lint_black
+
+lint_isort:
+ poetry run isort galaxy_benchmarker
+
+lint_black:
+ poetry run black --target-version py310 galaxy_benchmarker
+
+lint_mypy:
+ poetry run mypy galaxy_benchmarker
diff --git a/README.md b/README.md
index fcf80bdf..6cac15b5 100644
--- a/README.md
+++ b/README.md
@@ -1,332 +1,103 @@
# Galaxy Benchmarker
-A tool for benchmarking Galaxy job-destinations.
-The goal is to easily benchmark different job-destinations. All you have to do is to configure the benchmarker itself,
-define the destinations you want to benchmark, the workflows you want to run and the benchmarks you want to use. GalaxyBenchmarker
-should handle the rest, like configuring Galaxy (to send the jobs to the right destination), submitting workflows and
-collecting the metrics.
+A tool for benchmarking different storage types individually or in more complex setups like Galaxy in combination with iRODS.
-GalaxyBenchmarker is designed to be easily extendable in terms of destinations-types and benchmark-scenarios.
+Link to the [original version](https://github.com/usegalaxy-eu/GalaxyBenchmarker/tree/668b9a5125541f686d00950a0e862a260ca4b787) of the GalaxyBenchmarker.
-### Benchmark-Scenarios
-Currently there are three types/scenarios of benchmarks available. Every benchmark allows to define its destination and workflows to test, how often workflows should
-be run per destination and if some ansible-playbooks should be run before or after the benchmark ran.
-#### Cold vs Warm
-What is the difference between running a workflow for the first time or for it having been run multiple
-times already? What is the overhead for staging time, installing tools, etc?
+Link to the [results referenced](https://doi.org/10.5281/zenodo.7051186) in the `evaluation` section.
-This benchmark cleans up Pulsar for every cold run, using a Ansible-Playbook ([coldwarm_pretask.yml]).
+## Basic architecture
-#### Destination Comparison
-What are the differences between multiple destinations in terms of staging time (sending data
-to a remote location might take some time), runtime, etc.
+You can run GalaxyBenchmarker locally on your maschine or inside a container. It
+mainly uses [Ansible](https://docs.ansible.com/) and [Docker](https://www.docker.com/)
+to configure the target hosts. This ensures that the target hosts will be in a clean
+state after benchmarking.
-#### Burst
-How does a destination handle a big burst of requests?
+### Project structure
-
-## Requirements
-* A Galaxy-Instance
-* InfluxDB for saving the benchmark results
-* Some job-destinations to benchmark
-* Python 3.7
-* Ansible
-* Docker (if you want to use the docker-compose setup)
-
-## Usage
-A docker-compose setup is already provided that ships with InfluxDB and Grafana for easy
-analysis of the metrics.
-
-### Install all dependencies (not needed if using docker-compose setup)
-Some additional packages are needed in order for GalaxyBenchmarker to function properly.
-To install those, run the following command:
-```shell
-pip3 install -r requirements.txt
```
-
-### Configure the GalaxyBenchmarker
-To use the benchmarker, you first need to create a yaml-configuration file.
-You can find a basic example in `benchmark_config.yml.usegalaxyeu`. As a start,
-rename it to `benchmark_config.yml` and fill in the credentials for your regular
-UseGalaxy.eu user and, additionally, the user key for which the jobs are routed
-to your wanted destination.
-
-Next, we need to define the workflows that should be run. The benchmarker
-uses the test functionality of [Planemo](https://github.com/galaxyproject/planemo) to
-submit workflows. The docker-compose setup already clones the examples from
-https://github.com/usegalaxy-eu/workflow-testing. These are available at the
-the path `/workflow-testing`.
-
-Workflows are defined in the configuration as following:
-```yaml
-workflows:
- - name: ARDWorkflow
- type: Galaxy
- path: /workflow-testing/sklearn/ard/ard.ga
-```
-
-The benchmarker can submit jobs to multiple job destinations to compare the performance of each. For routing
-the jobs to the right destination, users can be defined for each. The GalaxyBenchmarker can take
-care of that
-(see the [configuration examples](https://github.com/AndreasSko/Galaxy-Benchmarker/blob/master/benchmark_config.yml.example))
-if you are an administrator for the Galaxy instance. Otherwise, it is also possible to use different
-users for each destination and link them in the configuration of the benchmarker:
-```yaml
-destinations:
- - name: Destination1
- type: Galaxy
- galaxy_user_key: USERKEY
-```
-
-Now, we just need to define the actual benchmark that we want to perform. To compare different job destinations,
-you can do the following:
-```yaml
-benchmarks:
- - name: DestinationComparisonBenchmark
- type: DestinationComparison
- destinations:
- - Destination1
- workflows:
- - ARDWorkflow
- runs_per_workflow: 1
+.
+├── ansible -- Ansible playbooks and inventory
+├── evaluation -- Example plot code in jupiter notebooks
+├── examples -- Example configurations
+├── galaxy_benchmarker -- Source code
+├── logs -- Generated logs
+├── results -- Generated results
+...
```
-### Run the benchmark using docker-compose
-Now you should be ready to run the benchmark. Simply run:
-````shell
-docker-compose up
-````
-This will spin up a InfluxDB and Grafana container, together with a container
-running the benchmarker. After the benchmarking has been finished, you can
-view the results using Grafana at http://localhost:3000
-(username: admin, password: admin).
-
-:warning:
+## Usage
-The data of InfluxDB is stored inside the container. Before running
-`docker-compose down` remember to back up your data!
+You can use GalaxyBenchmarker with the prepackaged and preconfigured container
+environment or as a local project. For the container env it automatically maps
+SSH (config, agent, ...) inside the container. With this you can use your local
+SSH config and keys within the inventory.
-### Run the benchmarks (without docker-compose)
-The GalaxyBenchmarker can use different configuration files. If none is given, it will look for `benchmark_config.yml`
-```shell
-python3 galaxy_benchmarker --config benchmark_config.yml
-```
+[Example usage described here](#example-usage)
-## Benchmark Types
-### Destination Comparison
-Used to compare the performance of different destinations.
+### Run with container
-#### Requirements
-* 1-n Galaxy destinations
-* 1-n Galaxy workflows
-* `runs_per_workflow` >= 1: How many times should a workflow be run on every destination?
-#### Optional settings
-* `warmup`: if set to true, a "warmup run" will be performed for every workflow
-on every destination, while its results won't be counted
-* ``pre_task``/`post_task`: a task that will be run before or after the benchmark has been completed
+```bash
+# Build and prepare the image(s)
+docker-compose build
-### Cold vs Warm
-Used to compare the performance of a cold run (workflows hasn't been run before) to a warm run. To
-simulate a cold run, you can define pre tasks that will be run before every cold workflow run, to
-clean up caches etc.
+# Change 'command' in docker-compose.yml to point to your config file
+nano docker-compose.yml
-Note: This benchmark type can only use one destination!
+# Option 1: Run GalaxyBenchmarker and display progress
+docker-compose up -d
+docker-compose logs -f benchmarker
-#### Requirements
-* 1 Galaxy destination
-* 1-n Galaxy workflows
-* `runs_per_workflow` >= 1: How many times should a workflow be run on every destination?
-* `cold_pre_task`: a task task that will be run in the cold phase before every workflow run
+# Option 2: Run with make commands
+make run
+## If you require sudo for docker-compose (-E flag is required!)
+make run_sudo
-#### Optional settings
-* ``pre_task``/`post_task`: a task that will be run before or after the benchmark has been completed
-* `warm_pre_task`: a task task that will be run in the warm phase before every workflow run
+# Gracefull shutdown (stores all results before exiting)
+make stop
+make stop_sudo
+```
-### Burst
-Used to start a burst of workflows at the same time.
+### Run locally
-#### Requirements
-* 1-n Galaxy or Condor destinations
-* 1-n Galaxy or Condor workflows
-* `runs_per_workflow` >= 1: How many times should a workflow be run on every destination?
-* `burst_rate` > 0: How many workflows should be submitted per second?
-(for example: 0.5 results in a workflow submit every two seconds)
+This project requires [Poetry](https://python-poetry.org/docs/).
-#### Optional settings
-* `warmup`: if set to true, a "warmup run" will be performed for every workflow
-on every destination, while its results won't be counted
-* ``pre_task``/`post_task`: a task that will be run before or after the benchmark has been completed
+```bash
+# Setup project
+poetry env use python3.10
+poetry install
-## Destination Types
-Currently, the benchmarks can be run on two main types of destinations, while the first
-is the best supported.
-### Galaxy Destination
-#### Regular
-All you need to define is the username and api key to be used. This type expects, that
-routing to destinations is handled by Galaxy and is user-based (e.g. one user per
-destination).
-```yaml
-name: DestinationName
-type: Galaxy
-galaxy_user_name: me@andreas-sk.de
-galaxy_user_key: f4284f9728e430a8140435861b17a454
+# Run GalaxyBenchmarker
+poetry run python -m galaxy_benchmarker [... optional args]
```
-#### PulsarMQ
-This type is used, if you want GalaxyBenchmarker to configure Galaxy to use a Pulsar
-destination.
-````yaml
-name: PulsarDestinationName
-type: PulsarMQ
-amqp_url: "pyamqp://username:password@rabbitmq.example.com:5672//"
-tool_dependency_dir: /data/share/tools
-jobs_directory_dir: /data/share/staging
-persistence_dir: /data/share/persisted_data
-# To configure additional params in job_conf.xml
-job_plugin_params:
- manager: __default__
-job_destination_params:
- dependency_resolution: remote
- other_parameter: abc
-````
-
-### Condor Destination
-This destination type was defined in order to benchmark HTCondor directly. You need
-to set the credentials to your Condor submit node. GalaxyBenchmarker directly
-connects to the host via SSH and runs ``condor_submit``.
-````yaml
-name: CondorDestinationName
-type: Condor
-host: submit.htcondor.com
-host_user: ssh-user
-ssh_key: /local/path/to/ssh/key.cert
-jobs_directory_dir: /data/share/condor
-````
+## Inventory / Hosts / Targets
-## Workflow Types
-### Galaxy Workflow
-The benchmarker uses the test functionality of [Planemo](https://github.com/galaxyproject/planemo) to
-submit workflows. Examples can be found at: https://github.com/usegalaxy-eu/workflow-testing. For a start, you
-can clone this repository and use those workflows for benchmarking. Workflows are defined in the
-configuration as following:
-```yaml
-name: GalaxyWorkflowName
-type: Galaxy
-path: path/to/galaxy/workflow/file.ga
-timeout: 100
-```
+The targets/hosts are defined in [ansible/inventory/](./ansible/inventory/) as
+[Ansible inventory](https://docs.ansible.com/ansible/latest/user_guide/intro_inventory.html).
+Here, you can also set host specific variables, if necessary.
-### Condor Workflow
-A Condor Workflow is defined by the path to the folder containing its
-files and the actual job file. GalaxyBenchmarker will upload the folder to
-the Condor submit node using an Ansible Playbook and will trigger a
-``condor_submit`` to start the workflow.
-````yaml
-name: CondorWorkflowName
-type: Condor
-path: path/to/condor/workflow/folder
-job_file: job.job
-````
+The defined hosts can then be used throughout the various benchmarker configs.
-## Task Types
-### Ansible Playbook
-An Ansible Playbook can be run on every destination defined in a benchmark.
-Note: You will need to add `host`, `host_user` and `ssh_key` to the definition of
-every destination.
-
-Define a task as follows:
-```yaml
-type: ansible-playbook
-playbook: /path/to/playbook.yml
-```
+## Example usage
-### Benchmarker Task
-These are tasks defined in ``task.py``. Currently, there exist the following tasks:
-* `delete_old_histories`: This will delete all histories of a user on Galaxy
-* ``reboot_openstack_servers``: This will reboot all OpenStack instance which name correspond to
-``name_contains``
-* ``reboot_random_openstack_server``: This will reboot a randomly chosen OpenStack instance
-which name correspond to ``name_contains``
-* ``rebuild_random_openstack_server``: This will rebuild a randomly chosen OpenStack instance
-which name correspond to ``name_contains``
+1. All examples use `irods_client` and `irods_server` as aliases for the hosts. These hosts have to be configured in [the inventory](./ansible/inventory/example.yml)
+1. Run the GalaxyBenchmarker with `--cfg examples/01_verify_setup.yml` to verify the setup.
-Define a task as follows:
-````yaml
-type: benchmarker-task
-name: task-name
-params:
- param1: ab
- param2: cd
-````
-## Additional options
-All possible options can be found in the [configuration examples](https://github.com/AndreasSko/Galaxy-Benchmarker/blob/master/benchmark_config.yml.example).
+## Debugging
-### InfluxDB
-In normal cases, GalaxyBenchmarker will save the results in a json file under the `results` directory. However, job
-metrics can also be submitted to InfluxDB for further analysis.
-```yaml
-influxdb:
- host: influxdb.example.com
- port: 8086
- username: glx_benchmarker_user
- password: supersecret
- db_name: glx_benchmarker
-```
-Example Dashboards for Grafana can be found at [grafana_dashboards](https://github.com/AndreasSko/Galaxy-Benchmarker/tree/master/grafana_dashboards)
+1. Change the configuration for more detailed logging
+ ```
+ log_ansible_output: true
+ results_save_raw_results: true
+ ```
-### OpenStack
-There exist some tasks that need access to OpenStack to work properly. For this,
-you can define your credentials:
-```yaml
-openstack:
- auth_url: https://auth.url.com:5000/v3/
- compute_endpoint_version: 2.1
- username: username
- password: password
- project_id: id
- region_name: region
- user_domain_name: Default
-```
+1. Run GalaxyBenchmarker with the flags `--only-pre-task`, `--only-benchmark`, and `--only-post-task` to check the individual stages
-### Let GalaxyBenchmarker handle the configuration
-The GalaxyBenchmarker can configure Galaxy to use different job destinations and to install
-tool dependencies. For that, you need have an admin user and SSH access to the instance.
-```yaml
-galaxy:
- ...
- # Install tool dependencies
- shed_install: true
- # Should Galaxy be configured to use the given Destinations or is everything already set?
- configure_job_destinations: true
- ssh_user: ubuntu
- ssh_key: /local/path/to/ssh/key.cert
- galaxy_root_path: /srv/galaxy
- galaxy_config_dir: /srv/galaxy/server/config
- galaxy_user: galaxy
-```
-The settings for a new destination can then be defined as follows:
-```yaml
-destinations:
- - name: PulsarDestination
- type: PulsarMQ
- amqp_url: "pyamqp://username:password@rabbitmq.example.com:5672//"
- # If used for ColdWarmBenchmark, we need to have ssh-access to the Pulsar-Server
- host: pulsar.example.com
- host_user: centos
- ssh_key: /local/path/to/ssh/key.cert
- tool_dependency_dir: /data/share/tools
- jobs_directory_dir: /data/share/staging
- persistence_dir: /data/share/persisted_data
- # To configure additional params in job_conf.xml
- job_plugin_params:
- manager: __default__
- job_destination_params:
- dependency_resolution: remote
- default_file_action: remote_transfer
- remote_metadata: false
- rewrite_parameters: true
- amqp_acknowledge: true
- amqp_ack_republish_time: 10
-```
\ No newline at end of file
+1. Have a look into the logs of the container:
+ ```
+ irods-fuse -> /tmp
+ irods -> /var/lib/irods/log/
+ ```
diff --git a/ansible/cleanup_galaxy_server.yml b/ansible/cleanup_galaxy_server.yml
new file mode 100644
index 00000000..d33c723b
--- /dev/null
+++ b/ansible/cleanup_galaxy_server.yml
@@ -0,0 +1,38 @@
+- hosts: "{{ host }}"
+ become: true
+ tasks:
+ - import_tasks: tasks/remove_container.yml
+ vars:
+ path: "/galaxy"
+
+ - name: Delete galaxy files
+ when: galaxy_host_volume is defined
+ become: yes
+ community.docker.docker_container:
+ name: fix_filepermissions
+ image: ubuntu
+ user: 1450
+ detach: false
+ cleanup: true
+ volumes:
+ - "{{ galaxy_host_volume }}/galaxy_in_progress:/mnt/volume_under_test"
+ command: [
+ "rm",
+ "-rf",
+ "/mnt/volume_under_test/files",
+ ]
+
+ - name: Cleanup Host Volume
+ when: galaxy_host_volume is defined
+ file:
+ path: "{{ galaxy_host_volume }}/galaxy_in_progress"
+ state: absent
+ retries: 15
+ delay: 5
+ register: cleanup
+ until: not cleanup["failed"]
+
+ - name: Cleanup Export Volume
+ file:
+ path: "{{ galaxy_export_volume }}/glx_export"
+ state: absent
diff --git a/ansible/cleanup_irods_fuse_client.yml b/ansible/cleanup_irods_fuse_client.yml
new file mode 100644
index 00000000..0aa2555b
--- /dev/null
+++ b/ansible/cleanup_irods_fuse_client.yml
@@ -0,0 +1,15 @@
+- hosts: "{{ host }}"
+ become: true
+ tasks:
+ - import_tasks: tasks/remove_container.yml
+ vars:
+ path: "/irods"
+
+ - name: Unmount fuse mount
+ ansible.posix.mount:
+ path: "{{ irods_host_volume }}/irods-fuse"
+ state: unmounted
+ - name: Remove irods volume
+ file:
+ path: "{{ irods_host_volume }}/irods-fuse"
+ state: absent
diff --git a/ansible/cleanup_irods_server.yml b/ansible/cleanup_irods_server.yml
new file mode 100644
index 00000000..f637d3c5
--- /dev/null
+++ b/ansible/cleanup_irods_server.yml
@@ -0,0 +1,17 @@
+- hosts: "{{ host }}"
+ become: true
+ tasks:
+ - import_tasks: tasks/remove_container.yml
+ when: (irods_use_davrods is defined) and (irods_use_davrods|bool)
+ vars:
+ path: "/davrods"
+
+ - import_tasks: tasks/remove_container.yml
+ vars:
+ path: "/irods"
+
+ - name: Remove irods volume
+ when: (irods_host_volume is defined)
+ file:
+ path: "{{ irods_host_volume }}/irods"
+ state: absent
diff --git a/ansible/cleanup_irods_webdav_client.yml b/ansible/cleanup_irods_webdav_client.yml
new file mode 100644
index 00000000..107b3ed5
--- /dev/null
+++ b/ansible/cleanup_irods_webdav_client.yml
@@ -0,0 +1,15 @@
+- hosts: "{{ host }}"
+ become: true
+ tasks:
+ - import_tasks: tasks/remove_container.yml
+ vars:
+ path: "/irods"
+
+ - name: Unmount webdav mount
+ ansible.posix.mount:
+ path: "{{ irods_host_volume }}/irods-webdav"
+ state: unmounted
+ - name: Remove irods volume
+ file:
+ path: "{{ irods_host_volume }}/irods-webdav"
+ state: absent
diff --git a/ansible/connection_test.yml b/ansible/connection_test.yml
new file mode 100644
index 00000000..ad587563
--- /dev/null
+++ b/ansible/connection_test.yml
@@ -0,0 +1,4 @@
+- hosts: "{{ host }}"
+ tasks:
+ - name: "Get the hostname to see if host is reachable"
+ shell: hostname
diff --git a/ansible/files/galaxy-server/.gitignore b/ansible/files/galaxy-server/.gitignore
new file mode 100644
index 00000000..1f5d6409
--- /dev/null
+++ b/ansible/files/galaxy-server/.gitignore
@@ -0,0 +1 @@
+export
\ No newline at end of file
diff --git a/ansible/files/galaxy-server/docker-compose.yml b/ansible/files/galaxy-server/docker-compose.yml
new file mode 100644
index 00000000..37513219
--- /dev/null
+++ b/ansible/files/galaxy-server/docker-compose.yml
@@ -0,0 +1,22 @@
+version: "3"
+services:
+ galaxy:
+ build: galaxy
+ hostname: galaxy
+ domainname: local
+ ports:
+ # Webserver
+ - "8080:80"
+ # supvisord web app
+ - "9002:9002"
+ volumes:
+ - ${GALAXY_EXPORT_VOLUME}:/export
+ - ./galaxy/custom_tools:/custom_tools
+ - ./galaxy/custom_config:/custom_config
+ # PLACEHOLDER_VOLUMES
+ environment:
+ - GALAXY_CONFIG_TOOL_CONFIG_FILE=/custom_tools/custom_tool_conf.xml
+ # PLACEHOLDER_ENV
+
+ galaxy-job-starter:
+ build: job-starter
diff --git a/ansible/files/galaxy-server/galaxy/Dockerfile b/ansible/files/galaxy-server/galaxy/Dockerfile
new file mode 100644
index 00000000..12092840
--- /dev/null
+++ b/ansible/files/galaxy-server/galaxy/Dockerfile
@@ -0,0 +1,7 @@
+FROM bgruening/galaxy-stable
+
+# Install additional requirements
+COPY requirements.txt /requirements.txt
+RUN . $GALAXY_VIRTUAL_ENV/bin/activate \
+ && pip install -r /requirements.txt \
+ && deactivate
diff --git a/ansible/files/galaxy-server/galaxy/custom_config/object_store_conf.irods.xml.j2 b/ansible/files/galaxy-server/galaxy/custom_config/object_store_conf.irods.xml.j2
new file mode 100644
index 00000000..58af82ad
--- /dev/null
+++ b/ansible/files/galaxy-server/galaxy/custom_config/object_store_conf.irods.xml.j2
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ansible/files/galaxy-server/galaxy/custom_config/object_store_conf.local_mount.xml b/ansible/files/galaxy-server/galaxy/custom_config/object_store_conf.local_mount.xml
new file mode 100644
index 00000000..b5137a9f
--- /dev/null
+++ b/ansible/files/galaxy-server/galaxy/custom_config/object_store_conf.local_mount.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ansible/files/galaxy-server/galaxy/custom_config/object_store_conf.s3.xml.j2 b/ansible/files/galaxy-server/galaxy/custom_config/object_store_conf.s3.xml.j2
new file mode 100644
index 00000000..fb9f8efb
--- /dev/null
+++ b/ansible/files/galaxy-server/galaxy/custom_config/object_store_conf.s3.xml.j2
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ansible/files/galaxy-server/galaxy/custom_tools/custom_tool_conf.xml b/ansible/files/galaxy-server/galaxy/custom_tools/custom_tool_conf.xml
new file mode 100644
index 00000000..e8f56ebb
--- /dev/null
+++ b/ansible/files/galaxy-server/galaxy/custom_tools/custom_tool_conf.xml
@@ -0,0 +1,6 @@
+
+
+
+
diff --git a/ansible/files/galaxy-server/galaxy/custom_tools/file_gen.py b/ansible/files/galaxy-server/galaxy/custom_tools/file_gen.py
new file mode 100644
index 00000000..1050c918
--- /dev/null
+++ b/ansible/files/galaxy-server/galaxy/custom_tools/file_gen.py
@@ -0,0 +1,51 @@
+#!/usr/bin/env python
+# Generate files with random data
+
+import sys
+import io
+import os
+from pathlib import Path
+import random
+
+BUFFER_BLOCK_SIZE = 1024*1024 # 1MiB
+BUFFER_NUM_BLOCKS = 1024 # Total size: 1 GiB
+
+def fill_file_from_buffer(file: Path, buffer: io.BytesIO, file_size: int):
+ """Fill file with random blocks from buffer until file_size is reached"""
+
+ num_blocks, rest = divmod(file_size, BUFFER_BLOCK_SIZE)
+ with file.open("wb") as out:
+ # Copy num_blocks random blocks
+ for _ in range(num_blocks):
+ rand_block = random.randint(0,BUFFER_NUM_BLOCKS-1)
+ buffer.seek(rand_block)
+ out.write(buffer.read(BUFFER_BLOCK_SIZE))
+
+ # Write the last block
+ rand_block = random.randint(0,BUFFER_NUM_BLOCKS-1)
+ buffer.seek(rand_block)
+ out.write(buffer.read(rest))
+
+def main():
+
+ num_files = int(sys.argv[1])
+ file_size_in_bytes = int(sys.argv[2])
+ output_dir = Path(sys.argv[3])
+
+ output_dir.mkdir(parents=True, exist_ok=True)
+
+ with io.BytesIO() as buffer:
+ if file_size_in_bytes > 0:
+ # Create a buffer with random data of size
+ # BUFFER_BLOCK_SIZE*BUFFER_NUM_BLOCKS
+ for _ in range (BUFFER_NUM_BLOCKS):
+ buffer.write(os.urandom(BUFFER_BLOCK_SIZE))
+ buffer.seek(0)
+
+ for i in range(num_files):
+ file = output_dir / f"data_{i}.data"
+ fill_file_from_buffer(file, buffer, file_size_in_bytes)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible/files/galaxy-server/galaxy/custom_tools/file_gen.xml b/ansible/files/galaxy-server/galaxy/custom_tools/file_gen.xml
new file mode 100644
index 00000000..ef37a827
--- /dev/null
+++ b/ansible/files/galaxy-server/galaxy/custom_tools/file_gen.xml
@@ -0,0 +1,20 @@
+
+ Create random files
+
+python '$__tool_directory__/file_gen.py' '${num_files}' '${file_size_in_bytes}' './output'
+
+
+
+
+
+
+
+
+
+
+
+
+
+Create **num_files** files with **file_size_in_bytes** bytes.
+
+
diff --git a/ansible/files/galaxy-server/galaxy/requirements.txt b/ansible/files/galaxy-server/galaxy/requirements.txt
new file mode 100644
index 00000000..927fd105
--- /dev/null
+++ b/ansible/files/galaxy-server/galaxy/requirements.txt
@@ -0,0 +1,2 @@
+git+https://github.com/galaxyproject/bioblend.git@main#egg=bioblend
+python-irodsclient>1,<2
\ No newline at end of file
diff --git a/ansible/files/galaxy-server/job-starter/Dockerfile b/ansible/files/galaxy-server/job-starter/Dockerfile
new file mode 100644
index 00000000..ad4c2a77
--- /dev/null
+++ b/ansible/files/galaxy-server/job-starter/Dockerfile
@@ -0,0 +1,19 @@
+FROM python:3.10-slim
+
+# Runtime for galaxy scripts
+
+RUN \
+ apt-get update \
+ && DEBIAN_FRONTEND=noninteractive apt-get install -y \
+ git \
+ && apt-get clean \
+ && rm -rf /var/lib/apt/lists/* /tmp/*
+
+WORKDIR /scripts
+COPY scripts/requirements.txt /scripts
+
+RUN python -m pip install -r requirements.txt
+
+COPY scripts/ /scripts/
+
+ENTRYPOINT ["/scripts/entrypoint.sh"]
diff --git a/ansible/files/galaxy-server/job-starter/scripts/entrypoint.sh b/ansible/files/galaxy-server/job-starter/scripts/entrypoint.sh
new file mode 100755
index 00000000..ee9e034c
--- /dev/null
+++ b/ansible/files/galaxy-server/job-starter/scripts/entrypoint.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+sleep infinity
diff --git a/ansible/files/galaxy-server/job-starter/scripts/requirements.txt b/ansible/files/galaxy-server/job-starter/scripts/requirements.txt
new file mode 100644
index 00000000..61fabcae
--- /dev/null
+++ b/ansible/files/galaxy-server/job-starter/scripts/requirements.txt
@@ -0,0 +1 @@
+git+https://github.com/galaxyproject/bioblend.git@main#egg=bioblend
diff --git a/ansible/files/galaxy-server/job-starter/scripts/run_job.py b/ansible/files/galaxy-server/job-starter/scripts/run_job.py
new file mode 100644
index 00000000..5e263287
--- /dev/null
+++ b/ansible/files/galaxy-server/job-starter/scripts/run_job.py
@@ -0,0 +1,46 @@
+import os
+import json
+import logging
+import shlex
+from bioblend.galaxy import GalaxyInstance
+
+log = logging.getLogger(__name__)
+logging.basicConfig(level=logging.INFO)
+
+GLX_HISTORY_NAME = os.getenv("GLX_HISTORY_NAME", "galaxy_benchmarker")
+GLX_TOOL_ID = os.getenv("GLX_TOOL_ID", "")
+GLX_TOOL_INPUT = os.getenv("GLX_TOOL_INPUT", "")
+
+def main():
+ """Connect to the local galaxy and trigger a job"""
+ if not GLX_TOOL_ID:
+ raise ValueError("Env var 'GLX_TOOL_ID' is required. Expected: Tool id which should be executed")
+ if not GLX_TOOL_INPUT:
+ raise ValueError("Env var 'GLX_TOOL_INPUT' is required. Expected: String encoded json input")
+ tool_input = json.loads(shlex.split(GLX_TOOL_INPUT)[0])
+
+ glx = GalaxyInstance(url="http://galaxy", key="fakekey")
+
+ history_id = get_history_id(glx, GLX_HISTORY_NAME)
+
+ log.info("Initiate tool run")
+ result = glx.tools.run_tool(
+ tool_id=GLX_TOOL_ID,
+ tool_inputs=tool_input,
+ history_id=history_id
+ )
+ log.info(result)
+
+def get_history_id(glx: GalaxyInstance, history_name: str) -> str:
+ """Get history id by history name"""
+ histories = glx.histories.get_histories()
+ for history in histories:
+ if history["name"] == history_name:
+ return history["id"]
+
+ result = glx.histories.create_history(history_name)
+ return result["id"]
+
+
+if __name__ == '__main__':
+ main()
\ No newline at end of file
diff --git a/ansible/files/irods-davrods-server/Dockerfile b/ansible/files/irods-davrods-server/Dockerfile
new file mode 100644
index 00000000..9ca9e8db
--- /dev/null
+++ b/ansible/files/irods-davrods-server/Dockerfile
@@ -0,0 +1,28 @@
+FROM ubuntu:18.04
+RUN apt-get update \
+ && apt-get install -y \
+ apache2 \
+ apache2-utils \
+ gnupg \
+ lsb-release \
+ wget \
+ && wget -qO - https://packages.irods.org/irods-signing-key.asc | apt-key add - \
+ && echo "deb [arch=amd64] https://packages.irods.org/apt/ $(lsb_release -sc) main" | tee /etc/apt/sources.list.d/renci-irods.list \
+ && apt-get update \
+ && apt-get install -y \
+ irods-runtime=4.2.10 \
+ && wget -qO - https://github.com/UtrechtUniversity/davrods/releases/download/4.2.10_1.5.0/davrods-4.2.10-1.5.0.deb > /tmp/davrods.deb \
+ && apt install /tmp/davrods.deb \
+ && apt-get clean \
+ && rm -rf /var/lib/apt/lists/* /tmp/*
+
+RUN a2enmod dav \
+ && a2enmod davrods \
+ && a2ensite davrods-vhost
+
+ARG host_owner_uid
+RUN adduser --disabled-password --gecos "" --uid "${host_owner_uid}" irods
+
+EXPOSE 80
+ENTRYPOINT ["apache2ctl"]
+CMD ["-DFOREGROUND"]
diff --git a/ansible/files/irods-davrods-server/docker-compose.yml b/ansible/files/irods-davrods-server/docker-compose.yml
new file mode 100644
index 00000000..2b82783a
--- /dev/null
+++ b/ansible/files/irods-davrods-server/docker-compose.yml
@@ -0,0 +1,19 @@
+version: "3"
+services:
+ davrods:
+ build:
+ context: .
+ args:
+ host_owner_uid: ${DAVRODS_VOLUME_OWNER}
+
+ networks:
+ - irods_default
+ ports:
+ - 80:80
+ volumes:
+ - ./volumes/davrods-vhost.conf:/etc/apache2/sites-available/davrods-vhost.conf
+ - ./volumes/irods_environment.json:/etc/apache2/irods/irods_environment.json
+
+networks:
+ irods_default:
+ external: true
diff --git a/ansible/files/irods-davrods-server/volumes/davrods-vhost.conf b/ansible/files/irods-davrods-server/volumes/davrods-vhost.conf
new file mode 100644
index 00000000..b70193a3
--- /dev/null
+++ b/ansible/files/irods-davrods-server/volumes/davrods-vhost.conf
@@ -0,0 +1,213 @@
+# -*- mode: apache -*-
+# vim: ft=apache ts=4 sw=4 et
+#
+# davrods-vhost.conf
+#
+# Davrods is a mod_dav WebDAV provider. Configuration directives for
+# Davrods should be placed in a block.
+#
+# Below we provide an example vhost configuration that enables Davrods
+# using its default options.
+#
+
+
+ # Enter your server name here.
+ ServerName localhost
+ ServerAlias SET_TO_HOST_IP
+
+ # NB: Some webdav clients expect the server to implement webdav at the root
+ # location (they execute an OPTIONS request to verify existence of webdav
+ # protocol support).
+
+
+
+ # Options needed to enable Davrods. {{{
+ # =================================
+
+ # Disable built-in Apache directory listings - Davrods will
+ # provide this instead.
+ DirectoryIndex disabled
+
+ # Restrict access to authenticated users.
+ AuthType Basic
+ Require valid-user
+
+ # The realm name that will be shown to clients upon authentication
+ AuthName DAV
+
+ # Use the 'irods' HTTP basic authentication provider, implemented by Davrods.
+ AuthBasicProvider irods
+
+ # The DAV provider for this location.
+ #
+ # Davrods implements multiple dav providers, use either:
+ # - davrods-nolocks: WebDAV class 1 provider, no support for locking
+ # - davrods-locallock (recommended): WebDAV class 2 provider, uses a DBM lock database local to this webserver
+ #
+ # Note that the davrods-locallock provider requires an apache-writable lockdb directory
+ # (/var/lib/davrods, or a path specified using the DavrodsLockDB directive - see further down this file).
+ # The RPM/DEB distribution creates this directory for you.
+ #
+ Dav davrods-locallock
+
+ # }}}
+
+ # Davrods configuration directives. {{{
+ # =================================
+
+ # Location of the iRODS environment file that specifies the iRODS
+ # client configuration used by Davrods.
+ #
+ # Note: When options in the iRODS environment file overlap with Davrods
+ # configuration directives, as with the host, port, and zone of the
+ # iRODS server, the values specified in the iRODS environment file are
+ # NOT used.
+ #
+ DavrodsEnvFile /etc/apache2/irods/irods_environment.json
+
+ # The following options can be used to customize Davrods for your environment.
+ # These options and their default values are provided below.
+ # Having these directives commented out has the effect of enabling
+ # the listed default option.
+
+ # Hostname and port of the iRODS server to connect to.
+ #
+ DavrodsServer irods 1247
+
+ # Data grid zone id of the iRODS server.
+ #
+ DavrodsZone zone_1
+
+ # Authentication type to use when connecting to iRODS.
+ #
+ # Supported authentication types are 'Native' and 'Pam'.
+ # ('Native' corresponds to what was formerly called 'Standard' auth in iRODS).
+ #
+ #DavrodsAuthScheme Native
+
+ # Anonymous mode switch.
+ #
+ # (default: Off)
+ # When 'Off', basic authentication is required to log into
+ # Davrods. AuthType must be set to 'Basic' and AuthBasicProvider
+ # must be set to 'irods'. There must also be a 'Require valid-user'
+ # line.
+ #
+ # When 'On', Davrods will log into iRODS with a preset
+ # username and password (See options DavrodsAnonymousLogin and
+ # DavrodsAuthScheme). AuthType must be unset, or set to None,
+ # and there should be no 'Require valid-user' line
+ # (instead: Require all granted).
+ #
+ # This allows users to access Davrods without being prompted
+ # for a login, making public access and embedding in web pages
+ # easier.
+ #DavrodsAnonymousMode Off
+
+ # iRODS authentication options for Davrods anonymous mode.
+ #
+ # This option is used only when DavrodsAnonymousMode is set to
+ # 'On'.
+ #
+ # Specifies the username and password to use for anonymous login.
+ # The default value is 'anonymous', with an empty password.
+ # (this user, if created, is treated specially within iRODS)
+ #
+ # The special 'anonymous' iRODS user normally requires the
+ # DavrodsAuthScheme to be set to Native.
+ #
+ #DavrodsAnonymousLogin "anonymous" ""
+
+ # iRODS default resource to use for file uploads.
+ #
+ # Leave this empty to let the server decide.
+ #
+ #DavrodsDefaultResource ""
+
+ # Exposed top collection of iRODS.
+ #
+ # Note that the collection chosen MUST be readable for all users,
+ # otherwise they will experience problems when mounting the drive.
+ # For example, if you set it to "Home", then as a rodsadmin user
+ # execute the icommand: ichmod read public /zone-name/home
+ #
+ # Davrods accepts the following values for exposed-root:
+ # - 'Zone' (collection /zone-name)
+ # - 'Home' (collection /zone-name/home)
+ # - 'User' (collection /zone-name/home/logged-in-username)
+ # - full-path (named collection, must be absolute path, starts with /)
+ #
+ DavrodsExposedRoot User
+
+ # Size of the buffers used for file transfer to/from the iRODS server.
+ #
+ # The default values optimize performance for regular configurations.
+ # The Tx buffer is used for transfer to iRODS (PUT), while the Rx
+ # buffer is used for transfer from iRODS (GET).
+ # Buffer sizes lower than 1024K will lead to decreased file transfer performance.
+ #
+ # The buffer sizes are specified as a number of kibibytes ('1' means 1024 bytes).
+ # We use 4 MiB transfer buffers by default.
+ #
+ #DavrodsTxBufferKbs 4096
+ #DavrodsRxBufferKbs 4096
+
+ # Optionally Davrods can support rollback for aborted uploads. In this scenario
+ # a temporary file is created during upload and upon succesful transfer this
+ # temporary file is renamed to the destination filename.
+ # NB: Please note that the use of temporary files may conflict with your iRODS
+ # data policies (e.g. a acPostProcForPut would act upon the temporary filename).
+ # Valid values for this option are 'On'/'Yes' and 'Off'/'No'.
+ #
+ #DavrodsTmpfileRollback Off
+
+ # When using the davrods-locallock DAV provider (see the 'Dav'
+ # directive above), this option can be used to set the location of the
+ # lock database.
+ #
+ #DavrodsLockDB /var/lib/davrods/lockdb_locallock
+
+ # Davrods provides read-only HTML directory listings for web browser access.
+ # The UI is basic and unstyled by default, but can be modified with three
+ # theming directives.
+ #
+ # Each of these directives points to a local HTML file that must be readable
+ # by the apache user.
+ #
+ # The default value for each of these is "", which disables the option.
+ #
+ # - DavrodsHtmlHead is inserted in the HEAD tag of the listing.
+ # - DavrodsHtmlHeader is inserted at the top of the listing's BODY tag.
+ # - DavrodsHtmlFooter is inserted at the bottom of the listing's BODY tag.
+ #
+ # Example HTML files are provided in /etc/apache2/irods. You should edit these
+ # before enabling them.
+ #
+ # To see an example, uncomment the following three lines:
+ #
+ #DavrodsHtmlHead "/etc/apache2/irods/head.html"
+ #DavrodsHtmlHeader "/etc/apache2/irods/header.html"
+ #DavrodsHtmlFooter "/etc/apache2/irods/footer.html"
+
+ # Depending on file type, web browser clients will either display
+ # files directly or offer a download to the user.
+ # This behavior can be influenced with the 'Content-Disposition' header.
+ #
+ # By default (value 'Off'), no such header is sent by Davrods.
+ # When DavrodsForceDownload is 'On', Davrods will send
+ # 'Content-Disposition: attachment' for all data objects, signalling that
+ # web browsers should not display files inline, but offer a download
+ # instead.
+ #
+ #DavrodsForceDownload Off
+
+ # }}}
+
+
+
+ # To avoid cleartext password communication we strongly recommend to
+ # enable Davrods only over SSL.
+ # For HTTPS-only access, change the port at the start of the vhost block
+ # from 80 to 443 and add your SSL options below.
+
+
diff --git a/ansible/files/irods-davrods-server/volumes/irods_environment.json b/ansible/files/irods-davrods-server/volumes/irods_environment.json
new file mode 100644
index 00000000..258aa66d
--- /dev/null
+++ b/ansible/files/irods-davrods-server/volumes/irods_environment.json
@@ -0,0 +1,9 @@
+{
+ "irods_client_server_negotiation": "request_server_negotiation",
+ "irods_client_server_policy": "CS_NEG_REFUSE",
+ "irods_encryption_key_size": 32,
+ "irods_encryption_salt_size": 8,
+ "irods_encryption_num_hash_rounds": 16,
+ "irods_encryption_algorithm": "AES-256-CBC",
+ "irods_ssl_verify_server": "hostname"
+}
diff --git a/ansible/files/irods-fuse-client/Dockerfile b/ansible/files/irods-fuse-client/Dockerfile
new file mode 100644
index 00000000..0c8f68a8
--- /dev/null
+++ b/ansible/files/irods-fuse-client/Dockerfile
@@ -0,0 +1,46 @@
+FROM ubuntu:20.04
+
+## Install official release version
+
+# ENV IRODSFS_VERSION=v0.7.3
+# ENV IRODSFS_TAR=irodsfs_amd64_linux_${IRODSFS_VERSION}.tar
+
+# RUN \
+# apt-get update \
+# && apt-get install -y \
+# fuse \
+# wget \
+# && wget https://github.com/cyverse/irodsfs/releases/download/${IRODSFS_VERSION}/${IRODSFS_TAR} \
+# && mkdir /irodsfs \
+# && tar -xf ${IRODSFS_TAR} -C /irodsfs \
+# && rm ${IRODSFS_TAR} \
+# && apt-get clean \
+# && rm -rf /var/lib/apt/lists/* /tmp/*
+
+## Install from specific commit
+ENV IRODSFS_COMMIT_SHA=f60d7e16a1ca4f1dcdddc725847ad3095bee92c2
+
+RUN \
+ apt-get update \
+ && DEBIAN_FRONTEND=noninteractive apt-get install -y \
+ fuse \
+ wget \
+ make \
+ git \
+ golang-go \
+ && apt-get clean \
+ && rm -rf /var/lib/apt/lists/* /tmp/* \
+ && mkdir /irodsfs.git \
+ && git clone https://github.com/cyverse/irodsfs /irodsfs.git \
+ && cd /irodsfs.git \
+ && git checkout ${IRODSFS_COMMIT_SHA} \
+ && make build \
+ && mkdir /irodsfs \
+ && mv /irodsfs.git/bin/* /irodsfs
+
+COPY scripts/ /scripts
+
+ARG host_owner_uid
+RUN adduser --disabled-password --gecos "" --uid "${host_owner_uid}" irods
+
+ENTRYPOINT ["/scripts/entrypoint.sh"]
diff --git a/ansible/files/irods-fuse-client/docker-compose.yml b/ansible/files/irods-fuse-client/docker-compose.yml
new file mode 100644
index 00000000..b86e90f9
--- /dev/null
+++ b/ansible/files/irods-fuse-client/docker-compose.yml
@@ -0,0 +1,13 @@
+version: "3"
+services:
+ irods-fuse:
+ build:
+ context: .
+ args:
+ host_owner_uid: ${IRODS_HOST_VOLUME_OWNER}
+ privileged: true
+ user: "${IRODS_HOST_VOLUME_OWNER}:${IRODS_HOST_VOLUME_OWNER}"
+ volumes:
+ # Volume under test
+ - "${IRODS_HOST_VOLUME}/irods-fuse:/mnt/volume_under_test:rshared"
+ - "./volumes/fuse.conf:/etc/fuse.conf"
diff --git a/ansible/files/irods-fuse-client/scripts/config.yml b/ansible/files/irods-fuse-client/scripts/config.yml
new file mode 100644
index 00000000..ca8a3516
--- /dev/null
+++ b/ansible/files/irods-fuse-client/scripts/config.yml
@@ -0,0 +1,14 @@
+host: # Set by playbook
+port: 1247
+proxy_user: rods
+client_user: rods
+zone: zone_1
+password: adminpass
+
+path_mappings:
+ - irods_path: /zone_1/home/rods
+ mapping_path: /
+ resource_type: dir
+
+
+allow_other: true
\ No newline at end of file
diff --git a/ansible/files/irods-fuse-client/scripts/entrypoint.sh b/ansible/files/irods-fuse-client/scripts/entrypoint.sh
new file mode 100755
index 00000000..3d367d84
--- /dev/null
+++ b/ansible/files/irods-fuse-client/scripts/entrypoint.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+echo Starting fuse-client...
+
+/irodsfs/irodsfs -config /scripts/config.yml /mnt/volume_under_test
+
+sleep infinity
diff --git a/ansible/files/irods-fuse-client/volumes/fuse.conf b/ansible/files/irods-fuse-client/volumes/fuse.conf
new file mode 100644
index 00000000..926d936f
--- /dev/null
+++ b/ansible/files/irods-fuse-client/volumes/fuse.conf
@@ -0,0 +1,8 @@
+# /etc/fuse.conf - Configuration file for Filesystem in Userspace (FUSE)
+
+# Set the maximum number of FUSE mounts allowed to non-root users.
+# The default is 1000.
+#mount_max = 1000
+
+# Allow non-root users to specify the allow_other or allow_root mount options.
+user_allow_other
diff --git a/ansible/files/irods-server/Dockerfile b/ansible/files/irods-server/Dockerfile
new file mode 100644
index 00000000..691e1e03
--- /dev/null
+++ b/ansible/files/irods-server/Dockerfile
@@ -0,0 +1,29 @@
+FROM ubuntu:18.04
+
+ENV IRODS_VERSION=4.2.10
+
+RUN \
+ apt-get update \
+ && apt-get install -y \
+ expect \
+ gnupg \
+ lsb-release \
+ wget \
+ && wget -qO - https://packages.irods.org/irods-signing-key.asc | apt-key add - \
+ && echo "deb [arch=amd64] https://packages.irods.org/apt/ $(lsb_release -sc) main" | tee /etc/apt/sources.list.d/renci-irods.list \
+ && apt-get update \
+ && apt-get install -y \
+ irods-runtime=${IRODS_VERSION}* \
+ irods-icommands=${IRODS_VERSION}* \
+ irods-server=${IRODS_VERSION}* \
+ irods-database-plugin-postgres=${IRODS_VERSION}* \
+ irods-resource-plugin-s3=${IRODS_VERSION}* \
+ libcurl4-gnutls-dev \
+ && apt-get clean \
+ && rm -rf /var/lib/apt/lists/* /tmp/*
+
+COPY scripts/ /scripts
+
+RUN adduser --disabled-password --gecos "" --uid 1000 irods
+
+ENTRYPOINT ["/scripts/entrypoint.sh"]
\ No newline at end of file
diff --git a/ansible/files/irods-server/docker-compose.yml b/ansible/files/irods-server/docker-compose.yml
new file mode 100644
index 00000000..fab5a938
--- /dev/null
+++ b/ansible/files/irods-server/docker-compose.yml
@@ -0,0 +1,30 @@
+version: "3"
+services:
+ irods:
+ build: .
+ hostname: irods
+ domainname: local
+ depends_on:
+ - db
+ ports:
+ # Zone Endpoint
+ - 1247:1247
+ # Control plane
+ - 1248:1248
+ # Server port range
+ - "20000-20199:20000-20199"
+ volumes:
+ # Irods client config
+ - ./volumes/config_client/:/root/.irods/
+ # Irods server config
+ - ./volumes/config_server/:/etc/irods/
+ # Volume under test
+ - ${IRODS_HOST_VOLUME}/irods:/mnt/mountedVolumeResc
+ db:
+ image: postgres:11-alpine
+ # volumes:
+ # - ./db:/var/lib/postgresql/data
+ environment:
+ POSTGRES_USER: irods
+ POSTGRES_PASSWORD: password
+ POSTGRES_DB: ICAT
diff --git a/ansible/files/irods-server/scripts/entrypoint.sh b/ansible/files/irods-server/scripts/entrypoint.sh
new file mode 100755
index 00000000..af835961
--- /dev/null
+++ b/ansible/files/irods-server/scripts/entrypoint.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+
+
+if [ -f "/.irods_installed" ]; then
+ echo iRODS already installed. Skipping Installation
+else
+ echo Installing iRODS...
+
+ python /var/lib/irods/scripts/setup_irods.py --json_configuration_file /scripts/setup_config.json
+
+ if [ $? -eq 0 ]; then
+ echo Installation done!
+ touch /.irods_installed
+ else
+ echo "setup_irods failed"
+ exit 1
+ fi
+fi
+
+echo Starting server as user 'irods'...
+su -c "python /var/lib/irods/scripts/irods_control.py start" irods
+
+sleep infinity
diff --git a/ansible/files/irods-server/scripts/setup_config.json b/ansible/files/irods-server/scripts/setup_config.json
new file mode 100644
index 00000000..6e6c10d8
--- /dev/null
+++ b/ansible/files/irods-server/scripts/setup_config.json
@@ -0,0 +1,124 @@
+{
+ "admin_password": "adminpass",
+ "host_system_information": {
+ "service_account_user_name": "irods",
+ "service_account_group_name": "irods"
+ },
+ "server_config": {
+ "catalog_provider_hosts": [
+ "irods.local"
+ ],
+ "catalog_service_role": "provider",
+ "default_hash_scheme": "SHA256",
+ "default_resource_name": "demoResc",
+ "federation": [],
+ "match_hash_policy": "compatible",
+ "negotiation_key": "uP0aes0pien1eeg8euch9eegah1Aighi",
+ "rule_engine_namespaces": [
+ ""
+ ],
+ "schema_validation_base_uri": "file:///var/lib/irods/configuration_schemas",
+ "server_control_plane_encryption_algorithm": "AES-256-CBC",
+ "server_control_plane_encryption_num_hash_rounds": 16,
+ "server_control_plane_key": "phiSai4vee9yoojai4dae6Aep8EiTh9m",
+ "server_control_plane_port": 1248,
+ "server_control_plane_timeout_milliseconds": 10000,
+ "server_port_range_end": 20199,
+ "server_port_range_start": 20000,
+ "zone_auth_scheme": "native",
+ "zone_key": "foepeiFiecaothai0ie3Oow2PooreV5o",
+ "zone_name": "zone_1",
+ "zone_port": 1247,
+ "zone_user": "rods",
+ "advanced_settings": {
+ "default_log_rotation_in_days": 5,
+ "default_number_of_transfer_threads": 4,
+ "default_temporary_password_lifetime_in_seconds": 120,
+ "dns_cache": {
+ "eviction_age_in_seconds": 3600,
+ "shared_memory_size_in_bytes": 5000000
+ },
+ "hostname_cache": {
+ "eviction_age_in_seconds": 3600,
+ "shared_memory_size_in_bytes": 2500000
+ },
+ "maximum_number_of_concurrent_rule_engine_server_processes": 4,
+ "maximum_size_for_single_buffer_in_megabytes": 32,
+ "maximum_size_of_delay_queue_in_bytes": 0,
+ "maximum_temporary_password_lifetime_in_seconds": 1000,
+ "rule_engine_server_execution_time_in_seconds": 120,
+ "rule_engine_server_sleep_time_in_seconds": 30,
+ "transfer_buffer_size_for_parallel_transfer_in_megabytes": 4,
+ "transfer_chunk_size_for_parallel_transfer_in_megabytes": 40
+ },
+ "environment_variables": {
+ "IRODS_DATABASE_USER_PASSWORD_SALT": "eux9cooseiJee1IePheiSh6jeichae8j"
+ },
+ "plugin_configuration": {
+ "authentication": {},
+ "database": {
+ "postgres": {
+ "db_host": "db",
+ "db_name": "ICAT",
+ "db_odbc_driver": "PostgreSQL Unicode",
+ "db_password": "password",
+ "db_port": 5432,
+ "db_username": "irods"
+ }
+ },
+ "network": {},
+ "resource": {},
+ "rule_engines": [
+ {
+ "instance_name": "irods_rule_engine_plugin-irods_rule_language-instance",
+ "plugin_name": "irods_rule_engine_plugin-irods_rule_language",
+ "plugin_specific_configuration": {
+ "re_data_variable_mapping_set": [
+ "core"
+ ],
+ "re_function_name_mapping_set": [
+ "core"
+ ],
+ "re_rulebase_set": [
+ "core"
+ ],
+ "regexes_for_supported_peps": [
+ "ac[^ ]*",
+ "msi[^ ]*",
+ "[^ ]*pep_[^ ]*_(pre|post|except|finally)"
+ ]
+ },
+ "shared_memory_instance": "irods_rule_language_rule_engine"
+ },
+ {
+ "instance_name": "irods_rule_engine_plugin-cpp_default_policy-instance",
+ "plugin_name": "irods_rule_engine_plugin-cpp_default_policy",
+ "plugin_specific_configuration": {}
+ }
+ ]
+ }
+ },
+ "hosts_config": {
+ "host_entries": []
+ },
+ "host_access_control_config": {
+ "access_entries": []
+ },
+ "service_account_environment": {
+ "irods_client_server_negotiation": "request_server_negotiation",
+ "irods_client_server_policy": "CS_NEG_REFUSE",
+ "irods_cwd": "/zone_1/home/rods",
+ "irods_default_hash_scheme": "SHA256",
+ "irods_default_resource": "rootResc",
+ "irods_encryption_algorithm": "AES-256-CBC",
+ "irods_encryption_key_size": 32,
+ "irods_encryption_num_hash_rounds": 16,
+ "irods_encryption_salt_size": 8,
+ "irods_home": "/zone_1/home/rods",
+ "irods_host": "irods.local",
+ "irods_match_hash_policy": "compatible",
+ "irods_port": 1247,
+ "irods_user_name": "rods",
+ "irods_zone_name": "zone_1"
+ }
+}
\ No newline at end of file
diff --git a/ansible/files/irods-server/volumes/config_client/irods_environment.json b/ansible/files/irods-server/volumes/config_client/irods_environment.json
new file mode 100644
index 00000000..e3234ea3
--- /dev/null
+++ b/ansible/files/irods-server/volumes/config_client/irods_environment.json
@@ -0,0 +1,6 @@
+{
+ "irods_host": "localhost",
+ "irods_port": 1247,
+ "irods_user_name": "rods",
+ "irods_zone_name": "zone_1"
+}
diff --git a/ansible/files/irods-server/volumes/config_server/.gitignore b/ansible/files/irods-server/volumes/config_server/.gitignore
new file mode 100644
index 00000000..e69de29b
diff --git a/ansible/files/irods-webdav-client/Dockerfile b/ansible/files/irods-webdav-client/Dockerfile
new file mode 100644
index 00000000..65a8f756
--- /dev/null
+++ b/ansible/files/irods-webdav-client/Dockerfile
@@ -0,0 +1,17 @@
+FROM ubuntu:20.04
+
+RUN \
+ apt-get update \
+ && apt-get install -y \
+ davfs2 \
+ && apt-get clean \
+ && rm -rf /var/lib/apt/lists/* /tmp/*
+
+COPY scripts/ /scripts
+
+RUN echo "/mnt/volume_under_test rods adminpass" >> /etc/davfs2/secrets
+
+ARG host_owner_uid
+RUN adduser --disabled-password --gecos "" --uid "${host_owner_uid}" irods
+
+ENTRYPOINT ["/scripts/entrypoint.sh"]
diff --git a/ansible/files/irods-webdav-client/docker-compose.yml b/ansible/files/irods-webdav-client/docker-compose.yml
new file mode 100644
index 00000000..65598b2b
--- /dev/null
+++ b/ansible/files/irods-webdav-client/docker-compose.yml
@@ -0,0 +1,14 @@
+version: "3"
+services:
+ irods-webdav:
+ build:
+ context: .
+ args:
+ host_owner_uid: ${IRODS_HOST_VOLUME_OWNER}
+ privileged: true
+ volumes:
+ # Configure mount for non-root users
+ - ./volumes/fstab:/etc/fstab
+ - ./volumes/davfs2.conf:/etc/davfs2/davfs2.conf
+ # Volume under test
+ - "${IRODS_HOST_VOLUME}/irods-webdav:/mnt/volume_under_test:rshared"
diff --git a/ansible/files/irods-webdav-client/scripts/entrypoint.sh b/ansible/files/irods-webdav-client/scripts/entrypoint.sh
new file mode 100755
index 00000000..3e7246e3
--- /dev/null
+++ b/ansible/files/irods-webdav-client/scripts/entrypoint.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+echo Starting webdav-client...
+
+mount /mnt/volume_under_test
+
+sleep infinity
diff --git a/ansible/files/irods-webdav-client/volumes/davfs2.conf b/ansible/files/irods-webdav-client/volumes/davfs2.conf
new file mode 100644
index 00000000..87ae1627
--- /dev/null
+++ b/ansible/files/irods-webdav-client/volumes/davfs2.conf
@@ -0,0 +1,77 @@
+# davfs2 configuration file 2014-08-10
+# version 12
+# ------------------------------------
+
+# Copyright (C) 2006, 2007, 2008, 2009, 2012, 2013, 2014 Werner Baumann
+
+# Copying and distribution of this file, with or without modification, are
+# permitted in any medium without royalty provided the copyright notice
+# and this notice are preserved.
+
+
+# Please read the davfs2.conf (5) man page for a description of the
+# configuration options and syntax rules.
+
+
+# Available options and default values
+# ====================================
+
+# General Options
+# ---------------
+
+# dav_user davfs2 # system wide config file only
+# dav_group davfs2 # system wide config file only
+# kernel_fs fuse
+# buf_size 16 # KiByte
+
+# WebDAV Related Options
+# ----------------------
+
+# use_proxy 1 # system wide config file only
+# proxy # system wide config file only
+# trust_ca_cert
+# servercert # deprecated: use trust_ca_cert
+# trust_server_cert
+# clientcert
+# secrets ~/.davfs2/secrets # user config file only
+# ask_auth 1
+# use_locks 1
+# lock_owner
+# lock_timeout 1800 # seconds
+# lock_refresh 60 # seconds
+# use_expect100 0
+# if_match_bug 0
+# drop_weak_etags 0
+# n_cookies 0
+# precheck 1
+# ignore_dav_header 0
+# use_compression 0
+# min_propset 0
+# follow_redirect 0
+# server_charset
+# connect_timeout 10 # seconds
+# read_timeout 30 # seconds
+# retry 30 # seconds
+# max_retry 300 # seconds
+# add_header
+
+# Cache Related Options
+# ---------------------
+
+# backup_dir lost+found
+# cache_dir /var/cache/davfs2 # system wide cache
+# ~/.davfs2/cache # per user cache
+# cache_size 50 # MiByte
+# table_size 1024
+# dir_refresh 60 # seconds
+# file_refresh 1 # second
+delay_upload 0
+# gui_optimize 0
+# minimize_mem 0
+
+# Debugging Options
+# -----------------
+
+# debug # possible values: config, kernel, cache, http, xml,
+ # httpauth, locks, ssl, httpbody, secrets, most
+
diff --git a/ansible/files/irods-webdav-client/volumes/fstab.j2 b/ansible/files/irods-webdav-client/volumes/fstab.j2
new file mode 100644
index 00000000..994ddf8d
--- /dev/null
+++ b/ansible/files/irods-webdav-client/volumes/fstab.j2
@@ -0,0 +1 @@
+http://{{ irods_server_host }} /mnt/volume_under_test davfs rw,user,uid={{davrods_owner_uid}},gid={{davrods_owner_uid}},noauto,_netdev 0 0
diff --git a/ansible/files/tool-dd/Dockerfile b/ansible/files/tool-dd/Dockerfile
new file mode 100644
index 00000000..2c045c36
--- /dev/null
+++ b/ansible/files/tool-dd/Dockerfile
@@ -0,0 +1,6 @@
+FROM ubuntu:22.04
+
+COPY entrypoint.sh /entrypoint.sh
+RUN chmod +x /entrypoint.sh
+
+ENTRYPOINT [ "/entrypoint.sh" ]
diff --git a/ansible/files/tool-dd/entrypoint.sh b/ansible/files/tool-dd/entrypoint.sh
new file mode 100755
index 00000000..aec59136
--- /dev/null
+++ b/ansible/files/tool-dd/entrypoint.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+dd bs=$DD_BS if=$DD_IF of=$DD_OF conv=fsync count=$DD_COUNT &
+dd bs=$DD_BS if=$DD_IF of=$DD_OF conv=fsync count=$DD_COUNT seek=$DD_SEEK_1 skip=$DD_SKIP_1 &
+dd bs=$DD_BS if=$DD_IF of=$DD_OF conv=fsync count=$DD_COUNT seek=$DD_SEEK_2 skip=$DD_SKIP_2 &
+dd bs=$DD_BS if=$DD_IF of=$DD_OF conv=fsync count=$DD_COUNT seek=$DD_SEEK_3 skip=$DD_SKIP_3 &
+wait
diff --git a/ansible/files/tool-fio/Dockerfile b/ansible/files/tool-fio/Dockerfile
new file mode 100644
index 00000000..d100e1b9
--- /dev/null
+++ b/ansible/files/tool-fio/Dockerfile
@@ -0,0 +1,22 @@
+FROM ubuntu:22.04
+
+ENV FIO_SRC_REF=fio-3.30
+
+RUN \
+ apt-get update \
+ && apt-get install -y \
+ build-essential \
+ libaio1 \
+ libaio-dev \
+ git \
+ && apt-get clean \
+ && rm -rf /var/lib/apt/lists/* /tmp/* \
+ && mkdir /fio.git \
+ && git clone https://github.com/axboe/fio /fio.git \
+ && cd /fio.git \
+ && git checkout ${FIO_SRC_REF} \
+ && ./configure \
+ && make \
+ && make install
+
+ENTRYPOINT [ "fio" ]
\ No newline at end of file
diff --git a/ansible/files/tool-mdtest/Dockerfile b/ansible/files/tool-mdtest/Dockerfile
new file mode 100644
index 00000000..1a1cfc2f
--- /dev/null
+++ b/ansible/files/tool-mdtest/Dockerfile
@@ -0,0 +1,24 @@
+FROM ubuntu:20.04
+
+ENV MDTEST_VERSION_TAG=3.3.0
+
+RUN \
+ apt-get update \
+ && DEBIAN_FRONTEND=noninteractive apt-get install -y \
+ git automake autoconf make gcc mpich \
+ && apt-get clean \
+ && rm -rf /var/lib/apt/lists/* /tmp/* \
+ && git clone https://github.com/hpc/ior /mdtest \
+ && cd /mdtest \
+ && git checkout ${MDTEST_VERSION_TAG} \
+ && ./bootstrap \
+ && mkdir build \
+ && cd build \
+ && ../configure \
+ && make \
+ && make install \
+ && mkdir /mnt/volume_under_test
+
+WORKDIR /mnt/volume_under_test
+
+ENTRYPOINT [ "mdtest" ]
\ No newline at end of file
diff --git a/ansible/files/tool-s3-benchmark/Dockerfile b/ansible/files/tool-s3-benchmark/Dockerfile
new file mode 100644
index 00000000..2e1b2086
--- /dev/null
+++ b/ansible/files/tool-s3-benchmark/Dockerfile
@@ -0,0 +1,14 @@
+FROM golang:1.15-alpine
+
+RUN apk add --update --no-cache \
+ git \
+ && git clone https://github.com/chinglinwen/s3-benchmark.git /s3-benchmark \
+ && cd /s3-benchmark \
+ && git checkout tags/v1.0-beta -b branch-v1.0-beta \
+ && go get code.cloudfoundry.org/bytefmt \
+ && go get github.com/aws/aws-sdk-go/aws \
+ && go get github.com/jmespath/go-jmespath \
+ && go build s3-benchmark.go
+
+WORKDIR /s3-benchmark
+ENTRYPOINT [ "./s3-benchmark" ]
\ No newline at end of file
diff --git a/ansible/files/tool-warp/Dockerfile b/ansible/files/tool-warp/Dockerfile
new file mode 100644
index 00000000..4db17788
--- /dev/null
+++ b/ansible/files/tool-warp/Dockerfile
@@ -0,0 +1,21 @@
+FROM golang:1.14.7-alpine
+
+WORKDIR /warp
+
+ENV CGO_ENABLED=0
+
+RUN apk add --update --no-cache \
+ git \
+ && git clone https://github.com/minio/warp /warp \
+ && cd /warp \
+ && git checkout tags/v0.5.5 -b branch-v0.5.5 \
+ && go mod download \
+ && go build -ldflags '-w -s' -a -o warp .
+
+
+FROM scratch
+
+COPY --from=0 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
+COPY --from=0 /warp/warp /warp
+
+ENTRYPOINT ["/warp"]
\ No newline at end of file
diff --git a/ansible/install_docker.yml b/ansible/install_docker.yml
new file mode 100644
index 00000000..e880f20b
--- /dev/null
+++ b/ansible/install_docker.yml
@@ -0,0 +1,59 @@
+- hosts: "{{ host }}"
+ become: true
+ tasks:
+
+ - name: Check if Docker is installed
+ command: docker --version
+ register: docker_valid
+ ignore_errors: yes
+
+ # Install docker if not present
+ - name: Install docker dependencies
+ when: docker_valid.failed
+ apt: name={{ item }} state=latest update_cache=yes
+ loop:
+ - 'apt-transport-https'
+ - 'ca-certificates'
+ - 'curl'
+ - 'software-properties-common'
+ - 'python3-pip'
+ - 'virtualenv'
+ - 'python3-setuptools'
+ - name: Add Docker GPG key
+ when: docker_valid.failed
+ apt_key:
+ url: https://download.docker.com/linux/ubuntu/gpg
+ state: present
+ - name: Add Docker repository
+ when: docker_valid.failed
+ apt_repository:
+ repo: deb https://download.docker.com/linux/ubuntu bionic stable
+ state: present
+ - name: Install Docker
+ when: docker_valid.failed
+ apt: update_cache=yes name=docker-ce state=latest
+
+ - name: Check if Docker-compose is installed
+ command: docker-compose --version
+ register: docker_compose_valid
+ ignore_errors: yes
+
+ # Install docker-compose if not present
+ - name: Install docker-compose dependencies
+ when: docker_compose_valid.failed
+ apt: name={{ item }} state=latest update_cache=yes
+ loop:
+ - python3-pip
+ - python3-dev
+ - libffi-dev
+ - librust-openssl-dev
+ - gcc
+ - libc-dev
+ - rustc
+ - cargo
+ - make
+ - name: Install docker-compose
+ when: docker_compose_valid.failed
+ pip:
+ name:
+ - docker-compose
diff --git a/ansible/inventory/.gitignore b/ansible/inventory/.gitignore
new file mode 100644
index 00000000..8f4119ff
--- /dev/null
+++ b/ansible/inventory/.gitignore
@@ -0,0 +1,3 @@
+*
+!.gitignore
+!example.yml
\ No newline at end of file
diff --git a/ansible/inventory/example.yml b/ansible/inventory/example.yml
new file mode 100644
index 00000000..d790a4c4
--- /dev/null
+++ b/ansible/inventory/example.yml
@@ -0,0 +1,7 @@
+all:
+ # For more details see https://docs.ansible.com/ansible/latest/user_guide/intro_inventory.html
+ hosts:
+ irods_server:
+ ansible_host: #TODO
+ irods_client:
+ ansible_host: #TODO
diff --git a/ansible/run_dd_benchmark.yml b/ansible/run_dd_benchmark.yml
new file mode 100644
index 00000000..4682ad17
--- /dev/null
+++ b/ansible/run_dd_benchmark.yml
@@ -0,0 +1,101 @@
+- hosts: "{{ host }}"
+ tasks:
+ - name: Create test directory
+ file:
+ path: "{{ dd_dir }}/dd_in_progress"
+ state: directory
+
+ - name: Copy Dockerfile
+ ansible.posix.synchronize:
+ src: tool-dd/
+ dest: "{{ dd_dir }}/dd_build"
+ recursive: yes
+
+ - name: Build latest dd image
+ become: yes
+ community.docker.docker_image:
+ build:
+ path: "{{ dd_dir }}/dd_build"
+ name: galaxybenchmarker/tool-dd
+ tag: latest
+ force_source: yes
+ force_tag: yes
+ source: build
+
+ - name: Clear in-ram caches
+ become: yes
+ copy:
+ content: 3
+ dest: /proc/sys/vm/drop_caches
+ unsafe_writes: yes
+
+ - name: Run dd container (single dd)
+ become: yes
+ when: not dd_parallel|bool
+ community.docker.docker_container:
+ name: dd
+ image: galaxybenchmarker/tool-dd
+ detach: false
+ cleanup: true
+ volumes:
+ - "{{ dd_dir }}/dd_in_progress:/mnt/volume_under_test:rw"
+ working_dir: /mnt/volume_under_test
+ entrypoint: "dd"
+ command: [
+ "bs={{ dd_blocksize }}",
+ "count={{ dd_blockcount }}",
+ "if={{ dd_input }}",
+ "of={{ dd_output }}",
+ "{% if dd_flush|bool %}conv=fsync{% endif %}",
+ ]
+ register: dd_single_results
+
+ - name: Save results (single dd)
+ when: not dd_parallel|bool
+ local_action:
+ module: copy
+ content: "{{ dd_single_results.container.Output }}"
+ dest: "{{ controller_dir }}/{{ dd_result_file }}"
+
+ - name: Run dd container (parallel dd)
+ become: yes
+ when: dd_parallel|bool
+ community.docker.docker_container:
+ name: dd
+ image: galaxybenchmarker/tool-dd
+ detach: false
+ cleanup: true
+ volumes:
+ - "{{ dd_dir }}/dd_in_progress:/mnt/volume_under_test:rw"
+ working_dir: /mnt/volume_under_test
+ env:
+ DD_BS: "{{ dd_blocksize }}"
+ DD_IF: "{{ dd_input }}"
+ DD_OF: "{{ dd_output }}"
+ DD_COUNT: "{{ dd_blockcount|int // 4 }}"
+ DD_SEEK_1: "{{ dd_blockcount|int // 4 * 1 }}"
+ DD_SKIP_1: "{{ dd_blockcount|int // 4 * 1 }}"
+ DD_SEEK_2: "{{ dd_blockcount|int // 4 * 2 }}"
+ DD_SKIP_2: "{{ dd_blockcount|int // 4 * 2 }}"
+ DD_SEEK_3: "{{ dd_blockcount|int // 4 * 3 }}"
+ DD_SKIP_3: "{{ dd_blockcount|int // 4 * 3 }}"
+ register: dd_parallel_results
+
+ # - name: debug
+ # debug: var=dd_results
+
+ - name: Save results (parallel dd)
+ when: dd_parallel|bool
+ local_action:
+ module: copy
+ content: "{{ dd_parallel_results.container.Output }}"
+ dest: "{{ controller_dir }}/{{ dd_result_file }}"
+
+ - name: Cleanup
+ file:
+ path: "{{ dd_dir }}/{{ item }}"
+ state: absent
+ when: dd_cleanup|bool
+ loop:
+ - dd_in_progress
+ - dd_build
diff --git a/ansible/run_fio_benchmark.yml b/ansible/run_fio_benchmark.yml
new file mode 100644
index 00000000..dbec1b66
--- /dev/null
+++ b/ansible/run_fio_benchmark.yml
@@ -0,0 +1,187 @@
+- hosts: "{{ host }}"
+ tasks:
+ - name: Run fio benchmark
+ block:
+ - name: Create test directory
+ file:
+ path: "{{ fio_dir }}/fio_in_progress"
+ state: directory
+ mode: '0777'
+
+ - name: Create temporary build directory
+ tempfile:
+ state: directory
+ suffix: .fio.build
+ register: tempdir
+
+ - name: Copy Dockerfile
+ ansible.posix.synchronize:
+ src: tool-fio/
+ dest: "{{ tempdir.path }}"
+ recursive: yes
+
+ - name: Build latest fio image
+ become: yes
+ community.docker.docker_image:
+ build:
+ path: "{{ tempdir.path }}"
+ name: galaxybenchmarker/tool-fio
+ tag: latest
+ force_source: yes
+ force_tag: yes
+ source: build
+
+ - name: Prepare read benchmark
+ when: fio_mode in ["read", "randread"]
+ block:
+ - name: Prepare in /tmp and move to destination
+ when: fio_prepare_read_benchmark_in_tmp|bool
+ block:
+ - name: Run fio container (create files)
+ become: yes
+ community.docker.docker_container:
+ name: fio
+ image: galaxybenchmarker/tool-fio
+ detach: false
+ cleanup: true
+ volumes:
+ - "{{ tempdir.path }}:/results:rw"
+ - "/tmp/fio_in_progress:/mnt/volume_under_test:rw"
+ working_dir: /mnt/volume_under_test
+ command: [
+ "--bs={{ fio_blocksize }}",
+ "--direct=1",
+ "--end_fsync=1",
+ "--filename=testfile.fio",
+ "--group_reporting",
+ "--iodepth={{ fio_iodepth }}",
+ "--ioengine=libaio",
+ "--name={{ fio_jobname }}",
+ "--numjobs={{ fio_numjobs }}",
+ "--output", "/results/{{ fio_result_file }}",
+ "--output-format=json",
+ "{% if fio_refill_buffers|bool %}--refill_buffers{% endif %}",
+ "{% if fio_time_based|bool %}--runtime={{ fio_runtime_in_s }}{% endif %}",
+ "--rw={{ fio_mode }}",
+ "--size={{ fio_filesize }}",
+ "{% if fio_time_based|bool %}--time_based{% endif %}",
+ "--create_only=1",
+ ]
+ register: fio_results
+ ignore_errors: yes
+
+ - name: debug
+ debug: var=fio_results
+
+ - name: Move testfile to {{ fio_dir }}/fio_in_progress
+ become: yes
+ command: mv /tmp/fio_in_progress/testfile.fio {{ fio_dir }}/fio_in_progress
+
+ - name: Change owner of testfile
+ become: yes
+ command: chown 1000:1000 {{ fio_dir }}/fio_in_progress/testfile.fio
+
+ - name: Prepare in destination
+ when: not fio_prepare_read_benchmark_in_tmp|bool
+ block:
+ - name: Run fio container (create files)
+ become: yes
+ community.docker.docker_container:
+ name: fio
+ image: galaxybenchmarker/tool-fio
+ detach: false
+ cleanup: true
+ volumes:
+ - "{{ tempdir.path }}:/results:rw"
+ - "{{ fio_dir }}/fio_in_progress:/mnt/volume_under_test:rw"
+ working_dir: /mnt/volume_under_test
+ command: [
+ "--bs={{ fio_blocksize }}",
+ "--direct=1",
+ "--end_fsync=1",
+ "--filename=testfile.fio",
+ "--group_reporting",
+ "--iodepth={{ fio_iodepth }}",
+ "--ioengine=libaio",
+ "--name={{ fio_jobname }}",
+ "--numjobs={{ fio_numjobs }}",
+ "--output", "/results/{{ fio_result_file }}",
+ "--output-format=json",
+ "{% if fio_refill_buffers|bool %}--refill_buffers{% endif %}",
+ "{% if fio_time_based|bool %}--runtime={{ fio_runtime_in_s }}{% endif %}",
+ "--rw={{ fio_mode }}",
+ "--size={{ fio_filesize }}",
+ "{% if fio_time_based|bool %}--time_based{% endif %}",
+ "--create_only=1",
+ ]
+ register: fio_results
+ ignore_errors: yes
+
+ - name: debug
+ debug: var=fio_results
+
+ # Stat breaks with OSError
+ # - name: Check if file exists
+ # stat: path={{ fio_dir }}/fio_in_progress/testfile.fio
+ # when: fio_mode in ["read", "randread"]
+ # register: stat_result
+ - name: Check if file exists (1)
+ shell: ls -la {{ fio_dir }}/fio_in_progress/testfile.fio
+ register: ls_result
+
+ - name: Wait for file
+ when: not ("{{ fio_filesize_in_bytes }}" in ls_result.stdout)
+ block:
+ - name: Pause for 10 seconds to wait for file
+ pause:
+ seconds: 10
+
+ - name: Check if file exists (2)
+ shell: ls -la {{ fio_dir }}/fio_in_progress/testfile.fio
+ register: ls_result2
+
+ - name: Wait for file looonger
+ when: not ("{{ fio_filesize_in_bytes }}" in ls_result2.stdout)
+ block:
+
+ - name: Pause for 1 minute to wait for file
+ pause:
+ minutes: 1
+
+ - name: Check if file exists (3)
+ shell: ls -la {{ fio_dir }}/fio_in_progress/testfile.fio
+ register: ls_result3
+
+ - name: Wait for file loooooonger
+ when: not ("{{ fio_filesize_in_bytes }}" in ls_result3.stdout)
+ block:
+
+ - name: Pause for 1 more minute to wait for file
+ pause:
+ minutes: 1
+
+ - name: Check if file exists (4)
+ shell: ls -la {{ fio_dir }}/fio_in_progress/testfile.fio
+ register: ls_result4
+ failed_when: not ("{{ fio_filesize_in_bytes }}" in ls_result4.stdout)
+
+ - include_tasks: tasks/run_fio_and_check_result.yml
+
+ - name: Collect result
+ ansible.builtin.fetch:
+ src: "{{ tempdir.path }}/{{ fio_result_file }}"
+ dest: "{{ controller_dir }}/{{ fio_result_file }}"
+ flat: yes
+
+ always:
+ - name: Cleanup
+ file:
+ path: "{{ item }}"
+ state: absent
+ loop:
+ - "{{ fio_dir }}/fio_in_progress"
+ - "{{ tempdir.path }}"
+ retries: 15
+ delay: 5
+ register: cleanup
+ until: not cleanup["failed"]
diff --git a/ansible/run_fio_benchmark_not_containerized.yml b/ansible/run_fio_benchmark_not_containerized.yml
new file mode 100644
index 00000000..4ad00895
--- /dev/null
+++ b/ansible/run_fio_benchmark_not_containerized.yml
@@ -0,0 +1,45 @@
+- hosts: "{{ host }}"
+ tasks:
+ - name: Cleanup possible left over files
+ file:
+ path: "{{ fio_dir }}/fio_in_progress"
+ state: absent
+ - name: Create test directory
+ file:
+ path: "{{ fio_dir }}/fio_in_progress"
+ state: directory
+
+ - name: Execute the fio test
+ ansible.builtin.command: |
+ fio
+ --rw={{ fio_mode }}
+ --name={{ fio_jobname }}
+ --bs={{ fio_blocksize }}
+ --direct=1
+ --numjobs={{ fio_numjobs }}
+ --ioengine=libaio
+ --iodepth={{ fio_iodepth }}
+ --group_reporting
+ --runtime={{ fio_runtime_in_s }}
+ --size={{ fio_filesize }}
+ --output-format=json
+ --filename=testfile.fio
+ --output {{ fio_result_file }}
+ {% if fio_time_based|bool %} --time_based {% endif %}
+ {% if fio_refill_buffers|bool %} --refill_buffers {% endif %}
+ --exec_prerun="./scripts/barrier.py localhost 42042"
+ args:
+ chdir: "{{ fio_dir }}/fio_in_progress"
+ become: yes
+ register: fio_status
+
+ - name: Collect result
+ ansible.builtin.fetch:
+ src: "{{ fio_dir }}/fio_in_progress/{{ fio_result_file }}"
+ dest: "{{ controller_dir }}/{{ fio_result_file }}"
+ flat: yes
+
+ - name: Cleanup
+ file:
+ path: "{{ fio_dir }}/fio_in_progress"
+ state: absent
\ No newline at end of file
diff --git a/ansible/run_fio_benchmark_not_containerized_check.yml b/ansible/run_fio_benchmark_not_containerized_check.yml
new file mode 100644
index 00000000..797561b4
--- /dev/null
+++ b/ansible/run_fio_benchmark_not_containerized_check.yml
@@ -0,0 +1,10 @@
+- hosts: "{{ host }}"
+ tasks:
+ - name: "Check if fio is installed"
+ shell: fio --help
+ register: package_check
+ ignore_errors: true
+ - name: "Error handling"
+ fail:
+ msg: "fio has to be installed"
+ when: package_check is failed
\ No newline at end of file
diff --git a/ansible/run_galaxy_job.yml b/ansible/run_galaxy_job.yml
new file mode 100644
index 00000000..25af5a49
--- /dev/null
+++ b/ansible/run_galaxy_job.yml
@@ -0,0 +1,3 @@
+- hosts: "{{ host }}"
+ tasks:
+ - include_tasks: tasks/run_galaxy_job.yml
diff --git a/ansible/run_galaxy_mount_benchmark.yml b/ansible/run_galaxy_mount_benchmark.yml
new file mode 100644
index 00000000..f9b742d8
--- /dev/null
+++ b/ansible/run_galaxy_mount_benchmark.yml
@@ -0,0 +1,10 @@
+- hosts: "{{ host }}"
+ tasks:
+ - include_tasks: tasks/run_galaxy_job.yml
+
+ - name: Check number of files
+ shell: find {{glx_path_to_files}} -type f -size {{ glx_expected_size_in_bytes }}c | wc -l
+ register: result
+ until: result.stdout.find("{{glx_expected_num_files}}") != -1
+ retries: "{{ glx_verification_timeout_in_s|int // 5 }}"
+ delay: 5
\ No newline at end of file
diff --git a/ansible/run_mdtest_benchmark.yml b/ansible/run_mdtest_benchmark.yml
new file mode 100644
index 00000000..aab8bcfa
--- /dev/null
+++ b/ansible/run_mdtest_benchmark.yml
@@ -0,0 +1,70 @@
+- hosts: "{{ host }}"
+ tasks:
+ - name: Create test directory
+ file:
+ path: "{{ mdtest_dir }}/mdtest_in_progress"
+ state: directory
+ mode: '0777'
+
+ - name: Create temporary build directory
+ tempfile:
+ state: directory
+ suffix: .mdtest.build
+ register: tempdir
+
+ - name: Copy Dockerfile
+ ansible.posix.synchronize:
+ src: tool-mdtest/
+ dest: "{{ tempdir.path }}"
+ recursive: yes
+
+ - name: Build latest mdtest image
+ become: yes
+ community.docker.docker_image:
+ build:
+ path: "{{ tempdir.path }}"
+ name: galaxybenchmarker/tool-mdtest
+ tag: latest
+ force_source: yes
+ force_tag: yes
+ source: build
+
+ - name: Run mdtest container
+ become: yes
+ community.docker.docker_container:
+ name: mdtest
+ image: galaxybenchmarker/tool-mdtest
+ detach: false
+ cleanup: true
+ volumes:
+ - "{{ mdtest_dir }}/mdtest_in_progress:/mnt/volume_under_test:rw"
+ working_dir: /mnt/volume_under_test
+ command: [
+ "-y",
+ "-Y",
+ "-n",
+ "{{ mdtest_num_files }}",
+ ]
+ register: mdtest_results
+ ignore_errors: yes
+
+ - name: debug
+ debug: var=mdtest_results
+
+ - name: Save results
+ local_action:
+ module: copy
+ content: "{{ mdtest_results.container.Output }}"
+ dest: "{{ controller_dir }}/{{ mdtest_result_file }}"
+
+ - name: Cleanup
+ file:
+ path: "{{ item }}"
+ state: absent
+ loop:
+ - "{{ mdtest_dir }}/mdtest_in_progress"
+ - "{{ tempdir.path }}"
+ retries: 15
+ delay: 5
+ register: cleanup
+ until: not cleanup["failed"]
diff --git a/ansible/run_s3-benchmark_benchmark.yml b/ansible/run_s3-benchmark_benchmark.yml
new file mode 100644
index 00000000..ded65e96
--- /dev/null
+++ b/ansible/run_s3-benchmark_benchmark.yml
@@ -0,0 +1,59 @@
+- hosts: "{{ host }}"
+ tasks:
+ - name: Create temporary build directory
+ tempfile:
+ state: directory
+ suffix: .s3-benchmark.build
+ register: tempdir
+
+ - name: Copy Dockerfile
+ ansible.posix.synchronize:
+ src: tool-s3-benchmark/
+ dest: "{{ tempdir.path }}"
+ recursive: yes
+
+ - name: Build latest s3-benchmark image
+ become: yes
+ community.docker.docker_image:
+ build:
+ path: "{{ tempdir.path }}"
+ name: galaxybenchmarker/tool-s3-benchmark
+ tag: latest
+ force_source: yes
+ force_tag: yes
+ source: build
+
+ - name: Run s3-benchmark container
+ become: yes
+ community.docker.docker_container:
+ name: s3-benchmark
+ image: galaxybenchmarker/tool-s3-benchmark
+ detach: false
+ cleanup: true
+ command: [
+ "-a", "{{ s3b_access_key_id }}",
+ "-b", "{{ s3b_bucket_name }}",
+ "-d", "{{ s3b_runtime_in_s }}",
+ "-s", "{{ s3b_secret_access_key }}",
+ "-t", "{{ s3b_threads }}",
+ "-u", "{{ s3b_base_url }}",
+ "-r", "{{ s3b_region }}",
+ "-z", "{{ s3b_filesize}}",
+ ]
+ register: s3b_results
+
+ - name: debug
+ debug: var=s3b_results
+
+ - name: Save results
+ local_action:
+ module: copy
+ content: "{{ s3b_results.container.Output }}"
+ dest: "{{ controller_dir }}/{{ s3b_result_file }}"
+
+ - name: Cleanup
+ file:
+ path: "{{ item }}"
+ state: absent
+ loop:
+ - tempdir.path
diff --git a/ansible/run_warp_benchmark.yml b/ansible/run_warp_benchmark.yml
new file mode 100644
index 00000000..64328dc8
--- /dev/null
+++ b/ansible/run_warp_benchmark.yml
@@ -0,0 +1,61 @@
+- hosts: "{{ host }}"
+ tasks:
+ - name: Create temporary build directory
+ tempfile:
+ state: directory
+ suffix: .warp.build
+ register: tempdir
+
+ - name: Copy Dockerfile
+ ansible.posix.synchronize:
+ src: tool-warp/
+ dest: "{{ tempdir.path }}"
+ recursive: yes
+
+ - name: Build latest warp image
+ become: yes
+ community.docker.docker_image:
+ build:
+ path: "{{ tempdir.path }}"
+ name: galaxybenchmarker/tool-warp
+ tag: latest
+ force_source: yes
+ force_tag: yes
+ source: build
+
+ - name: Run warp container
+ become: yes
+ community.docker.docker_container:
+ name: warp
+ image: galaxybenchmarker/tool-warp
+ detach: false
+ cleanup: true
+ command: [
+ "{{ warp_mode }}",
+ "--access-key", "{{ warp_access_key_id }}",
+ "--bucket", "{{ warp_bucket_name }}",
+ "--concurrent", "{{ warp_concurrent_ops }}",
+ "--duration", "{{ warp_runtime }}",
+ "--host", "{{ warp_base_url }}",
+ "--obj.size", "{{ warp_filesize}}",
+ "--region", "{{ warp_region }}",
+ "--secret-key", "{{ warp_secret_access_key }}",
+ "--tls",
+ ]
+ register: warp_results
+
+ - name: debug
+ debug: var=warp_results
+
+ - name: Save results
+ local_action:
+ module: copy
+ content: "{{ warp_results.container.Output }}"
+ dest: "{{ controller_dir }}/{{ warp_result_file }}"
+
+ - name: Cleanup
+ file:
+ path: "{{ item }}"
+ state: absent
+ loop:
+ - tempdir.path
diff --git a/ansible/setup_galaxy_server.yml b/ansible/setup_galaxy_server.yml
new file mode 100644
index 00000000..9aa896a3
--- /dev/null
+++ b/ansible/setup_galaxy_server.yml
@@ -0,0 +1,133 @@
+- hosts: "{{ host }}"
+ become: true
+ tasks:
+ - name: Copy container setup onto host (sychronize preserves file permissions)
+ ansible.posix.synchronize:
+ src: ../files/galaxy-server/
+ dest: /galaxy
+ recursive: yes
+
+ - include_tasks: tasks/initial_start_galaxy.yml
+
+ - name: Setup mount backend
+ when: (galaxy_use_mount is defined) and (galaxy_use_mount|bool)
+ block:
+ - name: Create galaxy files directory
+ file:
+ path: "{{ galaxy_host_volume }}/galaxy_in_progress"
+ state: directory
+ mode: '0777'
+
+ - name: Update docker-compose with object store config
+ lineinfile:
+ path: /galaxy/docker-compose.yml
+ backrefs: yes
+ regexp: '^(.*)# PLACEHOLDER_ENV'
+ line: '\1- GALAXY_CONFIG_OBJECT_STORE_CONFIG_FILE=/custom_config/object_store_conf.local_mount.xml'
+
+ - name: Update docker-compose volume mounts
+ lineinfile:
+ path: /galaxy/docker-compose.yml
+ backrefs: yes
+ regexp: '^(.*)# PLACEHOLDER_VOLUMES'
+ line: '\1- ${GALAXY_HOST_VOLUME}/galaxy_in_progress:/mnt/volume_under_test'
+
+ - name: Restart galaxy
+ community.docker.docker_compose:
+ project_src: /galaxy
+ restarted: yes
+ environment:
+ GALAXY_HOST_VOLUME: "{{ galaxy_host_volume }}"
+ GALAXY_EXPORT_VOLUME: "{{ galaxy_export_volume }}/glx_export"
+
+ - name: Setup s3 backend
+ when: (galaxy_use_s3 is defined) and (galaxy_use_s3|bool)
+ block:
+ - name: Update object store config with credentials
+ template:
+ src: ./files/galaxy-server/galaxy/custom_config/object_store_conf.s3.xml.j2
+ dest: /galaxy/galaxy/custom_config/object_store_conf.s3.xml
+ owner: 1450
+ group: 1450
+ mode: '0664'
+
+ - name: Update docker-compose with object store config
+ lineinfile:
+ path: /galaxy/docker-compose.yml
+ backrefs: yes
+ regexp: '^(.*)# PLACEHOLDER_ENV'
+ line: '\1- GALAXY_CONFIG_OBJECT_STORE_CONFIG_FILE=/custom_config/object_store_conf.s3.xml'
+
+ - name: Restart galaxy
+ community.docker.docker_compose:
+ project_src: /galaxy
+ restarted: yes
+ environment:
+ GALAXY_EXPORT_VOLUME: "{{ galaxy_export_volume }}/glx_export"
+
+ - name: Setup irods backend
+ when: (galaxy_use_irods is defined) and (galaxy_use_irods|bool)
+ block:
+ - name: Update object store config with connection details
+ template:
+ src: ./files/galaxy-server/galaxy/custom_config/object_store_conf.irods.xml.j2
+ dest: /galaxy/galaxy/custom_config/object_store_conf.irods.xml
+ owner: 1450
+ group: 1450
+ mode: '0664'
+
+ - name: Update docker-compose with object store config
+ lineinfile:
+ path: /galaxy/docker-compose.yml
+ backrefs: yes
+ regexp: '^(.*)# PLACEHOLDER_ENV'
+ line: '\1- GALAXY_CONFIG_OBJECT_STORE_CONFIG_FILE=/custom_config/object_store_conf.irods.xml'
+
+ - name: Restart galaxy
+ community.docker.docker_compose:
+ project_src: /galaxy
+ restarted: yes
+ environment:
+ GALAXY_EXPORT_VOLUME: "{{ galaxy_export_volume }}/glx_export"
+
+ - name: Wait for galaxy to become available
+ uri:
+ url: "http://localhost:8080/api/histories"
+ status_code: 200
+ headers:
+ x-api-key: fakekey
+ register: result
+ until: result.status == 200
+ retries: 4
+ delay: 5
+ ignore_errors: yes
+
+ - name: Restart handler0
+ when: result.status != 200
+ uri:
+ url: "http://localhost:9002/index.html?processname=galaxy%3Ahandler0&action=restart"
+ timeout: 60
+
+ - name: Restart handler1
+ when: result.status != 200
+ uri:
+ url: "http://localhost:9002/index.html?processname=galaxy%3Ahandler1&action=restart"
+ timeout: 60
+
+ - name: Restart handler0
+ when: result.status != 200
+ uri:
+ url: "http://localhost:9002/index.html?processname=galaxy%3Agalaxy_web&action=restart"
+ timeout: 60
+
+ - name: Wait for galaxy to become available (2)
+ uri:
+ url: "http://localhost:8080/api/histories"
+ status_code: 200
+ headers:
+ x-api-key: fakekey
+ register: result
+ until: result.status == 200
+ retries: 4
+ delay: 5
+ ignore_errors: yes
\ No newline at end of file
diff --git a/ansible/setup_irods_fuse_client.yml b/ansible/setup_irods_fuse_client.yml
new file mode 100644
index 00000000..3690ad6a
--- /dev/null
+++ b/ansible/setup_irods_fuse_client.yml
@@ -0,0 +1,44 @@
+- hosts: "{{ host }}"
+ become: true
+ tasks:
+ - name: Copy container setup onto host (sychronize preserves file permissions)
+ synchronize:
+ src: ./files/irods-fuse-client/
+ dest: /irods
+ recursive: yes
+
+ - name: Set irods host ip for client
+ lineinfile:
+ path: /irods/scripts/config.yml
+ regexp: '^host: '
+ line: "host: {{ irods_server_host }}"
+
+ - name: Create irods volume
+ file:
+ path: "{{ irods_host_volume }}/irods-fuse"
+ state: directory
+ owner: "{{ irods_host_volume_owner_uid }}"
+ group: "{{ irods_host_volume_owner_uid }}"
+ mode: '0755'
+
+ - name: Build and start container
+ community.docker.docker_compose:
+ project_src: /irods
+ state: present
+ build: yes
+ environment:
+ IRODS_HOST_VOLUME: "{{ irods_host_volume }}"
+ IRODS_HOST_VOLUME_OWNER: "{{ irods_host_volume_owner_uid }}"
+ register: output
+
+ # - name: Debug startup logs
+ # command: docker logs irods_irods-fuse_1
+ # register: logoutput
+
+ # - name: debug
+ # debug: var=output
+
+ - name: "Check 'Build and start container'"
+ ansible.builtin.assert:
+ that:
+ - "output.services['irods-fuse']['irods_irods-fuse_1'].state.running"
diff --git a/ansible/setup_irods_server.yml b/ansible/setup_irods_server.yml
new file mode 100644
index 00000000..b0f3de36
--- /dev/null
+++ b/ansible/setup_irods_server.yml
@@ -0,0 +1,21 @@
+- hosts: "{{ host }}"
+ become: true
+ tasks:
+ - include: tasks/install_irods.yml
+ vars:
+ irods_host_volume_internal: "{{ irods_host_volume | default('/tmp') }}"
+
+ - include: tasks/configure_irods_local_volume.yml
+ when: (irods_host_volume is defined)
+
+ - include: tasks/configure_irods_s3_volume.yml
+ when: (irods_use_s3 is defined) and (irods_use_s3|bool)
+ vars:
+ IRODS_S3_BUCKET: frct-smoe-bench-ec61-01
+ IRODS_S3_DOMAIN: s3.bwsfs.uni-freiburg.de
+ IRODS_S3_REGION: fr-repl
+ IRODS_S3_ACCESS_ID: ...
+ IRODS_S3_SECRET_KEY: ...
+
+ - include: tasks/install_davrods.yml
+ when: (irods_use_davrods is defined) and (irods_use_davrods|bool)
diff --git a/ansible/setup_irods_webdav_client.yml b/ansible/setup_irods_webdav_client.yml
new file mode 100644
index 00000000..869d720a
--- /dev/null
+++ b/ansible/setup_irods_webdav_client.yml
@@ -0,0 +1,43 @@
+- hosts: "{{ host }}"
+ become: true
+ tasks:
+ - name: Copy container setup onto host (sychronize preserves file permissions)
+ synchronize:
+ src: ./files/irods-webdav-client/
+ dest: /irods
+ recursive: yes
+
+ - name: Setup fstab
+ template:
+ src: ./files/irods-webdav-client/volumes/fstab.j2
+ dest: /irods/volumes/fstab
+
+ - name: Create irods volume
+ file:
+ path: "{{ irods_host_volume }}/irods-webdav"
+ state: directory
+ owner: "{{ davrods_owner_uid }}"
+ group: "{{ davrods_owner_uid }}"
+ mode: '0755'
+
+ - name: Build and start container
+ community.docker.docker_compose:
+ project_src: /irods
+ state: present
+ build: yes
+ environment:
+ IRODS_HOST_VOLUME: "{{ irods_host_volume }}"
+ IRODS_HOST_VOLUME_OWNER: "{{ davrods_owner_uid }}"
+ register: output
+
+ # - name: Debug startup logs
+ # command: docker logs irods_irods-fuse_1
+ # register: logoutput
+
+ # - name: debug
+ # debug: var=output
+
+ - name: "Check 'Build and start container'"
+ ansible.builtin.assert:
+ that:
+ - "output.services['irods-webdav']['irods_irods-webdav_1'].state.running"
diff --git a/ansible/tasks/configure_irods_local_volume.yml b/ansible/tasks/configure_irods_local_volume.yml
new file mode 100644
index 00000000..8b4a16f8
--- /dev/null
+++ b/ansible/tasks/configure_irods_local_volume.yml
@@ -0,0 +1,30 @@
+---
+- name: Add rootResc resource
+ community.docker.docker_container_exec:
+ container: irods_irods_1
+ argv:
+ - /bin/bash
+ - "-c"
+ - iadmin mkresc rootResc passthru
+
+- name: Add mountedVolumeResc resource
+ community.docker.docker_container_exec:
+ container: irods_irods_1
+ argv:
+ - /bin/bash
+ - "-c"
+ - iadmin mkresc mountedVolumeResc unixfilesystem irods.local:/mnt/mountedVolumeResc
+
+- name: Connect rootResc with mountedVolumeResc
+ community.docker.docker_container_exec:
+ container: irods_irods_1
+ argv:
+ - /bin/bash
+ - "-c"
+ - iadmin addchildtoresc rootResc mountedVolumeResc
+
+- name: Set rootResc as default resource
+ ansible.builtin.lineinfile:
+ path: /irods/volumes/config_server/core.re
+ regexp: '^acSetRescSchemeForCreate'
+ line: acSetRescSchemeForCreate {msiSetDefaultResc("rootResc","null"); }
diff --git a/ansible/tasks/configure_irods_s3_volume.yml b/ansible/tasks/configure_irods_s3_volume.yml
new file mode 100644
index 00000000..f5305d9c
--- /dev/null
+++ b/ansible/tasks/configure_irods_s3_volume.yml
@@ -0,0 +1,43 @@
+---
+- name: Add rootResc resource
+ community.docker.docker_container_exec:
+ container: irods_irods_1
+ argv:
+ - /bin/bash
+ - "-c"
+ - iadmin mkresc rootResc passthru
+
+- name: Create S3 credential file
+ copy:
+ content: "{{ IRODS_S3_ACCESS_ID }}\n{{ IRODS_S3_SECRET_KEY }}"
+ dest: /s3_credentials
+
+- name: Copy S3 credential file into irods container
+ shell: docker cp /s3_credentials irods_irods_1:/s3_credentials
+
+- name: Remove S3 credential file from host
+ file:
+ path: "/s3_credentials"
+ state: absent
+
+- name: Add s3Resc resource
+ community.docker.docker_container_exec:
+ container: irods_irods_1
+ argv:
+ - /bin/bash
+ - "-c"
+ - iadmin mkresc s3Resc s3 irods.local:/{{IRODS_S3_BUCKET}}/irods "S3_DEFAULT_HOSTNAME={{IRODS_S3_DOMAIN}};S3_AUTH_FILE=/s3_credentials;S3_REGIONNAME={{IRODS_S3_REGION}};S3_RETRY_COUNT=1;S3_WAIT_TIME_SEC=3;S3_PROTO=HTTPS;ARCHIVE_NAMING_POLICY=consistent;HOST_MODE=cacheless_attached"
+
+- name: Connect rootResc with s3Resc
+ community.docker.docker_container_exec:
+ container: irods_irods_1
+ argv:
+ - /bin/bash
+ - "-c"
+ - iadmin addchildtoresc rootResc s3Resc
+
+- name: Set rootResc as default resource
+ ansible.builtin.lineinfile:
+ path: /irods/volumes/config_server/core.re
+ regexp: '^acSetRescSchemeForCreate'
+ line: acSetRescSchemeForCreate {msiSetDefaultResc("rootResc","null"); }
diff --git a/ansible/tasks/initial_start_galaxy.yml b/ansible/tasks/initial_start_galaxy.yml
new file mode 100644
index 00000000..f06120ab
--- /dev/null
+++ b/ansible/tasks/initial_start_galaxy.yml
@@ -0,0 +1,52 @@
+---
+- name: Initial start of galaxy (Dockerfile has a racecondition, maybe retry necessary)
+ block:
+ - name: Increment the retry count
+ set_fact:
+ retry_count: "{{ 0 if retry_count is undefined else retry_count | int + 1 }}"
+
+ - name: Build and start galaxy
+ community.docker.docker_compose:
+ project_src: /galaxy
+ state: present
+ build: yes
+
+ environment:
+ GALAXY_EXPORT_VOLUME: "{{ galaxy_export_volume }}/glx_export"
+ register: output
+
+ - name: "Check 'Build and start galaxy'"
+ assert:
+ that:
+ - "output.services.galaxy.galaxy_galaxy_1.state.running"
+
+ - name: Wait for galaxy to become available
+ uri:
+ url: "http://localhost:8080/api/histories"
+ status_code: 200
+ headers:
+ x-api-key: fakekey
+ register: result
+ until: result.status == 200
+ retries: 18
+ delay: 5
+
+ rescue:
+ - fail:
+ msg: Maximum retries of grouped tasks reached
+ when: retry_count | int == 3
+
+ - debug:
+ msg: "Initial start of galaxy failed, let's give it another shot"
+
+ - name: Stop galaxy
+ community.docker.docker_compose:
+ project_src: "/galaxy"
+ state: absent
+
+ - name: Cleanup galaxy volume
+ file:
+ path: "{{ galaxy_export_volume }}/glx_export"
+ state: absent
+
+ - include_tasks: initial_start_galaxy.yml
\ No newline at end of file
diff --git a/ansible/tasks/install_davrods.yml b/ansible/tasks/install_davrods.yml
new file mode 100644
index 00000000..c9b2717c
--- /dev/null
+++ b/ansible/tasks/install_davrods.yml
@@ -0,0 +1,33 @@
+---
+- name: Copy container setup onto host (sychronize preserves file permissions)
+ ansible.posix.synchronize:
+ src: ../files/irods-davrods-server/
+ dest: /davrods
+ recursive: yes
+
+- name: Set host ip as server alias
+ lineinfile:
+ path: /davrods/volumes/davrods-vhost.conf
+ regexp: 'ServerAlias SET_TO_HOST_IP'
+ line: " ServerAlias {{ hostvars[inventory_hostname]['ansible_default_ipv4']['address'] }}"
+
+- name: Build and start container
+ community.docker.docker_compose:
+ project_src: /davrods
+ state: present
+ build: yes
+ environment:
+ DAVRODS_VOLUME_OWNER: "{{ davrods_owner_uid }}"
+ register: output
+
+# - name: Debug startup logs
+# command: docker logs irods_irods_1
+# register: logoutput
+
+# - name: debug
+# debug: var=logoutput
+
+- name: "Check 'Build and start container'"
+ ansible.builtin.assert:
+ that:
+ - "output.services.davrods.davrods_davrods_1.state.running"
diff --git a/ansible/tasks/install_irods.yml b/ansible/tasks/install_irods.yml
new file mode 100644
index 00000000..d6068836
--- /dev/null
+++ b/ansible/tasks/install_irods.yml
@@ -0,0 +1,54 @@
+---
+- name: Create irods volume
+ file:
+ path: "{{ irods_host_volume_internal }}/irods"
+ state: directory
+ owner: "1000"
+ group: "1000"
+ mode: '0755'
+
+- name: Copy container setup onto host (sychronize preserves file permissions)
+ ansible.posix.synchronize:
+ src: ../files/irods-server/
+ dest: /irods
+ recursive: yes
+
+- name: Build and start container
+ community.docker.docker_compose:
+ project_src: /irods
+ state: present
+ build: yes
+ environment:
+ IRODS_HOST_VOLUME: "{{ irods_host_volume_internal }}"
+ register: output
+
+# - name: Debug startup logs
+# command: docker logs irods_irods_1
+# register: logoutput
+
+# - name: debug
+# debug: var=logoutput
+
+- name: "Check 'Build and start container'"
+ ansible.builtin.assert:
+ that:
+ - "output.services.irods.irods_irods_1.state.running"
+ - "output.services.db.irods_db_1.state.running"
+
+- name: Wait for port 1247 to become open on the host
+ wait_for:
+ port: 1247
+ delay: 5
+ timeout: 30
+
+- name: Configure rods user for i-Commands
+ community.docker.docker_container_exec:
+ container: irods_irods_1
+ argv:
+ - /bin/bash
+ - "-c"
+ - iinit adminpass
+ retries: 3
+ delay: 3
+ register: result
+ until: result.rc == 0
diff --git a/ansible/tasks/remove_container.yml b/ansible/tasks/remove_container.yml
new file mode 100644
index 00000000..72ef2e3f
--- /dev/null
+++ b/ansible/tasks/remove_container.yml
@@ -0,0 +1,16 @@
+---
+- name: "Check if {{path}} exists"
+ stat:
+ path: "{{path}}"
+ register: stat_result
+
+- name: "Stop container for project in {{path}}"
+ when: stat_result.stat.exists
+ community.docker.docker_compose:
+ project_src: "{{path}}"
+ state: absent
+
+- name: "Remove {{path}} from server"
+ file:
+ path: "{{path}}"
+ state: absent
diff --git a/ansible/tasks/run_fio_and_check_result.yml b/ansible/tasks/run_fio_and_check_result.yml
new file mode 100644
index 00000000..b92d9c5c
--- /dev/null
+++ b/ansible/tasks/run_fio_and_check_result.yml
@@ -0,0 +1,57 @@
+---
+- name: "Taskgroup: run fio and check result, potential retry"
+ block:
+ - name: Increment the retry count
+ set_fact:
+ retry_count: "{{ 0 if retry_count is undefined else retry_count | int + 1 }}"
+
+ - name: Run fio container
+ become: true
+ community.docker.docker_container:
+ name: fio
+ image: galaxybenchmarker/tool-fio
+ detach: false
+ cleanup: true
+ volumes:
+ - "{{ tempdir.path }}:/results:rw"
+ - "{{ fio_dir }}/fio_in_progress:/mnt/volume_under_test:rw"
+ working_dir: /mnt/volume_under_test
+ command: [
+ "--bs={{ fio_blocksize }}",
+ "--direct=1",
+ "--end_fsync=1",
+ "--filename=testfile.fio",
+ "--group_reporting",
+ "--iodepth={{ fio_iodepth }}",
+ "--ioengine={{ fio_ioengine }}",
+ "--name={{ fio_jobname }}",
+ "--numjobs={{ fio_numjobs }}",
+ "--output", "/results/{{ fio_result_file }}",
+ "--output-format=json",
+ "{% if fio_refill_buffers|bool %}--refill_buffers{% endif %}",
+ "{% if fio_time_based|bool %}--runtime={{ fio_runtime_in_s }}{% endif %}",
+ "--rw={{ fio_mode }}",
+ "--size={{ fio_filesize }}",
+ "{% if fio_time_based|bool %}--time_based{% endif %}",
+ "{% if fio_mode in ['read', 'randread'] %}--create_on_open=1{% endif %}",
+ "{% if fio_ramptime_in_s %}--ramp_time={{ fio_ramptime_in_s }}{% endif%}",
+ ]
+ register: fio_results_2
+ # IRODSFS generates errors during cleanup
+ ignore_errors: true
+
+ - name: debug
+ debug: var=fio_results_2
+
+ - name: Check fio result, fail when bandwidth=0
+ command: 'grep -E "bw_mean\" : [1-9]" {{ tempdir.path }}/{{ fio_result_file }}'
+
+ rescue:
+ - fail:
+ msg: Maximum retries of grouped tasks reached
+ when: retry_count | int == 5
+
+ - debug:
+ msg: "Fio failed, let's give it another shot"
+
+ - include_tasks: run_fio_and_check_result.yml
\ No newline at end of file
diff --git a/ansible/tasks/run_galaxy_job.yml b/ansible/tasks/run_galaxy_job.yml
new file mode 100644
index 00000000..c4378757
--- /dev/null
+++ b/ansible/tasks/run_galaxy_job.yml
@@ -0,0 +1,18 @@
+---
+- name: Check if galaxy directory exist
+ stat:
+ path: "/galaxy"
+ register: stat_output
+ failed_when: not stat_output.stat.isdir
+
+- name: Run script inside galaxy-job-starter container
+ community.docker.docker_container_exec:
+ container: galaxy_galaxy-job-starter_1
+ command: python run_job.py
+ env:
+ GLX_TOOL_ID: "{{ glx_tool_id }}"
+ GLX_TOOL_INPUT: "'{{ glx_tool_input }}'"
+ register: job_result
+
+- name: debug
+ debug: var=job_result
diff --git a/benchmark_config.yml.example b/benchmark_config.yml.example
deleted file mode 100644
index 14abe70c..00000000
--- a/benchmark_config.yml.example
+++ /dev/null
@@ -1,130 +0,0 @@
-# You need have access to a GalaxyInstance with admin-rights
-galaxy:
- url: "http://galaxy.example.com"
- user_key: "blablabla"
- # Check, if tools need to be installed when running GalaxyWorkflow (can be turned off after first benchmark-run)
- shed_install: true
- # Should Galaxy be configured to use the given Destinations or is everything already set?
- configure_job_destinations: true
- # Used to deploy DynamicDestinations via Ansible
- ssh_user: ubuntu
- ssh_key: /local/path/to/ssh/key.cert
- galaxy_root_path: /srv/galaxy
- galaxy_config_dir: /srv/galaxy/server/config
- galaxy_user: galaxy
-
-# Used for analyzing results
-influxdb:
- host: influxdb.example.com
- port: 8086
- username: glx_benchmarker_user
- password: supersecret
- db_name: glx_benchmarker
-
-# Configure all the Destinations that should be benchmarked
-destinations:
- - name: LocalPulsar
- type: PulsarMQ
- amqp_url: "pyamqp://username:password@rabbitmq.example.com:5672//"
- # If used for ColdWarmBenchmark, we need to have ssh-access to the Pulsar-Server
- host: pulsar.example.com
- host_user: centos
- ssh_key: /local/path/to/ssh/key.cert
- tool_dependency_dir: /data/share/tools
- jobs_directory_dir: /data/share/staging
- persistence_dir: /data/share/persisted_data
- # To configure additional params in job_conf.xml
- job_plugin_params:
- manager: __default__
- job_destination_params:
- dependency_resolution: remote
- default_file_action: remote_transfer
- remote_metadata: false
- rewrite_parameters: true
- amqp_acknowledge: true
- amqp_ack_republish_time: 10
- - name: RemotePulsar1
- type: PulsarMQ
- # If credentials are set, Destination won't create a user on Galaxy and will just use these for submitting jobs
- galaxy_user_name: user2
- galaxy_user_key: blablablakey
- - name: RemotePulsar1
- type: PulsarMQ
- amqp_url: "pyamqp://username:password@rabbit.example3.com:5672//"
- job_plugin_params:
- manager: __default__
- job_destination_params:
- dependency_resolution: remote
- default_file_action: remote_transfer
- remote_metadata: false
- rewrite_parameters: true
- amqp_acknowledge: true
- amqp_ack_republish_time: 10
- - name: CondorManager
- type: Condor
- host: condor-manager.uni.andreas-sk.de
- host_user: centos
- ssh_key: /local/path/to/ssh/key.cert
- jobs_directory_dir: /data/share/condor
-
-workflows:
- - name: GalaxyWorkflow1
- type: Galaxy
- path: path/to/galaxy/workflow/file.ga
- timeout: 100 # Optional. Workflow will be canceled after timeout.
- - name: CondorWorkflow1
- type: Condor
- path: path/to/condor/workflow/folder
- job_file: job.job # needs to be in directory at "path"
-
-benchmarks:
- - name: ColdvsWarm
- type: ColdvsWarm
- destinations:
- - LocalPulsar
- runs_per_workflow: 5
- workflows:
- - GalaxyWorkflow1
- # Pre task to clean up Pulsar, so the workflow-run is actually cold (i.e. run for the "first time")
- cold_pre_task:
- type: "ansible-playbook"
- playbook: "coldwarm_pretask.yml"
- warm_pre_task:
- type: "ansible-playbook"
- playbook: "cleanup-pulsar.yml"
- # If you want to clean up Pulsar after Benchmark ran
- post_task:
- type: "ansible-playbook"
- playbook: "cleanup_pulsar.yml"
- - name: DestinationComparison
- type: DestinationComparison
- pre_task:
- type: "ansible-playbook"
- playbook: "cleanup-pulsar.yml"
- post_task:
- type: "ansible-playbook"
- playbook: "cleanup-pulsar.yml" # Call some script after ending benchmark (like for cleaning up)
- destinations:
- - LocalPulsar
- - RemotePulsar1
- - RemotePulsar2
- runs_per_workflow: 5
- warmup: true # Should a warmup-run happen before the actual benchmarking?
- workflows:
- - GalaxyWorkflow1
- - name: PulsarBurstBenchmark
- type: Burst
- runs_per_workflow: 100
- burst_rate: 1 # How many workflows should be submitted per second
- destinations:
- - RemotePulsar1 # Only one destination allowed!
- workflows:
- - GalaxyWorkflow1
- - name: CondorBurstBenchmark
- type: Burst
- runs_per_workflow: 1000
- burst_rate: 20
- destinations:
- - CondorManager
- workflows:
- - CondorWorkflow1
\ No newline at end of file
diff --git a/benchmark_config.yml.usegalaxyeu b/benchmark_config.yml.usegalaxyeu
deleted file mode 100644
index 9246fdb7..00000000
--- a/benchmark_config.yml.usegalaxyeu
+++ /dev/null
@@ -1,56 +0,0 @@
-galaxy:
- url: "https://usegalaxy.eu"
- # Check, if tools need to be installed when running GalaxyWorkflow (can be turned off after first benchmark-run)
- shed_install: false
- # Should Galaxy be configured to use the given Destinations or is everything already set?
- configure_job_destinations: false
- user_key: YOUR-REGULAR-USER-KEY
-
-# Configure all the Destinations that should be benchmarked
-destinations:
- - name: YourDestinationName
- type: Galaxy
- galaxy_user_key: YOUR-USER-KEY-FOR-DESTINATION-USER
-
-workflows:
- - name: ard
- type: Galaxy
- path: /workflow-testing/sklearn/ard/ard.ga
- timeout: 5000 # Optional. Workflow will be canceled after timeout.
- - name: gromacs
- type: Galaxy
- path: /workflow-testing/training/computational-chemistry/gromacs/gromacs.ga
- timeout: 5400
- - name: mapping_by_sequencing
- type: Galaxy
- path: /workflow-testing/training/variant-analysis/mapping-by-sequencing/mapping_by_sequencing.ga
- timeout: 5400
- - name: adaboost
- type: Galaxy
- path: /workflow-testing/sklearn/adaboost/adaboost.ga
- timeout: 1200
- - name: ard
- type: Galaxy
- path: /workflow-testing/sklearn/ard/ard.ga
- timeout: 1200
-
-benchmarks:
- - name: BenchmarkName
- type: DestinationComparison
- destinations:
- - YourDestinationName
- workflows:
- - ard
- - adaboost
- - gromacs
- - mapping_by_sequencing
- runs_per_workflow: 5
- warmup: true
-
-# Please do not change!
-influxdb:
- host: influxdb
- port: 8086
- username: glx_benchmarker
- password: glx_benchmarker
- db_name: glx_benchmarker
\ No newline at end of file
diff --git a/cleanup_pulsar.yml b/cleanup_pulsar.yml
deleted file mode 100644
index 42166022..00000000
--- a/cleanup_pulsar.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-- hosts: all
- tasks:
- - name: Delete old-folder in tool_dependency_dir
- file:
- path: "{{tool_dependency_dir}}/old"
- state: absent
\ No newline at end of file
diff --git a/coldwarm_pretask.yml b/coldwarm_pretask.yml
deleted file mode 100644
index a5b9dbac..00000000
--- a/coldwarm_pretask.yml
+++ /dev/null
@@ -1,50 +0,0 @@
-- hosts: all
- tasks:
- - name: Stop Pulsar
- service:
- name: pulsar
- state: stopped
- become: yes
- - name: Create old-folder for old _conda folders
- file:
- path: "{{tool_dependency_dir}}/old"
- state: directory
- - name: Check if base dependencies folder already exists
- stat:
- path: "{{tool_dependency_dir}}/_conda-base"
- register: conda_base_directory
- - name: Check if _conda folder already exists
- stat:
- path: "{{tool_dependency_dir}}/_conda"
- register: conda_directory
- - name: Move _conda to old-folder, so it can be removed at the end of benchmark (to speedup benchmark)
- command: "mv {{tool_dependency_dir}}/_conda {{tool_dependency_dir}}/old/_conda-{{ansible_date_time.iso8601_basic_short}}"
- when: conda_directory.stat.exists and conda_directory.stat.isdir
- - name: Delete conda.lock in tool_dependency_dir
- file:
- path: "{{tool_dependency_dir}}/conda.lock"
- state: absent
- - name: Copy _conda-base to _conda (to speed up Pulsar-Startup)
- command: "cp {{tool_dependency_dir}}/_conda-base/ {{tool_dependency_dir}}/_conda/ -R"
- when: conda_base_directory.stat.exists and conda_base_directory.stat.isdir
- - name: Delete persisted_data-folder
- file:
- path: "{{persistence_dir}}"
- state: absent
- - name: Delete staging-folder
- file:
- path: "{{jobs_directory_dir}}"
- state: absent
- ignore_errors: yes
- - name: Start Pulsar
- service:
- name: pulsar
- state: started
- become: yes
- - name: Pause for 10 minutes, so Pulsar has time to install all base dependencies
- pause:
- minutes: 10
- when: conda_base_directory.stat.exists == False
- - name: Copy _conda to _conda-base to speedup future Pulsar-Startups
- command: "cp {{tool_dependency_dir}}/_conda/ {{tool_dependency_dir}}/_conda-base/ -R"
- when: conda_base_directory.stat.exists == False
diff --git a/coldwarm_pretask_only_persistence_dir.yml b/coldwarm_pretask_only_persistence_dir.yml
deleted file mode 100644
index 1e4e65d1..00000000
--- a/coldwarm_pretask_only_persistence_dir.yml
+++ /dev/null
@@ -1,21 +0,0 @@
-- hosts: all
- tasks:
- - name: Stop Pulsar
- service:
- name: pulsar
- state: stopped
- become: yes
- - name: Delete persisted_data-folder
- file:
- path: "{{persistence_dir}}"
- state: absent
- - name: Delete staging-folder
- file:
- path: "{{jobs_directory_dir}}"
- state: absent
- ignore_errors: yes
- - name: Start Pulsar
- service:
- name: pulsar
- state: started
- become: yes
diff --git a/coldwarm_pretask_with_cvmfs-clear.yml b/coldwarm_pretask_with_cvmfs-clear.yml
deleted file mode 100644
index c19a21cc..00000000
--- a/coldwarm_pretask_with_cvmfs-clear.yml
+++ /dev/null
@@ -1,53 +0,0 @@
-- hosts: all
- tasks:
- - name: Stop Pulsar
- service:
- name: pulsar
- state: stopped
- become: yes
- - name: Create old-folder for old _conda folders
- file:
- path: "{{tool_dependency_dir}}/old"
- state: directory
- - name: Check if base dependencies folder already exists
- stat:
- path: "{{tool_dependency_dir}}/_conda-base"
- register: conda_base_directory
- - name: Check if _conda folder already exists
- stat:
- path: "{{tool_dependency_dir}}/_conda"
- register: conda_directory
- - name: Move _conda to old-folder, so it can be removed at the end of benchmark (to speedup benchmark)
- command: "mv {{tool_dependency_dir}}/_conda {{tool_dependency_dir}}/old/_conda-{{ansible_date_time.iso8601_basic_short}}"
- when: conda_directory.stat.exists and conda_directory.stat.isdir
- - name: Delete conda.lock in tool_dependency_dir
- file:
- path: "{{tool_dependency_dir}}/conda.lock"
- state: absent
- - name: Copy _conda-base to _conda (to speed up Pulsar-Startup)
- command: "cp {{tool_dependency_dir}}/_conda-base/ {{tool_dependency_dir}}/_conda/ -R"
- when: conda_base_directory.stat.exists and conda_base_directory.stat.isdir
- - name: Delete persisted_data-folder
- file:
- path: "{{persistence_dir}}"
- state: absent
- - name: Delete staging-folder
- file:
- path: "{{jobs_directory_dir}}"
- state: absent
- ignore_errors: yes
- - name: Clear CVMFS (cvmfs_config wipecache)
- command: "cvmfs_config wipecache"
- become: yes
- - name: Start Pulsar
- service:
- name: pulsar
- state: started
- become: yes
- - name: Pause for 10 minutes, so Pulsar has time to install all base dependencies
- pause:
- minutes: 10
- when: conda_base_directory.stat.exists == False
- - name: Copy _conda to _conda-base to speedup future Pulsar-Startups
- command: "cp {{tool_dependency_dir}}/_conda/ {{tool_dependency_dir}}/_conda-base/ -R"
- when: conda_base_directory.stat.exists == False
diff --git a/condor-workflows/exit/exit.job b/condor-workflows/exit/exit.job
deleted file mode 100644
index 19dc2a6d..00000000
--- a/condor-workflows/exit/exit.job
+++ /dev/null
@@ -1,7 +0,0 @@
-Universe = vanilla
-Executable = exit.sh
-Log = exit.$(process).log
-Output = exit.$(process).out
-Error = exit.$(process).err
-request_cpus = 1
-Queue 100
\ No newline at end of file
diff --git a/condor-workflows/exit/exit.sh b/condor-workflows/exit/exit.sh
deleted file mode 100644
index c3c3f3f5..00000000
--- a/condor-workflows/exit/exit.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/bin/bash
-exit 0
\ No newline at end of file
diff --git a/condor-workflows/fio/fio.job b/condor-workflows/fio/fio.job
deleted file mode 100644
index c506e947..00000000
--- a/condor-workflows/fio/fio.job
+++ /dev/null
@@ -1,7 +0,0 @@
-Universe = vanilla
-Executable = fio.sh
-Log = fio.$(process).log
-Output = fio.$(process).out
-Error = fio.$(process).err
-request_cpus = 2
-Queue 1
diff --git a/condor-workflows/fio/fio.sh b/condor-workflows/fio/fio.sh
deleted file mode 100644
index 8b3bf3aa..00000000
--- a/condor-workflows/fio/fio.sh
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-
-echo "Max-Write-Speed:"
-fio --rw=write --name=IOPS-write --bs=1024k --direct=1 --filename=test-file --numjobs=4 --ioengine=libaio --iodepth=32 --refill_buffers --group_reporting --runtime=60 --time_based --size=5G
-rm test-file
-
-echo "Max-Read-Speed:"
-fio --rw=read --name=IOPS-read --bs=1024k --direct=1 --filename=test-file --numjobs=4 --ioengine=libaio --iodepth=32 --refill_buffers --group_reporting --runtime=60 --time_based --size=5G
-rm test-file
-
-echo "IOPS-Write:"
-fio --rw=randwrite --name=IOPS-write --bs=4k --direct=1 --filename=test-file --numjobs=4 --ioengine=libaio --iodepth=32 --refill_buffers --group_reporting --runtime=60 --time_based --size=5G
-rm test-file
-
-echo "IOPS-Read:"
-fio --rw=randread --name=IOPS-read --bs=4k --direct=1 --filename=test-file --numjobs=4 --ioengine=libaio --iodepth=32 --refill_buffers --group_reporting --runtime=60 --time_based --size=5G
-rm test-file
diff --git a/condor-workflows/iperf/iperf.job b/condor-workflows/iperf/iperf.job
deleted file mode 100644
index 781b2561..00000000
--- a/condor-workflows/iperf/iperf.job
+++ /dev/null
@@ -1,7 +0,0 @@
-Universe = vanilla
-Executable = iperf.sh
-Log = iperf.$(process).log
-Output = iperf.$(process).out
-Error = iperf.$(process).err
-request_cpus = 1
-Queue 1
diff --git a/condor-workflows/iperf/iperf.sh b/condor-workflows/iperf/iperf.sh
deleted file mode 100644
index 008770f6..00000000
--- a/condor-workflows/iperf/iperf.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/bin/bash
-iperf -c iperf.uni.andreas-sk.de -t 30
diff --git a/condor-workflows/ping/ping.job b/condor-workflows/ping/ping.job
deleted file mode 100644
index 548bfb8e..00000000
--- a/condor-workflows/ping/ping.job
+++ /dev/null
@@ -1,7 +0,0 @@
-Universe = vanilla
-Executable = ping.sh
-Log = ping.$(process).log
-Output = ping.$(process).out
-Error = ping.$(process).err
-request_cpus = 1
-Queue 1
diff --git a/condor-workflows/ping/ping.sh b/condor-workflows/ping/ping.sh
deleted file mode 100644
index 5f9af03e..00000000
--- a/condor-workflows/ping/ping.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/bin/bash
-ping iperf.uni.andreas-sk.de -c 30
\ No newline at end of file
diff --git a/condor-workflows/sleep/test.job b/condor-workflows/sleep/test.job
deleted file mode 100644
index ead1e0f6..00000000
--- a/condor-workflows/sleep/test.job
+++ /dev/null
@@ -1,7 +0,0 @@
-Universe = vanilla
-Executable = test.sh
-Log = test.$(process).log
-Output = test.$(process).out
-Error = test.$(process).err
-request_cpus = 1
-Queue 1
\ No newline at end of file
diff --git a/condor-workflows/sleep/test.sh b/condor-workflows/sleep/test.sh
deleted file mode 100644
index ec964988..00000000
--- a/condor-workflows/sleep/test.sh
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/bash
-sleep 1
-echo "$(hostname)"
\ No newline at end of file
diff --git a/custom_galaxy_workflows/big-file-transfer/big-file-transfer-test.yml b/custom_galaxy_workflows/big-file-transfer/big-file-transfer-test.yml
deleted file mode 100644
index 16041cfd..00000000
--- a/custom_galaxy_workflows/big-file-transfer/big-file-transfer-test.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-- doc: Just transfer a big file
- job:
- big-file:
- class: File
- location: http://speedtest.belwue.net/random-1G
- filetype: txt
diff --git a/custom_galaxy_workflows/big-file-transfer/big-file-transfer.ga b/custom_galaxy_workflows/big-file-transfer/big-file-transfer.ga
deleted file mode 100644
index 4597de78..00000000
--- a/custom_galaxy_workflows/big-file-transfer/big-file-transfer.ga
+++ /dev/null
@@ -1,78 +0,0 @@
-{
- "uuid": "cec65567-724c-40f6-9079-b79a241be7a4",
- "tags": [],
- "format-version": "0.1",
- "name": "Big-File",
- "version": 1,
- "steps": {
- "0": {
- "tool_id": null,
- "tool_version": null,
- "outputs": [],
- "workflow_outputs": [{
- "output_name": "output",
- "uuid": "c1221d53-4d6b-4735-a39d-c8e9a4cd3492",
- "label": null
- }],
- "input_connections": {},
- "tool_state": "{}",
- "id": 0,
- "uuid": "2d85b49e-97c0-4200-be9f-7b20d7546b7e",
- "errors": null,
- "name": "Input dataset",
- "label": "big-file",
- "inputs": [
- {
- "description": "",
- "name": "big-file"
- }
- ],
- "position": {
- "top": 133,
- "left": 155.5
- },
- "annotation": "",
- "content_id": null,
- "type": "data_input"
- },
- "1": {
- "tool_id": "Show beginning1",
- "tool_version": "1.0.0",
- "outputs": [{
- "type": "input",
- "name": "out_file1"
- }],
- "workflow_outputs": [{
- "output_name": "out_file1",
- "uuid": "af594591-07e2-412e-840a-f6460edc1531",
- "label": null
- }],
- "input_connections": {
- "input": {
- "output_name": "output",
- "id": 0
- }
- },
- "tool_state": "{\"__page__\": null, \"input\": \"{\\\"__class__\\\": \\\"RuntimeValue\\\"}\", \"__rerun_remap_job_id__\": null, \"lineNum\": \"\\\"1\\\"\"}",
- "id": 1,
- "uuid": "010ee8b9-bd5d-4f99-87b9-a7dad0e0f985",
- "errors": null,
- "name": "Select first",
- "post_job_actions": {},
- "label": "Select first line ",
- "inputs": [{
- "name": "input",
- "description": "runtime parameter for tool Select first"
- }],
- "position": {
- "top": 446,
- "left": 481.5
- },
- "annotation": "",
- "content_id": "Show beginning1",
- "type": "tool"
- }
- },
- "annotation": "",
- "a_galaxy_workflow": "true"
-}
diff --git a/custom_galaxy_workflows/big-file-transfer/very-big-file-transfer-test.yml b/custom_galaxy_workflows/big-file-transfer/very-big-file-transfer-test.yml
deleted file mode 100644
index 49345ae8..00000000
--- a/custom_galaxy_workflows/big-file-transfer/very-big-file-transfer-test.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-- doc: Just transfer a big file
- job:
- big-file:
- class: File
- location: http://speedtest.belwue.net/10G
- filetype: txt
diff --git a/custom_galaxy_workflows/big-file-transfer/very-big-file-transfer.ga b/custom_galaxy_workflows/big-file-transfer/very-big-file-transfer.ga
deleted file mode 100644
index 4597de78..00000000
--- a/custom_galaxy_workflows/big-file-transfer/very-big-file-transfer.ga
+++ /dev/null
@@ -1,78 +0,0 @@
-{
- "uuid": "cec65567-724c-40f6-9079-b79a241be7a4",
- "tags": [],
- "format-version": "0.1",
- "name": "Big-File",
- "version": 1,
- "steps": {
- "0": {
- "tool_id": null,
- "tool_version": null,
- "outputs": [],
- "workflow_outputs": [{
- "output_name": "output",
- "uuid": "c1221d53-4d6b-4735-a39d-c8e9a4cd3492",
- "label": null
- }],
- "input_connections": {},
- "tool_state": "{}",
- "id": 0,
- "uuid": "2d85b49e-97c0-4200-be9f-7b20d7546b7e",
- "errors": null,
- "name": "Input dataset",
- "label": "big-file",
- "inputs": [
- {
- "description": "",
- "name": "big-file"
- }
- ],
- "position": {
- "top": 133,
- "left": 155.5
- },
- "annotation": "",
- "content_id": null,
- "type": "data_input"
- },
- "1": {
- "tool_id": "Show beginning1",
- "tool_version": "1.0.0",
- "outputs": [{
- "type": "input",
- "name": "out_file1"
- }],
- "workflow_outputs": [{
- "output_name": "out_file1",
- "uuid": "af594591-07e2-412e-840a-f6460edc1531",
- "label": null
- }],
- "input_connections": {
- "input": {
- "output_name": "output",
- "id": 0
- }
- },
- "tool_state": "{\"__page__\": null, \"input\": \"{\\\"__class__\\\": \\\"RuntimeValue\\\"}\", \"__rerun_remap_job_id__\": null, \"lineNum\": \"\\\"1\\\"\"}",
- "id": 1,
- "uuid": "010ee8b9-bd5d-4f99-87b9-a7dad0e0f985",
- "errors": null,
- "name": "Select first",
- "post_job_actions": {},
- "label": "Select first line ",
- "inputs": [{
- "name": "input",
- "description": "runtime parameter for tool Select first"
- }],
- "position": {
- "top": 446,
- "left": 481.5
- },
- "annotation": "",
- "content_id": "Show beginning1",
- "type": "tool"
- }
- },
- "annotation": "",
- "a_galaxy_workflow": "true"
-}
diff --git a/deploy_condor_workflow.yml b/deploy_condor_workflow.yml
deleted file mode 100644
index 0784904b..00000000
--- a/deploy_condor_workflow.yml
+++ /dev/null
@@ -1,11 +0,0 @@
-- hosts: all
- tasks:
- - name: Create folder for workflow
- file:
- path: "{{ jobs_directory_dir }}/{{ workflow_name }}"
- state: directory
- - name: Upload workflow-files
- copy:
- src: "{{ workflow_directory_path }}/"
- dest: "{{ jobs_directory_dir }}/{{ workflow_name }}"
- mode: 0700
\ No newline at end of file
diff --git a/docker-compose.yml b/docker-compose.yml
index 7fbb6577..b3a03aa5 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -2,31 +2,30 @@ version: "3"
services:
benchmarker:
build: .
+ environment:
+ - SSH_AUTH_SOCK=/ssh-agent
volumes:
- - .:/src
+ - type: bind
+ source: .
+ target: /src
+ - type: bind
+ source: ${SSH_AUTH_SOCK}
+ target: /ssh-agent
+ - type: bind
+ source: ${HOME}/.ssh
+ target: /root/.ssh-host
+ read_only: true
working_dir: /src
- entrypoint: python3 galaxy_benchmarker
- depends_on:
- - influxdb
- influxdb:
- image: influxdb:1.5
- ports:
- - 8086:8086
- environment:
- INFLUXDB_DATA_QUERY_LOG_ENABLED: "false"
- INFLUXDB_HTTP_LOG_ENABLED: "false"
- INFLUXDB_ADMIN_USER: admin
- INFLUXDB_ADMIN_PASSWORD: admin
- INFLUXDB_DB: glx_benchmarker
- INFLUXDB_USER: glx_benchmarker
- INFLUXDB_USER_PASSWORD: glx_benchmarker
- volumes:
- - .:/src
- grafana:
- image: grafana/grafana:6.5.0
- ports:
- - 3000:3000
- volumes:
- - ./grafana/config.ini:/etc/grafana/config.ini
- - ./grafana/provisioning:/etc/grafana/provisioning
- - ./grafana/dashboards:/var/lib/grafana/dashboards
\ No newline at end of file
+
+ command: [
+ "--cfg",
+ "examples/06_benchmark_galaxy_1x0.yml",
+ "examples/06_benchmark_galaxy_1x1K.yml",
+ "examples/06_benchmark_galaxy_10x1K.yml",
+ "examples/06_benchmark_galaxy_100x1K.yml",
+ "examples/06_benchmark_galaxy_1000x1K.yml",
+ "examples/06_benchmark_galaxy_2000x1K.yml",
+ "examples/06_benchmark_galaxy_1x1M.yml",
+ "examples/06_benchmark_galaxy_1x1G.yml",
+ "examples/06_benchmark_galaxy_10x1G.yml",
+ ]
diff --git a/evaluation/plots.ipynb b/evaluation/plots.ipynb
new file mode 100644
index 00000000..0e745447
--- /dev/null
+++ b/evaluation/plots.ipynb
@@ -0,0 +1,22051 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "ebc0884d",
+ "metadata": {},
+ "source": [
+ "# Evaluation and plotting for different experiments\n",
+ "\n",
+ "- Write throughput (fio vs dd, isilon vs netapp)\n",
+ "- Containerized fio vs non-containerized fio"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "c777afc1",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from __future__ import annotations\n",
+ "import json\n",
+ "import matplotlib.pyplot as plt\n",
+ "import numpy as np\n",
+ "import pandas as pd\n",
+ "import itertools\n",
+ "from pathlib import Path\n",
+ "import scipy.stats as st\n",
+ "from datetime import datetime, timedelta"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "3c021dc8",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "METRIC = {\n",
+ " \"throughput_write\": \"write_bw_mean_in_MiB\",\n",
+ " \"iops_write\": \"write_iops_mean\",\n",
+ " \"latency_write\": \"write_lat_mean_in_ms\",\n",
+ " \"throughput_read\": \"read_bw_mean_in_MiB\",\n",
+ " \"iops_read\": \"read_iops_mean\",\n",
+ " \"latency_read\": \"read_lat_mean_in_ms\",\n",
+ "}\n",
+ "\n",
+ "def load_data(filename: Path | str, metric: str, suffix: str=\"\", x_labels=None) -> pd.DataFrame:\n",
+ " \"\"\"Load data either from file or from multiple files placed in a directory\"\"\"\n",
+ " path = Path(filename)\n",
+ " if path.is_file():\n",
+ " data = load_json(path)\n",
+ " elif path.is_dir():\n",
+ " data = load_dir(path, suffix)\n",
+ " else:\n",
+ " data = load_json(str(path)+suffix)\n",
+ " return extract_results(data, metric, x_labels)\n",
+ "\n",
+ "def load_json(filename: Path | str) -> dict:\n",
+ " \"\"\"Load file as json\"\"\"\n",
+ " file = Path(filename)\n",
+ " assert file.is_file(), f\"{file} is not a file\"\n",
+ " return json.loads(file.read_text())\n",
+ "\n",
+ "def load_dir(path: Path, suffix: str) -> dict:\n",
+ " \"\"\"Combine results of multiple files in directory. Specify files by suffix\"\"\"\n",
+ " res = {\"results\":{}}\n",
+ "\n",
+ " for file in path.glob(f\"*{suffix}\"):\n",
+ " data = load_json(file)\n",
+ "\n",
+ " for key, value in data[\"results\"].items():\n",
+ " if key not in res[\"results\"]:\n",
+ " res[\"results\"][key] = []\n",
+ " res[\"results\"][key].extend(value)\n",
+ "\n",
+ " return res\n",
+ "\n",
+ "def extract_results(data, metric, x_labels=None):\n",
+ " x_labels = x_labels if x_labels else list(data[\"results\"].keys())\n",
+ " pd_data = []\n",
+ " for label in x_labels:\n",
+ " results = data[\"results\"][label]\n",
+ " values = [float(r[metric]) for r in results]\n",
+ " values = [v for v in values if v > 0]\n",
+ " mean = np.mean(values)\n",
+ " conf = confidence_interval(values)\n",
+ " std = np.std(values)\n",
+ " num = len(values)\n",
+ " pd_data.append([mean, std, num])\n",
+ "\n",
+ "\n",
+ " df = pd.DataFrame(data=pd_data, index=x_labels, columns=[\"mean\", \"std\", \"num\"])\n",
+ " df.rename(index={\"1024\": 1, \"2048\": 2, \"4096\": 4, \"8192\": 8, \"16384\": 16}, inplace=True)\n",
+ " df.rename(index={\"256M\": 0.25, \"1G\": 1, \"2G\": 2, \"4G\": 4, \"8G\": 8, \"16G\": 16}, inplace=True)\n",
+ " df.rename(index={\"1k\": 1, \"4k\": 4, \"16k\": 16, \"64k\": 64, \"256k\": 256, \"1024k\": 1024, \"4096k\": 4096, \"16384k\": 16384}, inplace=True)\n",
+ " df.rename(index={\"1\": 1, \"2\": 2, \"4\": 4, \"8\": 8, \"16\": 16, \"32\": 32}, inplace=True)\n",
+ " df.rename(index={\"10\": 10, \"20\": 20, \"60\": 60, \"120\": 120, \"300\": 300}, inplace=True)\n",
+ "\n",
+ " return df\n",
+ "\n",
+ "def confidence_interval(values):\n",
+ " confidence = 0.95\n",
+ " dof = len(values)-1\n",
+ " m = np.mean(values)\n",
+ " sem = st.sem(values)\n",
+ " interval = st.t.interval(alpha=confidence, df=dof, loc=m, scale=sem)\n",
+ " if np.isnan(interval[0]) or np.isnan(interval[1]):\n",
+ " return m * 10**-6\n",
+ " return (interval[1]-interval[0])/2\n",
+ " \n",
+ "def plot(filename, metric=\"write_bw_mean_in_mb\"):\n",
+ " data = load_json(filename)\n",
+ " df = extract_results(data, metric=metric)\n",
+ " print(df)\n",
+ " ax = df[\"mean\"].plot(kind='bar', rot=0, xlabel='Date', ylabel='Value', title='My Plot', figsize=(6, 4), yerr=df[\"conf\"])\n",
+ " plt.show()\n",
+ "\n",
+ "def tex_table(lst):\n",
+ " for item in lst:\n",
+ " print(*item, sep=\" & \", end=\"\\\\\\\\\\n\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "846f7df3",
+ "metadata": {},
+ "source": [
+ "# Experiment: Parameter-evaluation: dd"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "83992cb3",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "application/javascript": [
+ "/* Put everything inside the global mpl namespace */\n",
+ "/* global mpl */\n",
+ "window.mpl = {};\n",
+ "\n",
+ "mpl.get_websocket_type = function () {\n",
+ " if (typeof WebSocket !== 'undefined') {\n",
+ " return WebSocket;\n",
+ " } else if (typeof MozWebSocket !== 'undefined') {\n",
+ " return MozWebSocket;\n",
+ " } else {\n",
+ " alert(\n",
+ " 'Your browser does not have WebSocket support. ' +\n",
+ " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
+ " 'Firefox 4 and 5 are also supported but you ' +\n",
+ " 'have to enable WebSockets in about:config.'\n",
+ " );\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n",
+ " this.id = figure_id;\n",
+ "\n",
+ " this.ws = websocket;\n",
+ "\n",
+ " this.supports_binary = this.ws.binaryType !== undefined;\n",
+ "\n",
+ " if (!this.supports_binary) {\n",
+ " var warnings = document.getElementById('mpl-warnings');\n",
+ " if (warnings) {\n",
+ " warnings.style.display = 'block';\n",
+ " warnings.textContent =\n",
+ " 'This browser does not support binary websocket messages. ' +\n",
+ " 'Performance may be slow.';\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " this.imageObj = new Image();\n",
+ "\n",
+ " this.context = undefined;\n",
+ " this.message = undefined;\n",
+ " this.canvas = undefined;\n",
+ " this.rubberband_canvas = undefined;\n",
+ " this.rubberband_context = undefined;\n",
+ " this.format_dropdown = undefined;\n",
+ "\n",
+ " this.image_mode = 'full';\n",
+ "\n",
+ " this.root = document.createElement('div');\n",
+ " this.root.setAttribute('style', 'display: inline-block');\n",
+ " this._root_extra_style(this.root);\n",
+ "\n",
+ " parent_element.appendChild(this.root);\n",
+ "\n",
+ " this._init_header(this);\n",
+ " this._init_canvas(this);\n",
+ " this._init_toolbar(this);\n",
+ "\n",
+ " var fig = this;\n",
+ "\n",
+ " this.waiting = false;\n",
+ "\n",
+ " this.ws.onopen = function () {\n",
+ " fig.send_message('supports_binary', { value: fig.supports_binary });\n",
+ " fig.send_message('send_image_mode', {});\n",
+ " if (fig.ratio !== 1) {\n",
+ " fig.send_message('set_device_pixel_ratio', {\n",
+ " device_pixel_ratio: fig.ratio,\n",
+ " });\n",
+ " }\n",
+ " fig.send_message('refresh', {});\n",
+ " };\n",
+ "\n",
+ " this.imageObj.onload = function () {\n",
+ " if (fig.image_mode === 'full') {\n",
+ " // Full images could contain transparency (where diff images\n",
+ " // almost always do), so we need to clear the canvas so that\n",
+ " // there is no ghosting.\n",
+ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
+ " }\n",
+ " fig.context.drawImage(fig.imageObj, 0, 0);\n",
+ " };\n",
+ "\n",
+ " this.imageObj.onunload = function () {\n",
+ " fig.ws.close();\n",
+ " };\n",
+ "\n",
+ " this.ws.onmessage = this._make_on_message_function(this);\n",
+ "\n",
+ " this.ondownload = ondownload;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_header = function () {\n",
+ " var titlebar = document.createElement('div');\n",
+ " titlebar.classList =\n",
+ " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n",
+ " var titletext = document.createElement('div');\n",
+ " titletext.classList = 'ui-dialog-title';\n",
+ " titletext.setAttribute(\n",
+ " 'style',\n",
+ " 'width: 100%; text-align: center; padding: 3px;'\n",
+ " );\n",
+ " titlebar.appendChild(titletext);\n",
+ " this.root.appendChild(titlebar);\n",
+ " this.header = titletext;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n",
+ "\n",
+ "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n",
+ "\n",
+ "mpl.figure.prototype._init_canvas = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var canvas_div = (this.canvas_div = document.createElement('div'));\n",
+ " canvas_div.setAttribute(\n",
+ " 'style',\n",
+ " 'border: 1px solid #ddd;' +\n",
+ " 'box-sizing: content-box;' +\n",
+ " 'clear: both;' +\n",
+ " 'min-height: 1px;' +\n",
+ " 'min-width: 1px;' +\n",
+ " 'outline: 0;' +\n",
+ " 'overflow: hidden;' +\n",
+ " 'position: relative;' +\n",
+ " 'resize: both;'\n",
+ " );\n",
+ "\n",
+ " function on_keyboard_event_closure(name) {\n",
+ " return function (event) {\n",
+ " return fig.key_event(event, name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " canvas_div.addEventListener(\n",
+ " 'keydown',\n",
+ " on_keyboard_event_closure('key_press')\n",
+ " );\n",
+ " canvas_div.addEventListener(\n",
+ " 'keyup',\n",
+ " on_keyboard_event_closure('key_release')\n",
+ " );\n",
+ "\n",
+ " this._canvas_extra_style(canvas_div);\n",
+ " this.root.appendChild(canvas_div);\n",
+ "\n",
+ " var canvas = (this.canvas = document.createElement('canvas'));\n",
+ " canvas.classList.add('mpl-canvas');\n",
+ " canvas.setAttribute('style', 'box-sizing: content-box;');\n",
+ "\n",
+ " this.context = canvas.getContext('2d');\n",
+ "\n",
+ " var backingStore =\n",
+ " this.context.backingStorePixelRatio ||\n",
+ " this.context.webkitBackingStorePixelRatio ||\n",
+ " this.context.mozBackingStorePixelRatio ||\n",
+ " this.context.msBackingStorePixelRatio ||\n",
+ " this.context.oBackingStorePixelRatio ||\n",
+ " this.context.backingStorePixelRatio ||\n",
+ " 1;\n",
+ "\n",
+ " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
+ "\n",
+ " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n",
+ " 'canvas'\n",
+ " ));\n",
+ " rubberband_canvas.setAttribute(\n",
+ " 'style',\n",
+ " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n",
+ " );\n",
+ "\n",
+ " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n",
+ " if (this.ResizeObserver === undefined) {\n",
+ " if (window.ResizeObserver !== undefined) {\n",
+ " this.ResizeObserver = window.ResizeObserver;\n",
+ " } else {\n",
+ " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n",
+ " this.ResizeObserver = obs.ResizeObserver;\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n",
+ " var nentries = entries.length;\n",
+ " for (var i = 0; i < nentries; i++) {\n",
+ " var entry = entries[i];\n",
+ " var width, height;\n",
+ " if (entry.contentBoxSize) {\n",
+ " if (entry.contentBoxSize instanceof Array) {\n",
+ " // Chrome 84 implements new version of spec.\n",
+ " width = entry.contentBoxSize[0].inlineSize;\n",
+ " height = entry.contentBoxSize[0].blockSize;\n",
+ " } else {\n",
+ " // Firefox implements old version of spec.\n",
+ " width = entry.contentBoxSize.inlineSize;\n",
+ " height = entry.contentBoxSize.blockSize;\n",
+ " }\n",
+ " } else {\n",
+ " // Chrome <84 implements even older version of spec.\n",
+ " width = entry.contentRect.width;\n",
+ " height = entry.contentRect.height;\n",
+ " }\n",
+ "\n",
+ " // Keep the size of the canvas and rubber band canvas in sync with\n",
+ " // the canvas container.\n",
+ " if (entry.devicePixelContentBoxSize) {\n",
+ " // Chrome 84 implements new version of spec.\n",
+ " canvas.setAttribute(\n",
+ " 'width',\n",
+ " entry.devicePixelContentBoxSize[0].inlineSize\n",
+ " );\n",
+ " canvas.setAttribute(\n",
+ " 'height',\n",
+ " entry.devicePixelContentBoxSize[0].blockSize\n",
+ " );\n",
+ " } else {\n",
+ " canvas.setAttribute('width', width * fig.ratio);\n",
+ " canvas.setAttribute('height', height * fig.ratio);\n",
+ " }\n",
+ " canvas.setAttribute(\n",
+ " 'style',\n",
+ " 'width: ' + width + 'px; height: ' + height + 'px;'\n",
+ " );\n",
+ "\n",
+ " rubberband_canvas.setAttribute('width', width);\n",
+ " rubberband_canvas.setAttribute('height', height);\n",
+ "\n",
+ " // And update the size in Python. We ignore the initial 0/0 size\n",
+ " // that occurs as the element is placed into the DOM, which should\n",
+ " // otherwise not happen due to the minimum size styling.\n",
+ " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n",
+ " fig.request_resize(width, height);\n",
+ " }\n",
+ " }\n",
+ " });\n",
+ " this.resizeObserverInstance.observe(canvas_div);\n",
+ "\n",
+ " function on_mouse_event_closure(name) {\n",
+ " return function (event) {\n",
+ " return fig.mouse_event(event, name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mousedown',\n",
+ " on_mouse_event_closure('button_press')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseup',\n",
+ " on_mouse_event_closure('button_release')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'dblclick',\n",
+ " on_mouse_event_closure('dblclick')\n",
+ " );\n",
+ " // Throttle sequential mouse events to 1 every 20ms.\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mousemove',\n",
+ " on_mouse_event_closure('motion_notify')\n",
+ " );\n",
+ "\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseenter',\n",
+ " on_mouse_event_closure('figure_enter')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseleave',\n",
+ " on_mouse_event_closure('figure_leave')\n",
+ " );\n",
+ "\n",
+ " canvas_div.addEventListener('wheel', function (event) {\n",
+ " if (event.deltaY < 0) {\n",
+ " event.step = 1;\n",
+ " } else {\n",
+ " event.step = -1;\n",
+ " }\n",
+ " on_mouse_event_closure('scroll')(event);\n",
+ " });\n",
+ "\n",
+ " canvas_div.appendChild(canvas);\n",
+ " canvas_div.appendChild(rubberband_canvas);\n",
+ "\n",
+ " this.rubberband_context = rubberband_canvas.getContext('2d');\n",
+ " this.rubberband_context.strokeStyle = '#000000';\n",
+ "\n",
+ " this._resize_canvas = function (width, height, forward) {\n",
+ " if (forward) {\n",
+ " canvas_div.style.width = width + 'px';\n",
+ " canvas_div.style.height = height + 'px';\n",
+ " }\n",
+ " };\n",
+ "\n",
+ " // Disable right mouse context menu.\n",
+ " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n",
+ " event.preventDefault();\n",
+ " return false;\n",
+ " });\n",
+ "\n",
+ " function set_focus() {\n",
+ " canvas.focus();\n",
+ " canvas_div.focus();\n",
+ " }\n",
+ "\n",
+ " window.setTimeout(set_focus, 100);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_toolbar = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var toolbar = document.createElement('div');\n",
+ " toolbar.classList = 'mpl-toolbar';\n",
+ " this.root.appendChild(toolbar);\n",
+ "\n",
+ " function on_click_closure(name) {\n",
+ " return function (_event) {\n",
+ " return fig.toolbar_button_onclick(name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " function on_mouseover_closure(tooltip) {\n",
+ " return function (event) {\n",
+ " if (!event.currentTarget.disabled) {\n",
+ " return fig.toolbar_button_onmouseover(tooltip);\n",
+ " }\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " fig.buttons = {};\n",
+ " var buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'mpl-button-group';\n",
+ " for (var toolbar_ind in mpl.toolbar_items) {\n",
+ " var name = mpl.toolbar_items[toolbar_ind][0];\n",
+ " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+ " var image = mpl.toolbar_items[toolbar_ind][2];\n",
+ " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+ "\n",
+ " if (!name) {\n",
+ " /* Instead of a spacer, we start a new button group. */\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ " buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'mpl-button-group';\n",
+ " continue;\n",
+ " }\n",
+ "\n",
+ " var button = (fig.buttons[name] = document.createElement('button'));\n",
+ " button.classList = 'mpl-widget';\n",
+ " button.setAttribute('role', 'button');\n",
+ " button.setAttribute('aria-disabled', 'false');\n",
+ " button.addEventListener('click', on_click_closure(method_name));\n",
+ " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+ "\n",
+ " var icon_img = document.createElement('img');\n",
+ " icon_img.src = '_images/' + image + '.png';\n",
+ " icon_img.srcset = '_images/' + image + '_large.png 2x';\n",
+ " icon_img.alt = tooltip;\n",
+ " button.appendChild(icon_img);\n",
+ "\n",
+ " buttonGroup.appendChild(button);\n",
+ " }\n",
+ "\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ "\n",
+ " var fmt_picker = document.createElement('select');\n",
+ " fmt_picker.classList = 'mpl-widget';\n",
+ " toolbar.appendChild(fmt_picker);\n",
+ " this.format_dropdown = fmt_picker;\n",
+ "\n",
+ " for (var ind in mpl.extensions) {\n",
+ " var fmt = mpl.extensions[ind];\n",
+ " var option = document.createElement('option');\n",
+ " option.selected = fmt === mpl.default_extension;\n",
+ " option.innerHTML = fmt;\n",
+ " fmt_picker.appendChild(option);\n",
+ " }\n",
+ "\n",
+ " var status_bar = document.createElement('span');\n",
+ " status_bar.classList = 'mpl-message';\n",
+ " toolbar.appendChild(status_bar);\n",
+ " this.message = status_bar;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n",
+ " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
+ " // which will in turn request a refresh of the image.\n",
+ " this.send_message('resize', { width: x_pixels, height: y_pixels });\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.send_message = function (type, properties) {\n",
+ " properties['type'] = type;\n",
+ " properties['figure_id'] = this.id;\n",
+ " this.ws.send(JSON.stringify(properties));\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.send_draw_message = function () {\n",
+ " if (!this.waiting) {\n",
+ " this.waiting = true;\n",
+ " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+ " var format_dropdown = fig.format_dropdown;\n",
+ " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
+ " fig.ondownload(fig, format);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_resize = function (fig, msg) {\n",
+ " var size = msg['size'];\n",
+ " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n",
+ " fig._resize_canvas(size[0], size[1], msg['forward']);\n",
+ " fig.send_message('refresh', {});\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n",
+ " var x0 = msg['x0'] / fig.ratio;\n",
+ " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n",
+ " var x1 = msg['x1'] / fig.ratio;\n",
+ " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n",
+ " x0 = Math.floor(x0) + 0.5;\n",
+ " y0 = Math.floor(y0) + 0.5;\n",
+ " x1 = Math.floor(x1) + 0.5;\n",
+ " y1 = Math.floor(y1) + 0.5;\n",
+ " var min_x = Math.min(x0, x1);\n",
+ " var min_y = Math.min(y0, y1);\n",
+ " var width = Math.abs(x1 - x0);\n",
+ " var height = Math.abs(y1 - y0);\n",
+ "\n",
+ " fig.rubberband_context.clearRect(\n",
+ " 0,\n",
+ " 0,\n",
+ " fig.canvas.width / fig.ratio,\n",
+ " fig.canvas.height / fig.ratio\n",
+ " );\n",
+ "\n",
+ " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n",
+ " // Updates the figure title.\n",
+ " fig.header.textContent = msg['label'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n",
+ " fig.rubberband_canvas.style.cursor = msg['cursor'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_message = function (fig, msg) {\n",
+ " fig.message.textContent = msg['message'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n",
+ " // Request the server to send over a new figure.\n",
+ " fig.send_draw_message();\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n",
+ " fig.image_mode = msg['mode'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n",
+ " for (var key in msg) {\n",
+ " if (!(key in fig.buttons)) {\n",
+ " continue;\n",
+ " }\n",
+ " fig.buttons[key].disabled = !msg[key];\n",
+ " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n",
+ " if (msg['mode'] === 'PAN') {\n",
+ " fig.buttons['Pan'].classList.add('active');\n",
+ " fig.buttons['Zoom'].classList.remove('active');\n",
+ " } else if (msg['mode'] === 'ZOOM') {\n",
+ " fig.buttons['Pan'].classList.remove('active');\n",
+ " fig.buttons['Zoom'].classList.add('active');\n",
+ " } else {\n",
+ " fig.buttons['Pan'].classList.remove('active');\n",
+ " fig.buttons['Zoom'].classList.remove('active');\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.updated_canvas_event = function () {\n",
+ " // Called whenever the canvas gets updated.\n",
+ " this.send_message('ack', {});\n",
+ "};\n",
+ "\n",
+ "// A function to construct a web socket function for onmessage handling.\n",
+ "// Called in the figure constructor.\n",
+ "mpl.figure.prototype._make_on_message_function = function (fig) {\n",
+ " return function socket_on_message(evt) {\n",
+ " if (evt.data instanceof Blob) {\n",
+ " var img = evt.data;\n",
+ " if (img.type !== 'image/png') {\n",
+ " /* FIXME: We get \"Resource interpreted as Image but\n",
+ " * transferred with MIME type text/plain:\" errors on\n",
+ " * Chrome. But how to set the MIME type? It doesn't seem\n",
+ " * to be part of the websocket stream */\n",
+ " img.type = 'image/png';\n",
+ " }\n",
+ "\n",
+ " /* Free the memory for the previous frames */\n",
+ " if (fig.imageObj.src) {\n",
+ " (window.URL || window.webkitURL).revokeObjectURL(\n",
+ " fig.imageObj.src\n",
+ " );\n",
+ " }\n",
+ "\n",
+ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
+ " img\n",
+ " );\n",
+ " fig.updated_canvas_event();\n",
+ " fig.waiting = false;\n",
+ " return;\n",
+ " } else if (\n",
+ " typeof evt.data === 'string' &&\n",
+ " evt.data.slice(0, 21) === 'data:image/png;base64'\n",
+ " ) {\n",
+ " fig.imageObj.src = evt.data;\n",
+ " fig.updated_canvas_event();\n",
+ " fig.waiting = false;\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " var msg = JSON.parse(evt.data);\n",
+ " var msg_type = msg['type'];\n",
+ "\n",
+ " // Call the \"handle_{type}\" callback, which takes\n",
+ " // the figure and JSON message as its only arguments.\n",
+ " try {\n",
+ " var callback = fig['handle_' + msg_type];\n",
+ " } catch (e) {\n",
+ " console.log(\n",
+ " \"No handler for the '\" + msg_type + \"' message type: \",\n",
+ " msg\n",
+ " );\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " if (callback) {\n",
+ " try {\n",
+ " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
+ " callback(fig, msg);\n",
+ " } catch (e) {\n",
+ " console.log(\n",
+ " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n",
+ " e,\n",
+ " e.stack,\n",
+ " msg\n",
+ " );\n",
+ " }\n",
+ " }\n",
+ " };\n",
+ "};\n",
+ "\n",
+ "// from https://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
+ "mpl.findpos = function (e) {\n",
+ " //this section is from http://www.quirksmode.org/js/events_properties.html\n",
+ " var targ;\n",
+ " if (!e) {\n",
+ " e = window.event;\n",
+ " }\n",
+ " if (e.target) {\n",
+ " targ = e.target;\n",
+ " } else if (e.srcElement) {\n",
+ " targ = e.srcElement;\n",
+ " }\n",
+ " if (targ.nodeType === 3) {\n",
+ " // defeat Safari bug\n",
+ " targ = targ.parentNode;\n",
+ " }\n",
+ "\n",
+ " // pageX,Y are the mouse positions relative to the document\n",
+ " var boundingRect = targ.getBoundingClientRect();\n",
+ " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n",
+ " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n",
+ "\n",
+ " return { x: x, y: y };\n",
+ "};\n",
+ "\n",
+ "/*\n",
+ " * return a copy of an object with only non-object keys\n",
+ " * we need this to avoid circular references\n",
+ " * https://stackoverflow.com/a/24161582/3208463\n",
+ " */\n",
+ "function simpleKeys(original) {\n",
+ " return Object.keys(original).reduce(function (obj, key) {\n",
+ " if (typeof original[key] !== 'object') {\n",
+ " obj[key] = original[key];\n",
+ " }\n",
+ " return obj;\n",
+ " }, {});\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.mouse_event = function (event, name) {\n",
+ " var canvas_pos = mpl.findpos(event);\n",
+ "\n",
+ " if (name === 'button_press') {\n",
+ " this.canvas.focus();\n",
+ " this.canvas_div.focus();\n",
+ " }\n",
+ "\n",
+ " var x = canvas_pos.x * this.ratio;\n",
+ " var y = canvas_pos.y * this.ratio;\n",
+ "\n",
+ " this.send_message(name, {\n",
+ " x: x,\n",
+ " y: y,\n",
+ " button: event.button,\n",
+ " step: event.step,\n",
+ " guiEvent: simpleKeys(event),\n",
+ " });\n",
+ "\n",
+ " /* This prevents the web browser from automatically changing to\n",
+ " * the text insertion cursor when the button is pressed. We want\n",
+ " * to control all of the cursor setting manually through the\n",
+ " * 'cursor' event from matplotlib */\n",
+ " event.preventDefault();\n",
+ " return false;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n",
+ " // Handle any extra behaviour associated with a key event\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.key_event = function (event, name) {\n",
+ " // Prevent repeat events\n",
+ " if (name === 'key_press') {\n",
+ " if (event.key === this._key) {\n",
+ " return;\n",
+ " } else {\n",
+ " this._key = event.key;\n",
+ " }\n",
+ " }\n",
+ " if (name === 'key_release') {\n",
+ " this._key = null;\n",
+ " }\n",
+ "\n",
+ " var value = '';\n",
+ " if (event.ctrlKey && event.key !== 'Control') {\n",
+ " value += 'ctrl+';\n",
+ " }\n",
+ " else if (event.altKey && event.key !== 'Alt') {\n",
+ " value += 'alt+';\n",
+ " }\n",
+ " else if (event.shiftKey && event.key !== 'Shift') {\n",
+ " value += 'shift+';\n",
+ " }\n",
+ "\n",
+ " value += 'k' + event.key;\n",
+ "\n",
+ " this._key_event_extra(event, name);\n",
+ "\n",
+ " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n",
+ " return false;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n",
+ " if (name === 'download') {\n",
+ " this.handle_save(this, null);\n",
+ " } else {\n",
+ " this.send_message('toolbar_button', { name: name });\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n",
+ " this.message.textContent = tooltip;\n",
+ "};\n",
+ "\n",
+ "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n",
+ "// prettier-ignore\n",
+ "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n",
+ "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
+ "\n",
+ "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
+ "\n",
+ "mpl.default_extension = \"png\";/* global mpl */\n",
+ "\n",
+ "var comm_websocket_adapter = function (comm) {\n",
+ " // Create a \"websocket\"-like object which calls the given IPython comm\n",
+ " // object with the appropriate methods. Currently this is a non binary\n",
+ " // socket, so there is still some room for performance tuning.\n",
+ " var ws = {};\n",
+ "\n",
+ " ws.binaryType = comm.kernel.ws.binaryType;\n",
+ " ws.readyState = comm.kernel.ws.readyState;\n",
+ " function updateReadyState(_event) {\n",
+ " if (comm.kernel.ws) {\n",
+ " ws.readyState = comm.kernel.ws.readyState;\n",
+ " } else {\n",
+ " ws.readyState = 3; // Closed state.\n",
+ " }\n",
+ " }\n",
+ " comm.kernel.ws.addEventListener('open', updateReadyState);\n",
+ " comm.kernel.ws.addEventListener('close', updateReadyState);\n",
+ " comm.kernel.ws.addEventListener('error', updateReadyState);\n",
+ "\n",
+ " ws.close = function () {\n",
+ " comm.close();\n",
+ " };\n",
+ " ws.send = function (m) {\n",
+ " //console.log('sending', m);\n",
+ " comm.send(m);\n",
+ " };\n",
+ " // Register the callback with on_msg.\n",
+ " comm.on_msg(function (msg) {\n",
+ " //console.log('receiving', msg['content']['data'], msg);\n",
+ " var data = msg['content']['data'];\n",
+ " if (data['blob'] !== undefined) {\n",
+ " data = {\n",
+ " data: new Blob(msg['buffers'], { type: data['blob'] }),\n",
+ " };\n",
+ " }\n",
+ " // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
+ " ws.onmessage(data);\n",
+ " });\n",
+ " return ws;\n",
+ "};\n",
+ "\n",
+ "mpl.mpl_figure_comm = function (comm, msg) {\n",
+ " // This is the function which gets called when the mpl process\n",
+ " // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
+ "\n",
+ " var id = msg.content.data.id;\n",
+ " // Get hold of the div created by the display call when the Comm\n",
+ " // socket was opened in Python.\n",
+ " var element = document.getElementById(id);\n",
+ " var ws_proxy = comm_websocket_adapter(comm);\n",
+ "\n",
+ " function ondownload(figure, _format) {\n",
+ " window.open(figure.canvas.toDataURL());\n",
+ " }\n",
+ "\n",
+ " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n",
+ "\n",
+ " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
+ " // web socket which is closed, not our websocket->open comm proxy.\n",
+ " ws_proxy.onopen();\n",
+ "\n",
+ " fig.parent_element = element;\n",
+ " fig.cell_info = mpl.find_output_cell(\"\");\n",
+ " if (!fig.cell_info) {\n",
+ " console.error('Failed to find cell for figure', id, fig);\n",
+ " return;\n",
+ " }\n",
+ " fig.cell_info[0].output_area.element.on(\n",
+ " 'cleared',\n",
+ " { fig: fig },\n",
+ " fig._remove_fig_handler\n",
+ " );\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_close = function (fig, msg) {\n",
+ " var width = fig.canvas.width / fig.ratio;\n",
+ " fig.cell_info[0].output_area.element.off(\n",
+ " 'cleared',\n",
+ " fig._remove_fig_handler\n",
+ " );\n",
+ " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n",
+ "\n",
+ " // Update the output cell to use the data from the current canvas.\n",
+ " fig.push_to_output();\n",
+ " var dataURL = fig.canvas.toDataURL();\n",
+ " // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
+ " // the notebook keyboard shortcuts fail.\n",
+ " IPython.keyboard_manager.enable();\n",
+ " fig.parent_element.innerHTML =\n",
+ " '
';\n",
+ " fig.close_ws(fig, msg);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.close_ws = function (fig, msg) {\n",
+ " fig.send_message('closing', msg);\n",
+ " // fig.ws.close()\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n",
+ " // Turn the data on the canvas into data in the output cell.\n",
+ " var width = this.canvas.width / this.ratio;\n",
+ " var dataURL = this.canvas.toDataURL();\n",
+ " this.cell_info[1]['text/html'] =\n",
+ " '
';\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.updated_canvas_event = function () {\n",
+ " // Tell IPython that the notebook contents must change.\n",
+ " IPython.notebook.set_dirty(true);\n",
+ " this.send_message('ack', {});\n",
+ " var fig = this;\n",
+ " // Wait a second, then push the new image to the DOM so\n",
+ " // that it is saved nicely (might be nice to debounce this).\n",
+ " setTimeout(function () {\n",
+ " fig.push_to_output();\n",
+ " }, 1000);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_toolbar = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var toolbar = document.createElement('div');\n",
+ " toolbar.classList = 'btn-toolbar';\n",
+ " this.root.appendChild(toolbar);\n",
+ "\n",
+ " function on_click_closure(name) {\n",
+ " return function (_event) {\n",
+ " return fig.toolbar_button_onclick(name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " function on_mouseover_closure(tooltip) {\n",
+ " return function (event) {\n",
+ " if (!event.currentTarget.disabled) {\n",
+ " return fig.toolbar_button_onmouseover(tooltip);\n",
+ " }\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " fig.buttons = {};\n",
+ " var buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'btn-group';\n",
+ " var button;\n",
+ " for (var toolbar_ind in mpl.toolbar_items) {\n",
+ " var name = mpl.toolbar_items[toolbar_ind][0];\n",
+ " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+ " var image = mpl.toolbar_items[toolbar_ind][2];\n",
+ " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+ "\n",
+ " if (!name) {\n",
+ " /* Instead of a spacer, we start a new button group. */\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ " buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'btn-group';\n",
+ " continue;\n",
+ " }\n",
+ "\n",
+ " button = fig.buttons[name] = document.createElement('button');\n",
+ " button.classList = 'btn btn-default';\n",
+ " button.href = '#';\n",
+ " button.title = name;\n",
+ " button.innerHTML = '';\n",
+ " button.addEventListener('click', on_click_closure(method_name));\n",
+ " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+ " buttonGroup.appendChild(button);\n",
+ " }\n",
+ "\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ "\n",
+ " // Add the status bar.\n",
+ " var status_bar = document.createElement('span');\n",
+ " status_bar.classList = 'mpl-message pull-right';\n",
+ " toolbar.appendChild(status_bar);\n",
+ " this.message = status_bar;\n",
+ "\n",
+ " // Add the close button to the window.\n",
+ " var buttongrp = document.createElement('div');\n",
+ " buttongrp.classList = 'btn-group inline pull-right';\n",
+ " button = document.createElement('button');\n",
+ " button.classList = 'btn btn-mini btn-primary';\n",
+ " button.href = '#';\n",
+ " button.title = 'Stop Interaction';\n",
+ " button.innerHTML = '';\n",
+ " button.addEventListener('click', function (_evt) {\n",
+ " fig.handle_close(fig, {});\n",
+ " });\n",
+ " button.addEventListener(\n",
+ " 'mouseover',\n",
+ " on_mouseover_closure('Stop Interaction')\n",
+ " );\n",
+ " buttongrp.appendChild(button);\n",
+ " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n",
+ " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._remove_fig_handler = function (event) {\n",
+ " var fig = event.data.fig;\n",
+ " if (event.target !== this) {\n",
+ " // Ignore bubbled events from children.\n",
+ " return;\n",
+ " }\n",
+ " fig.close_ws(fig, {});\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._root_extra_style = function (el) {\n",
+ " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._canvas_extra_style = function (el) {\n",
+ " // this is important to make the div 'focusable\n",
+ " el.setAttribute('tabindex', 0);\n",
+ " // reach out to IPython and tell the keyboard manager to turn it's self\n",
+ " // off when our div gets focus\n",
+ "\n",
+ " // location in version 3\n",
+ " if (IPython.notebook.keyboard_manager) {\n",
+ " IPython.notebook.keyboard_manager.register_events(el);\n",
+ " } else {\n",
+ " // location in version 2\n",
+ " IPython.keyboard_manager.register_events(el);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._key_event_extra = function (event, _name) {\n",
+ " // Check for shift+enter\n",
+ " if (event.shiftKey && event.which === 13) {\n",
+ " this.canvas_div.blur();\n",
+ " // select the cell after this one\n",
+ " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n",
+ " IPython.notebook.select(index + 1);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+ " fig.ondownload(fig, null);\n",
+ "};\n",
+ "\n",
+ "mpl.find_output_cell = function (html_output) {\n",
+ " // Return the cell and output element which can be found *uniquely* in the notebook.\n",
+ " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
+ " // IPython event is triggered only after the cells have been serialised, which for\n",
+ " // our purposes (turning an active figure into a static one), is too late.\n",
+ " var cells = IPython.notebook.get_cells();\n",
+ " var ncells = cells.length;\n",
+ " for (var i = 0; i < ncells; i++) {\n",
+ " var cell = cells[i];\n",
+ " if (cell.cell_type === 'code') {\n",
+ " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n",
+ " var data = cell.output_area.outputs[j];\n",
+ " if (data.data) {\n",
+ " // IPython >= 3 moved mimebundle to data attribute of output\n",
+ " data = data.data;\n",
+ " }\n",
+ " if (data['text/html'] === html_output) {\n",
+ " return [cell, data, j];\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "// Register the function which deals with the matplotlib target/channel.\n",
+ "// The kernel may be null if the page has been refreshed.\n",
+ "if (IPython.notebook.kernel !== null) {\n",
+ " IPython.notebook.kernel.comm_manager.register_target(\n",
+ " 'matplotlib',\n",
+ " mpl.mpl_figure_comm\n",
+ " );\n",
+ "}\n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "
"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "%matplotlib notebook\n",
+ "\n",
+ "# Read in results\n",
+ "## Params - Either specify a file prefix or a whole folder\n",
+ "file_prefix = None # Path(\"./results/2022-07-05T20:00:06_throughput_write_dd_\")\n",
+ "folder = Path(\"./results/param_study_dd\")\n",
+ "\n",
+ "## Plot code\n",
+ "path = file_prefix if file_prefix else folder\n",
+ "\n",
+ "df_isilon = pd.concat({\n",
+ " \"Single\": load_data(path, metric=\"bw_in_MiB\", suffix=\"isilon_a.json\"),\n",
+ " \"Parallel\": load_data(path, metric=\"bw_in_MiB\", suffix=\"isilon_b.json\"),\n",
+ "})\n",
+ "\n",
+ "df_netapp = pd.concat({\n",
+ " \"Single\": load_data(path, metric=\"bw_in_MiB\", suffix=\"netapp_a.json\"),\n",
+ " \"Parallel\": load_data(path, metric=\"bw_in_MiB\", suffix=\"netapp_b.json\"),\n",
+ "})\n",
+ "\n",
+ "fig, (ax1, ax2) = plt.subplots(1, 2, sharey=True)\n",
+ "fig.suptitle('Parameter Study: dd')\n",
+ "\n",
+ "df_isilon[\"mean\"].unstack(level=0)[[\"Single\",\"Parallel\"]].plot(\n",
+ " ax=ax1,\n",
+ " kind='bar', rot=0,\n",
+ " xlabel='Transferred Data (GiB)',\n",
+ " ylabel='Bandwidth (MiB/s)',\n",
+ " title=f\"nfs_isilon\",\n",
+ " figsize=(8, 4),\n",
+ " yerr=df_isilon[\"std\"].unstack(level=0)[[\"Single\",\"Parallel\"]]\n",
+ ")\n",
+ "\n",
+ "df_netapp[\"mean\"].unstack(level=0)[[\"Single\",\"Parallel\"]].plot(\n",
+ " ax=ax2,\n",
+ " kind='bar', rot=0,\n",
+ " xlabel='Transferred Data (GiB)',\n",
+ " ylabel='Bandwidth (MiB/s)',\n",
+ " title=f\"nfs_netapp\",\n",
+ " figsize=(8, 4),\n",
+ " yerr=df_netapp[\"std\"].unstack(level=0)[[\"Single\",\"Parallel\"]],\n",
+ ")\n",
+ "\n",
+ "ax1.get_legend().remove()\n",
+ "ax2.legend(loc=\"upper left\")\n",
+ "plt.tight_layout()\n",
+ "plt.savefig(\"paramstudy_dd.png\",bbox_inches='tight')\n",
+ "plt.show()\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1f2c0e77",
+ "metadata": {},
+ "source": [
+ "# Experiment: Parameter-evaluation: Fio"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "b1f156a9",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "application/javascript": [
+ "/* Put everything inside the global mpl namespace */\n",
+ "/* global mpl */\n",
+ "window.mpl = {};\n",
+ "\n",
+ "mpl.get_websocket_type = function () {\n",
+ " if (typeof WebSocket !== 'undefined') {\n",
+ " return WebSocket;\n",
+ " } else if (typeof MozWebSocket !== 'undefined') {\n",
+ " return MozWebSocket;\n",
+ " } else {\n",
+ " alert(\n",
+ " 'Your browser does not have WebSocket support. ' +\n",
+ " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
+ " 'Firefox 4 and 5 are also supported but you ' +\n",
+ " 'have to enable WebSockets in about:config.'\n",
+ " );\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n",
+ " this.id = figure_id;\n",
+ "\n",
+ " this.ws = websocket;\n",
+ "\n",
+ " this.supports_binary = this.ws.binaryType !== undefined;\n",
+ "\n",
+ " if (!this.supports_binary) {\n",
+ " var warnings = document.getElementById('mpl-warnings');\n",
+ " if (warnings) {\n",
+ " warnings.style.display = 'block';\n",
+ " warnings.textContent =\n",
+ " 'This browser does not support binary websocket messages. ' +\n",
+ " 'Performance may be slow.';\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " this.imageObj = new Image();\n",
+ "\n",
+ " this.context = undefined;\n",
+ " this.message = undefined;\n",
+ " this.canvas = undefined;\n",
+ " this.rubberband_canvas = undefined;\n",
+ " this.rubberband_context = undefined;\n",
+ " this.format_dropdown = undefined;\n",
+ "\n",
+ " this.image_mode = 'full';\n",
+ "\n",
+ " this.root = document.createElement('div');\n",
+ " this.root.setAttribute('style', 'display: inline-block');\n",
+ " this._root_extra_style(this.root);\n",
+ "\n",
+ " parent_element.appendChild(this.root);\n",
+ "\n",
+ " this._init_header(this);\n",
+ " this._init_canvas(this);\n",
+ " this._init_toolbar(this);\n",
+ "\n",
+ " var fig = this;\n",
+ "\n",
+ " this.waiting = false;\n",
+ "\n",
+ " this.ws.onopen = function () {\n",
+ " fig.send_message('supports_binary', { value: fig.supports_binary });\n",
+ " fig.send_message('send_image_mode', {});\n",
+ " if (fig.ratio !== 1) {\n",
+ " fig.send_message('set_device_pixel_ratio', {\n",
+ " device_pixel_ratio: fig.ratio,\n",
+ " });\n",
+ " }\n",
+ " fig.send_message('refresh', {});\n",
+ " };\n",
+ "\n",
+ " this.imageObj.onload = function () {\n",
+ " if (fig.image_mode === 'full') {\n",
+ " // Full images could contain transparency (where diff images\n",
+ " // almost always do), so we need to clear the canvas so that\n",
+ " // there is no ghosting.\n",
+ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
+ " }\n",
+ " fig.context.drawImage(fig.imageObj, 0, 0);\n",
+ " };\n",
+ "\n",
+ " this.imageObj.onunload = function () {\n",
+ " fig.ws.close();\n",
+ " };\n",
+ "\n",
+ " this.ws.onmessage = this._make_on_message_function(this);\n",
+ "\n",
+ " this.ondownload = ondownload;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_header = function () {\n",
+ " var titlebar = document.createElement('div');\n",
+ " titlebar.classList =\n",
+ " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n",
+ " var titletext = document.createElement('div');\n",
+ " titletext.classList = 'ui-dialog-title';\n",
+ " titletext.setAttribute(\n",
+ " 'style',\n",
+ " 'width: 100%; text-align: center; padding: 3px;'\n",
+ " );\n",
+ " titlebar.appendChild(titletext);\n",
+ " this.root.appendChild(titlebar);\n",
+ " this.header = titletext;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n",
+ "\n",
+ "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n",
+ "\n",
+ "mpl.figure.prototype._init_canvas = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var canvas_div = (this.canvas_div = document.createElement('div'));\n",
+ " canvas_div.setAttribute(\n",
+ " 'style',\n",
+ " 'border: 1px solid #ddd;' +\n",
+ " 'box-sizing: content-box;' +\n",
+ " 'clear: both;' +\n",
+ " 'min-height: 1px;' +\n",
+ " 'min-width: 1px;' +\n",
+ " 'outline: 0;' +\n",
+ " 'overflow: hidden;' +\n",
+ " 'position: relative;' +\n",
+ " 'resize: both;'\n",
+ " );\n",
+ "\n",
+ " function on_keyboard_event_closure(name) {\n",
+ " return function (event) {\n",
+ " return fig.key_event(event, name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " canvas_div.addEventListener(\n",
+ " 'keydown',\n",
+ " on_keyboard_event_closure('key_press')\n",
+ " );\n",
+ " canvas_div.addEventListener(\n",
+ " 'keyup',\n",
+ " on_keyboard_event_closure('key_release')\n",
+ " );\n",
+ "\n",
+ " this._canvas_extra_style(canvas_div);\n",
+ " this.root.appendChild(canvas_div);\n",
+ "\n",
+ " var canvas = (this.canvas = document.createElement('canvas'));\n",
+ " canvas.classList.add('mpl-canvas');\n",
+ " canvas.setAttribute('style', 'box-sizing: content-box;');\n",
+ "\n",
+ " this.context = canvas.getContext('2d');\n",
+ "\n",
+ " var backingStore =\n",
+ " this.context.backingStorePixelRatio ||\n",
+ " this.context.webkitBackingStorePixelRatio ||\n",
+ " this.context.mozBackingStorePixelRatio ||\n",
+ " this.context.msBackingStorePixelRatio ||\n",
+ " this.context.oBackingStorePixelRatio ||\n",
+ " this.context.backingStorePixelRatio ||\n",
+ " 1;\n",
+ "\n",
+ " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
+ "\n",
+ " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n",
+ " 'canvas'\n",
+ " ));\n",
+ " rubberband_canvas.setAttribute(\n",
+ " 'style',\n",
+ " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n",
+ " );\n",
+ "\n",
+ " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n",
+ " if (this.ResizeObserver === undefined) {\n",
+ " if (window.ResizeObserver !== undefined) {\n",
+ " this.ResizeObserver = window.ResizeObserver;\n",
+ " } else {\n",
+ " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n",
+ " this.ResizeObserver = obs.ResizeObserver;\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n",
+ " var nentries = entries.length;\n",
+ " for (var i = 0; i < nentries; i++) {\n",
+ " var entry = entries[i];\n",
+ " var width, height;\n",
+ " if (entry.contentBoxSize) {\n",
+ " if (entry.contentBoxSize instanceof Array) {\n",
+ " // Chrome 84 implements new version of spec.\n",
+ " width = entry.contentBoxSize[0].inlineSize;\n",
+ " height = entry.contentBoxSize[0].blockSize;\n",
+ " } else {\n",
+ " // Firefox implements old version of spec.\n",
+ " width = entry.contentBoxSize.inlineSize;\n",
+ " height = entry.contentBoxSize.blockSize;\n",
+ " }\n",
+ " } else {\n",
+ " // Chrome <84 implements even older version of spec.\n",
+ " width = entry.contentRect.width;\n",
+ " height = entry.contentRect.height;\n",
+ " }\n",
+ "\n",
+ " // Keep the size of the canvas and rubber band canvas in sync with\n",
+ " // the canvas container.\n",
+ " if (entry.devicePixelContentBoxSize) {\n",
+ " // Chrome 84 implements new version of spec.\n",
+ " canvas.setAttribute(\n",
+ " 'width',\n",
+ " entry.devicePixelContentBoxSize[0].inlineSize\n",
+ " );\n",
+ " canvas.setAttribute(\n",
+ " 'height',\n",
+ " entry.devicePixelContentBoxSize[0].blockSize\n",
+ " );\n",
+ " } else {\n",
+ " canvas.setAttribute('width', width * fig.ratio);\n",
+ " canvas.setAttribute('height', height * fig.ratio);\n",
+ " }\n",
+ " canvas.setAttribute(\n",
+ " 'style',\n",
+ " 'width: ' + width + 'px; height: ' + height + 'px;'\n",
+ " );\n",
+ "\n",
+ " rubberband_canvas.setAttribute('width', width);\n",
+ " rubberband_canvas.setAttribute('height', height);\n",
+ "\n",
+ " // And update the size in Python. We ignore the initial 0/0 size\n",
+ " // that occurs as the element is placed into the DOM, which should\n",
+ " // otherwise not happen due to the minimum size styling.\n",
+ " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n",
+ " fig.request_resize(width, height);\n",
+ " }\n",
+ " }\n",
+ " });\n",
+ " this.resizeObserverInstance.observe(canvas_div);\n",
+ "\n",
+ " function on_mouse_event_closure(name) {\n",
+ " return function (event) {\n",
+ " return fig.mouse_event(event, name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mousedown',\n",
+ " on_mouse_event_closure('button_press')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseup',\n",
+ " on_mouse_event_closure('button_release')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'dblclick',\n",
+ " on_mouse_event_closure('dblclick')\n",
+ " );\n",
+ " // Throttle sequential mouse events to 1 every 20ms.\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mousemove',\n",
+ " on_mouse_event_closure('motion_notify')\n",
+ " );\n",
+ "\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseenter',\n",
+ " on_mouse_event_closure('figure_enter')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseleave',\n",
+ " on_mouse_event_closure('figure_leave')\n",
+ " );\n",
+ "\n",
+ " canvas_div.addEventListener('wheel', function (event) {\n",
+ " if (event.deltaY < 0) {\n",
+ " event.step = 1;\n",
+ " } else {\n",
+ " event.step = -1;\n",
+ " }\n",
+ " on_mouse_event_closure('scroll')(event);\n",
+ " });\n",
+ "\n",
+ " canvas_div.appendChild(canvas);\n",
+ " canvas_div.appendChild(rubberband_canvas);\n",
+ "\n",
+ " this.rubberband_context = rubberband_canvas.getContext('2d');\n",
+ " this.rubberband_context.strokeStyle = '#000000';\n",
+ "\n",
+ " this._resize_canvas = function (width, height, forward) {\n",
+ " if (forward) {\n",
+ " canvas_div.style.width = width + 'px';\n",
+ " canvas_div.style.height = height + 'px';\n",
+ " }\n",
+ " };\n",
+ "\n",
+ " // Disable right mouse context menu.\n",
+ " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n",
+ " event.preventDefault();\n",
+ " return false;\n",
+ " });\n",
+ "\n",
+ " function set_focus() {\n",
+ " canvas.focus();\n",
+ " canvas_div.focus();\n",
+ " }\n",
+ "\n",
+ " window.setTimeout(set_focus, 100);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_toolbar = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var toolbar = document.createElement('div');\n",
+ " toolbar.classList = 'mpl-toolbar';\n",
+ " this.root.appendChild(toolbar);\n",
+ "\n",
+ " function on_click_closure(name) {\n",
+ " return function (_event) {\n",
+ " return fig.toolbar_button_onclick(name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " function on_mouseover_closure(tooltip) {\n",
+ " return function (event) {\n",
+ " if (!event.currentTarget.disabled) {\n",
+ " return fig.toolbar_button_onmouseover(tooltip);\n",
+ " }\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " fig.buttons = {};\n",
+ " var buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'mpl-button-group';\n",
+ " for (var toolbar_ind in mpl.toolbar_items) {\n",
+ " var name = mpl.toolbar_items[toolbar_ind][0];\n",
+ " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+ " var image = mpl.toolbar_items[toolbar_ind][2];\n",
+ " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+ "\n",
+ " if (!name) {\n",
+ " /* Instead of a spacer, we start a new button group. */\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ " buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'mpl-button-group';\n",
+ " continue;\n",
+ " }\n",
+ "\n",
+ " var button = (fig.buttons[name] = document.createElement('button'));\n",
+ " button.classList = 'mpl-widget';\n",
+ " button.setAttribute('role', 'button');\n",
+ " button.setAttribute('aria-disabled', 'false');\n",
+ " button.addEventListener('click', on_click_closure(method_name));\n",
+ " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+ "\n",
+ " var icon_img = document.createElement('img');\n",
+ " icon_img.src = '_images/' + image + '.png';\n",
+ " icon_img.srcset = '_images/' + image + '_large.png 2x';\n",
+ " icon_img.alt = tooltip;\n",
+ " button.appendChild(icon_img);\n",
+ "\n",
+ " buttonGroup.appendChild(button);\n",
+ " }\n",
+ "\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ "\n",
+ " var fmt_picker = document.createElement('select');\n",
+ " fmt_picker.classList = 'mpl-widget';\n",
+ " toolbar.appendChild(fmt_picker);\n",
+ " this.format_dropdown = fmt_picker;\n",
+ "\n",
+ " for (var ind in mpl.extensions) {\n",
+ " var fmt = mpl.extensions[ind];\n",
+ " var option = document.createElement('option');\n",
+ " option.selected = fmt === mpl.default_extension;\n",
+ " option.innerHTML = fmt;\n",
+ " fmt_picker.appendChild(option);\n",
+ " }\n",
+ "\n",
+ " var status_bar = document.createElement('span');\n",
+ " status_bar.classList = 'mpl-message';\n",
+ " toolbar.appendChild(status_bar);\n",
+ " this.message = status_bar;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n",
+ " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
+ " // which will in turn request a refresh of the image.\n",
+ " this.send_message('resize', { width: x_pixels, height: y_pixels });\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.send_message = function (type, properties) {\n",
+ " properties['type'] = type;\n",
+ " properties['figure_id'] = this.id;\n",
+ " this.ws.send(JSON.stringify(properties));\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.send_draw_message = function () {\n",
+ " if (!this.waiting) {\n",
+ " this.waiting = true;\n",
+ " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+ " var format_dropdown = fig.format_dropdown;\n",
+ " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
+ " fig.ondownload(fig, format);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_resize = function (fig, msg) {\n",
+ " var size = msg['size'];\n",
+ " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n",
+ " fig._resize_canvas(size[0], size[1], msg['forward']);\n",
+ " fig.send_message('refresh', {});\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n",
+ " var x0 = msg['x0'] / fig.ratio;\n",
+ " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n",
+ " var x1 = msg['x1'] / fig.ratio;\n",
+ " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n",
+ " x0 = Math.floor(x0) + 0.5;\n",
+ " y0 = Math.floor(y0) + 0.5;\n",
+ " x1 = Math.floor(x1) + 0.5;\n",
+ " y1 = Math.floor(y1) + 0.5;\n",
+ " var min_x = Math.min(x0, x1);\n",
+ " var min_y = Math.min(y0, y1);\n",
+ " var width = Math.abs(x1 - x0);\n",
+ " var height = Math.abs(y1 - y0);\n",
+ "\n",
+ " fig.rubberband_context.clearRect(\n",
+ " 0,\n",
+ " 0,\n",
+ " fig.canvas.width / fig.ratio,\n",
+ " fig.canvas.height / fig.ratio\n",
+ " );\n",
+ "\n",
+ " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n",
+ " // Updates the figure title.\n",
+ " fig.header.textContent = msg['label'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n",
+ " fig.rubberband_canvas.style.cursor = msg['cursor'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_message = function (fig, msg) {\n",
+ " fig.message.textContent = msg['message'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n",
+ " // Request the server to send over a new figure.\n",
+ " fig.send_draw_message();\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n",
+ " fig.image_mode = msg['mode'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n",
+ " for (var key in msg) {\n",
+ " if (!(key in fig.buttons)) {\n",
+ " continue;\n",
+ " }\n",
+ " fig.buttons[key].disabled = !msg[key];\n",
+ " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n",
+ " if (msg['mode'] === 'PAN') {\n",
+ " fig.buttons['Pan'].classList.add('active');\n",
+ " fig.buttons['Zoom'].classList.remove('active');\n",
+ " } else if (msg['mode'] === 'ZOOM') {\n",
+ " fig.buttons['Pan'].classList.remove('active');\n",
+ " fig.buttons['Zoom'].classList.add('active');\n",
+ " } else {\n",
+ " fig.buttons['Pan'].classList.remove('active');\n",
+ " fig.buttons['Zoom'].classList.remove('active');\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.updated_canvas_event = function () {\n",
+ " // Called whenever the canvas gets updated.\n",
+ " this.send_message('ack', {});\n",
+ "};\n",
+ "\n",
+ "// A function to construct a web socket function for onmessage handling.\n",
+ "// Called in the figure constructor.\n",
+ "mpl.figure.prototype._make_on_message_function = function (fig) {\n",
+ " return function socket_on_message(evt) {\n",
+ " if (evt.data instanceof Blob) {\n",
+ " var img = evt.data;\n",
+ " if (img.type !== 'image/png') {\n",
+ " /* FIXME: We get \"Resource interpreted as Image but\n",
+ " * transferred with MIME type text/plain:\" errors on\n",
+ " * Chrome. But how to set the MIME type? It doesn't seem\n",
+ " * to be part of the websocket stream */\n",
+ " img.type = 'image/png';\n",
+ " }\n",
+ "\n",
+ " /* Free the memory for the previous frames */\n",
+ " if (fig.imageObj.src) {\n",
+ " (window.URL || window.webkitURL).revokeObjectURL(\n",
+ " fig.imageObj.src\n",
+ " );\n",
+ " }\n",
+ "\n",
+ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
+ " img\n",
+ " );\n",
+ " fig.updated_canvas_event();\n",
+ " fig.waiting = false;\n",
+ " return;\n",
+ " } else if (\n",
+ " typeof evt.data === 'string' &&\n",
+ " evt.data.slice(0, 21) === 'data:image/png;base64'\n",
+ " ) {\n",
+ " fig.imageObj.src = evt.data;\n",
+ " fig.updated_canvas_event();\n",
+ " fig.waiting = false;\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " var msg = JSON.parse(evt.data);\n",
+ " var msg_type = msg['type'];\n",
+ "\n",
+ " // Call the \"handle_{type}\" callback, which takes\n",
+ " // the figure and JSON message as its only arguments.\n",
+ " try {\n",
+ " var callback = fig['handle_' + msg_type];\n",
+ " } catch (e) {\n",
+ " console.log(\n",
+ " \"No handler for the '\" + msg_type + \"' message type: \",\n",
+ " msg\n",
+ " );\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " if (callback) {\n",
+ " try {\n",
+ " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
+ " callback(fig, msg);\n",
+ " } catch (e) {\n",
+ " console.log(\n",
+ " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n",
+ " e,\n",
+ " e.stack,\n",
+ " msg\n",
+ " );\n",
+ " }\n",
+ " }\n",
+ " };\n",
+ "};\n",
+ "\n",
+ "// from https://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
+ "mpl.findpos = function (e) {\n",
+ " //this section is from http://www.quirksmode.org/js/events_properties.html\n",
+ " var targ;\n",
+ " if (!e) {\n",
+ " e = window.event;\n",
+ " }\n",
+ " if (e.target) {\n",
+ " targ = e.target;\n",
+ " } else if (e.srcElement) {\n",
+ " targ = e.srcElement;\n",
+ " }\n",
+ " if (targ.nodeType === 3) {\n",
+ " // defeat Safari bug\n",
+ " targ = targ.parentNode;\n",
+ " }\n",
+ "\n",
+ " // pageX,Y are the mouse positions relative to the document\n",
+ " var boundingRect = targ.getBoundingClientRect();\n",
+ " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n",
+ " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n",
+ "\n",
+ " return { x: x, y: y };\n",
+ "};\n",
+ "\n",
+ "/*\n",
+ " * return a copy of an object with only non-object keys\n",
+ " * we need this to avoid circular references\n",
+ " * https://stackoverflow.com/a/24161582/3208463\n",
+ " */\n",
+ "function simpleKeys(original) {\n",
+ " return Object.keys(original).reduce(function (obj, key) {\n",
+ " if (typeof original[key] !== 'object') {\n",
+ " obj[key] = original[key];\n",
+ " }\n",
+ " return obj;\n",
+ " }, {});\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.mouse_event = function (event, name) {\n",
+ " var canvas_pos = mpl.findpos(event);\n",
+ "\n",
+ " if (name === 'button_press') {\n",
+ " this.canvas.focus();\n",
+ " this.canvas_div.focus();\n",
+ " }\n",
+ "\n",
+ " var x = canvas_pos.x * this.ratio;\n",
+ " var y = canvas_pos.y * this.ratio;\n",
+ "\n",
+ " this.send_message(name, {\n",
+ " x: x,\n",
+ " y: y,\n",
+ " button: event.button,\n",
+ " step: event.step,\n",
+ " guiEvent: simpleKeys(event),\n",
+ " });\n",
+ "\n",
+ " /* This prevents the web browser from automatically changing to\n",
+ " * the text insertion cursor when the button is pressed. We want\n",
+ " * to control all of the cursor setting manually through the\n",
+ " * 'cursor' event from matplotlib */\n",
+ " event.preventDefault();\n",
+ " return false;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n",
+ " // Handle any extra behaviour associated with a key event\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.key_event = function (event, name) {\n",
+ " // Prevent repeat events\n",
+ " if (name === 'key_press') {\n",
+ " if (event.key === this._key) {\n",
+ " return;\n",
+ " } else {\n",
+ " this._key = event.key;\n",
+ " }\n",
+ " }\n",
+ " if (name === 'key_release') {\n",
+ " this._key = null;\n",
+ " }\n",
+ "\n",
+ " var value = '';\n",
+ " if (event.ctrlKey && event.key !== 'Control') {\n",
+ " value += 'ctrl+';\n",
+ " }\n",
+ " else if (event.altKey && event.key !== 'Alt') {\n",
+ " value += 'alt+';\n",
+ " }\n",
+ " else if (event.shiftKey && event.key !== 'Shift') {\n",
+ " value += 'shift+';\n",
+ " }\n",
+ "\n",
+ " value += 'k' + event.key;\n",
+ "\n",
+ " this._key_event_extra(event, name);\n",
+ "\n",
+ " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n",
+ " return false;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n",
+ " if (name === 'download') {\n",
+ " this.handle_save(this, null);\n",
+ " } else {\n",
+ " this.send_message('toolbar_button', { name: name });\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n",
+ " this.message.textContent = tooltip;\n",
+ "};\n",
+ "\n",
+ "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n",
+ "// prettier-ignore\n",
+ "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n",
+ "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
+ "\n",
+ "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
+ "\n",
+ "mpl.default_extension = \"png\";/* global mpl */\n",
+ "\n",
+ "var comm_websocket_adapter = function (comm) {\n",
+ " // Create a \"websocket\"-like object which calls the given IPython comm\n",
+ " // object with the appropriate methods. Currently this is a non binary\n",
+ " // socket, so there is still some room for performance tuning.\n",
+ " var ws = {};\n",
+ "\n",
+ " ws.binaryType = comm.kernel.ws.binaryType;\n",
+ " ws.readyState = comm.kernel.ws.readyState;\n",
+ " function updateReadyState(_event) {\n",
+ " if (comm.kernel.ws) {\n",
+ " ws.readyState = comm.kernel.ws.readyState;\n",
+ " } else {\n",
+ " ws.readyState = 3; // Closed state.\n",
+ " }\n",
+ " }\n",
+ " comm.kernel.ws.addEventListener('open', updateReadyState);\n",
+ " comm.kernel.ws.addEventListener('close', updateReadyState);\n",
+ " comm.kernel.ws.addEventListener('error', updateReadyState);\n",
+ "\n",
+ " ws.close = function () {\n",
+ " comm.close();\n",
+ " };\n",
+ " ws.send = function (m) {\n",
+ " //console.log('sending', m);\n",
+ " comm.send(m);\n",
+ " };\n",
+ " // Register the callback with on_msg.\n",
+ " comm.on_msg(function (msg) {\n",
+ " //console.log('receiving', msg['content']['data'], msg);\n",
+ " var data = msg['content']['data'];\n",
+ " if (data['blob'] !== undefined) {\n",
+ " data = {\n",
+ " data: new Blob(msg['buffers'], { type: data['blob'] }),\n",
+ " };\n",
+ " }\n",
+ " // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
+ " ws.onmessage(data);\n",
+ " });\n",
+ " return ws;\n",
+ "};\n",
+ "\n",
+ "mpl.mpl_figure_comm = function (comm, msg) {\n",
+ " // This is the function which gets called when the mpl process\n",
+ " // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
+ "\n",
+ " var id = msg.content.data.id;\n",
+ " // Get hold of the div created by the display call when the Comm\n",
+ " // socket was opened in Python.\n",
+ " var element = document.getElementById(id);\n",
+ " var ws_proxy = comm_websocket_adapter(comm);\n",
+ "\n",
+ " function ondownload(figure, _format) {\n",
+ " window.open(figure.canvas.toDataURL());\n",
+ " }\n",
+ "\n",
+ " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n",
+ "\n",
+ " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
+ " // web socket which is closed, not our websocket->open comm proxy.\n",
+ " ws_proxy.onopen();\n",
+ "\n",
+ " fig.parent_element = element;\n",
+ " fig.cell_info = mpl.find_output_cell(\"\");\n",
+ " if (!fig.cell_info) {\n",
+ " console.error('Failed to find cell for figure', id, fig);\n",
+ " return;\n",
+ " }\n",
+ " fig.cell_info[0].output_area.element.on(\n",
+ " 'cleared',\n",
+ " { fig: fig },\n",
+ " fig._remove_fig_handler\n",
+ " );\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_close = function (fig, msg) {\n",
+ " var width = fig.canvas.width / fig.ratio;\n",
+ " fig.cell_info[0].output_area.element.off(\n",
+ " 'cleared',\n",
+ " fig._remove_fig_handler\n",
+ " );\n",
+ " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n",
+ "\n",
+ " // Update the output cell to use the data from the current canvas.\n",
+ " fig.push_to_output();\n",
+ " var dataURL = fig.canvas.toDataURL();\n",
+ " // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
+ " // the notebook keyboard shortcuts fail.\n",
+ " IPython.keyboard_manager.enable();\n",
+ " fig.parent_element.innerHTML =\n",
+ " '
';\n",
+ " fig.close_ws(fig, msg);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.close_ws = function (fig, msg) {\n",
+ " fig.send_message('closing', msg);\n",
+ " // fig.ws.close()\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n",
+ " // Turn the data on the canvas into data in the output cell.\n",
+ " var width = this.canvas.width / this.ratio;\n",
+ " var dataURL = this.canvas.toDataURL();\n",
+ " this.cell_info[1]['text/html'] =\n",
+ " '
';\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.updated_canvas_event = function () {\n",
+ " // Tell IPython that the notebook contents must change.\n",
+ " IPython.notebook.set_dirty(true);\n",
+ " this.send_message('ack', {});\n",
+ " var fig = this;\n",
+ " // Wait a second, then push the new image to the DOM so\n",
+ " // that it is saved nicely (might be nice to debounce this).\n",
+ " setTimeout(function () {\n",
+ " fig.push_to_output();\n",
+ " }, 1000);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_toolbar = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var toolbar = document.createElement('div');\n",
+ " toolbar.classList = 'btn-toolbar';\n",
+ " this.root.appendChild(toolbar);\n",
+ "\n",
+ " function on_click_closure(name) {\n",
+ " return function (_event) {\n",
+ " return fig.toolbar_button_onclick(name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " function on_mouseover_closure(tooltip) {\n",
+ " return function (event) {\n",
+ " if (!event.currentTarget.disabled) {\n",
+ " return fig.toolbar_button_onmouseover(tooltip);\n",
+ " }\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " fig.buttons = {};\n",
+ " var buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'btn-group';\n",
+ " var button;\n",
+ " for (var toolbar_ind in mpl.toolbar_items) {\n",
+ " var name = mpl.toolbar_items[toolbar_ind][0];\n",
+ " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+ " var image = mpl.toolbar_items[toolbar_ind][2];\n",
+ " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+ "\n",
+ " if (!name) {\n",
+ " /* Instead of a spacer, we start a new button group. */\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ " buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'btn-group';\n",
+ " continue;\n",
+ " }\n",
+ "\n",
+ " button = fig.buttons[name] = document.createElement('button');\n",
+ " button.classList = 'btn btn-default';\n",
+ " button.href = '#';\n",
+ " button.title = name;\n",
+ " button.innerHTML = '';\n",
+ " button.addEventListener('click', on_click_closure(method_name));\n",
+ " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+ " buttonGroup.appendChild(button);\n",
+ " }\n",
+ "\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ "\n",
+ " // Add the status bar.\n",
+ " var status_bar = document.createElement('span');\n",
+ " status_bar.classList = 'mpl-message pull-right';\n",
+ " toolbar.appendChild(status_bar);\n",
+ " this.message = status_bar;\n",
+ "\n",
+ " // Add the close button to the window.\n",
+ " var buttongrp = document.createElement('div');\n",
+ " buttongrp.classList = 'btn-group inline pull-right';\n",
+ " button = document.createElement('button');\n",
+ " button.classList = 'btn btn-mini btn-primary';\n",
+ " button.href = '#';\n",
+ " button.title = 'Stop Interaction';\n",
+ " button.innerHTML = '';\n",
+ " button.addEventListener('click', function (_evt) {\n",
+ " fig.handle_close(fig, {});\n",
+ " });\n",
+ " button.addEventListener(\n",
+ " 'mouseover',\n",
+ " on_mouseover_closure('Stop Interaction')\n",
+ " );\n",
+ " buttongrp.appendChild(button);\n",
+ " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n",
+ " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._remove_fig_handler = function (event) {\n",
+ " var fig = event.data.fig;\n",
+ " if (event.target !== this) {\n",
+ " // Ignore bubbled events from children.\n",
+ " return;\n",
+ " }\n",
+ " fig.close_ws(fig, {});\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._root_extra_style = function (el) {\n",
+ " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._canvas_extra_style = function (el) {\n",
+ " // this is important to make the div 'focusable\n",
+ " el.setAttribute('tabindex', 0);\n",
+ " // reach out to IPython and tell the keyboard manager to turn it's self\n",
+ " // off when our div gets focus\n",
+ "\n",
+ " // location in version 3\n",
+ " if (IPython.notebook.keyboard_manager) {\n",
+ " IPython.notebook.keyboard_manager.register_events(el);\n",
+ " } else {\n",
+ " // location in version 2\n",
+ " IPython.keyboard_manager.register_events(el);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._key_event_extra = function (event, _name) {\n",
+ " // Check for shift+enter\n",
+ " if (event.shiftKey && event.which === 13) {\n",
+ " this.canvas_div.blur();\n",
+ " // select the cell after this one\n",
+ " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n",
+ " IPython.notebook.select(index + 1);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+ " fig.ondownload(fig, null);\n",
+ "};\n",
+ "\n",
+ "mpl.find_output_cell = function (html_output) {\n",
+ " // Return the cell and output element which can be found *uniquely* in the notebook.\n",
+ " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
+ " // IPython event is triggered only after the cells have been serialised, which for\n",
+ " // our purposes (turning an active figure into a static one), is too late.\n",
+ " var cells = IPython.notebook.get_cells();\n",
+ " var ncells = cells.length;\n",
+ " for (var i = 0; i < ncells; i++) {\n",
+ " var cell = cells[i];\n",
+ " if (cell.cell_type === 'code') {\n",
+ " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n",
+ " var data = cell.output_area.outputs[j];\n",
+ " if (data.data) {\n",
+ " // IPython >= 3 moved mimebundle to data attribute of output\n",
+ " data = data.data;\n",
+ " }\n",
+ " if (data['text/html'] === html_output) {\n",
+ " return [cell, data, j];\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "// Register the function which deals with the matplotlib target/channel.\n",
+ "// The kernel may be null if the page has been refreshed.\n",
+ "if (IPython.notebook.kernel !== null) {\n",
+ " IPython.notebook.kernel.comm_manager.register_target(\n",
+ " 'matplotlib',\n",
+ " mpl.mpl_figure_comm\n",
+ " );\n",
+ "}\n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "
"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "%matplotlib notebook\n",
+ "\n",
+ "# Read in results\n",
+ "## Params - Either specify a file prefix or a whole folder\n",
+ "file_prefix = None # Path(\"./results/2022-05-17T21:18:41_throughput_write_\")\n",
+ "folder = Path(\"./results/param_study_fio_throughput\")\n",
+ "\n",
+ "## Plot code\n",
+ "path = file_prefix if file_prefix else folder\n",
+ "\n",
+ "\n",
+ "post_i = \"_isilon.json\"\n",
+ "post_n = \"_netapp.json\"\n",
+ "metric = METRIC[\"throughput_write\"]\n",
+ "\n",
+ "df_1 = pd.concat({\n",
+ " \"nfs_isilon\": load_data(path, suffix=\"blocksize\"+post_i, metric=metric),\n",
+ " \"nfs_netapp\": load_data(path, suffix=\"blocksize\"+post_n, metric=metric),\n",
+ "})\n",
+ "df_2 = pd.concat({\n",
+ " \"nfs_isilon\": load_data(path, suffix=\"iodepth\"+post_i, metric=metric),\n",
+ " \"nfs_netapp\": load_data(path, suffix=\"iodepth\"+post_n, metric=metric),\n",
+ "})\n",
+ "df_3 = pd.concat({\n",
+ " \"nfs_isilon\": load_data(path, suffix=\"runtime\"+post_i, metric=metric),\n",
+ " \"nfs_netapp\": load_data(path, suffix=\"runtime\"+post_n, metric=metric),\n",
+ "})\n",
+ "df_4 = pd.concat({\n",
+ " \"nfs_isilon\": load_data(path, suffix=\"ramptime\"+post_i, metric=metric),\n",
+ " \"nfs_netapp\": load_data(path, suffix=\"ramptime\"+post_n, metric=metric),\n",
+ "})\n",
+ "df_5 = pd.concat({\n",
+ " \"nfs_isilon\": load_data(path, suffix=\"numjobs\"+post_i, metric=metric),\n",
+ " \"nfs_netapp\": load_data(path, suffix=\"numjobs\"+post_n, metric=metric),\n",
+ "})\n",
+ "df_6 = pd.concat({\n",
+ " \"nfs_isilon\": load_data(path, suffix=\"filesize\"+post_i, metric=metric),\n",
+ " \"nfs_netapp\": load_data(path, suffix=\"filesize\"+post_n, metric=metric),\n",
+ "})\n",
+ "\n",
+ "fig, axis = plt.subplots(3, 2, sharey=True)\n",
+ "fig.suptitle('Parameter Study: Fio Write Bandwidth')\n",
+ "\n",
+ "color = [\"tab:red\", \"tab:cyan\"]\n",
+ " \n",
+ "df_1[\"mean\"].unstack(level=0).plot(\n",
+ " ax=axis[0][0],\n",
+ " kind='bar', rot=0,\n",
+ " xlabel='Blocksize in KiB',\n",
+ " title='Variable Blocksize',\n",
+ " yerr=df_1[\"std\"].unstack(level=0),\n",
+ " color=color,\n",
+ ")\n",
+ "df_2[\"mean\"].unstack(level=0).plot(\n",
+ " ax=axis[0][1],\n",
+ " kind='bar', rot=0,\n",
+ " xlabel='IODepth',\n",
+ " title='Variable IODepth',\n",
+ " yerr=df_2[\"std\"].unstack(level=0),\n",
+ " color=color,\n",
+ ")\n",
+ "\n",
+ "df_3[\"mean\"].unstack(level=0).plot(\n",
+ " ax=axis[1][0],\n",
+ " kind='bar', rot=0,\n",
+ " xlabel='Time in s',\n",
+ " ylabel='Bandwidth (MiB/s)',\n",
+ " title='Variable Runtime',\n",
+ " yerr=df_3[\"std\"].unstack(level=0),\n",
+ " color=color,\n",
+ ")\n",
+ "\n",
+ "df_4[\"mean\"].unstack(level=0).plot(\n",
+ " ax=axis[1][1],\n",
+ " kind='bar', rot=0,\n",
+ " xlabel='Time in s',\n",
+ " title='Variable Ramptime',\n",
+ " yerr=df_4[\"std\"].unstack(level=0),\n",
+ " color=color,\n",
+ ")\n",
+ "df_5[\"mean\"].unstack(level=0).plot(\n",
+ " ax=axis[2][0],\n",
+ " kind='bar', rot=0,\n",
+ " xlabel='Jobs',\n",
+ " title='Variable Number of Jobs',\n",
+ " yerr=df_5[\"std\"].unstack(level=0),\n",
+ " color=color,\n",
+ ")\n",
+ "df_6[\"mean\"].unstack(level=0).plot(\n",
+ " ax=axis[2][1],\n",
+ " kind='bar', rot=0,\n",
+ " xlabel='Filesize in GiB',\n",
+ " title='Variable Filesize',\n",
+ " yerr=df_6[\"std\"].unstack(level=0),\n",
+ " color=color,\n",
+ ")\n",
+ "for i,j in [(0,0),(0,1),(1,0),(1,1),(2,0)]:\n",
+ " axis[i][j].get_legend().remove()\n",
+ " \n",
+ "plt.tight_layout()\n",
+ "plt.savefig(\"paramstudy_fio_throughput.png\",bbox_inches='tight')\n",
+ "plt.show()\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "5a161036",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "application/javascript": [
+ "/* Put everything inside the global mpl namespace */\n",
+ "/* global mpl */\n",
+ "window.mpl = {};\n",
+ "\n",
+ "mpl.get_websocket_type = function () {\n",
+ " if (typeof WebSocket !== 'undefined') {\n",
+ " return WebSocket;\n",
+ " } else if (typeof MozWebSocket !== 'undefined') {\n",
+ " return MozWebSocket;\n",
+ " } else {\n",
+ " alert(\n",
+ " 'Your browser does not have WebSocket support. ' +\n",
+ " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
+ " 'Firefox 4 and 5 are also supported but you ' +\n",
+ " 'have to enable WebSockets in about:config.'\n",
+ " );\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n",
+ " this.id = figure_id;\n",
+ "\n",
+ " this.ws = websocket;\n",
+ "\n",
+ " this.supports_binary = this.ws.binaryType !== undefined;\n",
+ "\n",
+ " if (!this.supports_binary) {\n",
+ " var warnings = document.getElementById('mpl-warnings');\n",
+ " if (warnings) {\n",
+ " warnings.style.display = 'block';\n",
+ " warnings.textContent =\n",
+ " 'This browser does not support binary websocket messages. ' +\n",
+ " 'Performance may be slow.';\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " this.imageObj = new Image();\n",
+ "\n",
+ " this.context = undefined;\n",
+ " this.message = undefined;\n",
+ " this.canvas = undefined;\n",
+ " this.rubberband_canvas = undefined;\n",
+ " this.rubberband_context = undefined;\n",
+ " this.format_dropdown = undefined;\n",
+ "\n",
+ " this.image_mode = 'full';\n",
+ "\n",
+ " this.root = document.createElement('div');\n",
+ " this.root.setAttribute('style', 'display: inline-block');\n",
+ " this._root_extra_style(this.root);\n",
+ "\n",
+ " parent_element.appendChild(this.root);\n",
+ "\n",
+ " this._init_header(this);\n",
+ " this._init_canvas(this);\n",
+ " this._init_toolbar(this);\n",
+ "\n",
+ " var fig = this;\n",
+ "\n",
+ " this.waiting = false;\n",
+ "\n",
+ " this.ws.onopen = function () {\n",
+ " fig.send_message('supports_binary', { value: fig.supports_binary });\n",
+ " fig.send_message('send_image_mode', {});\n",
+ " if (fig.ratio !== 1) {\n",
+ " fig.send_message('set_device_pixel_ratio', {\n",
+ " device_pixel_ratio: fig.ratio,\n",
+ " });\n",
+ " }\n",
+ " fig.send_message('refresh', {});\n",
+ " };\n",
+ "\n",
+ " this.imageObj.onload = function () {\n",
+ " if (fig.image_mode === 'full') {\n",
+ " // Full images could contain transparency (where diff images\n",
+ " // almost always do), so we need to clear the canvas so that\n",
+ " // there is no ghosting.\n",
+ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
+ " }\n",
+ " fig.context.drawImage(fig.imageObj, 0, 0);\n",
+ " };\n",
+ "\n",
+ " this.imageObj.onunload = function () {\n",
+ " fig.ws.close();\n",
+ " };\n",
+ "\n",
+ " this.ws.onmessage = this._make_on_message_function(this);\n",
+ "\n",
+ " this.ondownload = ondownload;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_header = function () {\n",
+ " var titlebar = document.createElement('div');\n",
+ " titlebar.classList =\n",
+ " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n",
+ " var titletext = document.createElement('div');\n",
+ " titletext.classList = 'ui-dialog-title';\n",
+ " titletext.setAttribute(\n",
+ " 'style',\n",
+ " 'width: 100%; text-align: center; padding: 3px;'\n",
+ " );\n",
+ " titlebar.appendChild(titletext);\n",
+ " this.root.appendChild(titlebar);\n",
+ " this.header = titletext;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n",
+ "\n",
+ "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n",
+ "\n",
+ "mpl.figure.prototype._init_canvas = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var canvas_div = (this.canvas_div = document.createElement('div'));\n",
+ " canvas_div.setAttribute(\n",
+ " 'style',\n",
+ " 'border: 1px solid #ddd;' +\n",
+ " 'box-sizing: content-box;' +\n",
+ " 'clear: both;' +\n",
+ " 'min-height: 1px;' +\n",
+ " 'min-width: 1px;' +\n",
+ " 'outline: 0;' +\n",
+ " 'overflow: hidden;' +\n",
+ " 'position: relative;' +\n",
+ " 'resize: both;'\n",
+ " );\n",
+ "\n",
+ " function on_keyboard_event_closure(name) {\n",
+ " return function (event) {\n",
+ " return fig.key_event(event, name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " canvas_div.addEventListener(\n",
+ " 'keydown',\n",
+ " on_keyboard_event_closure('key_press')\n",
+ " );\n",
+ " canvas_div.addEventListener(\n",
+ " 'keyup',\n",
+ " on_keyboard_event_closure('key_release')\n",
+ " );\n",
+ "\n",
+ " this._canvas_extra_style(canvas_div);\n",
+ " this.root.appendChild(canvas_div);\n",
+ "\n",
+ " var canvas = (this.canvas = document.createElement('canvas'));\n",
+ " canvas.classList.add('mpl-canvas');\n",
+ " canvas.setAttribute('style', 'box-sizing: content-box;');\n",
+ "\n",
+ " this.context = canvas.getContext('2d');\n",
+ "\n",
+ " var backingStore =\n",
+ " this.context.backingStorePixelRatio ||\n",
+ " this.context.webkitBackingStorePixelRatio ||\n",
+ " this.context.mozBackingStorePixelRatio ||\n",
+ " this.context.msBackingStorePixelRatio ||\n",
+ " this.context.oBackingStorePixelRatio ||\n",
+ " this.context.backingStorePixelRatio ||\n",
+ " 1;\n",
+ "\n",
+ " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
+ "\n",
+ " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n",
+ " 'canvas'\n",
+ " ));\n",
+ " rubberband_canvas.setAttribute(\n",
+ " 'style',\n",
+ " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n",
+ " );\n",
+ "\n",
+ " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n",
+ " if (this.ResizeObserver === undefined) {\n",
+ " if (window.ResizeObserver !== undefined) {\n",
+ " this.ResizeObserver = window.ResizeObserver;\n",
+ " } else {\n",
+ " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n",
+ " this.ResizeObserver = obs.ResizeObserver;\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n",
+ " var nentries = entries.length;\n",
+ " for (var i = 0; i < nentries; i++) {\n",
+ " var entry = entries[i];\n",
+ " var width, height;\n",
+ " if (entry.contentBoxSize) {\n",
+ " if (entry.contentBoxSize instanceof Array) {\n",
+ " // Chrome 84 implements new version of spec.\n",
+ " width = entry.contentBoxSize[0].inlineSize;\n",
+ " height = entry.contentBoxSize[0].blockSize;\n",
+ " } else {\n",
+ " // Firefox implements old version of spec.\n",
+ " width = entry.contentBoxSize.inlineSize;\n",
+ " height = entry.contentBoxSize.blockSize;\n",
+ " }\n",
+ " } else {\n",
+ " // Chrome <84 implements even older version of spec.\n",
+ " width = entry.contentRect.width;\n",
+ " height = entry.contentRect.height;\n",
+ " }\n",
+ "\n",
+ " // Keep the size of the canvas and rubber band canvas in sync with\n",
+ " // the canvas container.\n",
+ " if (entry.devicePixelContentBoxSize) {\n",
+ " // Chrome 84 implements new version of spec.\n",
+ " canvas.setAttribute(\n",
+ " 'width',\n",
+ " entry.devicePixelContentBoxSize[0].inlineSize\n",
+ " );\n",
+ " canvas.setAttribute(\n",
+ " 'height',\n",
+ " entry.devicePixelContentBoxSize[0].blockSize\n",
+ " );\n",
+ " } else {\n",
+ " canvas.setAttribute('width', width * fig.ratio);\n",
+ " canvas.setAttribute('height', height * fig.ratio);\n",
+ " }\n",
+ " canvas.setAttribute(\n",
+ " 'style',\n",
+ " 'width: ' + width + 'px; height: ' + height + 'px;'\n",
+ " );\n",
+ "\n",
+ " rubberband_canvas.setAttribute('width', width);\n",
+ " rubberband_canvas.setAttribute('height', height);\n",
+ "\n",
+ " // And update the size in Python. We ignore the initial 0/0 size\n",
+ " // that occurs as the element is placed into the DOM, which should\n",
+ " // otherwise not happen due to the minimum size styling.\n",
+ " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n",
+ " fig.request_resize(width, height);\n",
+ " }\n",
+ " }\n",
+ " });\n",
+ " this.resizeObserverInstance.observe(canvas_div);\n",
+ "\n",
+ " function on_mouse_event_closure(name) {\n",
+ " return function (event) {\n",
+ " return fig.mouse_event(event, name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mousedown',\n",
+ " on_mouse_event_closure('button_press')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseup',\n",
+ " on_mouse_event_closure('button_release')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'dblclick',\n",
+ " on_mouse_event_closure('dblclick')\n",
+ " );\n",
+ " // Throttle sequential mouse events to 1 every 20ms.\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mousemove',\n",
+ " on_mouse_event_closure('motion_notify')\n",
+ " );\n",
+ "\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseenter',\n",
+ " on_mouse_event_closure('figure_enter')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseleave',\n",
+ " on_mouse_event_closure('figure_leave')\n",
+ " );\n",
+ "\n",
+ " canvas_div.addEventListener('wheel', function (event) {\n",
+ " if (event.deltaY < 0) {\n",
+ " event.step = 1;\n",
+ " } else {\n",
+ " event.step = -1;\n",
+ " }\n",
+ " on_mouse_event_closure('scroll')(event);\n",
+ " });\n",
+ "\n",
+ " canvas_div.appendChild(canvas);\n",
+ " canvas_div.appendChild(rubberband_canvas);\n",
+ "\n",
+ " this.rubberband_context = rubberband_canvas.getContext('2d');\n",
+ " this.rubberband_context.strokeStyle = '#000000';\n",
+ "\n",
+ " this._resize_canvas = function (width, height, forward) {\n",
+ " if (forward) {\n",
+ " canvas_div.style.width = width + 'px';\n",
+ " canvas_div.style.height = height + 'px';\n",
+ " }\n",
+ " };\n",
+ "\n",
+ " // Disable right mouse context menu.\n",
+ " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n",
+ " event.preventDefault();\n",
+ " return false;\n",
+ " });\n",
+ "\n",
+ " function set_focus() {\n",
+ " canvas.focus();\n",
+ " canvas_div.focus();\n",
+ " }\n",
+ "\n",
+ " window.setTimeout(set_focus, 100);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_toolbar = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var toolbar = document.createElement('div');\n",
+ " toolbar.classList = 'mpl-toolbar';\n",
+ " this.root.appendChild(toolbar);\n",
+ "\n",
+ " function on_click_closure(name) {\n",
+ " return function (_event) {\n",
+ " return fig.toolbar_button_onclick(name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " function on_mouseover_closure(tooltip) {\n",
+ " return function (event) {\n",
+ " if (!event.currentTarget.disabled) {\n",
+ " return fig.toolbar_button_onmouseover(tooltip);\n",
+ " }\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " fig.buttons = {};\n",
+ " var buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'mpl-button-group';\n",
+ " for (var toolbar_ind in mpl.toolbar_items) {\n",
+ " var name = mpl.toolbar_items[toolbar_ind][0];\n",
+ " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+ " var image = mpl.toolbar_items[toolbar_ind][2];\n",
+ " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+ "\n",
+ " if (!name) {\n",
+ " /* Instead of a spacer, we start a new button group. */\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ " buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'mpl-button-group';\n",
+ " continue;\n",
+ " }\n",
+ "\n",
+ " var button = (fig.buttons[name] = document.createElement('button'));\n",
+ " button.classList = 'mpl-widget';\n",
+ " button.setAttribute('role', 'button');\n",
+ " button.setAttribute('aria-disabled', 'false');\n",
+ " button.addEventListener('click', on_click_closure(method_name));\n",
+ " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+ "\n",
+ " var icon_img = document.createElement('img');\n",
+ " icon_img.src = '_images/' + image + '.png';\n",
+ " icon_img.srcset = '_images/' + image + '_large.png 2x';\n",
+ " icon_img.alt = tooltip;\n",
+ " button.appendChild(icon_img);\n",
+ "\n",
+ " buttonGroup.appendChild(button);\n",
+ " }\n",
+ "\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ "\n",
+ " var fmt_picker = document.createElement('select');\n",
+ " fmt_picker.classList = 'mpl-widget';\n",
+ " toolbar.appendChild(fmt_picker);\n",
+ " this.format_dropdown = fmt_picker;\n",
+ "\n",
+ " for (var ind in mpl.extensions) {\n",
+ " var fmt = mpl.extensions[ind];\n",
+ " var option = document.createElement('option');\n",
+ " option.selected = fmt === mpl.default_extension;\n",
+ " option.innerHTML = fmt;\n",
+ " fmt_picker.appendChild(option);\n",
+ " }\n",
+ "\n",
+ " var status_bar = document.createElement('span');\n",
+ " status_bar.classList = 'mpl-message';\n",
+ " toolbar.appendChild(status_bar);\n",
+ " this.message = status_bar;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n",
+ " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
+ " // which will in turn request a refresh of the image.\n",
+ " this.send_message('resize', { width: x_pixels, height: y_pixels });\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.send_message = function (type, properties) {\n",
+ " properties['type'] = type;\n",
+ " properties['figure_id'] = this.id;\n",
+ " this.ws.send(JSON.stringify(properties));\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.send_draw_message = function () {\n",
+ " if (!this.waiting) {\n",
+ " this.waiting = true;\n",
+ " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+ " var format_dropdown = fig.format_dropdown;\n",
+ " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
+ " fig.ondownload(fig, format);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_resize = function (fig, msg) {\n",
+ " var size = msg['size'];\n",
+ " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n",
+ " fig._resize_canvas(size[0], size[1], msg['forward']);\n",
+ " fig.send_message('refresh', {});\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n",
+ " var x0 = msg['x0'] / fig.ratio;\n",
+ " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n",
+ " var x1 = msg['x1'] / fig.ratio;\n",
+ " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n",
+ " x0 = Math.floor(x0) + 0.5;\n",
+ " y0 = Math.floor(y0) + 0.5;\n",
+ " x1 = Math.floor(x1) + 0.5;\n",
+ " y1 = Math.floor(y1) + 0.5;\n",
+ " var min_x = Math.min(x0, x1);\n",
+ " var min_y = Math.min(y0, y1);\n",
+ " var width = Math.abs(x1 - x0);\n",
+ " var height = Math.abs(y1 - y0);\n",
+ "\n",
+ " fig.rubberband_context.clearRect(\n",
+ " 0,\n",
+ " 0,\n",
+ " fig.canvas.width / fig.ratio,\n",
+ " fig.canvas.height / fig.ratio\n",
+ " );\n",
+ "\n",
+ " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n",
+ " // Updates the figure title.\n",
+ " fig.header.textContent = msg['label'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n",
+ " fig.rubberband_canvas.style.cursor = msg['cursor'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_message = function (fig, msg) {\n",
+ " fig.message.textContent = msg['message'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n",
+ " // Request the server to send over a new figure.\n",
+ " fig.send_draw_message();\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n",
+ " fig.image_mode = msg['mode'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n",
+ " for (var key in msg) {\n",
+ " if (!(key in fig.buttons)) {\n",
+ " continue;\n",
+ " }\n",
+ " fig.buttons[key].disabled = !msg[key];\n",
+ " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n",
+ " if (msg['mode'] === 'PAN') {\n",
+ " fig.buttons['Pan'].classList.add('active');\n",
+ " fig.buttons['Zoom'].classList.remove('active');\n",
+ " } else if (msg['mode'] === 'ZOOM') {\n",
+ " fig.buttons['Pan'].classList.remove('active');\n",
+ " fig.buttons['Zoom'].classList.add('active');\n",
+ " } else {\n",
+ " fig.buttons['Pan'].classList.remove('active');\n",
+ " fig.buttons['Zoom'].classList.remove('active');\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.updated_canvas_event = function () {\n",
+ " // Called whenever the canvas gets updated.\n",
+ " this.send_message('ack', {});\n",
+ "};\n",
+ "\n",
+ "// A function to construct a web socket function for onmessage handling.\n",
+ "// Called in the figure constructor.\n",
+ "mpl.figure.prototype._make_on_message_function = function (fig) {\n",
+ " return function socket_on_message(evt) {\n",
+ " if (evt.data instanceof Blob) {\n",
+ " var img = evt.data;\n",
+ " if (img.type !== 'image/png') {\n",
+ " /* FIXME: We get \"Resource interpreted as Image but\n",
+ " * transferred with MIME type text/plain:\" errors on\n",
+ " * Chrome. But how to set the MIME type? It doesn't seem\n",
+ " * to be part of the websocket stream */\n",
+ " img.type = 'image/png';\n",
+ " }\n",
+ "\n",
+ " /* Free the memory for the previous frames */\n",
+ " if (fig.imageObj.src) {\n",
+ " (window.URL || window.webkitURL).revokeObjectURL(\n",
+ " fig.imageObj.src\n",
+ " );\n",
+ " }\n",
+ "\n",
+ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
+ " img\n",
+ " );\n",
+ " fig.updated_canvas_event();\n",
+ " fig.waiting = false;\n",
+ " return;\n",
+ " } else if (\n",
+ " typeof evt.data === 'string' &&\n",
+ " evt.data.slice(0, 21) === 'data:image/png;base64'\n",
+ " ) {\n",
+ " fig.imageObj.src = evt.data;\n",
+ " fig.updated_canvas_event();\n",
+ " fig.waiting = false;\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " var msg = JSON.parse(evt.data);\n",
+ " var msg_type = msg['type'];\n",
+ "\n",
+ " // Call the \"handle_{type}\" callback, which takes\n",
+ " // the figure and JSON message as its only arguments.\n",
+ " try {\n",
+ " var callback = fig['handle_' + msg_type];\n",
+ " } catch (e) {\n",
+ " console.log(\n",
+ " \"No handler for the '\" + msg_type + \"' message type: \",\n",
+ " msg\n",
+ " );\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " if (callback) {\n",
+ " try {\n",
+ " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
+ " callback(fig, msg);\n",
+ " } catch (e) {\n",
+ " console.log(\n",
+ " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n",
+ " e,\n",
+ " e.stack,\n",
+ " msg\n",
+ " );\n",
+ " }\n",
+ " }\n",
+ " };\n",
+ "};\n",
+ "\n",
+ "// from https://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
+ "mpl.findpos = function (e) {\n",
+ " //this section is from http://www.quirksmode.org/js/events_properties.html\n",
+ " var targ;\n",
+ " if (!e) {\n",
+ " e = window.event;\n",
+ " }\n",
+ " if (e.target) {\n",
+ " targ = e.target;\n",
+ " } else if (e.srcElement) {\n",
+ " targ = e.srcElement;\n",
+ " }\n",
+ " if (targ.nodeType === 3) {\n",
+ " // defeat Safari bug\n",
+ " targ = targ.parentNode;\n",
+ " }\n",
+ "\n",
+ " // pageX,Y are the mouse positions relative to the document\n",
+ " var boundingRect = targ.getBoundingClientRect();\n",
+ " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n",
+ " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n",
+ "\n",
+ " return { x: x, y: y };\n",
+ "};\n",
+ "\n",
+ "/*\n",
+ " * return a copy of an object with only non-object keys\n",
+ " * we need this to avoid circular references\n",
+ " * https://stackoverflow.com/a/24161582/3208463\n",
+ " */\n",
+ "function simpleKeys(original) {\n",
+ " return Object.keys(original).reduce(function (obj, key) {\n",
+ " if (typeof original[key] !== 'object') {\n",
+ " obj[key] = original[key];\n",
+ " }\n",
+ " return obj;\n",
+ " }, {});\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.mouse_event = function (event, name) {\n",
+ " var canvas_pos = mpl.findpos(event);\n",
+ "\n",
+ " if (name === 'button_press') {\n",
+ " this.canvas.focus();\n",
+ " this.canvas_div.focus();\n",
+ " }\n",
+ "\n",
+ " var x = canvas_pos.x * this.ratio;\n",
+ " var y = canvas_pos.y * this.ratio;\n",
+ "\n",
+ " this.send_message(name, {\n",
+ " x: x,\n",
+ " y: y,\n",
+ " button: event.button,\n",
+ " step: event.step,\n",
+ " guiEvent: simpleKeys(event),\n",
+ " });\n",
+ "\n",
+ " /* This prevents the web browser from automatically changing to\n",
+ " * the text insertion cursor when the button is pressed. We want\n",
+ " * to control all of the cursor setting manually through the\n",
+ " * 'cursor' event from matplotlib */\n",
+ " event.preventDefault();\n",
+ " return false;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n",
+ " // Handle any extra behaviour associated with a key event\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.key_event = function (event, name) {\n",
+ " // Prevent repeat events\n",
+ " if (name === 'key_press') {\n",
+ " if (event.key === this._key) {\n",
+ " return;\n",
+ " } else {\n",
+ " this._key = event.key;\n",
+ " }\n",
+ " }\n",
+ " if (name === 'key_release') {\n",
+ " this._key = null;\n",
+ " }\n",
+ "\n",
+ " var value = '';\n",
+ " if (event.ctrlKey && event.key !== 'Control') {\n",
+ " value += 'ctrl+';\n",
+ " }\n",
+ " else if (event.altKey && event.key !== 'Alt') {\n",
+ " value += 'alt+';\n",
+ " }\n",
+ " else if (event.shiftKey && event.key !== 'Shift') {\n",
+ " value += 'shift+';\n",
+ " }\n",
+ "\n",
+ " value += 'k' + event.key;\n",
+ "\n",
+ " this._key_event_extra(event, name);\n",
+ "\n",
+ " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n",
+ " return false;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n",
+ " if (name === 'download') {\n",
+ " this.handle_save(this, null);\n",
+ " } else {\n",
+ " this.send_message('toolbar_button', { name: name });\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n",
+ " this.message.textContent = tooltip;\n",
+ "};\n",
+ "\n",
+ "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n",
+ "// prettier-ignore\n",
+ "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n",
+ "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
+ "\n",
+ "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
+ "\n",
+ "mpl.default_extension = \"png\";/* global mpl */\n",
+ "\n",
+ "var comm_websocket_adapter = function (comm) {\n",
+ " // Create a \"websocket\"-like object which calls the given IPython comm\n",
+ " // object with the appropriate methods. Currently this is a non binary\n",
+ " // socket, so there is still some room for performance tuning.\n",
+ " var ws = {};\n",
+ "\n",
+ " ws.binaryType = comm.kernel.ws.binaryType;\n",
+ " ws.readyState = comm.kernel.ws.readyState;\n",
+ " function updateReadyState(_event) {\n",
+ " if (comm.kernel.ws) {\n",
+ " ws.readyState = comm.kernel.ws.readyState;\n",
+ " } else {\n",
+ " ws.readyState = 3; // Closed state.\n",
+ " }\n",
+ " }\n",
+ " comm.kernel.ws.addEventListener('open', updateReadyState);\n",
+ " comm.kernel.ws.addEventListener('close', updateReadyState);\n",
+ " comm.kernel.ws.addEventListener('error', updateReadyState);\n",
+ "\n",
+ " ws.close = function () {\n",
+ " comm.close();\n",
+ " };\n",
+ " ws.send = function (m) {\n",
+ " //console.log('sending', m);\n",
+ " comm.send(m);\n",
+ " };\n",
+ " // Register the callback with on_msg.\n",
+ " comm.on_msg(function (msg) {\n",
+ " //console.log('receiving', msg['content']['data'], msg);\n",
+ " var data = msg['content']['data'];\n",
+ " if (data['blob'] !== undefined) {\n",
+ " data = {\n",
+ " data: new Blob(msg['buffers'], { type: data['blob'] }),\n",
+ " };\n",
+ " }\n",
+ " // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
+ " ws.onmessage(data);\n",
+ " });\n",
+ " return ws;\n",
+ "};\n",
+ "\n",
+ "mpl.mpl_figure_comm = function (comm, msg) {\n",
+ " // This is the function which gets called when the mpl process\n",
+ " // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
+ "\n",
+ " var id = msg.content.data.id;\n",
+ " // Get hold of the div created by the display call when the Comm\n",
+ " // socket was opened in Python.\n",
+ " var element = document.getElementById(id);\n",
+ " var ws_proxy = comm_websocket_adapter(comm);\n",
+ "\n",
+ " function ondownload(figure, _format) {\n",
+ " window.open(figure.canvas.toDataURL());\n",
+ " }\n",
+ "\n",
+ " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n",
+ "\n",
+ " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
+ " // web socket which is closed, not our websocket->open comm proxy.\n",
+ " ws_proxy.onopen();\n",
+ "\n",
+ " fig.parent_element = element;\n",
+ " fig.cell_info = mpl.find_output_cell(\"\");\n",
+ " if (!fig.cell_info) {\n",
+ " console.error('Failed to find cell for figure', id, fig);\n",
+ " return;\n",
+ " }\n",
+ " fig.cell_info[0].output_area.element.on(\n",
+ " 'cleared',\n",
+ " { fig: fig },\n",
+ " fig._remove_fig_handler\n",
+ " );\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_close = function (fig, msg) {\n",
+ " var width = fig.canvas.width / fig.ratio;\n",
+ " fig.cell_info[0].output_area.element.off(\n",
+ " 'cleared',\n",
+ " fig._remove_fig_handler\n",
+ " );\n",
+ " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n",
+ "\n",
+ " // Update the output cell to use the data from the current canvas.\n",
+ " fig.push_to_output();\n",
+ " var dataURL = fig.canvas.toDataURL();\n",
+ " // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
+ " // the notebook keyboard shortcuts fail.\n",
+ " IPython.keyboard_manager.enable();\n",
+ " fig.parent_element.innerHTML =\n",
+ " '
';\n",
+ " fig.close_ws(fig, msg);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.close_ws = function (fig, msg) {\n",
+ " fig.send_message('closing', msg);\n",
+ " // fig.ws.close()\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n",
+ " // Turn the data on the canvas into data in the output cell.\n",
+ " var width = this.canvas.width / this.ratio;\n",
+ " var dataURL = this.canvas.toDataURL();\n",
+ " this.cell_info[1]['text/html'] =\n",
+ " '
';\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.updated_canvas_event = function () {\n",
+ " // Tell IPython that the notebook contents must change.\n",
+ " IPython.notebook.set_dirty(true);\n",
+ " this.send_message('ack', {});\n",
+ " var fig = this;\n",
+ " // Wait a second, then push the new image to the DOM so\n",
+ " // that it is saved nicely (might be nice to debounce this).\n",
+ " setTimeout(function () {\n",
+ " fig.push_to_output();\n",
+ " }, 1000);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_toolbar = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var toolbar = document.createElement('div');\n",
+ " toolbar.classList = 'btn-toolbar';\n",
+ " this.root.appendChild(toolbar);\n",
+ "\n",
+ " function on_click_closure(name) {\n",
+ " return function (_event) {\n",
+ " return fig.toolbar_button_onclick(name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " function on_mouseover_closure(tooltip) {\n",
+ " return function (event) {\n",
+ " if (!event.currentTarget.disabled) {\n",
+ " return fig.toolbar_button_onmouseover(tooltip);\n",
+ " }\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " fig.buttons = {};\n",
+ " var buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'btn-group';\n",
+ " var button;\n",
+ " for (var toolbar_ind in mpl.toolbar_items) {\n",
+ " var name = mpl.toolbar_items[toolbar_ind][0];\n",
+ " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+ " var image = mpl.toolbar_items[toolbar_ind][2];\n",
+ " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+ "\n",
+ " if (!name) {\n",
+ " /* Instead of a spacer, we start a new button group. */\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ " buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'btn-group';\n",
+ " continue;\n",
+ " }\n",
+ "\n",
+ " button = fig.buttons[name] = document.createElement('button');\n",
+ " button.classList = 'btn btn-default';\n",
+ " button.href = '#';\n",
+ " button.title = name;\n",
+ " button.innerHTML = '';\n",
+ " button.addEventListener('click', on_click_closure(method_name));\n",
+ " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+ " buttonGroup.appendChild(button);\n",
+ " }\n",
+ "\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ "\n",
+ " // Add the status bar.\n",
+ " var status_bar = document.createElement('span');\n",
+ " status_bar.classList = 'mpl-message pull-right';\n",
+ " toolbar.appendChild(status_bar);\n",
+ " this.message = status_bar;\n",
+ "\n",
+ " // Add the close button to the window.\n",
+ " var buttongrp = document.createElement('div');\n",
+ " buttongrp.classList = 'btn-group inline pull-right';\n",
+ " button = document.createElement('button');\n",
+ " button.classList = 'btn btn-mini btn-primary';\n",
+ " button.href = '#';\n",
+ " button.title = 'Stop Interaction';\n",
+ " button.innerHTML = '';\n",
+ " button.addEventListener('click', function (_evt) {\n",
+ " fig.handle_close(fig, {});\n",
+ " });\n",
+ " button.addEventListener(\n",
+ " 'mouseover',\n",
+ " on_mouseover_closure('Stop Interaction')\n",
+ " );\n",
+ " buttongrp.appendChild(button);\n",
+ " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n",
+ " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._remove_fig_handler = function (event) {\n",
+ " var fig = event.data.fig;\n",
+ " if (event.target !== this) {\n",
+ " // Ignore bubbled events from children.\n",
+ " return;\n",
+ " }\n",
+ " fig.close_ws(fig, {});\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._root_extra_style = function (el) {\n",
+ " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._canvas_extra_style = function (el) {\n",
+ " // this is important to make the div 'focusable\n",
+ " el.setAttribute('tabindex', 0);\n",
+ " // reach out to IPython and tell the keyboard manager to turn it's self\n",
+ " // off when our div gets focus\n",
+ "\n",
+ " // location in version 3\n",
+ " if (IPython.notebook.keyboard_manager) {\n",
+ " IPython.notebook.keyboard_manager.register_events(el);\n",
+ " } else {\n",
+ " // location in version 2\n",
+ " IPython.keyboard_manager.register_events(el);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._key_event_extra = function (event, _name) {\n",
+ " // Check for shift+enter\n",
+ " if (event.shiftKey && event.which === 13) {\n",
+ " this.canvas_div.blur();\n",
+ " // select the cell after this one\n",
+ " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n",
+ " IPython.notebook.select(index + 1);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+ " fig.ondownload(fig, null);\n",
+ "};\n",
+ "\n",
+ "mpl.find_output_cell = function (html_output) {\n",
+ " // Return the cell and output element which can be found *uniquely* in the notebook.\n",
+ " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
+ " // IPython event is triggered only after the cells have been serialised, which for\n",
+ " // our purposes (turning an active figure into a static one), is too late.\n",
+ " var cells = IPython.notebook.get_cells();\n",
+ " var ncells = cells.length;\n",
+ " for (var i = 0; i < ncells; i++) {\n",
+ " var cell = cells[i];\n",
+ " if (cell.cell_type === 'code') {\n",
+ " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n",
+ " var data = cell.output_area.outputs[j];\n",
+ " if (data.data) {\n",
+ " // IPython >= 3 moved mimebundle to data attribute of output\n",
+ " data = data.data;\n",
+ " }\n",
+ " if (data['text/html'] === html_output) {\n",
+ " return [cell, data, j];\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "// Register the function which deals with the matplotlib target/channel.\n",
+ "// The kernel may be null if the page has been refreshed.\n",
+ "if (IPython.notebook.kernel !== null) {\n",
+ " IPython.notebook.kernel.comm_manager.register_target(\n",
+ " 'matplotlib',\n",
+ " mpl.mpl_figure_comm\n",
+ " );\n",
+ "}\n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "
"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "%matplotlib notebook\n",
+ "\n",
+ "# Read in results\n",
+ "## Params - Either specify a file prefix or a whole folder\n",
+ "file_prefix = None # Path(\"./results/2022-07-05T22:22:50_iops_write_\")\n",
+ "folder = Path(\"./results/param_study_fio_iops\")\n",
+ "\n",
+ "## Plot code\n",
+ "path = file_prefix if file_prefix else folder\n",
+ "\n",
+ "\n",
+ "post_i = \"_isilon.json\"\n",
+ "post_n = \"_netapp.json\"\n",
+ "metric = METRIC[\"iops_write\"]\n",
+ "\n",
+ "df_1 = pd.concat({\n",
+ " \"nfs_isilon\": load_data(path, suffix=\"blocksize\"+post_i, metric=metric),\n",
+ " \"nfs_netapp\": load_data(path, suffix=\"blocksize\"+post_n, metric=metric),\n",
+ "})\n",
+ "df_2 = pd.concat({\n",
+ " \"nfs_isilon\": load_data(path, suffix=\"filesize\"+post_i, metric=metric),\n",
+ " \"nfs_netapp\": load_data(path, suffix=\"filesize\"+post_n, metric=metric),\n",
+ "})\n",
+ "df_3 = pd.concat({\n",
+ " \"nfs_isilon\": load_data(path, suffix=\"iodepth\"+post_i, metric=metric),\n",
+ " \"nfs_netapp\": load_data(path, suffix=\"iodepth\"+post_n, metric=metric),\n",
+ "})\n",
+ "df_4 = pd.concat({\n",
+ " \"nfs_isilon\": load_data(path, suffix=\"numjobs\"+post_i, metric=metric),\n",
+ " \"nfs_netapp\": load_data(path, suffix=\"numjobs\"+post_n, metric=metric),\n",
+ "})\n",
+ "\n",
+ "fig, axis = plt.subplots(2, 2, sharey=True)\n",
+ "fig.suptitle('Parameter Study: Fio Write IOPS')\n",
+ "\n",
+ "color = [\"tab:red\", \"tab:cyan\"]\n",
+ " \n",
+ "df_1[\"mean\"].unstack(level=0).plot(\n",
+ " ax=axis[0][0],\n",
+ " kind='bar', rot=0,\n",
+ " xlabel='Blocksize in KiB',\n",
+ " ylabel='IOPS',\n",
+ " title='Variable Blocksize',\n",
+ " yerr=df_1[\"std\"].unstack(level=0),\n",
+ " color=color,\n",
+ ")\n",
+ "df_2[\"mean\"].unstack(level=0).plot(\n",
+ " ax=axis[0][1],\n",
+ " kind='bar', rot=0,\n",
+ " xlabel='Filesize in GiB',\n",
+ " title='Variable Filesize',\n",
+ " yerr=df_2[\"std\"].unstack(level=0),\n",
+ " color=color,\n",
+ ")\n",
+ "\n",
+ "df_3[\"mean\"].unstack(level=0).plot(\n",
+ " ax=axis[1][0],\n",
+ " kind='bar', rot=0,\n",
+ " xlabel='IODepth',\n",
+ " ylabel='IOPS',\n",
+ " title='Variable IODepth',\n",
+ " yerr=df_3[\"std\"].unstack(level=0),\n",
+ " color=color,\n",
+ ")\n",
+ "\n",
+ "df_4[\"mean\"].unstack(level=0).plot(\n",
+ " ax=axis[1][1],\n",
+ " kind='bar', rot=0,\n",
+ " xlabel='Jobs',\n",
+ " title='Variable Number of Jobs',\n",
+ " yerr=df_4[\"std\"].unstack(level=0),\n",
+ " color=color,\n",
+ ")\n",
+ "\n",
+ "for i,j in [(0,1),(1,0),(1,1)]:\n",
+ " axis[i][j].get_legend().remove()\n",
+ " \n",
+ "plt.tight_layout()\n",
+ "plt.savefig(\"paramstudy_fio_iops.png\",bbox_inches='tight')\n",
+ "plt.show()\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "0ed3bfbb",
+ "metadata": {},
+ "source": [
+ "# Experiment: Parameter-evaluation: MDTest"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "id": "8f15ae0a",
+ "metadata": {
+ "scrolled": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "application/javascript": [
+ "/* Put everything inside the global mpl namespace */\n",
+ "/* global mpl */\n",
+ "window.mpl = {};\n",
+ "\n",
+ "mpl.get_websocket_type = function () {\n",
+ " if (typeof WebSocket !== 'undefined') {\n",
+ " return WebSocket;\n",
+ " } else if (typeof MozWebSocket !== 'undefined') {\n",
+ " return MozWebSocket;\n",
+ " } else {\n",
+ " alert(\n",
+ " 'Your browser does not have WebSocket support. ' +\n",
+ " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
+ " 'Firefox 4 and 5 are also supported but you ' +\n",
+ " 'have to enable WebSockets in about:config.'\n",
+ " );\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n",
+ " this.id = figure_id;\n",
+ "\n",
+ " this.ws = websocket;\n",
+ "\n",
+ " this.supports_binary = this.ws.binaryType !== undefined;\n",
+ "\n",
+ " if (!this.supports_binary) {\n",
+ " var warnings = document.getElementById('mpl-warnings');\n",
+ " if (warnings) {\n",
+ " warnings.style.display = 'block';\n",
+ " warnings.textContent =\n",
+ " 'This browser does not support binary websocket messages. ' +\n",
+ " 'Performance may be slow.';\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " this.imageObj = new Image();\n",
+ "\n",
+ " this.context = undefined;\n",
+ " this.message = undefined;\n",
+ " this.canvas = undefined;\n",
+ " this.rubberband_canvas = undefined;\n",
+ " this.rubberband_context = undefined;\n",
+ " this.format_dropdown = undefined;\n",
+ "\n",
+ " this.image_mode = 'full';\n",
+ "\n",
+ " this.root = document.createElement('div');\n",
+ " this.root.setAttribute('style', 'display: inline-block');\n",
+ " this._root_extra_style(this.root);\n",
+ "\n",
+ " parent_element.appendChild(this.root);\n",
+ "\n",
+ " this._init_header(this);\n",
+ " this._init_canvas(this);\n",
+ " this._init_toolbar(this);\n",
+ "\n",
+ " var fig = this;\n",
+ "\n",
+ " this.waiting = false;\n",
+ "\n",
+ " this.ws.onopen = function () {\n",
+ " fig.send_message('supports_binary', { value: fig.supports_binary });\n",
+ " fig.send_message('send_image_mode', {});\n",
+ " if (fig.ratio !== 1) {\n",
+ " fig.send_message('set_device_pixel_ratio', {\n",
+ " device_pixel_ratio: fig.ratio,\n",
+ " });\n",
+ " }\n",
+ " fig.send_message('refresh', {});\n",
+ " };\n",
+ "\n",
+ " this.imageObj.onload = function () {\n",
+ " if (fig.image_mode === 'full') {\n",
+ " // Full images could contain transparency (where diff images\n",
+ " // almost always do), so we need to clear the canvas so that\n",
+ " // there is no ghosting.\n",
+ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
+ " }\n",
+ " fig.context.drawImage(fig.imageObj, 0, 0);\n",
+ " };\n",
+ "\n",
+ " this.imageObj.onunload = function () {\n",
+ " fig.ws.close();\n",
+ " };\n",
+ "\n",
+ " this.ws.onmessage = this._make_on_message_function(this);\n",
+ "\n",
+ " this.ondownload = ondownload;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_header = function () {\n",
+ " var titlebar = document.createElement('div');\n",
+ " titlebar.classList =\n",
+ " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n",
+ " var titletext = document.createElement('div');\n",
+ " titletext.classList = 'ui-dialog-title';\n",
+ " titletext.setAttribute(\n",
+ " 'style',\n",
+ " 'width: 100%; text-align: center; padding: 3px;'\n",
+ " );\n",
+ " titlebar.appendChild(titletext);\n",
+ " this.root.appendChild(titlebar);\n",
+ " this.header = titletext;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n",
+ "\n",
+ "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n",
+ "\n",
+ "mpl.figure.prototype._init_canvas = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var canvas_div = (this.canvas_div = document.createElement('div'));\n",
+ " canvas_div.setAttribute(\n",
+ " 'style',\n",
+ " 'border: 1px solid #ddd;' +\n",
+ " 'box-sizing: content-box;' +\n",
+ " 'clear: both;' +\n",
+ " 'min-height: 1px;' +\n",
+ " 'min-width: 1px;' +\n",
+ " 'outline: 0;' +\n",
+ " 'overflow: hidden;' +\n",
+ " 'position: relative;' +\n",
+ " 'resize: both;'\n",
+ " );\n",
+ "\n",
+ " function on_keyboard_event_closure(name) {\n",
+ " return function (event) {\n",
+ " return fig.key_event(event, name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " canvas_div.addEventListener(\n",
+ " 'keydown',\n",
+ " on_keyboard_event_closure('key_press')\n",
+ " );\n",
+ " canvas_div.addEventListener(\n",
+ " 'keyup',\n",
+ " on_keyboard_event_closure('key_release')\n",
+ " );\n",
+ "\n",
+ " this._canvas_extra_style(canvas_div);\n",
+ " this.root.appendChild(canvas_div);\n",
+ "\n",
+ " var canvas = (this.canvas = document.createElement('canvas'));\n",
+ " canvas.classList.add('mpl-canvas');\n",
+ " canvas.setAttribute('style', 'box-sizing: content-box;');\n",
+ "\n",
+ " this.context = canvas.getContext('2d');\n",
+ "\n",
+ " var backingStore =\n",
+ " this.context.backingStorePixelRatio ||\n",
+ " this.context.webkitBackingStorePixelRatio ||\n",
+ " this.context.mozBackingStorePixelRatio ||\n",
+ " this.context.msBackingStorePixelRatio ||\n",
+ " this.context.oBackingStorePixelRatio ||\n",
+ " this.context.backingStorePixelRatio ||\n",
+ " 1;\n",
+ "\n",
+ " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
+ "\n",
+ " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n",
+ " 'canvas'\n",
+ " ));\n",
+ " rubberband_canvas.setAttribute(\n",
+ " 'style',\n",
+ " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n",
+ " );\n",
+ "\n",
+ " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n",
+ " if (this.ResizeObserver === undefined) {\n",
+ " if (window.ResizeObserver !== undefined) {\n",
+ " this.ResizeObserver = window.ResizeObserver;\n",
+ " } else {\n",
+ " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n",
+ " this.ResizeObserver = obs.ResizeObserver;\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n",
+ " var nentries = entries.length;\n",
+ " for (var i = 0; i < nentries; i++) {\n",
+ " var entry = entries[i];\n",
+ " var width, height;\n",
+ " if (entry.contentBoxSize) {\n",
+ " if (entry.contentBoxSize instanceof Array) {\n",
+ " // Chrome 84 implements new version of spec.\n",
+ " width = entry.contentBoxSize[0].inlineSize;\n",
+ " height = entry.contentBoxSize[0].blockSize;\n",
+ " } else {\n",
+ " // Firefox implements old version of spec.\n",
+ " width = entry.contentBoxSize.inlineSize;\n",
+ " height = entry.contentBoxSize.blockSize;\n",
+ " }\n",
+ " } else {\n",
+ " // Chrome <84 implements even older version of spec.\n",
+ " width = entry.contentRect.width;\n",
+ " height = entry.contentRect.height;\n",
+ " }\n",
+ "\n",
+ " // Keep the size of the canvas and rubber band canvas in sync with\n",
+ " // the canvas container.\n",
+ " if (entry.devicePixelContentBoxSize) {\n",
+ " // Chrome 84 implements new version of spec.\n",
+ " canvas.setAttribute(\n",
+ " 'width',\n",
+ " entry.devicePixelContentBoxSize[0].inlineSize\n",
+ " );\n",
+ " canvas.setAttribute(\n",
+ " 'height',\n",
+ " entry.devicePixelContentBoxSize[0].blockSize\n",
+ " );\n",
+ " } else {\n",
+ " canvas.setAttribute('width', width * fig.ratio);\n",
+ " canvas.setAttribute('height', height * fig.ratio);\n",
+ " }\n",
+ " canvas.setAttribute(\n",
+ " 'style',\n",
+ " 'width: ' + width + 'px; height: ' + height + 'px;'\n",
+ " );\n",
+ "\n",
+ " rubberband_canvas.setAttribute('width', width);\n",
+ " rubberband_canvas.setAttribute('height', height);\n",
+ "\n",
+ " // And update the size in Python. We ignore the initial 0/0 size\n",
+ " // that occurs as the element is placed into the DOM, which should\n",
+ " // otherwise not happen due to the minimum size styling.\n",
+ " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n",
+ " fig.request_resize(width, height);\n",
+ " }\n",
+ " }\n",
+ " });\n",
+ " this.resizeObserverInstance.observe(canvas_div);\n",
+ "\n",
+ " function on_mouse_event_closure(name) {\n",
+ " return function (event) {\n",
+ " return fig.mouse_event(event, name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mousedown',\n",
+ " on_mouse_event_closure('button_press')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseup',\n",
+ " on_mouse_event_closure('button_release')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'dblclick',\n",
+ " on_mouse_event_closure('dblclick')\n",
+ " );\n",
+ " // Throttle sequential mouse events to 1 every 20ms.\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mousemove',\n",
+ " on_mouse_event_closure('motion_notify')\n",
+ " );\n",
+ "\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseenter',\n",
+ " on_mouse_event_closure('figure_enter')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseleave',\n",
+ " on_mouse_event_closure('figure_leave')\n",
+ " );\n",
+ "\n",
+ " canvas_div.addEventListener('wheel', function (event) {\n",
+ " if (event.deltaY < 0) {\n",
+ " event.step = 1;\n",
+ " } else {\n",
+ " event.step = -1;\n",
+ " }\n",
+ " on_mouse_event_closure('scroll')(event);\n",
+ " });\n",
+ "\n",
+ " canvas_div.appendChild(canvas);\n",
+ " canvas_div.appendChild(rubberband_canvas);\n",
+ "\n",
+ " this.rubberband_context = rubberband_canvas.getContext('2d');\n",
+ " this.rubberband_context.strokeStyle = '#000000';\n",
+ "\n",
+ " this._resize_canvas = function (width, height, forward) {\n",
+ " if (forward) {\n",
+ " canvas_div.style.width = width + 'px';\n",
+ " canvas_div.style.height = height + 'px';\n",
+ " }\n",
+ " };\n",
+ "\n",
+ " // Disable right mouse context menu.\n",
+ " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n",
+ " event.preventDefault();\n",
+ " return false;\n",
+ " });\n",
+ "\n",
+ " function set_focus() {\n",
+ " canvas.focus();\n",
+ " canvas_div.focus();\n",
+ " }\n",
+ "\n",
+ " window.setTimeout(set_focus, 100);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_toolbar = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var toolbar = document.createElement('div');\n",
+ " toolbar.classList = 'mpl-toolbar';\n",
+ " this.root.appendChild(toolbar);\n",
+ "\n",
+ " function on_click_closure(name) {\n",
+ " return function (_event) {\n",
+ " return fig.toolbar_button_onclick(name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " function on_mouseover_closure(tooltip) {\n",
+ " return function (event) {\n",
+ " if (!event.currentTarget.disabled) {\n",
+ " return fig.toolbar_button_onmouseover(tooltip);\n",
+ " }\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " fig.buttons = {};\n",
+ " var buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'mpl-button-group';\n",
+ " for (var toolbar_ind in mpl.toolbar_items) {\n",
+ " var name = mpl.toolbar_items[toolbar_ind][0];\n",
+ " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+ " var image = mpl.toolbar_items[toolbar_ind][2];\n",
+ " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+ "\n",
+ " if (!name) {\n",
+ " /* Instead of a spacer, we start a new button group. */\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ " buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'mpl-button-group';\n",
+ " continue;\n",
+ " }\n",
+ "\n",
+ " var button = (fig.buttons[name] = document.createElement('button'));\n",
+ " button.classList = 'mpl-widget';\n",
+ " button.setAttribute('role', 'button');\n",
+ " button.setAttribute('aria-disabled', 'false');\n",
+ " button.addEventListener('click', on_click_closure(method_name));\n",
+ " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+ "\n",
+ " var icon_img = document.createElement('img');\n",
+ " icon_img.src = '_images/' + image + '.png';\n",
+ " icon_img.srcset = '_images/' + image + '_large.png 2x';\n",
+ " icon_img.alt = tooltip;\n",
+ " button.appendChild(icon_img);\n",
+ "\n",
+ " buttonGroup.appendChild(button);\n",
+ " }\n",
+ "\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ "\n",
+ " var fmt_picker = document.createElement('select');\n",
+ " fmt_picker.classList = 'mpl-widget';\n",
+ " toolbar.appendChild(fmt_picker);\n",
+ " this.format_dropdown = fmt_picker;\n",
+ "\n",
+ " for (var ind in mpl.extensions) {\n",
+ " var fmt = mpl.extensions[ind];\n",
+ " var option = document.createElement('option');\n",
+ " option.selected = fmt === mpl.default_extension;\n",
+ " option.innerHTML = fmt;\n",
+ " fmt_picker.appendChild(option);\n",
+ " }\n",
+ "\n",
+ " var status_bar = document.createElement('span');\n",
+ " status_bar.classList = 'mpl-message';\n",
+ " toolbar.appendChild(status_bar);\n",
+ " this.message = status_bar;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n",
+ " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
+ " // which will in turn request a refresh of the image.\n",
+ " this.send_message('resize', { width: x_pixels, height: y_pixels });\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.send_message = function (type, properties) {\n",
+ " properties['type'] = type;\n",
+ " properties['figure_id'] = this.id;\n",
+ " this.ws.send(JSON.stringify(properties));\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.send_draw_message = function () {\n",
+ " if (!this.waiting) {\n",
+ " this.waiting = true;\n",
+ " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+ " var format_dropdown = fig.format_dropdown;\n",
+ " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
+ " fig.ondownload(fig, format);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_resize = function (fig, msg) {\n",
+ " var size = msg['size'];\n",
+ " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n",
+ " fig._resize_canvas(size[0], size[1], msg['forward']);\n",
+ " fig.send_message('refresh', {});\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n",
+ " var x0 = msg['x0'] / fig.ratio;\n",
+ " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n",
+ " var x1 = msg['x1'] / fig.ratio;\n",
+ " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n",
+ " x0 = Math.floor(x0) + 0.5;\n",
+ " y0 = Math.floor(y0) + 0.5;\n",
+ " x1 = Math.floor(x1) + 0.5;\n",
+ " y1 = Math.floor(y1) + 0.5;\n",
+ " var min_x = Math.min(x0, x1);\n",
+ " var min_y = Math.min(y0, y1);\n",
+ " var width = Math.abs(x1 - x0);\n",
+ " var height = Math.abs(y1 - y0);\n",
+ "\n",
+ " fig.rubberband_context.clearRect(\n",
+ " 0,\n",
+ " 0,\n",
+ " fig.canvas.width / fig.ratio,\n",
+ " fig.canvas.height / fig.ratio\n",
+ " );\n",
+ "\n",
+ " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n",
+ " // Updates the figure title.\n",
+ " fig.header.textContent = msg['label'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n",
+ " fig.rubberband_canvas.style.cursor = msg['cursor'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_message = function (fig, msg) {\n",
+ " fig.message.textContent = msg['message'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n",
+ " // Request the server to send over a new figure.\n",
+ " fig.send_draw_message();\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n",
+ " fig.image_mode = msg['mode'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n",
+ " for (var key in msg) {\n",
+ " if (!(key in fig.buttons)) {\n",
+ " continue;\n",
+ " }\n",
+ " fig.buttons[key].disabled = !msg[key];\n",
+ " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n",
+ " if (msg['mode'] === 'PAN') {\n",
+ " fig.buttons['Pan'].classList.add('active');\n",
+ " fig.buttons['Zoom'].classList.remove('active');\n",
+ " } else if (msg['mode'] === 'ZOOM') {\n",
+ " fig.buttons['Pan'].classList.remove('active');\n",
+ " fig.buttons['Zoom'].classList.add('active');\n",
+ " } else {\n",
+ " fig.buttons['Pan'].classList.remove('active');\n",
+ " fig.buttons['Zoom'].classList.remove('active');\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.updated_canvas_event = function () {\n",
+ " // Called whenever the canvas gets updated.\n",
+ " this.send_message('ack', {});\n",
+ "};\n",
+ "\n",
+ "// A function to construct a web socket function for onmessage handling.\n",
+ "// Called in the figure constructor.\n",
+ "mpl.figure.prototype._make_on_message_function = function (fig) {\n",
+ " return function socket_on_message(evt) {\n",
+ " if (evt.data instanceof Blob) {\n",
+ " var img = evt.data;\n",
+ " if (img.type !== 'image/png') {\n",
+ " /* FIXME: We get \"Resource interpreted as Image but\n",
+ " * transferred with MIME type text/plain:\" errors on\n",
+ " * Chrome. But how to set the MIME type? It doesn't seem\n",
+ " * to be part of the websocket stream */\n",
+ " img.type = 'image/png';\n",
+ " }\n",
+ "\n",
+ " /* Free the memory for the previous frames */\n",
+ " if (fig.imageObj.src) {\n",
+ " (window.URL || window.webkitURL).revokeObjectURL(\n",
+ " fig.imageObj.src\n",
+ " );\n",
+ " }\n",
+ "\n",
+ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
+ " img\n",
+ " );\n",
+ " fig.updated_canvas_event();\n",
+ " fig.waiting = false;\n",
+ " return;\n",
+ " } else if (\n",
+ " typeof evt.data === 'string' &&\n",
+ " evt.data.slice(0, 21) === 'data:image/png;base64'\n",
+ " ) {\n",
+ " fig.imageObj.src = evt.data;\n",
+ " fig.updated_canvas_event();\n",
+ " fig.waiting = false;\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " var msg = JSON.parse(evt.data);\n",
+ " var msg_type = msg['type'];\n",
+ "\n",
+ " // Call the \"handle_{type}\" callback, which takes\n",
+ " // the figure and JSON message as its only arguments.\n",
+ " try {\n",
+ " var callback = fig['handle_' + msg_type];\n",
+ " } catch (e) {\n",
+ " console.log(\n",
+ " \"No handler for the '\" + msg_type + \"' message type: \",\n",
+ " msg\n",
+ " );\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " if (callback) {\n",
+ " try {\n",
+ " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
+ " callback(fig, msg);\n",
+ " } catch (e) {\n",
+ " console.log(\n",
+ " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n",
+ " e,\n",
+ " e.stack,\n",
+ " msg\n",
+ " );\n",
+ " }\n",
+ " }\n",
+ " };\n",
+ "};\n",
+ "\n",
+ "// from https://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
+ "mpl.findpos = function (e) {\n",
+ " //this section is from http://www.quirksmode.org/js/events_properties.html\n",
+ " var targ;\n",
+ " if (!e) {\n",
+ " e = window.event;\n",
+ " }\n",
+ " if (e.target) {\n",
+ " targ = e.target;\n",
+ " } else if (e.srcElement) {\n",
+ " targ = e.srcElement;\n",
+ " }\n",
+ " if (targ.nodeType === 3) {\n",
+ " // defeat Safari bug\n",
+ " targ = targ.parentNode;\n",
+ " }\n",
+ "\n",
+ " // pageX,Y are the mouse positions relative to the document\n",
+ " var boundingRect = targ.getBoundingClientRect();\n",
+ " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n",
+ " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n",
+ "\n",
+ " return { x: x, y: y };\n",
+ "};\n",
+ "\n",
+ "/*\n",
+ " * return a copy of an object with only non-object keys\n",
+ " * we need this to avoid circular references\n",
+ " * https://stackoverflow.com/a/24161582/3208463\n",
+ " */\n",
+ "function simpleKeys(original) {\n",
+ " return Object.keys(original).reduce(function (obj, key) {\n",
+ " if (typeof original[key] !== 'object') {\n",
+ " obj[key] = original[key];\n",
+ " }\n",
+ " return obj;\n",
+ " }, {});\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.mouse_event = function (event, name) {\n",
+ " var canvas_pos = mpl.findpos(event);\n",
+ "\n",
+ " if (name === 'button_press') {\n",
+ " this.canvas.focus();\n",
+ " this.canvas_div.focus();\n",
+ " }\n",
+ "\n",
+ " var x = canvas_pos.x * this.ratio;\n",
+ " var y = canvas_pos.y * this.ratio;\n",
+ "\n",
+ " this.send_message(name, {\n",
+ " x: x,\n",
+ " y: y,\n",
+ " button: event.button,\n",
+ " step: event.step,\n",
+ " guiEvent: simpleKeys(event),\n",
+ " });\n",
+ "\n",
+ " /* This prevents the web browser from automatically changing to\n",
+ " * the text insertion cursor when the button is pressed. We want\n",
+ " * to control all of the cursor setting manually through the\n",
+ " * 'cursor' event from matplotlib */\n",
+ " event.preventDefault();\n",
+ " return false;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n",
+ " // Handle any extra behaviour associated with a key event\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.key_event = function (event, name) {\n",
+ " // Prevent repeat events\n",
+ " if (name === 'key_press') {\n",
+ " if (event.key === this._key) {\n",
+ " return;\n",
+ " } else {\n",
+ " this._key = event.key;\n",
+ " }\n",
+ " }\n",
+ " if (name === 'key_release') {\n",
+ " this._key = null;\n",
+ " }\n",
+ "\n",
+ " var value = '';\n",
+ " if (event.ctrlKey && event.key !== 'Control') {\n",
+ " value += 'ctrl+';\n",
+ " }\n",
+ " else if (event.altKey && event.key !== 'Alt') {\n",
+ " value += 'alt+';\n",
+ " }\n",
+ " else if (event.shiftKey && event.key !== 'Shift') {\n",
+ " value += 'shift+';\n",
+ " }\n",
+ "\n",
+ " value += 'k' + event.key;\n",
+ "\n",
+ " this._key_event_extra(event, name);\n",
+ "\n",
+ " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n",
+ " return false;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n",
+ " if (name === 'download') {\n",
+ " this.handle_save(this, null);\n",
+ " } else {\n",
+ " this.send_message('toolbar_button', { name: name });\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n",
+ " this.message.textContent = tooltip;\n",
+ "};\n",
+ "\n",
+ "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n",
+ "// prettier-ignore\n",
+ "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n",
+ "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
+ "\n",
+ "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
+ "\n",
+ "mpl.default_extension = \"png\";/* global mpl */\n",
+ "\n",
+ "var comm_websocket_adapter = function (comm) {\n",
+ " // Create a \"websocket\"-like object which calls the given IPython comm\n",
+ " // object with the appropriate methods. Currently this is a non binary\n",
+ " // socket, so there is still some room for performance tuning.\n",
+ " var ws = {};\n",
+ "\n",
+ " ws.binaryType = comm.kernel.ws.binaryType;\n",
+ " ws.readyState = comm.kernel.ws.readyState;\n",
+ " function updateReadyState(_event) {\n",
+ " if (comm.kernel.ws) {\n",
+ " ws.readyState = comm.kernel.ws.readyState;\n",
+ " } else {\n",
+ " ws.readyState = 3; // Closed state.\n",
+ " }\n",
+ " }\n",
+ " comm.kernel.ws.addEventListener('open', updateReadyState);\n",
+ " comm.kernel.ws.addEventListener('close', updateReadyState);\n",
+ " comm.kernel.ws.addEventListener('error', updateReadyState);\n",
+ "\n",
+ " ws.close = function () {\n",
+ " comm.close();\n",
+ " };\n",
+ " ws.send = function (m) {\n",
+ " //console.log('sending', m);\n",
+ " comm.send(m);\n",
+ " };\n",
+ " // Register the callback with on_msg.\n",
+ " comm.on_msg(function (msg) {\n",
+ " //console.log('receiving', msg['content']['data'], msg);\n",
+ " var data = msg['content']['data'];\n",
+ " if (data['blob'] !== undefined) {\n",
+ " data = {\n",
+ " data: new Blob(msg['buffers'], { type: data['blob'] }),\n",
+ " };\n",
+ " }\n",
+ " // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
+ " ws.onmessage(data);\n",
+ " });\n",
+ " return ws;\n",
+ "};\n",
+ "\n",
+ "mpl.mpl_figure_comm = function (comm, msg) {\n",
+ " // This is the function which gets called when the mpl process\n",
+ " // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
+ "\n",
+ " var id = msg.content.data.id;\n",
+ " // Get hold of the div created by the display call when the Comm\n",
+ " // socket was opened in Python.\n",
+ " var element = document.getElementById(id);\n",
+ " var ws_proxy = comm_websocket_adapter(comm);\n",
+ "\n",
+ " function ondownload(figure, _format) {\n",
+ " window.open(figure.canvas.toDataURL());\n",
+ " }\n",
+ "\n",
+ " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n",
+ "\n",
+ " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
+ " // web socket which is closed, not our websocket->open comm proxy.\n",
+ " ws_proxy.onopen();\n",
+ "\n",
+ " fig.parent_element = element;\n",
+ " fig.cell_info = mpl.find_output_cell(\"\");\n",
+ " if (!fig.cell_info) {\n",
+ " console.error('Failed to find cell for figure', id, fig);\n",
+ " return;\n",
+ " }\n",
+ " fig.cell_info[0].output_area.element.on(\n",
+ " 'cleared',\n",
+ " { fig: fig },\n",
+ " fig._remove_fig_handler\n",
+ " );\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_close = function (fig, msg) {\n",
+ " var width = fig.canvas.width / fig.ratio;\n",
+ " fig.cell_info[0].output_area.element.off(\n",
+ " 'cleared',\n",
+ " fig._remove_fig_handler\n",
+ " );\n",
+ " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n",
+ "\n",
+ " // Update the output cell to use the data from the current canvas.\n",
+ " fig.push_to_output();\n",
+ " var dataURL = fig.canvas.toDataURL();\n",
+ " // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
+ " // the notebook keyboard shortcuts fail.\n",
+ " IPython.keyboard_manager.enable();\n",
+ " fig.parent_element.innerHTML =\n",
+ " '
';\n",
+ " fig.close_ws(fig, msg);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.close_ws = function (fig, msg) {\n",
+ " fig.send_message('closing', msg);\n",
+ " // fig.ws.close()\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n",
+ " // Turn the data on the canvas into data in the output cell.\n",
+ " var width = this.canvas.width / this.ratio;\n",
+ " var dataURL = this.canvas.toDataURL();\n",
+ " this.cell_info[1]['text/html'] =\n",
+ " '
';\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.updated_canvas_event = function () {\n",
+ " // Tell IPython that the notebook contents must change.\n",
+ " IPython.notebook.set_dirty(true);\n",
+ " this.send_message('ack', {});\n",
+ " var fig = this;\n",
+ " // Wait a second, then push the new image to the DOM so\n",
+ " // that it is saved nicely (might be nice to debounce this).\n",
+ " setTimeout(function () {\n",
+ " fig.push_to_output();\n",
+ " }, 1000);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_toolbar = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var toolbar = document.createElement('div');\n",
+ " toolbar.classList = 'btn-toolbar';\n",
+ " this.root.appendChild(toolbar);\n",
+ "\n",
+ " function on_click_closure(name) {\n",
+ " return function (_event) {\n",
+ " return fig.toolbar_button_onclick(name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " function on_mouseover_closure(tooltip) {\n",
+ " return function (event) {\n",
+ " if (!event.currentTarget.disabled) {\n",
+ " return fig.toolbar_button_onmouseover(tooltip);\n",
+ " }\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " fig.buttons = {};\n",
+ " var buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'btn-group';\n",
+ " var button;\n",
+ " for (var toolbar_ind in mpl.toolbar_items) {\n",
+ " var name = mpl.toolbar_items[toolbar_ind][0];\n",
+ " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+ " var image = mpl.toolbar_items[toolbar_ind][2];\n",
+ " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+ "\n",
+ " if (!name) {\n",
+ " /* Instead of a spacer, we start a new button group. */\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ " buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'btn-group';\n",
+ " continue;\n",
+ " }\n",
+ "\n",
+ " button = fig.buttons[name] = document.createElement('button');\n",
+ " button.classList = 'btn btn-default';\n",
+ " button.href = '#';\n",
+ " button.title = name;\n",
+ " button.innerHTML = '';\n",
+ " button.addEventListener('click', on_click_closure(method_name));\n",
+ " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+ " buttonGroup.appendChild(button);\n",
+ " }\n",
+ "\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ "\n",
+ " // Add the status bar.\n",
+ " var status_bar = document.createElement('span');\n",
+ " status_bar.classList = 'mpl-message pull-right';\n",
+ " toolbar.appendChild(status_bar);\n",
+ " this.message = status_bar;\n",
+ "\n",
+ " // Add the close button to the window.\n",
+ " var buttongrp = document.createElement('div');\n",
+ " buttongrp.classList = 'btn-group inline pull-right';\n",
+ " button = document.createElement('button');\n",
+ " button.classList = 'btn btn-mini btn-primary';\n",
+ " button.href = '#';\n",
+ " button.title = 'Stop Interaction';\n",
+ " button.innerHTML = '';\n",
+ " button.addEventListener('click', function (_evt) {\n",
+ " fig.handle_close(fig, {});\n",
+ " });\n",
+ " button.addEventListener(\n",
+ " 'mouseover',\n",
+ " on_mouseover_closure('Stop Interaction')\n",
+ " );\n",
+ " buttongrp.appendChild(button);\n",
+ " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n",
+ " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._remove_fig_handler = function (event) {\n",
+ " var fig = event.data.fig;\n",
+ " if (event.target !== this) {\n",
+ " // Ignore bubbled events from children.\n",
+ " return;\n",
+ " }\n",
+ " fig.close_ws(fig, {});\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._root_extra_style = function (el) {\n",
+ " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._canvas_extra_style = function (el) {\n",
+ " // this is important to make the div 'focusable\n",
+ " el.setAttribute('tabindex', 0);\n",
+ " // reach out to IPython and tell the keyboard manager to turn it's self\n",
+ " // off when our div gets focus\n",
+ "\n",
+ " // location in version 3\n",
+ " if (IPython.notebook.keyboard_manager) {\n",
+ " IPython.notebook.keyboard_manager.register_events(el);\n",
+ " } else {\n",
+ " // location in version 2\n",
+ " IPython.keyboard_manager.register_events(el);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._key_event_extra = function (event, _name) {\n",
+ " // Check for shift+enter\n",
+ " if (event.shiftKey && event.which === 13) {\n",
+ " this.canvas_div.blur();\n",
+ " // select the cell after this one\n",
+ " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n",
+ " IPython.notebook.select(index + 1);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+ " fig.ondownload(fig, null);\n",
+ "};\n",
+ "\n",
+ "mpl.find_output_cell = function (html_output) {\n",
+ " // Return the cell and output element which can be found *uniquely* in the notebook.\n",
+ " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
+ " // IPython event is triggered only after the cells have been serialised, which for\n",
+ " // our purposes (turning an active figure into a static one), is too late.\n",
+ " var cells = IPython.notebook.get_cells();\n",
+ " var ncells = cells.length;\n",
+ " for (var i = 0; i < ncells; i++) {\n",
+ " var cell = cells[i];\n",
+ " if (cell.cell_type === 'code') {\n",
+ " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n",
+ " var data = cell.output_area.outputs[j];\n",
+ " if (data.data) {\n",
+ " // IPython >= 3 moved mimebundle to data attribute of output\n",
+ " data = data.data;\n",
+ " }\n",
+ " if (data['text/html'] === html_output) {\n",
+ " return [cell, data, j];\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "// Register the function which deals with the matplotlib target/channel.\n",
+ "// The kernel may be null if the page has been refreshed.\n",
+ "if (IPython.notebook.kernel !== null) {\n",
+ " IPython.notebook.kernel.comm_manager.register_target(\n",
+ " 'matplotlib',\n",
+ " mpl.mpl_figure_comm\n",
+ " );\n",
+ "}\n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "
"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " mean std num\n",
+ "dir_create 10^1 280.55660 367.812856 20\n",
+ " 10^2 870.04705 214.024901 20\n",
+ " 10^3 1095.08435 216.723094 20\n",
+ " 10^4 1161.64235 166.196069 20\n",
+ " 10^5 1180.50980 53.700815 20\n",
+ " mean std num\n",
+ "dir_stat 10^1 59722.32145 131417.670234 20\n",
+ " 10^2 99183.02005 135083.384508 20\n",
+ " 10^3 233443.53515 69215.281521 20\n",
+ " 10^4 348868.19885 37224.326942 20\n",
+ " 10^5 5740.32620 776.724857 20\n",
+ " mean std num\n",
+ "dir_remove 10^1 721.58990 150.587023 20\n",
+ " 10^2 804.84300 174.153828 20\n",
+ " 10^3 798.34010 136.877770 20\n",
+ " 10^4 669.08225 85.009592 20\n",
+ " 10^5 692.32010 28.690286 20\n",
+ " mean std num\n",
+ "file_create 10^1 776.37855 151.638302 20\n",
+ " 10^2 903.83465 126.004420 20\n",
+ " 10^3 890.59460 164.678292 20\n",
+ " 10^4 933.20065 44.686408 20\n",
+ " 10^5 918.76760 20.496545 20\n",
+ " mean std num\n",
+ "file_stat 10^1 54006.49675 118169.827073 20\n",
+ " 10^2 102877.26100 140927.550124 20\n",
+ " 10^3 241069.36130 84737.679978 20\n",
+ " 10^4 5632.01315 662.772729 20\n",
+ " 10^5 4866.17720 390.810018 20\n",
+ " mean std num\n",
+ "file_read 10^1 1212.11170 283.114533 20\n",
+ " 10^2 1577.01555 101.303439 20\n",
+ " 10^3 1623.92030 117.784612 20\n",
+ " 10^4 1622.45610 92.810976 20\n",
+ " 10^5 1590.42615 101.705627 20\n",
+ " mean std num\n",
+ "file_remove 10^1 1147.01635 240.201849 20\n",
+ " 10^2 1411.83485 74.184542 20\n",
+ " 10^3 1327.03895 203.794847 20\n",
+ " 10^4 1089.15860 110.579144 20\n",
+ " 10^5 1011.23390 24.363037 20\n"
+ ]
+ }
+ ],
+ "source": [
+ "%matplotlib notebook\n",
+ "\n",
+ "# Read in results\n",
+ "## Params - Either specify a file prefix or a whole folder\n",
+ "file_prefix = None # Path(\"./results/2022-07-13T11:04:51_parameval_warp_\")\n",
+ "folder = Path(\"./results/param_study_mdtest\")\n",
+ "\n",
+ "## Plot code\n",
+ "path = file_prefix if file_prefix else folder\n",
+ "\n",
+ "metrics = [\n",
+ " \"dir_create\",\n",
+ " \"dir_stat\",\n",
+ " \"dir_remove\",\n",
+ " \"file_create\",\n",
+ " \"file_stat\",\n",
+ " \"file_read\",\n",
+ " \"file_remove\",\n",
+ " #\"tree_create\",\n",
+ " #\"tree_remove\",\n",
+ "]\n",
+ "dfs = []\n",
+ "\n",
+ "for metric in metrics:\n",
+ " dfs.append(pd.concat({\n",
+ " #\"local\": load_data(path, metric=metric, suffix=\"mdtest_local.json\"),\n",
+ " metric: load_data(path, metric=metric, suffix=\"mdtest_isilon.json\"),\n",
+ " #\"nfs_netapp\": load_data(path, metric=metric, suffix=\"mdtest_netapp.json\"),\n",
+ " }))\n",
+ " dfs[-1].rename(index={10: \"10^1\", \"100\": \"10^2\", \"1000\": \"10^3\", \"10000\": \"10^4\", \"100000\": \"10^5\"}, inplace=True)\n",
+ "\n",
+ " \n",
+ "\n",
+ "fig, axis = plt.subplots(1, 2)\n",
+ "fig.suptitle('Parameter Study: mdtest on nfs_isilon')\n",
+ "\n",
+ "dfs[1][\"mean\"].unstack(level=0).plot(\n",
+ " ax=axis[0],\n",
+ " kind='line', rot=0,\n",
+ " xlabel='num_files',\n",
+ " ylabel='Operations (1/s)',\n",
+ " yerr=dfs[1][\"std\"].unstack(level=0)\n",
+ ")\n",
+ "dfs[4][\"mean\"].unstack(level=0).plot(\n",
+ " ax=axis[0],\n",
+ " kind='line', rot=0,\n",
+ " yerr=dfs[4][\"std\"].unstack(level=0)\n",
+ ")\n",
+ "\n",
+ "dfs[0][\"mean\"].unstack(level=0).plot(\n",
+ " ax=axis[1],\n",
+ " kind='line', rot=0,\n",
+ " xlabel='num_files',\n",
+ " ylabel='Operations (1/s)',\n",
+ " yerr=dfs[0][\"std\"].unstack(level=0)\n",
+ ")\n",
+ "\n",
+ "dfs[2][\"mean\"].unstack(level=0).plot(\n",
+ " ax=axis[1],\n",
+ " kind='line', rot=0,\n",
+ " yerr=dfs[2][\"std\"].unstack(level=0)\n",
+ ")\n",
+ "dfs[3][\"mean\"].unstack(level=0).plot(\n",
+ " ax=axis[1],\n",
+ " kind='line', rot=0,\n",
+ " yerr=dfs[3][\"std\"].unstack(level=0)\n",
+ ")\n",
+ "dfs[5][\"mean\"].unstack(level=0).plot(\n",
+ " ax=axis[1],\n",
+ " kind='line', rot=0,\n",
+ " yerr=dfs[5][\"std\"].unstack(level=0)\n",
+ ")\n",
+ "dfs[6][\"mean\"].unstack(level=0).plot(\n",
+ " ax=axis[1],\n",
+ " kind='line', rot=0,\n",
+ " yerr=dfs[6][\"std\"].unstack(level=0)\n",
+ ")\n",
+ "#dfs[7][\"mean\"].unstack(level=0).plot(\n",
+ "# ax=axis[1],\n",
+ "# kind='line', rot=0,\n",
+ "# yerr=dfs[7][\"std\"].unstack(level=0)\n",
+ "#)\n",
+ "#dfs[8][\"mean\"].unstack(level=0).plot(\n",
+ "# ax=axis[1],\n",
+ "# kind='line', rot=0,\n",
+ "# yerr=dfs[8][\"std\"].unstack(level=0)\n",
+ "#)\n",
+ "\n",
+ "\n",
+ "for df in dfs:\n",
+ " print(df)\n",
+ "\n",
+ "plt.tight_layout()\n",
+ "plt.savefig(\"paramstudy_mdtest.png\",bbox_inches='tight')\n",
+ "plt.show()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3f5cbb01",
+ "metadata": {},
+ "source": [
+ "# Experiment: Parameter-evaluation: s3-benchmark"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "id": "8ec03515",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "application/javascript": [
+ "/* Put everything inside the global mpl namespace */\n",
+ "/* global mpl */\n",
+ "window.mpl = {};\n",
+ "\n",
+ "mpl.get_websocket_type = function () {\n",
+ " if (typeof WebSocket !== 'undefined') {\n",
+ " return WebSocket;\n",
+ " } else if (typeof MozWebSocket !== 'undefined') {\n",
+ " return MozWebSocket;\n",
+ " } else {\n",
+ " alert(\n",
+ " 'Your browser does not have WebSocket support. ' +\n",
+ " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
+ " 'Firefox 4 and 5 are also supported but you ' +\n",
+ " 'have to enable WebSockets in about:config.'\n",
+ " );\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n",
+ " this.id = figure_id;\n",
+ "\n",
+ " this.ws = websocket;\n",
+ "\n",
+ " this.supports_binary = this.ws.binaryType !== undefined;\n",
+ "\n",
+ " if (!this.supports_binary) {\n",
+ " var warnings = document.getElementById('mpl-warnings');\n",
+ " if (warnings) {\n",
+ " warnings.style.display = 'block';\n",
+ " warnings.textContent =\n",
+ " 'This browser does not support binary websocket messages. ' +\n",
+ " 'Performance may be slow.';\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " this.imageObj = new Image();\n",
+ "\n",
+ " this.context = undefined;\n",
+ " this.message = undefined;\n",
+ " this.canvas = undefined;\n",
+ " this.rubberband_canvas = undefined;\n",
+ " this.rubberband_context = undefined;\n",
+ " this.format_dropdown = undefined;\n",
+ "\n",
+ " this.image_mode = 'full';\n",
+ "\n",
+ " this.root = document.createElement('div');\n",
+ " this.root.setAttribute('style', 'display: inline-block');\n",
+ " this._root_extra_style(this.root);\n",
+ "\n",
+ " parent_element.appendChild(this.root);\n",
+ "\n",
+ " this._init_header(this);\n",
+ " this._init_canvas(this);\n",
+ " this._init_toolbar(this);\n",
+ "\n",
+ " var fig = this;\n",
+ "\n",
+ " this.waiting = false;\n",
+ "\n",
+ " this.ws.onopen = function () {\n",
+ " fig.send_message('supports_binary', { value: fig.supports_binary });\n",
+ " fig.send_message('send_image_mode', {});\n",
+ " if (fig.ratio !== 1) {\n",
+ " fig.send_message('set_device_pixel_ratio', {\n",
+ " device_pixel_ratio: fig.ratio,\n",
+ " });\n",
+ " }\n",
+ " fig.send_message('refresh', {});\n",
+ " };\n",
+ "\n",
+ " this.imageObj.onload = function () {\n",
+ " if (fig.image_mode === 'full') {\n",
+ " // Full images could contain transparency (where diff images\n",
+ " // almost always do), so we need to clear the canvas so that\n",
+ " // there is no ghosting.\n",
+ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
+ " }\n",
+ " fig.context.drawImage(fig.imageObj, 0, 0);\n",
+ " };\n",
+ "\n",
+ " this.imageObj.onunload = function () {\n",
+ " fig.ws.close();\n",
+ " };\n",
+ "\n",
+ " this.ws.onmessage = this._make_on_message_function(this);\n",
+ "\n",
+ " this.ondownload = ondownload;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_header = function () {\n",
+ " var titlebar = document.createElement('div');\n",
+ " titlebar.classList =\n",
+ " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n",
+ " var titletext = document.createElement('div');\n",
+ " titletext.classList = 'ui-dialog-title';\n",
+ " titletext.setAttribute(\n",
+ " 'style',\n",
+ " 'width: 100%; text-align: center; padding: 3px;'\n",
+ " );\n",
+ " titlebar.appendChild(titletext);\n",
+ " this.root.appendChild(titlebar);\n",
+ " this.header = titletext;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n",
+ "\n",
+ "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n",
+ "\n",
+ "mpl.figure.prototype._init_canvas = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var canvas_div = (this.canvas_div = document.createElement('div'));\n",
+ " canvas_div.setAttribute(\n",
+ " 'style',\n",
+ " 'border: 1px solid #ddd;' +\n",
+ " 'box-sizing: content-box;' +\n",
+ " 'clear: both;' +\n",
+ " 'min-height: 1px;' +\n",
+ " 'min-width: 1px;' +\n",
+ " 'outline: 0;' +\n",
+ " 'overflow: hidden;' +\n",
+ " 'position: relative;' +\n",
+ " 'resize: both;'\n",
+ " );\n",
+ "\n",
+ " function on_keyboard_event_closure(name) {\n",
+ " return function (event) {\n",
+ " return fig.key_event(event, name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " canvas_div.addEventListener(\n",
+ " 'keydown',\n",
+ " on_keyboard_event_closure('key_press')\n",
+ " );\n",
+ " canvas_div.addEventListener(\n",
+ " 'keyup',\n",
+ " on_keyboard_event_closure('key_release')\n",
+ " );\n",
+ "\n",
+ " this._canvas_extra_style(canvas_div);\n",
+ " this.root.appendChild(canvas_div);\n",
+ "\n",
+ " var canvas = (this.canvas = document.createElement('canvas'));\n",
+ " canvas.classList.add('mpl-canvas');\n",
+ " canvas.setAttribute('style', 'box-sizing: content-box;');\n",
+ "\n",
+ " this.context = canvas.getContext('2d');\n",
+ "\n",
+ " var backingStore =\n",
+ " this.context.backingStorePixelRatio ||\n",
+ " this.context.webkitBackingStorePixelRatio ||\n",
+ " this.context.mozBackingStorePixelRatio ||\n",
+ " this.context.msBackingStorePixelRatio ||\n",
+ " this.context.oBackingStorePixelRatio ||\n",
+ " this.context.backingStorePixelRatio ||\n",
+ " 1;\n",
+ "\n",
+ " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
+ "\n",
+ " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n",
+ " 'canvas'\n",
+ " ));\n",
+ " rubberband_canvas.setAttribute(\n",
+ " 'style',\n",
+ " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n",
+ " );\n",
+ "\n",
+ " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n",
+ " if (this.ResizeObserver === undefined) {\n",
+ " if (window.ResizeObserver !== undefined) {\n",
+ " this.ResizeObserver = window.ResizeObserver;\n",
+ " } else {\n",
+ " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n",
+ " this.ResizeObserver = obs.ResizeObserver;\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n",
+ " var nentries = entries.length;\n",
+ " for (var i = 0; i < nentries; i++) {\n",
+ " var entry = entries[i];\n",
+ " var width, height;\n",
+ " if (entry.contentBoxSize) {\n",
+ " if (entry.contentBoxSize instanceof Array) {\n",
+ " // Chrome 84 implements new version of spec.\n",
+ " width = entry.contentBoxSize[0].inlineSize;\n",
+ " height = entry.contentBoxSize[0].blockSize;\n",
+ " } else {\n",
+ " // Firefox implements old version of spec.\n",
+ " width = entry.contentBoxSize.inlineSize;\n",
+ " height = entry.contentBoxSize.blockSize;\n",
+ " }\n",
+ " } else {\n",
+ " // Chrome <84 implements even older version of spec.\n",
+ " width = entry.contentRect.width;\n",
+ " height = entry.contentRect.height;\n",
+ " }\n",
+ "\n",
+ " // Keep the size of the canvas and rubber band canvas in sync with\n",
+ " // the canvas container.\n",
+ " if (entry.devicePixelContentBoxSize) {\n",
+ " // Chrome 84 implements new version of spec.\n",
+ " canvas.setAttribute(\n",
+ " 'width',\n",
+ " entry.devicePixelContentBoxSize[0].inlineSize\n",
+ " );\n",
+ " canvas.setAttribute(\n",
+ " 'height',\n",
+ " entry.devicePixelContentBoxSize[0].blockSize\n",
+ " );\n",
+ " } else {\n",
+ " canvas.setAttribute('width', width * fig.ratio);\n",
+ " canvas.setAttribute('height', height * fig.ratio);\n",
+ " }\n",
+ " canvas.setAttribute(\n",
+ " 'style',\n",
+ " 'width: ' + width + 'px; height: ' + height + 'px;'\n",
+ " );\n",
+ "\n",
+ " rubberband_canvas.setAttribute('width', width);\n",
+ " rubberband_canvas.setAttribute('height', height);\n",
+ "\n",
+ " // And update the size in Python. We ignore the initial 0/0 size\n",
+ " // that occurs as the element is placed into the DOM, which should\n",
+ " // otherwise not happen due to the minimum size styling.\n",
+ " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n",
+ " fig.request_resize(width, height);\n",
+ " }\n",
+ " }\n",
+ " });\n",
+ " this.resizeObserverInstance.observe(canvas_div);\n",
+ "\n",
+ " function on_mouse_event_closure(name) {\n",
+ " return function (event) {\n",
+ " return fig.mouse_event(event, name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mousedown',\n",
+ " on_mouse_event_closure('button_press')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseup',\n",
+ " on_mouse_event_closure('button_release')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'dblclick',\n",
+ " on_mouse_event_closure('dblclick')\n",
+ " );\n",
+ " // Throttle sequential mouse events to 1 every 20ms.\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mousemove',\n",
+ " on_mouse_event_closure('motion_notify')\n",
+ " );\n",
+ "\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseenter',\n",
+ " on_mouse_event_closure('figure_enter')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseleave',\n",
+ " on_mouse_event_closure('figure_leave')\n",
+ " );\n",
+ "\n",
+ " canvas_div.addEventListener('wheel', function (event) {\n",
+ " if (event.deltaY < 0) {\n",
+ " event.step = 1;\n",
+ " } else {\n",
+ " event.step = -1;\n",
+ " }\n",
+ " on_mouse_event_closure('scroll')(event);\n",
+ " });\n",
+ "\n",
+ " canvas_div.appendChild(canvas);\n",
+ " canvas_div.appendChild(rubberband_canvas);\n",
+ "\n",
+ " this.rubberband_context = rubberband_canvas.getContext('2d');\n",
+ " this.rubberband_context.strokeStyle = '#000000';\n",
+ "\n",
+ " this._resize_canvas = function (width, height, forward) {\n",
+ " if (forward) {\n",
+ " canvas_div.style.width = width + 'px';\n",
+ " canvas_div.style.height = height + 'px';\n",
+ " }\n",
+ " };\n",
+ "\n",
+ " // Disable right mouse context menu.\n",
+ " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n",
+ " event.preventDefault();\n",
+ " return false;\n",
+ " });\n",
+ "\n",
+ " function set_focus() {\n",
+ " canvas.focus();\n",
+ " canvas_div.focus();\n",
+ " }\n",
+ "\n",
+ " window.setTimeout(set_focus, 100);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_toolbar = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var toolbar = document.createElement('div');\n",
+ " toolbar.classList = 'mpl-toolbar';\n",
+ " this.root.appendChild(toolbar);\n",
+ "\n",
+ " function on_click_closure(name) {\n",
+ " return function (_event) {\n",
+ " return fig.toolbar_button_onclick(name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " function on_mouseover_closure(tooltip) {\n",
+ " return function (event) {\n",
+ " if (!event.currentTarget.disabled) {\n",
+ " return fig.toolbar_button_onmouseover(tooltip);\n",
+ " }\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " fig.buttons = {};\n",
+ " var buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'mpl-button-group';\n",
+ " for (var toolbar_ind in mpl.toolbar_items) {\n",
+ " var name = mpl.toolbar_items[toolbar_ind][0];\n",
+ " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+ " var image = mpl.toolbar_items[toolbar_ind][2];\n",
+ " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+ "\n",
+ " if (!name) {\n",
+ " /* Instead of a spacer, we start a new button group. */\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ " buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'mpl-button-group';\n",
+ " continue;\n",
+ " }\n",
+ "\n",
+ " var button = (fig.buttons[name] = document.createElement('button'));\n",
+ " button.classList = 'mpl-widget';\n",
+ " button.setAttribute('role', 'button');\n",
+ " button.setAttribute('aria-disabled', 'false');\n",
+ " button.addEventListener('click', on_click_closure(method_name));\n",
+ " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+ "\n",
+ " var icon_img = document.createElement('img');\n",
+ " icon_img.src = '_images/' + image + '.png';\n",
+ " icon_img.srcset = '_images/' + image + '_large.png 2x';\n",
+ " icon_img.alt = tooltip;\n",
+ " button.appendChild(icon_img);\n",
+ "\n",
+ " buttonGroup.appendChild(button);\n",
+ " }\n",
+ "\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ "\n",
+ " var fmt_picker = document.createElement('select');\n",
+ " fmt_picker.classList = 'mpl-widget';\n",
+ " toolbar.appendChild(fmt_picker);\n",
+ " this.format_dropdown = fmt_picker;\n",
+ "\n",
+ " for (var ind in mpl.extensions) {\n",
+ " var fmt = mpl.extensions[ind];\n",
+ " var option = document.createElement('option');\n",
+ " option.selected = fmt === mpl.default_extension;\n",
+ " option.innerHTML = fmt;\n",
+ " fmt_picker.appendChild(option);\n",
+ " }\n",
+ "\n",
+ " var status_bar = document.createElement('span');\n",
+ " status_bar.classList = 'mpl-message';\n",
+ " toolbar.appendChild(status_bar);\n",
+ " this.message = status_bar;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n",
+ " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
+ " // which will in turn request a refresh of the image.\n",
+ " this.send_message('resize', { width: x_pixels, height: y_pixels });\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.send_message = function (type, properties) {\n",
+ " properties['type'] = type;\n",
+ " properties['figure_id'] = this.id;\n",
+ " this.ws.send(JSON.stringify(properties));\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.send_draw_message = function () {\n",
+ " if (!this.waiting) {\n",
+ " this.waiting = true;\n",
+ " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+ " var format_dropdown = fig.format_dropdown;\n",
+ " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
+ " fig.ondownload(fig, format);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_resize = function (fig, msg) {\n",
+ " var size = msg['size'];\n",
+ " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n",
+ " fig._resize_canvas(size[0], size[1], msg['forward']);\n",
+ " fig.send_message('refresh', {});\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n",
+ " var x0 = msg['x0'] / fig.ratio;\n",
+ " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n",
+ " var x1 = msg['x1'] / fig.ratio;\n",
+ " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n",
+ " x0 = Math.floor(x0) + 0.5;\n",
+ " y0 = Math.floor(y0) + 0.5;\n",
+ " x1 = Math.floor(x1) + 0.5;\n",
+ " y1 = Math.floor(y1) + 0.5;\n",
+ " var min_x = Math.min(x0, x1);\n",
+ " var min_y = Math.min(y0, y1);\n",
+ " var width = Math.abs(x1 - x0);\n",
+ " var height = Math.abs(y1 - y0);\n",
+ "\n",
+ " fig.rubberband_context.clearRect(\n",
+ " 0,\n",
+ " 0,\n",
+ " fig.canvas.width / fig.ratio,\n",
+ " fig.canvas.height / fig.ratio\n",
+ " );\n",
+ "\n",
+ " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n",
+ " // Updates the figure title.\n",
+ " fig.header.textContent = msg['label'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n",
+ " fig.rubberband_canvas.style.cursor = msg['cursor'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_message = function (fig, msg) {\n",
+ " fig.message.textContent = msg['message'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n",
+ " // Request the server to send over a new figure.\n",
+ " fig.send_draw_message();\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n",
+ " fig.image_mode = msg['mode'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n",
+ " for (var key in msg) {\n",
+ " if (!(key in fig.buttons)) {\n",
+ " continue;\n",
+ " }\n",
+ " fig.buttons[key].disabled = !msg[key];\n",
+ " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n",
+ " if (msg['mode'] === 'PAN') {\n",
+ " fig.buttons['Pan'].classList.add('active');\n",
+ " fig.buttons['Zoom'].classList.remove('active');\n",
+ " } else if (msg['mode'] === 'ZOOM') {\n",
+ " fig.buttons['Pan'].classList.remove('active');\n",
+ " fig.buttons['Zoom'].classList.add('active');\n",
+ " } else {\n",
+ " fig.buttons['Pan'].classList.remove('active');\n",
+ " fig.buttons['Zoom'].classList.remove('active');\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.updated_canvas_event = function () {\n",
+ " // Called whenever the canvas gets updated.\n",
+ " this.send_message('ack', {});\n",
+ "};\n",
+ "\n",
+ "// A function to construct a web socket function for onmessage handling.\n",
+ "// Called in the figure constructor.\n",
+ "mpl.figure.prototype._make_on_message_function = function (fig) {\n",
+ " return function socket_on_message(evt) {\n",
+ " if (evt.data instanceof Blob) {\n",
+ " var img = evt.data;\n",
+ " if (img.type !== 'image/png') {\n",
+ " /* FIXME: We get \"Resource interpreted as Image but\n",
+ " * transferred with MIME type text/plain:\" errors on\n",
+ " * Chrome. But how to set the MIME type? It doesn't seem\n",
+ " * to be part of the websocket stream */\n",
+ " img.type = 'image/png';\n",
+ " }\n",
+ "\n",
+ " /* Free the memory for the previous frames */\n",
+ " if (fig.imageObj.src) {\n",
+ " (window.URL || window.webkitURL).revokeObjectURL(\n",
+ " fig.imageObj.src\n",
+ " );\n",
+ " }\n",
+ "\n",
+ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
+ " img\n",
+ " );\n",
+ " fig.updated_canvas_event();\n",
+ " fig.waiting = false;\n",
+ " return;\n",
+ " } else if (\n",
+ " typeof evt.data === 'string' &&\n",
+ " evt.data.slice(0, 21) === 'data:image/png;base64'\n",
+ " ) {\n",
+ " fig.imageObj.src = evt.data;\n",
+ " fig.updated_canvas_event();\n",
+ " fig.waiting = false;\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " var msg = JSON.parse(evt.data);\n",
+ " var msg_type = msg['type'];\n",
+ "\n",
+ " // Call the \"handle_{type}\" callback, which takes\n",
+ " // the figure and JSON message as its only arguments.\n",
+ " try {\n",
+ " var callback = fig['handle_' + msg_type];\n",
+ " } catch (e) {\n",
+ " console.log(\n",
+ " \"No handler for the '\" + msg_type + \"' message type: \",\n",
+ " msg\n",
+ " );\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " if (callback) {\n",
+ " try {\n",
+ " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
+ " callback(fig, msg);\n",
+ " } catch (e) {\n",
+ " console.log(\n",
+ " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n",
+ " e,\n",
+ " e.stack,\n",
+ " msg\n",
+ " );\n",
+ " }\n",
+ " }\n",
+ " };\n",
+ "};\n",
+ "\n",
+ "// from https://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
+ "mpl.findpos = function (e) {\n",
+ " //this section is from http://www.quirksmode.org/js/events_properties.html\n",
+ " var targ;\n",
+ " if (!e) {\n",
+ " e = window.event;\n",
+ " }\n",
+ " if (e.target) {\n",
+ " targ = e.target;\n",
+ " } else if (e.srcElement) {\n",
+ " targ = e.srcElement;\n",
+ " }\n",
+ " if (targ.nodeType === 3) {\n",
+ " // defeat Safari bug\n",
+ " targ = targ.parentNode;\n",
+ " }\n",
+ "\n",
+ " // pageX,Y are the mouse positions relative to the document\n",
+ " var boundingRect = targ.getBoundingClientRect();\n",
+ " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n",
+ " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n",
+ "\n",
+ " return { x: x, y: y };\n",
+ "};\n",
+ "\n",
+ "/*\n",
+ " * return a copy of an object with only non-object keys\n",
+ " * we need this to avoid circular references\n",
+ " * https://stackoverflow.com/a/24161582/3208463\n",
+ " */\n",
+ "function simpleKeys(original) {\n",
+ " return Object.keys(original).reduce(function (obj, key) {\n",
+ " if (typeof original[key] !== 'object') {\n",
+ " obj[key] = original[key];\n",
+ " }\n",
+ " return obj;\n",
+ " }, {});\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.mouse_event = function (event, name) {\n",
+ " var canvas_pos = mpl.findpos(event);\n",
+ "\n",
+ " if (name === 'button_press') {\n",
+ " this.canvas.focus();\n",
+ " this.canvas_div.focus();\n",
+ " }\n",
+ "\n",
+ " var x = canvas_pos.x * this.ratio;\n",
+ " var y = canvas_pos.y * this.ratio;\n",
+ "\n",
+ " this.send_message(name, {\n",
+ " x: x,\n",
+ " y: y,\n",
+ " button: event.button,\n",
+ " step: event.step,\n",
+ " guiEvent: simpleKeys(event),\n",
+ " });\n",
+ "\n",
+ " /* This prevents the web browser from automatically changing to\n",
+ " * the text insertion cursor when the button is pressed. We want\n",
+ " * to control all of the cursor setting manually through the\n",
+ " * 'cursor' event from matplotlib */\n",
+ " event.preventDefault();\n",
+ " return false;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n",
+ " // Handle any extra behaviour associated with a key event\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.key_event = function (event, name) {\n",
+ " // Prevent repeat events\n",
+ " if (name === 'key_press') {\n",
+ " if (event.key === this._key) {\n",
+ " return;\n",
+ " } else {\n",
+ " this._key = event.key;\n",
+ " }\n",
+ " }\n",
+ " if (name === 'key_release') {\n",
+ " this._key = null;\n",
+ " }\n",
+ "\n",
+ " var value = '';\n",
+ " if (event.ctrlKey && event.key !== 'Control') {\n",
+ " value += 'ctrl+';\n",
+ " }\n",
+ " else if (event.altKey && event.key !== 'Alt') {\n",
+ " value += 'alt+';\n",
+ " }\n",
+ " else if (event.shiftKey && event.key !== 'Shift') {\n",
+ " value += 'shift+';\n",
+ " }\n",
+ "\n",
+ " value += 'k' + event.key;\n",
+ "\n",
+ " this._key_event_extra(event, name);\n",
+ "\n",
+ " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n",
+ " return false;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n",
+ " if (name === 'download') {\n",
+ " this.handle_save(this, null);\n",
+ " } else {\n",
+ " this.send_message('toolbar_button', { name: name });\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n",
+ " this.message.textContent = tooltip;\n",
+ "};\n",
+ "\n",
+ "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n",
+ "// prettier-ignore\n",
+ "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n",
+ "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
+ "\n",
+ "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
+ "\n",
+ "mpl.default_extension = \"png\";/* global mpl */\n",
+ "\n",
+ "var comm_websocket_adapter = function (comm) {\n",
+ " // Create a \"websocket\"-like object which calls the given IPython comm\n",
+ " // object with the appropriate methods. Currently this is a non binary\n",
+ " // socket, so there is still some room for performance tuning.\n",
+ " var ws = {};\n",
+ "\n",
+ " ws.binaryType = comm.kernel.ws.binaryType;\n",
+ " ws.readyState = comm.kernel.ws.readyState;\n",
+ " function updateReadyState(_event) {\n",
+ " if (comm.kernel.ws) {\n",
+ " ws.readyState = comm.kernel.ws.readyState;\n",
+ " } else {\n",
+ " ws.readyState = 3; // Closed state.\n",
+ " }\n",
+ " }\n",
+ " comm.kernel.ws.addEventListener('open', updateReadyState);\n",
+ " comm.kernel.ws.addEventListener('close', updateReadyState);\n",
+ " comm.kernel.ws.addEventListener('error', updateReadyState);\n",
+ "\n",
+ " ws.close = function () {\n",
+ " comm.close();\n",
+ " };\n",
+ " ws.send = function (m) {\n",
+ " //console.log('sending', m);\n",
+ " comm.send(m);\n",
+ " };\n",
+ " // Register the callback with on_msg.\n",
+ " comm.on_msg(function (msg) {\n",
+ " //console.log('receiving', msg['content']['data'], msg);\n",
+ " var data = msg['content']['data'];\n",
+ " if (data['blob'] !== undefined) {\n",
+ " data = {\n",
+ " data: new Blob(msg['buffers'], { type: data['blob'] }),\n",
+ " };\n",
+ " }\n",
+ " // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
+ " ws.onmessage(data);\n",
+ " });\n",
+ " return ws;\n",
+ "};\n",
+ "\n",
+ "mpl.mpl_figure_comm = function (comm, msg) {\n",
+ " // This is the function which gets called when the mpl process\n",
+ " // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
+ "\n",
+ " var id = msg.content.data.id;\n",
+ " // Get hold of the div created by the display call when the Comm\n",
+ " // socket was opened in Python.\n",
+ " var element = document.getElementById(id);\n",
+ " var ws_proxy = comm_websocket_adapter(comm);\n",
+ "\n",
+ " function ondownload(figure, _format) {\n",
+ " window.open(figure.canvas.toDataURL());\n",
+ " }\n",
+ "\n",
+ " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n",
+ "\n",
+ " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
+ " // web socket which is closed, not our websocket->open comm proxy.\n",
+ " ws_proxy.onopen();\n",
+ "\n",
+ " fig.parent_element = element;\n",
+ " fig.cell_info = mpl.find_output_cell(\"\");\n",
+ " if (!fig.cell_info) {\n",
+ " console.error('Failed to find cell for figure', id, fig);\n",
+ " return;\n",
+ " }\n",
+ " fig.cell_info[0].output_area.element.on(\n",
+ " 'cleared',\n",
+ " { fig: fig },\n",
+ " fig._remove_fig_handler\n",
+ " );\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_close = function (fig, msg) {\n",
+ " var width = fig.canvas.width / fig.ratio;\n",
+ " fig.cell_info[0].output_area.element.off(\n",
+ " 'cleared',\n",
+ " fig._remove_fig_handler\n",
+ " );\n",
+ " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n",
+ "\n",
+ " // Update the output cell to use the data from the current canvas.\n",
+ " fig.push_to_output();\n",
+ " var dataURL = fig.canvas.toDataURL();\n",
+ " // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
+ " // the notebook keyboard shortcuts fail.\n",
+ " IPython.keyboard_manager.enable();\n",
+ " fig.parent_element.innerHTML =\n",
+ " '
';\n",
+ " fig.close_ws(fig, msg);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.close_ws = function (fig, msg) {\n",
+ " fig.send_message('closing', msg);\n",
+ " // fig.ws.close()\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n",
+ " // Turn the data on the canvas into data in the output cell.\n",
+ " var width = this.canvas.width / this.ratio;\n",
+ " var dataURL = this.canvas.toDataURL();\n",
+ " this.cell_info[1]['text/html'] =\n",
+ " '
';\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.updated_canvas_event = function () {\n",
+ " // Tell IPython that the notebook contents must change.\n",
+ " IPython.notebook.set_dirty(true);\n",
+ " this.send_message('ack', {});\n",
+ " var fig = this;\n",
+ " // Wait a second, then push the new image to the DOM so\n",
+ " // that it is saved nicely (might be nice to debounce this).\n",
+ " setTimeout(function () {\n",
+ " fig.push_to_output();\n",
+ " }, 1000);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_toolbar = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var toolbar = document.createElement('div');\n",
+ " toolbar.classList = 'btn-toolbar';\n",
+ " this.root.appendChild(toolbar);\n",
+ "\n",
+ " function on_click_closure(name) {\n",
+ " return function (_event) {\n",
+ " return fig.toolbar_button_onclick(name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " function on_mouseover_closure(tooltip) {\n",
+ " return function (event) {\n",
+ " if (!event.currentTarget.disabled) {\n",
+ " return fig.toolbar_button_onmouseover(tooltip);\n",
+ " }\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " fig.buttons = {};\n",
+ " var buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'btn-group';\n",
+ " var button;\n",
+ " for (var toolbar_ind in mpl.toolbar_items) {\n",
+ " var name = mpl.toolbar_items[toolbar_ind][0];\n",
+ " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+ " var image = mpl.toolbar_items[toolbar_ind][2];\n",
+ " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+ "\n",
+ " if (!name) {\n",
+ " /* Instead of a spacer, we start a new button group. */\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ " buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'btn-group';\n",
+ " continue;\n",
+ " }\n",
+ "\n",
+ " button = fig.buttons[name] = document.createElement('button');\n",
+ " button.classList = 'btn btn-default';\n",
+ " button.href = '#';\n",
+ " button.title = name;\n",
+ " button.innerHTML = '';\n",
+ " button.addEventListener('click', on_click_closure(method_name));\n",
+ " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+ " buttonGroup.appendChild(button);\n",
+ " }\n",
+ "\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ "\n",
+ " // Add the status bar.\n",
+ " var status_bar = document.createElement('span');\n",
+ " status_bar.classList = 'mpl-message pull-right';\n",
+ " toolbar.appendChild(status_bar);\n",
+ " this.message = status_bar;\n",
+ "\n",
+ " // Add the close button to the window.\n",
+ " var buttongrp = document.createElement('div');\n",
+ " buttongrp.classList = 'btn-group inline pull-right';\n",
+ " button = document.createElement('button');\n",
+ " button.classList = 'btn btn-mini btn-primary';\n",
+ " button.href = '#';\n",
+ " button.title = 'Stop Interaction';\n",
+ " button.innerHTML = '';\n",
+ " button.addEventListener('click', function (_evt) {\n",
+ " fig.handle_close(fig, {});\n",
+ " });\n",
+ " button.addEventListener(\n",
+ " 'mouseover',\n",
+ " on_mouseover_closure('Stop Interaction')\n",
+ " );\n",
+ " buttongrp.appendChild(button);\n",
+ " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n",
+ " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._remove_fig_handler = function (event) {\n",
+ " var fig = event.data.fig;\n",
+ " if (event.target !== this) {\n",
+ " // Ignore bubbled events from children.\n",
+ " return;\n",
+ " }\n",
+ " fig.close_ws(fig, {});\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._root_extra_style = function (el) {\n",
+ " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._canvas_extra_style = function (el) {\n",
+ " // this is important to make the div 'focusable\n",
+ " el.setAttribute('tabindex', 0);\n",
+ " // reach out to IPython and tell the keyboard manager to turn it's self\n",
+ " // off when our div gets focus\n",
+ "\n",
+ " // location in version 3\n",
+ " if (IPython.notebook.keyboard_manager) {\n",
+ " IPython.notebook.keyboard_manager.register_events(el);\n",
+ " } else {\n",
+ " // location in version 2\n",
+ " IPython.keyboard_manager.register_events(el);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._key_event_extra = function (event, _name) {\n",
+ " // Check for shift+enter\n",
+ " if (event.shiftKey && event.which === 13) {\n",
+ " this.canvas_div.blur();\n",
+ " // select the cell after this one\n",
+ " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n",
+ " IPython.notebook.select(index + 1);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+ " fig.ondownload(fig, null);\n",
+ "};\n",
+ "\n",
+ "mpl.find_output_cell = function (html_output) {\n",
+ " // Return the cell and output element which can be found *uniquely* in the notebook.\n",
+ " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
+ " // IPython event is triggered only after the cells have been serialised, which for\n",
+ " // our purposes (turning an active figure into a static one), is too late.\n",
+ " var cells = IPython.notebook.get_cells();\n",
+ " var ncells = cells.length;\n",
+ " for (var i = 0; i < ncells; i++) {\n",
+ " var cell = cells[i];\n",
+ " if (cell.cell_type === 'code') {\n",
+ " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n",
+ " var data = cell.output_area.outputs[j];\n",
+ " if (data.data) {\n",
+ " // IPython >= 3 moved mimebundle to data attribute of output\n",
+ " data = data.data;\n",
+ " }\n",
+ " if (data['text/html'] === html_output) {\n",
+ " return [cell, data, j];\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "// Register the function which deals with the matplotlib target/channel.\n",
+ "// The kernel may be null if the page has been refreshed.\n",
+ "if (IPython.notebook.kernel !== null) {\n",
+ " IPython.notebook.kernel.comm_manager.register_target(\n",
+ " 'matplotlib',\n",
+ " mpl.mpl_figure_comm\n",
+ " );\n",
+ "}\n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "
"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "%matplotlib notebook\n",
+ "\n",
+ "# Read in results\n",
+ "## Params - Either specify a file prefix or a whole folder\n",
+ "file_prefix = None # Path(\"./results/2022-07-17T20:00:04_parameval_s3benchmark_\")\n",
+ "folder = Path(\"./results/param_study_s3benchmark\")\n",
+ "\n",
+ "## Plot code\n",
+ "path = file_prefix if file_prefix else folder\n",
+ "\n",
+ "metric = \"put_bw_in_mibi\"\n",
+ "df_1 = load_data(path, suffix=\"runtime.json\", metric=metric)\n",
+ "df_2 = load_data(path, suffix=\"threads.json\", metric=metric, x_labels=[\"1\", \"2\", \"4\", \"8\", \"16\", \"32\", \"64\", \"128\"])\n",
+ "\n",
+ "fig, axis = plt.subplots(2, 1)\n",
+ "fig.suptitle('Parameter Study: s3-benchmark')\n",
+ "\n",
+ " \n",
+ "df_1[\"mean\"].plot(\n",
+ " ax=axis[0],\n",
+ " kind='bar', rot=0,\n",
+ " xlabel='Runtime in s',\n",
+ " ylabel='Bandwidth (MiB/s)',\n",
+ " title='Variable Runtime',\n",
+ " yerr=df_1[\"std\"]\n",
+ ")\n",
+ "df_2[\"mean\"].plot(\n",
+ " ax=axis[1],\n",
+ " kind='bar', rot=0,\n",
+ " xlabel='Threads',\n",
+ " ylabel='Bandwidth (MiB/s)',\n",
+ " title='Variable Number of Threads',\n",
+ " yerr=df_2[\"std\"]\n",
+ ")\n",
+ "\n",
+ "plt.tight_layout()\n",
+ "plt.savefig(\"paramstudy_s3benchmark.png\",bbox_inches='tight')\n",
+ "plt.show()\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ab9fc54e",
+ "metadata": {},
+ "source": [
+ "# Experiment: Parameter-evaluation: Warp"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "id": "c3c99db2",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "application/javascript": [
+ "/* Put everything inside the global mpl namespace */\n",
+ "/* global mpl */\n",
+ "window.mpl = {};\n",
+ "\n",
+ "mpl.get_websocket_type = function () {\n",
+ " if (typeof WebSocket !== 'undefined') {\n",
+ " return WebSocket;\n",
+ " } else if (typeof MozWebSocket !== 'undefined') {\n",
+ " return MozWebSocket;\n",
+ " } else {\n",
+ " alert(\n",
+ " 'Your browser does not have WebSocket support. ' +\n",
+ " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
+ " 'Firefox 4 and 5 are also supported but you ' +\n",
+ " 'have to enable WebSockets in about:config.'\n",
+ " );\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n",
+ " this.id = figure_id;\n",
+ "\n",
+ " this.ws = websocket;\n",
+ "\n",
+ " this.supports_binary = this.ws.binaryType !== undefined;\n",
+ "\n",
+ " if (!this.supports_binary) {\n",
+ " var warnings = document.getElementById('mpl-warnings');\n",
+ " if (warnings) {\n",
+ " warnings.style.display = 'block';\n",
+ " warnings.textContent =\n",
+ " 'This browser does not support binary websocket messages. ' +\n",
+ " 'Performance may be slow.';\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " this.imageObj = new Image();\n",
+ "\n",
+ " this.context = undefined;\n",
+ " this.message = undefined;\n",
+ " this.canvas = undefined;\n",
+ " this.rubberband_canvas = undefined;\n",
+ " this.rubberband_context = undefined;\n",
+ " this.format_dropdown = undefined;\n",
+ "\n",
+ " this.image_mode = 'full';\n",
+ "\n",
+ " this.root = document.createElement('div');\n",
+ " this.root.setAttribute('style', 'display: inline-block');\n",
+ " this._root_extra_style(this.root);\n",
+ "\n",
+ " parent_element.appendChild(this.root);\n",
+ "\n",
+ " this._init_header(this);\n",
+ " this._init_canvas(this);\n",
+ " this._init_toolbar(this);\n",
+ "\n",
+ " var fig = this;\n",
+ "\n",
+ " this.waiting = false;\n",
+ "\n",
+ " this.ws.onopen = function () {\n",
+ " fig.send_message('supports_binary', { value: fig.supports_binary });\n",
+ " fig.send_message('send_image_mode', {});\n",
+ " if (fig.ratio !== 1) {\n",
+ " fig.send_message('set_device_pixel_ratio', {\n",
+ " device_pixel_ratio: fig.ratio,\n",
+ " });\n",
+ " }\n",
+ " fig.send_message('refresh', {});\n",
+ " };\n",
+ "\n",
+ " this.imageObj.onload = function () {\n",
+ " if (fig.image_mode === 'full') {\n",
+ " // Full images could contain transparency (where diff images\n",
+ " // almost always do), so we need to clear the canvas so that\n",
+ " // there is no ghosting.\n",
+ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
+ " }\n",
+ " fig.context.drawImage(fig.imageObj, 0, 0);\n",
+ " };\n",
+ "\n",
+ " this.imageObj.onunload = function () {\n",
+ " fig.ws.close();\n",
+ " };\n",
+ "\n",
+ " this.ws.onmessage = this._make_on_message_function(this);\n",
+ "\n",
+ " this.ondownload = ondownload;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_header = function () {\n",
+ " var titlebar = document.createElement('div');\n",
+ " titlebar.classList =\n",
+ " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n",
+ " var titletext = document.createElement('div');\n",
+ " titletext.classList = 'ui-dialog-title';\n",
+ " titletext.setAttribute(\n",
+ " 'style',\n",
+ " 'width: 100%; text-align: center; padding: 3px;'\n",
+ " );\n",
+ " titlebar.appendChild(titletext);\n",
+ " this.root.appendChild(titlebar);\n",
+ " this.header = titletext;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n",
+ "\n",
+ "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n",
+ "\n",
+ "mpl.figure.prototype._init_canvas = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var canvas_div = (this.canvas_div = document.createElement('div'));\n",
+ " canvas_div.setAttribute(\n",
+ " 'style',\n",
+ " 'border: 1px solid #ddd;' +\n",
+ " 'box-sizing: content-box;' +\n",
+ " 'clear: both;' +\n",
+ " 'min-height: 1px;' +\n",
+ " 'min-width: 1px;' +\n",
+ " 'outline: 0;' +\n",
+ " 'overflow: hidden;' +\n",
+ " 'position: relative;' +\n",
+ " 'resize: both;'\n",
+ " );\n",
+ "\n",
+ " function on_keyboard_event_closure(name) {\n",
+ " return function (event) {\n",
+ " return fig.key_event(event, name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " canvas_div.addEventListener(\n",
+ " 'keydown',\n",
+ " on_keyboard_event_closure('key_press')\n",
+ " );\n",
+ " canvas_div.addEventListener(\n",
+ " 'keyup',\n",
+ " on_keyboard_event_closure('key_release')\n",
+ " );\n",
+ "\n",
+ " this._canvas_extra_style(canvas_div);\n",
+ " this.root.appendChild(canvas_div);\n",
+ "\n",
+ " var canvas = (this.canvas = document.createElement('canvas'));\n",
+ " canvas.classList.add('mpl-canvas');\n",
+ " canvas.setAttribute('style', 'box-sizing: content-box;');\n",
+ "\n",
+ " this.context = canvas.getContext('2d');\n",
+ "\n",
+ " var backingStore =\n",
+ " this.context.backingStorePixelRatio ||\n",
+ " this.context.webkitBackingStorePixelRatio ||\n",
+ " this.context.mozBackingStorePixelRatio ||\n",
+ " this.context.msBackingStorePixelRatio ||\n",
+ " this.context.oBackingStorePixelRatio ||\n",
+ " this.context.backingStorePixelRatio ||\n",
+ " 1;\n",
+ "\n",
+ " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
+ "\n",
+ " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n",
+ " 'canvas'\n",
+ " ));\n",
+ " rubberband_canvas.setAttribute(\n",
+ " 'style',\n",
+ " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n",
+ " );\n",
+ "\n",
+ " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n",
+ " if (this.ResizeObserver === undefined) {\n",
+ " if (window.ResizeObserver !== undefined) {\n",
+ " this.ResizeObserver = window.ResizeObserver;\n",
+ " } else {\n",
+ " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n",
+ " this.ResizeObserver = obs.ResizeObserver;\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n",
+ " var nentries = entries.length;\n",
+ " for (var i = 0; i < nentries; i++) {\n",
+ " var entry = entries[i];\n",
+ " var width, height;\n",
+ " if (entry.contentBoxSize) {\n",
+ " if (entry.contentBoxSize instanceof Array) {\n",
+ " // Chrome 84 implements new version of spec.\n",
+ " width = entry.contentBoxSize[0].inlineSize;\n",
+ " height = entry.contentBoxSize[0].blockSize;\n",
+ " } else {\n",
+ " // Firefox implements old version of spec.\n",
+ " width = entry.contentBoxSize.inlineSize;\n",
+ " height = entry.contentBoxSize.blockSize;\n",
+ " }\n",
+ " } else {\n",
+ " // Chrome <84 implements even older version of spec.\n",
+ " width = entry.contentRect.width;\n",
+ " height = entry.contentRect.height;\n",
+ " }\n",
+ "\n",
+ " // Keep the size of the canvas and rubber band canvas in sync with\n",
+ " // the canvas container.\n",
+ " if (entry.devicePixelContentBoxSize) {\n",
+ " // Chrome 84 implements new version of spec.\n",
+ " canvas.setAttribute(\n",
+ " 'width',\n",
+ " entry.devicePixelContentBoxSize[0].inlineSize\n",
+ " );\n",
+ " canvas.setAttribute(\n",
+ " 'height',\n",
+ " entry.devicePixelContentBoxSize[0].blockSize\n",
+ " );\n",
+ " } else {\n",
+ " canvas.setAttribute('width', width * fig.ratio);\n",
+ " canvas.setAttribute('height', height * fig.ratio);\n",
+ " }\n",
+ " canvas.setAttribute(\n",
+ " 'style',\n",
+ " 'width: ' + width + 'px; height: ' + height + 'px;'\n",
+ " );\n",
+ "\n",
+ " rubberband_canvas.setAttribute('width', width);\n",
+ " rubberband_canvas.setAttribute('height', height);\n",
+ "\n",
+ " // And update the size in Python. We ignore the initial 0/0 size\n",
+ " // that occurs as the element is placed into the DOM, which should\n",
+ " // otherwise not happen due to the minimum size styling.\n",
+ " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n",
+ " fig.request_resize(width, height);\n",
+ " }\n",
+ " }\n",
+ " });\n",
+ " this.resizeObserverInstance.observe(canvas_div);\n",
+ "\n",
+ " function on_mouse_event_closure(name) {\n",
+ " return function (event) {\n",
+ " return fig.mouse_event(event, name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mousedown',\n",
+ " on_mouse_event_closure('button_press')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseup',\n",
+ " on_mouse_event_closure('button_release')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'dblclick',\n",
+ " on_mouse_event_closure('dblclick')\n",
+ " );\n",
+ " // Throttle sequential mouse events to 1 every 20ms.\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mousemove',\n",
+ " on_mouse_event_closure('motion_notify')\n",
+ " );\n",
+ "\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseenter',\n",
+ " on_mouse_event_closure('figure_enter')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseleave',\n",
+ " on_mouse_event_closure('figure_leave')\n",
+ " );\n",
+ "\n",
+ " canvas_div.addEventListener('wheel', function (event) {\n",
+ " if (event.deltaY < 0) {\n",
+ " event.step = 1;\n",
+ " } else {\n",
+ " event.step = -1;\n",
+ " }\n",
+ " on_mouse_event_closure('scroll')(event);\n",
+ " });\n",
+ "\n",
+ " canvas_div.appendChild(canvas);\n",
+ " canvas_div.appendChild(rubberband_canvas);\n",
+ "\n",
+ " this.rubberband_context = rubberband_canvas.getContext('2d');\n",
+ " this.rubberband_context.strokeStyle = '#000000';\n",
+ "\n",
+ " this._resize_canvas = function (width, height, forward) {\n",
+ " if (forward) {\n",
+ " canvas_div.style.width = width + 'px';\n",
+ " canvas_div.style.height = height + 'px';\n",
+ " }\n",
+ " };\n",
+ "\n",
+ " // Disable right mouse context menu.\n",
+ " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n",
+ " event.preventDefault();\n",
+ " return false;\n",
+ " });\n",
+ "\n",
+ " function set_focus() {\n",
+ " canvas.focus();\n",
+ " canvas_div.focus();\n",
+ " }\n",
+ "\n",
+ " window.setTimeout(set_focus, 100);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_toolbar = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var toolbar = document.createElement('div');\n",
+ " toolbar.classList = 'mpl-toolbar';\n",
+ " this.root.appendChild(toolbar);\n",
+ "\n",
+ " function on_click_closure(name) {\n",
+ " return function (_event) {\n",
+ " return fig.toolbar_button_onclick(name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " function on_mouseover_closure(tooltip) {\n",
+ " return function (event) {\n",
+ " if (!event.currentTarget.disabled) {\n",
+ " return fig.toolbar_button_onmouseover(tooltip);\n",
+ " }\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " fig.buttons = {};\n",
+ " var buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'mpl-button-group';\n",
+ " for (var toolbar_ind in mpl.toolbar_items) {\n",
+ " var name = mpl.toolbar_items[toolbar_ind][0];\n",
+ " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+ " var image = mpl.toolbar_items[toolbar_ind][2];\n",
+ " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+ "\n",
+ " if (!name) {\n",
+ " /* Instead of a spacer, we start a new button group. */\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ " buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'mpl-button-group';\n",
+ " continue;\n",
+ " }\n",
+ "\n",
+ " var button = (fig.buttons[name] = document.createElement('button'));\n",
+ " button.classList = 'mpl-widget';\n",
+ " button.setAttribute('role', 'button');\n",
+ " button.setAttribute('aria-disabled', 'false');\n",
+ " button.addEventListener('click', on_click_closure(method_name));\n",
+ " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+ "\n",
+ " var icon_img = document.createElement('img');\n",
+ " icon_img.src = '_images/' + image + '.png';\n",
+ " icon_img.srcset = '_images/' + image + '_large.png 2x';\n",
+ " icon_img.alt = tooltip;\n",
+ " button.appendChild(icon_img);\n",
+ "\n",
+ " buttonGroup.appendChild(button);\n",
+ " }\n",
+ "\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ "\n",
+ " var fmt_picker = document.createElement('select');\n",
+ " fmt_picker.classList = 'mpl-widget';\n",
+ " toolbar.appendChild(fmt_picker);\n",
+ " this.format_dropdown = fmt_picker;\n",
+ "\n",
+ " for (var ind in mpl.extensions) {\n",
+ " var fmt = mpl.extensions[ind];\n",
+ " var option = document.createElement('option');\n",
+ " option.selected = fmt === mpl.default_extension;\n",
+ " option.innerHTML = fmt;\n",
+ " fmt_picker.appendChild(option);\n",
+ " }\n",
+ "\n",
+ " var status_bar = document.createElement('span');\n",
+ " status_bar.classList = 'mpl-message';\n",
+ " toolbar.appendChild(status_bar);\n",
+ " this.message = status_bar;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n",
+ " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
+ " // which will in turn request a refresh of the image.\n",
+ " this.send_message('resize', { width: x_pixels, height: y_pixels });\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.send_message = function (type, properties) {\n",
+ " properties['type'] = type;\n",
+ " properties['figure_id'] = this.id;\n",
+ " this.ws.send(JSON.stringify(properties));\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.send_draw_message = function () {\n",
+ " if (!this.waiting) {\n",
+ " this.waiting = true;\n",
+ " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+ " var format_dropdown = fig.format_dropdown;\n",
+ " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
+ " fig.ondownload(fig, format);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_resize = function (fig, msg) {\n",
+ " var size = msg['size'];\n",
+ " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n",
+ " fig._resize_canvas(size[0], size[1], msg['forward']);\n",
+ " fig.send_message('refresh', {});\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n",
+ " var x0 = msg['x0'] / fig.ratio;\n",
+ " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n",
+ " var x1 = msg['x1'] / fig.ratio;\n",
+ " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n",
+ " x0 = Math.floor(x0) + 0.5;\n",
+ " y0 = Math.floor(y0) + 0.5;\n",
+ " x1 = Math.floor(x1) + 0.5;\n",
+ " y1 = Math.floor(y1) + 0.5;\n",
+ " var min_x = Math.min(x0, x1);\n",
+ " var min_y = Math.min(y0, y1);\n",
+ " var width = Math.abs(x1 - x0);\n",
+ " var height = Math.abs(y1 - y0);\n",
+ "\n",
+ " fig.rubberband_context.clearRect(\n",
+ " 0,\n",
+ " 0,\n",
+ " fig.canvas.width / fig.ratio,\n",
+ " fig.canvas.height / fig.ratio\n",
+ " );\n",
+ "\n",
+ " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n",
+ " // Updates the figure title.\n",
+ " fig.header.textContent = msg['label'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n",
+ " fig.rubberband_canvas.style.cursor = msg['cursor'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_message = function (fig, msg) {\n",
+ " fig.message.textContent = msg['message'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n",
+ " // Request the server to send over a new figure.\n",
+ " fig.send_draw_message();\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n",
+ " fig.image_mode = msg['mode'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n",
+ " for (var key in msg) {\n",
+ " if (!(key in fig.buttons)) {\n",
+ " continue;\n",
+ " }\n",
+ " fig.buttons[key].disabled = !msg[key];\n",
+ " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n",
+ " if (msg['mode'] === 'PAN') {\n",
+ " fig.buttons['Pan'].classList.add('active');\n",
+ " fig.buttons['Zoom'].classList.remove('active');\n",
+ " } else if (msg['mode'] === 'ZOOM') {\n",
+ " fig.buttons['Pan'].classList.remove('active');\n",
+ " fig.buttons['Zoom'].classList.add('active');\n",
+ " } else {\n",
+ " fig.buttons['Pan'].classList.remove('active');\n",
+ " fig.buttons['Zoom'].classList.remove('active');\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.updated_canvas_event = function () {\n",
+ " // Called whenever the canvas gets updated.\n",
+ " this.send_message('ack', {});\n",
+ "};\n",
+ "\n",
+ "// A function to construct a web socket function for onmessage handling.\n",
+ "// Called in the figure constructor.\n",
+ "mpl.figure.prototype._make_on_message_function = function (fig) {\n",
+ " return function socket_on_message(evt) {\n",
+ " if (evt.data instanceof Blob) {\n",
+ " var img = evt.data;\n",
+ " if (img.type !== 'image/png') {\n",
+ " /* FIXME: We get \"Resource interpreted as Image but\n",
+ " * transferred with MIME type text/plain:\" errors on\n",
+ " * Chrome. But how to set the MIME type? It doesn't seem\n",
+ " * to be part of the websocket stream */\n",
+ " img.type = 'image/png';\n",
+ " }\n",
+ "\n",
+ " /* Free the memory for the previous frames */\n",
+ " if (fig.imageObj.src) {\n",
+ " (window.URL || window.webkitURL).revokeObjectURL(\n",
+ " fig.imageObj.src\n",
+ " );\n",
+ " }\n",
+ "\n",
+ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
+ " img\n",
+ " );\n",
+ " fig.updated_canvas_event();\n",
+ " fig.waiting = false;\n",
+ " return;\n",
+ " } else if (\n",
+ " typeof evt.data === 'string' &&\n",
+ " evt.data.slice(0, 21) === 'data:image/png;base64'\n",
+ " ) {\n",
+ " fig.imageObj.src = evt.data;\n",
+ " fig.updated_canvas_event();\n",
+ " fig.waiting = false;\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " var msg = JSON.parse(evt.data);\n",
+ " var msg_type = msg['type'];\n",
+ "\n",
+ " // Call the \"handle_{type}\" callback, which takes\n",
+ " // the figure and JSON message as its only arguments.\n",
+ " try {\n",
+ " var callback = fig['handle_' + msg_type];\n",
+ " } catch (e) {\n",
+ " console.log(\n",
+ " \"No handler for the '\" + msg_type + \"' message type: \",\n",
+ " msg\n",
+ " );\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " if (callback) {\n",
+ " try {\n",
+ " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
+ " callback(fig, msg);\n",
+ " } catch (e) {\n",
+ " console.log(\n",
+ " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n",
+ " e,\n",
+ " e.stack,\n",
+ " msg\n",
+ " );\n",
+ " }\n",
+ " }\n",
+ " };\n",
+ "};\n",
+ "\n",
+ "// from https://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
+ "mpl.findpos = function (e) {\n",
+ " //this section is from http://www.quirksmode.org/js/events_properties.html\n",
+ " var targ;\n",
+ " if (!e) {\n",
+ " e = window.event;\n",
+ " }\n",
+ " if (e.target) {\n",
+ " targ = e.target;\n",
+ " } else if (e.srcElement) {\n",
+ " targ = e.srcElement;\n",
+ " }\n",
+ " if (targ.nodeType === 3) {\n",
+ " // defeat Safari bug\n",
+ " targ = targ.parentNode;\n",
+ " }\n",
+ "\n",
+ " // pageX,Y are the mouse positions relative to the document\n",
+ " var boundingRect = targ.getBoundingClientRect();\n",
+ " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n",
+ " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n",
+ "\n",
+ " return { x: x, y: y };\n",
+ "};\n",
+ "\n",
+ "/*\n",
+ " * return a copy of an object with only non-object keys\n",
+ " * we need this to avoid circular references\n",
+ " * https://stackoverflow.com/a/24161582/3208463\n",
+ " */\n",
+ "function simpleKeys(original) {\n",
+ " return Object.keys(original).reduce(function (obj, key) {\n",
+ " if (typeof original[key] !== 'object') {\n",
+ " obj[key] = original[key];\n",
+ " }\n",
+ " return obj;\n",
+ " }, {});\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.mouse_event = function (event, name) {\n",
+ " var canvas_pos = mpl.findpos(event);\n",
+ "\n",
+ " if (name === 'button_press') {\n",
+ " this.canvas.focus();\n",
+ " this.canvas_div.focus();\n",
+ " }\n",
+ "\n",
+ " var x = canvas_pos.x * this.ratio;\n",
+ " var y = canvas_pos.y * this.ratio;\n",
+ "\n",
+ " this.send_message(name, {\n",
+ " x: x,\n",
+ " y: y,\n",
+ " button: event.button,\n",
+ " step: event.step,\n",
+ " guiEvent: simpleKeys(event),\n",
+ " });\n",
+ "\n",
+ " /* This prevents the web browser from automatically changing to\n",
+ " * the text insertion cursor when the button is pressed. We want\n",
+ " * to control all of the cursor setting manually through the\n",
+ " * 'cursor' event from matplotlib */\n",
+ " event.preventDefault();\n",
+ " return false;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n",
+ " // Handle any extra behaviour associated with a key event\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.key_event = function (event, name) {\n",
+ " // Prevent repeat events\n",
+ " if (name === 'key_press') {\n",
+ " if (event.key === this._key) {\n",
+ " return;\n",
+ " } else {\n",
+ " this._key = event.key;\n",
+ " }\n",
+ " }\n",
+ " if (name === 'key_release') {\n",
+ " this._key = null;\n",
+ " }\n",
+ "\n",
+ " var value = '';\n",
+ " if (event.ctrlKey && event.key !== 'Control') {\n",
+ " value += 'ctrl+';\n",
+ " }\n",
+ " else if (event.altKey && event.key !== 'Alt') {\n",
+ " value += 'alt+';\n",
+ " }\n",
+ " else if (event.shiftKey && event.key !== 'Shift') {\n",
+ " value += 'shift+';\n",
+ " }\n",
+ "\n",
+ " value += 'k' + event.key;\n",
+ "\n",
+ " this._key_event_extra(event, name);\n",
+ "\n",
+ " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n",
+ " return false;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n",
+ " if (name === 'download') {\n",
+ " this.handle_save(this, null);\n",
+ " } else {\n",
+ " this.send_message('toolbar_button', { name: name });\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n",
+ " this.message.textContent = tooltip;\n",
+ "};\n",
+ "\n",
+ "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n",
+ "// prettier-ignore\n",
+ "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n",
+ "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
+ "\n",
+ "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
+ "\n",
+ "mpl.default_extension = \"png\";/* global mpl */\n",
+ "\n",
+ "var comm_websocket_adapter = function (comm) {\n",
+ " // Create a \"websocket\"-like object which calls the given IPython comm\n",
+ " // object with the appropriate methods. Currently this is a non binary\n",
+ " // socket, so there is still some room for performance tuning.\n",
+ " var ws = {};\n",
+ "\n",
+ " ws.binaryType = comm.kernel.ws.binaryType;\n",
+ " ws.readyState = comm.kernel.ws.readyState;\n",
+ " function updateReadyState(_event) {\n",
+ " if (comm.kernel.ws) {\n",
+ " ws.readyState = comm.kernel.ws.readyState;\n",
+ " } else {\n",
+ " ws.readyState = 3; // Closed state.\n",
+ " }\n",
+ " }\n",
+ " comm.kernel.ws.addEventListener('open', updateReadyState);\n",
+ " comm.kernel.ws.addEventListener('close', updateReadyState);\n",
+ " comm.kernel.ws.addEventListener('error', updateReadyState);\n",
+ "\n",
+ " ws.close = function () {\n",
+ " comm.close();\n",
+ " };\n",
+ " ws.send = function (m) {\n",
+ " //console.log('sending', m);\n",
+ " comm.send(m);\n",
+ " };\n",
+ " // Register the callback with on_msg.\n",
+ " comm.on_msg(function (msg) {\n",
+ " //console.log('receiving', msg['content']['data'], msg);\n",
+ " var data = msg['content']['data'];\n",
+ " if (data['blob'] !== undefined) {\n",
+ " data = {\n",
+ " data: new Blob(msg['buffers'], { type: data['blob'] }),\n",
+ " };\n",
+ " }\n",
+ " // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
+ " ws.onmessage(data);\n",
+ " });\n",
+ " return ws;\n",
+ "};\n",
+ "\n",
+ "mpl.mpl_figure_comm = function (comm, msg) {\n",
+ " // This is the function which gets called when the mpl process\n",
+ " // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
+ "\n",
+ " var id = msg.content.data.id;\n",
+ " // Get hold of the div created by the display call when the Comm\n",
+ " // socket was opened in Python.\n",
+ " var element = document.getElementById(id);\n",
+ " var ws_proxy = comm_websocket_adapter(comm);\n",
+ "\n",
+ " function ondownload(figure, _format) {\n",
+ " window.open(figure.canvas.toDataURL());\n",
+ " }\n",
+ "\n",
+ " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n",
+ "\n",
+ " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
+ " // web socket which is closed, not our websocket->open comm proxy.\n",
+ " ws_proxy.onopen();\n",
+ "\n",
+ " fig.parent_element = element;\n",
+ " fig.cell_info = mpl.find_output_cell(\"\");\n",
+ " if (!fig.cell_info) {\n",
+ " console.error('Failed to find cell for figure', id, fig);\n",
+ " return;\n",
+ " }\n",
+ " fig.cell_info[0].output_area.element.on(\n",
+ " 'cleared',\n",
+ " { fig: fig },\n",
+ " fig._remove_fig_handler\n",
+ " );\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_close = function (fig, msg) {\n",
+ " var width = fig.canvas.width / fig.ratio;\n",
+ " fig.cell_info[0].output_area.element.off(\n",
+ " 'cleared',\n",
+ " fig._remove_fig_handler\n",
+ " );\n",
+ " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n",
+ "\n",
+ " // Update the output cell to use the data from the current canvas.\n",
+ " fig.push_to_output();\n",
+ " var dataURL = fig.canvas.toDataURL();\n",
+ " // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
+ " // the notebook keyboard shortcuts fail.\n",
+ " IPython.keyboard_manager.enable();\n",
+ " fig.parent_element.innerHTML =\n",
+ " '
';\n",
+ " fig.close_ws(fig, msg);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.close_ws = function (fig, msg) {\n",
+ " fig.send_message('closing', msg);\n",
+ " // fig.ws.close()\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n",
+ " // Turn the data on the canvas into data in the output cell.\n",
+ " var width = this.canvas.width / this.ratio;\n",
+ " var dataURL = this.canvas.toDataURL();\n",
+ " this.cell_info[1]['text/html'] =\n",
+ " '
';\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.updated_canvas_event = function () {\n",
+ " // Tell IPython that the notebook contents must change.\n",
+ " IPython.notebook.set_dirty(true);\n",
+ " this.send_message('ack', {});\n",
+ " var fig = this;\n",
+ " // Wait a second, then push the new image to the DOM so\n",
+ " // that it is saved nicely (might be nice to debounce this).\n",
+ " setTimeout(function () {\n",
+ " fig.push_to_output();\n",
+ " }, 1000);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_toolbar = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var toolbar = document.createElement('div');\n",
+ " toolbar.classList = 'btn-toolbar';\n",
+ " this.root.appendChild(toolbar);\n",
+ "\n",
+ " function on_click_closure(name) {\n",
+ " return function (_event) {\n",
+ " return fig.toolbar_button_onclick(name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " function on_mouseover_closure(tooltip) {\n",
+ " return function (event) {\n",
+ " if (!event.currentTarget.disabled) {\n",
+ " return fig.toolbar_button_onmouseover(tooltip);\n",
+ " }\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " fig.buttons = {};\n",
+ " var buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'btn-group';\n",
+ " var button;\n",
+ " for (var toolbar_ind in mpl.toolbar_items) {\n",
+ " var name = mpl.toolbar_items[toolbar_ind][0];\n",
+ " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+ " var image = mpl.toolbar_items[toolbar_ind][2];\n",
+ " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+ "\n",
+ " if (!name) {\n",
+ " /* Instead of a spacer, we start a new button group. */\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ " buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'btn-group';\n",
+ " continue;\n",
+ " }\n",
+ "\n",
+ " button = fig.buttons[name] = document.createElement('button');\n",
+ " button.classList = 'btn btn-default';\n",
+ " button.href = '#';\n",
+ " button.title = name;\n",
+ " button.innerHTML = '';\n",
+ " button.addEventListener('click', on_click_closure(method_name));\n",
+ " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+ " buttonGroup.appendChild(button);\n",
+ " }\n",
+ "\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ "\n",
+ " // Add the status bar.\n",
+ " var status_bar = document.createElement('span');\n",
+ " status_bar.classList = 'mpl-message pull-right';\n",
+ " toolbar.appendChild(status_bar);\n",
+ " this.message = status_bar;\n",
+ "\n",
+ " // Add the close button to the window.\n",
+ " var buttongrp = document.createElement('div');\n",
+ " buttongrp.classList = 'btn-group inline pull-right';\n",
+ " button = document.createElement('button');\n",
+ " button.classList = 'btn btn-mini btn-primary';\n",
+ " button.href = '#';\n",
+ " button.title = 'Stop Interaction';\n",
+ " button.innerHTML = '';\n",
+ " button.addEventListener('click', function (_evt) {\n",
+ " fig.handle_close(fig, {});\n",
+ " });\n",
+ " button.addEventListener(\n",
+ " 'mouseover',\n",
+ " on_mouseover_closure('Stop Interaction')\n",
+ " );\n",
+ " buttongrp.appendChild(button);\n",
+ " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n",
+ " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._remove_fig_handler = function (event) {\n",
+ " var fig = event.data.fig;\n",
+ " if (event.target !== this) {\n",
+ " // Ignore bubbled events from children.\n",
+ " return;\n",
+ " }\n",
+ " fig.close_ws(fig, {});\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._root_extra_style = function (el) {\n",
+ " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._canvas_extra_style = function (el) {\n",
+ " // this is important to make the div 'focusable\n",
+ " el.setAttribute('tabindex', 0);\n",
+ " // reach out to IPython and tell the keyboard manager to turn it's self\n",
+ " // off when our div gets focus\n",
+ "\n",
+ " // location in version 3\n",
+ " if (IPython.notebook.keyboard_manager) {\n",
+ " IPython.notebook.keyboard_manager.register_events(el);\n",
+ " } else {\n",
+ " // location in version 2\n",
+ " IPython.keyboard_manager.register_events(el);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._key_event_extra = function (event, _name) {\n",
+ " // Check for shift+enter\n",
+ " if (event.shiftKey && event.which === 13) {\n",
+ " this.canvas_div.blur();\n",
+ " // select the cell after this one\n",
+ " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n",
+ " IPython.notebook.select(index + 1);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+ " fig.ondownload(fig, null);\n",
+ "};\n",
+ "\n",
+ "mpl.find_output_cell = function (html_output) {\n",
+ " // Return the cell and output element which can be found *uniquely* in the notebook.\n",
+ " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
+ " // IPython event is triggered only after the cells have been serialised, which for\n",
+ " // our purposes (turning an active figure into a static one), is too late.\n",
+ " var cells = IPython.notebook.get_cells();\n",
+ " var ncells = cells.length;\n",
+ " for (var i = 0; i < ncells; i++) {\n",
+ " var cell = cells[i];\n",
+ " if (cell.cell_type === 'code') {\n",
+ " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n",
+ " var data = cell.output_area.outputs[j];\n",
+ " if (data.data) {\n",
+ " // IPython >= 3 moved mimebundle to data attribute of output\n",
+ " data = data.data;\n",
+ " }\n",
+ " if (data['text/html'] === html_output) {\n",
+ " return [cell, data, j];\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "// Register the function which deals with the matplotlib target/channel.\n",
+ "// The kernel may be null if the page has been refreshed.\n",
+ "if (IPython.notebook.kernel !== null) {\n",
+ " IPython.notebook.kernel.comm_manager.register_target(\n",
+ " 'matplotlib',\n",
+ " mpl.mpl_figure_comm\n",
+ " );\n",
+ "}\n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "
"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "%matplotlib notebook\n",
+ "\n",
+ "# Read in results\n",
+ "## Params - Either specify a file prefix or a whole folder\n",
+ "file_prefix = None # Path(\"./results/2022-07-13T11:04:51_parameval_warp_\")\n",
+ "folder = Path(\"./results/param_study_warp\")\n",
+ "\n",
+ "## Plot code\n",
+ "path = file_prefix if file_prefix else folder\n",
+ "\n",
+ "metric = \"put_bw_in_MiB\"\n",
+ "df_1 = load_data(path, suffix=\"runtime.json\", metric=metric)\n",
+ "df_2 = load_data(path, suffix=\"concurrent.json\", metric=metric, x_labels=[\"1\", \"2\", \"4\", \"8\", \"16\", \"32\", \"64\"])\n",
+ "\n",
+ "fig, axis = plt.subplots(2, 1, sharey=True)\n",
+ "fig.suptitle('Parameter Study: WARP')\n",
+ "\n",
+ " \n",
+ "df_1[\"mean\"].plot(\n",
+ " ax=axis[0],\n",
+ " kind='bar', rot=0,\n",
+ " xlabel='Runtime in s',\n",
+ " ylabel='Bandwidth (MiB/s)',\n",
+ " title='Variable Runtime',\n",
+ " yerr=df_1[\"std\"]\n",
+ ")\n",
+ "df_2[\"mean\"].plot(\n",
+ " ax=axis[1],\n",
+ " kind='bar', rot=0,\n",
+ " xlabel='Number of Parallel Operations',\n",
+ " ylabel='Bandwidth (MiB/s)',\n",
+ " title='Variable Number of Threads',\n",
+ " yerr=df_2[\"std\"]\n",
+ ")\n",
+ "\n",
+ "plt.tight_layout()\n",
+ "plt.savefig(\"paramstudy_warp.png\",bbox_inches='tight')\n",
+ "plt.show()\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "66eb19e5",
+ "metadata": {},
+ "source": [
+ "# Experiment: Compare dd and fio"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "id": "160204ff",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "application/javascript": [
+ "/* Put everything inside the global mpl namespace */\n",
+ "/* global mpl */\n",
+ "window.mpl = {};\n",
+ "\n",
+ "mpl.get_websocket_type = function () {\n",
+ " if (typeof WebSocket !== 'undefined') {\n",
+ " return WebSocket;\n",
+ " } else if (typeof MozWebSocket !== 'undefined') {\n",
+ " return MozWebSocket;\n",
+ " } else {\n",
+ " alert(\n",
+ " 'Your browser does not have WebSocket support. ' +\n",
+ " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
+ " 'Firefox 4 and 5 are also supported but you ' +\n",
+ " 'have to enable WebSockets in about:config.'\n",
+ " );\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n",
+ " this.id = figure_id;\n",
+ "\n",
+ " this.ws = websocket;\n",
+ "\n",
+ " this.supports_binary = this.ws.binaryType !== undefined;\n",
+ "\n",
+ " if (!this.supports_binary) {\n",
+ " var warnings = document.getElementById('mpl-warnings');\n",
+ " if (warnings) {\n",
+ " warnings.style.display = 'block';\n",
+ " warnings.textContent =\n",
+ " 'This browser does not support binary websocket messages. ' +\n",
+ " 'Performance may be slow.';\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " this.imageObj = new Image();\n",
+ "\n",
+ " this.context = undefined;\n",
+ " this.message = undefined;\n",
+ " this.canvas = undefined;\n",
+ " this.rubberband_canvas = undefined;\n",
+ " this.rubberband_context = undefined;\n",
+ " this.format_dropdown = undefined;\n",
+ "\n",
+ " this.image_mode = 'full';\n",
+ "\n",
+ " this.root = document.createElement('div');\n",
+ " this.root.setAttribute('style', 'display: inline-block');\n",
+ " this._root_extra_style(this.root);\n",
+ "\n",
+ " parent_element.appendChild(this.root);\n",
+ "\n",
+ " this._init_header(this);\n",
+ " this._init_canvas(this);\n",
+ " this._init_toolbar(this);\n",
+ "\n",
+ " var fig = this;\n",
+ "\n",
+ " this.waiting = false;\n",
+ "\n",
+ " this.ws.onopen = function () {\n",
+ " fig.send_message('supports_binary', { value: fig.supports_binary });\n",
+ " fig.send_message('send_image_mode', {});\n",
+ " if (fig.ratio !== 1) {\n",
+ " fig.send_message('set_device_pixel_ratio', {\n",
+ " device_pixel_ratio: fig.ratio,\n",
+ " });\n",
+ " }\n",
+ " fig.send_message('refresh', {});\n",
+ " };\n",
+ "\n",
+ " this.imageObj.onload = function () {\n",
+ " if (fig.image_mode === 'full') {\n",
+ " // Full images could contain transparency (where diff images\n",
+ " // almost always do), so we need to clear the canvas so that\n",
+ " // there is no ghosting.\n",
+ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
+ " }\n",
+ " fig.context.drawImage(fig.imageObj, 0, 0);\n",
+ " };\n",
+ "\n",
+ " this.imageObj.onunload = function () {\n",
+ " fig.ws.close();\n",
+ " };\n",
+ "\n",
+ " this.ws.onmessage = this._make_on_message_function(this);\n",
+ "\n",
+ " this.ondownload = ondownload;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_header = function () {\n",
+ " var titlebar = document.createElement('div');\n",
+ " titlebar.classList =\n",
+ " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n",
+ " var titletext = document.createElement('div');\n",
+ " titletext.classList = 'ui-dialog-title';\n",
+ " titletext.setAttribute(\n",
+ " 'style',\n",
+ " 'width: 100%; text-align: center; padding: 3px;'\n",
+ " );\n",
+ " titlebar.appendChild(titletext);\n",
+ " this.root.appendChild(titlebar);\n",
+ " this.header = titletext;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n",
+ "\n",
+ "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n",
+ "\n",
+ "mpl.figure.prototype._init_canvas = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var canvas_div = (this.canvas_div = document.createElement('div'));\n",
+ " canvas_div.setAttribute(\n",
+ " 'style',\n",
+ " 'border: 1px solid #ddd;' +\n",
+ " 'box-sizing: content-box;' +\n",
+ " 'clear: both;' +\n",
+ " 'min-height: 1px;' +\n",
+ " 'min-width: 1px;' +\n",
+ " 'outline: 0;' +\n",
+ " 'overflow: hidden;' +\n",
+ " 'position: relative;' +\n",
+ " 'resize: both;'\n",
+ " );\n",
+ "\n",
+ " function on_keyboard_event_closure(name) {\n",
+ " return function (event) {\n",
+ " return fig.key_event(event, name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " canvas_div.addEventListener(\n",
+ " 'keydown',\n",
+ " on_keyboard_event_closure('key_press')\n",
+ " );\n",
+ " canvas_div.addEventListener(\n",
+ " 'keyup',\n",
+ " on_keyboard_event_closure('key_release')\n",
+ " );\n",
+ "\n",
+ " this._canvas_extra_style(canvas_div);\n",
+ " this.root.appendChild(canvas_div);\n",
+ "\n",
+ " var canvas = (this.canvas = document.createElement('canvas'));\n",
+ " canvas.classList.add('mpl-canvas');\n",
+ " canvas.setAttribute('style', 'box-sizing: content-box;');\n",
+ "\n",
+ " this.context = canvas.getContext('2d');\n",
+ "\n",
+ " var backingStore =\n",
+ " this.context.backingStorePixelRatio ||\n",
+ " this.context.webkitBackingStorePixelRatio ||\n",
+ " this.context.mozBackingStorePixelRatio ||\n",
+ " this.context.msBackingStorePixelRatio ||\n",
+ " this.context.oBackingStorePixelRatio ||\n",
+ " this.context.backingStorePixelRatio ||\n",
+ " 1;\n",
+ "\n",
+ " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
+ "\n",
+ " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n",
+ " 'canvas'\n",
+ " ));\n",
+ " rubberband_canvas.setAttribute(\n",
+ " 'style',\n",
+ " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n",
+ " );\n",
+ "\n",
+ " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n",
+ " if (this.ResizeObserver === undefined) {\n",
+ " if (window.ResizeObserver !== undefined) {\n",
+ " this.ResizeObserver = window.ResizeObserver;\n",
+ " } else {\n",
+ " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n",
+ " this.ResizeObserver = obs.ResizeObserver;\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n",
+ " var nentries = entries.length;\n",
+ " for (var i = 0; i < nentries; i++) {\n",
+ " var entry = entries[i];\n",
+ " var width, height;\n",
+ " if (entry.contentBoxSize) {\n",
+ " if (entry.contentBoxSize instanceof Array) {\n",
+ " // Chrome 84 implements new version of spec.\n",
+ " width = entry.contentBoxSize[0].inlineSize;\n",
+ " height = entry.contentBoxSize[0].blockSize;\n",
+ " } else {\n",
+ " // Firefox implements old version of spec.\n",
+ " width = entry.contentBoxSize.inlineSize;\n",
+ " height = entry.contentBoxSize.blockSize;\n",
+ " }\n",
+ " } else {\n",
+ " // Chrome <84 implements even older version of spec.\n",
+ " width = entry.contentRect.width;\n",
+ " height = entry.contentRect.height;\n",
+ " }\n",
+ "\n",
+ " // Keep the size of the canvas and rubber band canvas in sync with\n",
+ " // the canvas container.\n",
+ " if (entry.devicePixelContentBoxSize) {\n",
+ " // Chrome 84 implements new version of spec.\n",
+ " canvas.setAttribute(\n",
+ " 'width',\n",
+ " entry.devicePixelContentBoxSize[0].inlineSize\n",
+ " );\n",
+ " canvas.setAttribute(\n",
+ " 'height',\n",
+ " entry.devicePixelContentBoxSize[0].blockSize\n",
+ " );\n",
+ " } else {\n",
+ " canvas.setAttribute('width', width * fig.ratio);\n",
+ " canvas.setAttribute('height', height * fig.ratio);\n",
+ " }\n",
+ " canvas.setAttribute(\n",
+ " 'style',\n",
+ " 'width: ' + width + 'px; height: ' + height + 'px;'\n",
+ " );\n",
+ "\n",
+ " rubberband_canvas.setAttribute('width', width);\n",
+ " rubberband_canvas.setAttribute('height', height);\n",
+ "\n",
+ " // And update the size in Python. We ignore the initial 0/0 size\n",
+ " // that occurs as the element is placed into the DOM, which should\n",
+ " // otherwise not happen due to the minimum size styling.\n",
+ " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n",
+ " fig.request_resize(width, height);\n",
+ " }\n",
+ " }\n",
+ " });\n",
+ " this.resizeObserverInstance.observe(canvas_div);\n",
+ "\n",
+ " function on_mouse_event_closure(name) {\n",
+ " return function (event) {\n",
+ " return fig.mouse_event(event, name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mousedown',\n",
+ " on_mouse_event_closure('button_press')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseup',\n",
+ " on_mouse_event_closure('button_release')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'dblclick',\n",
+ " on_mouse_event_closure('dblclick')\n",
+ " );\n",
+ " // Throttle sequential mouse events to 1 every 20ms.\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mousemove',\n",
+ " on_mouse_event_closure('motion_notify')\n",
+ " );\n",
+ "\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseenter',\n",
+ " on_mouse_event_closure('figure_enter')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseleave',\n",
+ " on_mouse_event_closure('figure_leave')\n",
+ " );\n",
+ "\n",
+ " canvas_div.addEventListener('wheel', function (event) {\n",
+ " if (event.deltaY < 0) {\n",
+ " event.step = 1;\n",
+ " } else {\n",
+ " event.step = -1;\n",
+ " }\n",
+ " on_mouse_event_closure('scroll')(event);\n",
+ " });\n",
+ "\n",
+ " canvas_div.appendChild(canvas);\n",
+ " canvas_div.appendChild(rubberband_canvas);\n",
+ "\n",
+ " this.rubberband_context = rubberband_canvas.getContext('2d');\n",
+ " this.rubberband_context.strokeStyle = '#000000';\n",
+ "\n",
+ " this._resize_canvas = function (width, height, forward) {\n",
+ " if (forward) {\n",
+ " canvas_div.style.width = width + 'px';\n",
+ " canvas_div.style.height = height + 'px';\n",
+ " }\n",
+ " };\n",
+ "\n",
+ " // Disable right mouse context menu.\n",
+ " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n",
+ " event.preventDefault();\n",
+ " return false;\n",
+ " });\n",
+ "\n",
+ " function set_focus() {\n",
+ " canvas.focus();\n",
+ " canvas_div.focus();\n",
+ " }\n",
+ "\n",
+ " window.setTimeout(set_focus, 100);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_toolbar = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var toolbar = document.createElement('div');\n",
+ " toolbar.classList = 'mpl-toolbar';\n",
+ " this.root.appendChild(toolbar);\n",
+ "\n",
+ " function on_click_closure(name) {\n",
+ " return function (_event) {\n",
+ " return fig.toolbar_button_onclick(name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " function on_mouseover_closure(tooltip) {\n",
+ " return function (event) {\n",
+ " if (!event.currentTarget.disabled) {\n",
+ " return fig.toolbar_button_onmouseover(tooltip);\n",
+ " }\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " fig.buttons = {};\n",
+ " var buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'mpl-button-group';\n",
+ " for (var toolbar_ind in mpl.toolbar_items) {\n",
+ " var name = mpl.toolbar_items[toolbar_ind][0];\n",
+ " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+ " var image = mpl.toolbar_items[toolbar_ind][2];\n",
+ " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+ "\n",
+ " if (!name) {\n",
+ " /* Instead of a spacer, we start a new button group. */\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ " buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'mpl-button-group';\n",
+ " continue;\n",
+ " }\n",
+ "\n",
+ " var button = (fig.buttons[name] = document.createElement('button'));\n",
+ " button.classList = 'mpl-widget';\n",
+ " button.setAttribute('role', 'button');\n",
+ " button.setAttribute('aria-disabled', 'false');\n",
+ " button.addEventListener('click', on_click_closure(method_name));\n",
+ " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+ "\n",
+ " var icon_img = document.createElement('img');\n",
+ " icon_img.src = '_images/' + image + '.png';\n",
+ " icon_img.srcset = '_images/' + image + '_large.png 2x';\n",
+ " icon_img.alt = tooltip;\n",
+ " button.appendChild(icon_img);\n",
+ "\n",
+ " buttonGroup.appendChild(button);\n",
+ " }\n",
+ "\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ "\n",
+ " var fmt_picker = document.createElement('select');\n",
+ " fmt_picker.classList = 'mpl-widget';\n",
+ " toolbar.appendChild(fmt_picker);\n",
+ " this.format_dropdown = fmt_picker;\n",
+ "\n",
+ " for (var ind in mpl.extensions) {\n",
+ " var fmt = mpl.extensions[ind];\n",
+ " var option = document.createElement('option');\n",
+ " option.selected = fmt === mpl.default_extension;\n",
+ " option.innerHTML = fmt;\n",
+ " fmt_picker.appendChild(option);\n",
+ " }\n",
+ "\n",
+ " var status_bar = document.createElement('span');\n",
+ " status_bar.classList = 'mpl-message';\n",
+ " toolbar.appendChild(status_bar);\n",
+ " this.message = status_bar;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n",
+ " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
+ " // which will in turn request a refresh of the image.\n",
+ " this.send_message('resize', { width: x_pixels, height: y_pixels });\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.send_message = function (type, properties) {\n",
+ " properties['type'] = type;\n",
+ " properties['figure_id'] = this.id;\n",
+ " this.ws.send(JSON.stringify(properties));\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.send_draw_message = function () {\n",
+ " if (!this.waiting) {\n",
+ " this.waiting = true;\n",
+ " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+ " var format_dropdown = fig.format_dropdown;\n",
+ " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
+ " fig.ondownload(fig, format);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_resize = function (fig, msg) {\n",
+ " var size = msg['size'];\n",
+ " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n",
+ " fig._resize_canvas(size[0], size[1], msg['forward']);\n",
+ " fig.send_message('refresh', {});\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n",
+ " var x0 = msg['x0'] / fig.ratio;\n",
+ " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n",
+ " var x1 = msg['x1'] / fig.ratio;\n",
+ " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n",
+ " x0 = Math.floor(x0) + 0.5;\n",
+ " y0 = Math.floor(y0) + 0.5;\n",
+ " x1 = Math.floor(x1) + 0.5;\n",
+ " y1 = Math.floor(y1) + 0.5;\n",
+ " var min_x = Math.min(x0, x1);\n",
+ " var min_y = Math.min(y0, y1);\n",
+ " var width = Math.abs(x1 - x0);\n",
+ " var height = Math.abs(y1 - y0);\n",
+ "\n",
+ " fig.rubberband_context.clearRect(\n",
+ " 0,\n",
+ " 0,\n",
+ " fig.canvas.width / fig.ratio,\n",
+ " fig.canvas.height / fig.ratio\n",
+ " );\n",
+ "\n",
+ " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n",
+ " // Updates the figure title.\n",
+ " fig.header.textContent = msg['label'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n",
+ " fig.rubberband_canvas.style.cursor = msg['cursor'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_message = function (fig, msg) {\n",
+ " fig.message.textContent = msg['message'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n",
+ " // Request the server to send over a new figure.\n",
+ " fig.send_draw_message();\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n",
+ " fig.image_mode = msg['mode'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n",
+ " for (var key in msg) {\n",
+ " if (!(key in fig.buttons)) {\n",
+ " continue;\n",
+ " }\n",
+ " fig.buttons[key].disabled = !msg[key];\n",
+ " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n",
+ " if (msg['mode'] === 'PAN') {\n",
+ " fig.buttons['Pan'].classList.add('active');\n",
+ " fig.buttons['Zoom'].classList.remove('active');\n",
+ " } else if (msg['mode'] === 'ZOOM') {\n",
+ " fig.buttons['Pan'].classList.remove('active');\n",
+ " fig.buttons['Zoom'].classList.add('active');\n",
+ " } else {\n",
+ " fig.buttons['Pan'].classList.remove('active');\n",
+ " fig.buttons['Zoom'].classList.remove('active');\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.updated_canvas_event = function () {\n",
+ " // Called whenever the canvas gets updated.\n",
+ " this.send_message('ack', {});\n",
+ "};\n",
+ "\n",
+ "// A function to construct a web socket function for onmessage handling.\n",
+ "// Called in the figure constructor.\n",
+ "mpl.figure.prototype._make_on_message_function = function (fig) {\n",
+ " return function socket_on_message(evt) {\n",
+ " if (evt.data instanceof Blob) {\n",
+ " var img = evt.data;\n",
+ " if (img.type !== 'image/png') {\n",
+ " /* FIXME: We get \"Resource interpreted as Image but\n",
+ " * transferred with MIME type text/plain:\" errors on\n",
+ " * Chrome. But how to set the MIME type? It doesn't seem\n",
+ " * to be part of the websocket stream */\n",
+ " img.type = 'image/png';\n",
+ " }\n",
+ "\n",
+ " /* Free the memory for the previous frames */\n",
+ " if (fig.imageObj.src) {\n",
+ " (window.URL || window.webkitURL).revokeObjectURL(\n",
+ " fig.imageObj.src\n",
+ " );\n",
+ " }\n",
+ "\n",
+ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
+ " img\n",
+ " );\n",
+ " fig.updated_canvas_event();\n",
+ " fig.waiting = false;\n",
+ " return;\n",
+ " } else if (\n",
+ " typeof evt.data === 'string' &&\n",
+ " evt.data.slice(0, 21) === 'data:image/png;base64'\n",
+ " ) {\n",
+ " fig.imageObj.src = evt.data;\n",
+ " fig.updated_canvas_event();\n",
+ " fig.waiting = false;\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " var msg = JSON.parse(evt.data);\n",
+ " var msg_type = msg['type'];\n",
+ "\n",
+ " // Call the \"handle_{type}\" callback, which takes\n",
+ " // the figure and JSON message as its only arguments.\n",
+ " try {\n",
+ " var callback = fig['handle_' + msg_type];\n",
+ " } catch (e) {\n",
+ " console.log(\n",
+ " \"No handler for the '\" + msg_type + \"' message type: \",\n",
+ " msg\n",
+ " );\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " if (callback) {\n",
+ " try {\n",
+ " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
+ " callback(fig, msg);\n",
+ " } catch (e) {\n",
+ " console.log(\n",
+ " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n",
+ " e,\n",
+ " e.stack,\n",
+ " msg\n",
+ " );\n",
+ " }\n",
+ " }\n",
+ " };\n",
+ "};\n",
+ "\n",
+ "// from https://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
+ "mpl.findpos = function (e) {\n",
+ " //this section is from http://www.quirksmode.org/js/events_properties.html\n",
+ " var targ;\n",
+ " if (!e) {\n",
+ " e = window.event;\n",
+ " }\n",
+ " if (e.target) {\n",
+ " targ = e.target;\n",
+ " } else if (e.srcElement) {\n",
+ " targ = e.srcElement;\n",
+ " }\n",
+ " if (targ.nodeType === 3) {\n",
+ " // defeat Safari bug\n",
+ " targ = targ.parentNode;\n",
+ " }\n",
+ "\n",
+ " // pageX,Y are the mouse positions relative to the document\n",
+ " var boundingRect = targ.getBoundingClientRect();\n",
+ " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n",
+ " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n",
+ "\n",
+ " return { x: x, y: y };\n",
+ "};\n",
+ "\n",
+ "/*\n",
+ " * return a copy of an object with only non-object keys\n",
+ " * we need this to avoid circular references\n",
+ " * https://stackoverflow.com/a/24161582/3208463\n",
+ " */\n",
+ "function simpleKeys(original) {\n",
+ " return Object.keys(original).reduce(function (obj, key) {\n",
+ " if (typeof original[key] !== 'object') {\n",
+ " obj[key] = original[key];\n",
+ " }\n",
+ " return obj;\n",
+ " }, {});\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.mouse_event = function (event, name) {\n",
+ " var canvas_pos = mpl.findpos(event);\n",
+ "\n",
+ " if (name === 'button_press') {\n",
+ " this.canvas.focus();\n",
+ " this.canvas_div.focus();\n",
+ " }\n",
+ "\n",
+ " var x = canvas_pos.x * this.ratio;\n",
+ " var y = canvas_pos.y * this.ratio;\n",
+ "\n",
+ " this.send_message(name, {\n",
+ " x: x,\n",
+ " y: y,\n",
+ " button: event.button,\n",
+ " step: event.step,\n",
+ " guiEvent: simpleKeys(event),\n",
+ " });\n",
+ "\n",
+ " /* This prevents the web browser from automatically changing to\n",
+ " * the text insertion cursor when the button is pressed. We want\n",
+ " * to control all of the cursor setting manually through the\n",
+ " * 'cursor' event from matplotlib */\n",
+ " event.preventDefault();\n",
+ " return false;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n",
+ " // Handle any extra behaviour associated with a key event\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.key_event = function (event, name) {\n",
+ " // Prevent repeat events\n",
+ " if (name === 'key_press') {\n",
+ " if (event.key === this._key) {\n",
+ " return;\n",
+ " } else {\n",
+ " this._key = event.key;\n",
+ " }\n",
+ " }\n",
+ " if (name === 'key_release') {\n",
+ " this._key = null;\n",
+ " }\n",
+ "\n",
+ " var value = '';\n",
+ " if (event.ctrlKey && event.key !== 'Control') {\n",
+ " value += 'ctrl+';\n",
+ " }\n",
+ " else if (event.altKey && event.key !== 'Alt') {\n",
+ " value += 'alt+';\n",
+ " }\n",
+ " else if (event.shiftKey && event.key !== 'Shift') {\n",
+ " value += 'shift+';\n",
+ " }\n",
+ "\n",
+ " value += 'k' + event.key;\n",
+ "\n",
+ " this._key_event_extra(event, name);\n",
+ "\n",
+ " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n",
+ " return false;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n",
+ " if (name === 'download') {\n",
+ " this.handle_save(this, null);\n",
+ " } else {\n",
+ " this.send_message('toolbar_button', { name: name });\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n",
+ " this.message.textContent = tooltip;\n",
+ "};\n",
+ "\n",
+ "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n",
+ "// prettier-ignore\n",
+ "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n",
+ "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
+ "\n",
+ "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
+ "\n",
+ "mpl.default_extension = \"png\";/* global mpl */\n",
+ "\n",
+ "var comm_websocket_adapter = function (comm) {\n",
+ " // Create a \"websocket\"-like object which calls the given IPython comm\n",
+ " // object with the appropriate methods. Currently this is a non binary\n",
+ " // socket, so there is still some room for performance tuning.\n",
+ " var ws = {};\n",
+ "\n",
+ " ws.binaryType = comm.kernel.ws.binaryType;\n",
+ " ws.readyState = comm.kernel.ws.readyState;\n",
+ " function updateReadyState(_event) {\n",
+ " if (comm.kernel.ws) {\n",
+ " ws.readyState = comm.kernel.ws.readyState;\n",
+ " } else {\n",
+ " ws.readyState = 3; // Closed state.\n",
+ " }\n",
+ " }\n",
+ " comm.kernel.ws.addEventListener('open', updateReadyState);\n",
+ " comm.kernel.ws.addEventListener('close', updateReadyState);\n",
+ " comm.kernel.ws.addEventListener('error', updateReadyState);\n",
+ "\n",
+ " ws.close = function () {\n",
+ " comm.close();\n",
+ " };\n",
+ " ws.send = function (m) {\n",
+ " //console.log('sending', m);\n",
+ " comm.send(m);\n",
+ " };\n",
+ " // Register the callback with on_msg.\n",
+ " comm.on_msg(function (msg) {\n",
+ " //console.log('receiving', msg['content']['data'], msg);\n",
+ " var data = msg['content']['data'];\n",
+ " if (data['blob'] !== undefined) {\n",
+ " data = {\n",
+ " data: new Blob(msg['buffers'], { type: data['blob'] }),\n",
+ " };\n",
+ " }\n",
+ " // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
+ " ws.onmessage(data);\n",
+ " });\n",
+ " return ws;\n",
+ "};\n",
+ "\n",
+ "mpl.mpl_figure_comm = function (comm, msg) {\n",
+ " // This is the function which gets called when the mpl process\n",
+ " // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
+ "\n",
+ " var id = msg.content.data.id;\n",
+ " // Get hold of the div created by the display call when the Comm\n",
+ " // socket was opened in Python.\n",
+ " var element = document.getElementById(id);\n",
+ " var ws_proxy = comm_websocket_adapter(comm);\n",
+ "\n",
+ " function ondownload(figure, _format) {\n",
+ " window.open(figure.canvas.toDataURL());\n",
+ " }\n",
+ "\n",
+ " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n",
+ "\n",
+ " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
+ " // web socket which is closed, not our websocket->open comm proxy.\n",
+ " ws_proxy.onopen();\n",
+ "\n",
+ " fig.parent_element = element;\n",
+ " fig.cell_info = mpl.find_output_cell(\"\");\n",
+ " if (!fig.cell_info) {\n",
+ " console.error('Failed to find cell for figure', id, fig);\n",
+ " return;\n",
+ " }\n",
+ " fig.cell_info[0].output_area.element.on(\n",
+ " 'cleared',\n",
+ " { fig: fig },\n",
+ " fig._remove_fig_handler\n",
+ " );\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_close = function (fig, msg) {\n",
+ " var width = fig.canvas.width / fig.ratio;\n",
+ " fig.cell_info[0].output_area.element.off(\n",
+ " 'cleared',\n",
+ " fig._remove_fig_handler\n",
+ " );\n",
+ " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n",
+ "\n",
+ " // Update the output cell to use the data from the current canvas.\n",
+ " fig.push_to_output();\n",
+ " var dataURL = fig.canvas.toDataURL();\n",
+ " // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
+ " // the notebook keyboard shortcuts fail.\n",
+ " IPython.keyboard_manager.enable();\n",
+ " fig.parent_element.innerHTML =\n",
+ " '
';\n",
+ " fig.close_ws(fig, msg);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.close_ws = function (fig, msg) {\n",
+ " fig.send_message('closing', msg);\n",
+ " // fig.ws.close()\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n",
+ " // Turn the data on the canvas into data in the output cell.\n",
+ " var width = this.canvas.width / this.ratio;\n",
+ " var dataURL = this.canvas.toDataURL();\n",
+ " this.cell_info[1]['text/html'] =\n",
+ " '
';\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.updated_canvas_event = function () {\n",
+ " // Tell IPython that the notebook contents must change.\n",
+ " IPython.notebook.set_dirty(true);\n",
+ " this.send_message('ack', {});\n",
+ " var fig = this;\n",
+ " // Wait a second, then push the new image to the DOM so\n",
+ " // that it is saved nicely (might be nice to debounce this).\n",
+ " setTimeout(function () {\n",
+ " fig.push_to_output();\n",
+ " }, 1000);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_toolbar = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var toolbar = document.createElement('div');\n",
+ " toolbar.classList = 'btn-toolbar';\n",
+ " this.root.appendChild(toolbar);\n",
+ "\n",
+ " function on_click_closure(name) {\n",
+ " return function (_event) {\n",
+ " return fig.toolbar_button_onclick(name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " function on_mouseover_closure(tooltip) {\n",
+ " return function (event) {\n",
+ " if (!event.currentTarget.disabled) {\n",
+ " return fig.toolbar_button_onmouseover(tooltip);\n",
+ " }\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " fig.buttons = {};\n",
+ " var buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'btn-group';\n",
+ " var button;\n",
+ " for (var toolbar_ind in mpl.toolbar_items) {\n",
+ " var name = mpl.toolbar_items[toolbar_ind][0];\n",
+ " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+ " var image = mpl.toolbar_items[toolbar_ind][2];\n",
+ " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+ "\n",
+ " if (!name) {\n",
+ " /* Instead of a spacer, we start a new button group. */\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ " buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'btn-group';\n",
+ " continue;\n",
+ " }\n",
+ "\n",
+ " button = fig.buttons[name] = document.createElement('button');\n",
+ " button.classList = 'btn btn-default';\n",
+ " button.href = '#';\n",
+ " button.title = name;\n",
+ " button.innerHTML = '';\n",
+ " button.addEventListener('click', on_click_closure(method_name));\n",
+ " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+ " buttonGroup.appendChild(button);\n",
+ " }\n",
+ "\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ "\n",
+ " // Add the status bar.\n",
+ " var status_bar = document.createElement('span');\n",
+ " status_bar.classList = 'mpl-message pull-right';\n",
+ " toolbar.appendChild(status_bar);\n",
+ " this.message = status_bar;\n",
+ "\n",
+ " // Add the close button to the window.\n",
+ " var buttongrp = document.createElement('div');\n",
+ " buttongrp.classList = 'btn-group inline pull-right';\n",
+ " button = document.createElement('button');\n",
+ " button.classList = 'btn btn-mini btn-primary';\n",
+ " button.href = '#';\n",
+ " button.title = 'Stop Interaction';\n",
+ " button.innerHTML = '';\n",
+ " button.addEventListener('click', function (_evt) {\n",
+ " fig.handle_close(fig, {});\n",
+ " });\n",
+ " button.addEventListener(\n",
+ " 'mouseover',\n",
+ " on_mouseover_closure('Stop Interaction')\n",
+ " );\n",
+ " buttongrp.appendChild(button);\n",
+ " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n",
+ " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._remove_fig_handler = function (event) {\n",
+ " var fig = event.data.fig;\n",
+ " if (event.target !== this) {\n",
+ " // Ignore bubbled events from children.\n",
+ " return;\n",
+ " }\n",
+ " fig.close_ws(fig, {});\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._root_extra_style = function (el) {\n",
+ " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._canvas_extra_style = function (el) {\n",
+ " // this is important to make the div 'focusable\n",
+ " el.setAttribute('tabindex', 0);\n",
+ " // reach out to IPython and tell the keyboard manager to turn it's self\n",
+ " // off when our div gets focus\n",
+ "\n",
+ " // location in version 3\n",
+ " if (IPython.notebook.keyboard_manager) {\n",
+ " IPython.notebook.keyboard_manager.register_events(el);\n",
+ " } else {\n",
+ " // location in version 2\n",
+ " IPython.keyboard_manager.register_events(el);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._key_event_extra = function (event, _name) {\n",
+ " // Check for shift+enter\n",
+ " if (event.shiftKey && event.which === 13) {\n",
+ " this.canvas_div.blur();\n",
+ " // select the cell after this one\n",
+ " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n",
+ " IPython.notebook.select(index + 1);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+ " fig.ondownload(fig, null);\n",
+ "};\n",
+ "\n",
+ "mpl.find_output_cell = function (html_output) {\n",
+ " // Return the cell and output element which can be found *uniquely* in the notebook.\n",
+ " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
+ " // IPython event is triggered only after the cells have been serialised, which for\n",
+ " // our purposes (turning an active figure into a static one), is too late.\n",
+ " var cells = IPython.notebook.get_cells();\n",
+ " var ncells = cells.length;\n",
+ " for (var i = 0; i < ncells; i++) {\n",
+ " var cell = cells[i];\n",
+ " if (cell.cell_type === 'code') {\n",
+ " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n",
+ " var data = cell.output_area.outputs[j];\n",
+ " if (data.data) {\n",
+ " // IPython >= 3 moved mimebundle to data attribute of output\n",
+ " data = data.data;\n",
+ " }\n",
+ " if (data['text/html'] === html_output) {\n",
+ " return [cell, data, j];\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "// Register the function which deals with the matplotlib target/channel.\n",
+ "// The kernel may be null if the page has been refreshed.\n",
+ "if (IPython.notebook.kernel !== null) {\n",
+ " IPython.notebook.kernel.comm_manager.register_target(\n",
+ " 'matplotlib',\n",
+ " mpl.mpl_figure_comm\n",
+ " );\n",
+ "}\n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "
"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "%matplotlib notebook\n",
+ "\n",
+ "# Read in results\n",
+ "## Params - Either specify a file prefix or a whole folder\n",
+ "file_prefix = None # Path(\"./results/2022-07-05T20:00:06_throughput_write_\")\n",
+ "folder = Path(\"./results/compare_dd_vs_fio\")\n",
+ "\n",
+ "## Plot code\n",
+ "path = file_prefix if file_prefix else folder\n",
+ "\n",
+ "df_isilon = pd.concat({\n",
+ " \"fio\": load_data(path, metric=\"write_bw_mean_in_MiB\", suffix=\"isilon_a.json\"),\n",
+ " \"dd\": load_data(path, metric=\"bw_in_MiB\", suffix=\"isilon_b.json\"),\n",
+ "})\n",
+ "\n",
+ "df_netapp = pd.concat({\n",
+ " \"fio\": load_data(path, metric=\"write_bw_mean_in_MiB\", suffix=\"netapp_a.json\"),\n",
+ " \"dd\": load_data(path, metric=\"bw_in_MiB\", suffix=\"netapp_b.json\"),\n",
+ "})\n",
+ "\n",
+ "fig, (ax1, ax2) = plt.subplots(1, 2, sharey=True)\n",
+ "fig.suptitle('Tool comparison: dd vs. fio')\n",
+ "\n",
+ "df_isilon[\"mean\"].unstack(level=0).plot(\n",
+ " ax=ax1,\n",
+ " kind='bar', rot=0,\n",
+ " xlabel='Transferred Data (GiB)',\n",
+ " ylabel='Bandwidth (MiB/s)',\n",
+ " title=f\"nfs_isilon\",\n",
+ " figsize=(8, 4),\n",
+ " yerr=df_isilon[\"std\"].unstack(level=0)\n",
+ ")\n",
+ "\n",
+ "df_netapp[\"mean\"].unstack(level=0).plot(\n",
+ " ax=ax2,\n",
+ " kind='bar', rot=0,\n",
+ " xlabel='Transferred Data (GiB)',\n",
+ " ylabel='Bandwidth (MiB/s)',\n",
+ " title=f\"nfs_netapp\",\n",
+ " figsize=(8, 4),\n",
+ " yerr=df_netapp[\"std\"].unstack(level=0)\n",
+ ")\n",
+ "\n",
+ "ax1.get_legend().remove()\n",
+ "plt.tight_layout()\n",
+ "plt.savefig(\"dd_vs_fio.png\",bbox_inches='tight')\n",
+ "plt.show()\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1a1d799e",
+ "metadata": {},
+ "source": [
+ "# Experiment: Native vs Container"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "id": "a8cfed3e",
+ "metadata": {
+ "scrolled": true
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " & & \\multicolumn{3}{c}{Native} & \\multicolumn{3}{c}{Container}\\\\\n",
+ "Bandwidth (MiB/s) & write & 669 & $\\pm$ & 195 & 652 & $\\pm$ & 182\\\\\n",
+ " & read & 543 & $\\pm$ & 161 & 542 & $\\pm$ & 157\\\\\n",
+ "IOPS (ops/s) & write & 3652 & $\\pm$ & 608 & 3796 & $\\pm$ & 599\\\\\n",
+ " & read & 22705 & $\\pm$ & 1107 & 22624 & $\\pm$ & 1196\\\\\n",
+ "Latency (ms) & write & 1.02 & $\\pm$ & 0.059 & 0.99 & $\\pm$ & 0.044\\\\\n",
+ " & read & 0.55 & $\\pm$ & 0.022 & 0.55 & $\\pm$ & 0.020\\\\\n"
+ ]
+ }
+ ],
+ "source": [
+ "%matplotlib notebook\n",
+ "\n",
+ "# Read in results\n",
+ "## Params - Either specify a file prefix or a whole folder\n",
+ "file_prefix = None # Path(\"./results/2022-05-31T21:42:26_con_vs_nativ_\")\n",
+ "folder = Path(\"./results/compare_native_vs_container\")\n",
+ "\n",
+ "## Plot code\n",
+ "path = file_prefix if file_prefix else folder\n",
+ "\n",
+ "def load_df(benchmark):\n",
+ "\n",
+ " df = pd.concat([\n",
+ " load_data(path, suffix=benchmark+\"_con.json\", metric=METRIC[benchmark]).rename(index={\"con_vs_nativ_\"+benchmark+\"_con\": \"container\"}),\n",
+ " load_data(path, suffix=benchmark+\"_nativ.json\", metric=METRIC[benchmark]).rename(index={\"con_vs_nativ_\"+benchmark+\"_nativ\": \"nativ\"}),\n",
+ " ])\n",
+ "\n",
+ " return df\n",
+ "df_1 = load_df(\"throughput_write\")\n",
+ "df_2 = load_df(\"iops_write\")\n",
+ "df_3 = load_df(\"latency_write\")\n",
+ "df_4 = load_df(\"throughput_read\")\n",
+ "df_5 = load_df(\"iops_read\")\n",
+ "df_6 = load_df(\"latency_read\")\n",
+ "\n",
+ "def extract(df):\n",
+ " return (\n",
+ " df.loc['nativ'].at['mean'],\n",
+ " df.loc['nativ'].at['std'],\n",
+ " df.loc['container'].at['mean'],\n",
+ " df.loc['container'].at['std'],\n",
+ " )\n",
+ "\n",
+ "## {1}{2}{3}{4}\n",
+ "# 1: b=troughput, i=iops, l=latency\n",
+ "# 2: w=write, r=read\n",
+ "# 3: n=nativ, c=container\n",
+ "# 4: m=mean, c=conf\n",
+ " \n",
+ "bwnm, bwnc, bwcm, bwcc = extract(df_1)\n",
+ "brnm, brnc, brcm, brcc = extract(df_4)\n",
+ "iwnm, iwnc, iwcm, iwcc = extract(df_2)\n",
+ "irnm, irnc, ircm, ircc = extract(df_5)\n",
+ "lwnm, lwnc, lwcm, lwcc = extract(df_3)\n",
+ "lrnm, lrnc, lrcm, lrcc = extract(df_6)\n",
+ "\n",
+ "\n",
+ "tex_table([\n",
+ " [\"\", \"\", \"\\multicolumn{3}{c}{Native}\", \"\\multicolumn{3}{c}{Container}\"],\n",
+ " [\"Bandwidth (MiB/s)\", \"write\", f\"{bwnm:.0f}\", \"$\\pm$\", f\"{bwnc:.0f}\", f\"{bwcm:.0f}\", \"$\\pm$\", f\"{bwcc:.0f}\"],\n",
+ " [\"\" , \"read\" , f\"{brnm:.0f}\", \"$\\pm$\", f\"{brnc:.0f}\", f\"{brcm:.0f}\", \"$\\pm$\", f\"{brcc:.0f}\"],\n",
+ " [\"IOPS (ops/s)\" , \"write\", f\"{iwnm:.0f}\", \"$\\pm$\", f\"{iwnc:.0f}\", f\"{iwcm:.0f}\", \"$\\pm$\", f\"{iwcc:.0f}\"],\n",
+ " [\"\" , \"read\" , f\"{irnm:.0f}\", \"$\\pm$\", f\"{irnc:.0f}\", f\"{ircm:.0f}\", \"$\\pm$\", f\"{ircc:.0f}\"],\n",
+ " [\"Latency (ms)\" , \"write\", f\"{lwnm:.2f}\", \"$\\pm$\", f\"{lwnc:.3f}\", f\"{lwcm:.2f}\", \"$\\pm$\", f\"{lwcc:.3f}\"],\n",
+ " [\"\" , \"read\" , f\"{lrnm:.2f}\", \"$\\pm$\", f\"{lrnc:.3f}\", f\"{lrcm:.2f}\", \"$\\pm$\", f\"{lrcc:.3f}\"],\n",
+ "])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "59d156c3",
+ "metadata": {},
+ "source": [
+ "# Experiment: Compare S3Benchmark vs Warp"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "id": "f4ad9d58",
+ "metadata": {
+ "scrolled": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "application/javascript": [
+ "/* Put everything inside the global mpl namespace */\n",
+ "/* global mpl */\n",
+ "window.mpl = {};\n",
+ "\n",
+ "mpl.get_websocket_type = function () {\n",
+ " if (typeof WebSocket !== 'undefined') {\n",
+ " return WebSocket;\n",
+ " } else if (typeof MozWebSocket !== 'undefined') {\n",
+ " return MozWebSocket;\n",
+ " } else {\n",
+ " alert(\n",
+ " 'Your browser does not have WebSocket support. ' +\n",
+ " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
+ " 'Firefox 4 and 5 are also supported but you ' +\n",
+ " 'have to enable WebSockets in about:config.'\n",
+ " );\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n",
+ " this.id = figure_id;\n",
+ "\n",
+ " this.ws = websocket;\n",
+ "\n",
+ " this.supports_binary = this.ws.binaryType !== undefined;\n",
+ "\n",
+ " if (!this.supports_binary) {\n",
+ " var warnings = document.getElementById('mpl-warnings');\n",
+ " if (warnings) {\n",
+ " warnings.style.display = 'block';\n",
+ " warnings.textContent =\n",
+ " 'This browser does not support binary websocket messages. ' +\n",
+ " 'Performance may be slow.';\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " this.imageObj = new Image();\n",
+ "\n",
+ " this.context = undefined;\n",
+ " this.message = undefined;\n",
+ " this.canvas = undefined;\n",
+ " this.rubberband_canvas = undefined;\n",
+ " this.rubberband_context = undefined;\n",
+ " this.format_dropdown = undefined;\n",
+ "\n",
+ " this.image_mode = 'full';\n",
+ "\n",
+ " this.root = document.createElement('div');\n",
+ " this.root.setAttribute('style', 'display: inline-block');\n",
+ " this._root_extra_style(this.root);\n",
+ "\n",
+ " parent_element.appendChild(this.root);\n",
+ "\n",
+ " this._init_header(this);\n",
+ " this._init_canvas(this);\n",
+ " this._init_toolbar(this);\n",
+ "\n",
+ " var fig = this;\n",
+ "\n",
+ " this.waiting = false;\n",
+ "\n",
+ " this.ws.onopen = function () {\n",
+ " fig.send_message('supports_binary', { value: fig.supports_binary });\n",
+ " fig.send_message('send_image_mode', {});\n",
+ " if (fig.ratio !== 1) {\n",
+ " fig.send_message('set_device_pixel_ratio', {\n",
+ " device_pixel_ratio: fig.ratio,\n",
+ " });\n",
+ " }\n",
+ " fig.send_message('refresh', {});\n",
+ " };\n",
+ "\n",
+ " this.imageObj.onload = function () {\n",
+ " if (fig.image_mode === 'full') {\n",
+ " // Full images could contain transparency (where diff images\n",
+ " // almost always do), so we need to clear the canvas so that\n",
+ " // there is no ghosting.\n",
+ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
+ " }\n",
+ " fig.context.drawImage(fig.imageObj, 0, 0);\n",
+ " };\n",
+ "\n",
+ " this.imageObj.onunload = function () {\n",
+ " fig.ws.close();\n",
+ " };\n",
+ "\n",
+ " this.ws.onmessage = this._make_on_message_function(this);\n",
+ "\n",
+ " this.ondownload = ondownload;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_header = function () {\n",
+ " var titlebar = document.createElement('div');\n",
+ " titlebar.classList =\n",
+ " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n",
+ " var titletext = document.createElement('div');\n",
+ " titletext.classList = 'ui-dialog-title';\n",
+ " titletext.setAttribute(\n",
+ " 'style',\n",
+ " 'width: 100%; text-align: center; padding: 3px;'\n",
+ " );\n",
+ " titlebar.appendChild(titletext);\n",
+ " this.root.appendChild(titlebar);\n",
+ " this.header = titletext;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n",
+ "\n",
+ "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n",
+ "\n",
+ "mpl.figure.prototype._init_canvas = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var canvas_div = (this.canvas_div = document.createElement('div'));\n",
+ " canvas_div.setAttribute(\n",
+ " 'style',\n",
+ " 'border: 1px solid #ddd;' +\n",
+ " 'box-sizing: content-box;' +\n",
+ " 'clear: both;' +\n",
+ " 'min-height: 1px;' +\n",
+ " 'min-width: 1px;' +\n",
+ " 'outline: 0;' +\n",
+ " 'overflow: hidden;' +\n",
+ " 'position: relative;' +\n",
+ " 'resize: both;'\n",
+ " );\n",
+ "\n",
+ " function on_keyboard_event_closure(name) {\n",
+ " return function (event) {\n",
+ " return fig.key_event(event, name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " canvas_div.addEventListener(\n",
+ " 'keydown',\n",
+ " on_keyboard_event_closure('key_press')\n",
+ " );\n",
+ " canvas_div.addEventListener(\n",
+ " 'keyup',\n",
+ " on_keyboard_event_closure('key_release')\n",
+ " );\n",
+ "\n",
+ " this._canvas_extra_style(canvas_div);\n",
+ " this.root.appendChild(canvas_div);\n",
+ "\n",
+ " var canvas = (this.canvas = document.createElement('canvas'));\n",
+ " canvas.classList.add('mpl-canvas');\n",
+ " canvas.setAttribute('style', 'box-sizing: content-box;');\n",
+ "\n",
+ " this.context = canvas.getContext('2d');\n",
+ "\n",
+ " var backingStore =\n",
+ " this.context.backingStorePixelRatio ||\n",
+ " this.context.webkitBackingStorePixelRatio ||\n",
+ " this.context.mozBackingStorePixelRatio ||\n",
+ " this.context.msBackingStorePixelRatio ||\n",
+ " this.context.oBackingStorePixelRatio ||\n",
+ " this.context.backingStorePixelRatio ||\n",
+ " 1;\n",
+ "\n",
+ " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
+ "\n",
+ " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n",
+ " 'canvas'\n",
+ " ));\n",
+ " rubberband_canvas.setAttribute(\n",
+ " 'style',\n",
+ " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n",
+ " );\n",
+ "\n",
+ " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n",
+ " if (this.ResizeObserver === undefined) {\n",
+ " if (window.ResizeObserver !== undefined) {\n",
+ " this.ResizeObserver = window.ResizeObserver;\n",
+ " } else {\n",
+ " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n",
+ " this.ResizeObserver = obs.ResizeObserver;\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n",
+ " var nentries = entries.length;\n",
+ " for (var i = 0; i < nentries; i++) {\n",
+ " var entry = entries[i];\n",
+ " var width, height;\n",
+ " if (entry.contentBoxSize) {\n",
+ " if (entry.contentBoxSize instanceof Array) {\n",
+ " // Chrome 84 implements new version of spec.\n",
+ " width = entry.contentBoxSize[0].inlineSize;\n",
+ " height = entry.contentBoxSize[0].blockSize;\n",
+ " } else {\n",
+ " // Firefox implements old version of spec.\n",
+ " width = entry.contentBoxSize.inlineSize;\n",
+ " height = entry.contentBoxSize.blockSize;\n",
+ " }\n",
+ " } else {\n",
+ " // Chrome <84 implements even older version of spec.\n",
+ " width = entry.contentRect.width;\n",
+ " height = entry.contentRect.height;\n",
+ " }\n",
+ "\n",
+ " // Keep the size of the canvas and rubber band canvas in sync with\n",
+ " // the canvas container.\n",
+ " if (entry.devicePixelContentBoxSize) {\n",
+ " // Chrome 84 implements new version of spec.\n",
+ " canvas.setAttribute(\n",
+ " 'width',\n",
+ " entry.devicePixelContentBoxSize[0].inlineSize\n",
+ " );\n",
+ " canvas.setAttribute(\n",
+ " 'height',\n",
+ " entry.devicePixelContentBoxSize[0].blockSize\n",
+ " );\n",
+ " } else {\n",
+ " canvas.setAttribute('width', width * fig.ratio);\n",
+ " canvas.setAttribute('height', height * fig.ratio);\n",
+ " }\n",
+ " canvas.setAttribute(\n",
+ " 'style',\n",
+ " 'width: ' + width + 'px; height: ' + height + 'px;'\n",
+ " );\n",
+ "\n",
+ " rubberband_canvas.setAttribute('width', width);\n",
+ " rubberband_canvas.setAttribute('height', height);\n",
+ "\n",
+ " // And update the size in Python. We ignore the initial 0/0 size\n",
+ " // that occurs as the element is placed into the DOM, which should\n",
+ " // otherwise not happen due to the minimum size styling.\n",
+ " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n",
+ " fig.request_resize(width, height);\n",
+ " }\n",
+ " }\n",
+ " });\n",
+ " this.resizeObserverInstance.observe(canvas_div);\n",
+ "\n",
+ " function on_mouse_event_closure(name) {\n",
+ " return function (event) {\n",
+ " return fig.mouse_event(event, name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mousedown',\n",
+ " on_mouse_event_closure('button_press')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseup',\n",
+ " on_mouse_event_closure('button_release')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'dblclick',\n",
+ " on_mouse_event_closure('dblclick')\n",
+ " );\n",
+ " // Throttle sequential mouse events to 1 every 20ms.\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mousemove',\n",
+ " on_mouse_event_closure('motion_notify')\n",
+ " );\n",
+ "\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseenter',\n",
+ " on_mouse_event_closure('figure_enter')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseleave',\n",
+ " on_mouse_event_closure('figure_leave')\n",
+ " );\n",
+ "\n",
+ " canvas_div.addEventListener('wheel', function (event) {\n",
+ " if (event.deltaY < 0) {\n",
+ " event.step = 1;\n",
+ " } else {\n",
+ " event.step = -1;\n",
+ " }\n",
+ " on_mouse_event_closure('scroll')(event);\n",
+ " });\n",
+ "\n",
+ " canvas_div.appendChild(canvas);\n",
+ " canvas_div.appendChild(rubberband_canvas);\n",
+ "\n",
+ " this.rubberband_context = rubberband_canvas.getContext('2d');\n",
+ " this.rubberband_context.strokeStyle = '#000000';\n",
+ "\n",
+ " this._resize_canvas = function (width, height, forward) {\n",
+ " if (forward) {\n",
+ " canvas_div.style.width = width + 'px';\n",
+ " canvas_div.style.height = height + 'px';\n",
+ " }\n",
+ " };\n",
+ "\n",
+ " // Disable right mouse context menu.\n",
+ " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n",
+ " event.preventDefault();\n",
+ " return false;\n",
+ " });\n",
+ "\n",
+ " function set_focus() {\n",
+ " canvas.focus();\n",
+ " canvas_div.focus();\n",
+ " }\n",
+ "\n",
+ " window.setTimeout(set_focus, 100);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_toolbar = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var toolbar = document.createElement('div');\n",
+ " toolbar.classList = 'mpl-toolbar';\n",
+ " this.root.appendChild(toolbar);\n",
+ "\n",
+ " function on_click_closure(name) {\n",
+ " return function (_event) {\n",
+ " return fig.toolbar_button_onclick(name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " function on_mouseover_closure(tooltip) {\n",
+ " return function (event) {\n",
+ " if (!event.currentTarget.disabled) {\n",
+ " return fig.toolbar_button_onmouseover(tooltip);\n",
+ " }\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " fig.buttons = {};\n",
+ " var buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'mpl-button-group';\n",
+ " for (var toolbar_ind in mpl.toolbar_items) {\n",
+ " var name = mpl.toolbar_items[toolbar_ind][0];\n",
+ " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+ " var image = mpl.toolbar_items[toolbar_ind][2];\n",
+ " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+ "\n",
+ " if (!name) {\n",
+ " /* Instead of a spacer, we start a new button group. */\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ " buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'mpl-button-group';\n",
+ " continue;\n",
+ " }\n",
+ "\n",
+ " var button = (fig.buttons[name] = document.createElement('button'));\n",
+ " button.classList = 'mpl-widget';\n",
+ " button.setAttribute('role', 'button');\n",
+ " button.setAttribute('aria-disabled', 'false');\n",
+ " button.addEventListener('click', on_click_closure(method_name));\n",
+ " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+ "\n",
+ " var icon_img = document.createElement('img');\n",
+ " icon_img.src = '_images/' + image + '.png';\n",
+ " icon_img.srcset = '_images/' + image + '_large.png 2x';\n",
+ " icon_img.alt = tooltip;\n",
+ " button.appendChild(icon_img);\n",
+ "\n",
+ " buttonGroup.appendChild(button);\n",
+ " }\n",
+ "\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ "\n",
+ " var fmt_picker = document.createElement('select');\n",
+ " fmt_picker.classList = 'mpl-widget';\n",
+ " toolbar.appendChild(fmt_picker);\n",
+ " this.format_dropdown = fmt_picker;\n",
+ "\n",
+ " for (var ind in mpl.extensions) {\n",
+ " var fmt = mpl.extensions[ind];\n",
+ " var option = document.createElement('option');\n",
+ " option.selected = fmt === mpl.default_extension;\n",
+ " option.innerHTML = fmt;\n",
+ " fmt_picker.appendChild(option);\n",
+ " }\n",
+ "\n",
+ " var status_bar = document.createElement('span');\n",
+ " status_bar.classList = 'mpl-message';\n",
+ " toolbar.appendChild(status_bar);\n",
+ " this.message = status_bar;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n",
+ " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
+ " // which will in turn request a refresh of the image.\n",
+ " this.send_message('resize', { width: x_pixels, height: y_pixels });\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.send_message = function (type, properties) {\n",
+ " properties['type'] = type;\n",
+ " properties['figure_id'] = this.id;\n",
+ " this.ws.send(JSON.stringify(properties));\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.send_draw_message = function () {\n",
+ " if (!this.waiting) {\n",
+ " this.waiting = true;\n",
+ " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+ " var format_dropdown = fig.format_dropdown;\n",
+ " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
+ " fig.ondownload(fig, format);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_resize = function (fig, msg) {\n",
+ " var size = msg['size'];\n",
+ " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n",
+ " fig._resize_canvas(size[0], size[1], msg['forward']);\n",
+ " fig.send_message('refresh', {});\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n",
+ " var x0 = msg['x0'] / fig.ratio;\n",
+ " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n",
+ " var x1 = msg['x1'] / fig.ratio;\n",
+ " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n",
+ " x0 = Math.floor(x0) + 0.5;\n",
+ " y0 = Math.floor(y0) + 0.5;\n",
+ " x1 = Math.floor(x1) + 0.5;\n",
+ " y1 = Math.floor(y1) + 0.5;\n",
+ " var min_x = Math.min(x0, x1);\n",
+ " var min_y = Math.min(y0, y1);\n",
+ " var width = Math.abs(x1 - x0);\n",
+ " var height = Math.abs(y1 - y0);\n",
+ "\n",
+ " fig.rubberband_context.clearRect(\n",
+ " 0,\n",
+ " 0,\n",
+ " fig.canvas.width / fig.ratio,\n",
+ " fig.canvas.height / fig.ratio\n",
+ " );\n",
+ "\n",
+ " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n",
+ " // Updates the figure title.\n",
+ " fig.header.textContent = msg['label'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n",
+ " fig.rubberband_canvas.style.cursor = msg['cursor'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_message = function (fig, msg) {\n",
+ " fig.message.textContent = msg['message'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n",
+ " // Request the server to send over a new figure.\n",
+ " fig.send_draw_message();\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n",
+ " fig.image_mode = msg['mode'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n",
+ " for (var key in msg) {\n",
+ " if (!(key in fig.buttons)) {\n",
+ " continue;\n",
+ " }\n",
+ " fig.buttons[key].disabled = !msg[key];\n",
+ " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n",
+ " if (msg['mode'] === 'PAN') {\n",
+ " fig.buttons['Pan'].classList.add('active');\n",
+ " fig.buttons['Zoom'].classList.remove('active');\n",
+ " } else if (msg['mode'] === 'ZOOM') {\n",
+ " fig.buttons['Pan'].classList.remove('active');\n",
+ " fig.buttons['Zoom'].classList.add('active');\n",
+ " } else {\n",
+ " fig.buttons['Pan'].classList.remove('active');\n",
+ " fig.buttons['Zoom'].classList.remove('active');\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.updated_canvas_event = function () {\n",
+ " // Called whenever the canvas gets updated.\n",
+ " this.send_message('ack', {});\n",
+ "};\n",
+ "\n",
+ "// A function to construct a web socket function for onmessage handling.\n",
+ "// Called in the figure constructor.\n",
+ "mpl.figure.prototype._make_on_message_function = function (fig) {\n",
+ " return function socket_on_message(evt) {\n",
+ " if (evt.data instanceof Blob) {\n",
+ " var img = evt.data;\n",
+ " if (img.type !== 'image/png') {\n",
+ " /* FIXME: We get \"Resource interpreted as Image but\n",
+ " * transferred with MIME type text/plain:\" errors on\n",
+ " * Chrome. But how to set the MIME type? It doesn't seem\n",
+ " * to be part of the websocket stream */\n",
+ " img.type = 'image/png';\n",
+ " }\n",
+ "\n",
+ " /* Free the memory for the previous frames */\n",
+ " if (fig.imageObj.src) {\n",
+ " (window.URL || window.webkitURL).revokeObjectURL(\n",
+ " fig.imageObj.src\n",
+ " );\n",
+ " }\n",
+ "\n",
+ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
+ " img\n",
+ " );\n",
+ " fig.updated_canvas_event();\n",
+ " fig.waiting = false;\n",
+ " return;\n",
+ " } else if (\n",
+ " typeof evt.data === 'string' &&\n",
+ " evt.data.slice(0, 21) === 'data:image/png;base64'\n",
+ " ) {\n",
+ " fig.imageObj.src = evt.data;\n",
+ " fig.updated_canvas_event();\n",
+ " fig.waiting = false;\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " var msg = JSON.parse(evt.data);\n",
+ " var msg_type = msg['type'];\n",
+ "\n",
+ " // Call the \"handle_{type}\" callback, which takes\n",
+ " // the figure and JSON message as its only arguments.\n",
+ " try {\n",
+ " var callback = fig['handle_' + msg_type];\n",
+ " } catch (e) {\n",
+ " console.log(\n",
+ " \"No handler for the '\" + msg_type + \"' message type: \",\n",
+ " msg\n",
+ " );\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " if (callback) {\n",
+ " try {\n",
+ " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
+ " callback(fig, msg);\n",
+ " } catch (e) {\n",
+ " console.log(\n",
+ " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n",
+ " e,\n",
+ " e.stack,\n",
+ " msg\n",
+ " );\n",
+ " }\n",
+ " }\n",
+ " };\n",
+ "};\n",
+ "\n",
+ "// from https://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
+ "mpl.findpos = function (e) {\n",
+ " //this section is from http://www.quirksmode.org/js/events_properties.html\n",
+ " var targ;\n",
+ " if (!e) {\n",
+ " e = window.event;\n",
+ " }\n",
+ " if (e.target) {\n",
+ " targ = e.target;\n",
+ " } else if (e.srcElement) {\n",
+ " targ = e.srcElement;\n",
+ " }\n",
+ " if (targ.nodeType === 3) {\n",
+ " // defeat Safari bug\n",
+ " targ = targ.parentNode;\n",
+ " }\n",
+ "\n",
+ " // pageX,Y are the mouse positions relative to the document\n",
+ " var boundingRect = targ.getBoundingClientRect();\n",
+ " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n",
+ " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n",
+ "\n",
+ " return { x: x, y: y };\n",
+ "};\n",
+ "\n",
+ "/*\n",
+ " * return a copy of an object with only non-object keys\n",
+ " * we need this to avoid circular references\n",
+ " * https://stackoverflow.com/a/24161582/3208463\n",
+ " */\n",
+ "function simpleKeys(original) {\n",
+ " return Object.keys(original).reduce(function (obj, key) {\n",
+ " if (typeof original[key] !== 'object') {\n",
+ " obj[key] = original[key];\n",
+ " }\n",
+ " return obj;\n",
+ " }, {});\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.mouse_event = function (event, name) {\n",
+ " var canvas_pos = mpl.findpos(event);\n",
+ "\n",
+ " if (name === 'button_press') {\n",
+ " this.canvas.focus();\n",
+ " this.canvas_div.focus();\n",
+ " }\n",
+ "\n",
+ " var x = canvas_pos.x * this.ratio;\n",
+ " var y = canvas_pos.y * this.ratio;\n",
+ "\n",
+ " this.send_message(name, {\n",
+ " x: x,\n",
+ " y: y,\n",
+ " button: event.button,\n",
+ " step: event.step,\n",
+ " guiEvent: simpleKeys(event),\n",
+ " });\n",
+ "\n",
+ " /* This prevents the web browser from automatically changing to\n",
+ " * the text insertion cursor when the button is pressed. We want\n",
+ " * to control all of the cursor setting manually through the\n",
+ " * 'cursor' event from matplotlib */\n",
+ " event.preventDefault();\n",
+ " return false;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n",
+ " // Handle any extra behaviour associated with a key event\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.key_event = function (event, name) {\n",
+ " // Prevent repeat events\n",
+ " if (name === 'key_press') {\n",
+ " if (event.key === this._key) {\n",
+ " return;\n",
+ " } else {\n",
+ " this._key = event.key;\n",
+ " }\n",
+ " }\n",
+ " if (name === 'key_release') {\n",
+ " this._key = null;\n",
+ " }\n",
+ "\n",
+ " var value = '';\n",
+ " if (event.ctrlKey && event.key !== 'Control') {\n",
+ " value += 'ctrl+';\n",
+ " }\n",
+ " else if (event.altKey && event.key !== 'Alt') {\n",
+ " value += 'alt+';\n",
+ " }\n",
+ " else if (event.shiftKey && event.key !== 'Shift') {\n",
+ " value += 'shift+';\n",
+ " }\n",
+ "\n",
+ " value += 'k' + event.key;\n",
+ "\n",
+ " this._key_event_extra(event, name);\n",
+ "\n",
+ " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n",
+ " return false;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n",
+ " if (name === 'download') {\n",
+ " this.handle_save(this, null);\n",
+ " } else {\n",
+ " this.send_message('toolbar_button', { name: name });\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n",
+ " this.message.textContent = tooltip;\n",
+ "};\n",
+ "\n",
+ "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n",
+ "// prettier-ignore\n",
+ "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n",
+ "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
+ "\n",
+ "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
+ "\n",
+ "mpl.default_extension = \"png\";/* global mpl */\n",
+ "\n",
+ "var comm_websocket_adapter = function (comm) {\n",
+ " // Create a \"websocket\"-like object which calls the given IPython comm\n",
+ " // object with the appropriate methods. Currently this is a non binary\n",
+ " // socket, so there is still some room for performance tuning.\n",
+ " var ws = {};\n",
+ "\n",
+ " ws.binaryType = comm.kernel.ws.binaryType;\n",
+ " ws.readyState = comm.kernel.ws.readyState;\n",
+ " function updateReadyState(_event) {\n",
+ " if (comm.kernel.ws) {\n",
+ " ws.readyState = comm.kernel.ws.readyState;\n",
+ " } else {\n",
+ " ws.readyState = 3; // Closed state.\n",
+ " }\n",
+ " }\n",
+ " comm.kernel.ws.addEventListener('open', updateReadyState);\n",
+ " comm.kernel.ws.addEventListener('close', updateReadyState);\n",
+ " comm.kernel.ws.addEventListener('error', updateReadyState);\n",
+ "\n",
+ " ws.close = function () {\n",
+ " comm.close();\n",
+ " };\n",
+ " ws.send = function (m) {\n",
+ " //console.log('sending', m);\n",
+ " comm.send(m);\n",
+ " };\n",
+ " // Register the callback with on_msg.\n",
+ " comm.on_msg(function (msg) {\n",
+ " //console.log('receiving', msg['content']['data'], msg);\n",
+ " var data = msg['content']['data'];\n",
+ " if (data['blob'] !== undefined) {\n",
+ " data = {\n",
+ " data: new Blob(msg['buffers'], { type: data['blob'] }),\n",
+ " };\n",
+ " }\n",
+ " // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
+ " ws.onmessage(data);\n",
+ " });\n",
+ " return ws;\n",
+ "};\n",
+ "\n",
+ "mpl.mpl_figure_comm = function (comm, msg) {\n",
+ " // This is the function which gets called when the mpl process\n",
+ " // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
+ "\n",
+ " var id = msg.content.data.id;\n",
+ " // Get hold of the div created by the display call when the Comm\n",
+ " // socket was opened in Python.\n",
+ " var element = document.getElementById(id);\n",
+ " var ws_proxy = comm_websocket_adapter(comm);\n",
+ "\n",
+ " function ondownload(figure, _format) {\n",
+ " window.open(figure.canvas.toDataURL());\n",
+ " }\n",
+ "\n",
+ " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n",
+ "\n",
+ " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
+ " // web socket which is closed, not our websocket->open comm proxy.\n",
+ " ws_proxy.onopen();\n",
+ "\n",
+ " fig.parent_element = element;\n",
+ " fig.cell_info = mpl.find_output_cell(\"\");\n",
+ " if (!fig.cell_info) {\n",
+ " console.error('Failed to find cell for figure', id, fig);\n",
+ " return;\n",
+ " }\n",
+ " fig.cell_info[0].output_area.element.on(\n",
+ " 'cleared',\n",
+ " { fig: fig },\n",
+ " fig._remove_fig_handler\n",
+ " );\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_close = function (fig, msg) {\n",
+ " var width = fig.canvas.width / fig.ratio;\n",
+ " fig.cell_info[0].output_area.element.off(\n",
+ " 'cleared',\n",
+ " fig._remove_fig_handler\n",
+ " );\n",
+ " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n",
+ "\n",
+ " // Update the output cell to use the data from the current canvas.\n",
+ " fig.push_to_output();\n",
+ " var dataURL = fig.canvas.toDataURL();\n",
+ " // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
+ " // the notebook keyboard shortcuts fail.\n",
+ " IPython.keyboard_manager.enable();\n",
+ " fig.parent_element.innerHTML =\n",
+ " '
';\n",
+ " fig.close_ws(fig, msg);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.close_ws = function (fig, msg) {\n",
+ " fig.send_message('closing', msg);\n",
+ " // fig.ws.close()\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n",
+ " // Turn the data on the canvas into data in the output cell.\n",
+ " var width = this.canvas.width / this.ratio;\n",
+ " var dataURL = this.canvas.toDataURL();\n",
+ " this.cell_info[1]['text/html'] =\n",
+ " '
';\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.updated_canvas_event = function () {\n",
+ " // Tell IPython that the notebook contents must change.\n",
+ " IPython.notebook.set_dirty(true);\n",
+ " this.send_message('ack', {});\n",
+ " var fig = this;\n",
+ " // Wait a second, then push the new image to the DOM so\n",
+ " // that it is saved nicely (might be nice to debounce this).\n",
+ " setTimeout(function () {\n",
+ " fig.push_to_output();\n",
+ " }, 1000);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_toolbar = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var toolbar = document.createElement('div');\n",
+ " toolbar.classList = 'btn-toolbar';\n",
+ " this.root.appendChild(toolbar);\n",
+ "\n",
+ " function on_click_closure(name) {\n",
+ " return function (_event) {\n",
+ " return fig.toolbar_button_onclick(name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " function on_mouseover_closure(tooltip) {\n",
+ " return function (event) {\n",
+ " if (!event.currentTarget.disabled) {\n",
+ " return fig.toolbar_button_onmouseover(tooltip);\n",
+ " }\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " fig.buttons = {};\n",
+ " var buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'btn-group';\n",
+ " var button;\n",
+ " for (var toolbar_ind in mpl.toolbar_items) {\n",
+ " var name = mpl.toolbar_items[toolbar_ind][0];\n",
+ " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+ " var image = mpl.toolbar_items[toolbar_ind][2];\n",
+ " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+ "\n",
+ " if (!name) {\n",
+ " /* Instead of a spacer, we start a new button group. */\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ " buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'btn-group';\n",
+ " continue;\n",
+ " }\n",
+ "\n",
+ " button = fig.buttons[name] = document.createElement('button');\n",
+ " button.classList = 'btn btn-default';\n",
+ " button.href = '#';\n",
+ " button.title = name;\n",
+ " button.innerHTML = '';\n",
+ " button.addEventListener('click', on_click_closure(method_name));\n",
+ " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+ " buttonGroup.appendChild(button);\n",
+ " }\n",
+ "\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ "\n",
+ " // Add the status bar.\n",
+ " var status_bar = document.createElement('span');\n",
+ " status_bar.classList = 'mpl-message pull-right';\n",
+ " toolbar.appendChild(status_bar);\n",
+ " this.message = status_bar;\n",
+ "\n",
+ " // Add the close button to the window.\n",
+ " var buttongrp = document.createElement('div');\n",
+ " buttongrp.classList = 'btn-group inline pull-right';\n",
+ " button = document.createElement('button');\n",
+ " button.classList = 'btn btn-mini btn-primary';\n",
+ " button.href = '#';\n",
+ " button.title = 'Stop Interaction';\n",
+ " button.innerHTML = '';\n",
+ " button.addEventListener('click', function (_evt) {\n",
+ " fig.handle_close(fig, {});\n",
+ " });\n",
+ " button.addEventListener(\n",
+ " 'mouseover',\n",
+ " on_mouseover_closure('Stop Interaction')\n",
+ " );\n",
+ " buttongrp.appendChild(button);\n",
+ " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n",
+ " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._remove_fig_handler = function (event) {\n",
+ " var fig = event.data.fig;\n",
+ " if (event.target !== this) {\n",
+ " // Ignore bubbled events from children.\n",
+ " return;\n",
+ " }\n",
+ " fig.close_ws(fig, {});\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._root_extra_style = function (el) {\n",
+ " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._canvas_extra_style = function (el) {\n",
+ " // this is important to make the div 'focusable\n",
+ " el.setAttribute('tabindex', 0);\n",
+ " // reach out to IPython and tell the keyboard manager to turn it's self\n",
+ " // off when our div gets focus\n",
+ "\n",
+ " // location in version 3\n",
+ " if (IPython.notebook.keyboard_manager) {\n",
+ " IPython.notebook.keyboard_manager.register_events(el);\n",
+ " } else {\n",
+ " // location in version 2\n",
+ " IPython.keyboard_manager.register_events(el);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._key_event_extra = function (event, _name) {\n",
+ " // Check for shift+enter\n",
+ " if (event.shiftKey && event.which === 13) {\n",
+ " this.canvas_div.blur();\n",
+ " // select the cell after this one\n",
+ " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n",
+ " IPython.notebook.select(index + 1);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+ " fig.ondownload(fig, null);\n",
+ "};\n",
+ "\n",
+ "mpl.find_output_cell = function (html_output) {\n",
+ " // Return the cell and output element which can be found *uniquely* in the notebook.\n",
+ " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
+ " // IPython event is triggered only after the cells have been serialised, which for\n",
+ " // our purposes (turning an active figure into a static one), is too late.\n",
+ " var cells = IPython.notebook.get_cells();\n",
+ " var ncells = cells.length;\n",
+ " for (var i = 0; i < ncells; i++) {\n",
+ " var cell = cells[i];\n",
+ " if (cell.cell_type === 'code') {\n",
+ " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n",
+ " var data = cell.output_area.outputs[j];\n",
+ " if (data.data) {\n",
+ " // IPython >= 3 moved mimebundle to data attribute of output\n",
+ " data = data.data;\n",
+ " }\n",
+ " if (data['text/html'] === html_output) {\n",
+ " return [cell, data, j];\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "// Register the function which deals with the matplotlib target/channel.\n",
+ "// The kernel may be null if the page has been refreshed.\n",
+ "if (IPython.notebook.kernel !== null) {\n",
+ " IPython.notebook.kernel.comm_manager.register_target(\n",
+ " 'matplotlib',\n",
+ " mpl.mpl_figure_comm\n",
+ " );\n",
+ "}\n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "
"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " mean std num\n",
+ "s3-benchmark GET 708.292857 208.375645 14\n",
+ "WARP GET 4542.232857 77.069324 14\n",
+ "s3-benchmark PUT 802.750000 34.320751 14\n",
+ "WARP PUT 1105.368571 81.658908 14\n",
+ "s3-benchmark DELETE 739.635714 196.806662 14\n",
+ "WARP DELETE 2425.240714 155.261849 14\n"
+ ]
+ }
+ ],
+ "source": [
+ "%matplotlib notebook\n",
+ "\n",
+ "# Read in results\n",
+ "## Params - Either specify a file prefix or a whole folder\n",
+ "file_prefix = None # Path(\"./results/2022-07-05T20:00:06_throughput_write_\")\n",
+ "folder = Path(\"./results/compare_s3benchmark_vs_warp\")\n",
+ "\n",
+ "## Plot code\n",
+ "path = file_prefix if file_prefix else folder\n",
+ "\n",
+ "\n",
+ "df_get = pd.concat({\n",
+ " \"s3-benchmark\": load_data(path, metric=\"get_bw_in_mibi\", suffix=\"_get_put_a.json\"),\n",
+ " \"WARP\": load_data(path, metric=\"get_bw_in_MiB\", suffix=\"_get_put_b.json\"),\n",
+ "})\n",
+ "df_get.rename(index={\"s3benchmark_vs_warp_get_put_a\": \"GET\", \"s3benchmark_vs_warp_get_put_b\": \"GET\"}, inplace=True)\n",
+ "\n",
+ "df_put = pd.concat({\n",
+ " \"s3-benchmark\": load_data(path, metric=\"put_bw_in_mibi\", suffix=\"_get_put_a.json\"),\n",
+ " \"WARP\": load_data(path, metric=\"put_bw_in_MiB\", suffix=\"_get_put_b.json\"),\n",
+ "})\n",
+ "df_put.rename(index={\"s3benchmark_vs_warp_get_put_a\": \"PUT\", \"s3benchmark_vs_warp_get_put_b\": \"PUT\"}, inplace=True)\n",
+ "\n",
+ "df_get_put = pd.concat([df_get, df_put])\n",
+ "\n",
+ "\n",
+ "df_get_ops = pd.concat({\n",
+ " \"s3-benchmark\": load_data(path, metric=\"get_op_per_s\", suffix=\"get_put_ops_a.json\"),\n",
+ " \"WARP\": load_data(path, metric=\"get_ops\", suffix=\"get_put_ops_b.json\"),\n",
+ "})\n",
+ "df_put_ops = pd.concat({\n",
+ " \"s3-benchmark\": load_data(path, metric=\"put_op_per_s\", suffix=\"get_put_ops_a.json\"),\n",
+ " \"WARP\": load_data(path, metric=\"put_ops\", suffix=\"get_put_ops_b.json\"),\n",
+ "})\n",
+ "df_del_ops = pd.concat({\n",
+ " \"s3-benchmark\": load_data(path, metric=\"del_op_per_s\", suffix=\"delete_a.json\"),\n",
+ " \"WARP\": load_data(path, metric=\"delete_ops\", suffix=\"delete_b.json\"),\n",
+ "})\n",
+ "df_get_ops.rename(index={\"s3benchmark_vs_warp_get_put_ops_a\": \"GET\", \"s3benchmark_vs_warp_get_put_ops_b\": \"GET\"}, inplace=True)\n",
+ "df_put_ops.rename(index={\"s3benchmark_vs_warp_get_put_ops_a\": \"PUT\", \"s3benchmark_vs_warp_get_put_ops_b\": \"PUT\"}, inplace=True)\n",
+ "df_del_ops.rename(index={\"s3benchmark_vs_warp_delete_a\": \"DELETE\", \"s3benchmark_vs_warp_delete_b\": \"DELETE\"}, inplace=True)\n",
+ "\n",
+ "df_ops = pd.concat([df_get_ops, df_put_ops, df_del_ops])\n",
+ "\n",
+ "fig, (ax1, ax2) = plt.subplots(1, 2)\n",
+ "fig.suptitle('Tool comparison: s3-benchmark vs. WARP')\n",
+ "\n",
+ "i2 = [\"GET\",\"PUT\"]\n",
+ "i3 = [\"GET\",\"PUT\",\"DELETE\"]\n",
+ "\n",
+ "def fix_df(df, index):\n",
+ " \"\"\"unstack changes the order, fix order and rename index'\"\"\"\n",
+ " df = df.reindex(columns=[\"s3-benchmark\",\"WARP\"], index=index)\n",
+ " return df\n",
+ "\n",
+ "fix_df(df_get_put[\"mean\"].unstack(level=0), i2).plot(\n",
+ " ax=ax1,\n",
+ " kind='bar', rot=0,\n",
+ " xlabel='Mode',\n",
+ " ylabel='Bandwidth (MiB/s)',\n",
+ " title=f\"Bandwidth\",\n",
+ " yerr=fix_df(df_get_put[\"std\"].unstack(level=0), i2)\n",
+ ")\n",
+ "\n",
+ "fix_df(df_ops[\"mean\"].unstack(level=0), i3).plot(\n",
+ " ax=ax2,\n",
+ " kind='bar', rot=0,\n",
+ " xlabel='',\n",
+ " ylabel='Operations ($1/s$)',\n",
+ " title=\"Operations\",\n",
+ " yerr=fix_df(df_ops[\"std\"].unstack(level=0), i3)\n",
+ ")\n",
+ "print(df_ops)\n",
+ "\n",
+ "ax1.get_legend().remove()\n",
+ "ax2.legend(loc=\"center right\")\n",
+ "plt.tight_layout()\n",
+ "plt.savefig(\"s3benchmark_vs_warp.png\",bbox_inches='tight')\n",
+ "plt.show()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "25d0706c",
+ "metadata": {},
+ "source": [
+ "# Experiment: Daytime evaluation"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "id": "4fccebe5",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Netapp A400 & 217 & 539 & 383 & 4 & 55\\\\\n",
+ " & 191 & 540 & 384 & 4 & 57\\\\\n",
+ "Isilon & 250 & 1036 & 791 & 13 & 172\\\\\n",
+ " & 297 & 1034 & 785 & 14 & 172\\\\\n",
+ "Netapp StorageGRID & 428 & 1110 & 1053 & 6 & 78\\\\\n",
+ " & 715 & 1110 & 1055 & 6 & 70\\\\\n"
+ ]
+ },
+ {
+ "data": {
+ "application/javascript": [
+ "/* Put everything inside the global mpl namespace */\n",
+ "/* global mpl */\n",
+ "window.mpl = {};\n",
+ "\n",
+ "mpl.get_websocket_type = function () {\n",
+ " if (typeof WebSocket !== 'undefined') {\n",
+ " return WebSocket;\n",
+ " } else if (typeof MozWebSocket !== 'undefined') {\n",
+ " return MozWebSocket;\n",
+ " } else {\n",
+ " alert(\n",
+ " 'Your browser does not have WebSocket support. ' +\n",
+ " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
+ " 'Firefox 4 and 5 are also supported but you ' +\n",
+ " 'have to enable WebSockets in about:config.'\n",
+ " );\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n",
+ " this.id = figure_id;\n",
+ "\n",
+ " this.ws = websocket;\n",
+ "\n",
+ " this.supports_binary = this.ws.binaryType !== undefined;\n",
+ "\n",
+ " if (!this.supports_binary) {\n",
+ " var warnings = document.getElementById('mpl-warnings');\n",
+ " if (warnings) {\n",
+ " warnings.style.display = 'block';\n",
+ " warnings.textContent =\n",
+ " 'This browser does not support binary websocket messages. ' +\n",
+ " 'Performance may be slow.';\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " this.imageObj = new Image();\n",
+ "\n",
+ " this.context = undefined;\n",
+ " this.message = undefined;\n",
+ " this.canvas = undefined;\n",
+ " this.rubberband_canvas = undefined;\n",
+ " this.rubberband_context = undefined;\n",
+ " this.format_dropdown = undefined;\n",
+ "\n",
+ " this.image_mode = 'full';\n",
+ "\n",
+ " this.root = document.createElement('div');\n",
+ " this.root.setAttribute('style', 'display: inline-block');\n",
+ " this._root_extra_style(this.root);\n",
+ "\n",
+ " parent_element.appendChild(this.root);\n",
+ "\n",
+ " this._init_header(this);\n",
+ " this._init_canvas(this);\n",
+ " this._init_toolbar(this);\n",
+ "\n",
+ " var fig = this;\n",
+ "\n",
+ " this.waiting = false;\n",
+ "\n",
+ " this.ws.onopen = function () {\n",
+ " fig.send_message('supports_binary', { value: fig.supports_binary });\n",
+ " fig.send_message('send_image_mode', {});\n",
+ " if (fig.ratio !== 1) {\n",
+ " fig.send_message('set_device_pixel_ratio', {\n",
+ " device_pixel_ratio: fig.ratio,\n",
+ " });\n",
+ " }\n",
+ " fig.send_message('refresh', {});\n",
+ " };\n",
+ "\n",
+ " this.imageObj.onload = function () {\n",
+ " if (fig.image_mode === 'full') {\n",
+ " // Full images could contain transparency (where diff images\n",
+ " // almost always do), so we need to clear the canvas so that\n",
+ " // there is no ghosting.\n",
+ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
+ " }\n",
+ " fig.context.drawImage(fig.imageObj, 0, 0);\n",
+ " };\n",
+ "\n",
+ " this.imageObj.onunload = function () {\n",
+ " fig.ws.close();\n",
+ " };\n",
+ "\n",
+ " this.ws.onmessage = this._make_on_message_function(this);\n",
+ "\n",
+ " this.ondownload = ondownload;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_header = function () {\n",
+ " var titlebar = document.createElement('div');\n",
+ " titlebar.classList =\n",
+ " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n",
+ " var titletext = document.createElement('div');\n",
+ " titletext.classList = 'ui-dialog-title';\n",
+ " titletext.setAttribute(\n",
+ " 'style',\n",
+ " 'width: 100%; text-align: center; padding: 3px;'\n",
+ " );\n",
+ " titlebar.appendChild(titletext);\n",
+ " this.root.appendChild(titlebar);\n",
+ " this.header = titletext;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n",
+ "\n",
+ "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n",
+ "\n",
+ "mpl.figure.prototype._init_canvas = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var canvas_div = (this.canvas_div = document.createElement('div'));\n",
+ " canvas_div.setAttribute(\n",
+ " 'style',\n",
+ " 'border: 1px solid #ddd;' +\n",
+ " 'box-sizing: content-box;' +\n",
+ " 'clear: both;' +\n",
+ " 'min-height: 1px;' +\n",
+ " 'min-width: 1px;' +\n",
+ " 'outline: 0;' +\n",
+ " 'overflow: hidden;' +\n",
+ " 'position: relative;' +\n",
+ " 'resize: both;'\n",
+ " );\n",
+ "\n",
+ " function on_keyboard_event_closure(name) {\n",
+ " return function (event) {\n",
+ " return fig.key_event(event, name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " canvas_div.addEventListener(\n",
+ " 'keydown',\n",
+ " on_keyboard_event_closure('key_press')\n",
+ " );\n",
+ " canvas_div.addEventListener(\n",
+ " 'keyup',\n",
+ " on_keyboard_event_closure('key_release')\n",
+ " );\n",
+ "\n",
+ " this._canvas_extra_style(canvas_div);\n",
+ " this.root.appendChild(canvas_div);\n",
+ "\n",
+ " var canvas = (this.canvas = document.createElement('canvas'));\n",
+ " canvas.classList.add('mpl-canvas');\n",
+ " canvas.setAttribute('style', 'box-sizing: content-box;');\n",
+ "\n",
+ " this.context = canvas.getContext('2d');\n",
+ "\n",
+ " var backingStore =\n",
+ " this.context.backingStorePixelRatio ||\n",
+ " this.context.webkitBackingStorePixelRatio ||\n",
+ " this.context.mozBackingStorePixelRatio ||\n",
+ " this.context.msBackingStorePixelRatio ||\n",
+ " this.context.oBackingStorePixelRatio ||\n",
+ " this.context.backingStorePixelRatio ||\n",
+ " 1;\n",
+ "\n",
+ " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
+ "\n",
+ " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n",
+ " 'canvas'\n",
+ " ));\n",
+ " rubberband_canvas.setAttribute(\n",
+ " 'style',\n",
+ " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n",
+ " );\n",
+ "\n",
+ " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n",
+ " if (this.ResizeObserver === undefined) {\n",
+ " if (window.ResizeObserver !== undefined) {\n",
+ " this.ResizeObserver = window.ResizeObserver;\n",
+ " } else {\n",
+ " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n",
+ " this.ResizeObserver = obs.ResizeObserver;\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n",
+ " var nentries = entries.length;\n",
+ " for (var i = 0; i < nentries; i++) {\n",
+ " var entry = entries[i];\n",
+ " var width, height;\n",
+ " if (entry.contentBoxSize) {\n",
+ " if (entry.contentBoxSize instanceof Array) {\n",
+ " // Chrome 84 implements new version of spec.\n",
+ " width = entry.contentBoxSize[0].inlineSize;\n",
+ " height = entry.contentBoxSize[0].blockSize;\n",
+ " } else {\n",
+ " // Firefox implements old version of spec.\n",
+ " width = entry.contentBoxSize.inlineSize;\n",
+ " height = entry.contentBoxSize.blockSize;\n",
+ " }\n",
+ " } else {\n",
+ " // Chrome <84 implements even older version of spec.\n",
+ " width = entry.contentRect.width;\n",
+ " height = entry.contentRect.height;\n",
+ " }\n",
+ "\n",
+ " // Keep the size of the canvas and rubber band canvas in sync with\n",
+ " // the canvas container.\n",
+ " if (entry.devicePixelContentBoxSize) {\n",
+ " // Chrome 84 implements new version of spec.\n",
+ " canvas.setAttribute(\n",
+ " 'width',\n",
+ " entry.devicePixelContentBoxSize[0].inlineSize\n",
+ " );\n",
+ " canvas.setAttribute(\n",
+ " 'height',\n",
+ " entry.devicePixelContentBoxSize[0].blockSize\n",
+ " );\n",
+ " } else {\n",
+ " canvas.setAttribute('width', width * fig.ratio);\n",
+ " canvas.setAttribute('height', height * fig.ratio);\n",
+ " }\n",
+ " canvas.setAttribute(\n",
+ " 'style',\n",
+ " 'width: ' + width + 'px; height: ' + height + 'px;'\n",
+ " );\n",
+ "\n",
+ " rubberband_canvas.setAttribute('width', width);\n",
+ " rubberband_canvas.setAttribute('height', height);\n",
+ "\n",
+ " // And update the size in Python. We ignore the initial 0/0 size\n",
+ " // that occurs as the element is placed into the DOM, which should\n",
+ " // otherwise not happen due to the minimum size styling.\n",
+ " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n",
+ " fig.request_resize(width, height);\n",
+ " }\n",
+ " }\n",
+ " });\n",
+ " this.resizeObserverInstance.observe(canvas_div);\n",
+ "\n",
+ " function on_mouse_event_closure(name) {\n",
+ " return function (event) {\n",
+ " return fig.mouse_event(event, name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mousedown',\n",
+ " on_mouse_event_closure('button_press')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseup',\n",
+ " on_mouse_event_closure('button_release')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'dblclick',\n",
+ " on_mouse_event_closure('dblclick')\n",
+ " );\n",
+ " // Throttle sequential mouse events to 1 every 20ms.\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mousemove',\n",
+ " on_mouse_event_closure('motion_notify')\n",
+ " );\n",
+ "\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseenter',\n",
+ " on_mouse_event_closure('figure_enter')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseleave',\n",
+ " on_mouse_event_closure('figure_leave')\n",
+ " );\n",
+ "\n",
+ " canvas_div.addEventListener('wheel', function (event) {\n",
+ " if (event.deltaY < 0) {\n",
+ " event.step = 1;\n",
+ " } else {\n",
+ " event.step = -1;\n",
+ " }\n",
+ " on_mouse_event_closure('scroll')(event);\n",
+ " });\n",
+ "\n",
+ " canvas_div.appendChild(canvas);\n",
+ " canvas_div.appendChild(rubberband_canvas);\n",
+ "\n",
+ " this.rubberband_context = rubberband_canvas.getContext('2d');\n",
+ " this.rubberband_context.strokeStyle = '#000000';\n",
+ "\n",
+ " this._resize_canvas = function (width, height, forward) {\n",
+ " if (forward) {\n",
+ " canvas_div.style.width = width + 'px';\n",
+ " canvas_div.style.height = height + 'px';\n",
+ " }\n",
+ " };\n",
+ "\n",
+ " // Disable right mouse context menu.\n",
+ " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n",
+ " event.preventDefault();\n",
+ " return false;\n",
+ " });\n",
+ "\n",
+ " function set_focus() {\n",
+ " canvas.focus();\n",
+ " canvas_div.focus();\n",
+ " }\n",
+ "\n",
+ " window.setTimeout(set_focus, 100);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_toolbar = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var toolbar = document.createElement('div');\n",
+ " toolbar.classList = 'mpl-toolbar';\n",
+ " this.root.appendChild(toolbar);\n",
+ "\n",
+ " function on_click_closure(name) {\n",
+ " return function (_event) {\n",
+ " return fig.toolbar_button_onclick(name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " function on_mouseover_closure(tooltip) {\n",
+ " return function (event) {\n",
+ " if (!event.currentTarget.disabled) {\n",
+ " return fig.toolbar_button_onmouseover(tooltip);\n",
+ " }\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " fig.buttons = {};\n",
+ " var buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'mpl-button-group';\n",
+ " for (var toolbar_ind in mpl.toolbar_items) {\n",
+ " var name = mpl.toolbar_items[toolbar_ind][0];\n",
+ " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+ " var image = mpl.toolbar_items[toolbar_ind][2];\n",
+ " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+ "\n",
+ " if (!name) {\n",
+ " /* Instead of a spacer, we start a new button group. */\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ " buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'mpl-button-group';\n",
+ " continue;\n",
+ " }\n",
+ "\n",
+ " var button = (fig.buttons[name] = document.createElement('button'));\n",
+ " button.classList = 'mpl-widget';\n",
+ " button.setAttribute('role', 'button');\n",
+ " button.setAttribute('aria-disabled', 'false');\n",
+ " button.addEventListener('click', on_click_closure(method_name));\n",
+ " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+ "\n",
+ " var icon_img = document.createElement('img');\n",
+ " icon_img.src = '_images/' + image + '.png';\n",
+ " icon_img.srcset = '_images/' + image + '_large.png 2x';\n",
+ " icon_img.alt = tooltip;\n",
+ " button.appendChild(icon_img);\n",
+ "\n",
+ " buttonGroup.appendChild(button);\n",
+ " }\n",
+ "\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ "\n",
+ " var fmt_picker = document.createElement('select');\n",
+ " fmt_picker.classList = 'mpl-widget';\n",
+ " toolbar.appendChild(fmt_picker);\n",
+ " this.format_dropdown = fmt_picker;\n",
+ "\n",
+ " for (var ind in mpl.extensions) {\n",
+ " var fmt = mpl.extensions[ind];\n",
+ " var option = document.createElement('option');\n",
+ " option.selected = fmt === mpl.default_extension;\n",
+ " option.innerHTML = fmt;\n",
+ " fmt_picker.appendChild(option);\n",
+ " }\n",
+ "\n",
+ " var status_bar = document.createElement('span');\n",
+ " status_bar.classList = 'mpl-message';\n",
+ " toolbar.appendChild(status_bar);\n",
+ " this.message = status_bar;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n",
+ " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
+ " // which will in turn request a refresh of the image.\n",
+ " this.send_message('resize', { width: x_pixels, height: y_pixels });\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.send_message = function (type, properties) {\n",
+ " properties['type'] = type;\n",
+ " properties['figure_id'] = this.id;\n",
+ " this.ws.send(JSON.stringify(properties));\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.send_draw_message = function () {\n",
+ " if (!this.waiting) {\n",
+ " this.waiting = true;\n",
+ " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+ " var format_dropdown = fig.format_dropdown;\n",
+ " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
+ " fig.ondownload(fig, format);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_resize = function (fig, msg) {\n",
+ " var size = msg['size'];\n",
+ " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n",
+ " fig._resize_canvas(size[0], size[1], msg['forward']);\n",
+ " fig.send_message('refresh', {});\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n",
+ " var x0 = msg['x0'] / fig.ratio;\n",
+ " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n",
+ " var x1 = msg['x1'] / fig.ratio;\n",
+ " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n",
+ " x0 = Math.floor(x0) + 0.5;\n",
+ " y0 = Math.floor(y0) + 0.5;\n",
+ " x1 = Math.floor(x1) + 0.5;\n",
+ " y1 = Math.floor(y1) + 0.5;\n",
+ " var min_x = Math.min(x0, x1);\n",
+ " var min_y = Math.min(y0, y1);\n",
+ " var width = Math.abs(x1 - x0);\n",
+ " var height = Math.abs(y1 - y0);\n",
+ "\n",
+ " fig.rubberband_context.clearRect(\n",
+ " 0,\n",
+ " 0,\n",
+ " fig.canvas.width / fig.ratio,\n",
+ " fig.canvas.height / fig.ratio\n",
+ " );\n",
+ "\n",
+ " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n",
+ " // Updates the figure title.\n",
+ " fig.header.textContent = msg['label'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n",
+ " fig.rubberband_canvas.style.cursor = msg['cursor'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_message = function (fig, msg) {\n",
+ " fig.message.textContent = msg['message'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n",
+ " // Request the server to send over a new figure.\n",
+ " fig.send_draw_message();\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n",
+ " fig.image_mode = msg['mode'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n",
+ " for (var key in msg) {\n",
+ " if (!(key in fig.buttons)) {\n",
+ " continue;\n",
+ " }\n",
+ " fig.buttons[key].disabled = !msg[key];\n",
+ " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n",
+ " if (msg['mode'] === 'PAN') {\n",
+ " fig.buttons['Pan'].classList.add('active');\n",
+ " fig.buttons['Zoom'].classList.remove('active');\n",
+ " } else if (msg['mode'] === 'ZOOM') {\n",
+ " fig.buttons['Pan'].classList.remove('active');\n",
+ " fig.buttons['Zoom'].classList.add('active');\n",
+ " } else {\n",
+ " fig.buttons['Pan'].classList.remove('active');\n",
+ " fig.buttons['Zoom'].classList.remove('active');\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.updated_canvas_event = function () {\n",
+ " // Called whenever the canvas gets updated.\n",
+ " this.send_message('ack', {});\n",
+ "};\n",
+ "\n",
+ "// A function to construct a web socket function for onmessage handling.\n",
+ "// Called in the figure constructor.\n",
+ "mpl.figure.prototype._make_on_message_function = function (fig) {\n",
+ " return function socket_on_message(evt) {\n",
+ " if (evt.data instanceof Blob) {\n",
+ " var img = evt.data;\n",
+ " if (img.type !== 'image/png') {\n",
+ " /* FIXME: We get \"Resource interpreted as Image but\n",
+ " * transferred with MIME type text/plain:\" errors on\n",
+ " * Chrome. But how to set the MIME type? It doesn't seem\n",
+ " * to be part of the websocket stream */\n",
+ " img.type = 'image/png';\n",
+ " }\n",
+ "\n",
+ " /* Free the memory for the previous frames */\n",
+ " if (fig.imageObj.src) {\n",
+ " (window.URL || window.webkitURL).revokeObjectURL(\n",
+ " fig.imageObj.src\n",
+ " );\n",
+ " }\n",
+ "\n",
+ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
+ " img\n",
+ " );\n",
+ " fig.updated_canvas_event();\n",
+ " fig.waiting = false;\n",
+ " return;\n",
+ " } else if (\n",
+ " typeof evt.data === 'string' &&\n",
+ " evt.data.slice(0, 21) === 'data:image/png;base64'\n",
+ " ) {\n",
+ " fig.imageObj.src = evt.data;\n",
+ " fig.updated_canvas_event();\n",
+ " fig.waiting = false;\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " var msg = JSON.parse(evt.data);\n",
+ " var msg_type = msg['type'];\n",
+ "\n",
+ " // Call the \"handle_{type}\" callback, which takes\n",
+ " // the figure and JSON message as its only arguments.\n",
+ " try {\n",
+ " var callback = fig['handle_' + msg_type];\n",
+ " } catch (e) {\n",
+ " console.log(\n",
+ " \"No handler for the '\" + msg_type + \"' message type: \",\n",
+ " msg\n",
+ " );\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " if (callback) {\n",
+ " try {\n",
+ " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
+ " callback(fig, msg);\n",
+ " } catch (e) {\n",
+ " console.log(\n",
+ " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n",
+ " e,\n",
+ " e.stack,\n",
+ " msg\n",
+ " );\n",
+ " }\n",
+ " }\n",
+ " };\n",
+ "};\n",
+ "\n",
+ "// from https://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
+ "mpl.findpos = function (e) {\n",
+ " //this section is from http://www.quirksmode.org/js/events_properties.html\n",
+ " var targ;\n",
+ " if (!e) {\n",
+ " e = window.event;\n",
+ " }\n",
+ " if (e.target) {\n",
+ " targ = e.target;\n",
+ " } else if (e.srcElement) {\n",
+ " targ = e.srcElement;\n",
+ " }\n",
+ " if (targ.nodeType === 3) {\n",
+ " // defeat Safari bug\n",
+ " targ = targ.parentNode;\n",
+ " }\n",
+ "\n",
+ " // pageX,Y are the mouse positions relative to the document\n",
+ " var boundingRect = targ.getBoundingClientRect();\n",
+ " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n",
+ " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n",
+ "\n",
+ " return { x: x, y: y };\n",
+ "};\n",
+ "\n",
+ "/*\n",
+ " * return a copy of an object with only non-object keys\n",
+ " * we need this to avoid circular references\n",
+ " * https://stackoverflow.com/a/24161582/3208463\n",
+ " */\n",
+ "function simpleKeys(original) {\n",
+ " return Object.keys(original).reduce(function (obj, key) {\n",
+ " if (typeof original[key] !== 'object') {\n",
+ " obj[key] = original[key];\n",
+ " }\n",
+ " return obj;\n",
+ " }, {});\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.mouse_event = function (event, name) {\n",
+ " var canvas_pos = mpl.findpos(event);\n",
+ "\n",
+ " if (name === 'button_press') {\n",
+ " this.canvas.focus();\n",
+ " this.canvas_div.focus();\n",
+ " }\n",
+ "\n",
+ " var x = canvas_pos.x * this.ratio;\n",
+ " var y = canvas_pos.y * this.ratio;\n",
+ "\n",
+ " this.send_message(name, {\n",
+ " x: x,\n",
+ " y: y,\n",
+ " button: event.button,\n",
+ " step: event.step,\n",
+ " guiEvent: simpleKeys(event),\n",
+ " });\n",
+ "\n",
+ " /* This prevents the web browser from automatically changing to\n",
+ " * the text insertion cursor when the button is pressed. We want\n",
+ " * to control all of the cursor setting manually through the\n",
+ " * 'cursor' event from matplotlib */\n",
+ " event.preventDefault();\n",
+ " return false;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n",
+ " // Handle any extra behaviour associated with a key event\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.key_event = function (event, name) {\n",
+ " // Prevent repeat events\n",
+ " if (name === 'key_press') {\n",
+ " if (event.key === this._key) {\n",
+ " return;\n",
+ " } else {\n",
+ " this._key = event.key;\n",
+ " }\n",
+ " }\n",
+ " if (name === 'key_release') {\n",
+ " this._key = null;\n",
+ " }\n",
+ "\n",
+ " var value = '';\n",
+ " if (event.ctrlKey && event.key !== 'Control') {\n",
+ " value += 'ctrl+';\n",
+ " }\n",
+ " else if (event.altKey && event.key !== 'Alt') {\n",
+ " value += 'alt+';\n",
+ " }\n",
+ " else if (event.shiftKey && event.key !== 'Shift') {\n",
+ " value += 'shift+';\n",
+ " }\n",
+ "\n",
+ " value += 'k' + event.key;\n",
+ "\n",
+ " this._key_event_extra(event, name);\n",
+ "\n",
+ " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n",
+ " return false;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n",
+ " if (name === 'download') {\n",
+ " this.handle_save(this, null);\n",
+ " } else {\n",
+ " this.send_message('toolbar_button', { name: name });\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n",
+ " this.message.textContent = tooltip;\n",
+ "};\n",
+ "\n",
+ "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n",
+ "// prettier-ignore\n",
+ "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n",
+ "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
+ "\n",
+ "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
+ "\n",
+ "mpl.default_extension = \"png\";/* global mpl */\n",
+ "\n",
+ "var comm_websocket_adapter = function (comm) {\n",
+ " // Create a \"websocket\"-like object which calls the given IPython comm\n",
+ " // object with the appropriate methods. Currently this is a non binary\n",
+ " // socket, so there is still some room for performance tuning.\n",
+ " var ws = {};\n",
+ "\n",
+ " ws.binaryType = comm.kernel.ws.binaryType;\n",
+ " ws.readyState = comm.kernel.ws.readyState;\n",
+ " function updateReadyState(_event) {\n",
+ " if (comm.kernel.ws) {\n",
+ " ws.readyState = comm.kernel.ws.readyState;\n",
+ " } else {\n",
+ " ws.readyState = 3; // Closed state.\n",
+ " }\n",
+ " }\n",
+ " comm.kernel.ws.addEventListener('open', updateReadyState);\n",
+ " comm.kernel.ws.addEventListener('close', updateReadyState);\n",
+ " comm.kernel.ws.addEventListener('error', updateReadyState);\n",
+ "\n",
+ " ws.close = function () {\n",
+ " comm.close();\n",
+ " };\n",
+ " ws.send = function (m) {\n",
+ " //console.log('sending', m);\n",
+ " comm.send(m);\n",
+ " };\n",
+ " // Register the callback with on_msg.\n",
+ " comm.on_msg(function (msg) {\n",
+ " //console.log('receiving', msg['content']['data'], msg);\n",
+ " var data = msg['content']['data'];\n",
+ " if (data['blob'] !== undefined) {\n",
+ " data = {\n",
+ " data: new Blob(msg['buffers'], { type: data['blob'] }),\n",
+ " };\n",
+ " }\n",
+ " // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
+ " ws.onmessage(data);\n",
+ " });\n",
+ " return ws;\n",
+ "};\n",
+ "\n",
+ "mpl.mpl_figure_comm = function (comm, msg) {\n",
+ " // This is the function which gets called when the mpl process\n",
+ " // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
+ "\n",
+ " var id = msg.content.data.id;\n",
+ " // Get hold of the div created by the display call when the Comm\n",
+ " // socket was opened in Python.\n",
+ " var element = document.getElementById(id);\n",
+ " var ws_proxy = comm_websocket_adapter(comm);\n",
+ "\n",
+ " function ondownload(figure, _format) {\n",
+ " window.open(figure.canvas.toDataURL());\n",
+ " }\n",
+ "\n",
+ " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n",
+ "\n",
+ " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
+ " // web socket which is closed, not our websocket->open comm proxy.\n",
+ " ws_proxy.onopen();\n",
+ "\n",
+ " fig.parent_element = element;\n",
+ " fig.cell_info = mpl.find_output_cell(\"\");\n",
+ " if (!fig.cell_info) {\n",
+ " console.error('Failed to find cell for figure', id, fig);\n",
+ " return;\n",
+ " }\n",
+ " fig.cell_info[0].output_area.element.on(\n",
+ " 'cleared',\n",
+ " { fig: fig },\n",
+ " fig._remove_fig_handler\n",
+ " );\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_close = function (fig, msg) {\n",
+ " var width = fig.canvas.width / fig.ratio;\n",
+ " fig.cell_info[0].output_area.element.off(\n",
+ " 'cleared',\n",
+ " fig._remove_fig_handler\n",
+ " );\n",
+ " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n",
+ "\n",
+ " // Update the output cell to use the data from the current canvas.\n",
+ " fig.push_to_output();\n",
+ " var dataURL = fig.canvas.toDataURL();\n",
+ " // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
+ " // the notebook keyboard shortcuts fail.\n",
+ " IPython.keyboard_manager.enable();\n",
+ " fig.parent_element.innerHTML =\n",
+ " '
';\n",
+ " fig.close_ws(fig, msg);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.close_ws = function (fig, msg) {\n",
+ " fig.send_message('closing', msg);\n",
+ " // fig.ws.close()\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n",
+ " // Turn the data on the canvas into data in the output cell.\n",
+ " var width = this.canvas.width / this.ratio;\n",
+ " var dataURL = this.canvas.toDataURL();\n",
+ " this.cell_info[1]['text/html'] =\n",
+ " '
';\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.updated_canvas_event = function () {\n",
+ " // Tell IPython that the notebook contents must change.\n",
+ " IPython.notebook.set_dirty(true);\n",
+ " this.send_message('ack', {});\n",
+ " var fig = this;\n",
+ " // Wait a second, then push the new image to the DOM so\n",
+ " // that it is saved nicely (might be nice to debounce this).\n",
+ " setTimeout(function () {\n",
+ " fig.push_to_output();\n",
+ " }, 1000);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_toolbar = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var toolbar = document.createElement('div');\n",
+ " toolbar.classList = 'btn-toolbar';\n",
+ " this.root.appendChild(toolbar);\n",
+ "\n",
+ " function on_click_closure(name) {\n",
+ " return function (_event) {\n",
+ " return fig.toolbar_button_onclick(name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " function on_mouseover_closure(tooltip) {\n",
+ " return function (event) {\n",
+ " if (!event.currentTarget.disabled) {\n",
+ " return fig.toolbar_button_onmouseover(tooltip);\n",
+ " }\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " fig.buttons = {};\n",
+ " var buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'btn-group';\n",
+ " var button;\n",
+ " for (var toolbar_ind in mpl.toolbar_items) {\n",
+ " var name = mpl.toolbar_items[toolbar_ind][0];\n",
+ " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+ " var image = mpl.toolbar_items[toolbar_ind][2];\n",
+ " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+ "\n",
+ " if (!name) {\n",
+ " /* Instead of a spacer, we start a new button group. */\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ " buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'btn-group';\n",
+ " continue;\n",
+ " }\n",
+ "\n",
+ " button = fig.buttons[name] = document.createElement('button');\n",
+ " button.classList = 'btn btn-default';\n",
+ " button.href = '#';\n",
+ " button.title = name;\n",
+ " button.innerHTML = '';\n",
+ " button.addEventListener('click', on_click_closure(method_name));\n",
+ " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+ " buttonGroup.appendChild(button);\n",
+ " }\n",
+ "\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ "\n",
+ " // Add the status bar.\n",
+ " var status_bar = document.createElement('span');\n",
+ " status_bar.classList = 'mpl-message pull-right';\n",
+ " toolbar.appendChild(status_bar);\n",
+ " this.message = status_bar;\n",
+ "\n",
+ " // Add the close button to the window.\n",
+ " var buttongrp = document.createElement('div');\n",
+ " buttongrp.classList = 'btn-group inline pull-right';\n",
+ " button = document.createElement('button');\n",
+ " button.classList = 'btn btn-mini btn-primary';\n",
+ " button.href = '#';\n",
+ " button.title = 'Stop Interaction';\n",
+ " button.innerHTML = '';\n",
+ " button.addEventListener('click', function (_evt) {\n",
+ " fig.handle_close(fig, {});\n",
+ " });\n",
+ " button.addEventListener(\n",
+ " 'mouseover',\n",
+ " on_mouseover_closure('Stop Interaction')\n",
+ " );\n",
+ " buttongrp.appendChild(button);\n",
+ " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n",
+ " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._remove_fig_handler = function (event) {\n",
+ " var fig = event.data.fig;\n",
+ " if (event.target !== this) {\n",
+ " // Ignore bubbled events from children.\n",
+ " return;\n",
+ " }\n",
+ " fig.close_ws(fig, {});\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._root_extra_style = function (el) {\n",
+ " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._canvas_extra_style = function (el) {\n",
+ " // this is important to make the div 'focusable\n",
+ " el.setAttribute('tabindex', 0);\n",
+ " // reach out to IPython and tell the keyboard manager to turn it's self\n",
+ " // off when our div gets focus\n",
+ "\n",
+ " // location in version 3\n",
+ " if (IPython.notebook.keyboard_manager) {\n",
+ " IPython.notebook.keyboard_manager.register_events(el);\n",
+ " } else {\n",
+ " // location in version 2\n",
+ " IPython.keyboard_manager.register_events(el);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._key_event_extra = function (event, _name) {\n",
+ " // Check for shift+enter\n",
+ " if (event.shiftKey && event.which === 13) {\n",
+ " this.canvas_div.blur();\n",
+ " // select the cell after this one\n",
+ " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n",
+ " IPython.notebook.select(index + 1);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+ " fig.ondownload(fig, null);\n",
+ "};\n",
+ "\n",
+ "mpl.find_output_cell = function (html_output) {\n",
+ " // Return the cell and output element which can be found *uniquely* in the notebook.\n",
+ " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
+ " // IPython event is triggered only after the cells have been serialised, which for\n",
+ " // our purposes (turning an active figure into a static one), is too late.\n",
+ " var cells = IPython.notebook.get_cells();\n",
+ " var ncells = cells.length;\n",
+ " for (var i = 0; i < ncells; i++) {\n",
+ " var cell = cells[i];\n",
+ " if (cell.cell_type === 'code') {\n",
+ " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n",
+ " var data = cell.output_area.outputs[j];\n",
+ " if (data.data) {\n",
+ " // IPython >= 3 moved mimebundle to data attribute of output\n",
+ " data = data.data;\n",
+ " }\n",
+ " if (data['text/html'] === html_output) {\n",
+ " return [cell, data, j];\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "// Register the function which deals with the matplotlib target/channel.\n",
+ "// The kernel may be null if the page has been refreshed.\n",
+ "if (IPython.notebook.kernel !== null) {\n",
+ " IPython.notebook.kernel.comm_manager.register_target(\n",
+ " 'matplotlib',\n",
+ " mpl.mpl_figure_comm\n",
+ " );\n",
+ "}\n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "
"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "%matplotlib notebook\n",
+ "\n",
+ "# Read in results\n",
+ "## Params\n",
+ "folder = Path(\"./results/daytime_eval\")\n",
+ "\n",
+ "## Plot code\n",
+ "res = {}\n",
+ "\n",
+ "\n",
+ "def _load():\n",
+ " # res: dict[datetime, results]\n",
+ " # results: dict[tuple[system, num], bw_in_MiB]\n",
+ " for file in folder.iterdir():\n",
+ " date_str, _, _, system, num = file.stem.split(\"_\")\n",
+ " date = datetime.fromisoformat(date_str) + timedelta(hours=2)\n",
+ " file_content = json.loads(file.read_text())\n",
+ "\n",
+ " run_res = file_content[\"results\"][f\"daytime_evaluation_{system}_{num}\"]\n",
+ " if len(run_res) == 0:\n",
+ " print(\"No result for:\", date_str, system, num)\n",
+ " continue\n",
+ " if \"put_bw_in_MiB\" in run_res[0]:\n",
+ " value = float(run_res[0][\"put_bw_in_MiB\"])\n",
+ " else:\n",
+ " value = run_res[0][\"write_bw_mean_in_MiB\"] \n",
+ "\n",
+ " if date not in res:\n",
+ " res[date] = {}\n",
+ "\n",
+ " key = (system, int(num))\n",
+ " if key in res:\n",
+ " print(\"Duplication for:\", date, key)\n",
+ " res[date][key] = value\n",
+ "\n",
+ " labels = sorted(k for k in res.keys())\n",
+ " return labels, res\n",
+ "\n",
+ "def generate_tex_table(labels, res):\n",
+ " # Generate values for tex-table\n",
+ " values_netapp_1 = []\n",
+ " values_netapp_2 = []\n",
+ " values_isilon_1 = []\n",
+ " values_isilon_2 = []\n",
+ " values_s3_1 = []\n",
+ " values_s3_2 = []\n",
+ "\n",
+ " for key in labels:\n",
+ " value = res[key]\n",
+ " values_netapp_1.append(value.get((\"netapp\", 1), 0))\n",
+ " values_netapp_2.append(value.get((\"netapp\", 2), 0))\n",
+ " values_isilon_1.append(value.get((\"isilon\", 1), 0))\n",
+ " values_isilon_2.append(value.get((\"isilon\", 2), 0))\n",
+ " values_s3_1.append(value.get((\"s3\", 1), 0))\n",
+ " values_s3_2.append(value.get((\"s3\", 2), 0))\n",
+ "\n",
+ " def extract_stats(values, label=\"\"):\n",
+ " min_, max_ = min(values), max(values)\n",
+ " avg, conf = sum(values)/len(values), confidence_interval(values)\n",
+ " std = np.std(values)\n",
+ " print(f\"{label} & {min_:.0f} & {max_:.0f} & {avg:.0f} & {conf:.0f} & {std:.0f}\\\\\\\\\")\n",
+ "\n",
+ " extract_stats(values_netapp_1, \"Netapp A400\")\n",
+ " extract_stats(values_netapp_2)\n",
+ " extract_stats(values_isilon_1, \"Isilon\")\n",
+ " extract_stats(values_isilon_2)\n",
+ " extract_stats(values_s3_1, \"Netapp StorageGRID\")\n",
+ " extract_stats(values_s3_2)\n",
+ "\n",
+ "def generate_plot(labels, res):\n",
+ " # Generate plot\n",
+ " values_netapp_1 = []\n",
+ " values_netapp_2 = []\n",
+ " values_isilon_1 = []\n",
+ " values_isilon_2 = []\n",
+ " values_s3_1 = []\n",
+ " values_s3_2 = []\n",
+ " ## Sliding windows\n",
+ " sw_netapp_1 = []\n",
+ " sw_netapp_2 = []\n",
+ " sw_isilon_1 = []\n",
+ " sw_isilon_2 = []\n",
+ " sw_s3_1 = []\n",
+ " sw_s3_2 = []\n",
+ "\n",
+ " def calc_sliding_window(value, sw):\n",
+ " width = 4\n",
+ " sw.append(value)\n",
+ " if len(sw) > width:\n",
+ " sw.pop(0)\n",
+ " return sum(sw)/len(sw)\n",
+ "\n",
+ " for key in labels:\n",
+ " value = res[key]\n",
+ " values_netapp_1.append(calc_sliding_window(value.get((\"netapp\", 1), 0),sw_netapp_1))\n",
+ " values_netapp_2.append(calc_sliding_window(value.get((\"netapp\", 2), 0),sw_netapp_2))\n",
+ " values_isilon_1.append(calc_sliding_window(value.get((\"isilon\", 1), 0),sw_isilon_1))\n",
+ " values_isilon_2.append(calc_sliding_window(value.get((\"isilon\", 2), 0),sw_isilon_2))\n",
+ " values_s3_1.append(calc_sliding_window(value.get((\"s3\", 1), 0),sw_s3_1))\n",
+ " values_s3_2.append(calc_sliding_window(value.get((\"s3\", 2), 0),sw_s3_2))\n",
+ "\n",
+ " #plt.plot(labels, values_isilon_1, label=\"Isilon 01\")\n",
+ " #plt.plot(labels, values_netapp_1, label=\"Netapp 01\")\n",
+ " #plt.plot(labels, values_s3_1, label=\"S3 01\")\n",
+ " plt.plot(labels, values_isilon_2, label=\"Isilon\")\n",
+ " plt.plot(labels, values_netapp_2, label=\"Netapp A400\")\n",
+ " plt.plot(labels, values_s3_2, label=\"Netapp StorageGRID\")\n",
+ "\n",
+ " # Plot day seperation lines\n",
+ " for month, day in [(7,19),(7,20),(7,21),(7,22),(7,23),(7,24),(7,25)]:\n",
+ " x = datetime(year=2022, month=month, day=day)\n",
+ " plt.vlines(x=x, ymin=200, ymax=1150, linewidth=2, linestyles=\"dashed\", colors=\"k\", linewidths=0.75)\n",
+ " #x_8, x_17 = x.replace(hour=8), x.replace(hour=17)\n",
+ " #plt.axvspan(x_8, x_17, color='red', alpha=0.25)\n",
+ "\n",
+ " plt.xticks(rotation=25, ha='right')\n",
+ " plt.ylabel('Bandwidth (MiB/s)')\n",
+ "\n",
+ " plt.legend()\n",
+ " plt.title(\"Bandwidth Over Time\")\n",
+ " plt.savefig(\"daytime_evaluation.png\",bbox_inches='tight')\n",
+ " plt.show()\n",
+ "\n",
+ "labels, res = _load()\n",
+ "generate_tex_table(labels, res)\n",
+ "generate_plot(labels, res)\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "5fb262ad",
+ "metadata": {},
+ "source": [
+ "## Experiment: Compare Storage Systems (bandwidth, iops, latency)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "id": "47d047e4",
+ "metadata": {
+ "scrolled": false
+ },
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/fromnumeric.py:3440: RuntimeWarning: Mean of empty slice.\n",
+ " return _methods._mean(a, axis=axis, dtype=dtype,\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:189: RuntimeWarning: invalid value encountered in double_scalars\n",
+ " ret = ret.dtype.type(ret / rcount)\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:262: RuntimeWarning: Degrees of freedom <= 0 for slice\n",
+ " ret = _var(a, axis=axis, dtype=dtype, out=out, ddof=ddof,\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:222: RuntimeWarning: invalid value encountered in true_divide\n",
+ " arrmean = um.true_divide(arrmean, div, out=arrmean, casting='unsafe',\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:254: RuntimeWarning: invalid value encountered in double_scalars\n",
+ " ret = ret.dtype.type(ret / rcount)\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/fromnumeric.py:3440: RuntimeWarning: Mean of empty slice.\n",
+ " return _methods._mean(a, axis=axis, dtype=dtype,\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:189: RuntimeWarning: invalid value encountered in double_scalars\n",
+ " ret = ret.dtype.type(ret / rcount)\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:262: RuntimeWarning: Degrees of freedom <= 0 for slice\n",
+ " ret = _var(a, axis=axis, dtype=dtype, out=out, ddof=ddof,\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:222: RuntimeWarning: invalid value encountered in true_divide\n",
+ " arrmean = um.true_divide(arrmean, div, out=arrmean, casting='unsafe',\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:254: RuntimeWarning: invalid value encountered in double_scalars\n",
+ " ret = ret.dtype.type(ret / rcount)\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/fromnumeric.py:3440: RuntimeWarning: Mean of empty slice.\n",
+ " return _methods._mean(a, axis=axis, dtype=dtype,\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:189: RuntimeWarning: invalid value encountered in double_scalars\n",
+ " ret = ret.dtype.type(ret / rcount)\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:262: RuntimeWarning: Degrees of freedom <= 0 for slice\n",
+ " ret = _var(a, axis=axis, dtype=dtype, out=out, ddof=ddof,\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:222: RuntimeWarning: invalid value encountered in true_divide\n",
+ " arrmean = um.true_divide(arrmean, div, out=arrmean, casting='unsafe',\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:254: RuntimeWarning: invalid value encountered in double_scalars\n",
+ " ret = ret.dtype.type(ret / rcount)\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/fromnumeric.py:3440: RuntimeWarning: Mean of empty slice.\n",
+ " return _methods._mean(a, axis=axis, dtype=dtype,\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:189: RuntimeWarning: invalid value encountered in double_scalars\n",
+ " ret = ret.dtype.type(ret / rcount)\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:262: RuntimeWarning: Degrees of freedom <= 0 for slice\n",
+ " ret = _var(a, axis=axis, dtype=dtype, out=out, ddof=ddof,\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:222: RuntimeWarning: invalid value encountered in true_divide\n",
+ " arrmean = um.true_divide(arrmean, div, out=arrmean, casting='unsafe',\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:254: RuntimeWarning: invalid value encountered in double_scalars\n",
+ " ret = ret.dtype.type(ret / rcount)\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/fromnumeric.py:3440: RuntimeWarning: Mean of empty slice.\n",
+ " return _methods._mean(a, axis=axis, dtype=dtype,\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:189: RuntimeWarning: invalid value encountered in double_scalars\n",
+ " ret = ret.dtype.type(ret / rcount)\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:262: RuntimeWarning: Degrees of freedom <= 0 for slice\n",
+ " ret = _var(a, axis=axis, dtype=dtype, out=out, ddof=ddof,\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:222: RuntimeWarning: invalid value encountered in true_divide\n",
+ " arrmean = um.true_divide(arrmean, div, out=arrmean, casting='unsafe',\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:254: RuntimeWarning: invalid value encountered in double_scalars\n",
+ " ret = ret.dtype.type(ret / rcount)\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/fromnumeric.py:3440: RuntimeWarning: Mean of empty slice.\n",
+ " return _methods._mean(a, axis=axis, dtype=dtype,\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:189: RuntimeWarning: invalid value encountered in double_scalars\n",
+ " ret = ret.dtype.type(ret / rcount)\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:262: RuntimeWarning: Degrees of freedom <= 0 for slice\n",
+ " ret = _var(a, axis=axis, dtype=dtype, out=out, ddof=ddof,\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:222: RuntimeWarning: invalid value encountered in true_divide\n",
+ " arrmean = um.true_divide(arrmean, div, out=arrmean, casting='unsafe',\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:254: RuntimeWarning: invalid value encountered in double_scalars\n",
+ " ret = ret.dtype.type(ret / rcount)\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/fromnumeric.py:3440: RuntimeWarning: Mean of empty slice.\n",
+ " return _methods._mean(a, axis=axis, dtype=dtype,\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:189: RuntimeWarning: invalid value encountered in double_scalars\n",
+ " ret = ret.dtype.type(ret / rcount)\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:262: RuntimeWarning: Degrees of freedom <= 0 for slice\n",
+ " ret = _var(a, axis=axis, dtype=dtype, out=out, ddof=ddof,\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:222: RuntimeWarning: invalid value encountered in true_divide\n",
+ " arrmean = um.true_divide(arrmean, div, out=arrmean, casting='unsafe',\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:254: RuntimeWarning: invalid value encountered in double_scalars\n",
+ " ret = ret.dtype.type(ret / rcount)\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/fromnumeric.py:3440: RuntimeWarning: Mean of empty slice.\n",
+ " return _methods._mean(a, axis=axis, dtype=dtype,\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:189: RuntimeWarning: invalid value encountered in double_scalars\n",
+ " ret = ret.dtype.type(ret / rcount)\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:262: RuntimeWarning: Degrees of freedom <= 0 for slice\n",
+ " ret = _var(a, axis=axis, dtype=dtype, out=out, ddof=ddof,\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:222: RuntimeWarning: invalid value encountered in true_divide\n",
+ " arrmean = um.true_divide(arrmean, div, out=arrmean, casting='unsafe',\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:254: RuntimeWarning: invalid value encountered in double_scalars\n",
+ " ret = ret.dtype.type(ret / rcount)\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/fromnumeric.py:3440: RuntimeWarning: Mean of empty slice.\n",
+ " return _methods._mean(a, axis=axis, dtype=dtype,\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:189: RuntimeWarning: invalid value encountered in double_scalars\n",
+ " ret = ret.dtype.type(ret / rcount)\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:262: RuntimeWarning: Degrees of freedom <= 0 for slice\n",
+ " ret = _var(a, axis=axis, dtype=dtype, out=out, ddof=ddof,\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:222: RuntimeWarning: invalid value encountered in true_divide\n",
+ " arrmean = um.true_divide(arrmean, div, out=arrmean, casting='unsafe',\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:254: RuntimeWarning: invalid value encountered in double_scalars\n",
+ " ret = ret.dtype.type(ret / rcount)\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/fromnumeric.py:3440: RuntimeWarning: Mean of empty slice.\n",
+ " return _methods._mean(a, axis=axis, dtype=dtype,\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:189: RuntimeWarning: invalid value encountered in double_scalars\n",
+ " ret = ret.dtype.type(ret / rcount)\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:262: RuntimeWarning: Degrees of freedom <= 0 for slice\n",
+ " ret = _var(a, axis=axis, dtype=dtype, out=out, ddof=ddof,\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:222: RuntimeWarning: invalid value encountered in true_divide\n",
+ " arrmean = um.true_divide(arrmean, div, out=arrmean, casting='unsafe',\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:254: RuntimeWarning: invalid value encountered in double_scalars\n",
+ " ret = ret.dtype.type(ret / rcount)\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/fromnumeric.py:3440: RuntimeWarning: Mean of empty slice.\n",
+ " return _methods._mean(a, axis=axis, dtype=dtype,\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:189: RuntimeWarning: invalid value encountered in double_scalars\n",
+ " ret = ret.dtype.type(ret / rcount)\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:262: RuntimeWarning: Degrees of freedom <= 0 for slice\n",
+ " ret = _var(a, axis=axis, dtype=dtype, out=out, ddof=ddof,\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:222: RuntimeWarning: invalid value encountered in true_divide\n",
+ " arrmean = um.true_divide(arrmean, div, out=arrmean, casting='unsafe',\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:254: RuntimeWarning: invalid value encountered in double_scalars\n",
+ " ret = ret.dtype.type(ret / rcount)\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/fromnumeric.py:3440: RuntimeWarning: Mean of empty slice.\n",
+ " return _methods._mean(a, axis=axis, dtype=dtype,\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:189: RuntimeWarning: invalid value encountered in double_scalars\n",
+ " ret = ret.dtype.type(ret / rcount)\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:262: RuntimeWarning: Degrees of freedom <= 0 for slice\n",
+ " ret = _var(a, axis=axis, dtype=dtype, out=out, ddof=ddof,\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:222: RuntimeWarning: invalid value encountered in true_divide\n",
+ " arrmean = um.true_divide(arrmean, div, out=arrmean, casting='unsafe',\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:254: RuntimeWarning: invalid value encountered in double_scalars\n",
+ " ret = ret.dtype.type(ret / rcount)\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " mean std num\n",
+ "local throughput_write 128.060578 6.146713 16\n",
+ "nfs_isilon throughput_write 445.967968 54.696129 16\n",
+ "nfs_netapp throughput_write 370.237354 51.623348 16\n",
+ "nfs_ssdtank throughput_write 332.996586 2.512257 16\n",
+ "irodsfs_isilon throughput_write 145.969143 3.155809 16\n",
+ "irodsfs_netapp throughput_write 145.492831 4.040569 16\n",
+ "irodsfs_s3 throughput_write NaN NaN 0\n",
+ "webdav_isilon throughput_write 481.487045 24.733831 16\n",
+ "webdav_netapp throughput_write 495.023286 20.975037 15\n",
+ "webdav_s3 throughput_write NaN NaN 0\n",
+ " mean std num\n",
+ "local throughput_read 103.240813 5.642437 16\n",
+ "nfs_isilon throughput_read 692.785714 214.542098 16\n",
+ "nfs_netapp throughput_read 223.579847 27.156816 16\n",
+ "nfs_ssdtank throughput_read 992.937335 115.347483 16\n",
+ "irodsfs_isilon throughput_read 414.176076 31.938263 13\n",
+ "irodsfs_netapp throughput_read 306.628007 26.766749 12\n",
+ "irodsfs_s3 throughput_read NaN NaN 0\n",
+ "webdav_isilon throughput_read 1541.166369 199.253438 16\n",
+ "webdav_netapp throughput_read 1462.531331 383.598147 15\n",
+ "webdav_s3 throughput_read NaN NaN 0\n",
+ " mean std num\n",
+ "local iops_write 794.721170 3.508450 16\n",
+ "nfs_isilon iops_write 9639.938751 1274.151275 16\n",
+ "nfs_netapp iops_write 25164.049895 3030.353567 16\n",
+ "nfs_ssdtank iops_write 682.105620 8.961278 16\n",
+ "irodsfs_isilon iops_write 919.868347 24.244180 3\n",
+ "irodsfs_netapp iops_write 894.912605 21.943125 5\n",
+ "irodsfs_s3 iops_write NaN NaN 0\n",
+ "webdav_isilon iops_write 16290.605668 334.899233 16\n",
+ "webdav_netapp iops_write 16221.098345 250.879202 14\n",
+ "webdav_s3 iops_write NaN NaN 0\n",
+ " mean std num\n",
+ "local iops_read 801.355729 0.070015 16\n",
+ "nfs_isilon iops_read 40278.403887 1809.157118 16\n",
+ "nfs_netapp iops_read 31910.917682 3718.674746 16\n",
+ "nfs_ssdtank iops_read 43909.241597 2530.065501 16\n",
+ "irodsfs_isilon iops_read 1065.074469 51.539141 13\n",
+ "irodsfs_netapp iops_read 833.467536 75.039880 10\n",
+ "irodsfs_s3 iops_read NaN NaN 0\n",
+ "webdav_isilon iops_read 129855.383631 16570.959797 16\n",
+ "webdav_netapp iops_read 111579.243791 44543.207920 15\n",
+ "webdav_s3 iops_read NaN NaN 0\n",
+ " mean std num\n",
+ "local latency_write 1.332769 0.065432 16\n",
+ "nfs_isilon latency_write 0.610347 0.033426 16\n",
+ "nfs_netapp latency_write 0.843478 0.152214 16\n",
+ "nfs_ssdtank latency_write 1.867753 0.030613 16\n",
+ "irodsfs_isilon latency_write NaN NaN 0\n",
+ "irodsfs_netapp latency_write NaN NaN 0\n",
+ "irodsfs_s3 latency_write NaN NaN 0\n",
+ "webdav_isilon latency_write 0.074547 0.000709 16\n",
+ "webdav_netapp latency_write 0.074447 0.000529 15\n",
+ "webdav_s3 latency_write NaN NaN 0\n",
+ " mean std num\n",
+ "local latency_read 1.443774 0.169324 16\n",
+ "nfs_isilon latency_read 0.364842 0.021308 16\n",
+ "nfs_netapp latency_read 1.132750 0.239697 16\n",
+ "nfs_ssdtank latency_read 0.336024 0.027679 16\n",
+ "irodsfs_isilon latency_read 2.549647 0.101681 15\n",
+ "irodsfs_netapp latency_read 3.724552 0.484918 16\n",
+ "irodsfs_s3 latency_read NaN NaN 0\n",
+ "webdav_isilon latency_read 0.022976 0.002284 16\n",
+ "webdav_netapp latency_read 0.023815 0.002340 15\n",
+ "webdav_s3 latency_read NaN NaN 0\n",
+ "##########\n",
+ "read\n",
+ "local&103 & $\\ \\pm\\ $ & 6&801 & $\\ \\pm\\ $ & 0&1.44 & $\\ \\pm\\ $ & 0.17\\\\\n",
+ "nfs\\_isilon&693 & $\\ \\pm\\ $ & 215&40278 & $\\ \\pm\\ $ & 1809&0.36 & $\\ \\pm\\ $ & 0.02\\\\\n",
+ "nfs\\_netapp&224 & $\\ \\pm\\ $ & 27&31911 & $\\ \\pm\\ $ & 3719&1.13 & $\\ \\pm\\ $ & 0.24\\\\\n",
+ "nfs\\_ssdtank&993 & $\\ \\pm\\ $ & 115&43909 & $\\ \\pm\\ $ & 2530&0.34 & $\\ \\pm\\ $ & 0.03\\\\\n",
+ "irodsfs\\_isilon&414 & $\\ \\pm\\ $ & 32&1065 & $\\ \\pm\\ $ & 52&2.55 & $\\ \\pm\\ $ & 0.10\\\\\n",
+ "irodsfs\\_netapp&307 & $\\ \\pm\\ $ & 27&833 & $\\ \\pm\\ $ & 75&3.72 & $\\ \\pm\\ $ & 0.48\\\\\n",
+ "irodsfs\\_s3& &-& & &-& & &-& \\\\\n",
+ "webdav\\_isilon&1541 & $\\ \\pm\\ $ & 199&129855 & $\\ \\pm\\ $ & 16571&0.02 & $\\ \\pm\\ $ & 0.00\\\\\n",
+ "webdav\\_netapp&1463 & $\\ \\pm\\ $ & 384&111579 & $\\ \\pm\\ $ & 44543&0.02 & $\\ \\pm\\ $ & 0.00\\\\\n",
+ "webdav\\_s3& &-& & &-& & &-& \\\\\n",
+ "##########\n",
+ "write\n",
+ "local&128 & $\\ \\pm\\ $ & 6&795 & $\\ \\pm\\ $ & 4&1.33 & $\\ \\pm\\ $ & 0.07\\\\\n",
+ "nfs\\_isilon&446 & $\\ \\pm\\ $ & 55&9640 & $\\ \\pm\\ $ & 1274&0.61 & $\\ \\pm\\ $ & 0.03\\\\\n",
+ "nfs\\_netapp&370 & $\\ \\pm\\ $ & 52&25164 & $\\ \\pm\\ $ & 3030&0.84 & $\\ \\pm\\ $ & 0.15\\\\\n",
+ "nfs\\_ssdtank&333 & $\\ \\pm\\ $ & 3&682 & $\\ \\pm\\ $ & 9&1.87 & $\\ \\pm\\ $ & 0.03\\\\\n",
+ "irodsfs\\_isilon&146 & $\\ \\pm\\ $ & 3&920 & $\\ \\pm\\ $ & 24& &-& \\\\\n",
+ "irodsfs\\_netapp&145 & $\\ \\pm\\ $ & 4&895 & $\\ \\pm\\ $ & 22& &-& \\\\\n",
+ "irodsfs\\_s3& &-& & &-& & &-& \\\\\n",
+ "webdav\\_isilon&481 & $\\ \\pm\\ $ & 25&16291 & $\\ \\pm\\ $ & 335&0.07 & $\\ \\pm\\ $ & 0.00\\\\\n",
+ "webdav\\_netapp&495 & $\\ \\pm\\ $ & 21&16221 & $\\ \\pm\\ $ & 251&0.07 & $\\ \\pm\\ $ & 0.00\\\\\n",
+ "webdav\\_s3& &-& & &-& & &-& \\\\\n",
+ "##########\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/fromnumeric.py:3440: RuntimeWarning: Mean of empty slice.\n",
+ " return _methods._mean(a, axis=axis, dtype=dtype,\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:189: RuntimeWarning: invalid value encountered in double_scalars\n",
+ " ret = ret.dtype.type(ret / rcount)\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:262: RuntimeWarning: Degrees of freedom <= 0 for slice\n",
+ " ret = _var(a, axis=axis, dtype=dtype, out=out, ddof=ddof,\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:222: RuntimeWarning: invalid value encountered in true_divide\n",
+ " arrmean = um.true_divide(arrmean, div, out=arrmean, casting='unsafe',\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:254: RuntimeWarning: invalid value encountered in double_scalars\n",
+ " ret = ret.dtype.type(ret / rcount)\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/fromnumeric.py:3440: RuntimeWarning: Mean of empty slice.\n",
+ " return _methods._mean(a, axis=axis, dtype=dtype,\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:189: RuntimeWarning: invalid value encountered in double_scalars\n",
+ " ret = ret.dtype.type(ret / rcount)\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:262: RuntimeWarning: Degrees of freedom <= 0 for slice\n",
+ " ret = _var(a, axis=axis, dtype=dtype, out=out, ddof=ddof,\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:222: RuntimeWarning: invalid value encountered in true_divide\n",
+ " arrmean = um.true_divide(arrmean, div, out=arrmean, casting='unsafe',\n",
+ "/home/stefan/.local/lib/python3.8/site-packages/numpy/core/_methods.py:254: RuntimeWarning: invalid value encountered in double_scalars\n",
+ " ret = ret.dtype.type(ret / rcount)\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Read in results\n",
+ "## Params - Either specify a file prefix or a whole folder\n",
+ "file_prefix = None # Path(\"./results/2022-07-05T20:00:06_throughput_write_\")\n",
+ "folder = Path(\"./results/benchmark_posix\")\n",
+ "\n",
+ "## Plot code\n",
+ "path = file_prefix if file_prefix else folder\n",
+ "\n",
+ "benchmarks = [\n",
+ " (\"throughput_write\", \"write_bw_mean_in_MiB\"),\n",
+ " (\"throughput_read\", \"read_bw_mean_in_MiB\"),\n",
+ " (\"iops_write\", \"write_iops_mean\"),\n",
+ " (\"iops_read\", \"read_iops_mean\"),\n",
+ " (\"latency_write\", \"write_lat_mean_in_ms\"),\n",
+ " (\"latency_read\", \"read_lat_mean_in_ms\"),\n",
+ "]\n",
+ "dfs = []\n",
+ "\n",
+ "for label, metric in benchmarks:\n",
+ " dfs.append(pd.concat({\n",
+ " \"local\": load_data(path, metric=metric, suffix=\"posix_local.json\", x_labels=[label]),\n",
+ " \"nfs_isilon\": load_data(path, metric=metric, suffix=\"posix_isilon.json\", x_labels=[label]),\n",
+ " \"nfs_netapp\": load_data(path, metric=metric, suffix=\"posix_netapp.json\", x_labels=[label]),\n",
+ " \"nfs_ssdtank\": load_data(path, metric=metric, suffix=\"posix_ssd_tank.json\", x_labels=[label]),\n",
+ " \"irodsfs_isilon\": load_data(path, metric=metric, suffix=\"posix_irods_fuse_on_isilon.json\", x_labels=[label]),\n",
+ " \"irodsfs_netapp\": load_data(path, metric=metric, suffix=\"posix_irods_fuse_on_netapp.json\", x_labels=[label]),\n",
+ " \"irodsfs_s3\": load_data(path, metric=metric, suffix=\"posix_irods_fuse_on_s3.json\", x_labels=[label]),\n",
+ " \"webdav_isilon\": load_data(path, metric=metric, suffix=\"posix_irods_davrods_on_isilon.json\", x_labels=[label]),\n",
+ " \"webdav_netapp\": load_data(path, metric=metric, suffix=\"posix_irods_davrods_on_netapp.json\", x_labels=[label]),\n",
+ " \"webdav_s3\": load_data(path, metric=metric, suffix=\"posix_irods_davrods_on_s3.json\", x_labels=[label]),\n",
+ " }))\n",
+ " \n",
+ "for df in dfs:\n",
+ " print(df)\n",
+ "\n",
+ "def extract_stats(dfs, benchmark_type, label, index):\n",
+ " pm = \" & $\\ \\pm\\ $ & \" # Plus/minus seperator\n",
+ " \n",
+ " print(f\"{label}\", end=\"\")\n",
+ " for benchmark, df in zip(benchmarks, dfs):\n",
+ " if benchmark_type not in benchmark[0]:\n",
+ " continue\n",
+ " avg, std = df[\"mean\"][index], df[\"std\"][index]\n",
+ " if np.isnan(avg) or np.isnan(std):\n",
+ " print(\"& &-& \", end=\"\")\n",
+ " elif \"latency\" in benchmark[0]:\n",
+ " print(f\"&{avg:.2f}{pm}{std:.2f}\", end=\"\")\n",
+ " else:\n",
+ " print(f\"&{avg:.0f}{pm}{std:.0f}\", end=\"\")\n",
+ " print(\"\\\\\\\\\")\n",
+ "\n",
+ "print(\"#\"*10)\n",
+ "print(\"read\")\n",
+ "extract_stats(dfs, \"read\", \"local\", 0)\n",
+ "extract_stats(dfs, \"read\", \"nfs\\_isilon\", 1)\n",
+ "extract_stats(dfs, \"read\", \"nfs\\_netapp\", 2)\n",
+ "extract_stats(dfs, \"read\", \"nfs\\_ssdtank\", 3)\n",
+ "extract_stats(dfs, \"read\", \"irodsfs\\_isilon\", 4)\n",
+ "extract_stats(dfs, \"read\", \"irodsfs\\_netapp\", 5)\n",
+ "extract_stats(dfs, \"read\", \"irodsfs\\_s3\", 6)\n",
+ "extract_stats(dfs, \"read\", \"webdav\\_isilon\", 7)\n",
+ "extract_stats(dfs, \"read\", \"webdav\\_netapp\", 8)\n",
+ "extract_stats(dfs, \"read\", \"webdav\\_s3\", 9)\n",
+ "print(\"#\"*10)\n",
+ "print(\"write\")\n",
+ "extract_stats(dfs, \"write\", \"local\", 0)\n",
+ "extract_stats(dfs, \"write\", \"nfs\\_isilon\", 1)\n",
+ "extract_stats(dfs, \"write\", \"nfs\\_netapp\", 2)\n",
+ "extract_stats(dfs, \"write\", \"nfs\\_ssdtank\", 3)\n",
+ "extract_stats(dfs, \"write\", \"irodsfs\\_isilon\", 4)\n",
+ "extract_stats(dfs, \"write\", \"irodsfs\\_netapp\", 5)\n",
+ "extract_stats(dfs, \"write\", \"irodsfs\\_s3\", 6)\n",
+ "extract_stats(dfs, \"write\", \"webdav\\_isilon\", 7)\n",
+ "extract_stats(dfs, \"write\", \"webdav\\_netapp\", 8)\n",
+ "extract_stats(dfs, \"write\", \"webdav\\_s3\", 9)\n",
+ "print(\"#\"*10)\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2f425dd9",
+ "metadata": {},
+ "source": [
+ "# Experiment: Compare Storage Systems (metadata ops)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "id": "d22b0f25",
+ "metadata": {
+ "scrolled": false
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "dir_create\n",
+ " mean std num\n",
+ "local 10 329.163769 152.234556 13\n",
+ " 100 2071.620385 884.264211 13\n",
+ " 1000 10232.780308 1272.849495 13\n",
+ " 10000 9032.135538 435.179116 13\n",
+ " 100000 9299.858923 175.215523 13\n",
+ "nfs\\_isilon 10 195.988000 59.939368 13\n",
+ " 100 793.855385 135.069714 13\n",
+ " 1000 1101.131231 193.412898 13\n",
+ " 10000 1135.692692 194.854261 13\n",
+ " 100000 1143.458308 180.377407 13\n",
+ "nfs\\_netapp 10 224.921154 67.818113 13\n",
+ " 100 555.779154 260.472172 13\n",
+ " 1000 624.946077 193.867056 13\n",
+ " 10000 576.150308 176.623132 13\n",
+ " 100000 511.110462 138.396179 13\n",
+ "nfs\\_ssdtank 10 164.436385 54.121114 13\n",
+ " 100 444.391923 53.602625 13\n",
+ " 1000 549.655692 17.742884 13\n",
+ " 10000 570.905769 12.877858 13\n",
+ " 100000 572.165385 7.762889 13\n",
+ "irodsfs\\_isilon 10 48.612846 6.384393 13\n",
+ " 100 66.647231 4.040323 13\n",
+ " 1000 67.148077 4.118740 13\n",
+ "irodsfs\\_netapp 10 54.868000 5.241568 13\n",
+ " 100 65.060385 5.534450 13\n",
+ " 1000 66.788231 4.543550 13\n",
+ "irodsfs\\_s3 10 52.199000 5.496321 13\n",
+ " 100 65.853769 5.133760 13\n",
+ " 1000 65.617769 4.313346 13\n",
+ "webdav\\_isilon 10 43.415077 2.727672 13\n",
+ " 100 52.333692 3.522601 13\n",
+ " 1000 51.939615 2.655561 13\n",
+ "webdav\\_netapp 10 43.050000 5.280436 13\n",
+ " 100 52.408154 3.453970 13\n",
+ " 1000 52.628923 2.875553 13\n",
+ "webdav\\_s3 10 46.547385 5.849078 13\n",
+ " 100 53.453077 2.703629 13\n",
+ " 1000 49.934077 1.998971 13\n",
+ "dir_stat\n",
+ " mean std num\n",
+ "local 10 4903.421769 441.547795 13\n",
+ " 100 45838.354923 5377.864877 13\n",
+ " 1000 233061.933769 55760.969888 13\n",
+ " 10000 375090.382385 82336.096811 13\n",
+ " 100000 493404.431923 31582.908479 13\n",
+ "nfs\\_isilon 10 5046.035308 426.992078 13\n",
+ " 100 47908.866154 9164.167719 13\n",
+ " 1000 217926.376000 18603.163358 13\n",
+ " 10000 347076.756538 26372.698212 13\n",
+ " 100000 5933.326923 723.409106 13\n",
+ "nfs\\_netapp 10 5032.723000 530.669961 13\n",
+ " 100 42446.628154 4613.618048 13\n",
+ " 1000 199228.459231 13750.305203 13\n",
+ " 10000 320719.258769 88893.118314 13\n",
+ " 100000 1711.970000 345.235216 13\n",
+ "nfs\\_ssdtank 10 4859.555385 1085.588514 13\n",
+ " 100 42218.569231 6822.250879 13\n",
+ " 1000 199143.034769 33649.752286 13\n",
+ " 10000 350900.231846 15439.923913 13\n",
+ " 100000 5953.793231 555.896984 13\n",
+ "irodsfs\\_isilon 10 5035.984538 921.358526 13\n",
+ " 100 43024.587462 6898.244423 13\n",
+ " 1000 237523.874769 13015.711273 13\n",
+ "irodsfs\\_netapp 10 5157.653615 838.771320 13\n",
+ " 100 45628.061769 7559.504808 13\n",
+ " 1000 232570.004308 21502.649131 13\n",
+ "irodsfs\\_s3 10 5308.842308 738.370070 13\n",
+ " 100 42802.546462 6221.474577 13\n",
+ " 1000 256695.605308 22044.428503 13\n",
+ "webdav\\_isilon 10 4179.347154 892.416027 13\n",
+ " 100 29056.400000 2752.434597 13\n",
+ " 1000 37438.846846 4834.634912 13\n",
+ "webdav\\_netapp 10 5037.517846 898.702958 13\n",
+ " 100 31875.486615 5067.051866 13\n",
+ " 1000 35221.576538 1093.067040 13\n",
+ "webdav\\_s3 10 4761.742462 988.414662 13\n",
+ " 100 27853.972462 2585.285027 13\n",
+ " 1000 37180.181077 6067.943252 13\n",
+ "dir_remove\n",
+ " mean std num\n",
+ "local 10 1821.557538 528.942466 13\n",
+ " 100 13189.609077 4378.672436 13\n",
+ " 1000 44171.995385 7241.695999 13\n",
+ " 10000 51278.081000 3253.314651 13\n",
+ " 100000 50921.158231 4063.479253 13\n",
+ "nfs\\_isilon 10 694.152231 148.851698 13\n",
+ " 100 798.781385 138.882741 13\n",
+ " 1000 787.212308 149.117879 13\n",
+ " 10000 659.417615 108.473817 13\n",
+ " 100000 668.133615 94.712954 13\n",
+ "nfs\\_netapp 10 616.598308 293.039897 13\n",
+ " 100 665.335231 423.913667 13\n",
+ " 1000 578.009846 208.307393 13\n",
+ " 10000 527.639000 124.240981 13\n",
+ " 100000 348.622231 139.202960 13\n",
+ "nfs\\_ssdtank 10 502.098000 30.780070 13\n",
+ " 100 560.564769 25.186562 13\n",
+ " 1000 571.138923 19.432215 13\n",
+ " 10000 557.507308 22.281691 13\n",
+ " 100000 566.146846 8.793936 13\n",
+ "irodsfs\\_isilon 10 79.571615 8.184698 13\n",
+ " 100 80.615923 6.300171 13\n",
+ " 1000 79.635154 5.704232 13\n",
+ "irodsfs\\_netapp 10 79.429538 8.798302 13\n",
+ " 100 78.024923 9.059523 13\n",
+ " 1000 76.922462 4.977721 13\n",
+ "irodsfs\\_s3 10 77.849769 14.291074 13\n",
+ " 100 79.215231 4.942006 13\n",
+ " 1000 76.930231 5.321220 13\n",
+ "webdav\\_isilon 10 25.302385 2.312536 13\n",
+ " 100 26.834615 1.803641 13\n",
+ " 1000 24.479154 1.163734 13\n",
+ "webdav\\_netapp 10 26.420846 1.292369 13\n",
+ " 100 27.683231 1.077549 13\n",
+ " 1000 24.944385 1.190534 13\n",
+ "webdav\\_s3 10 25.829538 2.388285 13\n",
+ " 100 27.255923 1.542436 13\n",
+ " 1000 24.586538 0.847337 13\n",
+ "file_create\n",
+ " mean std num\n",
+ "local 10 2269.084385 493.167142 13\n",
+ " 100 14860.881000 4475.754971 13\n",
+ " 1000 46389.432615 11445.400606 13\n",
+ " 10000 69316.555308 5957.516021 13\n",
+ " 100000 60108.552692 5030.659188 13\n",
+ "nfs\\_isilon 10 739.175923 168.284640 13\n",
+ " 100 914.898231 154.825232 13\n",
+ " 1000 883.298308 165.035079 13\n",
+ " 10000 888.657769 143.740313 13\n",
+ " 100000 885.593000 113.886786 13\n",
+ "nfs\\_netapp 10 712.926077 147.005125 13\n",
+ " 100 787.941000 174.857985 13\n",
+ " 1000 715.902231 133.802680 13\n",
+ " 10000 644.425462 70.703895 13\n",
+ " 100000 609.878154 111.072831 13\n",
+ "nfs\\_ssdtank 10 452.141615 22.782450 13\n",
+ " 100 489.759692 37.549920 13\n",
+ " 1000 519.751846 22.905900 13\n",
+ " 10000 531.789231 21.559985 13\n",
+ " 100000 533.790846 13.829183 13\n",
+ "irodsfs\\_isilon 10 13.763923 0.607451 13\n",
+ " 100 11.366462 0.350744 13\n",
+ " 1000 6.492231 0.117393 13\n",
+ "irodsfs\\_netapp 10 13.378077 1.003661 13\n",
+ " 100 11.015846 0.544440 13\n",
+ " 1000 6.435077 0.082649 13\n",
+ "irodsfs\\_s3 10 4.710154 0.653466 13\n",
+ " 100 7.469692 0.628851 13\n",
+ " 1000 5.761308 0.255480 13\n",
+ "webdav\\_isilon 10 7.918385 0.284586 13\n",
+ " 100 7.741154 0.232701 13\n",
+ " 1000 7.721000 0.215543 13\n",
+ "webdav\\_netapp 10 8.001923 0.530186 13\n",
+ " 100 7.823615 0.325362 13\n",
+ " 1000 7.688308 0.279878 13\n",
+ "webdav\\_s3 10 4.495846 0.176324 13\n",
+ " 100 4.470462 0.413204 13\n",
+ " 1000 4.514462 0.096477 13\n",
+ "file_stat\n",
+ " mean std num\n",
+ "local 10 6498.657846 580.412268 13\n",
+ " 100 61827.760385 7749.924436 13\n",
+ " 1000 317965.234923 30767.858842 13\n",
+ " 10000 441784.347923 90929.086964 13\n",
+ " 100000 497632.692615 33653.042218 13\n",
+ "nfs\\_isilon 10 6309.326462 946.283365 13\n",
+ " 100 49271.744231 9801.768938 13\n",
+ " 1000 231276.330846 28322.443228 13\n",
+ " 10000 5499.986538 563.309754 13\n",
+ " 100000 4917.837308 433.719772 13\n",
+ "nfs\\_netapp 10 6906.592154 1054.363347 13\n",
+ " 100 48707.785846 4619.834015 13\n",
+ " 1000 220725.584846 25204.265524 13\n",
+ " 10000 1830.346308 243.995131 13\n",
+ " 100000 1624.909000 485.601595 13\n",
+ "nfs\\_ssdtank 10 6567.300077 1299.470539 13\n",
+ " 100 45847.169000 8974.315155 13\n",
+ " 1000 219190.580231 23380.023372 13\n",
+ " 10000 6217.690077 793.742725 13\n",
+ " 100000 5451.145000 522.150744 13\n",
+ "irodsfs\\_isilon 10 4949.429308 1124.488087 13\n",
+ " 100 40234.515077 8195.438287 13\n",
+ " 1000 143.668231 25.846130 13\n",
+ "irodsfs\\_netapp 10 4442.225000 1123.853088 13\n",
+ " 100 41247.678231 4496.840883 13\n",
+ " 1000 153.234692 11.690668 13\n",
+ "irodsfs\\_s3 10 4024.333769 1004.505053 13\n",
+ " 100 38446.422692 5438.448783 13\n",
+ " 1000 171.549462 10.386184 13\n",
+ "webdav\\_isilon 10 214.338538 27.507937 13\n",
+ " 100 1956.569000 241.793420 13\n",
+ " 1000 13825.906077 1281.377382 13\n",
+ "webdav\\_netapp 10 209.670308 15.834858 13\n",
+ " 100 2055.081077 309.890926 13\n",
+ " 1000 12532.129846 1441.986798 13\n",
+ "webdav\\_s3 10 76.310769 12.816307 13\n",
+ " 100 767.701462 67.726645 13\n",
+ " 1000 6099.770769 716.522894 13\n",
+ "file_read\n",
+ " mean std num\n",
+ "local 10 6750.087846 981.965112 13\n",
+ " 100 54380.022615 7137.089127 13\n",
+ " 1000 236905.286462 22110.129076 13\n",
+ " 10000 339217.932615 17668.767155 13\n",
+ " 100000 331679.056692 17961.417736 13\n",
+ "nfs\\_isilon 10 1283.661692 93.185867 13\n",
+ " 100 1575.255615 177.752205 13\n",
+ " 1000 1654.904615 124.041412 13\n",
+ " 10000 1611.166308 88.349216 13\n",
+ " 100000 1593.013692 83.450422 13\n",
+ "nfs\\_netapp 10 823.933462 286.700545 13\n",
+ " 100 1007.820077 167.890270 13\n",
+ " 1000 879.279923 204.276480 13\n",
+ " 10000 889.289538 114.458868 13\n",
+ " 100000 817.301308 235.867273 13\n",
+ "nfs\\_ssdtank 10 1683.901231 213.019189 13\n",
+ " 100 2593.463462 328.565185 13\n",
+ " 1000 2769.372000 284.208925 13\n",
+ " 10000 2901.442615 288.648728 13\n",
+ " 100000 2729.638769 241.099419 13\n",
+ "irodsfs\\_isilon 10 33.972538 4.591990 13\n",
+ " 100 20.181308 1.390393 13\n",
+ " 1000 13.825538 0.940195 13\n",
+ "irodsfs\\_netapp 10 31.971154 4.411706 13\n",
+ " 100 19.341385 1.514505 13\n",
+ " 1000 13.111385 0.897127 13\n",
+ "irodsfs\\_s3 10 22.774846 1.847655 13\n",
+ " 100 28.524538 1.734236 13\n",
+ " 1000 18.877692 0.919563 13\n",
+ "webdav\\_isilon 10 7.551769 0.303996 13\n",
+ " 100 7.340923 0.205797 13\n",
+ " 1000 7.298000 0.215045 13\n",
+ "webdav\\_netapp 10 7.708231 0.351873 13\n",
+ " 100 7.233308 0.294328 13\n",
+ " 1000 7.261231 0.256176 13\n",
+ "webdav\\_s3 10 4.568000 0.161705 13\n",
+ " 100 4.237231 0.178763 13\n",
+ " 1000 4.272923 0.100173 13\n",
+ "file_remove\n",
+ " mean std num\n",
+ "local 10 2281.792538 325.961595 13\n",
+ " 100 15463.142462 5713.414167 13\n",
+ " 1000 63101.556846 9712.901621 13\n",
+ " 10000 85105.330846 13878.507486 13\n",
+ " 100000 73783.329462 8160.672156 13\n",
+ "nfs\\_isilon 10 1120.344308 213.322372 13\n",
+ " 100 1349.434308 226.913384 13\n",
+ " 1000 1314.556385 270.320106 13\n",
+ " 10000 1063.162615 199.936011 13\n",
+ " 100000 975.398000 76.462666 13\n",
+ "nfs\\_netapp 10 1154.976769 419.681848 13\n",
+ " 100 1203.067615 324.666288 13\n",
+ " 1000 1313.495692 138.465489 13\n",
+ " 10000 1285.437538 125.227800 13\n",
+ " 100000 1095.549231 263.683799 13\n",
+ "nfs\\_ssdtank 10 522.643538 17.370707 13\n",
+ " 100 579.388231 20.756202 13\n",
+ " 1000 578.652077 17.434715 13\n",
+ " 10000 578.331154 17.605361 13\n",
+ " 100000 585.016462 8.678842 13\n",
+ "irodsfs\\_isilon 10 37.532385 4.914748 13\n",
+ " 100 35.967385 6.331701 13\n",
+ " 1000 34.034385 4.324617 13\n",
+ "irodsfs\\_netapp 10 37.968308 5.544754 13\n",
+ " 100 39.250846 4.042518 13\n",
+ " 1000 32.508846 3.989500 13\n",
+ "irodsfs\\_s3 10 16.967154 4.362387 13\n",
+ " 100 25.240154 1.138464 13\n",
+ " 1000 25.159769 0.482726 13\n",
+ "webdav\\_isilon 10 15.608769 1.064103 13\n",
+ " 100 16.859846 0.792448 13\n",
+ " 1000 17.200692 0.588769 13\n",
+ "webdav\\_netapp 10 14.423231 0.914523 13\n",
+ " 100 15.214538 1.138684 13\n",
+ " 1000 15.700846 1.141287 13\n",
+ "webdav\\_s3 10 3.659462 0.539954 13\n",
+ " 100 4.393231 0.605544 13\n",
+ " 1000 4.942769 0.795328 13\n",
+ "##########\n",
+ "local & 9300 & $\\ \\pm\\ $ & 175 & 493404 & $\\ \\pm\\ $ & 31583 & 50921 & $\\ \\pm\\ $ & 4063\\\\\n",
+ "nfs\\_isilon & 1143 & $\\ \\pm\\ $ & 180 & 5933 & $\\ \\pm\\ $ & 723 & 668 & $\\ \\pm\\ $ & 95\\\\\n",
+ "nfs\\_netapp & 511 & $\\ \\pm\\ $ & 138 & 1712 & $\\ \\pm\\ $ & 345 & 349 & $\\ \\pm\\ $ & 139\\\\\n",
+ "nfs\\_ssdtank & 572 & $\\ \\pm\\ $ & 8 & 5954 & $\\ \\pm\\ $ & 556 & 566 & $\\ \\pm\\ $ & 9\\\\\n",
+ "irodsfs\\_isilon & 67 & $\\ \\pm\\ $ & 4 & 237524 & $\\ \\pm\\ $ & 13016 & 80 & $\\ \\pm\\ $ & 6\\\\\n",
+ "irodsfs\\_netapp & 67 & $\\ \\pm\\ $ & 5 & 232570 & $\\ \\pm\\ $ & 21503 & 77 & $\\ \\pm\\ $ & 5\\\\\n",
+ "irodsfs\\_s3 & 66 & $\\ \\pm\\ $ & 4 & 256696 & $\\ \\pm\\ $ & 22044 & 77 & $\\ \\pm\\ $ & 5\\\\\n",
+ "webdav\\_isilon & 52 & $\\ \\pm\\ $ & 3 & 37439 & $\\ \\pm\\ $ & 4835 & 24 & $\\ \\pm\\ $ & 1\\\\\n",
+ "webdav\\_netapp & 53 & $\\ \\pm\\ $ & 3 & 35222 & $\\ \\pm\\ $ & 1093 & 25 & $\\ \\pm\\ $ & 1\\\\\n",
+ "webdav\\_s3 & 50 & $\\ \\pm\\ $ & 2 & 37180 & $\\ \\pm\\ $ & 6068 & 25 & $\\ \\pm\\ $ & 1\\\\\n",
+ "##########\n",
+ "local & 60109 & $\\ \\pm\\ $ & 5031 & 497633 & $\\ \\pm\\ $ & 33653 & 331679 & $\\ \\pm\\ $ & 17961 & 73783 & $\\ \\pm\\ $ & 8161\\\\\n",
+ "nfs\\_isilon & 886 & $\\ \\pm\\ $ & 114 & 4918 & $\\ \\pm\\ $ & 434 & 1593 & $\\ \\pm\\ $ & 83 & 975 & $\\ \\pm\\ $ & 76\\\\\n",
+ "nfs\\_netapp & 610 & $\\ \\pm\\ $ & 111 & 1625 & $\\ \\pm\\ $ & 486 & 817 & $\\ \\pm\\ $ & 236 & 1096 & $\\ \\pm\\ $ & 264\\\\\n",
+ "nfs\\_ssdtank & 534 & $\\ \\pm\\ $ & 14 & 5451 & $\\ \\pm\\ $ & 522 & 2730 & $\\ \\pm\\ $ & 241 & 585 & $\\ \\pm\\ $ & 9\\\\\n",
+ "irodsfs\\_isilon & 6 & $\\ \\pm\\ $ & 0 & 144 & $\\ \\pm\\ $ & 26 & 14 & $\\ \\pm\\ $ & 1 & 34 & $\\ \\pm\\ $ & 4\\\\\n",
+ "irodsfs\\_netapp & 6 & $\\ \\pm\\ $ & 0 & 153 & $\\ \\pm\\ $ & 12 & 13 & $\\ \\pm\\ $ & 1 & 33 & $\\ \\pm\\ $ & 4\\\\\n",
+ "irodsfs\\_s3 & 6 & $\\ \\pm\\ $ & 0 & 172 & $\\ \\pm\\ $ & 10 & 19 & $\\ \\pm\\ $ & 1 & 25 & $\\ \\pm\\ $ & 0\\\\\n",
+ "webdav\\_isilon & 8 & $\\ \\pm\\ $ & 0 & 13826 & $\\ \\pm\\ $ & 1281 & 7 & $\\ \\pm\\ $ & 0 & 17 & $\\ \\pm\\ $ & 1\\\\\n",
+ "webdav\\_netapp & 8 & $\\ \\pm\\ $ & 0 & 12532 & $\\ \\pm\\ $ & 1442 & 7 & $\\ \\pm\\ $ & 0 & 16 & $\\ \\pm\\ $ & 1\\\\\n",
+ "webdav\\_s3 & 5 & $\\ \\pm\\ $ & 0 & 6100 & $\\ \\pm\\ $ & 717 & 4 & $\\ \\pm\\ $ & 0 & 5 & $\\ \\pm\\ $ & 1\\\\\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Read in results\n",
+ "## Params - Either specify a file prefix or a whole folder\n",
+ "file_prefix = None # Path(\"./results/2022-07-05T20:00:06_throughput_write_\")\n",
+ "folder = Path(\"./results/benchmark_metadata\")\n",
+ "\n",
+ "## Plot code\n",
+ "path = file_prefix if file_prefix else folder\n",
+ "\n",
+ "metrics = [\n",
+ " \"dir_create\",\n",
+ " \"dir_stat\",\n",
+ " \"dir_remove\",\n",
+ " \"file_create\",\n",
+ " \"file_stat\",\n",
+ " \"file_read\",\n",
+ " \"file_remove\",\n",
+ " #\"tree_create\",\n",
+ " #\"tree_remove\",\n",
+ "]\n",
+ "dfs = []\n",
+ "\n",
+ "for metric in metrics:\n",
+ " dfs.append(pd.concat({\n",
+ " \"local\": load_data(path, metric=metric, suffix=\"metadata_local.json\"),\n",
+ " \"nfs\\_isilon\": load_data(path, metric=metric, suffix=\"metadata_isilon.json\"),\n",
+ " \"nfs\\_netapp\": load_data(path, metric=metric, suffix=\"metadata_netapp.json\"),\n",
+ " \"nfs\\_ssdtank\": load_data(path, metric=metric, suffix=\"metadata_ssd_tank.json\"),\n",
+ " \"irodsfs\\_isilon\": load_data(path, metric=metric, suffix=\"metadata_irods_fuse_on_isilon.json\"),\n",
+ " \"irodsfs\\_netapp\": load_data(path, metric=metric, suffix=\"metadata_irods_fuse_on_netapp.json\"),\n",
+ " \"irodsfs\\_s3\": load_data(path, metric=metric, suffix=\"metadata_irods_fuse_on_s3.json\"),\n",
+ " \"webdav\\_isilon\": load_data(path, metric=metric, suffix=\"metadata_irods_davrods_on_isilon.json\"),\n",
+ " \"webdav\\_netapp\": load_data(path, metric=metric, suffix=\"metadata_irods_davrods_on_netapp.json\"),\n",
+ " \"webdav\\_s3\": load_data(path, metric=metric, suffix=\"metadata_irods_davrods_on_s3.json\"),\n",
+ " }))\n",
+ "for metric,df in zip(metrics, dfs):\n",
+ " print(metric)\n",
+ " print(df)\n",
+ " pass\n",
+ " \n",
+ "def extract_stats(dfs, num_files, metric_part,label):\n",
+ " pm = \" & $\\ \\pm\\ $ & \" # Plus/minus seperator\n",
+ " \n",
+ " print(f\"{label}\", end=\"\")\n",
+ " for metric, df in zip(metrics, dfs):\n",
+ " if metric_part not in metric:\n",
+ " continue\n",
+ " current = df.loc[(label, num_files)]\n",
+ " avg, std = current[\"mean\"], current[\"std\"]\n",
+ " print(f\" & {avg:.0f}{pm}{std:.0f}\", end=\"\")\n",
+ " print(\"\\\\\\\\\")\n",
+ "\n",
+ "print(\"#\"*10)\n",
+ "extract_stats(dfs, \"100000\", \"dir_\", \"local\")\n",
+ "extract_stats(dfs, \"100000\", \"dir_\", \"nfs\\_isilon\")\n",
+ "extract_stats(dfs, \"100000\", \"dir_\", \"nfs\\_netapp\")\n",
+ "extract_stats(dfs, \"100000\", \"dir_\", \"nfs\\_ssdtank\")\n",
+ "extract_stats(dfs, \"1000\", \"dir_\", \"irodsfs\\_isilon\")\n",
+ "extract_stats(dfs, \"1000\", \"dir_\", \"irodsfs\\_netapp\")\n",
+ "extract_stats(dfs, \"1000\", \"dir_\", \"irodsfs\\_s3\")\n",
+ "extract_stats(dfs, \"1000\", \"dir_\", \"webdav\\_isilon\")\n",
+ "extract_stats(dfs, \"1000\", \"dir_\", \"webdav\\_netapp\")\n",
+ "extract_stats(dfs, \"1000\", \"dir_\", \"webdav\\_s3\")\n",
+ "print(\"#\"*10)\n",
+ "extract_stats(dfs, \"100000\", \"file_\", \"local\")\n",
+ "extract_stats(dfs, \"100000\", \"file_\", \"nfs\\_isilon\")\n",
+ "extract_stats(dfs, \"100000\", \"file_\", \"nfs\\_netapp\")\n",
+ "extract_stats(dfs, \"100000\", \"file_\", \"nfs\\_ssdtank\")\n",
+ "extract_stats(dfs, \"1000\", \"file_\", \"irodsfs\\_isilon\")\n",
+ "extract_stats(dfs, \"1000\", \"file_\", \"irodsfs\\_netapp\")\n",
+ "extract_stats(dfs, \"1000\", \"file_\", \"irodsfs\\_s3\")\n",
+ "extract_stats(dfs, \"1000\", \"file_\", \"webdav\\_isilon\")\n",
+ "extract_stats(dfs, \"1000\", \"file_\", \"webdav\\_netapp\")\n",
+ "extract_stats(dfs, \"1000\", \"file_\", \"webdav\\_s3\")\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "17ac87fa",
+ "metadata": {},
+ "source": [
+ "# Experiment: Compare storage systems (S3)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "id": "4c7b4d20",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " mean std num\n",
+ "bw_get 791.015000 30.901982 16\n",
+ "bw_put 1066.654375 60.888718 16\n",
+ "ops_get 4501.262500 143.819740 8\n",
+ "ops_put 1060.538750 104.157789 8\n",
+ "ops_del 2515.205000 124.952359 16\n",
+ "##########\n",
+ "Read Bandwidth\n",
+ "s3\\_netapp & 791 & $\\ \\pm\\ $ & 31& 4501 & $\\ \\pm\\ $ & 144\\\\\n",
+ "##########\n",
+ "Write Bandwidth\n",
+ "s3\\_netapp & 1067 & $\\ \\pm\\ $ & 61& 1061 & $\\ \\pm\\ $ & 104\\\\\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Read in results\n",
+ "## Params - Either specify a file prefix or a whole folder\n",
+ "file_prefix = None # Path(\"./results/2022-07-05T20:00:06_throughput_write_\")\n",
+ "folder = Path(\"./results/benchmark_s3\")\n",
+ "\n",
+ "## Plot code\n",
+ "path = file_prefix if file_prefix else folder\n",
+ "\n",
+ "df = pd.concat({\n",
+ " \"bw_get\": load_data(path, metric=\"get_bw_in_MiB\", suffix=\"s3_warp_get_put.json\"),\n",
+ " \"bw_put\": load_data(path, metric=\"put_bw_in_MiB\", suffix=\"s3_warp_get_put.json\"),\n",
+ " \"ops_get\": load_data(path, metric=\"get_ops\", suffix=\"s3_warp_get_put_ops.json\"),\n",
+ " \"ops_put\": load_data(path, metric=\"put_ops\", suffix=\"s3_warp_get_put_ops.json\"),\n",
+ " \"ops_del\": load_data(path, metric=\"delete_ops\", suffix=\"s3_warp_delete.json\"),\n",
+ "})\n",
+ "df.index = df.index.droplevel(1)\n",
+ "\n",
+ "print(df)\n",
+ "\n",
+ "df_r_bw = df.loc[(\"bw_get\")]\n",
+ "df_w_bw = df.loc[(\"bw_put\")]\n",
+ "df_r_iops = df.loc[(\"ops_get\")]\n",
+ "df_w_iops = df.loc[(\"ops_put\")]\n",
+ "\n",
+ "pm = \" & $\\ \\pm\\ $ & \"\n",
+ "print(\"#\"*10)\n",
+ "print(\"Read Bandwidth\")\n",
+ "print(\"s3\\_netapp \", end=\"\")\n",
+ "print(f\"& {df_r_bw['mean']:.0f}{pm}{df_r_bw['std']:.0f}\", end=\"\")\n",
+ "print(f\"& {df_r_iops['mean']:.0f}{pm}{df_r_iops['std']:.0f}\", end=\"\")\n",
+ "print(\"\\\\\\\\\")\n",
+ "\n",
+ "print(\"#\"*10)\n",
+ "print(\"Write Bandwidth\")\n",
+ "print(\"s3\\_netapp \", end=\"\")\n",
+ "print(f\"& {df_w_bw['mean']:.0f}{pm}{df_w_bw['std']:.0f}\", end=\"\")\n",
+ "print(f\"& {df_w_iops['mean']:.0f}{pm}{df_w_iops['std']:.0f}\", end=\"\")\n",
+ "print(\"\\\\\\\\\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "fda89769",
+ "metadata": {},
+ "source": [
+ "# Experiment: Different access points for SSD Tank\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "id": "c6bfb214",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "application/javascript": [
+ "/* Put everything inside the global mpl namespace */\n",
+ "/* global mpl */\n",
+ "window.mpl = {};\n",
+ "\n",
+ "mpl.get_websocket_type = function () {\n",
+ " if (typeof WebSocket !== 'undefined') {\n",
+ " return WebSocket;\n",
+ " } else if (typeof MozWebSocket !== 'undefined') {\n",
+ " return MozWebSocket;\n",
+ " } else {\n",
+ " alert(\n",
+ " 'Your browser does not have WebSocket support. ' +\n",
+ " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
+ " 'Firefox 4 and 5 are also supported but you ' +\n",
+ " 'have to enable WebSockets in about:config.'\n",
+ " );\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n",
+ " this.id = figure_id;\n",
+ "\n",
+ " this.ws = websocket;\n",
+ "\n",
+ " this.supports_binary = this.ws.binaryType !== undefined;\n",
+ "\n",
+ " if (!this.supports_binary) {\n",
+ " var warnings = document.getElementById('mpl-warnings');\n",
+ " if (warnings) {\n",
+ " warnings.style.display = 'block';\n",
+ " warnings.textContent =\n",
+ " 'This browser does not support binary websocket messages. ' +\n",
+ " 'Performance may be slow.';\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " this.imageObj = new Image();\n",
+ "\n",
+ " this.context = undefined;\n",
+ " this.message = undefined;\n",
+ " this.canvas = undefined;\n",
+ " this.rubberband_canvas = undefined;\n",
+ " this.rubberband_context = undefined;\n",
+ " this.format_dropdown = undefined;\n",
+ "\n",
+ " this.image_mode = 'full';\n",
+ "\n",
+ " this.root = document.createElement('div');\n",
+ " this.root.setAttribute('style', 'display: inline-block');\n",
+ " this._root_extra_style(this.root);\n",
+ "\n",
+ " parent_element.appendChild(this.root);\n",
+ "\n",
+ " this._init_header(this);\n",
+ " this._init_canvas(this);\n",
+ " this._init_toolbar(this);\n",
+ "\n",
+ " var fig = this;\n",
+ "\n",
+ " this.waiting = false;\n",
+ "\n",
+ " this.ws.onopen = function () {\n",
+ " fig.send_message('supports_binary', { value: fig.supports_binary });\n",
+ " fig.send_message('send_image_mode', {});\n",
+ " if (fig.ratio !== 1) {\n",
+ " fig.send_message('set_device_pixel_ratio', {\n",
+ " device_pixel_ratio: fig.ratio,\n",
+ " });\n",
+ " }\n",
+ " fig.send_message('refresh', {});\n",
+ " };\n",
+ "\n",
+ " this.imageObj.onload = function () {\n",
+ " if (fig.image_mode === 'full') {\n",
+ " // Full images could contain transparency (where diff images\n",
+ " // almost always do), so we need to clear the canvas so that\n",
+ " // there is no ghosting.\n",
+ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
+ " }\n",
+ " fig.context.drawImage(fig.imageObj, 0, 0);\n",
+ " };\n",
+ "\n",
+ " this.imageObj.onunload = function () {\n",
+ " fig.ws.close();\n",
+ " };\n",
+ "\n",
+ " this.ws.onmessage = this._make_on_message_function(this);\n",
+ "\n",
+ " this.ondownload = ondownload;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_header = function () {\n",
+ " var titlebar = document.createElement('div');\n",
+ " titlebar.classList =\n",
+ " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n",
+ " var titletext = document.createElement('div');\n",
+ " titletext.classList = 'ui-dialog-title';\n",
+ " titletext.setAttribute(\n",
+ " 'style',\n",
+ " 'width: 100%; text-align: center; padding: 3px;'\n",
+ " );\n",
+ " titlebar.appendChild(titletext);\n",
+ " this.root.appendChild(titlebar);\n",
+ " this.header = titletext;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n",
+ "\n",
+ "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n",
+ "\n",
+ "mpl.figure.prototype._init_canvas = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var canvas_div = (this.canvas_div = document.createElement('div'));\n",
+ " canvas_div.setAttribute(\n",
+ " 'style',\n",
+ " 'border: 1px solid #ddd;' +\n",
+ " 'box-sizing: content-box;' +\n",
+ " 'clear: both;' +\n",
+ " 'min-height: 1px;' +\n",
+ " 'min-width: 1px;' +\n",
+ " 'outline: 0;' +\n",
+ " 'overflow: hidden;' +\n",
+ " 'position: relative;' +\n",
+ " 'resize: both;'\n",
+ " );\n",
+ "\n",
+ " function on_keyboard_event_closure(name) {\n",
+ " return function (event) {\n",
+ " return fig.key_event(event, name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " canvas_div.addEventListener(\n",
+ " 'keydown',\n",
+ " on_keyboard_event_closure('key_press')\n",
+ " );\n",
+ " canvas_div.addEventListener(\n",
+ " 'keyup',\n",
+ " on_keyboard_event_closure('key_release')\n",
+ " );\n",
+ "\n",
+ " this._canvas_extra_style(canvas_div);\n",
+ " this.root.appendChild(canvas_div);\n",
+ "\n",
+ " var canvas = (this.canvas = document.createElement('canvas'));\n",
+ " canvas.classList.add('mpl-canvas');\n",
+ " canvas.setAttribute('style', 'box-sizing: content-box;');\n",
+ "\n",
+ " this.context = canvas.getContext('2d');\n",
+ "\n",
+ " var backingStore =\n",
+ " this.context.backingStorePixelRatio ||\n",
+ " this.context.webkitBackingStorePixelRatio ||\n",
+ " this.context.mozBackingStorePixelRatio ||\n",
+ " this.context.msBackingStorePixelRatio ||\n",
+ " this.context.oBackingStorePixelRatio ||\n",
+ " this.context.backingStorePixelRatio ||\n",
+ " 1;\n",
+ "\n",
+ " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
+ "\n",
+ " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n",
+ " 'canvas'\n",
+ " ));\n",
+ " rubberband_canvas.setAttribute(\n",
+ " 'style',\n",
+ " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n",
+ " );\n",
+ "\n",
+ " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n",
+ " if (this.ResizeObserver === undefined) {\n",
+ " if (window.ResizeObserver !== undefined) {\n",
+ " this.ResizeObserver = window.ResizeObserver;\n",
+ " } else {\n",
+ " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n",
+ " this.ResizeObserver = obs.ResizeObserver;\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n",
+ " var nentries = entries.length;\n",
+ " for (var i = 0; i < nentries; i++) {\n",
+ " var entry = entries[i];\n",
+ " var width, height;\n",
+ " if (entry.contentBoxSize) {\n",
+ " if (entry.contentBoxSize instanceof Array) {\n",
+ " // Chrome 84 implements new version of spec.\n",
+ " width = entry.contentBoxSize[0].inlineSize;\n",
+ " height = entry.contentBoxSize[0].blockSize;\n",
+ " } else {\n",
+ " // Firefox implements old version of spec.\n",
+ " width = entry.contentBoxSize.inlineSize;\n",
+ " height = entry.contentBoxSize.blockSize;\n",
+ " }\n",
+ " } else {\n",
+ " // Chrome <84 implements even older version of spec.\n",
+ " width = entry.contentRect.width;\n",
+ " height = entry.contentRect.height;\n",
+ " }\n",
+ "\n",
+ " // Keep the size of the canvas and rubber band canvas in sync with\n",
+ " // the canvas container.\n",
+ " if (entry.devicePixelContentBoxSize) {\n",
+ " // Chrome 84 implements new version of spec.\n",
+ " canvas.setAttribute(\n",
+ " 'width',\n",
+ " entry.devicePixelContentBoxSize[0].inlineSize\n",
+ " );\n",
+ " canvas.setAttribute(\n",
+ " 'height',\n",
+ " entry.devicePixelContentBoxSize[0].blockSize\n",
+ " );\n",
+ " } else {\n",
+ " canvas.setAttribute('width', width * fig.ratio);\n",
+ " canvas.setAttribute('height', height * fig.ratio);\n",
+ " }\n",
+ " canvas.setAttribute(\n",
+ " 'style',\n",
+ " 'width: ' + width + 'px; height: ' + height + 'px;'\n",
+ " );\n",
+ "\n",
+ " rubberband_canvas.setAttribute('width', width);\n",
+ " rubberband_canvas.setAttribute('height', height);\n",
+ "\n",
+ " // And update the size in Python. We ignore the initial 0/0 size\n",
+ " // that occurs as the element is placed into the DOM, which should\n",
+ " // otherwise not happen due to the minimum size styling.\n",
+ " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n",
+ " fig.request_resize(width, height);\n",
+ " }\n",
+ " }\n",
+ " });\n",
+ " this.resizeObserverInstance.observe(canvas_div);\n",
+ "\n",
+ " function on_mouse_event_closure(name) {\n",
+ " return function (event) {\n",
+ " return fig.mouse_event(event, name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mousedown',\n",
+ " on_mouse_event_closure('button_press')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseup',\n",
+ " on_mouse_event_closure('button_release')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'dblclick',\n",
+ " on_mouse_event_closure('dblclick')\n",
+ " );\n",
+ " // Throttle sequential mouse events to 1 every 20ms.\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mousemove',\n",
+ " on_mouse_event_closure('motion_notify')\n",
+ " );\n",
+ "\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseenter',\n",
+ " on_mouse_event_closure('figure_enter')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseleave',\n",
+ " on_mouse_event_closure('figure_leave')\n",
+ " );\n",
+ "\n",
+ " canvas_div.addEventListener('wheel', function (event) {\n",
+ " if (event.deltaY < 0) {\n",
+ " event.step = 1;\n",
+ " } else {\n",
+ " event.step = -1;\n",
+ " }\n",
+ " on_mouse_event_closure('scroll')(event);\n",
+ " });\n",
+ "\n",
+ " canvas_div.appendChild(canvas);\n",
+ " canvas_div.appendChild(rubberband_canvas);\n",
+ "\n",
+ " this.rubberband_context = rubberband_canvas.getContext('2d');\n",
+ " this.rubberband_context.strokeStyle = '#000000';\n",
+ "\n",
+ " this._resize_canvas = function (width, height, forward) {\n",
+ " if (forward) {\n",
+ " canvas_div.style.width = width + 'px';\n",
+ " canvas_div.style.height = height + 'px';\n",
+ " }\n",
+ " };\n",
+ "\n",
+ " // Disable right mouse context menu.\n",
+ " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n",
+ " event.preventDefault();\n",
+ " return false;\n",
+ " });\n",
+ "\n",
+ " function set_focus() {\n",
+ " canvas.focus();\n",
+ " canvas_div.focus();\n",
+ " }\n",
+ "\n",
+ " window.setTimeout(set_focus, 100);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_toolbar = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var toolbar = document.createElement('div');\n",
+ " toolbar.classList = 'mpl-toolbar';\n",
+ " this.root.appendChild(toolbar);\n",
+ "\n",
+ " function on_click_closure(name) {\n",
+ " return function (_event) {\n",
+ " return fig.toolbar_button_onclick(name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " function on_mouseover_closure(tooltip) {\n",
+ " return function (event) {\n",
+ " if (!event.currentTarget.disabled) {\n",
+ " return fig.toolbar_button_onmouseover(tooltip);\n",
+ " }\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " fig.buttons = {};\n",
+ " var buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'mpl-button-group';\n",
+ " for (var toolbar_ind in mpl.toolbar_items) {\n",
+ " var name = mpl.toolbar_items[toolbar_ind][0];\n",
+ " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+ " var image = mpl.toolbar_items[toolbar_ind][2];\n",
+ " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+ "\n",
+ " if (!name) {\n",
+ " /* Instead of a spacer, we start a new button group. */\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ " buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'mpl-button-group';\n",
+ " continue;\n",
+ " }\n",
+ "\n",
+ " var button = (fig.buttons[name] = document.createElement('button'));\n",
+ " button.classList = 'mpl-widget';\n",
+ " button.setAttribute('role', 'button');\n",
+ " button.setAttribute('aria-disabled', 'false');\n",
+ " button.addEventListener('click', on_click_closure(method_name));\n",
+ " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+ "\n",
+ " var icon_img = document.createElement('img');\n",
+ " icon_img.src = '_images/' + image + '.png';\n",
+ " icon_img.srcset = '_images/' + image + '_large.png 2x';\n",
+ " icon_img.alt = tooltip;\n",
+ " button.appendChild(icon_img);\n",
+ "\n",
+ " buttonGroup.appendChild(button);\n",
+ " }\n",
+ "\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ "\n",
+ " var fmt_picker = document.createElement('select');\n",
+ " fmt_picker.classList = 'mpl-widget';\n",
+ " toolbar.appendChild(fmt_picker);\n",
+ " this.format_dropdown = fmt_picker;\n",
+ "\n",
+ " for (var ind in mpl.extensions) {\n",
+ " var fmt = mpl.extensions[ind];\n",
+ " var option = document.createElement('option');\n",
+ " option.selected = fmt === mpl.default_extension;\n",
+ " option.innerHTML = fmt;\n",
+ " fmt_picker.appendChild(option);\n",
+ " }\n",
+ "\n",
+ " var status_bar = document.createElement('span');\n",
+ " status_bar.classList = 'mpl-message';\n",
+ " toolbar.appendChild(status_bar);\n",
+ " this.message = status_bar;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n",
+ " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
+ " // which will in turn request a refresh of the image.\n",
+ " this.send_message('resize', { width: x_pixels, height: y_pixels });\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.send_message = function (type, properties) {\n",
+ " properties['type'] = type;\n",
+ " properties['figure_id'] = this.id;\n",
+ " this.ws.send(JSON.stringify(properties));\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.send_draw_message = function () {\n",
+ " if (!this.waiting) {\n",
+ " this.waiting = true;\n",
+ " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+ " var format_dropdown = fig.format_dropdown;\n",
+ " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
+ " fig.ondownload(fig, format);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_resize = function (fig, msg) {\n",
+ " var size = msg['size'];\n",
+ " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n",
+ " fig._resize_canvas(size[0], size[1], msg['forward']);\n",
+ " fig.send_message('refresh', {});\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n",
+ " var x0 = msg['x0'] / fig.ratio;\n",
+ " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n",
+ " var x1 = msg['x1'] / fig.ratio;\n",
+ " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n",
+ " x0 = Math.floor(x0) + 0.5;\n",
+ " y0 = Math.floor(y0) + 0.5;\n",
+ " x1 = Math.floor(x1) + 0.5;\n",
+ " y1 = Math.floor(y1) + 0.5;\n",
+ " var min_x = Math.min(x0, x1);\n",
+ " var min_y = Math.min(y0, y1);\n",
+ " var width = Math.abs(x1 - x0);\n",
+ " var height = Math.abs(y1 - y0);\n",
+ "\n",
+ " fig.rubberband_context.clearRect(\n",
+ " 0,\n",
+ " 0,\n",
+ " fig.canvas.width / fig.ratio,\n",
+ " fig.canvas.height / fig.ratio\n",
+ " );\n",
+ "\n",
+ " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n",
+ " // Updates the figure title.\n",
+ " fig.header.textContent = msg['label'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n",
+ " fig.rubberband_canvas.style.cursor = msg['cursor'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_message = function (fig, msg) {\n",
+ " fig.message.textContent = msg['message'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n",
+ " // Request the server to send over a new figure.\n",
+ " fig.send_draw_message();\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n",
+ " fig.image_mode = msg['mode'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n",
+ " for (var key in msg) {\n",
+ " if (!(key in fig.buttons)) {\n",
+ " continue;\n",
+ " }\n",
+ " fig.buttons[key].disabled = !msg[key];\n",
+ " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n",
+ " if (msg['mode'] === 'PAN') {\n",
+ " fig.buttons['Pan'].classList.add('active');\n",
+ " fig.buttons['Zoom'].classList.remove('active');\n",
+ " } else if (msg['mode'] === 'ZOOM') {\n",
+ " fig.buttons['Pan'].classList.remove('active');\n",
+ " fig.buttons['Zoom'].classList.add('active');\n",
+ " } else {\n",
+ " fig.buttons['Pan'].classList.remove('active');\n",
+ " fig.buttons['Zoom'].classList.remove('active');\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.updated_canvas_event = function () {\n",
+ " // Called whenever the canvas gets updated.\n",
+ " this.send_message('ack', {});\n",
+ "};\n",
+ "\n",
+ "// A function to construct a web socket function for onmessage handling.\n",
+ "// Called in the figure constructor.\n",
+ "mpl.figure.prototype._make_on_message_function = function (fig) {\n",
+ " return function socket_on_message(evt) {\n",
+ " if (evt.data instanceof Blob) {\n",
+ " var img = evt.data;\n",
+ " if (img.type !== 'image/png') {\n",
+ " /* FIXME: We get \"Resource interpreted as Image but\n",
+ " * transferred with MIME type text/plain:\" errors on\n",
+ " * Chrome. But how to set the MIME type? It doesn't seem\n",
+ " * to be part of the websocket stream */\n",
+ " img.type = 'image/png';\n",
+ " }\n",
+ "\n",
+ " /* Free the memory for the previous frames */\n",
+ " if (fig.imageObj.src) {\n",
+ " (window.URL || window.webkitURL).revokeObjectURL(\n",
+ " fig.imageObj.src\n",
+ " );\n",
+ " }\n",
+ "\n",
+ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
+ " img\n",
+ " );\n",
+ " fig.updated_canvas_event();\n",
+ " fig.waiting = false;\n",
+ " return;\n",
+ " } else if (\n",
+ " typeof evt.data === 'string' &&\n",
+ " evt.data.slice(0, 21) === 'data:image/png;base64'\n",
+ " ) {\n",
+ " fig.imageObj.src = evt.data;\n",
+ " fig.updated_canvas_event();\n",
+ " fig.waiting = false;\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " var msg = JSON.parse(evt.data);\n",
+ " var msg_type = msg['type'];\n",
+ "\n",
+ " // Call the \"handle_{type}\" callback, which takes\n",
+ " // the figure and JSON message as its only arguments.\n",
+ " try {\n",
+ " var callback = fig['handle_' + msg_type];\n",
+ " } catch (e) {\n",
+ " console.log(\n",
+ " \"No handler for the '\" + msg_type + \"' message type: \",\n",
+ " msg\n",
+ " );\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " if (callback) {\n",
+ " try {\n",
+ " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
+ " callback(fig, msg);\n",
+ " } catch (e) {\n",
+ " console.log(\n",
+ " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n",
+ " e,\n",
+ " e.stack,\n",
+ " msg\n",
+ " );\n",
+ " }\n",
+ " }\n",
+ " };\n",
+ "};\n",
+ "\n",
+ "// from https://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
+ "mpl.findpos = function (e) {\n",
+ " //this section is from http://www.quirksmode.org/js/events_properties.html\n",
+ " var targ;\n",
+ " if (!e) {\n",
+ " e = window.event;\n",
+ " }\n",
+ " if (e.target) {\n",
+ " targ = e.target;\n",
+ " } else if (e.srcElement) {\n",
+ " targ = e.srcElement;\n",
+ " }\n",
+ " if (targ.nodeType === 3) {\n",
+ " // defeat Safari bug\n",
+ " targ = targ.parentNode;\n",
+ " }\n",
+ "\n",
+ " // pageX,Y are the mouse positions relative to the document\n",
+ " var boundingRect = targ.getBoundingClientRect();\n",
+ " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n",
+ " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n",
+ "\n",
+ " return { x: x, y: y };\n",
+ "};\n",
+ "\n",
+ "/*\n",
+ " * return a copy of an object with only non-object keys\n",
+ " * we need this to avoid circular references\n",
+ " * https://stackoverflow.com/a/24161582/3208463\n",
+ " */\n",
+ "function simpleKeys(original) {\n",
+ " return Object.keys(original).reduce(function (obj, key) {\n",
+ " if (typeof original[key] !== 'object') {\n",
+ " obj[key] = original[key];\n",
+ " }\n",
+ " return obj;\n",
+ " }, {});\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.mouse_event = function (event, name) {\n",
+ " var canvas_pos = mpl.findpos(event);\n",
+ "\n",
+ " if (name === 'button_press') {\n",
+ " this.canvas.focus();\n",
+ " this.canvas_div.focus();\n",
+ " }\n",
+ "\n",
+ " var x = canvas_pos.x * this.ratio;\n",
+ " var y = canvas_pos.y * this.ratio;\n",
+ "\n",
+ " this.send_message(name, {\n",
+ " x: x,\n",
+ " y: y,\n",
+ " button: event.button,\n",
+ " step: event.step,\n",
+ " guiEvent: simpleKeys(event),\n",
+ " });\n",
+ "\n",
+ " /* This prevents the web browser from automatically changing to\n",
+ " * the text insertion cursor when the button is pressed. We want\n",
+ " * to control all of the cursor setting manually through the\n",
+ " * 'cursor' event from matplotlib */\n",
+ " event.preventDefault();\n",
+ " return false;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n",
+ " // Handle any extra behaviour associated with a key event\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.key_event = function (event, name) {\n",
+ " // Prevent repeat events\n",
+ " if (name === 'key_press') {\n",
+ " if (event.key === this._key) {\n",
+ " return;\n",
+ " } else {\n",
+ " this._key = event.key;\n",
+ " }\n",
+ " }\n",
+ " if (name === 'key_release') {\n",
+ " this._key = null;\n",
+ " }\n",
+ "\n",
+ " var value = '';\n",
+ " if (event.ctrlKey && event.key !== 'Control') {\n",
+ " value += 'ctrl+';\n",
+ " }\n",
+ " else if (event.altKey && event.key !== 'Alt') {\n",
+ " value += 'alt+';\n",
+ " }\n",
+ " else if (event.shiftKey && event.key !== 'Shift') {\n",
+ " value += 'shift+';\n",
+ " }\n",
+ "\n",
+ " value += 'k' + event.key;\n",
+ "\n",
+ " this._key_event_extra(event, name);\n",
+ "\n",
+ " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n",
+ " return false;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n",
+ " if (name === 'download') {\n",
+ " this.handle_save(this, null);\n",
+ " } else {\n",
+ " this.send_message('toolbar_button', { name: name });\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n",
+ " this.message.textContent = tooltip;\n",
+ "};\n",
+ "\n",
+ "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n",
+ "// prettier-ignore\n",
+ "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n",
+ "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
+ "\n",
+ "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
+ "\n",
+ "mpl.default_extension = \"png\";/* global mpl */\n",
+ "\n",
+ "var comm_websocket_adapter = function (comm) {\n",
+ " // Create a \"websocket\"-like object which calls the given IPython comm\n",
+ " // object with the appropriate methods. Currently this is a non binary\n",
+ " // socket, so there is still some room for performance tuning.\n",
+ " var ws = {};\n",
+ "\n",
+ " ws.binaryType = comm.kernel.ws.binaryType;\n",
+ " ws.readyState = comm.kernel.ws.readyState;\n",
+ " function updateReadyState(_event) {\n",
+ " if (comm.kernel.ws) {\n",
+ " ws.readyState = comm.kernel.ws.readyState;\n",
+ " } else {\n",
+ " ws.readyState = 3; // Closed state.\n",
+ " }\n",
+ " }\n",
+ " comm.kernel.ws.addEventListener('open', updateReadyState);\n",
+ " comm.kernel.ws.addEventListener('close', updateReadyState);\n",
+ " comm.kernel.ws.addEventListener('error', updateReadyState);\n",
+ "\n",
+ " ws.close = function () {\n",
+ " comm.close();\n",
+ " };\n",
+ " ws.send = function (m) {\n",
+ " //console.log('sending', m);\n",
+ " comm.send(m);\n",
+ " };\n",
+ " // Register the callback with on_msg.\n",
+ " comm.on_msg(function (msg) {\n",
+ " //console.log('receiving', msg['content']['data'], msg);\n",
+ " var data = msg['content']['data'];\n",
+ " if (data['blob'] !== undefined) {\n",
+ " data = {\n",
+ " data: new Blob(msg['buffers'], { type: data['blob'] }),\n",
+ " };\n",
+ " }\n",
+ " // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
+ " ws.onmessage(data);\n",
+ " });\n",
+ " return ws;\n",
+ "};\n",
+ "\n",
+ "mpl.mpl_figure_comm = function (comm, msg) {\n",
+ " // This is the function which gets called when the mpl process\n",
+ " // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
+ "\n",
+ " var id = msg.content.data.id;\n",
+ " // Get hold of the div created by the display call when the Comm\n",
+ " // socket was opened in Python.\n",
+ " var element = document.getElementById(id);\n",
+ " var ws_proxy = comm_websocket_adapter(comm);\n",
+ "\n",
+ " function ondownload(figure, _format) {\n",
+ " window.open(figure.canvas.toDataURL());\n",
+ " }\n",
+ "\n",
+ " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n",
+ "\n",
+ " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
+ " // web socket which is closed, not our websocket->open comm proxy.\n",
+ " ws_proxy.onopen();\n",
+ "\n",
+ " fig.parent_element = element;\n",
+ " fig.cell_info = mpl.find_output_cell(\"\");\n",
+ " if (!fig.cell_info) {\n",
+ " console.error('Failed to find cell for figure', id, fig);\n",
+ " return;\n",
+ " }\n",
+ " fig.cell_info[0].output_area.element.on(\n",
+ " 'cleared',\n",
+ " { fig: fig },\n",
+ " fig._remove_fig_handler\n",
+ " );\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_close = function (fig, msg) {\n",
+ " var width = fig.canvas.width / fig.ratio;\n",
+ " fig.cell_info[0].output_area.element.off(\n",
+ " 'cleared',\n",
+ " fig._remove_fig_handler\n",
+ " );\n",
+ " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n",
+ "\n",
+ " // Update the output cell to use the data from the current canvas.\n",
+ " fig.push_to_output();\n",
+ " var dataURL = fig.canvas.toDataURL();\n",
+ " // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
+ " // the notebook keyboard shortcuts fail.\n",
+ " IPython.keyboard_manager.enable();\n",
+ " fig.parent_element.innerHTML =\n",
+ " '
';\n",
+ " fig.close_ws(fig, msg);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.close_ws = function (fig, msg) {\n",
+ " fig.send_message('closing', msg);\n",
+ " // fig.ws.close()\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n",
+ " // Turn the data on the canvas into data in the output cell.\n",
+ " var width = this.canvas.width / this.ratio;\n",
+ " var dataURL = this.canvas.toDataURL();\n",
+ " this.cell_info[1]['text/html'] =\n",
+ " '
';\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.updated_canvas_event = function () {\n",
+ " // Tell IPython that the notebook contents must change.\n",
+ " IPython.notebook.set_dirty(true);\n",
+ " this.send_message('ack', {});\n",
+ " var fig = this;\n",
+ " // Wait a second, then push the new image to the DOM so\n",
+ " // that it is saved nicely (might be nice to debounce this).\n",
+ " setTimeout(function () {\n",
+ " fig.push_to_output();\n",
+ " }, 1000);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_toolbar = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var toolbar = document.createElement('div');\n",
+ " toolbar.classList = 'btn-toolbar';\n",
+ " this.root.appendChild(toolbar);\n",
+ "\n",
+ " function on_click_closure(name) {\n",
+ " return function (_event) {\n",
+ " return fig.toolbar_button_onclick(name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " function on_mouseover_closure(tooltip) {\n",
+ " return function (event) {\n",
+ " if (!event.currentTarget.disabled) {\n",
+ " return fig.toolbar_button_onmouseover(tooltip);\n",
+ " }\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " fig.buttons = {};\n",
+ " var buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'btn-group';\n",
+ " var button;\n",
+ " for (var toolbar_ind in mpl.toolbar_items) {\n",
+ " var name = mpl.toolbar_items[toolbar_ind][0];\n",
+ " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+ " var image = mpl.toolbar_items[toolbar_ind][2];\n",
+ " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+ "\n",
+ " if (!name) {\n",
+ " /* Instead of a spacer, we start a new button group. */\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ " buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'btn-group';\n",
+ " continue;\n",
+ " }\n",
+ "\n",
+ " button = fig.buttons[name] = document.createElement('button');\n",
+ " button.classList = 'btn btn-default';\n",
+ " button.href = '#';\n",
+ " button.title = name;\n",
+ " button.innerHTML = '';\n",
+ " button.addEventListener('click', on_click_closure(method_name));\n",
+ " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+ " buttonGroup.appendChild(button);\n",
+ " }\n",
+ "\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ "\n",
+ " // Add the status bar.\n",
+ " var status_bar = document.createElement('span');\n",
+ " status_bar.classList = 'mpl-message pull-right';\n",
+ " toolbar.appendChild(status_bar);\n",
+ " this.message = status_bar;\n",
+ "\n",
+ " // Add the close button to the window.\n",
+ " var buttongrp = document.createElement('div');\n",
+ " buttongrp.classList = 'btn-group inline pull-right';\n",
+ " button = document.createElement('button');\n",
+ " button.classList = 'btn btn-mini btn-primary';\n",
+ " button.href = '#';\n",
+ " button.title = 'Stop Interaction';\n",
+ " button.innerHTML = '';\n",
+ " button.addEventListener('click', function (_evt) {\n",
+ " fig.handle_close(fig, {});\n",
+ " });\n",
+ " button.addEventListener(\n",
+ " 'mouseover',\n",
+ " on_mouseover_closure('Stop Interaction')\n",
+ " );\n",
+ " buttongrp.appendChild(button);\n",
+ " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n",
+ " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._remove_fig_handler = function (event) {\n",
+ " var fig = event.data.fig;\n",
+ " if (event.target !== this) {\n",
+ " // Ignore bubbled events from children.\n",
+ " return;\n",
+ " }\n",
+ " fig.close_ws(fig, {});\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._root_extra_style = function (el) {\n",
+ " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._canvas_extra_style = function (el) {\n",
+ " // this is important to make the div 'focusable\n",
+ " el.setAttribute('tabindex', 0);\n",
+ " // reach out to IPython and tell the keyboard manager to turn it's self\n",
+ " // off when our div gets focus\n",
+ "\n",
+ " // location in version 3\n",
+ " if (IPython.notebook.keyboard_manager) {\n",
+ " IPython.notebook.keyboard_manager.register_events(el);\n",
+ " } else {\n",
+ " // location in version 2\n",
+ " IPython.keyboard_manager.register_events(el);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._key_event_extra = function (event, _name) {\n",
+ " // Check for shift+enter\n",
+ " if (event.shiftKey && event.which === 13) {\n",
+ " this.canvas_div.blur();\n",
+ " // select the cell after this one\n",
+ " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n",
+ " IPython.notebook.select(index + 1);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+ " fig.ondownload(fig, null);\n",
+ "};\n",
+ "\n",
+ "mpl.find_output_cell = function (html_output) {\n",
+ " // Return the cell and output element which can be found *uniquely* in the notebook.\n",
+ " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
+ " // IPython event is triggered only after the cells have been serialised, which for\n",
+ " // our purposes (turning an active figure into a static one), is too late.\n",
+ " var cells = IPython.notebook.get_cells();\n",
+ " var ncells = cells.length;\n",
+ " for (var i = 0; i < ncells; i++) {\n",
+ " var cell = cells[i];\n",
+ " if (cell.cell_type === 'code') {\n",
+ " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n",
+ " var data = cell.output_area.outputs[j];\n",
+ " if (data.data) {\n",
+ " // IPython >= 3 moved mimebundle to data attribute of output\n",
+ " data = data.data;\n",
+ " }\n",
+ " if (data['text/html'] === html_output) {\n",
+ " return [cell, data, j];\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "// Register the function which deals with the matplotlib target/channel.\n",
+ "// The kernel may be null if the page has been refreshed.\n",
+ "if (IPython.notebook.kernel !== null) {\n",
+ " IPython.notebook.kernel.comm_manager.register_target(\n",
+ " 'matplotlib',\n",
+ " mpl.mpl_figure_comm\n",
+ " );\n",
+ "}\n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "
"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "---------\n",
+ "Throughput write\n",
+ " mean std num\n",
+ "HDD 10Gb/s 878.803601 18.709457 5\n",
+ "HDD 40Gb/s 600.292803 87.651497 5\n",
+ "SSD 10Gb/s 606.877758 26.572561 5\n",
+ "SSD 40Gb/s 421.088637 9.143627 5\n",
+ "---------\n",
+ "Throughput read\n",
+ " mean std num\n",
+ "HDD 10Gb/s 492.820954 19.354635 5\n",
+ "HDD 40Gb/s 621.963134 65.783003 5\n",
+ "SSD 10Gb/s 395.877308 17.819422 5\n",
+ "SSD 40Gb/s 327.628254 22.662875 5\n",
+ "---------\n",
+ "IOPS write\n",
+ " mean std num\n",
+ "HDD 10Gb/s 4318.617123 940.576233 5\n",
+ "HDD 40Gb/s 9052.378237 1452.820299 5\n",
+ "SSD 10Gb/s 4953.676898 1122.344462 5\n",
+ "SSD 40Gb/s 9609.892948 1136.503413 5\n",
+ "---------\n",
+ "IOPS read\n",
+ " mean std num\n",
+ "HDD 10Gb/s 22353.215900 187.161457 5\n",
+ "HDD 40Gb/s 43927.077824 693.896096 5\n",
+ "SSD 10Gb/s 21739.072803 154.185243 5\n",
+ "SSD 40Gb/s 40184.774059 1188.841197 5\n",
+ "---------\n",
+ "Latency write\n",
+ " mean std num\n",
+ "HDD 10Gb/s 0.936706 0.027597 5\n",
+ "HDD 40Gb/s 0.636252 0.005675 5\n",
+ "SSD 10Gb/s 0.958689 0.016840 5\n",
+ "SSD 40Gb/s 0.745047 0.017858 5\n",
+ "---------\n",
+ "Latency read\n",
+ " mean std num\n",
+ "HDD 10Gb/s 0.515506 0.016763 5\n",
+ "HDD 40Gb/s 0.394226 0.007062 5\n",
+ "SSD 10Gb/s 0.520675 0.013789 5\n",
+ "SSD 40Gb/s 0.470065 0.038499 5\n"
+ ]
+ }
+ ],
+ "source": [
+ "%matplotlib notebook\n",
+ "\n",
+ "## Params\n",
+ "result_file_prefix = \"2022-06-08T19:53:53_benchmark\"\n",
+ "\n",
+ "## Plot code\n",
+ "hdd_s = load_json(f\"./results/{result_file_prefix}_isilon_hdd.json\")\n",
+ "hdd_f = load_json(f\"./results/{result_file_prefix}_isilon_hdd_fast.json\")\n",
+ "ssd_s = load_json(f\"./results/{result_file_prefix}_isilon_ssd.json\")\n",
+ "ssd_f = load_json(f\"./results/{result_file_prefix}_isilon_ssd_fast.json\")\n",
+ "\n",
+ "benchmarks = [\n",
+ " (\"throughput_write\", \"write_bw_mean_in_MiB\"),\n",
+ " (\"throughput_read\", \"read_bw_mean_in_MiB\"),\n",
+ " (\"iops_write\", \"write_iops_mean\"),\n",
+ " (\"iops_read\", \"read_iops_mean\"),\n",
+ " (\"latency_write\", \"write_lat_mean_in_ms\"),\n",
+ " (\"latency_read\", \"read_lat_mean_in_ms\"),\n",
+ "]\n",
+ "\n",
+ "dfs = []\n",
+ "for label, metric in benchmarks:\n",
+ " dfs.append(pd.concat([\n",
+ " extract_results(hdd_s, metric, [label]).rename(index={label: \"HDD 10Gb/s\"}),\n",
+ " extract_results(hdd_f, metric, [label]).rename(index={label: \"HDD 40Gb/s\"}),\n",
+ " extract_results(ssd_s, metric, [label]).rename(index={label: \"SSD 10Gb/s\"}),\n",
+ " extract_results(ssd_f, metric, [label]).rename(index={label: \"SSD 40Gb/s\"}),\n",
+ " ]))\n",
+ "\n",
+ " \n",
+ "fig, axis = plt.subplots(3, 2)\n",
+ "fig.suptitle('Isilon SSD,HDD')\n",
+ "plt.subplots_adjust(wspace=0.7500, hspace=1.5)\n",
+ " \n",
+ "dfs[0][\"mean\"].plot(\n",
+ " ax=axis[0][0],\n",
+ " kind='bar', rot=0,\n",
+ " ylabel='Bandwidth (MiB/s)',\n",
+ " title='Throughput (w)',\n",
+ " yerr=dfs[0][\"std\"],\n",
+ ")\n",
+ "dfs[1][\"mean\"].plot(\n",
+ " ax=axis[0][1],\n",
+ " kind='bar', rot=0,\n",
+ " title='Throughput (r)',\n",
+ " yerr=dfs[1][\"std\"],\n",
+ ")\n",
+ "\n",
+ "dfs[2][\"mean\"].plot(\n",
+ " ax=axis[1][0],\n",
+ " kind='bar', rot=0,\n",
+ " ylabel='IOPS',\n",
+ " title='IOPS (w)',\n",
+ " yerr=dfs[2][\"std\"]\n",
+ ")\n",
+ "\n",
+ "dfs[3][\"mean\"].plot(\n",
+ " ax=axis[1][1],\n",
+ " kind='bar', rot=0,\n",
+ " title='IOPS (r)',\n",
+ " yerr=dfs[3][\"std\"],\n",
+ ")\n",
+ "dfs[4][\"mean\"].plot(\n",
+ " ax=axis[2][0],\n",
+ " kind='bar', rot=0,\n",
+ " ylabel='Latency (ms)',\n",
+ " title='Latency (w)',\n",
+ " yerr=dfs[4][\"std\"]\n",
+ ")\n",
+ "dfs[5][\"mean\"].plot(\n",
+ " ax=axis[2][1],\n",
+ " kind='bar', rot=0,\n",
+ " title='Latency (r)',\n",
+ " yerr=dfs[5][\"std\"],\n",
+ ")\n",
+ "\n",
+ "axis[0][1].sharey(axis[0][0])\n",
+ "axis[1][0].sharey(axis[1][1])\n",
+ "axis[2][1].sharey(axis[2][0])\n",
+ "for i,j in [(0,0),(0,1),(1,0),(1,1),(2,0),(2,1)]:\n",
+ " axis[i][j].tick_params(labelrotation=20)\n",
+ " # axis[i][j].get_legend().remove()\n",
+ " \n",
+ "#plt.tight_layout()\n",
+ "# plt.savefig(\"paramstudy_throughput.png\",bbox_inches='tight')\n",
+ "plt.show()\n",
+ "\n",
+ "print(\"---------\", \"Throughput write\", sep=\"\\n\")\n",
+ "print(dfs[0])\n",
+ "print(\"---------\", \"Throughput read\", sep=\"\\n\")\n",
+ "print(dfs[1])\n",
+ "print(\"---------\", \"IOPS write\", sep=\"\\n\")\n",
+ "print(dfs[2])\n",
+ "print(\"---------\", \"IOPS read\", sep=\"\\n\")\n",
+ "print(dfs[3])\n",
+ "print(\"---------\", \"Latency write\", sep=\"\\n\")\n",
+ "print(dfs[4])\n",
+ "print(\"---------\", \"Latency read\", sep=\"\\n\")\n",
+ "print(dfs[5])\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3c1ea5a5",
+ "metadata": {},
+ "source": [
+ "# Experiment: Galaxy"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 18,
+ "id": "6450ef1f",
+ "metadata": {
+ "scrolled": false
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "No result for: 2022-09-01T15:53:08 10x1G netapp\n",
+ "No result for: 2022-08-31T08:43:00 1x0 irods_via_davrods_on_isilon\n",
+ "No result for: 2022-08-31T10:22:48 1x1K irods_via_davrods_on_netapp\n",
+ "No result for: 2022-09-01T15:50:52 1x1G irods_on_s3\n",
+ "No result for: 2022-09-01T15:50:52 1x1G irods_via_davrods_on_s3\n",
+ "No result for: 2022-09-01T15:50:52 1x1G isilon\n",
+ "No result for: 2022-08-31T11:10:13 10x1K irods_via_davrods_on_isilon\n",
+ "No result for: 2022-08-30T16:02:19 1x1K s3\n",
+ "No result for: 2022-09-04T19:23:17 1x1M irods_on_isilon\n",
+ "No result for: 2022-09-01T15:53:08 10x1G irods_on_isilon\n",
+ "No result for: 2022-08-31T08:43:00 1x0 s3\n",
+ "No result for: 2022-08-30T17:41:29 100x1K s3\n",
+ "No result for: 2022-09-01T15:14:36 1x1M irods_on_netapp\n",
+ "No result for: 2022-09-01T15:50:52 1x1G irods_on_isilon\n",
+ "No result for: 2022-09-01T15:50:52 1x1G irods_via_davrods_on_netapp\n",
+ "No result for: 2022-09-02T22:16:49 10x1G irods_on_s3\n",
+ "No result for: 2022-09-01T15:53:08 10x1G isilon\n",
+ "No result for: 2022-09-01T15:14:36 1x1M irods_on_isilon\n",
+ "No result for: 2022-09-01T15:53:08 10x1G irods_via_davrods_on_s3\n",
+ "No result for: 2022-09-01T15:53:08 10x1G irods_on_netapp\n",
+ "No result for: 2022-08-30T18:33:23 1000x1K s3\n",
+ "No result for: 2022-09-01T15:53:08 10x1G irods_on_s3\n",
+ "No result for: 2022-09-01T15:50:52 1x1G netapp\n",
+ "No result for: 2022-09-01T15:53:08 10x1G irods_via_davrods_on_isilon\n",
+ "No result for: 2022-08-30T22:51:10 1x1M s3\n",
+ "No result for: 2022-09-01T15:53:08 10x1G irods_via_davrods_on_netapp\n",
+ "No result for: 2022-09-03T16:25:08 2000x1K netapp\n",
+ "No result for: 2022-09-01T16:15:08 1x0 isilon\n",
+ "No result for: 2022-08-30T18:33:23 1000x1K netapp\n",
+ "No result for: 2022-09-03T00:00:04 1x0 irods_via_davrods_on_isilon\n",
+ "No result for: 2022-08-31T11:58:15 100x1K irods_on_netapp\n",
+ "No result for: 2022-09-01T15:50:52 1x1G irods_on_netapp\n",
+ "No result for: 2022-09-01T15:50:52 1x1G irods_via_davrods_on_isilon\n",
+ "No result for: 2022-09-01T15:14:36 1x1M irods_via_davrods_on_s3\n",
+ "No result for: 2022-09-01T15:14:36 1x1M irods_on_s3\n",
+ "No result for: 2022-08-30T20:18:06 2000x1K s3\n",
+ "No result for: 2022-08-30T23:34:17 1x1G irods_via_davrods_on_s3\n",
+ "No result for: 2022-08-30T18:33:23 1000x1K irods_via_davrods_on_isilon\n",
+ "No result for: 2022-08-30T16:50:41 10x1K s3\n",
+ "No result for: 2022-08-30T15:20:58 1x0 s3\n",
+ "['1000x1K', '100x1K', '10x1G', '10x1K', '1x0', '1x1G', '1x1K', '1x1M', '2000x1K']\n",
+ " mean std num\n",
+ "1x0 isilon 38.570352 4.155044 20\n",
+ " netapp 38.019479 4.514666 20\n",
+ " s3 30.605186 13.899926 18\n",
+ "1x1K isilon 61.779090 5.862412 19\n",
+ " netapp 61.750140 5.591803 19\n",
+ " s3 53.955329 13.170368 17\n",
+ "10x1K isilon 61.898669 5.417915 19\n",
+ " netapp 61.687431 5.491162 19\n",
+ " s3 55.906516 15.133376 17\n",
+ "100x1K isilon 61.409810 5.781275 20\n",
+ " netapp 61.627463 5.621175 20\n",
+ " s3 99.493895 11.511222 18\n",
+ "1000x1K isilon 74.438915 8.021540 19\n",
+ " netapp 72.967062 6.368718 18\n",
+ " s3 384.243165 23.929343 17\n",
+ "2000x1K isilon 86.488210 10.314142 19\n",
+ " netapp 85.885138 8.478362 18\n",
+ " s3 666.036083 68.643406 18\n"
+ ]
+ },
+ {
+ "data": {
+ "application/javascript": [
+ "/* Put everything inside the global mpl namespace */\n",
+ "/* global mpl */\n",
+ "window.mpl = {};\n",
+ "\n",
+ "mpl.get_websocket_type = function () {\n",
+ " if (typeof WebSocket !== 'undefined') {\n",
+ " return WebSocket;\n",
+ " } else if (typeof MozWebSocket !== 'undefined') {\n",
+ " return MozWebSocket;\n",
+ " } else {\n",
+ " alert(\n",
+ " 'Your browser does not have WebSocket support. ' +\n",
+ " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
+ " 'Firefox 4 and 5 are also supported but you ' +\n",
+ " 'have to enable WebSockets in about:config.'\n",
+ " );\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n",
+ " this.id = figure_id;\n",
+ "\n",
+ " this.ws = websocket;\n",
+ "\n",
+ " this.supports_binary = this.ws.binaryType !== undefined;\n",
+ "\n",
+ " if (!this.supports_binary) {\n",
+ " var warnings = document.getElementById('mpl-warnings');\n",
+ " if (warnings) {\n",
+ " warnings.style.display = 'block';\n",
+ " warnings.textContent =\n",
+ " 'This browser does not support binary websocket messages. ' +\n",
+ " 'Performance may be slow.';\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " this.imageObj = new Image();\n",
+ "\n",
+ " this.context = undefined;\n",
+ " this.message = undefined;\n",
+ " this.canvas = undefined;\n",
+ " this.rubberband_canvas = undefined;\n",
+ " this.rubberband_context = undefined;\n",
+ " this.format_dropdown = undefined;\n",
+ "\n",
+ " this.image_mode = 'full';\n",
+ "\n",
+ " this.root = document.createElement('div');\n",
+ " this.root.setAttribute('style', 'display: inline-block');\n",
+ " this._root_extra_style(this.root);\n",
+ "\n",
+ " parent_element.appendChild(this.root);\n",
+ "\n",
+ " this._init_header(this);\n",
+ " this._init_canvas(this);\n",
+ " this._init_toolbar(this);\n",
+ "\n",
+ " var fig = this;\n",
+ "\n",
+ " this.waiting = false;\n",
+ "\n",
+ " this.ws.onopen = function () {\n",
+ " fig.send_message('supports_binary', { value: fig.supports_binary });\n",
+ " fig.send_message('send_image_mode', {});\n",
+ " if (fig.ratio !== 1) {\n",
+ " fig.send_message('set_device_pixel_ratio', {\n",
+ " device_pixel_ratio: fig.ratio,\n",
+ " });\n",
+ " }\n",
+ " fig.send_message('refresh', {});\n",
+ " };\n",
+ "\n",
+ " this.imageObj.onload = function () {\n",
+ " if (fig.image_mode === 'full') {\n",
+ " // Full images could contain transparency (where diff images\n",
+ " // almost always do), so we need to clear the canvas so that\n",
+ " // there is no ghosting.\n",
+ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
+ " }\n",
+ " fig.context.drawImage(fig.imageObj, 0, 0);\n",
+ " };\n",
+ "\n",
+ " this.imageObj.onunload = function () {\n",
+ " fig.ws.close();\n",
+ " };\n",
+ "\n",
+ " this.ws.onmessage = this._make_on_message_function(this);\n",
+ "\n",
+ " this.ondownload = ondownload;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_header = function () {\n",
+ " var titlebar = document.createElement('div');\n",
+ " titlebar.classList =\n",
+ " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n",
+ " var titletext = document.createElement('div');\n",
+ " titletext.classList = 'ui-dialog-title';\n",
+ " titletext.setAttribute(\n",
+ " 'style',\n",
+ " 'width: 100%; text-align: center; padding: 3px;'\n",
+ " );\n",
+ " titlebar.appendChild(titletext);\n",
+ " this.root.appendChild(titlebar);\n",
+ " this.header = titletext;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n",
+ "\n",
+ "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n",
+ "\n",
+ "mpl.figure.prototype._init_canvas = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var canvas_div = (this.canvas_div = document.createElement('div'));\n",
+ " canvas_div.setAttribute(\n",
+ " 'style',\n",
+ " 'border: 1px solid #ddd;' +\n",
+ " 'box-sizing: content-box;' +\n",
+ " 'clear: both;' +\n",
+ " 'min-height: 1px;' +\n",
+ " 'min-width: 1px;' +\n",
+ " 'outline: 0;' +\n",
+ " 'overflow: hidden;' +\n",
+ " 'position: relative;' +\n",
+ " 'resize: both;'\n",
+ " );\n",
+ "\n",
+ " function on_keyboard_event_closure(name) {\n",
+ " return function (event) {\n",
+ " return fig.key_event(event, name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " canvas_div.addEventListener(\n",
+ " 'keydown',\n",
+ " on_keyboard_event_closure('key_press')\n",
+ " );\n",
+ " canvas_div.addEventListener(\n",
+ " 'keyup',\n",
+ " on_keyboard_event_closure('key_release')\n",
+ " );\n",
+ "\n",
+ " this._canvas_extra_style(canvas_div);\n",
+ " this.root.appendChild(canvas_div);\n",
+ "\n",
+ " var canvas = (this.canvas = document.createElement('canvas'));\n",
+ " canvas.classList.add('mpl-canvas');\n",
+ " canvas.setAttribute('style', 'box-sizing: content-box;');\n",
+ "\n",
+ " this.context = canvas.getContext('2d');\n",
+ "\n",
+ " var backingStore =\n",
+ " this.context.backingStorePixelRatio ||\n",
+ " this.context.webkitBackingStorePixelRatio ||\n",
+ " this.context.mozBackingStorePixelRatio ||\n",
+ " this.context.msBackingStorePixelRatio ||\n",
+ " this.context.oBackingStorePixelRatio ||\n",
+ " this.context.backingStorePixelRatio ||\n",
+ " 1;\n",
+ "\n",
+ " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
+ "\n",
+ " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n",
+ " 'canvas'\n",
+ " ));\n",
+ " rubberband_canvas.setAttribute(\n",
+ " 'style',\n",
+ " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n",
+ " );\n",
+ "\n",
+ " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n",
+ " if (this.ResizeObserver === undefined) {\n",
+ " if (window.ResizeObserver !== undefined) {\n",
+ " this.ResizeObserver = window.ResizeObserver;\n",
+ " } else {\n",
+ " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n",
+ " this.ResizeObserver = obs.ResizeObserver;\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n",
+ " var nentries = entries.length;\n",
+ " for (var i = 0; i < nentries; i++) {\n",
+ " var entry = entries[i];\n",
+ " var width, height;\n",
+ " if (entry.contentBoxSize) {\n",
+ " if (entry.contentBoxSize instanceof Array) {\n",
+ " // Chrome 84 implements new version of spec.\n",
+ " width = entry.contentBoxSize[0].inlineSize;\n",
+ " height = entry.contentBoxSize[0].blockSize;\n",
+ " } else {\n",
+ " // Firefox implements old version of spec.\n",
+ " width = entry.contentBoxSize.inlineSize;\n",
+ " height = entry.contentBoxSize.blockSize;\n",
+ " }\n",
+ " } else {\n",
+ " // Chrome <84 implements even older version of spec.\n",
+ " width = entry.contentRect.width;\n",
+ " height = entry.contentRect.height;\n",
+ " }\n",
+ "\n",
+ " // Keep the size of the canvas and rubber band canvas in sync with\n",
+ " // the canvas container.\n",
+ " if (entry.devicePixelContentBoxSize) {\n",
+ " // Chrome 84 implements new version of spec.\n",
+ " canvas.setAttribute(\n",
+ " 'width',\n",
+ " entry.devicePixelContentBoxSize[0].inlineSize\n",
+ " );\n",
+ " canvas.setAttribute(\n",
+ " 'height',\n",
+ " entry.devicePixelContentBoxSize[0].blockSize\n",
+ " );\n",
+ " } else {\n",
+ " canvas.setAttribute('width', width * fig.ratio);\n",
+ " canvas.setAttribute('height', height * fig.ratio);\n",
+ " }\n",
+ " canvas.setAttribute(\n",
+ " 'style',\n",
+ " 'width: ' + width + 'px; height: ' + height + 'px;'\n",
+ " );\n",
+ "\n",
+ " rubberband_canvas.setAttribute('width', width);\n",
+ " rubberband_canvas.setAttribute('height', height);\n",
+ "\n",
+ " // And update the size in Python. We ignore the initial 0/0 size\n",
+ " // that occurs as the element is placed into the DOM, which should\n",
+ " // otherwise not happen due to the minimum size styling.\n",
+ " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n",
+ " fig.request_resize(width, height);\n",
+ " }\n",
+ " }\n",
+ " });\n",
+ " this.resizeObserverInstance.observe(canvas_div);\n",
+ "\n",
+ " function on_mouse_event_closure(name) {\n",
+ " return function (event) {\n",
+ " return fig.mouse_event(event, name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mousedown',\n",
+ " on_mouse_event_closure('button_press')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseup',\n",
+ " on_mouse_event_closure('button_release')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'dblclick',\n",
+ " on_mouse_event_closure('dblclick')\n",
+ " );\n",
+ " // Throttle sequential mouse events to 1 every 20ms.\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mousemove',\n",
+ " on_mouse_event_closure('motion_notify')\n",
+ " );\n",
+ "\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseenter',\n",
+ " on_mouse_event_closure('figure_enter')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseleave',\n",
+ " on_mouse_event_closure('figure_leave')\n",
+ " );\n",
+ "\n",
+ " canvas_div.addEventListener('wheel', function (event) {\n",
+ " if (event.deltaY < 0) {\n",
+ " event.step = 1;\n",
+ " } else {\n",
+ " event.step = -1;\n",
+ " }\n",
+ " on_mouse_event_closure('scroll')(event);\n",
+ " });\n",
+ "\n",
+ " canvas_div.appendChild(canvas);\n",
+ " canvas_div.appendChild(rubberband_canvas);\n",
+ "\n",
+ " this.rubberband_context = rubberband_canvas.getContext('2d');\n",
+ " this.rubberband_context.strokeStyle = '#000000';\n",
+ "\n",
+ " this._resize_canvas = function (width, height, forward) {\n",
+ " if (forward) {\n",
+ " canvas_div.style.width = width + 'px';\n",
+ " canvas_div.style.height = height + 'px';\n",
+ " }\n",
+ " };\n",
+ "\n",
+ " // Disable right mouse context menu.\n",
+ " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n",
+ " event.preventDefault();\n",
+ " return false;\n",
+ " });\n",
+ "\n",
+ " function set_focus() {\n",
+ " canvas.focus();\n",
+ " canvas_div.focus();\n",
+ " }\n",
+ "\n",
+ " window.setTimeout(set_focus, 100);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_toolbar = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var toolbar = document.createElement('div');\n",
+ " toolbar.classList = 'mpl-toolbar';\n",
+ " this.root.appendChild(toolbar);\n",
+ "\n",
+ " function on_click_closure(name) {\n",
+ " return function (_event) {\n",
+ " return fig.toolbar_button_onclick(name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " function on_mouseover_closure(tooltip) {\n",
+ " return function (event) {\n",
+ " if (!event.currentTarget.disabled) {\n",
+ " return fig.toolbar_button_onmouseover(tooltip);\n",
+ " }\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " fig.buttons = {};\n",
+ " var buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'mpl-button-group';\n",
+ " for (var toolbar_ind in mpl.toolbar_items) {\n",
+ " var name = mpl.toolbar_items[toolbar_ind][0];\n",
+ " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+ " var image = mpl.toolbar_items[toolbar_ind][2];\n",
+ " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+ "\n",
+ " if (!name) {\n",
+ " /* Instead of a spacer, we start a new button group. */\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ " buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'mpl-button-group';\n",
+ " continue;\n",
+ " }\n",
+ "\n",
+ " var button = (fig.buttons[name] = document.createElement('button'));\n",
+ " button.classList = 'mpl-widget';\n",
+ " button.setAttribute('role', 'button');\n",
+ " button.setAttribute('aria-disabled', 'false');\n",
+ " button.addEventListener('click', on_click_closure(method_name));\n",
+ " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+ "\n",
+ " var icon_img = document.createElement('img');\n",
+ " icon_img.src = '_images/' + image + '.png';\n",
+ " icon_img.srcset = '_images/' + image + '_large.png 2x';\n",
+ " icon_img.alt = tooltip;\n",
+ " button.appendChild(icon_img);\n",
+ "\n",
+ " buttonGroup.appendChild(button);\n",
+ " }\n",
+ "\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ "\n",
+ " var fmt_picker = document.createElement('select');\n",
+ " fmt_picker.classList = 'mpl-widget';\n",
+ " toolbar.appendChild(fmt_picker);\n",
+ " this.format_dropdown = fmt_picker;\n",
+ "\n",
+ " for (var ind in mpl.extensions) {\n",
+ " var fmt = mpl.extensions[ind];\n",
+ " var option = document.createElement('option');\n",
+ " option.selected = fmt === mpl.default_extension;\n",
+ " option.innerHTML = fmt;\n",
+ " fmt_picker.appendChild(option);\n",
+ " }\n",
+ "\n",
+ " var status_bar = document.createElement('span');\n",
+ " status_bar.classList = 'mpl-message';\n",
+ " toolbar.appendChild(status_bar);\n",
+ " this.message = status_bar;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n",
+ " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
+ " // which will in turn request a refresh of the image.\n",
+ " this.send_message('resize', { width: x_pixels, height: y_pixels });\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.send_message = function (type, properties) {\n",
+ " properties['type'] = type;\n",
+ " properties['figure_id'] = this.id;\n",
+ " this.ws.send(JSON.stringify(properties));\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.send_draw_message = function () {\n",
+ " if (!this.waiting) {\n",
+ " this.waiting = true;\n",
+ " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+ " var format_dropdown = fig.format_dropdown;\n",
+ " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
+ " fig.ondownload(fig, format);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_resize = function (fig, msg) {\n",
+ " var size = msg['size'];\n",
+ " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n",
+ " fig._resize_canvas(size[0], size[1], msg['forward']);\n",
+ " fig.send_message('refresh', {});\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n",
+ " var x0 = msg['x0'] / fig.ratio;\n",
+ " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n",
+ " var x1 = msg['x1'] / fig.ratio;\n",
+ " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n",
+ " x0 = Math.floor(x0) + 0.5;\n",
+ " y0 = Math.floor(y0) + 0.5;\n",
+ " x1 = Math.floor(x1) + 0.5;\n",
+ " y1 = Math.floor(y1) + 0.5;\n",
+ " var min_x = Math.min(x0, x1);\n",
+ " var min_y = Math.min(y0, y1);\n",
+ " var width = Math.abs(x1 - x0);\n",
+ " var height = Math.abs(y1 - y0);\n",
+ "\n",
+ " fig.rubberband_context.clearRect(\n",
+ " 0,\n",
+ " 0,\n",
+ " fig.canvas.width / fig.ratio,\n",
+ " fig.canvas.height / fig.ratio\n",
+ " );\n",
+ "\n",
+ " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n",
+ " // Updates the figure title.\n",
+ " fig.header.textContent = msg['label'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n",
+ " fig.rubberband_canvas.style.cursor = msg['cursor'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_message = function (fig, msg) {\n",
+ " fig.message.textContent = msg['message'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n",
+ " // Request the server to send over a new figure.\n",
+ " fig.send_draw_message();\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n",
+ " fig.image_mode = msg['mode'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n",
+ " for (var key in msg) {\n",
+ " if (!(key in fig.buttons)) {\n",
+ " continue;\n",
+ " }\n",
+ " fig.buttons[key].disabled = !msg[key];\n",
+ " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n",
+ " if (msg['mode'] === 'PAN') {\n",
+ " fig.buttons['Pan'].classList.add('active');\n",
+ " fig.buttons['Zoom'].classList.remove('active');\n",
+ " } else if (msg['mode'] === 'ZOOM') {\n",
+ " fig.buttons['Pan'].classList.remove('active');\n",
+ " fig.buttons['Zoom'].classList.add('active');\n",
+ " } else {\n",
+ " fig.buttons['Pan'].classList.remove('active');\n",
+ " fig.buttons['Zoom'].classList.remove('active');\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.updated_canvas_event = function () {\n",
+ " // Called whenever the canvas gets updated.\n",
+ " this.send_message('ack', {});\n",
+ "};\n",
+ "\n",
+ "// A function to construct a web socket function for onmessage handling.\n",
+ "// Called in the figure constructor.\n",
+ "mpl.figure.prototype._make_on_message_function = function (fig) {\n",
+ " return function socket_on_message(evt) {\n",
+ " if (evt.data instanceof Blob) {\n",
+ " var img = evt.data;\n",
+ " if (img.type !== 'image/png') {\n",
+ " /* FIXME: We get \"Resource interpreted as Image but\n",
+ " * transferred with MIME type text/plain:\" errors on\n",
+ " * Chrome. But how to set the MIME type? It doesn't seem\n",
+ " * to be part of the websocket stream */\n",
+ " img.type = 'image/png';\n",
+ " }\n",
+ "\n",
+ " /* Free the memory for the previous frames */\n",
+ " if (fig.imageObj.src) {\n",
+ " (window.URL || window.webkitURL).revokeObjectURL(\n",
+ " fig.imageObj.src\n",
+ " );\n",
+ " }\n",
+ "\n",
+ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
+ " img\n",
+ " );\n",
+ " fig.updated_canvas_event();\n",
+ " fig.waiting = false;\n",
+ " return;\n",
+ " } else if (\n",
+ " typeof evt.data === 'string' &&\n",
+ " evt.data.slice(0, 21) === 'data:image/png;base64'\n",
+ " ) {\n",
+ " fig.imageObj.src = evt.data;\n",
+ " fig.updated_canvas_event();\n",
+ " fig.waiting = false;\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " var msg = JSON.parse(evt.data);\n",
+ " var msg_type = msg['type'];\n",
+ "\n",
+ " // Call the \"handle_{type}\" callback, which takes\n",
+ " // the figure and JSON message as its only arguments.\n",
+ " try {\n",
+ " var callback = fig['handle_' + msg_type];\n",
+ " } catch (e) {\n",
+ " console.log(\n",
+ " \"No handler for the '\" + msg_type + \"' message type: \",\n",
+ " msg\n",
+ " );\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " if (callback) {\n",
+ " try {\n",
+ " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
+ " callback(fig, msg);\n",
+ " } catch (e) {\n",
+ " console.log(\n",
+ " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n",
+ " e,\n",
+ " e.stack,\n",
+ " msg\n",
+ " );\n",
+ " }\n",
+ " }\n",
+ " };\n",
+ "};\n",
+ "\n",
+ "// from https://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
+ "mpl.findpos = function (e) {\n",
+ " //this section is from http://www.quirksmode.org/js/events_properties.html\n",
+ " var targ;\n",
+ " if (!e) {\n",
+ " e = window.event;\n",
+ " }\n",
+ " if (e.target) {\n",
+ " targ = e.target;\n",
+ " } else if (e.srcElement) {\n",
+ " targ = e.srcElement;\n",
+ " }\n",
+ " if (targ.nodeType === 3) {\n",
+ " // defeat Safari bug\n",
+ " targ = targ.parentNode;\n",
+ " }\n",
+ "\n",
+ " // pageX,Y are the mouse positions relative to the document\n",
+ " var boundingRect = targ.getBoundingClientRect();\n",
+ " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n",
+ " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n",
+ "\n",
+ " return { x: x, y: y };\n",
+ "};\n",
+ "\n",
+ "/*\n",
+ " * return a copy of an object with only non-object keys\n",
+ " * we need this to avoid circular references\n",
+ " * https://stackoverflow.com/a/24161582/3208463\n",
+ " */\n",
+ "function simpleKeys(original) {\n",
+ " return Object.keys(original).reduce(function (obj, key) {\n",
+ " if (typeof original[key] !== 'object') {\n",
+ " obj[key] = original[key];\n",
+ " }\n",
+ " return obj;\n",
+ " }, {});\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.mouse_event = function (event, name) {\n",
+ " var canvas_pos = mpl.findpos(event);\n",
+ "\n",
+ " if (name === 'button_press') {\n",
+ " this.canvas.focus();\n",
+ " this.canvas_div.focus();\n",
+ " }\n",
+ "\n",
+ " var x = canvas_pos.x * this.ratio;\n",
+ " var y = canvas_pos.y * this.ratio;\n",
+ "\n",
+ " this.send_message(name, {\n",
+ " x: x,\n",
+ " y: y,\n",
+ " button: event.button,\n",
+ " step: event.step,\n",
+ " guiEvent: simpleKeys(event),\n",
+ " });\n",
+ "\n",
+ " /* This prevents the web browser from automatically changing to\n",
+ " * the text insertion cursor when the button is pressed. We want\n",
+ " * to control all of the cursor setting manually through the\n",
+ " * 'cursor' event from matplotlib */\n",
+ " event.preventDefault();\n",
+ " return false;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n",
+ " // Handle any extra behaviour associated with a key event\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.key_event = function (event, name) {\n",
+ " // Prevent repeat events\n",
+ " if (name === 'key_press') {\n",
+ " if (event.key === this._key) {\n",
+ " return;\n",
+ " } else {\n",
+ " this._key = event.key;\n",
+ " }\n",
+ " }\n",
+ " if (name === 'key_release') {\n",
+ " this._key = null;\n",
+ " }\n",
+ "\n",
+ " var value = '';\n",
+ " if (event.ctrlKey && event.key !== 'Control') {\n",
+ " value += 'ctrl+';\n",
+ " }\n",
+ " else if (event.altKey && event.key !== 'Alt') {\n",
+ " value += 'alt+';\n",
+ " }\n",
+ " else if (event.shiftKey && event.key !== 'Shift') {\n",
+ " value += 'shift+';\n",
+ " }\n",
+ "\n",
+ " value += 'k' + event.key;\n",
+ "\n",
+ " this._key_event_extra(event, name);\n",
+ "\n",
+ " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n",
+ " return false;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n",
+ " if (name === 'download') {\n",
+ " this.handle_save(this, null);\n",
+ " } else {\n",
+ " this.send_message('toolbar_button', { name: name });\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n",
+ " this.message.textContent = tooltip;\n",
+ "};\n",
+ "\n",
+ "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n",
+ "// prettier-ignore\n",
+ "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n",
+ "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
+ "\n",
+ "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
+ "\n",
+ "mpl.default_extension = \"png\";/* global mpl */\n",
+ "\n",
+ "var comm_websocket_adapter = function (comm) {\n",
+ " // Create a \"websocket\"-like object which calls the given IPython comm\n",
+ " // object with the appropriate methods. Currently this is a non binary\n",
+ " // socket, so there is still some room for performance tuning.\n",
+ " var ws = {};\n",
+ "\n",
+ " ws.binaryType = comm.kernel.ws.binaryType;\n",
+ " ws.readyState = comm.kernel.ws.readyState;\n",
+ " function updateReadyState(_event) {\n",
+ " if (comm.kernel.ws) {\n",
+ " ws.readyState = comm.kernel.ws.readyState;\n",
+ " } else {\n",
+ " ws.readyState = 3; // Closed state.\n",
+ " }\n",
+ " }\n",
+ " comm.kernel.ws.addEventListener('open', updateReadyState);\n",
+ " comm.kernel.ws.addEventListener('close', updateReadyState);\n",
+ " comm.kernel.ws.addEventListener('error', updateReadyState);\n",
+ "\n",
+ " ws.close = function () {\n",
+ " comm.close();\n",
+ " };\n",
+ " ws.send = function (m) {\n",
+ " //console.log('sending', m);\n",
+ " comm.send(m);\n",
+ " };\n",
+ " // Register the callback with on_msg.\n",
+ " comm.on_msg(function (msg) {\n",
+ " //console.log('receiving', msg['content']['data'], msg);\n",
+ " var data = msg['content']['data'];\n",
+ " if (data['blob'] !== undefined) {\n",
+ " data = {\n",
+ " data: new Blob(msg['buffers'], { type: data['blob'] }),\n",
+ " };\n",
+ " }\n",
+ " // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
+ " ws.onmessage(data);\n",
+ " });\n",
+ " return ws;\n",
+ "};\n",
+ "\n",
+ "mpl.mpl_figure_comm = function (comm, msg) {\n",
+ " // This is the function which gets called when the mpl process\n",
+ " // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
+ "\n",
+ " var id = msg.content.data.id;\n",
+ " // Get hold of the div created by the display call when the Comm\n",
+ " // socket was opened in Python.\n",
+ " var element = document.getElementById(id);\n",
+ " var ws_proxy = comm_websocket_adapter(comm);\n",
+ "\n",
+ " function ondownload(figure, _format) {\n",
+ " window.open(figure.canvas.toDataURL());\n",
+ " }\n",
+ "\n",
+ " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n",
+ "\n",
+ " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
+ " // web socket which is closed, not our websocket->open comm proxy.\n",
+ " ws_proxy.onopen();\n",
+ "\n",
+ " fig.parent_element = element;\n",
+ " fig.cell_info = mpl.find_output_cell(\"\");\n",
+ " if (!fig.cell_info) {\n",
+ " console.error('Failed to find cell for figure', id, fig);\n",
+ " return;\n",
+ " }\n",
+ " fig.cell_info[0].output_area.element.on(\n",
+ " 'cleared',\n",
+ " { fig: fig },\n",
+ " fig._remove_fig_handler\n",
+ " );\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_close = function (fig, msg) {\n",
+ " var width = fig.canvas.width / fig.ratio;\n",
+ " fig.cell_info[0].output_area.element.off(\n",
+ " 'cleared',\n",
+ " fig._remove_fig_handler\n",
+ " );\n",
+ " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n",
+ "\n",
+ " // Update the output cell to use the data from the current canvas.\n",
+ " fig.push_to_output();\n",
+ " var dataURL = fig.canvas.toDataURL();\n",
+ " // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
+ " // the notebook keyboard shortcuts fail.\n",
+ " IPython.keyboard_manager.enable();\n",
+ " fig.parent_element.innerHTML =\n",
+ " '
';\n",
+ " fig.close_ws(fig, msg);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.close_ws = function (fig, msg) {\n",
+ " fig.send_message('closing', msg);\n",
+ " // fig.ws.close()\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n",
+ " // Turn the data on the canvas into data in the output cell.\n",
+ " var width = this.canvas.width / this.ratio;\n",
+ " var dataURL = this.canvas.toDataURL();\n",
+ " this.cell_info[1]['text/html'] =\n",
+ " '
';\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.updated_canvas_event = function () {\n",
+ " // Tell IPython that the notebook contents must change.\n",
+ " IPython.notebook.set_dirty(true);\n",
+ " this.send_message('ack', {});\n",
+ " var fig = this;\n",
+ " // Wait a second, then push the new image to the DOM so\n",
+ " // that it is saved nicely (might be nice to debounce this).\n",
+ " setTimeout(function () {\n",
+ " fig.push_to_output();\n",
+ " }, 1000);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_toolbar = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var toolbar = document.createElement('div');\n",
+ " toolbar.classList = 'btn-toolbar';\n",
+ " this.root.appendChild(toolbar);\n",
+ "\n",
+ " function on_click_closure(name) {\n",
+ " return function (_event) {\n",
+ " return fig.toolbar_button_onclick(name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " function on_mouseover_closure(tooltip) {\n",
+ " return function (event) {\n",
+ " if (!event.currentTarget.disabled) {\n",
+ " return fig.toolbar_button_onmouseover(tooltip);\n",
+ " }\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " fig.buttons = {};\n",
+ " var buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'btn-group';\n",
+ " var button;\n",
+ " for (var toolbar_ind in mpl.toolbar_items) {\n",
+ " var name = mpl.toolbar_items[toolbar_ind][0];\n",
+ " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+ " var image = mpl.toolbar_items[toolbar_ind][2];\n",
+ " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+ "\n",
+ " if (!name) {\n",
+ " /* Instead of a spacer, we start a new button group. */\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ " buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'btn-group';\n",
+ " continue;\n",
+ " }\n",
+ "\n",
+ " button = fig.buttons[name] = document.createElement('button');\n",
+ " button.classList = 'btn btn-default';\n",
+ " button.href = '#';\n",
+ " button.title = name;\n",
+ " button.innerHTML = '';\n",
+ " button.addEventListener('click', on_click_closure(method_name));\n",
+ " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+ " buttonGroup.appendChild(button);\n",
+ " }\n",
+ "\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ "\n",
+ " // Add the status bar.\n",
+ " var status_bar = document.createElement('span');\n",
+ " status_bar.classList = 'mpl-message pull-right';\n",
+ " toolbar.appendChild(status_bar);\n",
+ " this.message = status_bar;\n",
+ "\n",
+ " // Add the close button to the window.\n",
+ " var buttongrp = document.createElement('div');\n",
+ " buttongrp.classList = 'btn-group inline pull-right';\n",
+ " button = document.createElement('button');\n",
+ " button.classList = 'btn btn-mini btn-primary';\n",
+ " button.href = '#';\n",
+ " button.title = 'Stop Interaction';\n",
+ " button.innerHTML = '';\n",
+ " button.addEventListener('click', function (_evt) {\n",
+ " fig.handle_close(fig, {});\n",
+ " });\n",
+ " button.addEventListener(\n",
+ " 'mouseover',\n",
+ " on_mouseover_closure('Stop Interaction')\n",
+ " );\n",
+ " buttongrp.appendChild(button);\n",
+ " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n",
+ " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._remove_fig_handler = function (event) {\n",
+ " var fig = event.data.fig;\n",
+ " if (event.target !== this) {\n",
+ " // Ignore bubbled events from children.\n",
+ " return;\n",
+ " }\n",
+ " fig.close_ws(fig, {});\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._root_extra_style = function (el) {\n",
+ " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._canvas_extra_style = function (el) {\n",
+ " // this is important to make the div 'focusable\n",
+ " el.setAttribute('tabindex', 0);\n",
+ " // reach out to IPython and tell the keyboard manager to turn it's self\n",
+ " // off when our div gets focus\n",
+ "\n",
+ " // location in version 3\n",
+ " if (IPython.notebook.keyboard_manager) {\n",
+ " IPython.notebook.keyboard_manager.register_events(el);\n",
+ " } else {\n",
+ " // location in version 2\n",
+ " IPython.keyboard_manager.register_events(el);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._key_event_extra = function (event, _name) {\n",
+ " // Check for shift+enter\n",
+ " if (event.shiftKey && event.which === 13) {\n",
+ " this.canvas_div.blur();\n",
+ " // select the cell after this one\n",
+ " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n",
+ " IPython.notebook.select(index + 1);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+ " fig.ondownload(fig, null);\n",
+ "};\n",
+ "\n",
+ "mpl.find_output_cell = function (html_output) {\n",
+ " // Return the cell and output element which can be found *uniquely* in the notebook.\n",
+ " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
+ " // IPython event is triggered only after the cells have been serialised, which for\n",
+ " // our purposes (turning an active figure into a static one), is too late.\n",
+ " var cells = IPython.notebook.get_cells();\n",
+ " var ncells = cells.length;\n",
+ " for (var i = 0; i < ncells; i++) {\n",
+ " var cell = cells[i];\n",
+ " if (cell.cell_type === 'code') {\n",
+ " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n",
+ " var data = cell.output_area.outputs[j];\n",
+ " if (data.data) {\n",
+ " // IPython >= 3 moved mimebundle to data attribute of output\n",
+ " data = data.data;\n",
+ " }\n",
+ " if (data['text/html'] === html_output) {\n",
+ " return [cell, data, j];\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "// Register the function which deals with the matplotlib target/channel.\n",
+ "// The kernel may be null if the page has been refreshed.\n",
+ "if (IPython.notebook.kernel !== null) {\n",
+ " IPython.notebook.kernel.comm_manager.register_target(\n",
+ " 'matplotlib',\n",
+ " mpl.mpl_figure_comm\n",
+ " );\n",
+ "}\n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "
"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " mean std num\n",
+ "1x0 isilon 38.570352 4.155044 20\n",
+ " netapp 38.019479 4.514666 20\n",
+ " s3 30.605186 13.899926 18\n",
+ "1x1K isilon 61.779090 5.862412 19\n",
+ " netapp 61.750140 5.591803 19\n",
+ " s3 53.955329 13.170368 17\n",
+ "1x1M isilon 61.689178 5.719017 19\n",
+ " netapp 61.496473 5.694692 19\n",
+ " s3 41.793109 1.196732 17\n",
+ "1x1G isilon 65.067275 8.125209 18\n",
+ " netapp 63.104740 5.135069 18\n",
+ " s3 0.000000 0.000000 1\n",
+ "10x1G isilon 259.802910 5.809441 9\n",
+ " netapp 183.547638 5.057323 9\n",
+ " s3 0.000000 0.000000 1\n"
+ ]
+ },
+ {
+ "data": {
+ "application/javascript": [
+ "/* Put everything inside the global mpl namespace */\n",
+ "/* global mpl */\n",
+ "window.mpl = {};\n",
+ "\n",
+ "mpl.get_websocket_type = function () {\n",
+ " if (typeof WebSocket !== 'undefined') {\n",
+ " return WebSocket;\n",
+ " } else if (typeof MozWebSocket !== 'undefined') {\n",
+ " return MozWebSocket;\n",
+ " } else {\n",
+ " alert(\n",
+ " 'Your browser does not have WebSocket support. ' +\n",
+ " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
+ " 'Firefox 4 and 5 are also supported but you ' +\n",
+ " 'have to enable WebSockets in about:config.'\n",
+ " );\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n",
+ " this.id = figure_id;\n",
+ "\n",
+ " this.ws = websocket;\n",
+ "\n",
+ " this.supports_binary = this.ws.binaryType !== undefined;\n",
+ "\n",
+ " if (!this.supports_binary) {\n",
+ " var warnings = document.getElementById('mpl-warnings');\n",
+ " if (warnings) {\n",
+ " warnings.style.display = 'block';\n",
+ " warnings.textContent =\n",
+ " 'This browser does not support binary websocket messages. ' +\n",
+ " 'Performance may be slow.';\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " this.imageObj = new Image();\n",
+ "\n",
+ " this.context = undefined;\n",
+ " this.message = undefined;\n",
+ " this.canvas = undefined;\n",
+ " this.rubberband_canvas = undefined;\n",
+ " this.rubberband_context = undefined;\n",
+ " this.format_dropdown = undefined;\n",
+ "\n",
+ " this.image_mode = 'full';\n",
+ "\n",
+ " this.root = document.createElement('div');\n",
+ " this.root.setAttribute('style', 'display: inline-block');\n",
+ " this._root_extra_style(this.root);\n",
+ "\n",
+ " parent_element.appendChild(this.root);\n",
+ "\n",
+ " this._init_header(this);\n",
+ " this._init_canvas(this);\n",
+ " this._init_toolbar(this);\n",
+ "\n",
+ " var fig = this;\n",
+ "\n",
+ " this.waiting = false;\n",
+ "\n",
+ " this.ws.onopen = function () {\n",
+ " fig.send_message('supports_binary', { value: fig.supports_binary });\n",
+ " fig.send_message('send_image_mode', {});\n",
+ " if (fig.ratio !== 1) {\n",
+ " fig.send_message('set_device_pixel_ratio', {\n",
+ " device_pixel_ratio: fig.ratio,\n",
+ " });\n",
+ " }\n",
+ " fig.send_message('refresh', {});\n",
+ " };\n",
+ "\n",
+ " this.imageObj.onload = function () {\n",
+ " if (fig.image_mode === 'full') {\n",
+ " // Full images could contain transparency (where diff images\n",
+ " // almost always do), so we need to clear the canvas so that\n",
+ " // there is no ghosting.\n",
+ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
+ " }\n",
+ " fig.context.drawImage(fig.imageObj, 0, 0);\n",
+ " };\n",
+ "\n",
+ " this.imageObj.onunload = function () {\n",
+ " fig.ws.close();\n",
+ " };\n",
+ "\n",
+ " this.ws.onmessage = this._make_on_message_function(this);\n",
+ "\n",
+ " this.ondownload = ondownload;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_header = function () {\n",
+ " var titlebar = document.createElement('div');\n",
+ " titlebar.classList =\n",
+ " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n",
+ " var titletext = document.createElement('div');\n",
+ " titletext.classList = 'ui-dialog-title';\n",
+ " titletext.setAttribute(\n",
+ " 'style',\n",
+ " 'width: 100%; text-align: center; padding: 3px;'\n",
+ " );\n",
+ " titlebar.appendChild(titletext);\n",
+ " this.root.appendChild(titlebar);\n",
+ " this.header = titletext;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n",
+ "\n",
+ "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n",
+ "\n",
+ "mpl.figure.prototype._init_canvas = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var canvas_div = (this.canvas_div = document.createElement('div'));\n",
+ " canvas_div.setAttribute(\n",
+ " 'style',\n",
+ " 'border: 1px solid #ddd;' +\n",
+ " 'box-sizing: content-box;' +\n",
+ " 'clear: both;' +\n",
+ " 'min-height: 1px;' +\n",
+ " 'min-width: 1px;' +\n",
+ " 'outline: 0;' +\n",
+ " 'overflow: hidden;' +\n",
+ " 'position: relative;' +\n",
+ " 'resize: both;'\n",
+ " );\n",
+ "\n",
+ " function on_keyboard_event_closure(name) {\n",
+ " return function (event) {\n",
+ " return fig.key_event(event, name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " canvas_div.addEventListener(\n",
+ " 'keydown',\n",
+ " on_keyboard_event_closure('key_press')\n",
+ " );\n",
+ " canvas_div.addEventListener(\n",
+ " 'keyup',\n",
+ " on_keyboard_event_closure('key_release')\n",
+ " );\n",
+ "\n",
+ " this._canvas_extra_style(canvas_div);\n",
+ " this.root.appendChild(canvas_div);\n",
+ "\n",
+ " var canvas = (this.canvas = document.createElement('canvas'));\n",
+ " canvas.classList.add('mpl-canvas');\n",
+ " canvas.setAttribute('style', 'box-sizing: content-box;');\n",
+ "\n",
+ " this.context = canvas.getContext('2d');\n",
+ "\n",
+ " var backingStore =\n",
+ " this.context.backingStorePixelRatio ||\n",
+ " this.context.webkitBackingStorePixelRatio ||\n",
+ " this.context.mozBackingStorePixelRatio ||\n",
+ " this.context.msBackingStorePixelRatio ||\n",
+ " this.context.oBackingStorePixelRatio ||\n",
+ " this.context.backingStorePixelRatio ||\n",
+ " 1;\n",
+ "\n",
+ " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
+ "\n",
+ " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n",
+ " 'canvas'\n",
+ " ));\n",
+ " rubberband_canvas.setAttribute(\n",
+ " 'style',\n",
+ " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n",
+ " );\n",
+ "\n",
+ " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n",
+ " if (this.ResizeObserver === undefined) {\n",
+ " if (window.ResizeObserver !== undefined) {\n",
+ " this.ResizeObserver = window.ResizeObserver;\n",
+ " } else {\n",
+ " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n",
+ " this.ResizeObserver = obs.ResizeObserver;\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n",
+ " var nentries = entries.length;\n",
+ " for (var i = 0; i < nentries; i++) {\n",
+ " var entry = entries[i];\n",
+ " var width, height;\n",
+ " if (entry.contentBoxSize) {\n",
+ " if (entry.contentBoxSize instanceof Array) {\n",
+ " // Chrome 84 implements new version of spec.\n",
+ " width = entry.contentBoxSize[0].inlineSize;\n",
+ " height = entry.contentBoxSize[0].blockSize;\n",
+ " } else {\n",
+ " // Firefox implements old version of spec.\n",
+ " width = entry.contentBoxSize.inlineSize;\n",
+ " height = entry.contentBoxSize.blockSize;\n",
+ " }\n",
+ " } else {\n",
+ " // Chrome <84 implements even older version of spec.\n",
+ " width = entry.contentRect.width;\n",
+ " height = entry.contentRect.height;\n",
+ " }\n",
+ "\n",
+ " // Keep the size of the canvas and rubber band canvas in sync with\n",
+ " // the canvas container.\n",
+ " if (entry.devicePixelContentBoxSize) {\n",
+ " // Chrome 84 implements new version of spec.\n",
+ " canvas.setAttribute(\n",
+ " 'width',\n",
+ " entry.devicePixelContentBoxSize[0].inlineSize\n",
+ " );\n",
+ " canvas.setAttribute(\n",
+ " 'height',\n",
+ " entry.devicePixelContentBoxSize[0].blockSize\n",
+ " );\n",
+ " } else {\n",
+ " canvas.setAttribute('width', width * fig.ratio);\n",
+ " canvas.setAttribute('height', height * fig.ratio);\n",
+ " }\n",
+ " canvas.setAttribute(\n",
+ " 'style',\n",
+ " 'width: ' + width + 'px; height: ' + height + 'px;'\n",
+ " );\n",
+ "\n",
+ " rubberband_canvas.setAttribute('width', width);\n",
+ " rubberband_canvas.setAttribute('height', height);\n",
+ "\n",
+ " // And update the size in Python. We ignore the initial 0/0 size\n",
+ " // that occurs as the element is placed into the DOM, which should\n",
+ " // otherwise not happen due to the minimum size styling.\n",
+ " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n",
+ " fig.request_resize(width, height);\n",
+ " }\n",
+ " }\n",
+ " });\n",
+ " this.resizeObserverInstance.observe(canvas_div);\n",
+ "\n",
+ " function on_mouse_event_closure(name) {\n",
+ " return function (event) {\n",
+ " return fig.mouse_event(event, name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mousedown',\n",
+ " on_mouse_event_closure('button_press')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseup',\n",
+ " on_mouse_event_closure('button_release')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'dblclick',\n",
+ " on_mouse_event_closure('dblclick')\n",
+ " );\n",
+ " // Throttle sequential mouse events to 1 every 20ms.\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mousemove',\n",
+ " on_mouse_event_closure('motion_notify')\n",
+ " );\n",
+ "\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseenter',\n",
+ " on_mouse_event_closure('figure_enter')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseleave',\n",
+ " on_mouse_event_closure('figure_leave')\n",
+ " );\n",
+ "\n",
+ " canvas_div.addEventListener('wheel', function (event) {\n",
+ " if (event.deltaY < 0) {\n",
+ " event.step = 1;\n",
+ " } else {\n",
+ " event.step = -1;\n",
+ " }\n",
+ " on_mouse_event_closure('scroll')(event);\n",
+ " });\n",
+ "\n",
+ " canvas_div.appendChild(canvas);\n",
+ " canvas_div.appendChild(rubberband_canvas);\n",
+ "\n",
+ " this.rubberband_context = rubberband_canvas.getContext('2d');\n",
+ " this.rubberband_context.strokeStyle = '#000000';\n",
+ "\n",
+ " this._resize_canvas = function (width, height, forward) {\n",
+ " if (forward) {\n",
+ " canvas_div.style.width = width + 'px';\n",
+ " canvas_div.style.height = height + 'px';\n",
+ " }\n",
+ " };\n",
+ "\n",
+ " // Disable right mouse context menu.\n",
+ " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n",
+ " event.preventDefault();\n",
+ " return false;\n",
+ " });\n",
+ "\n",
+ " function set_focus() {\n",
+ " canvas.focus();\n",
+ " canvas_div.focus();\n",
+ " }\n",
+ "\n",
+ " window.setTimeout(set_focus, 100);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_toolbar = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var toolbar = document.createElement('div');\n",
+ " toolbar.classList = 'mpl-toolbar';\n",
+ " this.root.appendChild(toolbar);\n",
+ "\n",
+ " function on_click_closure(name) {\n",
+ " return function (_event) {\n",
+ " return fig.toolbar_button_onclick(name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " function on_mouseover_closure(tooltip) {\n",
+ " return function (event) {\n",
+ " if (!event.currentTarget.disabled) {\n",
+ " return fig.toolbar_button_onmouseover(tooltip);\n",
+ " }\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " fig.buttons = {};\n",
+ " var buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'mpl-button-group';\n",
+ " for (var toolbar_ind in mpl.toolbar_items) {\n",
+ " var name = mpl.toolbar_items[toolbar_ind][0];\n",
+ " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+ " var image = mpl.toolbar_items[toolbar_ind][2];\n",
+ " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+ "\n",
+ " if (!name) {\n",
+ " /* Instead of a spacer, we start a new button group. */\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ " buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'mpl-button-group';\n",
+ " continue;\n",
+ " }\n",
+ "\n",
+ " var button = (fig.buttons[name] = document.createElement('button'));\n",
+ " button.classList = 'mpl-widget';\n",
+ " button.setAttribute('role', 'button');\n",
+ " button.setAttribute('aria-disabled', 'false');\n",
+ " button.addEventListener('click', on_click_closure(method_name));\n",
+ " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+ "\n",
+ " var icon_img = document.createElement('img');\n",
+ " icon_img.src = '_images/' + image + '.png';\n",
+ " icon_img.srcset = '_images/' + image + '_large.png 2x';\n",
+ " icon_img.alt = tooltip;\n",
+ " button.appendChild(icon_img);\n",
+ "\n",
+ " buttonGroup.appendChild(button);\n",
+ " }\n",
+ "\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ "\n",
+ " var fmt_picker = document.createElement('select');\n",
+ " fmt_picker.classList = 'mpl-widget';\n",
+ " toolbar.appendChild(fmt_picker);\n",
+ " this.format_dropdown = fmt_picker;\n",
+ "\n",
+ " for (var ind in mpl.extensions) {\n",
+ " var fmt = mpl.extensions[ind];\n",
+ " var option = document.createElement('option');\n",
+ " option.selected = fmt === mpl.default_extension;\n",
+ " option.innerHTML = fmt;\n",
+ " fmt_picker.appendChild(option);\n",
+ " }\n",
+ "\n",
+ " var status_bar = document.createElement('span');\n",
+ " status_bar.classList = 'mpl-message';\n",
+ " toolbar.appendChild(status_bar);\n",
+ " this.message = status_bar;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n",
+ " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
+ " // which will in turn request a refresh of the image.\n",
+ " this.send_message('resize', { width: x_pixels, height: y_pixels });\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.send_message = function (type, properties) {\n",
+ " properties['type'] = type;\n",
+ " properties['figure_id'] = this.id;\n",
+ " this.ws.send(JSON.stringify(properties));\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.send_draw_message = function () {\n",
+ " if (!this.waiting) {\n",
+ " this.waiting = true;\n",
+ " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+ " var format_dropdown = fig.format_dropdown;\n",
+ " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
+ " fig.ondownload(fig, format);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_resize = function (fig, msg) {\n",
+ " var size = msg['size'];\n",
+ " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n",
+ " fig._resize_canvas(size[0], size[1], msg['forward']);\n",
+ " fig.send_message('refresh', {});\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n",
+ " var x0 = msg['x0'] / fig.ratio;\n",
+ " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n",
+ " var x1 = msg['x1'] / fig.ratio;\n",
+ " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n",
+ " x0 = Math.floor(x0) + 0.5;\n",
+ " y0 = Math.floor(y0) + 0.5;\n",
+ " x1 = Math.floor(x1) + 0.5;\n",
+ " y1 = Math.floor(y1) + 0.5;\n",
+ " var min_x = Math.min(x0, x1);\n",
+ " var min_y = Math.min(y0, y1);\n",
+ " var width = Math.abs(x1 - x0);\n",
+ " var height = Math.abs(y1 - y0);\n",
+ "\n",
+ " fig.rubberband_context.clearRect(\n",
+ " 0,\n",
+ " 0,\n",
+ " fig.canvas.width / fig.ratio,\n",
+ " fig.canvas.height / fig.ratio\n",
+ " );\n",
+ "\n",
+ " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n",
+ " // Updates the figure title.\n",
+ " fig.header.textContent = msg['label'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n",
+ " fig.rubberband_canvas.style.cursor = msg['cursor'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_message = function (fig, msg) {\n",
+ " fig.message.textContent = msg['message'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n",
+ " // Request the server to send over a new figure.\n",
+ " fig.send_draw_message();\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n",
+ " fig.image_mode = msg['mode'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n",
+ " for (var key in msg) {\n",
+ " if (!(key in fig.buttons)) {\n",
+ " continue;\n",
+ " }\n",
+ " fig.buttons[key].disabled = !msg[key];\n",
+ " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n",
+ " if (msg['mode'] === 'PAN') {\n",
+ " fig.buttons['Pan'].classList.add('active');\n",
+ " fig.buttons['Zoom'].classList.remove('active');\n",
+ " } else if (msg['mode'] === 'ZOOM') {\n",
+ " fig.buttons['Pan'].classList.remove('active');\n",
+ " fig.buttons['Zoom'].classList.add('active');\n",
+ " } else {\n",
+ " fig.buttons['Pan'].classList.remove('active');\n",
+ " fig.buttons['Zoom'].classList.remove('active');\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.updated_canvas_event = function () {\n",
+ " // Called whenever the canvas gets updated.\n",
+ " this.send_message('ack', {});\n",
+ "};\n",
+ "\n",
+ "// A function to construct a web socket function for onmessage handling.\n",
+ "// Called in the figure constructor.\n",
+ "mpl.figure.prototype._make_on_message_function = function (fig) {\n",
+ " return function socket_on_message(evt) {\n",
+ " if (evt.data instanceof Blob) {\n",
+ " var img = evt.data;\n",
+ " if (img.type !== 'image/png') {\n",
+ " /* FIXME: We get \"Resource interpreted as Image but\n",
+ " * transferred with MIME type text/plain:\" errors on\n",
+ " * Chrome. But how to set the MIME type? It doesn't seem\n",
+ " * to be part of the websocket stream */\n",
+ " img.type = 'image/png';\n",
+ " }\n",
+ "\n",
+ " /* Free the memory for the previous frames */\n",
+ " if (fig.imageObj.src) {\n",
+ " (window.URL || window.webkitURL).revokeObjectURL(\n",
+ " fig.imageObj.src\n",
+ " );\n",
+ " }\n",
+ "\n",
+ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
+ " img\n",
+ " );\n",
+ " fig.updated_canvas_event();\n",
+ " fig.waiting = false;\n",
+ " return;\n",
+ " } else if (\n",
+ " typeof evt.data === 'string' &&\n",
+ " evt.data.slice(0, 21) === 'data:image/png;base64'\n",
+ " ) {\n",
+ " fig.imageObj.src = evt.data;\n",
+ " fig.updated_canvas_event();\n",
+ " fig.waiting = false;\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " var msg = JSON.parse(evt.data);\n",
+ " var msg_type = msg['type'];\n",
+ "\n",
+ " // Call the \"handle_{type}\" callback, which takes\n",
+ " // the figure and JSON message as its only arguments.\n",
+ " try {\n",
+ " var callback = fig['handle_' + msg_type];\n",
+ " } catch (e) {\n",
+ " console.log(\n",
+ " \"No handler for the '\" + msg_type + \"' message type: \",\n",
+ " msg\n",
+ " );\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " if (callback) {\n",
+ " try {\n",
+ " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
+ " callback(fig, msg);\n",
+ " } catch (e) {\n",
+ " console.log(\n",
+ " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n",
+ " e,\n",
+ " e.stack,\n",
+ " msg\n",
+ " );\n",
+ " }\n",
+ " }\n",
+ " };\n",
+ "};\n",
+ "\n",
+ "// from https://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
+ "mpl.findpos = function (e) {\n",
+ " //this section is from http://www.quirksmode.org/js/events_properties.html\n",
+ " var targ;\n",
+ " if (!e) {\n",
+ " e = window.event;\n",
+ " }\n",
+ " if (e.target) {\n",
+ " targ = e.target;\n",
+ " } else if (e.srcElement) {\n",
+ " targ = e.srcElement;\n",
+ " }\n",
+ " if (targ.nodeType === 3) {\n",
+ " // defeat Safari bug\n",
+ " targ = targ.parentNode;\n",
+ " }\n",
+ "\n",
+ " // pageX,Y are the mouse positions relative to the document\n",
+ " var boundingRect = targ.getBoundingClientRect();\n",
+ " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n",
+ " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n",
+ "\n",
+ " return { x: x, y: y };\n",
+ "};\n",
+ "\n",
+ "/*\n",
+ " * return a copy of an object with only non-object keys\n",
+ " * we need this to avoid circular references\n",
+ " * https://stackoverflow.com/a/24161582/3208463\n",
+ " */\n",
+ "function simpleKeys(original) {\n",
+ " return Object.keys(original).reduce(function (obj, key) {\n",
+ " if (typeof original[key] !== 'object') {\n",
+ " obj[key] = original[key];\n",
+ " }\n",
+ " return obj;\n",
+ " }, {});\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.mouse_event = function (event, name) {\n",
+ " var canvas_pos = mpl.findpos(event);\n",
+ "\n",
+ " if (name === 'button_press') {\n",
+ " this.canvas.focus();\n",
+ " this.canvas_div.focus();\n",
+ " }\n",
+ "\n",
+ " var x = canvas_pos.x * this.ratio;\n",
+ " var y = canvas_pos.y * this.ratio;\n",
+ "\n",
+ " this.send_message(name, {\n",
+ " x: x,\n",
+ " y: y,\n",
+ " button: event.button,\n",
+ " step: event.step,\n",
+ " guiEvent: simpleKeys(event),\n",
+ " });\n",
+ "\n",
+ " /* This prevents the web browser from automatically changing to\n",
+ " * the text insertion cursor when the button is pressed. We want\n",
+ " * to control all of the cursor setting manually through the\n",
+ " * 'cursor' event from matplotlib */\n",
+ " event.preventDefault();\n",
+ " return false;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n",
+ " // Handle any extra behaviour associated with a key event\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.key_event = function (event, name) {\n",
+ " // Prevent repeat events\n",
+ " if (name === 'key_press') {\n",
+ " if (event.key === this._key) {\n",
+ " return;\n",
+ " } else {\n",
+ " this._key = event.key;\n",
+ " }\n",
+ " }\n",
+ " if (name === 'key_release') {\n",
+ " this._key = null;\n",
+ " }\n",
+ "\n",
+ " var value = '';\n",
+ " if (event.ctrlKey && event.key !== 'Control') {\n",
+ " value += 'ctrl+';\n",
+ " }\n",
+ " else if (event.altKey && event.key !== 'Alt') {\n",
+ " value += 'alt+';\n",
+ " }\n",
+ " else if (event.shiftKey && event.key !== 'Shift') {\n",
+ " value += 'shift+';\n",
+ " }\n",
+ "\n",
+ " value += 'k' + event.key;\n",
+ "\n",
+ " this._key_event_extra(event, name);\n",
+ "\n",
+ " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n",
+ " return false;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n",
+ " if (name === 'download') {\n",
+ " this.handle_save(this, null);\n",
+ " } else {\n",
+ " this.send_message('toolbar_button', { name: name });\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n",
+ " this.message.textContent = tooltip;\n",
+ "};\n",
+ "\n",
+ "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n",
+ "// prettier-ignore\n",
+ "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n",
+ "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
+ "\n",
+ "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
+ "\n",
+ "mpl.default_extension = \"png\";/* global mpl */\n",
+ "\n",
+ "var comm_websocket_adapter = function (comm) {\n",
+ " // Create a \"websocket\"-like object which calls the given IPython comm\n",
+ " // object with the appropriate methods. Currently this is a non binary\n",
+ " // socket, so there is still some room for performance tuning.\n",
+ " var ws = {};\n",
+ "\n",
+ " ws.binaryType = comm.kernel.ws.binaryType;\n",
+ " ws.readyState = comm.kernel.ws.readyState;\n",
+ " function updateReadyState(_event) {\n",
+ " if (comm.kernel.ws) {\n",
+ " ws.readyState = comm.kernel.ws.readyState;\n",
+ " } else {\n",
+ " ws.readyState = 3; // Closed state.\n",
+ " }\n",
+ " }\n",
+ " comm.kernel.ws.addEventListener('open', updateReadyState);\n",
+ " comm.kernel.ws.addEventListener('close', updateReadyState);\n",
+ " comm.kernel.ws.addEventListener('error', updateReadyState);\n",
+ "\n",
+ " ws.close = function () {\n",
+ " comm.close();\n",
+ " };\n",
+ " ws.send = function (m) {\n",
+ " //console.log('sending', m);\n",
+ " comm.send(m);\n",
+ " };\n",
+ " // Register the callback with on_msg.\n",
+ " comm.on_msg(function (msg) {\n",
+ " //console.log('receiving', msg['content']['data'], msg);\n",
+ " var data = msg['content']['data'];\n",
+ " if (data['blob'] !== undefined) {\n",
+ " data = {\n",
+ " data: new Blob(msg['buffers'], { type: data['blob'] }),\n",
+ " };\n",
+ " }\n",
+ " // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
+ " ws.onmessage(data);\n",
+ " });\n",
+ " return ws;\n",
+ "};\n",
+ "\n",
+ "mpl.mpl_figure_comm = function (comm, msg) {\n",
+ " // This is the function which gets called when the mpl process\n",
+ " // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
+ "\n",
+ " var id = msg.content.data.id;\n",
+ " // Get hold of the div created by the display call when the Comm\n",
+ " // socket was opened in Python.\n",
+ " var element = document.getElementById(id);\n",
+ " var ws_proxy = comm_websocket_adapter(comm);\n",
+ "\n",
+ " function ondownload(figure, _format) {\n",
+ " window.open(figure.canvas.toDataURL());\n",
+ " }\n",
+ "\n",
+ " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n",
+ "\n",
+ " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
+ " // web socket which is closed, not our websocket->open comm proxy.\n",
+ " ws_proxy.onopen();\n",
+ "\n",
+ " fig.parent_element = element;\n",
+ " fig.cell_info = mpl.find_output_cell(\"\");\n",
+ " if (!fig.cell_info) {\n",
+ " console.error('Failed to find cell for figure', id, fig);\n",
+ " return;\n",
+ " }\n",
+ " fig.cell_info[0].output_area.element.on(\n",
+ " 'cleared',\n",
+ " { fig: fig },\n",
+ " fig._remove_fig_handler\n",
+ " );\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_close = function (fig, msg) {\n",
+ " var width = fig.canvas.width / fig.ratio;\n",
+ " fig.cell_info[0].output_area.element.off(\n",
+ " 'cleared',\n",
+ " fig._remove_fig_handler\n",
+ " );\n",
+ " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n",
+ "\n",
+ " // Update the output cell to use the data from the current canvas.\n",
+ " fig.push_to_output();\n",
+ " var dataURL = fig.canvas.toDataURL();\n",
+ " // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
+ " // the notebook keyboard shortcuts fail.\n",
+ " IPython.keyboard_manager.enable();\n",
+ " fig.parent_element.innerHTML =\n",
+ " '
';\n",
+ " fig.close_ws(fig, msg);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.close_ws = function (fig, msg) {\n",
+ " fig.send_message('closing', msg);\n",
+ " // fig.ws.close()\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n",
+ " // Turn the data on the canvas into data in the output cell.\n",
+ " var width = this.canvas.width / this.ratio;\n",
+ " var dataURL = this.canvas.toDataURL();\n",
+ " this.cell_info[1]['text/html'] =\n",
+ " '
';\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.updated_canvas_event = function () {\n",
+ " // Tell IPython that the notebook contents must change.\n",
+ " IPython.notebook.set_dirty(true);\n",
+ " this.send_message('ack', {});\n",
+ " var fig = this;\n",
+ " // Wait a second, then push the new image to the DOM so\n",
+ " // that it is saved nicely (might be nice to debounce this).\n",
+ " setTimeout(function () {\n",
+ " fig.push_to_output();\n",
+ " }, 1000);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_toolbar = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var toolbar = document.createElement('div');\n",
+ " toolbar.classList = 'btn-toolbar';\n",
+ " this.root.appendChild(toolbar);\n",
+ "\n",
+ " function on_click_closure(name) {\n",
+ " return function (_event) {\n",
+ " return fig.toolbar_button_onclick(name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " function on_mouseover_closure(tooltip) {\n",
+ " return function (event) {\n",
+ " if (!event.currentTarget.disabled) {\n",
+ " return fig.toolbar_button_onmouseover(tooltip);\n",
+ " }\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " fig.buttons = {};\n",
+ " var buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'btn-group';\n",
+ " var button;\n",
+ " for (var toolbar_ind in mpl.toolbar_items) {\n",
+ " var name = mpl.toolbar_items[toolbar_ind][0];\n",
+ " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+ " var image = mpl.toolbar_items[toolbar_ind][2];\n",
+ " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+ "\n",
+ " if (!name) {\n",
+ " /* Instead of a spacer, we start a new button group. */\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ " buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'btn-group';\n",
+ " continue;\n",
+ " }\n",
+ "\n",
+ " button = fig.buttons[name] = document.createElement('button');\n",
+ " button.classList = 'btn btn-default';\n",
+ " button.href = '#';\n",
+ " button.title = name;\n",
+ " button.innerHTML = '';\n",
+ " button.addEventListener('click', on_click_closure(method_name));\n",
+ " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+ " buttonGroup.appendChild(button);\n",
+ " }\n",
+ "\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ "\n",
+ " // Add the status bar.\n",
+ " var status_bar = document.createElement('span');\n",
+ " status_bar.classList = 'mpl-message pull-right';\n",
+ " toolbar.appendChild(status_bar);\n",
+ " this.message = status_bar;\n",
+ "\n",
+ " // Add the close button to the window.\n",
+ " var buttongrp = document.createElement('div');\n",
+ " buttongrp.classList = 'btn-group inline pull-right';\n",
+ " button = document.createElement('button');\n",
+ " button.classList = 'btn btn-mini btn-primary';\n",
+ " button.href = '#';\n",
+ " button.title = 'Stop Interaction';\n",
+ " button.innerHTML = '';\n",
+ " button.addEventListener('click', function (_evt) {\n",
+ " fig.handle_close(fig, {});\n",
+ " });\n",
+ " button.addEventListener(\n",
+ " 'mouseover',\n",
+ " on_mouseover_closure('Stop Interaction')\n",
+ " );\n",
+ " buttongrp.appendChild(button);\n",
+ " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n",
+ " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._remove_fig_handler = function (event) {\n",
+ " var fig = event.data.fig;\n",
+ " if (event.target !== this) {\n",
+ " // Ignore bubbled events from children.\n",
+ " return;\n",
+ " }\n",
+ " fig.close_ws(fig, {});\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._root_extra_style = function (el) {\n",
+ " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._canvas_extra_style = function (el) {\n",
+ " // this is important to make the div 'focusable\n",
+ " el.setAttribute('tabindex', 0);\n",
+ " // reach out to IPython and tell the keyboard manager to turn it's self\n",
+ " // off when our div gets focus\n",
+ "\n",
+ " // location in version 3\n",
+ " if (IPython.notebook.keyboard_manager) {\n",
+ " IPython.notebook.keyboard_manager.register_events(el);\n",
+ " } else {\n",
+ " // location in version 2\n",
+ " IPython.keyboard_manager.register_events(el);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._key_event_extra = function (event, _name) {\n",
+ " // Check for shift+enter\n",
+ " if (event.shiftKey && event.which === 13) {\n",
+ " this.canvas_div.blur();\n",
+ " // select the cell after this one\n",
+ " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n",
+ " IPython.notebook.select(index + 1);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+ " fig.ondownload(fig, null);\n",
+ "};\n",
+ "\n",
+ "mpl.find_output_cell = function (html_output) {\n",
+ " // Return the cell and output element which can be found *uniquely* in the notebook.\n",
+ " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
+ " // IPython event is triggered only after the cells have been serialised, which for\n",
+ " // our purposes (turning an active figure into a static one), is too late.\n",
+ " var cells = IPython.notebook.get_cells();\n",
+ " var ncells = cells.length;\n",
+ " for (var i = 0; i < ncells; i++) {\n",
+ " var cell = cells[i];\n",
+ " if (cell.cell_type === 'code') {\n",
+ " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n",
+ " var data = cell.output_area.outputs[j];\n",
+ " if (data.data) {\n",
+ " // IPython >= 3 moved mimebundle to data attribute of output\n",
+ " data = data.data;\n",
+ " }\n",
+ " if (data['text/html'] === html_output) {\n",
+ " return [cell, data, j];\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "// Register the function which deals with the matplotlib target/channel.\n",
+ "// The kernel may be null if the page has been refreshed.\n",
+ "if (IPython.notebook.kernel !== null) {\n",
+ " IPython.notebook.kernel.comm_manager.register_target(\n",
+ " 'matplotlib',\n",
+ " mpl.mpl_figure_comm\n",
+ " );\n",
+ "}\n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "
"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " mean std num\n",
+ "1x0 irods_on_isilon 58.561781 28.682517 19\n",
+ " irods_on_netapp 39.473920 1.297394 10\n",
+ " irods_on_s3 38.528168 4.646165 19\n",
+ "1x1K irods_on_isilon 100.219069 1.270263 19\n",
+ " irods_on_netapp 100.081593 1.281383 10\n",
+ " irods_on_s3 61.579468 5.641403 19\n",
+ "10x1K irods_on_isilon 100.312221 1.484970 19\n",
+ " irods_on_netapp 100.054191 1.617283 10\n",
+ " irods_on_s3 69.618712 5.310661 19\n",
+ "100x1K irods_on_isilon 111.333367 6.102356 19\n",
+ " irods_on_netapp 107.249967 4.786830 9\n",
+ " irods_on_s3 142.857798 6.003477 19\n",
+ "1000x1K irods_on_isilon 575.954983 29.149668 19\n",
+ " irods_on_netapp 575.380933 13.318009 10\n",
+ " irods_on_s3 857.795866 17.849279 19\n",
+ "2000x1K irods_on_isilon 1082.106492 18.186798 19\n",
+ " irods_on_netapp 1088.393951 20.461509 10\n",
+ " irods_on_s3 1657.198189 49.299785 19\n"
+ ]
+ },
+ {
+ "data": {
+ "application/javascript": [
+ "/* Put everything inside the global mpl namespace */\n",
+ "/* global mpl */\n",
+ "window.mpl = {};\n",
+ "\n",
+ "mpl.get_websocket_type = function () {\n",
+ " if (typeof WebSocket !== 'undefined') {\n",
+ " return WebSocket;\n",
+ " } else if (typeof MozWebSocket !== 'undefined') {\n",
+ " return MozWebSocket;\n",
+ " } else {\n",
+ " alert(\n",
+ " 'Your browser does not have WebSocket support. ' +\n",
+ " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
+ " 'Firefox 4 and 5 are also supported but you ' +\n",
+ " 'have to enable WebSockets in about:config.'\n",
+ " );\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n",
+ " this.id = figure_id;\n",
+ "\n",
+ " this.ws = websocket;\n",
+ "\n",
+ " this.supports_binary = this.ws.binaryType !== undefined;\n",
+ "\n",
+ " if (!this.supports_binary) {\n",
+ " var warnings = document.getElementById('mpl-warnings');\n",
+ " if (warnings) {\n",
+ " warnings.style.display = 'block';\n",
+ " warnings.textContent =\n",
+ " 'This browser does not support binary websocket messages. ' +\n",
+ " 'Performance may be slow.';\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " this.imageObj = new Image();\n",
+ "\n",
+ " this.context = undefined;\n",
+ " this.message = undefined;\n",
+ " this.canvas = undefined;\n",
+ " this.rubberband_canvas = undefined;\n",
+ " this.rubberband_context = undefined;\n",
+ " this.format_dropdown = undefined;\n",
+ "\n",
+ " this.image_mode = 'full';\n",
+ "\n",
+ " this.root = document.createElement('div');\n",
+ " this.root.setAttribute('style', 'display: inline-block');\n",
+ " this._root_extra_style(this.root);\n",
+ "\n",
+ " parent_element.appendChild(this.root);\n",
+ "\n",
+ " this._init_header(this);\n",
+ " this._init_canvas(this);\n",
+ " this._init_toolbar(this);\n",
+ "\n",
+ " var fig = this;\n",
+ "\n",
+ " this.waiting = false;\n",
+ "\n",
+ " this.ws.onopen = function () {\n",
+ " fig.send_message('supports_binary', { value: fig.supports_binary });\n",
+ " fig.send_message('send_image_mode', {});\n",
+ " if (fig.ratio !== 1) {\n",
+ " fig.send_message('set_device_pixel_ratio', {\n",
+ " device_pixel_ratio: fig.ratio,\n",
+ " });\n",
+ " }\n",
+ " fig.send_message('refresh', {});\n",
+ " };\n",
+ "\n",
+ " this.imageObj.onload = function () {\n",
+ " if (fig.image_mode === 'full') {\n",
+ " // Full images could contain transparency (where diff images\n",
+ " // almost always do), so we need to clear the canvas so that\n",
+ " // there is no ghosting.\n",
+ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
+ " }\n",
+ " fig.context.drawImage(fig.imageObj, 0, 0);\n",
+ " };\n",
+ "\n",
+ " this.imageObj.onunload = function () {\n",
+ " fig.ws.close();\n",
+ " };\n",
+ "\n",
+ " this.ws.onmessage = this._make_on_message_function(this);\n",
+ "\n",
+ " this.ondownload = ondownload;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_header = function () {\n",
+ " var titlebar = document.createElement('div');\n",
+ " titlebar.classList =\n",
+ " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n",
+ " var titletext = document.createElement('div');\n",
+ " titletext.classList = 'ui-dialog-title';\n",
+ " titletext.setAttribute(\n",
+ " 'style',\n",
+ " 'width: 100%; text-align: center; padding: 3px;'\n",
+ " );\n",
+ " titlebar.appendChild(titletext);\n",
+ " this.root.appendChild(titlebar);\n",
+ " this.header = titletext;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n",
+ "\n",
+ "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n",
+ "\n",
+ "mpl.figure.prototype._init_canvas = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var canvas_div = (this.canvas_div = document.createElement('div'));\n",
+ " canvas_div.setAttribute(\n",
+ " 'style',\n",
+ " 'border: 1px solid #ddd;' +\n",
+ " 'box-sizing: content-box;' +\n",
+ " 'clear: both;' +\n",
+ " 'min-height: 1px;' +\n",
+ " 'min-width: 1px;' +\n",
+ " 'outline: 0;' +\n",
+ " 'overflow: hidden;' +\n",
+ " 'position: relative;' +\n",
+ " 'resize: both;'\n",
+ " );\n",
+ "\n",
+ " function on_keyboard_event_closure(name) {\n",
+ " return function (event) {\n",
+ " return fig.key_event(event, name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " canvas_div.addEventListener(\n",
+ " 'keydown',\n",
+ " on_keyboard_event_closure('key_press')\n",
+ " );\n",
+ " canvas_div.addEventListener(\n",
+ " 'keyup',\n",
+ " on_keyboard_event_closure('key_release')\n",
+ " );\n",
+ "\n",
+ " this._canvas_extra_style(canvas_div);\n",
+ " this.root.appendChild(canvas_div);\n",
+ "\n",
+ " var canvas = (this.canvas = document.createElement('canvas'));\n",
+ " canvas.classList.add('mpl-canvas');\n",
+ " canvas.setAttribute('style', 'box-sizing: content-box;');\n",
+ "\n",
+ " this.context = canvas.getContext('2d');\n",
+ "\n",
+ " var backingStore =\n",
+ " this.context.backingStorePixelRatio ||\n",
+ " this.context.webkitBackingStorePixelRatio ||\n",
+ " this.context.mozBackingStorePixelRatio ||\n",
+ " this.context.msBackingStorePixelRatio ||\n",
+ " this.context.oBackingStorePixelRatio ||\n",
+ " this.context.backingStorePixelRatio ||\n",
+ " 1;\n",
+ "\n",
+ " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
+ "\n",
+ " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n",
+ " 'canvas'\n",
+ " ));\n",
+ " rubberband_canvas.setAttribute(\n",
+ " 'style',\n",
+ " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n",
+ " );\n",
+ "\n",
+ " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n",
+ " if (this.ResizeObserver === undefined) {\n",
+ " if (window.ResizeObserver !== undefined) {\n",
+ " this.ResizeObserver = window.ResizeObserver;\n",
+ " } else {\n",
+ " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n",
+ " this.ResizeObserver = obs.ResizeObserver;\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n",
+ " var nentries = entries.length;\n",
+ " for (var i = 0; i < nentries; i++) {\n",
+ " var entry = entries[i];\n",
+ " var width, height;\n",
+ " if (entry.contentBoxSize) {\n",
+ " if (entry.contentBoxSize instanceof Array) {\n",
+ " // Chrome 84 implements new version of spec.\n",
+ " width = entry.contentBoxSize[0].inlineSize;\n",
+ " height = entry.contentBoxSize[0].blockSize;\n",
+ " } else {\n",
+ " // Firefox implements old version of spec.\n",
+ " width = entry.contentBoxSize.inlineSize;\n",
+ " height = entry.contentBoxSize.blockSize;\n",
+ " }\n",
+ " } else {\n",
+ " // Chrome <84 implements even older version of spec.\n",
+ " width = entry.contentRect.width;\n",
+ " height = entry.contentRect.height;\n",
+ " }\n",
+ "\n",
+ " // Keep the size of the canvas and rubber band canvas in sync with\n",
+ " // the canvas container.\n",
+ " if (entry.devicePixelContentBoxSize) {\n",
+ " // Chrome 84 implements new version of spec.\n",
+ " canvas.setAttribute(\n",
+ " 'width',\n",
+ " entry.devicePixelContentBoxSize[0].inlineSize\n",
+ " );\n",
+ " canvas.setAttribute(\n",
+ " 'height',\n",
+ " entry.devicePixelContentBoxSize[0].blockSize\n",
+ " );\n",
+ " } else {\n",
+ " canvas.setAttribute('width', width * fig.ratio);\n",
+ " canvas.setAttribute('height', height * fig.ratio);\n",
+ " }\n",
+ " canvas.setAttribute(\n",
+ " 'style',\n",
+ " 'width: ' + width + 'px; height: ' + height + 'px;'\n",
+ " );\n",
+ "\n",
+ " rubberband_canvas.setAttribute('width', width);\n",
+ " rubberband_canvas.setAttribute('height', height);\n",
+ "\n",
+ " // And update the size in Python. We ignore the initial 0/0 size\n",
+ " // that occurs as the element is placed into the DOM, which should\n",
+ " // otherwise not happen due to the minimum size styling.\n",
+ " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n",
+ " fig.request_resize(width, height);\n",
+ " }\n",
+ " }\n",
+ " });\n",
+ " this.resizeObserverInstance.observe(canvas_div);\n",
+ "\n",
+ " function on_mouse_event_closure(name) {\n",
+ " return function (event) {\n",
+ " return fig.mouse_event(event, name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mousedown',\n",
+ " on_mouse_event_closure('button_press')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseup',\n",
+ " on_mouse_event_closure('button_release')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'dblclick',\n",
+ " on_mouse_event_closure('dblclick')\n",
+ " );\n",
+ " // Throttle sequential mouse events to 1 every 20ms.\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mousemove',\n",
+ " on_mouse_event_closure('motion_notify')\n",
+ " );\n",
+ "\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseenter',\n",
+ " on_mouse_event_closure('figure_enter')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseleave',\n",
+ " on_mouse_event_closure('figure_leave')\n",
+ " );\n",
+ "\n",
+ " canvas_div.addEventListener('wheel', function (event) {\n",
+ " if (event.deltaY < 0) {\n",
+ " event.step = 1;\n",
+ " } else {\n",
+ " event.step = -1;\n",
+ " }\n",
+ " on_mouse_event_closure('scroll')(event);\n",
+ " });\n",
+ "\n",
+ " canvas_div.appendChild(canvas);\n",
+ " canvas_div.appendChild(rubberband_canvas);\n",
+ "\n",
+ " this.rubberband_context = rubberband_canvas.getContext('2d');\n",
+ " this.rubberband_context.strokeStyle = '#000000';\n",
+ "\n",
+ " this._resize_canvas = function (width, height, forward) {\n",
+ " if (forward) {\n",
+ " canvas_div.style.width = width + 'px';\n",
+ " canvas_div.style.height = height + 'px';\n",
+ " }\n",
+ " };\n",
+ "\n",
+ " // Disable right mouse context menu.\n",
+ " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n",
+ " event.preventDefault();\n",
+ " return false;\n",
+ " });\n",
+ "\n",
+ " function set_focus() {\n",
+ " canvas.focus();\n",
+ " canvas_div.focus();\n",
+ " }\n",
+ "\n",
+ " window.setTimeout(set_focus, 100);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_toolbar = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var toolbar = document.createElement('div');\n",
+ " toolbar.classList = 'mpl-toolbar';\n",
+ " this.root.appendChild(toolbar);\n",
+ "\n",
+ " function on_click_closure(name) {\n",
+ " return function (_event) {\n",
+ " return fig.toolbar_button_onclick(name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " function on_mouseover_closure(tooltip) {\n",
+ " return function (event) {\n",
+ " if (!event.currentTarget.disabled) {\n",
+ " return fig.toolbar_button_onmouseover(tooltip);\n",
+ " }\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " fig.buttons = {};\n",
+ " var buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'mpl-button-group';\n",
+ " for (var toolbar_ind in mpl.toolbar_items) {\n",
+ " var name = mpl.toolbar_items[toolbar_ind][0];\n",
+ " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+ " var image = mpl.toolbar_items[toolbar_ind][2];\n",
+ " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+ "\n",
+ " if (!name) {\n",
+ " /* Instead of a spacer, we start a new button group. */\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ " buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'mpl-button-group';\n",
+ " continue;\n",
+ " }\n",
+ "\n",
+ " var button = (fig.buttons[name] = document.createElement('button'));\n",
+ " button.classList = 'mpl-widget';\n",
+ " button.setAttribute('role', 'button');\n",
+ " button.setAttribute('aria-disabled', 'false');\n",
+ " button.addEventListener('click', on_click_closure(method_name));\n",
+ " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+ "\n",
+ " var icon_img = document.createElement('img');\n",
+ " icon_img.src = '_images/' + image + '.png';\n",
+ " icon_img.srcset = '_images/' + image + '_large.png 2x';\n",
+ " icon_img.alt = tooltip;\n",
+ " button.appendChild(icon_img);\n",
+ "\n",
+ " buttonGroup.appendChild(button);\n",
+ " }\n",
+ "\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ "\n",
+ " var fmt_picker = document.createElement('select');\n",
+ " fmt_picker.classList = 'mpl-widget';\n",
+ " toolbar.appendChild(fmt_picker);\n",
+ " this.format_dropdown = fmt_picker;\n",
+ "\n",
+ " for (var ind in mpl.extensions) {\n",
+ " var fmt = mpl.extensions[ind];\n",
+ " var option = document.createElement('option');\n",
+ " option.selected = fmt === mpl.default_extension;\n",
+ " option.innerHTML = fmt;\n",
+ " fmt_picker.appendChild(option);\n",
+ " }\n",
+ "\n",
+ " var status_bar = document.createElement('span');\n",
+ " status_bar.classList = 'mpl-message';\n",
+ " toolbar.appendChild(status_bar);\n",
+ " this.message = status_bar;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n",
+ " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
+ " // which will in turn request a refresh of the image.\n",
+ " this.send_message('resize', { width: x_pixels, height: y_pixels });\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.send_message = function (type, properties) {\n",
+ " properties['type'] = type;\n",
+ " properties['figure_id'] = this.id;\n",
+ " this.ws.send(JSON.stringify(properties));\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.send_draw_message = function () {\n",
+ " if (!this.waiting) {\n",
+ " this.waiting = true;\n",
+ " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+ " var format_dropdown = fig.format_dropdown;\n",
+ " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
+ " fig.ondownload(fig, format);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_resize = function (fig, msg) {\n",
+ " var size = msg['size'];\n",
+ " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n",
+ " fig._resize_canvas(size[0], size[1], msg['forward']);\n",
+ " fig.send_message('refresh', {});\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n",
+ " var x0 = msg['x0'] / fig.ratio;\n",
+ " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n",
+ " var x1 = msg['x1'] / fig.ratio;\n",
+ " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n",
+ " x0 = Math.floor(x0) + 0.5;\n",
+ " y0 = Math.floor(y0) + 0.5;\n",
+ " x1 = Math.floor(x1) + 0.5;\n",
+ " y1 = Math.floor(y1) + 0.5;\n",
+ " var min_x = Math.min(x0, x1);\n",
+ " var min_y = Math.min(y0, y1);\n",
+ " var width = Math.abs(x1 - x0);\n",
+ " var height = Math.abs(y1 - y0);\n",
+ "\n",
+ " fig.rubberband_context.clearRect(\n",
+ " 0,\n",
+ " 0,\n",
+ " fig.canvas.width / fig.ratio,\n",
+ " fig.canvas.height / fig.ratio\n",
+ " );\n",
+ "\n",
+ " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n",
+ " // Updates the figure title.\n",
+ " fig.header.textContent = msg['label'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n",
+ " fig.rubberband_canvas.style.cursor = msg['cursor'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_message = function (fig, msg) {\n",
+ " fig.message.textContent = msg['message'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n",
+ " // Request the server to send over a new figure.\n",
+ " fig.send_draw_message();\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n",
+ " fig.image_mode = msg['mode'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n",
+ " for (var key in msg) {\n",
+ " if (!(key in fig.buttons)) {\n",
+ " continue;\n",
+ " }\n",
+ " fig.buttons[key].disabled = !msg[key];\n",
+ " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n",
+ " if (msg['mode'] === 'PAN') {\n",
+ " fig.buttons['Pan'].classList.add('active');\n",
+ " fig.buttons['Zoom'].classList.remove('active');\n",
+ " } else if (msg['mode'] === 'ZOOM') {\n",
+ " fig.buttons['Pan'].classList.remove('active');\n",
+ " fig.buttons['Zoom'].classList.add('active');\n",
+ " } else {\n",
+ " fig.buttons['Pan'].classList.remove('active');\n",
+ " fig.buttons['Zoom'].classList.remove('active');\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.updated_canvas_event = function () {\n",
+ " // Called whenever the canvas gets updated.\n",
+ " this.send_message('ack', {});\n",
+ "};\n",
+ "\n",
+ "// A function to construct a web socket function for onmessage handling.\n",
+ "// Called in the figure constructor.\n",
+ "mpl.figure.prototype._make_on_message_function = function (fig) {\n",
+ " return function socket_on_message(evt) {\n",
+ " if (evt.data instanceof Blob) {\n",
+ " var img = evt.data;\n",
+ " if (img.type !== 'image/png') {\n",
+ " /* FIXME: We get \"Resource interpreted as Image but\n",
+ " * transferred with MIME type text/plain:\" errors on\n",
+ " * Chrome. But how to set the MIME type? It doesn't seem\n",
+ " * to be part of the websocket stream */\n",
+ " img.type = 'image/png';\n",
+ " }\n",
+ "\n",
+ " /* Free the memory for the previous frames */\n",
+ " if (fig.imageObj.src) {\n",
+ " (window.URL || window.webkitURL).revokeObjectURL(\n",
+ " fig.imageObj.src\n",
+ " );\n",
+ " }\n",
+ "\n",
+ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
+ " img\n",
+ " );\n",
+ " fig.updated_canvas_event();\n",
+ " fig.waiting = false;\n",
+ " return;\n",
+ " } else if (\n",
+ " typeof evt.data === 'string' &&\n",
+ " evt.data.slice(0, 21) === 'data:image/png;base64'\n",
+ " ) {\n",
+ " fig.imageObj.src = evt.data;\n",
+ " fig.updated_canvas_event();\n",
+ " fig.waiting = false;\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " var msg = JSON.parse(evt.data);\n",
+ " var msg_type = msg['type'];\n",
+ "\n",
+ " // Call the \"handle_{type}\" callback, which takes\n",
+ " // the figure and JSON message as its only arguments.\n",
+ " try {\n",
+ " var callback = fig['handle_' + msg_type];\n",
+ " } catch (e) {\n",
+ " console.log(\n",
+ " \"No handler for the '\" + msg_type + \"' message type: \",\n",
+ " msg\n",
+ " );\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " if (callback) {\n",
+ " try {\n",
+ " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
+ " callback(fig, msg);\n",
+ " } catch (e) {\n",
+ " console.log(\n",
+ " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n",
+ " e,\n",
+ " e.stack,\n",
+ " msg\n",
+ " );\n",
+ " }\n",
+ " }\n",
+ " };\n",
+ "};\n",
+ "\n",
+ "// from https://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
+ "mpl.findpos = function (e) {\n",
+ " //this section is from http://www.quirksmode.org/js/events_properties.html\n",
+ " var targ;\n",
+ " if (!e) {\n",
+ " e = window.event;\n",
+ " }\n",
+ " if (e.target) {\n",
+ " targ = e.target;\n",
+ " } else if (e.srcElement) {\n",
+ " targ = e.srcElement;\n",
+ " }\n",
+ " if (targ.nodeType === 3) {\n",
+ " // defeat Safari bug\n",
+ " targ = targ.parentNode;\n",
+ " }\n",
+ "\n",
+ " // pageX,Y are the mouse positions relative to the document\n",
+ " var boundingRect = targ.getBoundingClientRect();\n",
+ " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n",
+ " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n",
+ "\n",
+ " return { x: x, y: y };\n",
+ "};\n",
+ "\n",
+ "/*\n",
+ " * return a copy of an object with only non-object keys\n",
+ " * we need this to avoid circular references\n",
+ " * https://stackoverflow.com/a/24161582/3208463\n",
+ " */\n",
+ "function simpleKeys(original) {\n",
+ " return Object.keys(original).reduce(function (obj, key) {\n",
+ " if (typeof original[key] !== 'object') {\n",
+ " obj[key] = original[key];\n",
+ " }\n",
+ " return obj;\n",
+ " }, {});\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.mouse_event = function (event, name) {\n",
+ " var canvas_pos = mpl.findpos(event);\n",
+ "\n",
+ " if (name === 'button_press') {\n",
+ " this.canvas.focus();\n",
+ " this.canvas_div.focus();\n",
+ " }\n",
+ "\n",
+ " var x = canvas_pos.x * this.ratio;\n",
+ " var y = canvas_pos.y * this.ratio;\n",
+ "\n",
+ " this.send_message(name, {\n",
+ " x: x,\n",
+ " y: y,\n",
+ " button: event.button,\n",
+ " step: event.step,\n",
+ " guiEvent: simpleKeys(event),\n",
+ " });\n",
+ "\n",
+ " /* This prevents the web browser from automatically changing to\n",
+ " * the text insertion cursor when the button is pressed. We want\n",
+ " * to control all of the cursor setting manually through the\n",
+ " * 'cursor' event from matplotlib */\n",
+ " event.preventDefault();\n",
+ " return false;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n",
+ " // Handle any extra behaviour associated with a key event\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.key_event = function (event, name) {\n",
+ " // Prevent repeat events\n",
+ " if (name === 'key_press') {\n",
+ " if (event.key === this._key) {\n",
+ " return;\n",
+ " } else {\n",
+ " this._key = event.key;\n",
+ " }\n",
+ " }\n",
+ " if (name === 'key_release') {\n",
+ " this._key = null;\n",
+ " }\n",
+ "\n",
+ " var value = '';\n",
+ " if (event.ctrlKey && event.key !== 'Control') {\n",
+ " value += 'ctrl+';\n",
+ " }\n",
+ " else if (event.altKey && event.key !== 'Alt') {\n",
+ " value += 'alt+';\n",
+ " }\n",
+ " else if (event.shiftKey && event.key !== 'Shift') {\n",
+ " value += 'shift+';\n",
+ " }\n",
+ "\n",
+ " value += 'k' + event.key;\n",
+ "\n",
+ " this._key_event_extra(event, name);\n",
+ "\n",
+ " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n",
+ " return false;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n",
+ " if (name === 'download') {\n",
+ " this.handle_save(this, null);\n",
+ " } else {\n",
+ " this.send_message('toolbar_button', { name: name });\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n",
+ " this.message.textContent = tooltip;\n",
+ "};\n",
+ "\n",
+ "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n",
+ "// prettier-ignore\n",
+ "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n",
+ "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
+ "\n",
+ "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
+ "\n",
+ "mpl.default_extension = \"png\";/* global mpl */\n",
+ "\n",
+ "var comm_websocket_adapter = function (comm) {\n",
+ " // Create a \"websocket\"-like object which calls the given IPython comm\n",
+ " // object with the appropriate methods. Currently this is a non binary\n",
+ " // socket, so there is still some room for performance tuning.\n",
+ " var ws = {};\n",
+ "\n",
+ " ws.binaryType = comm.kernel.ws.binaryType;\n",
+ " ws.readyState = comm.kernel.ws.readyState;\n",
+ " function updateReadyState(_event) {\n",
+ " if (comm.kernel.ws) {\n",
+ " ws.readyState = comm.kernel.ws.readyState;\n",
+ " } else {\n",
+ " ws.readyState = 3; // Closed state.\n",
+ " }\n",
+ " }\n",
+ " comm.kernel.ws.addEventListener('open', updateReadyState);\n",
+ " comm.kernel.ws.addEventListener('close', updateReadyState);\n",
+ " comm.kernel.ws.addEventListener('error', updateReadyState);\n",
+ "\n",
+ " ws.close = function () {\n",
+ " comm.close();\n",
+ " };\n",
+ " ws.send = function (m) {\n",
+ " //console.log('sending', m);\n",
+ " comm.send(m);\n",
+ " };\n",
+ " // Register the callback with on_msg.\n",
+ " comm.on_msg(function (msg) {\n",
+ " //console.log('receiving', msg['content']['data'], msg);\n",
+ " var data = msg['content']['data'];\n",
+ " if (data['blob'] !== undefined) {\n",
+ " data = {\n",
+ " data: new Blob(msg['buffers'], { type: data['blob'] }),\n",
+ " };\n",
+ " }\n",
+ " // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
+ " ws.onmessage(data);\n",
+ " });\n",
+ " return ws;\n",
+ "};\n",
+ "\n",
+ "mpl.mpl_figure_comm = function (comm, msg) {\n",
+ " // This is the function which gets called when the mpl process\n",
+ " // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
+ "\n",
+ " var id = msg.content.data.id;\n",
+ " // Get hold of the div created by the display call when the Comm\n",
+ " // socket was opened in Python.\n",
+ " var element = document.getElementById(id);\n",
+ " var ws_proxy = comm_websocket_adapter(comm);\n",
+ "\n",
+ " function ondownload(figure, _format) {\n",
+ " window.open(figure.canvas.toDataURL());\n",
+ " }\n",
+ "\n",
+ " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n",
+ "\n",
+ " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
+ " // web socket which is closed, not our websocket->open comm proxy.\n",
+ " ws_proxy.onopen();\n",
+ "\n",
+ " fig.parent_element = element;\n",
+ " fig.cell_info = mpl.find_output_cell(\"\");\n",
+ " if (!fig.cell_info) {\n",
+ " console.error('Failed to find cell for figure', id, fig);\n",
+ " return;\n",
+ " }\n",
+ " fig.cell_info[0].output_area.element.on(\n",
+ " 'cleared',\n",
+ " { fig: fig },\n",
+ " fig._remove_fig_handler\n",
+ " );\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_close = function (fig, msg) {\n",
+ " var width = fig.canvas.width / fig.ratio;\n",
+ " fig.cell_info[0].output_area.element.off(\n",
+ " 'cleared',\n",
+ " fig._remove_fig_handler\n",
+ " );\n",
+ " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n",
+ "\n",
+ " // Update the output cell to use the data from the current canvas.\n",
+ " fig.push_to_output();\n",
+ " var dataURL = fig.canvas.toDataURL();\n",
+ " // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
+ " // the notebook keyboard shortcuts fail.\n",
+ " IPython.keyboard_manager.enable();\n",
+ " fig.parent_element.innerHTML =\n",
+ " '
';\n",
+ " fig.close_ws(fig, msg);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.close_ws = function (fig, msg) {\n",
+ " fig.send_message('closing', msg);\n",
+ " // fig.ws.close()\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n",
+ " // Turn the data on the canvas into data in the output cell.\n",
+ " var width = this.canvas.width / this.ratio;\n",
+ " var dataURL = this.canvas.toDataURL();\n",
+ " this.cell_info[1]['text/html'] =\n",
+ " '
';\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.updated_canvas_event = function () {\n",
+ " // Tell IPython that the notebook contents must change.\n",
+ " IPython.notebook.set_dirty(true);\n",
+ " this.send_message('ack', {});\n",
+ " var fig = this;\n",
+ " // Wait a second, then push the new image to the DOM so\n",
+ " // that it is saved nicely (might be nice to debounce this).\n",
+ " setTimeout(function () {\n",
+ " fig.push_to_output();\n",
+ " }, 1000);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_toolbar = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var toolbar = document.createElement('div');\n",
+ " toolbar.classList = 'btn-toolbar';\n",
+ " this.root.appendChild(toolbar);\n",
+ "\n",
+ " function on_click_closure(name) {\n",
+ " return function (_event) {\n",
+ " return fig.toolbar_button_onclick(name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " function on_mouseover_closure(tooltip) {\n",
+ " return function (event) {\n",
+ " if (!event.currentTarget.disabled) {\n",
+ " return fig.toolbar_button_onmouseover(tooltip);\n",
+ " }\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " fig.buttons = {};\n",
+ " var buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'btn-group';\n",
+ " var button;\n",
+ " for (var toolbar_ind in mpl.toolbar_items) {\n",
+ " var name = mpl.toolbar_items[toolbar_ind][0];\n",
+ " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+ " var image = mpl.toolbar_items[toolbar_ind][2];\n",
+ " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+ "\n",
+ " if (!name) {\n",
+ " /* Instead of a spacer, we start a new button group. */\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ " buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'btn-group';\n",
+ " continue;\n",
+ " }\n",
+ "\n",
+ " button = fig.buttons[name] = document.createElement('button');\n",
+ " button.classList = 'btn btn-default';\n",
+ " button.href = '#';\n",
+ " button.title = name;\n",
+ " button.innerHTML = '';\n",
+ " button.addEventListener('click', on_click_closure(method_name));\n",
+ " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+ " buttonGroup.appendChild(button);\n",
+ " }\n",
+ "\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ "\n",
+ " // Add the status bar.\n",
+ " var status_bar = document.createElement('span');\n",
+ " status_bar.classList = 'mpl-message pull-right';\n",
+ " toolbar.appendChild(status_bar);\n",
+ " this.message = status_bar;\n",
+ "\n",
+ " // Add the close button to the window.\n",
+ " var buttongrp = document.createElement('div');\n",
+ " buttongrp.classList = 'btn-group inline pull-right';\n",
+ " button = document.createElement('button');\n",
+ " button.classList = 'btn btn-mini btn-primary';\n",
+ " button.href = '#';\n",
+ " button.title = 'Stop Interaction';\n",
+ " button.innerHTML = '';\n",
+ " button.addEventListener('click', function (_evt) {\n",
+ " fig.handle_close(fig, {});\n",
+ " });\n",
+ " button.addEventListener(\n",
+ " 'mouseover',\n",
+ " on_mouseover_closure('Stop Interaction')\n",
+ " );\n",
+ " buttongrp.appendChild(button);\n",
+ " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n",
+ " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._remove_fig_handler = function (event) {\n",
+ " var fig = event.data.fig;\n",
+ " if (event.target !== this) {\n",
+ " // Ignore bubbled events from children.\n",
+ " return;\n",
+ " }\n",
+ " fig.close_ws(fig, {});\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._root_extra_style = function (el) {\n",
+ " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._canvas_extra_style = function (el) {\n",
+ " // this is important to make the div 'focusable\n",
+ " el.setAttribute('tabindex', 0);\n",
+ " // reach out to IPython and tell the keyboard manager to turn it's self\n",
+ " // off when our div gets focus\n",
+ "\n",
+ " // location in version 3\n",
+ " if (IPython.notebook.keyboard_manager) {\n",
+ " IPython.notebook.keyboard_manager.register_events(el);\n",
+ " } else {\n",
+ " // location in version 2\n",
+ " IPython.keyboard_manager.register_events(el);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._key_event_extra = function (event, _name) {\n",
+ " // Check for shift+enter\n",
+ " if (event.shiftKey && event.which === 13) {\n",
+ " this.canvas_div.blur();\n",
+ " // select the cell after this one\n",
+ " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n",
+ " IPython.notebook.select(index + 1);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+ " fig.ondownload(fig, null);\n",
+ "};\n",
+ "\n",
+ "mpl.find_output_cell = function (html_output) {\n",
+ " // Return the cell and output element which can be found *uniquely* in the notebook.\n",
+ " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
+ " // IPython event is triggered only after the cells have been serialised, which for\n",
+ " // our purposes (turning an active figure into a static one), is too late.\n",
+ " var cells = IPython.notebook.get_cells();\n",
+ " var ncells = cells.length;\n",
+ " for (var i = 0; i < ncells; i++) {\n",
+ " var cell = cells[i];\n",
+ " if (cell.cell_type === 'code') {\n",
+ " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n",
+ " var data = cell.output_area.outputs[j];\n",
+ " if (data.data) {\n",
+ " // IPython >= 3 moved mimebundle to data attribute of output\n",
+ " data = data.data;\n",
+ " }\n",
+ " if (data['text/html'] === html_output) {\n",
+ " return [cell, data, j];\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "// Register the function which deals with the matplotlib target/channel.\n",
+ "// The kernel may be null if the page has been refreshed.\n",
+ "if (IPython.notebook.kernel !== null) {\n",
+ " IPython.notebook.kernel.comm_manager.register_target(\n",
+ " 'matplotlib',\n",
+ " mpl.mpl_figure_comm\n",
+ " );\n",
+ "}\n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "
"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " mean std num\n",
+ "1x0 irods_on_isilon 58.561781 28.682517 19\n",
+ " irods_on_netapp 39.473920 1.297394 10\n",
+ " irods_on_s3 38.528168 4.646165 19\n",
+ "1x1K irods_on_isilon 100.219069 1.270263 19\n",
+ " irods_on_netapp 100.081593 1.281383 10\n",
+ " irods_on_s3 61.579468 5.641403 19\n",
+ "1x1M irods_on_isilon 100.629799 1.505577 17\n",
+ " irods_on_netapp 100.311454 1.300512 9\n",
+ " irods_on_s3 62.325737 5.009204 18\n",
+ "1x1G irods_on_isilon 130.385827 7.590377 18\n",
+ " irods_on_netapp 123.341776 3.075267 9\n",
+ " irods_on_s3 131.362494 8.960215 18\n",
+ "10x1G irods_on_isilon 898.671291 14.834686 9\n",
+ " irods_on_netapp 900.939860 12.974536 9\n",
+ " irods_on_s3 895.431666 14.051611 8\n"
+ ]
+ },
+ {
+ "data": {
+ "application/javascript": [
+ "/* Put everything inside the global mpl namespace */\n",
+ "/* global mpl */\n",
+ "window.mpl = {};\n",
+ "\n",
+ "mpl.get_websocket_type = function () {\n",
+ " if (typeof WebSocket !== 'undefined') {\n",
+ " return WebSocket;\n",
+ " } else if (typeof MozWebSocket !== 'undefined') {\n",
+ " return MozWebSocket;\n",
+ " } else {\n",
+ " alert(\n",
+ " 'Your browser does not have WebSocket support. ' +\n",
+ " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
+ " 'Firefox 4 and 5 are also supported but you ' +\n",
+ " 'have to enable WebSockets in about:config.'\n",
+ " );\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n",
+ " this.id = figure_id;\n",
+ "\n",
+ " this.ws = websocket;\n",
+ "\n",
+ " this.supports_binary = this.ws.binaryType !== undefined;\n",
+ "\n",
+ " if (!this.supports_binary) {\n",
+ " var warnings = document.getElementById('mpl-warnings');\n",
+ " if (warnings) {\n",
+ " warnings.style.display = 'block';\n",
+ " warnings.textContent =\n",
+ " 'This browser does not support binary websocket messages. ' +\n",
+ " 'Performance may be slow.';\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " this.imageObj = new Image();\n",
+ "\n",
+ " this.context = undefined;\n",
+ " this.message = undefined;\n",
+ " this.canvas = undefined;\n",
+ " this.rubberband_canvas = undefined;\n",
+ " this.rubberband_context = undefined;\n",
+ " this.format_dropdown = undefined;\n",
+ "\n",
+ " this.image_mode = 'full';\n",
+ "\n",
+ " this.root = document.createElement('div');\n",
+ " this.root.setAttribute('style', 'display: inline-block');\n",
+ " this._root_extra_style(this.root);\n",
+ "\n",
+ " parent_element.appendChild(this.root);\n",
+ "\n",
+ " this._init_header(this);\n",
+ " this._init_canvas(this);\n",
+ " this._init_toolbar(this);\n",
+ "\n",
+ " var fig = this;\n",
+ "\n",
+ " this.waiting = false;\n",
+ "\n",
+ " this.ws.onopen = function () {\n",
+ " fig.send_message('supports_binary', { value: fig.supports_binary });\n",
+ " fig.send_message('send_image_mode', {});\n",
+ " if (fig.ratio !== 1) {\n",
+ " fig.send_message('set_device_pixel_ratio', {\n",
+ " device_pixel_ratio: fig.ratio,\n",
+ " });\n",
+ " }\n",
+ " fig.send_message('refresh', {});\n",
+ " };\n",
+ "\n",
+ " this.imageObj.onload = function () {\n",
+ " if (fig.image_mode === 'full') {\n",
+ " // Full images could contain transparency (where diff images\n",
+ " // almost always do), so we need to clear the canvas so that\n",
+ " // there is no ghosting.\n",
+ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
+ " }\n",
+ " fig.context.drawImage(fig.imageObj, 0, 0);\n",
+ " };\n",
+ "\n",
+ " this.imageObj.onunload = function () {\n",
+ " fig.ws.close();\n",
+ " };\n",
+ "\n",
+ " this.ws.onmessage = this._make_on_message_function(this);\n",
+ "\n",
+ " this.ondownload = ondownload;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_header = function () {\n",
+ " var titlebar = document.createElement('div');\n",
+ " titlebar.classList =\n",
+ " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n",
+ " var titletext = document.createElement('div');\n",
+ " titletext.classList = 'ui-dialog-title';\n",
+ " titletext.setAttribute(\n",
+ " 'style',\n",
+ " 'width: 100%; text-align: center; padding: 3px;'\n",
+ " );\n",
+ " titlebar.appendChild(titletext);\n",
+ " this.root.appendChild(titlebar);\n",
+ " this.header = titletext;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n",
+ "\n",
+ "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n",
+ "\n",
+ "mpl.figure.prototype._init_canvas = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var canvas_div = (this.canvas_div = document.createElement('div'));\n",
+ " canvas_div.setAttribute(\n",
+ " 'style',\n",
+ " 'border: 1px solid #ddd;' +\n",
+ " 'box-sizing: content-box;' +\n",
+ " 'clear: both;' +\n",
+ " 'min-height: 1px;' +\n",
+ " 'min-width: 1px;' +\n",
+ " 'outline: 0;' +\n",
+ " 'overflow: hidden;' +\n",
+ " 'position: relative;' +\n",
+ " 'resize: both;'\n",
+ " );\n",
+ "\n",
+ " function on_keyboard_event_closure(name) {\n",
+ " return function (event) {\n",
+ " return fig.key_event(event, name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " canvas_div.addEventListener(\n",
+ " 'keydown',\n",
+ " on_keyboard_event_closure('key_press')\n",
+ " );\n",
+ " canvas_div.addEventListener(\n",
+ " 'keyup',\n",
+ " on_keyboard_event_closure('key_release')\n",
+ " );\n",
+ "\n",
+ " this._canvas_extra_style(canvas_div);\n",
+ " this.root.appendChild(canvas_div);\n",
+ "\n",
+ " var canvas = (this.canvas = document.createElement('canvas'));\n",
+ " canvas.classList.add('mpl-canvas');\n",
+ " canvas.setAttribute('style', 'box-sizing: content-box;');\n",
+ "\n",
+ " this.context = canvas.getContext('2d');\n",
+ "\n",
+ " var backingStore =\n",
+ " this.context.backingStorePixelRatio ||\n",
+ " this.context.webkitBackingStorePixelRatio ||\n",
+ " this.context.mozBackingStorePixelRatio ||\n",
+ " this.context.msBackingStorePixelRatio ||\n",
+ " this.context.oBackingStorePixelRatio ||\n",
+ " this.context.backingStorePixelRatio ||\n",
+ " 1;\n",
+ "\n",
+ " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
+ "\n",
+ " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n",
+ " 'canvas'\n",
+ " ));\n",
+ " rubberband_canvas.setAttribute(\n",
+ " 'style',\n",
+ " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n",
+ " );\n",
+ "\n",
+ " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n",
+ " if (this.ResizeObserver === undefined) {\n",
+ " if (window.ResizeObserver !== undefined) {\n",
+ " this.ResizeObserver = window.ResizeObserver;\n",
+ " } else {\n",
+ " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n",
+ " this.ResizeObserver = obs.ResizeObserver;\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n",
+ " var nentries = entries.length;\n",
+ " for (var i = 0; i < nentries; i++) {\n",
+ " var entry = entries[i];\n",
+ " var width, height;\n",
+ " if (entry.contentBoxSize) {\n",
+ " if (entry.contentBoxSize instanceof Array) {\n",
+ " // Chrome 84 implements new version of spec.\n",
+ " width = entry.contentBoxSize[0].inlineSize;\n",
+ " height = entry.contentBoxSize[0].blockSize;\n",
+ " } else {\n",
+ " // Firefox implements old version of spec.\n",
+ " width = entry.contentBoxSize.inlineSize;\n",
+ " height = entry.contentBoxSize.blockSize;\n",
+ " }\n",
+ " } else {\n",
+ " // Chrome <84 implements even older version of spec.\n",
+ " width = entry.contentRect.width;\n",
+ " height = entry.contentRect.height;\n",
+ " }\n",
+ "\n",
+ " // Keep the size of the canvas and rubber band canvas in sync with\n",
+ " // the canvas container.\n",
+ " if (entry.devicePixelContentBoxSize) {\n",
+ " // Chrome 84 implements new version of spec.\n",
+ " canvas.setAttribute(\n",
+ " 'width',\n",
+ " entry.devicePixelContentBoxSize[0].inlineSize\n",
+ " );\n",
+ " canvas.setAttribute(\n",
+ " 'height',\n",
+ " entry.devicePixelContentBoxSize[0].blockSize\n",
+ " );\n",
+ " } else {\n",
+ " canvas.setAttribute('width', width * fig.ratio);\n",
+ " canvas.setAttribute('height', height * fig.ratio);\n",
+ " }\n",
+ " canvas.setAttribute(\n",
+ " 'style',\n",
+ " 'width: ' + width + 'px; height: ' + height + 'px;'\n",
+ " );\n",
+ "\n",
+ " rubberband_canvas.setAttribute('width', width);\n",
+ " rubberband_canvas.setAttribute('height', height);\n",
+ "\n",
+ " // And update the size in Python. We ignore the initial 0/0 size\n",
+ " // that occurs as the element is placed into the DOM, which should\n",
+ " // otherwise not happen due to the minimum size styling.\n",
+ " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n",
+ " fig.request_resize(width, height);\n",
+ " }\n",
+ " }\n",
+ " });\n",
+ " this.resizeObserverInstance.observe(canvas_div);\n",
+ "\n",
+ " function on_mouse_event_closure(name) {\n",
+ " return function (event) {\n",
+ " return fig.mouse_event(event, name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mousedown',\n",
+ " on_mouse_event_closure('button_press')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseup',\n",
+ " on_mouse_event_closure('button_release')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'dblclick',\n",
+ " on_mouse_event_closure('dblclick')\n",
+ " );\n",
+ " // Throttle sequential mouse events to 1 every 20ms.\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mousemove',\n",
+ " on_mouse_event_closure('motion_notify')\n",
+ " );\n",
+ "\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseenter',\n",
+ " on_mouse_event_closure('figure_enter')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseleave',\n",
+ " on_mouse_event_closure('figure_leave')\n",
+ " );\n",
+ "\n",
+ " canvas_div.addEventListener('wheel', function (event) {\n",
+ " if (event.deltaY < 0) {\n",
+ " event.step = 1;\n",
+ " } else {\n",
+ " event.step = -1;\n",
+ " }\n",
+ " on_mouse_event_closure('scroll')(event);\n",
+ " });\n",
+ "\n",
+ " canvas_div.appendChild(canvas);\n",
+ " canvas_div.appendChild(rubberband_canvas);\n",
+ "\n",
+ " this.rubberband_context = rubberband_canvas.getContext('2d');\n",
+ " this.rubberband_context.strokeStyle = '#000000';\n",
+ "\n",
+ " this._resize_canvas = function (width, height, forward) {\n",
+ " if (forward) {\n",
+ " canvas_div.style.width = width + 'px';\n",
+ " canvas_div.style.height = height + 'px';\n",
+ " }\n",
+ " };\n",
+ "\n",
+ " // Disable right mouse context menu.\n",
+ " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n",
+ " event.preventDefault();\n",
+ " return false;\n",
+ " });\n",
+ "\n",
+ " function set_focus() {\n",
+ " canvas.focus();\n",
+ " canvas_div.focus();\n",
+ " }\n",
+ "\n",
+ " window.setTimeout(set_focus, 100);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_toolbar = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var toolbar = document.createElement('div');\n",
+ " toolbar.classList = 'mpl-toolbar';\n",
+ " this.root.appendChild(toolbar);\n",
+ "\n",
+ " function on_click_closure(name) {\n",
+ " return function (_event) {\n",
+ " return fig.toolbar_button_onclick(name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " function on_mouseover_closure(tooltip) {\n",
+ " return function (event) {\n",
+ " if (!event.currentTarget.disabled) {\n",
+ " return fig.toolbar_button_onmouseover(tooltip);\n",
+ " }\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " fig.buttons = {};\n",
+ " var buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'mpl-button-group';\n",
+ " for (var toolbar_ind in mpl.toolbar_items) {\n",
+ " var name = mpl.toolbar_items[toolbar_ind][0];\n",
+ " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+ " var image = mpl.toolbar_items[toolbar_ind][2];\n",
+ " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+ "\n",
+ " if (!name) {\n",
+ " /* Instead of a spacer, we start a new button group. */\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ " buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'mpl-button-group';\n",
+ " continue;\n",
+ " }\n",
+ "\n",
+ " var button = (fig.buttons[name] = document.createElement('button'));\n",
+ " button.classList = 'mpl-widget';\n",
+ " button.setAttribute('role', 'button');\n",
+ " button.setAttribute('aria-disabled', 'false');\n",
+ " button.addEventListener('click', on_click_closure(method_name));\n",
+ " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+ "\n",
+ " var icon_img = document.createElement('img');\n",
+ " icon_img.src = '_images/' + image + '.png';\n",
+ " icon_img.srcset = '_images/' + image + '_large.png 2x';\n",
+ " icon_img.alt = tooltip;\n",
+ " button.appendChild(icon_img);\n",
+ "\n",
+ " buttonGroup.appendChild(button);\n",
+ " }\n",
+ "\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ "\n",
+ " var fmt_picker = document.createElement('select');\n",
+ " fmt_picker.classList = 'mpl-widget';\n",
+ " toolbar.appendChild(fmt_picker);\n",
+ " this.format_dropdown = fmt_picker;\n",
+ "\n",
+ " for (var ind in mpl.extensions) {\n",
+ " var fmt = mpl.extensions[ind];\n",
+ " var option = document.createElement('option');\n",
+ " option.selected = fmt === mpl.default_extension;\n",
+ " option.innerHTML = fmt;\n",
+ " fmt_picker.appendChild(option);\n",
+ " }\n",
+ "\n",
+ " var status_bar = document.createElement('span');\n",
+ " status_bar.classList = 'mpl-message';\n",
+ " toolbar.appendChild(status_bar);\n",
+ " this.message = status_bar;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n",
+ " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
+ " // which will in turn request a refresh of the image.\n",
+ " this.send_message('resize', { width: x_pixels, height: y_pixels });\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.send_message = function (type, properties) {\n",
+ " properties['type'] = type;\n",
+ " properties['figure_id'] = this.id;\n",
+ " this.ws.send(JSON.stringify(properties));\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.send_draw_message = function () {\n",
+ " if (!this.waiting) {\n",
+ " this.waiting = true;\n",
+ " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+ " var format_dropdown = fig.format_dropdown;\n",
+ " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
+ " fig.ondownload(fig, format);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_resize = function (fig, msg) {\n",
+ " var size = msg['size'];\n",
+ " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n",
+ " fig._resize_canvas(size[0], size[1], msg['forward']);\n",
+ " fig.send_message('refresh', {});\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n",
+ " var x0 = msg['x0'] / fig.ratio;\n",
+ " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n",
+ " var x1 = msg['x1'] / fig.ratio;\n",
+ " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n",
+ " x0 = Math.floor(x0) + 0.5;\n",
+ " y0 = Math.floor(y0) + 0.5;\n",
+ " x1 = Math.floor(x1) + 0.5;\n",
+ " y1 = Math.floor(y1) + 0.5;\n",
+ " var min_x = Math.min(x0, x1);\n",
+ " var min_y = Math.min(y0, y1);\n",
+ " var width = Math.abs(x1 - x0);\n",
+ " var height = Math.abs(y1 - y0);\n",
+ "\n",
+ " fig.rubberband_context.clearRect(\n",
+ " 0,\n",
+ " 0,\n",
+ " fig.canvas.width / fig.ratio,\n",
+ " fig.canvas.height / fig.ratio\n",
+ " );\n",
+ "\n",
+ " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n",
+ " // Updates the figure title.\n",
+ " fig.header.textContent = msg['label'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n",
+ " fig.rubberband_canvas.style.cursor = msg['cursor'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_message = function (fig, msg) {\n",
+ " fig.message.textContent = msg['message'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n",
+ " // Request the server to send over a new figure.\n",
+ " fig.send_draw_message();\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n",
+ " fig.image_mode = msg['mode'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n",
+ " for (var key in msg) {\n",
+ " if (!(key in fig.buttons)) {\n",
+ " continue;\n",
+ " }\n",
+ " fig.buttons[key].disabled = !msg[key];\n",
+ " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n",
+ " if (msg['mode'] === 'PAN') {\n",
+ " fig.buttons['Pan'].classList.add('active');\n",
+ " fig.buttons['Zoom'].classList.remove('active');\n",
+ " } else if (msg['mode'] === 'ZOOM') {\n",
+ " fig.buttons['Pan'].classList.remove('active');\n",
+ " fig.buttons['Zoom'].classList.add('active');\n",
+ " } else {\n",
+ " fig.buttons['Pan'].classList.remove('active');\n",
+ " fig.buttons['Zoom'].classList.remove('active');\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.updated_canvas_event = function () {\n",
+ " // Called whenever the canvas gets updated.\n",
+ " this.send_message('ack', {});\n",
+ "};\n",
+ "\n",
+ "// A function to construct a web socket function for onmessage handling.\n",
+ "// Called in the figure constructor.\n",
+ "mpl.figure.prototype._make_on_message_function = function (fig) {\n",
+ " return function socket_on_message(evt) {\n",
+ " if (evt.data instanceof Blob) {\n",
+ " var img = evt.data;\n",
+ " if (img.type !== 'image/png') {\n",
+ " /* FIXME: We get \"Resource interpreted as Image but\n",
+ " * transferred with MIME type text/plain:\" errors on\n",
+ " * Chrome. But how to set the MIME type? It doesn't seem\n",
+ " * to be part of the websocket stream */\n",
+ " img.type = 'image/png';\n",
+ " }\n",
+ "\n",
+ " /* Free the memory for the previous frames */\n",
+ " if (fig.imageObj.src) {\n",
+ " (window.URL || window.webkitURL).revokeObjectURL(\n",
+ " fig.imageObj.src\n",
+ " );\n",
+ " }\n",
+ "\n",
+ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
+ " img\n",
+ " );\n",
+ " fig.updated_canvas_event();\n",
+ " fig.waiting = false;\n",
+ " return;\n",
+ " } else if (\n",
+ " typeof evt.data === 'string' &&\n",
+ " evt.data.slice(0, 21) === 'data:image/png;base64'\n",
+ " ) {\n",
+ " fig.imageObj.src = evt.data;\n",
+ " fig.updated_canvas_event();\n",
+ " fig.waiting = false;\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " var msg = JSON.parse(evt.data);\n",
+ " var msg_type = msg['type'];\n",
+ "\n",
+ " // Call the \"handle_{type}\" callback, which takes\n",
+ " // the figure and JSON message as its only arguments.\n",
+ " try {\n",
+ " var callback = fig['handle_' + msg_type];\n",
+ " } catch (e) {\n",
+ " console.log(\n",
+ " \"No handler for the '\" + msg_type + \"' message type: \",\n",
+ " msg\n",
+ " );\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " if (callback) {\n",
+ " try {\n",
+ " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
+ " callback(fig, msg);\n",
+ " } catch (e) {\n",
+ " console.log(\n",
+ " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n",
+ " e,\n",
+ " e.stack,\n",
+ " msg\n",
+ " );\n",
+ " }\n",
+ " }\n",
+ " };\n",
+ "};\n",
+ "\n",
+ "// from https://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
+ "mpl.findpos = function (e) {\n",
+ " //this section is from http://www.quirksmode.org/js/events_properties.html\n",
+ " var targ;\n",
+ " if (!e) {\n",
+ " e = window.event;\n",
+ " }\n",
+ " if (e.target) {\n",
+ " targ = e.target;\n",
+ " } else if (e.srcElement) {\n",
+ " targ = e.srcElement;\n",
+ " }\n",
+ " if (targ.nodeType === 3) {\n",
+ " // defeat Safari bug\n",
+ " targ = targ.parentNode;\n",
+ " }\n",
+ "\n",
+ " // pageX,Y are the mouse positions relative to the document\n",
+ " var boundingRect = targ.getBoundingClientRect();\n",
+ " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n",
+ " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n",
+ "\n",
+ " return { x: x, y: y };\n",
+ "};\n",
+ "\n",
+ "/*\n",
+ " * return a copy of an object with only non-object keys\n",
+ " * we need this to avoid circular references\n",
+ " * https://stackoverflow.com/a/24161582/3208463\n",
+ " */\n",
+ "function simpleKeys(original) {\n",
+ " return Object.keys(original).reduce(function (obj, key) {\n",
+ " if (typeof original[key] !== 'object') {\n",
+ " obj[key] = original[key];\n",
+ " }\n",
+ " return obj;\n",
+ " }, {});\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.mouse_event = function (event, name) {\n",
+ " var canvas_pos = mpl.findpos(event);\n",
+ "\n",
+ " if (name === 'button_press') {\n",
+ " this.canvas.focus();\n",
+ " this.canvas_div.focus();\n",
+ " }\n",
+ "\n",
+ " var x = canvas_pos.x * this.ratio;\n",
+ " var y = canvas_pos.y * this.ratio;\n",
+ "\n",
+ " this.send_message(name, {\n",
+ " x: x,\n",
+ " y: y,\n",
+ " button: event.button,\n",
+ " step: event.step,\n",
+ " guiEvent: simpleKeys(event),\n",
+ " });\n",
+ "\n",
+ " /* This prevents the web browser from automatically changing to\n",
+ " * the text insertion cursor when the button is pressed. We want\n",
+ " * to control all of the cursor setting manually through the\n",
+ " * 'cursor' event from matplotlib */\n",
+ " event.preventDefault();\n",
+ " return false;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n",
+ " // Handle any extra behaviour associated with a key event\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.key_event = function (event, name) {\n",
+ " // Prevent repeat events\n",
+ " if (name === 'key_press') {\n",
+ " if (event.key === this._key) {\n",
+ " return;\n",
+ " } else {\n",
+ " this._key = event.key;\n",
+ " }\n",
+ " }\n",
+ " if (name === 'key_release') {\n",
+ " this._key = null;\n",
+ " }\n",
+ "\n",
+ " var value = '';\n",
+ " if (event.ctrlKey && event.key !== 'Control') {\n",
+ " value += 'ctrl+';\n",
+ " }\n",
+ " else if (event.altKey && event.key !== 'Alt') {\n",
+ " value += 'alt+';\n",
+ " }\n",
+ " else if (event.shiftKey && event.key !== 'Shift') {\n",
+ " value += 'shift+';\n",
+ " }\n",
+ "\n",
+ " value += 'k' + event.key;\n",
+ "\n",
+ " this._key_event_extra(event, name);\n",
+ "\n",
+ " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n",
+ " return false;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n",
+ " if (name === 'download') {\n",
+ " this.handle_save(this, null);\n",
+ " } else {\n",
+ " this.send_message('toolbar_button', { name: name });\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n",
+ " this.message.textContent = tooltip;\n",
+ "};\n",
+ "\n",
+ "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n",
+ "// prettier-ignore\n",
+ "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n",
+ "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
+ "\n",
+ "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
+ "\n",
+ "mpl.default_extension = \"png\";/* global mpl */\n",
+ "\n",
+ "var comm_websocket_adapter = function (comm) {\n",
+ " // Create a \"websocket\"-like object which calls the given IPython comm\n",
+ " // object with the appropriate methods. Currently this is a non binary\n",
+ " // socket, so there is still some room for performance tuning.\n",
+ " var ws = {};\n",
+ "\n",
+ " ws.binaryType = comm.kernel.ws.binaryType;\n",
+ " ws.readyState = comm.kernel.ws.readyState;\n",
+ " function updateReadyState(_event) {\n",
+ " if (comm.kernel.ws) {\n",
+ " ws.readyState = comm.kernel.ws.readyState;\n",
+ " } else {\n",
+ " ws.readyState = 3; // Closed state.\n",
+ " }\n",
+ " }\n",
+ " comm.kernel.ws.addEventListener('open', updateReadyState);\n",
+ " comm.kernel.ws.addEventListener('close', updateReadyState);\n",
+ " comm.kernel.ws.addEventListener('error', updateReadyState);\n",
+ "\n",
+ " ws.close = function () {\n",
+ " comm.close();\n",
+ " };\n",
+ " ws.send = function (m) {\n",
+ " //console.log('sending', m);\n",
+ " comm.send(m);\n",
+ " };\n",
+ " // Register the callback with on_msg.\n",
+ " comm.on_msg(function (msg) {\n",
+ " //console.log('receiving', msg['content']['data'], msg);\n",
+ " var data = msg['content']['data'];\n",
+ " if (data['blob'] !== undefined) {\n",
+ " data = {\n",
+ " data: new Blob(msg['buffers'], { type: data['blob'] }),\n",
+ " };\n",
+ " }\n",
+ " // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
+ " ws.onmessage(data);\n",
+ " });\n",
+ " return ws;\n",
+ "};\n",
+ "\n",
+ "mpl.mpl_figure_comm = function (comm, msg) {\n",
+ " // This is the function which gets called when the mpl process\n",
+ " // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
+ "\n",
+ " var id = msg.content.data.id;\n",
+ " // Get hold of the div created by the display call when the Comm\n",
+ " // socket was opened in Python.\n",
+ " var element = document.getElementById(id);\n",
+ " var ws_proxy = comm_websocket_adapter(comm);\n",
+ "\n",
+ " function ondownload(figure, _format) {\n",
+ " window.open(figure.canvas.toDataURL());\n",
+ " }\n",
+ "\n",
+ " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n",
+ "\n",
+ " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
+ " // web socket which is closed, not our websocket->open comm proxy.\n",
+ " ws_proxy.onopen();\n",
+ "\n",
+ " fig.parent_element = element;\n",
+ " fig.cell_info = mpl.find_output_cell(\"\");\n",
+ " if (!fig.cell_info) {\n",
+ " console.error('Failed to find cell for figure', id, fig);\n",
+ " return;\n",
+ " }\n",
+ " fig.cell_info[0].output_area.element.on(\n",
+ " 'cleared',\n",
+ " { fig: fig },\n",
+ " fig._remove_fig_handler\n",
+ " );\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_close = function (fig, msg) {\n",
+ " var width = fig.canvas.width / fig.ratio;\n",
+ " fig.cell_info[0].output_area.element.off(\n",
+ " 'cleared',\n",
+ " fig._remove_fig_handler\n",
+ " );\n",
+ " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n",
+ "\n",
+ " // Update the output cell to use the data from the current canvas.\n",
+ " fig.push_to_output();\n",
+ " var dataURL = fig.canvas.toDataURL();\n",
+ " // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
+ " // the notebook keyboard shortcuts fail.\n",
+ " IPython.keyboard_manager.enable();\n",
+ " fig.parent_element.innerHTML =\n",
+ " '
';\n",
+ " fig.close_ws(fig, msg);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.close_ws = function (fig, msg) {\n",
+ " fig.send_message('closing', msg);\n",
+ " // fig.ws.close()\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n",
+ " // Turn the data on the canvas into data in the output cell.\n",
+ " var width = this.canvas.width / this.ratio;\n",
+ " var dataURL = this.canvas.toDataURL();\n",
+ " this.cell_info[1]['text/html'] =\n",
+ " '
';\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.updated_canvas_event = function () {\n",
+ " // Tell IPython that the notebook contents must change.\n",
+ " IPython.notebook.set_dirty(true);\n",
+ " this.send_message('ack', {});\n",
+ " var fig = this;\n",
+ " // Wait a second, then push the new image to the DOM so\n",
+ " // that it is saved nicely (might be nice to debounce this).\n",
+ " setTimeout(function () {\n",
+ " fig.push_to_output();\n",
+ " }, 1000);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_toolbar = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var toolbar = document.createElement('div');\n",
+ " toolbar.classList = 'btn-toolbar';\n",
+ " this.root.appendChild(toolbar);\n",
+ "\n",
+ " function on_click_closure(name) {\n",
+ " return function (_event) {\n",
+ " return fig.toolbar_button_onclick(name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " function on_mouseover_closure(tooltip) {\n",
+ " return function (event) {\n",
+ " if (!event.currentTarget.disabled) {\n",
+ " return fig.toolbar_button_onmouseover(tooltip);\n",
+ " }\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " fig.buttons = {};\n",
+ " var buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'btn-group';\n",
+ " var button;\n",
+ " for (var toolbar_ind in mpl.toolbar_items) {\n",
+ " var name = mpl.toolbar_items[toolbar_ind][0];\n",
+ " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+ " var image = mpl.toolbar_items[toolbar_ind][2];\n",
+ " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+ "\n",
+ " if (!name) {\n",
+ " /* Instead of a spacer, we start a new button group. */\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ " buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'btn-group';\n",
+ " continue;\n",
+ " }\n",
+ "\n",
+ " button = fig.buttons[name] = document.createElement('button');\n",
+ " button.classList = 'btn btn-default';\n",
+ " button.href = '#';\n",
+ " button.title = name;\n",
+ " button.innerHTML = '';\n",
+ " button.addEventListener('click', on_click_closure(method_name));\n",
+ " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+ " buttonGroup.appendChild(button);\n",
+ " }\n",
+ "\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ "\n",
+ " // Add the status bar.\n",
+ " var status_bar = document.createElement('span');\n",
+ " status_bar.classList = 'mpl-message pull-right';\n",
+ " toolbar.appendChild(status_bar);\n",
+ " this.message = status_bar;\n",
+ "\n",
+ " // Add the close button to the window.\n",
+ " var buttongrp = document.createElement('div');\n",
+ " buttongrp.classList = 'btn-group inline pull-right';\n",
+ " button = document.createElement('button');\n",
+ " button.classList = 'btn btn-mini btn-primary';\n",
+ " button.href = '#';\n",
+ " button.title = 'Stop Interaction';\n",
+ " button.innerHTML = '';\n",
+ " button.addEventListener('click', function (_evt) {\n",
+ " fig.handle_close(fig, {});\n",
+ " });\n",
+ " button.addEventListener(\n",
+ " 'mouseover',\n",
+ " on_mouseover_closure('Stop Interaction')\n",
+ " );\n",
+ " buttongrp.appendChild(button);\n",
+ " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n",
+ " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._remove_fig_handler = function (event) {\n",
+ " var fig = event.data.fig;\n",
+ " if (event.target !== this) {\n",
+ " // Ignore bubbled events from children.\n",
+ " return;\n",
+ " }\n",
+ " fig.close_ws(fig, {});\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._root_extra_style = function (el) {\n",
+ " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._canvas_extra_style = function (el) {\n",
+ " // this is important to make the div 'focusable\n",
+ " el.setAttribute('tabindex', 0);\n",
+ " // reach out to IPython and tell the keyboard manager to turn it's self\n",
+ " // off when our div gets focus\n",
+ "\n",
+ " // location in version 3\n",
+ " if (IPython.notebook.keyboard_manager) {\n",
+ " IPython.notebook.keyboard_manager.register_events(el);\n",
+ " } else {\n",
+ " // location in version 2\n",
+ " IPython.keyboard_manager.register_events(el);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._key_event_extra = function (event, _name) {\n",
+ " // Check for shift+enter\n",
+ " if (event.shiftKey && event.which === 13) {\n",
+ " this.canvas_div.blur();\n",
+ " // select the cell after this one\n",
+ " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n",
+ " IPython.notebook.select(index + 1);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+ " fig.ondownload(fig, null);\n",
+ "};\n",
+ "\n",
+ "mpl.find_output_cell = function (html_output) {\n",
+ " // Return the cell and output element which can be found *uniquely* in the notebook.\n",
+ " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
+ " // IPython event is triggered only after the cells have been serialised, which for\n",
+ " // our purposes (turning an active figure into a static one), is too late.\n",
+ " var cells = IPython.notebook.get_cells();\n",
+ " var ncells = cells.length;\n",
+ " for (var i = 0; i < ncells; i++) {\n",
+ " var cell = cells[i];\n",
+ " if (cell.cell_type === 'code') {\n",
+ " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n",
+ " var data = cell.output_area.outputs[j];\n",
+ " if (data.data) {\n",
+ " // IPython >= 3 moved mimebundle to data attribute of output\n",
+ " data = data.data;\n",
+ " }\n",
+ " if (data['text/html'] === html_output) {\n",
+ " return [cell, data, j];\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "// Register the function which deals with the matplotlib target/channel.\n",
+ "// The kernel may be null if the page has been refreshed.\n",
+ "if (IPython.notebook.kernel !== null) {\n",
+ " IPython.notebook.kernel.comm_manager.register_target(\n",
+ " 'matplotlib',\n",
+ " mpl.mpl_figure_comm\n",
+ " );\n",
+ "}\n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "
"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " mean std num\n",
+ "1x0 irods_via_davrods_on_isilon 45.970737 18.748706 18\n",
+ " irods_via_davrods_on_netapp 39.422446 0.920041 10\n",
+ " irods_via_davrods_on_s3 38.771205 4.741259 19\n",
+ "1x1K irods_via_davrods_on_isilon 100.057903 1.046998 19\n",
+ " irods_via_davrods_on_netapp 100.206922 0.858756 9\n",
+ " irods_via_davrods_on_s3 59.780832 5.975672 19\n",
+ "10x1K irods_via_davrods_on_isilon 100.749137 2.349299 18\n",
+ " irods_via_davrods_on_netapp 100.508246 1.126574 10\n",
+ " irods_via_davrods_on_s3 72.207316 5.164946 19\n",
+ "100x1K irods_via_davrods_on_isilon 100.584359 1.371663 20\n",
+ " irods_via_davrods_on_netapp 100.001887 0.612863 11\n",
+ " irods_via_davrods_on_s3 151.816277 6.997022 19\n",
+ "1000x1K irods_via_davrods_on_isilon 339.142480 9.159978 18\n",
+ " irods_via_davrods_on_netapp 337.333978 5.722026 10\n",
+ " irods_via_davrods_on_s3 890.672408 32.177313 19\n",
+ "2000x1K irods_via_davrods_on_isilon 617.350109 17.696956 19\n",
+ " irods_via_davrods_on_netapp 616.484709 14.031568 10\n",
+ " irods_via_davrods_on_s3 1714.515446 65.622139 19\n"
+ ]
+ },
+ {
+ "data": {
+ "application/javascript": [
+ "/* Put everything inside the global mpl namespace */\n",
+ "/* global mpl */\n",
+ "window.mpl = {};\n",
+ "\n",
+ "mpl.get_websocket_type = function () {\n",
+ " if (typeof WebSocket !== 'undefined') {\n",
+ " return WebSocket;\n",
+ " } else if (typeof MozWebSocket !== 'undefined') {\n",
+ " return MozWebSocket;\n",
+ " } else {\n",
+ " alert(\n",
+ " 'Your browser does not have WebSocket support. ' +\n",
+ " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
+ " 'Firefox 4 and 5 are also supported but you ' +\n",
+ " 'have to enable WebSockets in about:config.'\n",
+ " );\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n",
+ " this.id = figure_id;\n",
+ "\n",
+ " this.ws = websocket;\n",
+ "\n",
+ " this.supports_binary = this.ws.binaryType !== undefined;\n",
+ "\n",
+ " if (!this.supports_binary) {\n",
+ " var warnings = document.getElementById('mpl-warnings');\n",
+ " if (warnings) {\n",
+ " warnings.style.display = 'block';\n",
+ " warnings.textContent =\n",
+ " 'This browser does not support binary websocket messages. ' +\n",
+ " 'Performance may be slow.';\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " this.imageObj = new Image();\n",
+ "\n",
+ " this.context = undefined;\n",
+ " this.message = undefined;\n",
+ " this.canvas = undefined;\n",
+ " this.rubberband_canvas = undefined;\n",
+ " this.rubberband_context = undefined;\n",
+ " this.format_dropdown = undefined;\n",
+ "\n",
+ " this.image_mode = 'full';\n",
+ "\n",
+ " this.root = document.createElement('div');\n",
+ " this.root.setAttribute('style', 'display: inline-block');\n",
+ " this._root_extra_style(this.root);\n",
+ "\n",
+ " parent_element.appendChild(this.root);\n",
+ "\n",
+ " this._init_header(this);\n",
+ " this._init_canvas(this);\n",
+ " this._init_toolbar(this);\n",
+ "\n",
+ " var fig = this;\n",
+ "\n",
+ " this.waiting = false;\n",
+ "\n",
+ " this.ws.onopen = function () {\n",
+ " fig.send_message('supports_binary', { value: fig.supports_binary });\n",
+ " fig.send_message('send_image_mode', {});\n",
+ " if (fig.ratio !== 1) {\n",
+ " fig.send_message('set_device_pixel_ratio', {\n",
+ " device_pixel_ratio: fig.ratio,\n",
+ " });\n",
+ " }\n",
+ " fig.send_message('refresh', {});\n",
+ " };\n",
+ "\n",
+ " this.imageObj.onload = function () {\n",
+ " if (fig.image_mode === 'full') {\n",
+ " // Full images could contain transparency (where diff images\n",
+ " // almost always do), so we need to clear the canvas so that\n",
+ " // there is no ghosting.\n",
+ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
+ " }\n",
+ " fig.context.drawImage(fig.imageObj, 0, 0);\n",
+ " };\n",
+ "\n",
+ " this.imageObj.onunload = function () {\n",
+ " fig.ws.close();\n",
+ " };\n",
+ "\n",
+ " this.ws.onmessage = this._make_on_message_function(this);\n",
+ "\n",
+ " this.ondownload = ondownload;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_header = function () {\n",
+ " var titlebar = document.createElement('div');\n",
+ " titlebar.classList =\n",
+ " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n",
+ " var titletext = document.createElement('div');\n",
+ " titletext.classList = 'ui-dialog-title';\n",
+ " titletext.setAttribute(\n",
+ " 'style',\n",
+ " 'width: 100%; text-align: center; padding: 3px;'\n",
+ " );\n",
+ " titlebar.appendChild(titletext);\n",
+ " this.root.appendChild(titlebar);\n",
+ " this.header = titletext;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n",
+ "\n",
+ "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n",
+ "\n",
+ "mpl.figure.prototype._init_canvas = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var canvas_div = (this.canvas_div = document.createElement('div'));\n",
+ " canvas_div.setAttribute(\n",
+ " 'style',\n",
+ " 'border: 1px solid #ddd;' +\n",
+ " 'box-sizing: content-box;' +\n",
+ " 'clear: both;' +\n",
+ " 'min-height: 1px;' +\n",
+ " 'min-width: 1px;' +\n",
+ " 'outline: 0;' +\n",
+ " 'overflow: hidden;' +\n",
+ " 'position: relative;' +\n",
+ " 'resize: both;'\n",
+ " );\n",
+ "\n",
+ " function on_keyboard_event_closure(name) {\n",
+ " return function (event) {\n",
+ " return fig.key_event(event, name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " canvas_div.addEventListener(\n",
+ " 'keydown',\n",
+ " on_keyboard_event_closure('key_press')\n",
+ " );\n",
+ " canvas_div.addEventListener(\n",
+ " 'keyup',\n",
+ " on_keyboard_event_closure('key_release')\n",
+ " );\n",
+ "\n",
+ " this._canvas_extra_style(canvas_div);\n",
+ " this.root.appendChild(canvas_div);\n",
+ "\n",
+ " var canvas = (this.canvas = document.createElement('canvas'));\n",
+ " canvas.classList.add('mpl-canvas');\n",
+ " canvas.setAttribute('style', 'box-sizing: content-box;');\n",
+ "\n",
+ " this.context = canvas.getContext('2d');\n",
+ "\n",
+ " var backingStore =\n",
+ " this.context.backingStorePixelRatio ||\n",
+ " this.context.webkitBackingStorePixelRatio ||\n",
+ " this.context.mozBackingStorePixelRatio ||\n",
+ " this.context.msBackingStorePixelRatio ||\n",
+ " this.context.oBackingStorePixelRatio ||\n",
+ " this.context.backingStorePixelRatio ||\n",
+ " 1;\n",
+ "\n",
+ " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
+ "\n",
+ " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n",
+ " 'canvas'\n",
+ " ));\n",
+ " rubberband_canvas.setAttribute(\n",
+ " 'style',\n",
+ " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n",
+ " );\n",
+ "\n",
+ " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n",
+ " if (this.ResizeObserver === undefined) {\n",
+ " if (window.ResizeObserver !== undefined) {\n",
+ " this.ResizeObserver = window.ResizeObserver;\n",
+ " } else {\n",
+ " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n",
+ " this.ResizeObserver = obs.ResizeObserver;\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n",
+ " var nentries = entries.length;\n",
+ " for (var i = 0; i < nentries; i++) {\n",
+ " var entry = entries[i];\n",
+ " var width, height;\n",
+ " if (entry.contentBoxSize) {\n",
+ " if (entry.contentBoxSize instanceof Array) {\n",
+ " // Chrome 84 implements new version of spec.\n",
+ " width = entry.contentBoxSize[0].inlineSize;\n",
+ " height = entry.contentBoxSize[0].blockSize;\n",
+ " } else {\n",
+ " // Firefox implements old version of spec.\n",
+ " width = entry.contentBoxSize.inlineSize;\n",
+ " height = entry.contentBoxSize.blockSize;\n",
+ " }\n",
+ " } else {\n",
+ " // Chrome <84 implements even older version of spec.\n",
+ " width = entry.contentRect.width;\n",
+ " height = entry.contentRect.height;\n",
+ " }\n",
+ "\n",
+ " // Keep the size of the canvas and rubber band canvas in sync with\n",
+ " // the canvas container.\n",
+ " if (entry.devicePixelContentBoxSize) {\n",
+ " // Chrome 84 implements new version of spec.\n",
+ " canvas.setAttribute(\n",
+ " 'width',\n",
+ " entry.devicePixelContentBoxSize[0].inlineSize\n",
+ " );\n",
+ " canvas.setAttribute(\n",
+ " 'height',\n",
+ " entry.devicePixelContentBoxSize[0].blockSize\n",
+ " );\n",
+ " } else {\n",
+ " canvas.setAttribute('width', width * fig.ratio);\n",
+ " canvas.setAttribute('height', height * fig.ratio);\n",
+ " }\n",
+ " canvas.setAttribute(\n",
+ " 'style',\n",
+ " 'width: ' + width + 'px; height: ' + height + 'px;'\n",
+ " );\n",
+ "\n",
+ " rubberband_canvas.setAttribute('width', width);\n",
+ " rubberband_canvas.setAttribute('height', height);\n",
+ "\n",
+ " // And update the size in Python. We ignore the initial 0/0 size\n",
+ " // that occurs as the element is placed into the DOM, which should\n",
+ " // otherwise not happen due to the minimum size styling.\n",
+ " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n",
+ " fig.request_resize(width, height);\n",
+ " }\n",
+ " }\n",
+ " });\n",
+ " this.resizeObserverInstance.observe(canvas_div);\n",
+ "\n",
+ " function on_mouse_event_closure(name) {\n",
+ " return function (event) {\n",
+ " return fig.mouse_event(event, name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mousedown',\n",
+ " on_mouse_event_closure('button_press')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseup',\n",
+ " on_mouse_event_closure('button_release')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'dblclick',\n",
+ " on_mouse_event_closure('dblclick')\n",
+ " );\n",
+ " // Throttle sequential mouse events to 1 every 20ms.\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mousemove',\n",
+ " on_mouse_event_closure('motion_notify')\n",
+ " );\n",
+ "\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseenter',\n",
+ " on_mouse_event_closure('figure_enter')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseleave',\n",
+ " on_mouse_event_closure('figure_leave')\n",
+ " );\n",
+ "\n",
+ " canvas_div.addEventListener('wheel', function (event) {\n",
+ " if (event.deltaY < 0) {\n",
+ " event.step = 1;\n",
+ " } else {\n",
+ " event.step = -1;\n",
+ " }\n",
+ " on_mouse_event_closure('scroll')(event);\n",
+ " });\n",
+ "\n",
+ " canvas_div.appendChild(canvas);\n",
+ " canvas_div.appendChild(rubberband_canvas);\n",
+ "\n",
+ " this.rubberband_context = rubberband_canvas.getContext('2d');\n",
+ " this.rubberband_context.strokeStyle = '#000000';\n",
+ "\n",
+ " this._resize_canvas = function (width, height, forward) {\n",
+ " if (forward) {\n",
+ " canvas_div.style.width = width + 'px';\n",
+ " canvas_div.style.height = height + 'px';\n",
+ " }\n",
+ " };\n",
+ "\n",
+ " // Disable right mouse context menu.\n",
+ " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n",
+ " event.preventDefault();\n",
+ " return false;\n",
+ " });\n",
+ "\n",
+ " function set_focus() {\n",
+ " canvas.focus();\n",
+ " canvas_div.focus();\n",
+ " }\n",
+ "\n",
+ " window.setTimeout(set_focus, 100);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_toolbar = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var toolbar = document.createElement('div');\n",
+ " toolbar.classList = 'mpl-toolbar';\n",
+ " this.root.appendChild(toolbar);\n",
+ "\n",
+ " function on_click_closure(name) {\n",
+ " return function (_event) {\n",
+ " return fig.toolbar_button_onclick(name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " function on_mouseover_closure(tooltip) {\n",
+ " return function (event) {\n",
+ " if (!event.currentTarget.disabled) {\n",
+ " return fig.toolbar_button_onmouseover(tooltip);\n",
+ " }\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " fig.buttons = {};\n",
+ " var buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'mpl-button-group';\n",
+ " for (var toolbar_ind in mpl.toolbar_items) {\n",
+ " var name = mpl.toolbar_items[toolbar_ind][0];\n",
+ " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+ " var image = mpl.toolbar_items[toolbar_ind][2];\n",
+ " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+ "\n",
+ " if (!name) {\n",
+ " /* Instead of a spacer, we start a new button group. */\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ " buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'mpl-button-group';\n",
+ " continue;\n",
+ " }\n",
+ "\n",
+ " var button = (fig.buttons[name] = document.createElement('button'));\n",
+ " button.classList = 'mpl-widget';\n",
+ " button.setAttribute('role', 'button');\n",
+ " button.setAttribute('aria-disabled', 'false');\n",
+ " button.addEventListener('click', on_click_closure(method_name));\n",
+ " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+ "\n",
+ " var icon_img = document.createElement('img');\n",
+ " icon_img.src = '_images/' + image + '.png';\n",
+ " icon_img.srcset = '_images/' + image + '_large.png 2x';\n",
+ " icon_img.alt = tooltip;\n",
+ " button.appendChild(icon_img);\n",
+ "\n",
+ " buttonGroup.appendChild(button);\n",
+ " }\n",
+ "\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ "\n",
+ " var fmt_picker = document.createElement('select');\n",
+ " fmt_picker.classList = 'mpl-widget';\n",
+ " toolbar.appendChild(fmt_picker);\n",
+ " this.format_dropdown = fmt_picker;\n",
+ "\n",
+ " for (var ind in mpl.extensions) {\n",
+ " var fmt = mpl.extensions[ind];\n",
+ " var option = document.createElement('option');\n",
+ " option.selected = fmt === mpl.default_extension;\n",
+ " option.innerHTML = fmt;\n",
+ " fmt_picker.appendChild(option);\n",
+ " }\n",
+ "\n",
+ " var status_bar = document.createElement('span');\n",
+ " status_bar.classList = 'mpl-message';\n",
+ " toolbar.appendChild(status_bar);\n",
+ " this.message = status_bar;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n",
+ " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
+ " // which will in turn request a refresh of the image.\n",
+ " this.send_message('resize', { width: x_pixels, height: y_pixels });\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.send_message = function (type, properties) {\n",
+ " properties['type'] = type;\n",
+ " properties['figure_id'] = this.id;\n",
+ " this.ws.send(JSON.stringify(properties));\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.send_draw_message = function () {\n",
+ " if (!this.waiting) {\n",
+ " this.waiting = true;\n",
+ " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+ " var format_dropdown = fig.format_dropdown;\n",
+ " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
+ " fig.ondownload(fig, format);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_resize = function (fig, msg) {\n",
+ " var size = msg['size'];\n",
+ " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n",
+ " fig._resize_canvas(size[0], size[1], msg['forward']);\n",
+ " fig.send_message('refresh', {});\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n",
+ " var x0 = msg['x0'] / fig.ratio;\n",
+ " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n",
+ " var x1 = msg['x1'] / fig.ratio;\n",
+ " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n",
+ " x0 = Math.floor(x0) + 0.5;\n",
+ " y0 = Math.floor(y0) + 0.5;\n",
+ " x1 = Math.floor(x1) + 0.5;\n",
+ " y1 = Math.floor(y1) + 0.5;\n",
+ " var min_x = Math.min(x0, x1);\n",
+ " var min_y = Math.min(y0, y1);\n",
+ " var width = Math.abs(x1 - x0);\n",
+ " var height = Math.abs(y1 - y0);\n",
+ "\n",
+ " fig.rubberband_context.clearRect(\n",
+ " 0,\n",
+ " 0,\n",
+ " fig.canvas.width / fig.ratio,\n",
+ " fig.canvas.height / fig.ratio\n",
+ " );\n",
+ "\n",
+ " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n",
+ " // Updates the figure title.\n",
+ " fig.header.textContent = msg['label'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n",
+ " fig.rubberband_canvas.style.cursor = msg['cursor'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_message = function (fig, msg) {\n",
+ " fig.message.textContent = msg['message'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n",
+ " // Request the server to send over a new figure.\n",
+ " fig.send_draw_message();\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n",
+ " fig.image_mode = msg['mode'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n",
+ " for (var key in msg) {\n",
+ " if (!(key in fig.buttons)) {\n",
+ " continue;\n",
+ " }\n",
+ " fig.buttons[key].disabled = !msg[key];\n",
+ " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n",
+ " if (msg['mode'] === 'PAN') {\n",
+ " fig.buttons['Pan'].classList.add('active');\n",
+ " fig.buttons['Zoom'].classList.remove('active');\n",
+ " } else if (msg['mode'] === 'ZOOM') {\n",
+ " fig.buttons['Pan'].classList.remove('active');\n",
+ " fig.buttons['Zoom'].classList.add('active');\n",
+ " } else {\n",
+ " fig.buttons['Pan'].classList.remove('active');\n",
+ " fig.buttons['Zoom'].classList.remove('active');\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.updated_canvas_event = function () {\n",
+ " // Called whenever the canvas gets updated.\n",
+ " this.send_message('ack', {});\n",
+ "};\n",
+ "\n",
+ "// A function to construct a web socket function for onmessage handling.\n",
+ "// Called in the figure constructor.\n",
+ "mpl.figure.prototype._make_on_message_function = function (fig) {\n",
+ " return function socket_on_message(evt) {\n",
+ " if (evt.data instanceof Blob) {\n",
+ " var img = evt.data;\n",
+ " if (img.type !== 'image/png') {\n",
+ " /* FIXME: We get \"Resource interpreted as Image but\n",
+ " * transferred with MIME type text/plain:\" errors on\n",
+ " * Chrome. But how to set the MIME type? It doesn't seem\n",
+ " * to be part of the websocket stream */\n",
+ " img.type = 'image/png';\n",
+ " }\n",
+ "\n",
+ " /* Free the memory for the previous frames */\n",
+ " if (fig.imageObj.src) {\n",
+ " (window.URL || window.webkitURL).revokeObjectURL(\n",
+ " fig.imageObj.src\n",
+ " );\n",
+ " }\n",
+ "\n",
+ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
+ " img\n",
+ " );\n",
+ " fig.updated_canvas_event();\n",
+ " fig.waiting = false;\n",
+ " return;\n",
+ " } else if (\n",
+ " typeof evt.data === 'string' &&\n",
+ " evt.data.slice(0, 21) === 'data:image/png;base64'\n",
+ " ) {\n",
+ " fig.imageObj.src = evt.data;\n",
+ " fig.updated_canvas_event();\n",
+ " fig.waiting = false;\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " var msg = JSON.parse(evt.data);\n",
+ " var msg_type = msg['type'];\n",
+ "\n",
+ " // Call the \"handle_{type}\" callback, which takes\n",
+ " // the figure and JSON message as its only arguments.\n",
+ " try {\n",
+ " var callback = fig['handle_' + msg_type];\n",
+ " } catch (e) {\n",
+ " console.log(\n",
+ " \"No handler for the '\" + msg_type + \"' message type: \",\n",
+ " msg\n",
+ " );\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " if (callback) {\n",
+ " try {\n",
+ " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
+ " callback(fig, msg);\n",
+ " } catch (e) {\n",
+ " console.log(\n",
+ " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n",
+ " e,\n",
+ " e.stack,\n",
+ " msg\n",
+ " );\n",
+ " }\n",
+ " }\n",
+ " };\n",
+ "};\n",
+ "\n",
+ "// from https://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
+ "mpl.findpos = function (e) {\n",
+ " //this section is from http://www.quirksmode.org/js/events_properties.html\n",
+ " var targ;\n",
+ " if (!e) {\n",
+ " e = window.event;\n",
+ " }\n",
+ " if (e.target) {\n",
+ " targ = e.target;\n",
+ " } else if (e.srcElement) {\n",
+ " targ = e.srcElement;\n",
+ " }\n",
+ " if (targ.nodeType === 3) {\n",
+ " // defeat Safari bug\n",
+ " targ = targ.parentNode;\n",
+ " }\n",
+ "\n",
+ " // pageX,Y are the mouse positions relative to the document\n",
+ " var boundingRect = targ.getBoundingClientRect();\n",
+ " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n",
+ " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n",
+ "\n",
+ " return { x: x, y: y };\n",
+ "};\n",
+ "\n",
+ "/*\n",
+ " * return a copy of an object with only non-object keys\n",
+ " * we need this to avoid circular references\n",
+ " * https://stackoverflow.com/a/24161582/3208463\n",
+ " */\n",
+ "function simpleKeys(original) {\n",
+ " return Object.keys(original).reduce(function (obj, key) {\n",
+ " if (typeof original[key] !== 'object') {\n",
+ " obj[key] = original[key];\n",
+ " }\n",
+ " return obj;\n",
+ " }, {});\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.mouse_event = function (event, name) {\n",
+ " var canvas_pos = mpl.findpos(event);\n",
+ "\n",
+ " if (name === 'button_press') {\n",
+ " this.canvas.focus();\n",
+ " this.canvas_div.focus();\n",
+ " }\n",
+ "\n",
+ " var x = canvas_pos.x * this.ratio;\n",
+ " var y = canvas_pos.y * this.ratio;\n",
+ "\n",
+ " this.send_message(name, {\n",
+ " x: x,\n",
+ " y: y,\n",
+ " button: event.button,\n",
+ " step: event.step,\n",
+ " guiEvent: simpleKeys(event),\n",
+ " });\n",
+ "\n",
+ " /* This prevents the web browser from automatically changing to\n",
+ " * the text insertion cursor when the button is pressed. We want\n",
+ " * to control all of the cursor setting manually through the\n",
+ " * 'cursor' event from matplotlib */\n",
+ " event.preventDefault();\n",
+ " return false;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n",
+ " // Handle any extra behaviour associated with a key event\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.key_event = function (event, name) {\n",
+ " // Prevent repeat events\n",
+ " if (name === 'key_press') {\n",
+ " if (event.key === this._key) {\n",
+ " return;\n",
+ " } else {\n",
+ " this._key = event.key;\n",
+ " }\n",
+ " }\n",
+ " if (name === 'key_release') {\n",
+ " this._key = null;\n",
+ " }\n",
+ "\n",
+ " var value = '';\n",
+ " if (event.ctrlKey && event.key !== 'Control') {\n",
+ " value += 'ctrl+';\n",
+ " }\n",
+ " else if (event.altKey && event.key !== 'Alt') {\n",
+ " value += 'alt+';\n",
+ " }\n",
+ " else if (event.shiftKey && event.key !== 'Shift') {\n",
+ " value += 'shift+';\n",
+ " }\n",
+ "\n",
+ " value += 'k' + event.key;\n",
+ "\n",
+ " this._key_event_extra(event, name);\n",
+ "\n",
+ " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n",
+ " return false;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n",
+ " if (name === 'download') {\n",
+ " this.handle_save(this, null);\n",
+ " } else {\n",
+ " this.send_message('toolbar_button', { name: name });\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n",
+ " this.message.textContent = tooltip;\n",
+ "};\n",
+ "\n",
+ "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n",
+ "// prettier-ignore\n",
+ "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n",
+ "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
+ "\n",
+ "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
+ "\n",
+ "mpl.default_extension = \"png\";/* global mpl */\n",
+ "\n",
+ "var comm_websocket_adapter = function (comm) {\n",
+ " // Create a \"websocket\"-like object which calls the given IPython comm\n",
+ " // object with the appropriate methods. Currently this is a non binary\n",
+ " // socket, so there is still some room for performance tuning.\n",
+ " var ws = {};\n",
+ "\n",
+ " ws.binaryType = comm.kernel.ws.binaryType;\n",
+ " ws.readyState = comm.kernel.ws.readyState;\n",
+ " function updateReadyState(_event) {\n",
+ " if (comm.kernel.ws) {\n",
+ " ws.readyState = comm.kernel.ws.readyState;\n",
+ " } else {\n",
+ " ws.readyState = 3; // Closed state.\n",
+ " }\n",
+ " }\n",
+ " comm.kernel.ws.addEventListener('open', updateReadyState);\n",
+ " comm.kernel.ws.addEventListener('close', updateReadyState);\n",
+ " comm.kernel.ws.addEventListener('error', updateReadyState);\n",
+ "\n",
+ " ws.close = function () {\n",
+ " comm.close();\n",
+ " };\n",
+ " ws.send = function (m) {\n",
+ " //console.log('sending', m);\n",
+ " comm.send(m);\n",
+ " };\n",
+ " // Register the callback with on_msg.\n",
+ " comm.on_msg(function (msg) {\n",
+ " //console.log('receiving', msg['content']['data'], msg);\n",
+ " var data = msg['content']['data'];\n",
+ " if (data['blob'] !== undefined) {\n",
+ " data = {\n",
+ " data: new Blob(msg['buffers'], { type: data['blob'] }),\n",
+ " };\n",
+ " }\n",
+ " // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
+ " ws.onmessage(data);\n",
+ " });\n",
+ " return ws;\n",
+ "};\n",
+ "\n",
+ "mpl.mpl_figure_comm = function (comm, msg) {\n",
+ " // This is the function which gets called when the mpl process\n",
+ " // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
+ "\n",
+ " var id = msg.content.data.id;\n",
+ " // Get hold of the div created by the display call when the Comm\n",
+ " // socket was opened in Python.\n",
+ " var element = document.getElementById(id);\n",
+ " var ws_proxy = comm_websocket_adapter(comm);\n",
+ "\n",
+ " function ondownload(figure, _format) {\n",
+ " window.open(figure.canvas.toDataURL());\n",
+ " }\n",
+ "\n",
+ " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n",
+ "\n",
+ " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
+ " // web socket which is closed, not our websocket->open comm proxy.\n",
+ " ws_proxy.onopen();\n",
+ "\n",
+ " fig.parent_element = element;\n",
+ " fig.cell_info = mpl.find_output_cell(\"\");\n",
+ " if (!fig.cell_info) {\n",
+ " console.error('Failed to find cell for figure', id, fig);\n",
+ " return;\n",
+ " }\n",
+ " fig.cell_info[0].output_area.element.on(\n",
+ " 'cleared',\n",
+ " { fig: fig },\n",
+ " fig._remove_fig_handler\n",
+ " );\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_close = function (fig, msg) {\n",
+ " var width = fig.canvas.width / fig.ratio;\n",
+ " fig.cell_info[0].output_area.element.off(\n",
+ " 'cleared',\n",
+ " fig._remove_fig_handler\n",
+ " );\n",
+ " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n",
+ "\n",
+ " // Update the output cell to use the data from the current canvas.\n",
+ " fig.push_to_output();\n",
+ " var dataURL = fig.canvas.toDataURL();\n",
+ " // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
+ " // the notebook keyboard shortcuts fail.\n",
+ " IPython.keyboard_manager.enable();\n",
+ " fig.parent_element.innerHTML =\n",
+ " '
';\n",
+ " fig.close_ws(fig, msg);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.close_ws = function (fig, msg) {\n",
+ " fig.send_message('closing', msg);\n",
+ " // fig.ws.close()\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n",
+ " // Turn the data on the canvas into data in the output cell.\n",
+ " var width = this.canvas.width / this.ratio;\n",
+ " var dataURL = this.canvas.toDataURL();\n",
+ " this.cell_info[1]['text/html'] =\n",
+ " '
';\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.updated_canvas_event = function () {\n",
+ " // Tell IPython that the notebook contents must change.\n",
+ " IPython.notebook.set_dirty(true);\n",
+ " this.send_message('ack', {});\n",
+ " var fig = this;\n",
+ " // Wait a second, then push the new image to the DOM so\n",
+ " // that it is saved nicely (might be nice to debounce this).\n",
+ " setTimeout(function () {\n",
+ " fig.push_to_output();\n",
+ " }, 1000);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_toolbar = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var toolbar = document.createElement('div');\n",
+ " toolbar.classList = 'btn-toolbar';\n",
+ " this.root.appendChild(toolbar);\n",
+ "\n",
+ " function on_click_closure(name) {\n",
+ " return function (_event) {\n",
+ " return fig.toolbar_button_onclick(name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " function on_mouseover_closure(tooltip) {\n",
+ " return function (event) {\n",
+ " if (!event.currentTarget.disabled) {\n",
+ " return fig.toolbar_button_onmouseover(tooltip);\n",
+ " }\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " fig.buttons = {};\n",
+ " var buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'btn-group';\n",
+ " var button;\n",
+ " for (var toolbar_ind in mpl.toolbar_items) {\n",
+ " var name = mpl.toolbar_items[toolbar_ind][0];\n",
+ " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+ " var image = mpl.toolbar_items[toolbar_ind][2];\n",
+ " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+ "\n",
+ " if (!name) {\n",
+ " /* Instead of a spacer, we start a new button group. */\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ " buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'btn-group';\n",
+ " continue;\n",
+ " }\n",
+ "\n",
+ " button = fig.buttons[name] = document.createElement('button');\n",
+ " button.classList = 'btn btn-default';\n",
+ " button.href = '#';\n",
+ " button.title = name;\n",
+ " button.innerHTML = '';\n",
+ " button.addEventListener('click', on_click_closure(method_name));\n",
+ " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+ " buttonGroup.appendChild(button);\n",
+ " }\n",
+ "\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ "\n",
+ " // Add the status bar.\n",
+ " var status_bar = document.createElement('span');\n",
+ " status_bar.classList = 'mpl-message pull-right';\n",
+ " toolbar.appendChild(status_bar);\n",
+ " this.message = status_bar;\n",
+ "\n",
+ " // Add the close button to the window.\n",
+ " var buttongrp = document.createElement('div');\n",
+ " buttongrp.classList = 'btn-group inline pull-right';\n",
+ " button = document.createElement('button');\n",
+ " button.classList = 'btn btn-mini btn-primary';\n",
+ " button.href = '#';\n",
+ " button.title = 'Stop Interaction';\n",
+ " button.innerHTML = '';\n",
+ " button.addEventListener('click', function (_evt) {\n",
+ " fig.handle_close(fig, {});\n",
+ " });\n",
+ " button.addEventListener(\n",
+ " 'mouseover',\n",
+ " on_mouseover_closure('Stop Interaction')\n",
+ " );\n",
+ " buttongrp.appendChild(button);\n",
+ " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n",
+ " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._remove_fig_handler = function (event) {\n",
+ " var fig = event.data.fig;\n",
+ " if (event.target !== this) {\n",
+ " // Ignore bubbled events from children.\n",
+ " return;\n",
+ " }\n",
+ " fig.close_ws(fig, {});\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._root_extra_style = function (el) {\n",
+ " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._canvas_extra_style = function (el) {\n",
+ " // this is important to make the div 'focusable\n",
+ " el.setAttribute('tabindex', 0);\n",
+ " // reach out to IPython and tell the keyboard manager to turn it's self\n",
+ " // off when our div gets focus\n",
+ "\n",
+ " // location in version 3\n",
+ " if (IPython.notebook.keyboard_manager) {\n",
+ " IPython.notebook.keyboard_manager.register_events(el);\n",
+ " } else {\n",
+ " // location in version 2\n",
+ " IPython.keyboard_manager.register_events(el);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._key_event_extra = function (event, _name) {\n",
+ " // Check for shift+enter\n",
+ " if (event.shiftKey && event.which === 13) {\n",
+ " this.canvas_div.blur();\n",
+ " // select the cell after this one\n",
+ " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n",
+ " IPython.notebook.select(index + 1);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+ " fig.ondownload(fig, null);\n",
+ "};\n",
+ "\n",
+ "mpl.find_output_cell = function (html_output) {\n",
+ " // Return the cell and output element which can be found *uniquely* in the notebook.\n",
+ " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
+ " // IPython event is triggered only after the cells have been serialised, which for\n",
+ " // our purposes (turning an active figure into a static one), is too late.\n",
+ " var cells = IPython.notebook.get_cells();\n",
+ " var ncells = cells.length;\n",
+ " for (var i = 0; i < ncells; i++) {\n",
+ " var cell = cells[i];\n",
+ " if (cell.cell_type === 'code') {\n",
+ " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n",
+ " var data = cell.output_area.outputs[j];\n",
+ " if (data.data) {\n",
+ " // IPython >= 3 moved mimebundle to data attribute of output\n",
+ " data = data.data;\n",
+ " }\n",
+ " if (data['text/html'] === html_output) {\n",
+ " return [cell, data, j];\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "// Register the function which deals with the matplotlib target/channel.\n",
+ "// The kernel may be null if the page has been refreshed.\n",
+ "if (IPython.notebook.kernel !== null) {\n",
+ " IPython.notebook.kernel.comm_manager.register_target(\n",
+ " 'matplotlib',\n",
+ " mpl.mpl_figure_comm\n",
+ " );\n",
+ "}\n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "
"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " mean std num\n",
+ "1x0 irods_via_davrods_on_isilon 45.970737 18.748706 18\n",
+ " irods_via_davrods_on_netapp 39.422446 0.920041 10\n",
+ " irods_via_davrods_on_s3 38.771205 4.741259 19\n",
+ "1x1K irods_via_davrods_on_isilon 100.057903 1.046998 19\n",
+ " irods_via_davrods_on_netapp 100.206922 0.858756 9\n",
+ " irods_via_davrods_on_s3 59.780832 5.975672 19\n",
+ "1x1M irods_via_davrods_on_isilon 100.621282 1.388649 19\n",
+ " irods_via_davrods_on_netapp 100.568214 1.335967 10\n",
+ " irods_via_davrods_on_s3 62.377437 4.910795 18\n",
+ "1x1G irods_via_davrods_on_isilon 100.155441 1.003477 18\n",
+ " irods_via_davrods_on_netapp 100.337795 1.061627 9\n",
+ " irods_via_davrods_on_s3 129.621274 10.037603 16\n",
+ "10x1G irods_via_davrods_on_isilon 348.729399 7.039795 9\n",
+ " irods_via_davrods_on_netapp 352.437443 6.116535 9\n",
+ " irods_via_davrods_on_s3 900.107534 19.137691 9\n"
+ ]
+ },
+ {
+ "data": {
+ "application/javascript": [
+ "/* Put everything inside the global mpl namespace */\n",
+ "/* global mpl */\n",
+ "window.mpl = {};\n",
+ "\n",
+ "mpl.get_websocket_type = function () {\n",
+ " if (typeof WebSocket !== 'undefined') {\n",
+ " return WebSocket;\n",
+ " } else if (typeof MozWebSocket !== 'undefined') {\n",
+ " return MozWebSocket;\n",
+ " } else {\n",
+ " alert(\n",
+ " 'Your browser does not have WebSocket support. ' +\n",
+ " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
+ " 'Firefox 4 and 5 are also supported but you ' +\n",
+ " 'have to enable WebSockets in about:config.'\n",
+ " );\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n",
+ " this.id = figure_id;\n",
+ "\n",
+ " this.ws = websocket;\n",
+ "\n",
+ " this.supports_binary = this.ws.binaryType !== undefined;\n",
+ "\n",
+ " if (!this.supports_binary) {\n",
+ " var warnings = document.getElementById('mpl-warnings');\n",
+ " if (warnings) {\n",
+ " warnings.style.display = 'block';\n",
+ " warnings.textContent =\n",
+ " 'This browser does not support binary websocket messages. ' +\n",
+ " 'Performance may be slow.';\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " this.imageObj = new Image();\n",
+ "\n",
+ " this.context = undefined;\n",
+ " this.message = undefined;\n",
+ " this.canvas = undefined;\n",
+ " this.rubberband_canvas = undefined;\n",
+ " this.rubberband_context = undefined;\n",
+ " this.format_dropdown = undefined;\n",
+ "\n",
+ " this.image_mode = 'full';\n",
+ "\n",
+ " this.root = document.createElement('div');\n",
+ " this.root.setAttribute('style', 'display: inline-block');\n",
+ " this._root_extra_style(this.root);\n",
+ "\n",
+ " parent_element.appendChild(this.root);\n",
+ "\n",
+ " this._init_header(this);\n",
+ " this._init_canvas(this);\n",
+ " this._init_toolbar(this);\n",
+ "\n",
+ " var fig = this;\n",
+ "\n",
+ " this.waiting = false;\n",
+ "\n",
+ " this.ws.onopen = function () {\n",
+ " fig.send_message('supports_binary', { value: fig.supports_binary });\n",
+ " fig.send_message('send_image_mode', {});\n",
+ " if (fig.ratio !== 1) {\n",
+ " fig.send_message('set_device_pixel_ratio', {\n",
+ " device_pixel_ratio: fig.ratio,\n",
+ " });\n",
+ " }\n",
+ " fig.send_message('refresh', {});\n",
+ " };\n",
+ "\n",
+ " this.imageObj.onload = function () {\n",
+ " if (fig.image_mode === 'full') {\n",
+ " // Full images could contain transparency (where diff images\n",
+ " // almost always do), so we need to clear the canvas so that\n",
+ " // there is no ghosting.\n",
+ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
+ " }\n",
+ " fig.context.drawImage(fig.imageObj, 0, 0);\n",
+ " };\n",
+ "\n",
+ " this.imageObj.onunload = function () {\n",
+ " fig.ws.close();\n",
+ " };\n",
+ "\n",
+ " this.ws.onmessage = this._make_on_message_function(this);\n",
+ "\n",
+ " this.ondownload = ondownload;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_header = function () {\n",
+ " var titlebar = document.createElement('div');\n",
+ " titlebar.classList =\n",
+ " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n",
+ " var titletext = document.createElement('div');\n",
+ " titletext.classList = 'ui-dialog-title';\n",
+ " titletext.setAttribute(\n",
+ " 'style',\n",
+ " 'width: 100%; text-align: center; padding: 3px;'\n",
+ " );\n",
+ " titlebar.appendChild(titletext);\n",
+ " this.root.appendChild(titlebar);\n",
+ " this.header = titletext;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n",
+ "\n",
+ "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n",
+ "\n",
+ "mpl.figure.prototype._init_canvas = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var canvas_div = (this.canvas_div = document.createElement('div'));\n",
+ " canvas_div.setAttribute(\n",
+ " 'style',\n",
+ " 'border: 1px solid #ddd;' +\n",
+ " 'box-sizing: content-box;' +\n",
+ " 'clear: both;' +\n",
+ " 'min-height: 1px;' +\n",
+ " 'min-width: 1px;' +\n",
+ " 'outline: 0;' +\n",
+ " 'overflow: hidden;' +\n",
+ " 'position: relative;' +\n",
+ " 'resize: both;'\n",
+ " );\n",
+ "\n",
+ " function on_keyboard_event_closure(name) {\n",
+ " return function (event) {\n",
+ " return fig.key_event(event, name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " canvas_div.addEventListener(\n",
+ " 'keydown',\n",
+ " on_keyboard_event_closure('key_press')\n",
+ " );\n",
+ " canvas_div.addEventListener(\n",
+ " 'keyup',\n",
+ " on_keyboard_event_closure('key_release')\n",
+ " );\n",
+ "\n",
+ " this._canvas_extra_style(canvas_div);\n",
+ " this.root.appendChild(canvas_div);\n",
+ "\n",
+ " var canvas = (this.canvas = document.createElement('canvas'));\n",
+ " canvas.classList.add('mpl-canvas');\n",
+ " canvas.setAttribute('style', 'box-sizing: content-box;');\n",
+ "\n",
+ " this.context = canvas.getContext('2d');\n",
+ "\n",
+ " var backingStore =\n",
+ " this.context.backingStorePixelRatio ||\n",
+ " this.context.webkitBackingStorePixelRatio ||\n",
+ " this.context.mozBackingStorePixelRatio ||\n",
+ " this.context.msBackingStorePixelRatio ||\n",
+ " this.context.oBackingStorePixelRatio ||\n",
+ " this.context.backingStorePixelRatio ||\n",
+ " 1;\n",
+ "\n",
+ " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
+ "\n",
+ " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n",
+ " 'canvas'\n",
+ " ));\n",
+ " rubberband_canvas.setAttribute(\n",
+ " 'style',\n",
+ " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n",
+ " );\n",
+ "\n",
+ " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n",
+ " if (this.ResizeObserver === undefined) {\n",
+ " if (window.ResizeObserver !== undefined) {\n",
+ " this.ResizeObserver = window.ResizeObserver;\n",
+ " } else {\n",
+ " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n",
+ " this.ResizeObserver = obs.ResizeObserver;\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n",
+ " var nentries = entries.length;\n",
+ " for (var i = 0; i < nentries; i++) {\n",
+ " var entry = entries[i];\n",
+ " var width, height;\n",
+ " if (entry.contentBoxSize) {\n",
+ " if (entry.contentBoxSize instanceof Array) {\n",
+ " // Chrome 84 implements new version of spec.\n",
+ " width = entry.contentBoxSize[0].inlineSize;\n",
+ " height = entry.contentBoxSize[0].blockSize;\n",
+ " } else {\n",
+ " // Firefox implements old version of spec.\n",
+ " width = entry.contentBoxSize.inlineSize;\n",
+ " height = entry.contentBoxSize.blockSize;\n",
+ " }\n",
+ " } else {\n",
+ " // Chrome <84 implements even older version of spec.\n",
+ " width = entry.contentRect.width;\n",
+ " height = entry.contentRect.height;\n",
+ " }\n",
+ "\n",
+ " // Keep the size of the canvas and rubber band canvas in sync with\n",
+ " // the canvas container.\n",
+ " if (entry.devicePixelContentBoxSize) {\n",
+ " // Chrome 84 implements new version of spec.\n",
+ " canvas.setAttribute(\n",
+ " 'width',\n",
+ " entry.devicePixelContentBoxSize[0].inlineSize\n",
+ " );\n",
+ " canvas.setAttribute(\n",
+ " 'height',\n",
+ " entry.devicePixelContentBoxSize[0].blockSize\n",
+ " );\n",
+ " } else {\n",
+ " canvas.setAttribute('width', width * fig.ratio);\n",
+ " canvas.setAttribute('height', height * fig.ratio);\n",
+ " }\n",
+ " canvas.setAttribute(\n",
+ " 'style',\n",
+ " 'width: ' + width + 'px; height: ' + height + 'px;'\n",
+ " );\n",
+ "\n",
+ " rubberband_canvas.setAttribute('width', width);\n",
+ " rubberband_canvas.setAttribute('height', height);\n",
+ "\n",
+ " // And update the size in Python. We ignore the initial 0/0 size\n",
+ " // that occurs as the element is placed into the DOM, which should\n",
+ " // otherwise not happen due to the minimum size styling.\n",
+ " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n",
+ " fig.request_resize(width, height);\n",
+ " }\n",
+ " }\n",
+ " });\n",
+ " this.resizeObserverInstance.observe(canvas_div);\n",
+ "\n",
+ " function on_mouse_event_closure(name) {\n",
+ " return function (event) {\n",
+ " return fig.mouse_event(event, name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mousedown',\n",
+ " on_mouse_event_closure('button_press')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseup',\n",
+ " on_mouse_event_closure('button_release')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'dblclick',\n",
+ " on_mouse_event_closure('dblclick')\n",
+ " );\n",
+ " // Throttle sequential mouse events to 1 every 20ms.\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mousemove',\n",
+ " on_mouse_event_closure('motion_notify')\n",
+ " );\n",
+ "\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseenter',\n",
+ " on_mouse_event_closure('figure_enter')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseleave',\n",
+ " on_mouse_event_closure('figure_leave')\n",
+ " );\n",
+ "\n",
+ " canvas_div.addEventListener('wheel', function (event) {\n",
+ " if (event.deltaY < 0) {\n",
+ " event.step = 1;\n",
+ " } else {\n",
+ " event.step = -1;\n",
+ " }\n",
+ " on_mouse_event_closure('scroll')(event);\n",
+ " });\n",
+ "\n",
+ " canvas_div.appendChild(canvas);\n",
+ " canvas_div.appendChild(rubberband_canvas);\n",
+ "\n",
+ " this.rubberband_context = rubberband_canvas.getContext('2d');\n",
+ " this.rubberband_context.strokeStyle = '#000000';\n",
+ "\n",
+ " this._resize_canvas = function (width, height, forward) {\n",
+ " if (forward) {\n",
+ " canvas_div.style.width = width + 'px';\n",
+ " canvas_div.style.height = height + 'px';\n",
+ " }\n",
+ " };\n",
+ "\n",
+ " // Disable right mouse context menu.\n",
+ " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n",
+ " event.preventDefault();\n",
+ " return false;\n",
+ " });\n",
+ "\n",
+ " function set_focus() {\n",
+ " canvas.focus();\n",
+ " canvas_div.focus();\n",
+ " }\n",
+ "\n",
+ " window.setTimeout(set_focus, 100);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_toolbar = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var toolbar = document.createElement('div');\n",
+ " toolbar.classList = 'mpl-toolbar';\n",
+ " this.root.appendChild(toolbar);\n",
+ "\n",
+ " function on_click_closure(name) {\n",
+ " return function (_event) {\n",
+ " return fig.toolbar_button_onclick(name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " function on_mouseover_closure(tooltip) {\n",
+ " return function (event) {\n",
+ " if (!event.currentTarget.disabled) {\n",
+ " return fig.toolbar_button_onmouseover(tooltip);\n",
+ " }\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " fig.buttons = {};\n",
+ " var buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'mpl-button-group';\n",
+ " for (var toolbar_ind in mpl.toolbar_items) {\n",
+ " var name = mpl.toolbar_items[toolbar_ind][0];\n",
+ " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+ " var image = mpl.toolbar_items[toolbar_ind][2];\n",
+ " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+ "\n",
+ " if (!name) {\n",
+ " /* Instead of a spacer, we start a new button group. */\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ " buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'mpl-button-group';\n",
+ " continue;\n",
+ " }\n",
+ "\n",
+ " var button = (fig.buttons[name] = document.createElement('button'));\n",
+ " button.classList = 'mpl-widget';\n",
+ " button.setAttribute('role', 'button');\n",
+ " button.setAttribute('aria-disabled', 'false');\n",
+ " button.addEventListener('click', on_click_closure(method_name));\n",
+ " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+ "\n",
+ " var icon_img = document.createElement('img');\n",
+ " icon_img.src = '_images/' + image + '.png';\n",
+ " icon_img.srcset = '_images/' + image + '_large.png 2x';\n",
+ " icon_img.alt = tooltip;\n",
+ " button.appendChild(icon_img);\n",
+ "\n",
+ " buttonGroup.appendChild(button);\n",
+ " }\n",
+ "\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ "\n",
+ " var fmt_picker = document.createElement('select');\n",
+ " fmt_picker.classList = 'mpl-widget';\n",
+ " toolbar.appendChild(fmt_picker);\n",
+ " this.format_dropdown = fmt_picker;\n",
+ "\n",
+ " for (var ind in mpl.extensions) {\n",
+ " var fmt = mpl.extensions[ind];\n",
+ " var option = document.createElement('option');\n",
+ " option.selected = fmt === mpl.default_extension;\n",
+ " option.innerHTML = fmt;\n",
+ " fmt_picker.appendChild(option);\n",
+ " }\n",
+ "\n",
+ " var status_bar = document.createElement('span');\n",
+ " status_bar.classList = 'mpl-message';\n",
+ " toolbar.appendChild(status_bar);\n",
+ " this.message = status_bar;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n",
+ " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
+ " // which will in turn request a refresh of the image.\n",
+ " this.send_message('resize', { width: x_pixels, height: y_pixels });\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.send_message = function (type, properties) {\n",
+ " properties['type'] = type;\n",
+ " properties['figure_id'] = this.id;\n",
+ " this.ws.send(JSON.stringify(properties));\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.send_draw_message = function () {\n",
+ " if (!this.waiting) {\n",
+ " this.waiting = true;\n",
+ " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+ " var format_dropdown = fig.format_dropdown;\n",
+ " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
+ " fig.ondownload(fig, format);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_resize = function (fig, msg) {\n",
+ " var size = msg['size'];\n",
+ " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n",
+ " fig._resize_canvas(size[0], size[1], msg['forward']);\n",
+ " fig.send_message('refresh', {});\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n",
+ " var x0 = msg['x0'] / fig.ratio;\n",
+ " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n",
+ " var x1 = msg['x1'] / fig.ratio;\n",
+ " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n",
+ " x0 = Math.floor(x0) + 0.5;\n",
+ " y0 = Math.floor(y0) + 0.5;\n",
+ " x1 = Math.floor(x1) + 0.5;\n",
+ " y1 = Math.floor(y1) + 0.5;\n",
+ " var min_x = Math.min(x0, x1);\n",
+ " var min_y = Math.min(y0, y1);\n",
+ " var width = Math.abs(x1 - x0);\n",
+ " var height = Math.abs(y1 - y0);\n",
+ "\n",
+ " fig.rubberband_context.clearRect(\n",
+ " 0,\n",
+ " 0,\n",
+ " fig.canvas.width / fig.ratio,\n",
+ " fig.canvas.height / fig.ratio\n",
+ " );\n",
+ "\n",
+ " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n",
+ " // Updates the figure title.\n",
+ " fig.header.textContent = msg['label'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n",
+ " fig.rubberband_canvas.style.cursor = msg['cursor'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_message = function (fig, msg) {\n",
+ " fig.message.textContent = msg['message'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n",
+ " // Request the server to send over a new figure.\n",
+ " fig.send_draw_message();\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n",
+ " fig.image_mode = msg['mode'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n",
+ " for (var key in msg) {\n",
+ " if (!(key in fig.buttons)) {\n",
+ " continue;\n",
+ " }\n",
+ " fig.buttons[key].disabled = !msg[key];\n",
+ " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n",
+ " if (msg['mode'] === 'PAN') {\n",
+ " fig.buttons['Pan'].classList.add('active');\n",
+ " fig.buttons['Zoom'].classList.remove('active');\n",
+ " } else if (msg['mode'] === 'ZOOM') {\n",
+ " fig.buttons['Pan'].classList.remove('active');\n",
+ " fig.buttons['Zoom'].classList.add('active');\n",
+ " } else {\n",
+ " fig.buttons['Pan'].classList.remove('active');\n",
+ " fig.buttons['Zoom'].classList.remove('active');\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.updated_canvas_event = function () {\n",
+ " // Called whenever the canvas gets updated.\n",
+ " this.send_message('ack', {});\n",
+ "};\n",
+ "\n",
+ "// A function to construct a web socket function for onmessage handling.\n",
+ "// Called in the figure constructor.\n",
+ "mpl.figure.prototype._make_on_message_function = function (fig) {\n",
+ " return function socket_on_message(evt) {\n",
+ " if (evt.data instanceof Blob) {\n",
+ " var img = evt.data;\n",
+ " if (img.type !== 'image/png') {\n",
+ " /* FIXME: We get \"Resource interpreted as Image but\n",
+ " * transferred with MIME type text/plain:\" errors on\n",
+ " * Chrome. But how to set the MIME type? It doesn't seem\n",
+ " * to be part of the websocket stream */\n",
+ " img.type = 'image/png';\n",
+ " }\n",
+ "\n",
+ " /* Free the memory for the previous frames */\n",
+ " if (fig.imageObj.src) {\n",
+ " (window.URL || window.webkitURL).revokeObjectURL(\n",
+ " fig.imageObj.src\n",
+ " );\n",
+ " }\n",
+ "\n",
+ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
+ " img\n",
+ " );\n",
+ " fig.updated_canvas_event();\n",
+ " fig.waiting = false;\n",
+ " return;\n",
+ " } else if (\n",
+ " typeof evt.data === 'string' &&\n",
+ " evt.data.slice(0, 21) === 'data:image/png;base64'\n",
+ " ) {\n",
+ " fig.imageObj.src = evt.data;\n",
+ " fig.updated_canvas_event();\n",
+ " fig.waiting = false;\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " var msg = JSON.parse(evt.data);\n",
+ " var msg_type = msg['type'];\n",
+ "\n",
+ " // Call the \"handle_{type}\" callback, which takes\n",
+ " // the figure and JSON message as its only arguments.\n",
+ " try {\n",
+ " var callback = fig['handle_' + msg_type];\n",
+ " } catch (e) {\n",
+ " console.log(\n",
+ " \"No handler for the '\" + msg_type + \"' message type: \",\n",
+ " msg\n",
+ " );\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " if (callback) {\n",
+ " try {\n",
+ " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
+ " callback(fig, msg);\n",
+ " } catch (e) {\n",
+ " console.log(\n",
+ " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n",
+ " e,\n",
+ " e.stack,\n",
+ " msg\n",
+ " );\n",
+ " }\n",
+ " }\n",
+ " };\n",
+ "};\n",
+ "\n",
+ "// from https://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
+ "mpl.findpos = function (e) {\n",
+ " //this section is from http://www.quirksmode.org/js/events_properties.html\n",
+ " var targ;\n",
+ " if (!e) {\n",
+ " e = window.event;\n",
+ " }\n",
+ " if (e.target) {\n",
+ " targ = e.target;\n",
+ " } else if (e.srcElement) {\n",
+ " targ = e.srcElement;\n",
+ " }\n",
+ " if (targ.nodeType === 3) {\n",
+ " // defeat Safari bug\n",
+ " targ = targ.parentNode;\n",
+ " }\n",
+ "\n",
+ " // pageX,Y are the mouse positions relative to the document\n",
+ " var boundingRect = targ.getBoundingClientRect();\n",
+ " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n",
+ " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n",
+ "\n",
+ " return { x: x, y: y };\n",
+ "};\n",
+ "\n",
+ "/*\n",
+ " * return a copy of an object with only non-object keys\n",
+ " * we need this to avoid circular references\n",
+ " * https://stackoverflow.com/a/24161582/3208463\n",
+ " */\n",
+ "function simpleKeys(original) {\n",
+ " return Object.keys(original).reduce(function (obj, key) {\n",
+ " if (typeof original[key] !== 'object') {\n",
+ " obj[key] = original[key];\n",
+ " }\n",
+ " return obj;\n",
+ " }, {});\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.mouse_event = function (event, name) {\n",
+ " var canvas_pos = mpl.findpos(event);\n",
+ "\n",
+ " if (name === 'button_press') {\n",
+ " this.canvas.focus();\n",
+ " this.canvas_div.focus();\n",
+ " }\n",
+ "\n",
+ " var x = canvas_pos.x * this.ratio;\n",
+ " var y = canvas_pos.y * this.ratio;\n",
+ "\n",
+ " this.send_message(name, {\n",
+ " x: x,\n",
+ " y: y,\n",
+ " button: event.button,\n",
+ " step: event.step,\n",
+ " guiEvent: simpleKeys(event),\n",
+ " });\n",
+ "\n",
+ " /* This prevents the web browser from automatically changing to\n",
+ " * the text insertion cursor when the button is pressed. We want\n",
+ " * to control all of the cursor setting manually through the\n",
+ " * 'cursor' event from matplotlib */\n",
+ " event.preventDefault();\n",
+ " return false;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n",
+ " // Handle any extra behaviour associated with a key event\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.key_event = function (event, name) {\n",
+ " // Prevent repeat events\n",
+ " if (name === 'key_press') {\n",
+ " if (event.key === this._key) {\n",
+ " return;\n",
+ " } else {\n",
+ " this._key = event.key;\n",
+ " }\n",
+ " }\n",
+ " if (name === 'key_release') {\n",
+ " this._key = null;\n",
+ " }\n",
+ "\n",
+ " var value = '';\n",
+ " if (event.ctrlKey && event.key !== 'Control') {\n",
+ " value += 'ctrl+';\n",
+ " }\n",
+ " else if (event.altKey && event.key !== 'Alt') {\n",
+ " value += 'alt+';\n",
+ " }\n",
+ " else if (event.shiftKey && event.key !== 'Shift') {\n",
+ " value += 'shift+';\n",
+ " }\n",
+ "\n",
+ " value += 'k' + event.key;\n",
+ "\n",
+ " this._key_event_extra(event, name);\n",
+ "\n",
+ " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n",
+ " return false;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n",
+ " if (name === 'download') {\n",
+ " this.handle_save(this, null);\n",
+ " } else {\n",
+ " this.send_message('toolbar_button', { name: name });\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n",
+ " this.message.textContent = tooltip;\n",
+ "};\n",
+ "\n",
+ "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n",
+ "// prettier-ignore\n",
+ "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n",
+ "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
+ "\n",
+ "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
+ "\n",
+ "mpl.default_extension = \"png\";/* global mpl */\n",
+ "\n",
+ "var comm_websocket_adapter = function (comm) {\n",
+ " // Create a \"websocket\"-like object which calls the given IPython comm\n",
+ " // object with the appropriate methods. Currently this is a non binary\n",
+ " // socket, so there is still some room for performance tuning.\n",
+ " var ws = {};\n",
+ "\n",
+ " ws.binaryType = comm.kernel.ws.binaryType;\n",
+ " ws.readyState = comm.kernel.ws.readyState;\n",
+ " function updateReadyState(_event) {\n",
+ " if (comm.kernel.ws) {\n",
+ " ws.readyState = comm.kernel.ws.readyState;\n",
+ " } else {\n",
+ " ws.readyState = 3; // Closed state.\n",
+ " }\n",
+ " }\n",
+ " comm.kernel.ws.addEventListener('open', updateReadyState);\n",
+ " comm.kernel.ws.addEventListener('close', updateReadyState);\n",
+ " comm.kernel.ws.addEventListener('error', updateReadyState);\n",
+ "\n",
+ " ws.close = function () {\n",
+ " comm.close();\n",
+ " };\n",
+ " ws.send = function (m) {\n",
+ " //console.log('sending', m);\n",
+ " comm.send(m);\n",
+ " };\n",
+ " // Register the callback with on_msg.\n",
+ " comm.on_msg(function (msg) {\n",
+ " //console.log('receiving', msg['content']['data'], msg);\n",
+ " var data = msg['content']['data'];\n",
+ " if (data['blob'] !== undefined) {\n",
+ " data = {\n",
+ " data: new Blob(msg['buffers'], { type: data['blob'] }),\n",
+ " };\n",
+ " }\n",
+ " // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
+ " ws.onmessage(data);\n",
+ " });\n",
+ " return ws;\n",
+ "};\n",
+ "\n",
+ "mpl.mpl_figure_comm = function (comm, msg) {\n",
+ " // This is the function which gets called when the mpl process\n",
+ " // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
+ "\n",
+ " var id = msg.content.data.id;\n",
+ " // Get hold of the div created by the display call when the Comm\n",
+ " // socket was opened in Python.\n",
+ " var element = document.getElementById(id);\n",
+ " var ws_proxy = comm_websocket_adapter(comm);\n",
+ "\n",
+ " function ondownload(figure, _format) {\n",
+ " window.open(figure.canvas.toDataURL());\n",
+ " }\n",
+ "\n",
+ " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n",
+ "\n",
+ " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
+ " // web socket which is closed, not our websocket->open comm proxy.\n",
+ " ws_proxy.onopen();\n",
+ "\n",
+ " fig.parent_element = element;\n",
+ " fig.cell_info = mpl.find_output_cell(\"\");\n",
+ " if (!fig.cell_info) {\n",
+ " console.error('Failed to find cell for figure', id, fig);\n",
+ " return;\n",
+ " }\n",
+ " fig.cell_info[0].output_area.element.on(\n",
+ " 'cleared',\n",
+ " { fig: fig },\n",
+ " fig._remove_fig_handler\n",
+ " );\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_close = function (fig, msg) {\n",
+ " var width = fig.canvas.width / fig.ratio;\n",
+ " fig.cell_info[0].output_area.element.off(\n",
+ " 'cleared',\n",
+ " fig._remove_fig_handler\n",
+ " );\n",
+ " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n",
+ "\n",
+ " // Update the output cell to use the data from the current canvas.\n",
+ " fig.push_to_output();\n",
+ " var dataURL = fig.canvas.toDataURL();\n",
+ " // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
+ " // the notebook keyboard shortcuts fail.\n",
+ " IPython.keyboard_manager.enable();\n",
+ " fig.parent_element.innerHTML =\n",
+ " '
';\n",
+ " fig.close_ws(fig, msg);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.close_ws = function (fig, msg) {\n",
+ " fig.send_message('closing', msg);\n",
+ " // fig.ws.close()\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n",
+ " // Turn the data on the canvas into data in the output cell.\n",
+ " var width = this.canvas.width / this.ratio;\n",
+ " var dataURL = this.canvas.toDataURL();\n",
+ " this.cell_info[1]['text/html'] =\n",
+ " '
';\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.updated_canvas_event = function () {\n",
+ " // Tell IPython that the notebook contents must change.\n",
+ " IPython.notebook.set_dirty(true);\n",
+ " this.send_message('ack', {});\n",
+ " var fig = this;\n",
+ " // Wait a second, then push the new image to the DOM so\n",
+ " // that it is saved nicely (might be nice to debounce this).\n",
+ " setTimeout(function () {\n",
+ " fig.push_to_output();\n",
+ " }, 1000);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_toolbar = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var toolbar = document.createElement('div');\n",
+ " toolbar.classList = 'btn-toolbar';\n",
+ " this.root.appendChild(toolbar);\n",
+ "\n",
+ " function on_click_closure(name) {\n",
+ " return function (_event) {\n",
+ " return fig.toolbar_button_onclick(name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " function on_mouseover_closure(tooltip) {\n",
+ " return function (event) {\n",
+ " if (!event.currentTarget.disabled) {\n",
+ " return fig.toolbar_button_onmouseover(tooltip);\n",
+ " }\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " fig.buttons = {};\n",
+ " var buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'btn-group';\n",
+ " var button;\n",
+ " for (var toolbar_ind in mpl.toolbar_items) {\n",
+ " var name = mpl.toolbar_items[toolbar_ind][0];\n",
+ " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+ " var image = mpl.toolbar_items[toolbar_ind][2];\n",
+ " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+ "\n",
+ " if (!name) {\n",
+ " /* Instead of a spacer, we start a new button group. */\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ " buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'btn-group';\n",
+ " continue;\n",
+ " }\n",
+ "\n",
+ " button = fig.buttons[name] = document.createElement('button');\n",
+ " button.classList = 'btn btn-default';\n",
+ " button.href = '#';\n",
+ " button.title = name;\n",
+ " button.innerHTML = '';\n",
+ " button.addEventListener('click', on_click_closure(method_name));\n",
+ " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+ " buttonGroup.appendChild(button);\n",
+ " }\n",
+ "\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ "\n",
+ " // Add the status bar.\n",
+ " var status_bar = document.createElement('span');\n",
+ " status_bar.classList = 'mpl-message pull-right';\n",
+ " toolbar.appendChild(status_bar);\n",
+ " this.message = status_bar;\n",
+ "\n",
+ " // Add the close button to the window.\n",
+ " var buttongrp = document.createElement('div');\n",
+ " buttongrp.classList = 'btn-group inline pull-right';\n",
+ " button = document.createElement('button');\n",
+ " button.classList = 'btn btn-mini btn-primary';\n",
+ " button.href = '#';\n",
+ " button.title = 'Stop Interaction';\n",
+ " button.innerHTML = '';\n",
+ " button.addEventListener('click', function (_evt) {\n",
+ " fig.handle_close(fig, {});\n",
+ " });\n",
+ " button.addEventListener(\n",
+ " 'mouseover',\n",
+ " on_mouseover_closure('Stop Interaction')\n",
+ " );\n",
+ " buttongrp.appendChild(button);\n",
+ " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n",
+ " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._remove_fig_handler = function (event) {\n",
+ " var fig = event.data.fig;\n",
+ " if (event.target !== this) {\n",
+ " // Ignore bubbled events from children.\n",
+ " return;\n",
+ " }\n",
+ " fig.close_ws(fig, {});\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._root_extra_style = function (el) {\n",
+ " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._canvas_extra_style = function (el) {\n",
+ " // this is important to make the div 'focusable\n",
+ " el.setAttribute('tabindex', 0);\n",
+ " // reach out to IPython and tell the keyboard manager to turn it's self\n",
+ " // off when our div gets focus\n",
+ "\n",
+ " // location in version 3\n",
+ " if (IPython.notebook.keyboard_manager) {\n",
+ " IPython.notebook.keyboard_manager.register_events(el);\n",
+ " } else {\n",
+ " // location in version 2\n",
+ " IPython.keyboard_manager.register_events(el);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._key_event_extra = function (event, _name) {\n",
+ " // Check for shift+enter\n",
+ " if (event.shiftKey && event.which === 13) {\n",
+ " this.canvas_div.blur();\n",
+ " // select the cell after this one\n",
+ " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n",
+ " IPython.notebook.select(index + 1);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+ " fig.ondownload(fig, null);\n",
+ "};\n",
+ "\n",
+ "mpl.find_output_cell = function (html_output) {\n",
+ " // Return the cell and output element which can be found *uniquely* in the notebook.\n",
+ " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
+ " // IPython event is triggered only after the cells have been serialised, which for\n",
+ " // our purposes (turning an active figure into a static one), is too late.\n",
+ " var cells = IPython.notebook.get_cells();\n",
+ " var ncells = cells.length;\n",
+ " for (var i = 0; i < ncells; i++) {\n",
+ " var cell = cells[i];\n",
+ " if (cell.cell_type === 'code') {\n",
+ " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n",
+ " var data = cell.output_area.outputs[j];\n",
+ " if (data.data) {\n",
+ " // IPython >= 3 moved mimebundle to data attribute of output\n",
+ " data = data.data;\n",
+ " }\n",
+ " if (data['text/html'] === html_output) {\n",
+ " return [cell, data, j];\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "// Register the function which deals with the matplotlib target/channel.\n",
+ "// The kernel may be null if the page has been refreshed.\n",
+ "if (IPython.notebook.kernel !== null) {\n",
+ " IPython.notebook.kernel.comm_manager.register_target(\n",
+ " 'matplotlib',\n",
+ " mpl.mpl_figure_comm\n",
+ " );\n",
+ "}\n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "
"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " mean std num\n",
+ "1x0 isilon 38.570352 4.155044 20\n",
+ " irods_on_isilon 58.561781 28.682517 19\n",
+ " irods_via_davrods_on_isilon 45.970737 18.748706 18\n",
+ "1x1K isilon 61.779090 5.862412 19\n",
+ " irods_on_isilon 100.219069 1.270263 19\n",
+ " irods_via_davrods_on_isilon 100.057903 1.046998 19\n",
+ "10x1K isilon 61.898669 5.417915 19\n",
+ " irods_on_isilon 100.312221 1.484970 19\n",
+ " irods_via_davrods_on_isilon 100.749137 2.349299 18\n",
+ "100x1K isilon 61.409810 5.781275 20\n",
+ " irods_on_isilon 111.333367 6.102356 19\n",
+ " irods_via_davrods_on_isilon 100.584359 1.371663 20\n",
+ "1000x1K isilon 74.438915 8.021540 19\n",
+ " irods_on_isilon 575.954983 29.149668 19\n",
+ " irods_via_davrods_on_isilon 339.142480 9.159978 18\n",
+ "2000x1K isilon 86.488210 10.314142 19\n",
+ " irods_on_isilon 1082.106492 18.186798 19\n",
+ " irods_via_davrods_on_isilon 617.350109 17.696956 19\n"
+ ]
+ },
+ {
+ "data": {
+ "application/javascript": [
+ "/* Put everything inside the global mpl namespace */\n",
+ "/* global mpl */\n",
+ "window.mpl = {};\n",
+ "\n",
+ "mpl.get_websocket_type = function () {\n",
+ " if (typeof WebSocket !== 'undefined') {\n",
+ " return WebSocket;\n",
+ " } else if (typeof MozWebSocket !== 'undefined') {\n",
+ " return MozWebSocket;\n",
+ " } else {\n",
+ " alert(\n",
+ " 'Your browser does not have WebSocket support. ' +\n",
+ " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
+ " 'Firefox 4 and 5 are also supported but you ' +\n",
+ " 'have to enable WebSockets in about:config.'\n",
+ " );\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n",
+ " this.id = figure_id;\n",
+ "\n",
+ " this.ws = websocket;\n",
+ "\n",
+ " this.supports_binary = this.ws.binaryType !== undefined;\n",
+ "\n",
+ " if (!this.supports_binary) {\n",
+ " var warnings = document.getElementById('mpl-warnings');\n",
+ " if (warnings) {\n",
+ " warnings.style.display = 'block';\n",
+ " warnings.textContent =\n",
+ " 'This browser does not support binary websocket messages. ' +\n",
+ " 'Performance may be slow.';\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " this.imageObj = new Image();\n",
+ "\n",
+ " this.context = undefined;\n",
+ " this.message = undefined;\n",
+ " this.canvas = undefined;\n",
+ " this.rubberband_canvas = undefined;\n",
+ " this.rubberband_context = undefined;\n",
+ " this.format_dropdown = undefined;\n",
+ "\n",
+ " this.image_mode = 'full';\n",
+ "\n",
+ " this.root = document.createElement('div');\n",
+ " this.root.setAttribute('style', 'display: inline-block');\n",
+ " this._root_extra_style(this.root);\n",
+ "\n",
+ " parent_element.appendChild(this.root);\n",
+ "\n",
+ " this._init_header(this);\n",
+ " this._init_canvas(this);\n",
+ " this._init_toolbar(this);\n",
+ "\n",
+ " var fig = this;\n",
+ "\n",
+ " this.waiting = false;\n",
+ "\n",
+ " this.ws.onopen = function () {\n",
+ " fig.send_message('supports_binary', { value: fig.supports_binary });\n",
+ " fig.send_message('send_image_mode', {});\n",
+ " if (fig.ratio !== 1) {\n",
+ " fig.send_message('set_device_pixel_ratio', {\n",
+ " device_pixel_ratio: fig.ratio,\n",
+ " });\n",
+ " }\n",
+ " fig.send_message('refresh', {});\n",
+ " };\n",
+ "\n",
+ " this.imageObj.onload = function () {\n",
+ " if (fig.image_mode === 'full') {\n",
+ " // Full images could contain transparency (where diff images\n",
+ " // almost always do), so we need to clear the canvas so that\n",
+ " // there is no ghosting.\n",
+ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
+ " }\n",
+ " fig.context.drawImage(fig.imageObj, 0, 0);\n",
+ " };\n",
+ "\n",
+ " this.imageObj.onunload = function () {\n",
+ " fig.ws.close();\n",
+ " };\n",
+ "\n",
+ " this.ws.onmessage = this._make_on_message_function(this);\n",
+ "\n",
+ " this.ondownload = ondownload;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_header = function () {\n",
+ " var titlebar = document.createElement('div');\n",
+ " titlebar.classList =\n",
+ " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n",
+ " var titletext = document.createElement('div');\n",
+ " titletext.classList = 'ui-dialog-title';\n",
+ " titletext.setAttribute(\n",
+ " 'style',\n",
+ " 'width: 100%; text-align: center; padding: 3px;'\n",
+ " );\n",
+ " titlebar.appendChild(titletext);\n",
+ " this.root.appendChild(titlebar);\n",
+ " this.header = titletext;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n",
+ "\n",
+ "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n",
+ "\n",
+ "mpl.figure.prototype._init_canvas = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var canvas_div = (this.canvas_div = document.createElement('div'));\n",
+ " canvas_div.setAttribute(\n",
+ " 'style',\n",
+ " 'border: 1px solid #ddd;' +\n",
+ " 'box-sizing: content-box;' +\n",
+ " 'clear: both;' +\n",
+ " 'min-height: 1px;' +\n",
+ " 'min-width: 1px;' +\n",
+ " 'outline: 0;' +\n",
+ " 'overflow: hidden;' +\n",
+ " 'position: relative;' +\n",
+ " 'resize: both;'\n",
+ " );\n",
+ "\n",
+ " function on_keyboard_event_closure(name) {\n",
+ " return function (event) {\n",
+ " return fig.key_event(event, name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " canvas_div.addEventListener(\n",
+ " 'keydown',\n",
+ " on_keyboard_event_closure('key_press')\n",
+ " );\n",
+ " canvas_div.addEventListener(\n",
+ " 'keyup',\n",
+ " on_keyboard_event_closure('key_release')\n",
+ " );\n",
+ "\n",
+ " this._canvas_extra_style(canvas_div);\n",
+ " this.root.appendChild(canvas_div);\n",
+ "\n",
+ " var canvas = (this.canvas = document.createElement('canvas'));\n",
+ " canvas.classList.add('mpl-canvas');\n",
+ " canvas.setAttribute('style', 'box-sizing: content-box;');\n",
+ "\n",
+ " this.context = canvas.getContext('2d');\n",
+ "\n",
+ " var backingStore =\n",
+ " this.context.backingStorePixelRatio ||\n",
+ " this.context.webkitBackingStorePixelRatio ||\n",
+ " this.context.mozBackingStorePixelRatio ||\n",
+ " this.context.msBackingStorePixelRatio ||\n",
+ " this.context.oBackingStorePixelRatio ||\n",
+ " this.context.backingStorePixelRatio ||\n",
+ " 1;\n",
+ "\n",
+ " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
+ "\n",
+ " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n",
+ " 'canvas'\n",
+ " ));\n",
+ " rubberband_canvas.setAttribute(\n",
+ " 'style',\n",
+ " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n",
+ " );\n",
+ "\n",
+ " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n",
+ " if (this.ResizeObserver === undefined) {\n",
+ " if (window.ResizeObserver !== undefined) {\n",
+ " this.ResizeObserver = window.ResizeObserver;\n",
+ " } else {\n",
+ " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n",
+ " this.ResizeObserver = obs.ResizeObserver;\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n",
+ " var nentries = entries.length;\n",
+ " for (var i = 0; i < nentries; i++) {\n",
+ " var entry = entries[i];\n",
+ " var width, height;\n",
+ " if (entry.contentBoxSize) {\n",
+ " if (entry.contentBoxSize instanceof Array) {\n",
+ " // Chrome 84 implements new version of spec.\n",
+ " width = entry.contentBoxSize[0].inlineSize;\n",
+ " height = entry.contentBoxSize[0].blockSize;\n",
+ " } else {\n",
+ " // Firefox implements old version of spec.\n",
+ " width = entry.contentBoxSize.inlineSize;\n",
+ " height = entry.contentBoxSize.blockSize;\n",
+ " }\n",
+ " } else {\n",
+ " // Chrome <84 implements even older version of spec.\n",
+ " width = entry.contentRect.width;\n",
+ " height = entry.contentRect.height;\n",
+ " }\n",
+ "\n",
+ " // Keep the size of the canvas and rubber band canvas in sync with\n",
+ " // the canvas container.\n",
+ " if (entry.devicePixelContentBoxSize) {\n",
+ " // Chrome 84 implements new version of spec.\n",
+ " canvas.setAttribute(\n",
+ " 'width',\n",
+ " entry.devicePixelContentBoxSize[0].inlineSize\n",
+ " );\n",
+ " canvas.setAttribute(\n",
+ " 'height',\n",
+ " entry.devicePixelContentBoxSize[0].blockSize\n",
+ " );\n",
+ " } else {\n",
+ " canvas.setAttribute('width', width * fig.ratio);\n",
+ " canvas.setAttribute('height', height * fig.ratio);\n",
+ " }\n",
+ " canvas.setAttribute(\n",
+ " 'style',\n",
+ " 'width: ' + width + 'px; height: ' + height + 'px;'\n",
+ " );\n",
+ "\n",
+ " rubberband_canvas.setAttribute('width', width);\n",
+ " rubberband_canvas.setAttribute('height', height);\n",
+ "\n",
+ " // And update the size in Python. We ignore the initial 0/0 size\n",
+ " // that occurs as the element is placed into the DOM, which should\n",
+ " // otherwise not happen due to the minimum size styling.\n",
+ " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n",
+ " fig.request_resize(width, height);\n",
+ " }\n",
+ " }\n",
+ " });\n",
+ " this.resizeObserverInstance.observe(canvas_div);\n",
+ "\n",
+ " function on_mouse_event_closure(name) {\n",
+ " return function (event) {\n",
+ " return fig.mouse_event(event, name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mousedown',\n",
+ " on_mouse_event_closure('button_press')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseup',\n",
+ " on_mouse_event_closure('button_release')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'dblclick',\n",
+ " on_mouse_event_closure('dblclick')\n",
+ " );\n",
+ " // Throttle sequential mouse events to 1 every 20ms.\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mousemove',\n",
+ " on_mouse_event_closure('motion_notify')\n",
+ " );\n",
+ "\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseenter',\n",
+ " on_mouse_event_closure('figure_enter')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseleave',\n",
+ " on_mouse_event_closure('figure_leave')\n",
+ " );\n",
+ "\n",
+ " canvas_div.addEventListener('wheel', function (event) {\n",
+ " if (event.deltaY < 0) {\n",
+ " event.step = 1;\n",
+ " } else {\n",
+ " event.step = -1;\n",
+ " }\n",
+ " on_mouse_event_closure('scroll')(event);\n",
+ " });\n",
+ "\n",
+ " canvas_div.appendChild(canvas);\n",
+ " canvas_div.appendChild(rubberband_canvas);\n",
+ "\n",
+ " this.rubberband_context = rubberband_canvas.getContext('2d');\n",
+ " this.rubberband_context.strokeStyle = '#000000';\n",
+ "\n",
+ " this._resize_canvas = function (width, height, forward) {\n",
+ " if (forward) {\n",
+ " canvas_div.style.width = width + 'px';\n",
+ " canvas_div.style.height = height + 'px';\n",
+ " }\n",
+ " };\n",
+ "\n",
+ " // Disable right mouse context menu.\n",
+ " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n",
+ " event.preventDefault();\n",
+ " return false;\n",
+ " });\n",
+ "\n",
+ " function set_focus() {\n",
+ " canvas.focus();\n",
+ " canvas_div.focus();\n",
+ " }\n",
+ "\n",
+ " window.setTimeout(set_focus, 100);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_toolbar = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var toolbar = document.createElement('div');\n",
+ " toolbar.classList = 'mpl-toolbar';\n",
+ " this.root.appendChild(toolbar);\n",
+ "\n",
+ " function on_click_closure(name) {\n",
+ " return function (_event) {\n",
+ " return fig.toolbar_button_onclick(name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " function on_mouseover_closure(tooltip) {\n",
+ " return function (event) {\n",
+ " if (!event.currentTarget.disabled) {\n",
+ " return fig.toolbar_button_onmouseover(tooltip);\n",
+ " }\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " fig.buttons = {};\n",
+ " var buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'mpl-button-group';\n",
+ " for (var toolbar_ind in mpl.toolbar_items) {\n",
+ " var name = mpl.toolbar_items[toolbar_ind][0];\n",
+ " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+ " var image = mpl.toolbar_items[toolbar_ind][2];\n",
+ " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+ "\n",
+ " if (!name) {\n",
+ " /* Instead of a spacer, we start a new button group. */\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ " buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'mpl-button-group';\n",
+ " continue;\n",
+ " }\n",
+ "\n",
+ " var button = (fig.buttons[name] = document.createElement('button'));\n",
+ " button.classList = 'mpl-widget';\n",
+ " button.setAttribute('role', 'button');\n",
+ " button.setAttribute('aria-disabled', 'false');\n",
+ " button.addEventListener('click', on_click_closure(method_name));\n",
+ " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+ "\n",
+ " var icon_img = document.createElement('img');\n",
+ " icon_img.src = '_images/' + image + '.png';\n",
+ " icon_img.srcset = '_images/' + image + '_large.png 2x';\n",
+ " icon_img.alt = tooltip;\n",
+ " button.appendChild(icon_img);\n",
+ "\n",
+ " buttonGroup.appendChild(button);\n",
+ " }\n",
+ "\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ "\n",
+ " var fmt_picker = document.createElement('select');\n",
+ " fmt_picker.classList = 'mpl-widget';\n",
+ " toolbar.appendChild(fmt_picker);\n",
+ " this.format_dropdown = fmt_picker;\n",
+ "\n",
+ " for (var ind in mpl.extensions) {\n",
+ " var fmt = mpl.extensions[ind];\n",
+ " var option = document.createElement('option');\n",
+ " option.selected = fmt === mpl.default_extension;\n",
+ " option.innerHTML = fmt;\n",
+ " fmt_picker.appendChild(option);\n",
+ " }\n",
+ "\n",
+ " var status_bar = document.createElement('span');\n",
+ " status_bar.classList = 'mpl-message';\n",
+ " toolbar.appendChild(status_bar);\n",
+ " this.message = status_bar;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n",
+ " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
+ " // which will in turn request a refresh of the image.\n",
+ " this.send_message('resize', { width: x_pixels, height: y_pixels });\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.send_message = function (type, properties) {\n",
+ " properties['type'] = type;\n",
+ " properties['figure_id'] = this.id;\n",
+ " this.ws.send(JSON.stringify(properties));\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.send_draw_message = function () {\n",
+ " if (!this.waiting) {\n",
+ " this.waiting = true;\n",
+ " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+ " var format_dropdown = fig.format_dropdown;\n",
+ " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
+ " fig.ondownload(fig, format);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_resize = function (fig, msg) {\n",
+ " var size = msg['size'];\n",
+ " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n",
+ " fig._resize_canvas(size[0], size[1], msg['forward']);\n",
+ " fig.send_message('refresh', {});\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n",
+ " var x0 = msg['x0'] / fig.ratio;\n",
+ " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n",
+ " var x1 = msg['x1'] / fig.ratio;\n",
+ " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n",
+ " x0 = Math.floor(x0) + 0.5;\n",
+ " y0 = Math.floor(y0) + 0.5;\n",
+ " x1 = Math.floor(x1) + 0.5;\n",
+ " y1 = Math.floor(y1) + 0.5;\n",
+ " var min_x = Math.min(x0, x1);\n",
+ " var min_y = Math.min(y0, y1);\n",
+ " var width = Math.abs(x1 - x0);\n",
+ " var height = Math.abs(y1 - y0);\n",
+ "\n",
+ " fig.rubberband_context.clearRect(\n",
+ " 0,\n",
+ " 0,\n",
+ " fig.canvas.width / fig.ratio,\n",
+ " fig.canvas.height / fig.ratio\n",
+ " );\n",
+ "\n",
+ " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n",
+ " // Updates the figure title.\n",
+ " fig.header.textContent = msg['label'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n",
+ " fig.rubberband_canvas.style.cursor = msg['cursor'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_message = function (fig, msg) {\n",
+ " fig.message.textContent = msg['message'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n",
+ " // Request the server to send over a new figure.\n",
+ " fig.send_draw_message();\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n",
+ " fig.image_mode = msg['mode'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n",
+ " for (var key in msg) {\n",
+ " if (!(key in fig.buttons)) {\n",
+ " continue;\n",
+ " }\n",
+ " fig.buttons[key].disabled = !msg[key];\n",
+ " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n",
+ " if (msg['mode'] === 'PAN') {\n",
+ " fig.buttons['Pan'].classList.add('active');\n",
+ " fig.buttons['Zoom'].classList.remove('active');\n",
+ " } else if (msg['mode'] === 'ZOOM') {\n",
+ " fig.buttons['Pan'].classList.remove('active');\n",
+ " fig.buttons['Zoom'].classList.add('active');\n",
+ " } else {\n",
+ " fig.buttons['Pan'].classList.remove('active');\n",
+ " fig.buttons['Zoom'].classList.remove('active');\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.updated_canvas_event = function () {\n",
+ " // Called whenever the canvas gets updated.\n",
+ " this.send_message('ack', {});\n",
+ "};\n",
+ "\n",
+ "// A function to construct a web socket function for onmessage handling.\n",
+ "// Called in the figure constructor.\n",
+ "mpl.figure.prototype._make_on_message_function = function (fig) {\n",
+ " return function socket_on_message(evt) {\n",
+ " if (evt.data instanceof Blob) {\n",
+ " var img = evt.data;\n",
+ " if (img.type !== 'image/png') {\n",
+ " /* FIXME: We get \"Resource interpreted as Image but\n",
+ " * transferred with MIME type text/plain:\" errors on\n",
+ " * Chrome. But how to set the MIME type? It doesn't seem\n",
+ " * to be part of the websocket stream */\n",
+ " img.type = 'image/png';\n",
+ " }\n",
+ "\n",
+ " /* Free the memory for the previous frames */\n",
+ " if (fig.imageObj.src) {\n",
+ " (window.URL || window.webkitURL).revokeObjectURL(\n",
+ " fig.imageObj.src\n",
+ " );\n",
+ " }\n",
+ "\n",
+ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
+ " img\n",
+ " );\n",
+ " fig.updated_canvas_event();\n",
+ " fig.waiting = false;\n",
+ " return;\n",
+ " } else if (\n",
+ " typeof evt.data === 'string' &&\n",
+ " evt.data.slice(0, 21) === 'data:image/png;base64'\n",
+ " ) {\n",
+ " fig.imageObj.src = evt.data;\n",
+ " fig.updated_canvas_event();\n",
+ " fig.waiting = false;\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " var msg = JSON.parse(evt.data);\n",
+ " var msg_type = msg['type'];\n",
+ "\n",
+ " // Call the \"handle_{type}\" callback, which takes\n",
+ " // the figure and JSON message as its only arguments.\n",
+ " try {\n",
+ " var callback = fig['handle_' + msg_type];\n",
+ " } catch (e) {\n",
+ " console.log(\n",
+ " \"No handler for the '\" + msg_type + \"' message type: \",\n",
+ " msg\n",
+ " );\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " if (callback) {\n",
+ " try {\n",
+ " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
+ " callback(fig, msg);\n",
+ " } catch (e) {\n",
+ " console.log(\n",
+ " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n",
+ " e,\n",
+ " e.stack,\n",
+ " msg\n",
+ " );\n",
+ " }\n",
+ " }\n",
+ " };\n",
+ "};\n",
+ "\n",
+ "// from https://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
+ "mpl.findpos = function (e) {\n",
+ " //this section is from http://www.quirksmode.org/js/events_properties.html\n",
+ " var targ;\n",
+ " if (!e) {\n",
+ " e = window.event;\n",
+ " }\n",
+ " if (e.target) {\n",
+ " targ = e.target;\n",
+ " } else if (e.srcElement) {\n",
+ " targ = e.srcElement;\n",
+ " }\n",
+ " if (targ.nodeType === 3) {\n",
+ " // defeat Safari bug\n",
+ " targ = targ.parentNode;\n",
+ " }\n",
+ "\n",
+ " // pageX,Y are the mouse positions relative to the document\n",
+ " var boundingRect = targ.getBoundingClientRect();\n",
+ " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n",
+ " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n",
+ "\n",
+ " return { x: x, y: y };\n",
+ "};\n",
+ "\n",
+ "/*\n",
+ " * return a copy of an object with only non-object keys\n",
+ " * we need this to avoid circular references\n",
+ " * https://stackoverflow.com/a/24161582/3208463\n",
+ " */\n",
+ "function simpleKeys(original) {\n",
+ " return Object.keys(original).reduce(function (obj, key) {\n",
+ " if (typeof original[key] !== 'object') {\n",
+ " obj[key] = original[key];\n",
+ " }\n",
+ " return obj;\n",
+ " }, {});\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.mouse_event = function (event, name) {\n",
+ " var canvas_pos = mpl.findpos(event);\n",
+ "\n",
+ " if (name === 'button_press') {\n",
+ " this.canvas.focus();\n",
+ " this.canvas_div.focus();\n",
+ " }\n",
+ "\n",
+ " var x = canvas_pos.x * this.ratio;\n",
+ " var y = canvas_pos.y * this.ratio;\n",
+ "\n",
+ " this.send_message(name, {\n",
+ " x: x,\n",
+ " y: y,\n",
+ " button: event.button,\n",
+ " step: event.step,\n",
+ " guiEvent: simpleKeys(event),\n",
+ " });\n",
+ "\n",
+ " /* This prevents the web browser from automatically changing to\n",
+ " * the text insertion cursor when the button is pressed. We want\n",
+ " * to control all of the cursor setting manually through the\n",
+ " * 'cursor' event from matplotlib */\n",
+ " event.preventDefault();\n",
+ " return false;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n",
+ " // Handle any extra behaviour associated with a key event\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.key_event = function (event, name) {\n",
+ " // Prevent repeat events\n",
+ " if (name === 'key_press') {\n",
+ " if (event.key === this._key) {\n",
+ " return;\n",
+ " } else {\n",
+ " this._key = event.key;\n",
+ " }\n",
+ " }\n",
+ " if (name === 'key_release') {\n",
+ " this._key = null;\n",
+ " }\n",
+ "\n",
+ " var value = '';\n",
+ " if (event.ctrlKey && event.key !== 'Control') {\n",
+ " value += 'ctrl+';\n",
+ " }\n",
+ " else if (event.altKey && event.key !== 'Alt') {\n",
+ " value += 'alt+';\n",
+ " }\n",
+ " else if (event.shiftKey && event.key !== 'Shift') {\n",
+ " value += 'shift+';\n",
+ " }\n",
+ "\n",
+ " value += 'k' + event.key;\n",
+ "\n",
+ " this._key_event_extra(event, name);\n",
+ "\n",
+ " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n",
+ " return false;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n",
+ " if (name === 'download') {\n",
+ " this.handle_save(this, null);\n",
+ " } else {\n",
+ " this.send_message('toolbar_button', { name: name });\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n",
+ " this.message.textContent = tooltip;\n",
+ "};\n",
+ "\n",
+ "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n",
+ "// prettier-ignore\n",
+ "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n",
+ "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
+ "\n",
+ "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
+ "\n",
+ "mpl.default_extension = \"png\";/* global mpl */\n",
+ "\n",
+ "var comm_websocket_adapter = function (comm) {\n",
+ " // Create a \"websocket\"-like object which calls the given IPython comm\n",
+ " // object with the appropriate methods. Currently this is a non binary\n",
+ " // socket, so there is still some room for performance tuning.\n",
+ " var ws = {};\n",
+ "\n",
+ " ws.binaryType = comm.kernel.ws.binaryType;\n",
+ " ws.readyState = comm.kernel.ws.readyState;\n",
+ " function updateReadyState(_event) {\n",
+ " if (comm.kernel.ws) {\n",
+ " ws.readyState = comm.kernel.ws.readyState;\n",
+ " } else {\n",
+ " ws.readyState = 3; // Closed state.\n",
+ " }\n",
+ " }\n",
+ " comm.kernel.ws.addEventListener('open', updateReadyState);\n",
+ " comm.kernel.ws.addEventListener('close', updateReadyState);\n",
+ " comm.kernel.ws.addEventListener('error', updateReadyState);\n",
+ "\n",
+ " ws.close = function () {\n",
+ " comm.close();\n",
+ " };\n",
+ " ws.send = function (m) {\n",
+ " //console.log('sending', m);\n",
+ " comm.send(m);\n",
+ " };\n",
+ " // Register the callback with on_msg.\n",
+ " comm.on_msg(function (msg) {\n",
+ " //console.log('receiving', msg['content']['data'], msg);\n",
+ " var data = msg['content']['data'];\n",
+ " if (data['blob'] !== undefined) {\n",
+ " data = {\n",
+ " data: new Blob(msg['buffers'], { type: data['blob'] }),\n",
+ " };\n",
+ " }\n",
+ " // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
+ " ws.onmessage(data);\n",
+ " });\n",
+ " return ws;\n",
+ "};\n",
+ "\n",
+ "mpl.mpl_figure_comm = function (comm, msg) {\n",
+ " // This is the function which gets called when the mpl process\n",
+ " // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
+ "\n",
+ " var id = msg.content.data.id;\n",
+ " // Get hold of the div created by the display call when the Comm\n",
+ " // socket was opened in Python.\n",
+ " var element = document.getElementById(id);\n",
+ " var ws_proxy = comm_websocket_adapter(comm);\n",
+ "\n",
+ " function ondownload(figure, _format) {\n",
+ " window.open(figure.canvas.toDataURL());\n",
+ " }\n",
+ "\n",
+ " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n",
+ "\n",
+ " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
+ " // web socket which is closed, not our websocket->open comm proxy.\n",
+ " ws_proxy.onopen();\n",
+ "\n",
+ " fig.parent_element = element;\n",
+ " fig.cell_info = mpl.find_output_cell(\"\");\n",
+ " if (!fig.cell_info) {\n",
+ " console.error('Failed to find cell for figure', id, fig);\n",
+ " return;\n",
+ " }\n",
+ " fig.cell_info[0].output_area.element.on(\n",
+ " 'cleared',\n",
+ " { fig: fig },\n",
+ " fig._remove_fig_handler\n",
+ " );\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_close = function (fig, msg) {\n",
+ " var width = fig.canvas.width / fig.ratio;\n",
+ " fig.cell_info[0].output_area.element.off(\n",
+ " 'cleared',\n",
+ " fig._remove_fig_handler\n",
+ " );\n",
+ " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n",
+ "\n",
+ " // Update the output cell to use the data from the current canvas.\n",
+ " fig.push_to_output();\n",
+ " var dataURL = fig.canvas.toDataURL();\n",
+ " // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
+ " // the notebook keyboard shortcuts fail.\n",
+ " IPython.keyboard_manager.enable();\n",
+ " fig.parent_element.innerHTML =\n",
+ " '
';\n",
+ " fig.close_ws(fig, msg);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.close_ws = function (fig, msg) {\n",
+ " fig.send_message('closing', msg);\n",
+ " // fig.ws.close()\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n",
+ " // Turn the data on the canvas into data in the output cell.\n",
+ " var width = this.canvas.width / this.ratio;\n",
+ " var dataURL = this.canvas.toDataURL();\n",
+ " this.cell_info[1]['text/html'] =\n",
+ " '
';\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.updated_canvas_event = function () {\n",
+ " // Tell IPython that the notebook contents must change.\n",
+ " IPython.notebook.set_dirty(true);\n",
+ " this.send_message('ack', {});\n",
+ " var fig = this;\n",
+ " // Wait a second, then push the new image to the DOM so\n",
+ " // that it is saved nicely (might be nice to debounce this).\n",
+ " setTimeout(function () {\n",
+ " fig.push_to_output();\n",
+ " }, 1000);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_toolbar = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var toolbar = document.createElement('div');\n",
+ " toolbar.classList = 'btn-toolbar';\n",
+ " this.root.appendChild(toolbar);\n",
+ "\n",
+ " function on_click_closure(name) {\n",
+ " return function (_event) {\n",
+ " return fig.toolbar_button_onclick(name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " function on_mouseover_closure(tooltip) {\n",
+ " return function (event) {\n",
+ " if (!event.currentTarget.disabled) {\n",
+ " return fig.toolbar_button_onmouseover(tooltip);\n",
+ " }\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " fig.buttons = {};\n",
+ " var buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'btn-group';\n",
+ " var button;\n",
+ " for (var toolbar_ind in mpl.toolbar_items) {\n",
+ " var name = mpl.toolbar_items[toolbar_ind][0];\n",
+ " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+ " var image = mpl.toolbar_items[toolbar_ind][2];\n",
+ " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+ "\n",
+ " if (!name) {\n",
+ " /* Instead of a spacer, we start a new button group. */\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ " buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'btn-group';\n",
+ " continue;\n",
+ " }\n",
+ "\n",
+ " button = fig.buttons[name] = document.createElement('button');\n",
+ " button.classList = 'btn btn-default';\n",
+ " button.href = '#';\n",
+ " button.title = name;\n",
+ " button.innerHTML = '';\n",
+ " button.addEventListener('click', on_click_closure(method_name));\n",
+ " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+ " buttonGroup.appendChild(button);\n",
+ " }\n",
+ "\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ "\n",
+ " // Add the status bar.\n",
+ " var status_bar = document.createElement('span');\n",
+ " status_bar.classList = 'mpl-message pull-right';\n",
+ " toolbar.appendChild(status_bar);\n",
+ " this.message = status_bar;\n",
+ "\n",
+ " // Add the close button to the window.\n",
+ " var buttongrp = document.createElement('div');\n",
+ " buttongrp.classList = 'btn-group inline pull-right';\n",
+ " button = document.createElement('button');\n",
+ " button.classList = 'btn btn-mini btn-primary';\n",
+ " button.href = '#';\n",
+ " button.title = 'Stop Interaction';\n",
+ " button.innerHTML = '';\n",
+ " button.addEventListener('click', function (_evt) {\n",
+ " fig.handle_close(fig, {});\n",
+ " });\n",
+ " button.addEventListener(\n",
+ " 'mouseover',\n",
+ " on_mouseover_closure('Stop Interaction')\n",
+ " );\n",
+ " buttongrp.appendChild(button);\n",
+ " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n",
+ " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._remove_fig_handler = function (event) {\n",
+ " var fig = event.data.fig;\n",
+ " if (event.target !== this) {\n",
+ " // Ignore bubbled events from children.\n",
+ " return;\n",
+ " }\n",
+ " fig.close_ws(fig, {});\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._root_extra_style = function (el) {\n",
+ " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._canvas_extra_style = function (el) {\n",
+ " // this is important to make the div 'focusable\n",
+ " el.setAttribute('tabindex', 0);\n",
+ " // reach out to IPython and tell the keyboard manager to turn it's self\n",
+ " // off when our div gets focus\n",
+ "\n",
+ " // location in version 3\n",
+ " if (IPython.notebook.keyboard_manager) {\n",
+ " IPython.notebook.keyboard_manager.register_events(el);\n",
+ " } else {\n",
+ " // location in version 2\n",
+ " IPython.keyboard_manager.register_events(el);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._key_event_extra = function (event, _name) {\n",
+ " // Check for shift+enter\n",
+ " if (event.shiftKey && event.which === 13) {\n",
+ " this.canvas_div.blur();\n",
+ " // select the cell after this one\n",
+ " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n",
+ " IPython.notebook.select(index + 1);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+ " fig.ondownload(fig, null);\n",
+ "};\n",
+ "\n",
+ "mpl.find_output_cell = function (html_output) {\n",
+ " // Return the cell and output element which can be found *uniquely* in the notebook.\n",
+ " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
+ " // IPython event is triggered only after the cells have been serialised, which for\n",
+ " // our purposes (turning an active figure into a static one), is too late.\n",
+ " var cells = IPython.notebook.get_cells();\n",
+ " var ncells = cells.length;\n",
+ " for (var i = 0; i < ncells; i++) {\n",
+ " var cell = cells[i];\n",
+ " if (cell.cell_type === 'code') {\n",
+ " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n",
+ " var data = cell.output_area.outputs[j];\n",
+ " if (data.data) {\n",
+ " // IPython >= 3 moved mimebundle to data attribute of output\n",
+ " data = data.data;\n",
+ " }\n",
+ " if (data['text/html'] === html_output) {\n",
+ " return [cell, data, j];\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "// Register the function which deals with the matplotlib target/channel.\n",
+ "// The kernel may be null if the page has been refreshed.\n",
+ "if (IPython.notebook.kernel !== null) {\n",
+ " IPython.notebook.kernel.comm_manager.register_target(\n",
+ " 'matplotlib',\n",
+ " mpl.mpl_figure_comm\n",
+ " );\n",
+ "}\n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "
"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " mean std num\n",
+ "1x0 isilon 38.570352 4.155044 20\n",
+ " irods_on_isilon 58.561781 28.682517 19\n",
+ " irods_via_davrods_on_isilon 45.970737 18.748706 18\n",
+ "1x1K isilon 61.779090 5.862412 19\n",
+ " irods_on_isilon 100.219069 1.270263 19\n",
+ " irods_via_davrods_on_isilon 100.057903 1.046998 19\n",
+ "1x1M isilon 61.689178 5.719017 19\n",
+ " irods_on_isilon 100.629799 1.505577 17\n",
+ " irods_via_davrods_on_isilon 100.621282 1.388649 19\n",
+ "1x1G isilon 65.067275 8.125209 18\n",
+ " irods_on_isilon 130.385827 7.590377 18\n",
+ " irods_via_davrods_on_isilon 100.155441 1.003477 18\n",
+ "10x1G isilon 259.802910 5.809441 9\n",
+ " irods_on_isilon 898.671291 14.834686 9\n",
+ " irods_via_davrods_on_isilon 348.729399 7.039795 9\n"
+ ]
+ },
+ {
+ "data": {
+ "application/javascript": [
+ "/* Put everything inside the global mpl namespace */\n",
+ "/* global mpl */\n",
+ "window.mpl = {};\n",
+ "\n",
+ "mpl.get_websocket_type = function () {\n",
+ " if (typeof WebSocket !== 'undefined') {\n",
+ " return WebSocket;\n",
+ " } else if (typeof MozWebSocket !== 'undefined') {\n",
+ " return MozWebSocket;\n",
+ " } else {\n",
+ " alert(\n",
+ " 'Your browser does not have WebSocket support. ' +\n",
+ " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
+ " 'Firefox 4 and 5 are also supported but you ' +\n",
+ " 'have to enable WebSockets in about:config.'\n",
+ " );\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n",
+ " this.id = figure_id;\n",
+ "\n",
+ " this.ws = websocket;\n",
+ "\n",
+ " this.supports_binary = this.ws.binaryType !== undefined;\n",
+ "\n",
+ " if (!this.supports_binary) {\n",
+ " var warnings = document.getElementById('mpl-warnings');\n",
+ " if (warnings) {\n",
+ " warnings.style.display = 'block';\n",
+ " warnings.textContent =\n",
+ " 'This browser does not support binary websocket messages. ' +\n",
+ " 'Performance may be slow.';\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " this.imageObj = new Image();\n",
+ "\n",
+ " this.context = undefined;\n",
+ " this.message = undefined;\n",
+ " this.canvas = undefined;\n",
+ " this.rubberband_canvas = undefined;\n",
+ " this.rubberband_context = undefined;\n",
+ " this.format_dropdown = undefined;\n",
+ "\n",
+ " this.image_mode = 'full';\n",
+ "\n",
+ " this.root = document.createElement('div');\n",
+ " this.root.setAttribute('style', 'display: inline-block');\n",
+ " this._root_extra_style(this.root);\n",
+ "\n",
+ " parent_element.appendChild(this.root);\n",
+ "\n",
+ " this._init_header(this);\n",
+ " this._init_canvas(this);\n",
+ " this._init_toolbar(this);\n",
+ "\n",
+ " var fig = this;\n",
+ "\n",
+ " this.waiting = false;\n",
+ "\n",
+ " this.ws.onopen = function () {\n",
+ " fig.send_message('supports_binary', { value: fig.supports_binary });\n",
+ " fig.send_message('send_image_mode', {});\n",
+ " if (fig.ratio !== 1) {\n",
+ " fig.send_message('set_device_pixel_ratio', {\n",
+ " device_pixel_ratio: fig.ratio,\n",
+ " });\n",
+ " }\n",
+ " fig.send_message('refresh', {});\n",
+ " };\n",
+ "\n",
+ " this.imageObj.onload = function () {\n",
+ " if (fig.image_mode === 'full') {\n",
+ " // Full images could contain transparency (where diff images\n",
+ " // almost always do), so we need to clear the canvas so that\n",
+ " // there is no ghosting.\n",
+ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
+ " }\n",
+ " fig.context.drawImage(fig.imageObj, 0, 0);\n",
+ " };\n",
+ "\n",
+ " this.imageObj.onunload = function () {\n",
+ " fig.ws.close();\n",
+ " };\n",
+ "\n",
+ " this.ws.onmessage = this._make_on_message_function(this);\n",
+ "\n",
+ " this.ondownload = ondownload;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_header = function () {\n",
+ " var titlebar = document.createElement('div');\n",
+ " titlebar.classList =\n",
+ " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n",
+ " var titletext = document.createElement('div');\n",
+ " titletext.classList = 'ui-dialog-title';\n",
+ " titletext.setAttribute(\n",
+ " 'style',\n",
+ " 'width: 100%; text-align: center; padding: 3px;'\n",
+ " );\n",
+ " titlebar.appendChild(titletext);\n",
+ " this.root.appendChild(titlebar);\n",
+ " this.header = titletext;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n",
+ "\n",
+ "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n",
+ "\n",
+ "mpl.figure.prototype._init_canvas = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var canvas_div = (this.canvas_div = document.createElement('div'));\n",
+ " canvas_div.setAttribute(\n",
+ " 'style',\n",
+ " 'border: 1px solid #ddd;' +\n",
+ " 'box-sizing: content-box;' +\n",
+ " 'clear: both;' +\n",
+ " 'min-height: 1px;' +\n",
+ " 'min-width: 1px;' +\n",
+ " 'outline: 0;' +\n",
+ " 'overflow: hidden;' +\n",
+ " 'position: relative;' +\n",
+ " 'resize: both;'\n",
+ " );\n",
+ "\n",
+ " function on_keyboard_event_closure(name) {\n",
+ " return function (event) {\n",
+ " return fig.key_event(event, name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " canvas_div.addEventListener(\n",
+ " 'keydown',\n",
+ " on_keyboard_event_closure('key_press')\n",
+ " );\n",
+ " canvas_div.addEventListener(\n",
+ " 'keyup',\n",
+ " on_keyboard_event_closure('key_release')\n",
+ " );\n",
+ "\n",
+ " this._canvas_extra_style(canvas_div);\n",
+ " this.root.appendChild(canvas_div);\n",
+ "\n",
+ " var canvas = (this.canvas = document.createElement('canvas'));\n",
+ " canvas.classList.add('mpl-canvas');\n",
+ " canvas.setAttribute('style', 'box-sizing: content-box;');\n",
+ "\n",
+ " this.context = canvas.getContext('2d');\n",
+ "\n",
+ " var backingStore =\n",
+ " this.context.backingStorePixelRatio ||\n",
+ " this.context.webkitBackingStorePixelRatio ||\n",
+ " this.context.mozBackingStorePixelRatio ||\n",
+ " this.context.msBackingStorePixelRatio ||\n",
+ " this.context.oBackingStorePixelRatio ||\n",
+ " this.context.backingStorePixelRatio ||\n",
+ " 1;\n",
+ "\n",
+ " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
+ "\n",
+ " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n",
+ " 'canvas'\n",
+ " ));\n",
+ " rubberband_canvas.setAttribute(\n",
+ " 'style',\n",
+ " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n",
+ " );\n",
+ "\n",
+ " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n",
+ " if (this.ResizeObserver === undefined) {\n",
+ " if (window.ResizeObserver !== undefined) {\n",
+ " this.ResizeObserver = window.ResizeObserver;\n",
+ " } else {\n",
+ " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n",
+ " this.ResizeObserver = obs.ResizeObserver;\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n",
+ " var nentries = entries.length;\n",
+ " for (var i = 0; i < nentries; i++) {\n",
+ " var entry = entries[i];\n",
+ " var width, height;\n",
+ " if (entry.contentBoxSize) {\n",
+ " if (entry.contentBoxSize instanceof Array) {\n",
+ " // Chrome 84 implements new version of spec.\n",
+ " width = entry.contentBoxSize[0].inlineSize;\n",
+ " height = entry.contentBoxSize[0].blockSize;\n",
+ " } else {\n",
+ " // Firefox implements old version of spec.\n",
+ " width = entry.contentBoxSize.inlineSize;\n",
+ " height = entry.contentBoxSize.blockSize;\n",
+ " }\n",
+ " } else {\n",
+ " // Chrome <84 implements even older version of spec.\n",
+ " width = entry.contentRect.width;\n",
+ " height = entry.contentRect.height;\n",
+ " }\n",
+ "\n",
+ " // Keep the size of the canvas and rubber band canvas in sync with\n",
+ " // the canvas container.\n",
+ " if (entry.devicePixelContentBoxSize) {\n",
+ " // Chrome 84 implements new version of spec.\n",
+ " canvas.setAttribute(\n",
+ " 'width',\n",
+ " entry.devicePixelContentBoxSize[0].inlineSize\n",
+ " );\n",
+ " canvas.setAttribute(\n",
+ " 'height',\n",
+ " entry.devicePixelContentBoxSize[0].blockSize\n",
+ " );\n",
+ " } else {\n",
+ " canvas.setAttribute('width', width * fig.ratio);\n",
+ " canvas.setAttribute('height', height * fig.ratio);\n",
+ " }\n",
+ " canvas.setAttribute(\n",
+ " 'style',\n",
+ " 'width: ' + width + 'px; height: ' + height + 'px;'\n",
+ " );\n",
+ "\n",
+ " rubberband_canvas.setAttribute('width', width);\n",
+ " rubberband_canvas.setAttribute('height', height);\n",
+ "\n",
+ " // And update the size in Python. We ignore the initial 0/0 size\n",
+ " // that occurs as the element is placed into the DOM, which should\n",
+ " // otherwise not happen due to the minimum size styling.\n",
+ " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n",
+ " fig.request_resize(width, height);\n",
+ " }\n",
+ " }\n",
+ " });\n",
+ " this.resizeObserverInstance.observe(canvas_div);\n",
+ "\n",
+ " function on_mouse_event_closure(name) {\n",
+ " return function (event) {\n",
+ " return fig.mouse_event(event, name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mousedown',\n",
+ " on_mouse_event_closure('button_press')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseup',\n",
+ " on_mouse_event_closure('button_release')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'dblclick',\n",
+ " on_mouse_event_closure('dblclick')\n",
+ " );\n",
+ " // Throttle sequential mouse events to 1 every 20ms.\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mousemove',\n",
+ " on_mouse_event_closure('motion_notify')\n",
+ " );\n",
+ "\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseenter',\n",
+ " on_mouse_event_closure('figure_enter')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseleave',\n",
+ " on_mouse_event_closure('figure_leave')\n",
+ " );\n",
+ "\n",
+ " canvas_div.addEventListener('wheel', function (event) {\n",
+ " if (event.deltaY < 0) {\n",
+ " event.step = 1;\n",
+ " } else {\n",
+ " event.step = -1;\n",
+ " }\n",
+ " on_mouse_event_closure('scroll')(event);\n",
+ " });\n",
+ "\n",
+ " canvas_div.appendChild(canvas);\n",
+ " canvas_div.appendChild(rubberband_canvas);\n",
+ "\n",
+ " this.rubberband_context = rubberband_canvas.getContext('2d');\n",
+ " this.rubberband_context.strokeStyle = '#000000';\n",
+ "\n",
+ " this._resize_canvas = function (width, height, forward) {\n",
+ " if (forward) {\n",
+ " canvas_div.style.width = width + 'px';\n",
+ " canvas_div.style.height = height + 'px';\n",
+ " }\n",
+ " };\n",
+ "\n",
+ " // Disable right mouse context menu.\n",
+ " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n",
+ " event.preventDefault();\n",
+ " return false;\n",
+ " });\n",
+ "\n",
+ " function set_focus() {\n",
+ " canvas.focus();\n",
+ " canvas_div.focus();\n",
+ " }\n",
+ "\n",
+ " window.setTimeout(set_focus, 100);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_toolbar = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var toolbar = document.createElement('div');\n",
+ " toolbar.classList = 'mpl-toolbar';\n",
+ " this.root.appendChild(toolbar);\n",
+ "\n",
+ " function on_click_closure(name) {\n",
+ " return function (_event) {\n",
+ " return fig.toolbar_button_onclick(name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " function on_mouseover_closure(tooltip) {\n",
+ " return function (event) {\n",
+ " if (!event.currentTarget.disabled) {\n",
+ " return fig.toolbar_button_onmouseover(tooltip);\n",
+ " }\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " fig.buttons = {};\n",
+ " var buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'mpl-button-group';\n",
+ " for (var toolbar_ind in mpl.toolbar_items) {\n",
+ " var name = mpl.toolbar_items[toolbar_ind][0];\n",
+ " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+ " var image = mpl.toolbar_items[toolbar_ind][2];\n",
+ " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+ "\n",
+ " if (!name) {\n",
+ " /* Instead of a spacer, we start a new button group. */\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ " buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'mpl-button-group';\n",
+ " continue;\n",
+ " }\n",
+ "\n",
+ " var button = (fig.buttons[name] = document.createElement('button'));\n",
+ " button.classList = 'mpl-widget';\n",
+ " button.setAttribute('role', 'button');\n",
+ " button.setAttribute('aria-disabled', 'false');\n",
+ " button.addEventListener('click', on_click_closure(method_name));\n",
+ " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+ "\n",
+ " var icon_img = document.createElement('img');\n",
+ " icon_img.src = '_images/' + image + '.png';\n",
+ " icon_img.srcset = '_images/' + image + '_large.png 2x';\n",
+ " icon_img.alt = tooltip;\n",
+ " button.appendChild(icon_img);\n",
+ "\n",
+ " buttonGroup.appendChild(button);\n",
+ " }\n",
+ "\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ "\n",
+ " var fmt_picker = document.createElement('select');\n",
+ " fmt_picker.classList = 'mpl-widget';\n",
+ " toolbar.appendChild(fmt_picker);\n",
+ " this.format_dropdown = fmt_picker;\n",
+ "\n",
+ " for (var ind in mpl.extensions) {\n",
+ " var fmt = mpl.extensions[ind];\n",
+ " var option = document.createElement('option');\n",
+ " option.selected = fmt === mpl.default_extension;\n",
+ " option.innerHTML = fmt;\n",
+ " fmt_picker.appendChild(option);\n",
+ " }\n",
+ "\n",
+ " var status_bar = document.createElement('span');\n",
+ " status_bar.classList = 'mpl-message';\n",
+ " toolbar.appendChild(status_bar);\n",
+ " this.message = status_bar;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n",
+ " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
+ " // which will in turn request a refresh of the image.\n",
+ " this.send_message('resize', { width: x_pixels, height: y_pixels });\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.send_message = function (type, properties) {\n",
+ " properties['type'] = type;\n",
+ " properties['figure_id'] = this.id;\n",
+ " this.ws.send(JSON.stringify(properties));\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.send_draw_message = function () {\n",
+ " if (!this.waiting) {\n",
+ " this.waiting = true;\n",
+ " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+ " var format_dropdown = fig.format_dropdown;\n",
+ " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
+ " fig.ondownload(fig, format);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_resize = function (fig, msg) {\n",
+ " var size = msg['size'];\n",
+ " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n",
+ " fig._resize_canvas(size[0], size[1], msg['forward']);\n",
+ " fig.send_message('refresh', {});\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n",
+ " var x0 = msg['x0'] / fig.ratio;\n",
+ " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n",
+ " var x1 = msg['x1'] / fig.ratio;\n",
+ " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n",
+ " x0 = Math.floor(x0) + 0.5;\n",
+ " y0 = Math.floor(y0) + 0.5;\n",
+ " x1 = Math.floor(x1) + 0.5;\n",
+ " y1 = Math.floor(y1) + 0.5;\n",
+ " var min_x = Math.min(x0, x1);\n",
+ " var min_y = Math.min(y0, y1);\n",
+ " var width = Math.abs(x1 - x0);\n",
+ " var height = Math.abs(y1 - y0);\n",
+ "\n",
+ " fig.rubberband_context.clearRect(\n",
+ " 0,\n",
+ " 0,\n",
+ " fig.canvas.width / fig.ratio,\n",
+ " fig.canvas.height / fig.ratio\n",
+ " );\n",
+ "\n",
+ " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n",
+ " // Updates the figure title.\n",
+ " fig.header.textContent = msg['label'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n",
+ " fig.rubberband_canvas.style.cursor = msg['cursor'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_message = function (fig, msg) {\n",
+ " fig.message.textContent = msg['message'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n",
+ " // Request the server to send over a new figure.\n",
+ " fig.send_draw_message();\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n",
+ " fig.image_mode = msg['mode'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n",
+ " for (var key in msg) {\n",
+ " if (!(key in fig.buttons)) {\n",
+ " continue;\n",
+ " }\n",
+ " fig.buttons[key].disabled = !msg[key];\n",
+ " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n",
+ " if (msg['mode'] === 'PAN') {\n",
+ " fig.buttons['Pan'].classList.add('active');\n",
+ " fig.buttons['Zoom'].classList.remove('active');\n",
+ " } else if (msg['mode'] === 'ZOOM') {\n",
+ " fig.buttons['Pan'].classList.remove('active');\n",
+ " fig.buttons['Zoom'].classList.add('active');\n",
+ " } else {\n",
+ " fig.buttons['Pan'].classList.remove('active');\n",
+ " fig.buttons['Zoom'].classList.remove('active');\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.updated_canvas_event = function () {\n",
+ " // Called whenever the canvas gets updated.\n",
+ " this.send_message('ack', {});\n",
+ "};\n",
+ "\n",
+ "// A function to construct a web socket function for onmessage handling.\n",
+ "// Called in the figure constructor.\n",
+ "mpl.figure.prototype._make_on_message_function = function (fig) {\n",
+ " return function socket_on_message(evt) {\n",
+ " if (evt.data instanceof Blob) {\n",
+ " var img = evt.data;\n",
+ " if (img.type !== 'image/png') {\n",
+ " /* FIXME: We get \"Resource interpreted as Image but\n",
+ " * transferred with MIME type text/plain:\" errors on\n",
+ " * Chrome. But how to set the MIME type? It doesn't seem\n",
+ " * to be part of the websocket stream */\n",
+ " img.type = 'image/png';\n",
+ " }\n",
+ "\n",
+ " /* Free the memory for the previous frames */\n",
+ " if (fig.imageObj.src) {\n",
+ " (window.URL || window.webkitURL).revokeObjectURL(\n",
+ " fig.imageObj.src\n",
+ " );\n",
+ " }\n",
+ "\n",
+ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
+ " img\n",
+ " );\n",
+ " fig.updated_canvas_event();\n",
+ " fig.waiting = false;\n",
+ " return;\n",
+ " } else if (\n",
+ " typeof evt.data === 'string' &&\n",
+ " evt.data.slice(0, 21) === 'data:image/png;base64'\n",
+ " ) {\n",
+ " fig.imageObj.src = evt.data;\n",
+ " fig.updated_canvas_event();\n",
+ " fig.waiting = false;\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " var msg = JSON.parse(evt.data);\n",
+ " var msg_type = msg['type'];\n",
+ "\n",
+ " // Call the \"handle_{type}\" callback, which takes\n",
+ " // the figure and JSON message as its only arguments.\n",
+ " try {\n",
+ " var callback = fig['handle_' + msg_type];\n",
+ " } catch (e) {\n",
+ " console.log(\n",
+ " \"No handler for the '\" + msg_type + \"' message type: \",\n",
+ " msg\n",
+ " );\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " if (callback) {\n",
+ " try {\n",
+ " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
+ " callback(fig, msg);\n",
+ " } catch (e) {\n",
+ " console.log(\n",
+ " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n",
+ " e,\n",
+ " e.stack,\n",
+ " msg\n",
+ " );\n",
+ " }\n",
+ " }\n",
+ " };\n",
+ "};\n",
+ "\n",
+ "// from https://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
+ "mpl.findpos = function (e) {\n",
+ " //this section is from http://www.quirksmode.org/js/events_properties.html\n",
+ " var targ;\n",
+ " if (!e) {\n",
+ " e = window.event;\n",
+ " }\n",
+ " if (e.target) {\n",
+ " targ = e.target;\n",
+ " } else if (e.srcElement) {\n",
+ " targ = e.srcElement;\n",
+ " }\n",
+ " if (targ.nodeType === 3) {\n",
+ " // defeat Safari bug\n",
+ " targ = targ.parentNode;\n",
+ " }\n",
+ "\n",
+ " // pageX,Y are the mouse positions relative to the document\n",
+ " var boundingRect = targ.getBoundingClientRect();\n",
+ " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n",
+ " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n",
+ "\n",
+ " return { x: x, y: y };\n",
+ "};\n",
+ "\n",
+ "/*\n",
+ " * return a copy of an object with only non-object keys\n",
+ " * we need this to avoid circular references\n",
+ " * https://stackoverflow.com/a/24161582/3208463\n",
+ " */\n",
+ "function simpleKeys(original) {\n",
+ " return Object.keys(original).reduce(function (obj, key) {\n",
+ " if (typeof original[key] !== 'object') {\n",
+ " obj[key] = original[key];\n",
+ " }\n",
+ " return obj;\n",
+ " }, {});\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.mouse_event = function (event, name) {\n",
+ " var canvas_pos = mpl.findpos(event);\n",
+ "\n",
+ " if (name === 'button_press') {\n",
+ " this.canvas.focus();\n",
+ " this.canvas_div.focus();\n",
+ " }\n",
+ "\n",
+ " var x = canvas_pos.x * this.ratio;\n",
+ " var y = canvas_pos.y * this.ratio;\n",
+ "\n",
+ " this.send_message(name, {\n",
+ " x: x,\n",
+ " y: y,\n",
+ " button: event.button,\n",
+ " step: event.step,\n",
+ " guiEvent: simpleKeys(event),\n",
+ " });\n",
+ "\n",
+ " /* This prevents the web browser from automatically changing to\n",
+ " * the text insertion cursor when the button is pressed. We want\n",
+ " * to control all of the cursor setting manually through the\n",
+ " * 'cursor' event from matplotlib */\n",
+ " event.preventDefault();\n",
+ " return false;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n",
+ " // Handle any extra behaviour associated with a key event\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.key_event = function (event, name) {\n",
+ " // Prevent repeat events\n",
+ " if (name === 'key_press') {\n",
+ " if (event.key === this._key) {\n",
+ " return;\n",
+ " } else {\n",
+ " this._key = event.key;\n",
+ " }\n",
+ " }\n",
+ " if (name === 'key_release') {\n",
+ " this._key = null;\n",
+ " }\n",
+ "\n",
+ " var value = '';\n",
+ " if (event.ctrlKey && event.key !== 'Control') {\n",
+ " value += 'ctrl+';\n",
+ " }\n",
+ " else if (event.altKey && event.key !== 'Alt') {\n",
+ " value += 'alt+';\n",
+ " }\n",
+ " else if (event.shiftKey && event.key !== 'Shift') {\n",
+ " value += 'shift+';\n",
+ " }\n",
+ "\n",
+ " value += 'k' + event.key;\n",
+ "\n",
+ " this._key_event_extra(event, name);\n",
+ "\n",
+ " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n",
+ " return false;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n",
+ " if (name === 'download') {\n",
+ " this.handle_save(this, null);\n",
+ " } else {\n",
+ " this.send_message('toolbar_button', { name: name });\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n",
+ " this.message.textContent = tooltip;\n",
+ "};\n",
+ "\n",
+ "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n",
+ "// prettier-ignore\n",
+ "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n",
+ "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
+ "\n",
+ "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
+ "\n",
+ "mpl.default_extension = \"png\";/* global mpl */\n",
+ "\n",
+ "var comm_websocket_adapter = function (comm) {\n",
+ " // Create a \"websocket\"-like object which calls the given IPython comm\n",
+ " // object with the appropriate methods. Currently this is a non binary\n",
+ " // socket, so there is still some room for performance tuning.\n",
+ " var ws = {};\n",
+ "\n",
+ " ws.binaryType = comm.kernel.ws.binaryType;\n",
+ " ws.readyState = comm.kernel.ws.readyState;\n",
+ " function updateReadyState(_event) {\n",
+ " if (comm.kernel.ws) {\n",
+ " ws.readyState = comm.kernel.ws.readyState;\n",
+ " } else {\n",
+ " ws.readyState = 3; // Closed state.\n",
+ " }\n",
+ " }\n",
+ " comm.kernel.ws.addEventListener('open', updateReadyState);\n",
+ " comm.kernel.ws.addEventListener('close', updateReadyState);\n",
+ " comm.kernel.ws.addEventListener('error', updateReadyState);\n",
+ "\n",
+ " ws.close = function () {\n",
+ " comm.close();\n",
+ " };\n",
+ " ws.send = function (m) {\n",
+ " //console.log('sending', m);\n",
+ " comm.send(m);\n",
+ " };\n",
+ " // Register the callback with on_msg.\n",
+ " comm.on_msg(function (msg) {\n",
+ " //console.log('receiving', msg['content']['data'], msg);\n",
+ " var data = msg['content']['data'];\n",
+ " if (data['blob'] !== undefined) {\n",
+ " data = {\n",
+ " data: new Blob(msg['buffers'], { type: data['blob'] }),\n",
+ " };\n",
+ " }\n",
+ " // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
+ " ws.onmessage(data);\n",
+ " });\n",
+ " return ws;\n",
+ "};\n",
+ "\n",
+ "mpl.mpl_figure_comm = function (comm, msg) {\n",
+ " // This is the function which gets called when the mpl process\n",
+ " // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
+ "\n",
+ " var id = msg.content.data.id;\n",
+ " // Get hold of the div created by the display call when the Comm\n",
+ " // socket was opened in Python.\n",
+ " var element = document.getElementById(id);\n",
+ " var ws_proxy = comm_websocket_adapter(comm);\n",
+ "\n",
+ " function ondownload(figure, _format) {\n",
+ " window.open(figure.canvas.toDataURL());\n",
+ " }\n",
+ "\n",
+ " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n",
+ "\n",
+ " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
+ " // web socket which is closed, not our websocket->open comm proxy.\n",
+ " ws_proxy.onopen();\n",
+ "\n",
+ " fig.parent_element = element;\n",
+ " fig.cell_info = mpl.find_output_cell(\"\");\n",
+ " if (!fig.cell_info) {\n",
+ " console.error('Failed to find cell for figure', id, fig);\n",
+ " return;\n",
+ " }\n",
+ " fig.cell_info[0].output_area.element.on(\n",
+ " 'cleared',\n",
+ " { fig: fig },\n",
+ " fig._remove_fig_handler\n",
+ " );\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_close = function (fig, msg) {\n",
+ " var width = fig.canvas.width / fig.ratio;\n",
+ " fig.cell_info[0].output_area.element.off(\n",
+ " 'cleared',\n",
+ " fig._remove_fig_handler\n",
+ " );\n",
+ " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n",
+ "\n",
+ " // Update the output cell to use the data from the current canvas.\n",
+ " fig.push_to_output();\n",
+ " var dataURL = fig.canvas.toDataURL();\n",
+ " // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
+ " // the notebook keyboard shortcuts fail.\n",
+ " IPython.keyboard_manager.enable();\n",
+ " fig.parent_element.innerHTML =\n",
+ " '
';\n",
+ " fig.close_ws(fig, msg);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.close_ws = function (fig, msg) {\n",
+ " fig.send_message('closing', msg);\n",
+ " // fig.ws.close()\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n",
+ " // Turn the data on the canvas into data in the output cell.\n",
+ " var width = this.canvas.width / this.ratio;\n",
+ " var dataURL = this.canvas.toDataURL();\n",
+ " this.cell_info[1]['text/html'] =\n",
+ " '
';\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.updated_canvas_event = function () {\n",
+ " // Tell IPython that the notebook contents must change.\n",
+ " IPython.notebook.set_dirty(true);\n",
+ " this.send_message('ack', {});\n",
+ " var fig = this;\n",
+ " // Wait a second, then push the new image to the DOM so\n",
+ " // that it is saved nicely (might be nice to debounce this).\n",
+ " setTimeout(function () {\n",
+ " fig.push_to_output();\n",
+ " }, 1000);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_toolbar = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var toolbar = document.createElement('div');\n",
+ " toolbar.classList = 'btn-toolbar';\n",
+ " this.root.appendChild(toolbar);\n",
+ "\n",
+ " function on_click_closure(name) {\n",
+ " return function (_event) {\n",
+ " return fig.toolbar_button_onclick(name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " function on_mouseover_closure(tooltip) {\n",
+ " return function (event) {\n",
+ " if (!event.currentTarget.disabled) {\n",
+ " return fig.toolbar_button_onmouseover(tooltip);\n",
+ " }\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " fig.buttons = {};\n",
+ " var buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'btn-group';\n",
+ " var button;\n",
+ " for (var toolbar_ind in mpl.toolbar_items) {\n",
+ " var name = mpl.toolbar_items[toolbar_ind][0];\n",
+ " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+ " var image = mpl.toolbar_items[toolbar_ind][2];\n",
+ " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+ "\n",
+ " if (!name) {\n",
+ " /* Instead of a spacer, we start a new button group. */\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ " buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'btn-group';\n",
+ " continue;\n",
+ " }\n",
+ "\n",
+ " button = fig.buttons[name] = document.createElement('button');\n",
+ " button.classList = 'btn btn-default';\n",
+ " button.href = '#';\n",
+ " button.title = name;\n",
+ " button.innerHTML = '';\n",
+ " button.addEventListener('click', on_click_closure(method_name));\n",
+ " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+ " buttonGroup.appendChild(button);\n",
+ " }\n",
+ "\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ "\n",
+ " // Add the status bar.\n",
+ " var status_bar = document.createElement('span');\n",
+ " status_bar.classList = 'mpl-message pull-right';\n",
+ " toolbar.appendChild(status_bar);\n",
+ " this.message = status_bar;\n",
+ "\n",
+ " // Add the close button to the window.\n",
+ " var buttongrp = document.createElement('div');\n",
+ " buttongrp.classList = 'btn-group inline pull-right';\n",
+ " button = document.createElement('button');\n",
+ " button.classList = 'btn btn-mini btn-primary';\n",
+ " button.href = '#';\n",
+ " button.title = 'Stop Interaction';\n",
+ " button.innerHTML = '';\n",
+ " button.addEventListener('click', function (_evt) {\n",
+ " fig.handle_close(fig, {});\n",
+ " });\n",
+ " button.addEventListener(\n",
+ " 'mouseover',\n",
+ " on_mouseover_closure('Stop Interaction')\n",
+ " );\n",
+ " buttongrp.appendChild(button);\n",
+ " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n",
+ " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._remove_fig_handler = function (event) {\n",
+ " var fig = event.data.fig;\n",
+ " if (event.target !== this) {\n",
+ " // Ignore bubbled events from children.\n",
+ " return;\n",
+ " }\n",
+ " fig.close_ws(fig, {});\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._root_extra_style = function (el) {\n",
+ " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._canvas_extra_style = function (el) {\n",
+ " // this is important to make the div 'focusable\n",
+ " el.setAttribute('tabindex', 0);\n",
+ " // reach out to IPython and tell the keyboard manager to turn it's self\n",
+ " // off when our div gets focus\n",
+ "\n",
+ " // location in version 3\n",
+ " if (IPython.notebook.keyboard_manager) {\n",
+ " IPython.notebook.keyboard_manager.register_events(el);\n",
+ " } else {\n",
+ " // location in version 2\n",
+ " IPython.keyboard_manager.register_events(el);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._key_event_extra = function (event, _name) {\n",
+ " // Check for shift+enter\n",
+ " if (event.shiftKey && event.which === 13) {\n",
+ " this.canvas_div.blur();\n",
+ " // select the cell after this one\n",
+ " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n",
+ " IPython.notebook.select(index + 1);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+ " fig.ondownload(fig, null);\n",
+ "};\n",
+ "\n",
+ "mpl.find_output_cell = function (html_output) {\n",
+ " // Return the cell and output element which can be found *uniquely* in the notebook.\n",
+ " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
+ " // IPython event is triggered only after the cells have been serialised, which for\n",
+ " // our purposes (turning an active figure into a static one), is too late.\n",
+ " var cells = IPython.notebook.get_cells();\n",
+ " var ncells = cells.length;\n",
+ " for (var i = 0; i < ncells; i++) {\n",
+ " var cell = cells[i];\n",
+ " if (cell.cell_type === 'code') {\n",
+ " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n",
+ " var data = cell.output_area.outputs[j];\n",
+ " if (data.data) {\n",
+ " // IPython >= 3 moved mimebundle to data attribute of output\n",
+ " data = data.data;\n",
+ " }\n",
+ " if (data['text/html'] === html_output) {\n",
+ " return [cell, data, j];\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "// Register the function which deals with the matplotlib target/channel.\n",
+ "// The kernel may be null if the page has been refreshed.\n",
+ "if (IPython.notebook.kernel !== null) {\n",
+ " IPython.notebook.kernel.comm_manager.register_target(\n",
+ " 'matplotlib',\n",
+ " mpl.mpl_figure_comm\n",
+ " );\n",
+ "}\n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "
"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " mean std num\n",
+ "1x0 netapp 38.019479 4.514666 20\n",
+ " irods_on_netapp 39.473920 1.297394 10\n",
+ " irods_via_davrods_on_netapp 39.422446 0.920041 10\n",
+ "1x1K netapp 61.750140 5.591803 19\n",
+ " irods_on_netapp 100.081593 1.281383 10\n",
+ " irods_via_davrods_on_netapp 100.206922 0.858756 9\n",
+ "10x1K netapp 61.687431 5.491162 19\n",
+ " irods_on_netapp 100.054191 1.617283 10\n",
+ " irods_via_davrods_on_netapp 100.508246 1.126574 10\n",
+ "100x1K netapp 61.627463 5.621175 20\n",
+ " irods_on_netapp 107.249967 4.786830 9\n",
+ " irods_via_davrods_on_netapp 100.001887 0.612863 11\n",
+ "1000x1K netapp 72.967062 6.368718 18\n",
+ " irods_on_netapp 575.380933 13.318009 10\n",
+ " irods_via_davrods_on_netapp 337.333978 5.722026 10\n",
+ "2000x1K netapp 85.885138 8.478362 18\n",
+ " irods_on_netapp 1088.393951 20.461509 10\n",
+ " irods_via_davrods_on_netapp 616.484709 14.031568 10\n"
+ ]
+ },
+ {
+ "data": {
+ "application/javascript": [
+ "/* Put everything inside the global mpl namespace */\n",
+ "/* global mpl */\n",
+ "window.mpl = {};\n",
+ "\n",
+ "mpl.get_websocket_type = function () {\n",
+ " if (typeof WebSocket !== 'undefined') {\n",
+ " return WebSocket;\n",
+ " } else if (typeof MozWebSocket !== 'undefined') {\n",
+ " return MozWebSocket;\n",
+ " } else {\n",
+ " alert(\n",
+ " 'Your browser does not have WebSocket support. ' +\n",
+ " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
+ " 'Firefox 4 and 5 are also supported but you ' +\n",
+ " 'have to enable WebSockets in about:config.'\n",
+ " );\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n",
+ " this.id = figure_id;\n",
+ "\n",
+ " this.ws = websocket;\n",
+ "\n",
+ " this.supports_binary = this.ws.binaryType !== undefined;\n",
+ "\n",
+ " if (!this.supports_binary) {\n",
+ " var warnings = document.getElementById('mpl-warnings');\n",
+ " if (warnings) {\n",
+ " warnings.style.display = 'block';\n",
+ " warnings.textContent =\n",
+ " 'This browser does not support binary websocket messages. ' +\n",
+ " 'Performance may be slow.';\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " this.imageObj = new Image();\n",
+ "\n",
+ " this.context = undefined;\n",
+ " this.message = undefined;\n",
+ " this.canvas = undefined;\n",
+ " this.rubberband_canvas = undefined;\n",
+ " this.rubberband_context = undefined;\n",
+ " this.format_dropdown = undefined;\n",
+ "\n",
+ " this.image_mode = 'full';\n",
+ "\n",
+ " this.root = document.createElement('div');\n",
+ " this.root.setAttribute('style', 'display: inline-block');\n",
+ " this._root_extra_style(this.root);\n",
+ "\n",
+ " parent_element.appendChild(this.root);\n",
+ "\n",
+ " this._init_header(this);\n",
+ " this._init_canvas(this);\n",
+ " this._init_toolbar(this);\n",
+ "\n",
+ " var fig = this;\n",
+ "\n",
+ " this.waiting = false;\n",
+ "\n",
+ " this.ws.onopen = function () {\n",
+ " fig.send_message('supports_binary', { value: fig.supports_binary });\n",
+ " fig.send_message('send_image_mode', {});\n",
+ " if (fig.ratio !== 1) {\n",
+ " fig.send_message('set_device_pixel_ratio', {\n",
+ " device_pixel_ratio: fig.ratio,\n",
+ " });\n",
+ " }\n",
+ " fig.send_message('refresh', {});\n",
+ " };\n",
+ "\n",
+ " this.imageObj.onload = function () {\n",
+ " if (fig.image_mode === 'full') {\n",
+ " // Full images could contain transparency (where diff images\n",
+ " // almost always do), so we need to clear the canvas so that\n",
+ " // there is no ghosting.\n",
+ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
+ " }\n",
+ " fig.context.drawImage(fig.imageObj, 0, 0);\n",
+ " };\n",
+ "\n",
+ " this.imageObj.onunload = function () {\n",
+ " fig.ws.close();\n",
+ " };\n",
+ "\n",
+ " this.ws.onmessage = this._make_on_message_function(this);\n",
+ "\n",
+ " this.ondownload = ondownload;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_header = function () {\n",
+ " var titlebar = document.createElement('div');\n",
+ " titlebar.classList =\n",
+ " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n",
+ " var titletext = document.createElement('div');\n",
+ " titletext.classList = 'ui-dialog-title';\n",
+ " titletext.setAttribute(\n",
+ " 'style',\n",
+ " 'width: 100%; text-align: center; padding: 3px;'\n",
+ " );\n",
+ " titlebar.appendChild(titletext);\n",
+ " this.root.appendChild(titlebar);\n",
+ " this.header = titletext;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n",
+ "\n",
+ "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n",
+ "\n",
+ "mpl.figure.prototype._init_canvas = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var canvas_div = (this.canvas_div = document.createElement('div'));\n",
+ " canvas_div.setAttribute(\n",
+ " 'style',\n",
+ " 'border: 1px solid #ddd;' +\n",
+ " 'box-sizing: content-box;' +\n",
+ " 'clear: both;' +\n",
+ " 'min-height: 1px;' +\n",
+ " 'min-width: 1px;' +\n",
+ " 'outline: 0;' +\n",
+ " 'overflow: hidden;' +\n",
+ " 'position: relative;' +\n",
+ " 'resize: both;'\n",
+ " );\n",
+ "\n",
+ " function on_keyboard_event_closure(name) {\n",
+ " return function (event) {\n",
+ " return fig.key_event(event, name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " canvas_div.addEventListener(\n",
+ " 'keydown',\n",
+ " on_keyboard_event_closure('key_press')\n",
+ " );\n",
+ " canvas_div.addEventListener(\n",
+ " 'keyup',\n",
+ " on_keyboard_event_closure('key_release')\n",
+ " );\n",
+ "\n",
+ " this._canvas_extra_style(canvas_div);\n",
+ " this.root.appendChild(canvas_div);\n",
+ "\n",
+ " var canvas = (this.canvas = document.createElement('canvas'));\n",
+ " canvas.classList.add('mpl-canvas');\n",
+ " canvas.setAttribute('style', 'box-sizing: content-box;');\n",
+ "\n",
+ " this.context = canvas.getContext('2d');\n",
+ "\n",
+ " var backingStore =\n",
+ " this.context.backingStorePixelRatio ||\n",
+ " this.context.webkitBackingStorePixelRatio ||\n",
+ " this.context.mozBackingStorePixelRatio ||\n",
+ " this.context.msBackingStorePixelRatio ||\n",
+ " this.context.oBackingStorePixelRatio ||\n",
+ " this.context.backingStorePixelRatio ||\n",
+ " 1;\n",
+ "\n",
+ " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
+ "\n",
+ " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n",
+ " 'canvas'\n",
+ " ));\n",
+ " rubberband_canvas.setAttribute(\n",
+ " 'style',\n",
+ " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n",
+ " );\n",
+ "\n",
+ " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n",
+ " if (this.ResizeObserver === undefined) {\n",
+ " if (window.ResizeObserver !== undefined) {\n",
+ " this.ResizeObserver = window.ResizeObserver;\n",
+ " } else {\n",
+ " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n",
+ " this.ResizeObserver = obs.ResizeObserver;\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n",
+ " var nentries = entries.length;\n",
+ " for (var i = 0; i < nentries; i++) {\n",
+ " var entry = entries[i];\n",
+ " var width, height;\n",
+ " if (entry.contentBoxSize) {\n",
+ " if (entry.contentBoxSize instanceof Array) {\n",
+ " // Chrome 84 implements new version of spec.\n",
+ " width = entry.contentBoxSize[0].inlineSize;\n",
+ " height = entry.contentBoxSize[0].blockSize;\n",
+ " } else {\n",
+ " // Firefox implements old version of spec.\n",
+ " width = entry.contentBoxSize.inlineSize;\n",
+ " height = entry.contentBoxSize.blockSize;\n",
+ " }\n",
+ " } else {\n",
+ " // Chrome <84 implements even older version of spec.\n",
+ " width = entry.contentRect.width;\n",
+ " height = entry.contentRect.height;\n",
+ " }\n",
+ "\n",
+ " // Keep the size of the canvas and rubber band canvas in sync with\n",
+ " // the canvas container.\n",
+ " if (entry.devicePixelContentBoxSize) {\n",
+ " // Chrome 84 implements new version of spec.\n",
+ " canvas.setAttribute(\n",
+ " 'width',\n",
+ " entry.devicePixelContentBoxSize[0].inlineSize\n",
+ " );\n",
+ " canvas.setAttribute(\n",
+ " 'height',\n",
+ " entry.devicePixelContentBoxSize[0].blockSize\n",
+ " );\n",
+ " } else {\n",
+ " canvas.setAttribute('width', width * fig.ratio);\n",
+ " canvas.setAttribute('height', height * fig.ratio);\n",
+ " }\n",
+ " canvas.setAttribute(\n",
+ " 'style',\n",
+ " 'width: ' + width + 'px; height: ' + height + 'px;'\n",
+ " );\n",
+ "\n",
+ " rubberband_canvas.setAttribute('width', width);\n",
+ " rubberband_canvas.setAttribute('height', height);\n",
+ "\n",
+ " // And update the size in Python. We ignore the initial 0/0 size\n",
+ " // that occurs as the element is placed into the DOM, which should\n",
+ " // otherwise not happen due to the minimum size styling.\n",
+ " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n",
+ " fig.request_resize(width, height);\n",
+ " }\n",
+ " }\n",
+ " });\n",
+ " this.resizeObserverInstance.observe(canvas_div);\n",
+ "\n",
+ " function on_mouse_event_closure(name) {\n",
+ " return function (event) {\n",
+ " return fig.mouse_event(event, name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mousedown',\n",
+ " on_mouse_event_closure('button_press')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseup',\n",
+ " on_mouse_event_closure('button_release')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'dblclick',\n",
+ " on_mouse_event_closure('dblclick')\n",
+ " );\n",
+ " // Throttle sequential mouse events to 1 every 20ms.\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mousemove',\n",
+ " on_mouse_event_closure('motion_notify')\n",
+ " );\n",
+ "\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseenter',\n",
+ " on_mouse_event_closure('figure_enter')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseleave',\n",
+ " on_mouse_event_closure('figure_leave')\n",
+ " );\n",
+ "\n",
+ " canvas_div.addEventListener('wheel', function (event) {\n",
+ " if (event.deltaY < 0) {\n",
+ " event.step = 1;\n",
+ " } else {\n",
+ " event.step = -1;\n",
+ " }\n",
+ " on_mouse_event_closure('scroll')(event);\n",
+ " });\n",
+ "\n",
+ " canvas_div.appendChild(canvas);\n",
+ " canvas_div.appendChild(rubberband_canvas);\n",
+ "\n",
+ " this.rubberband_context = rubberband_canvas.getContext('2d');\n",
+ " this.rubberband_context.strokeStyle = '#000000';\n",
+ "\n",
+ " this._resize_canvas = function (width, height, forward) {\n",
+ " if (forward) {\n",
+ " canvas_div.style.width = width + 'px';\n",
+ " canvas_div.style.height = height + 'px';\n",
+ " }\n",
+ " };\n",
+ "\n",
+ " // Disable right mouse context menu.\n",
+ " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n",
+ " event.preventDefault();\n",
+ " return false;\n",
+ " });\n",
+ "\n",
+ " function set_focus() {\n",
+ " canvas.focus();\n",
+ " canvas_div.focus();\n",
+ " }\n",
+ "\n",
+ " window.setTimeout(set_focus, 100);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_toolbar = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var toolbar = document.createElement('div');\n",
+ " toolbar.classList = 'mpl-toolbar';\n",
+ " this.root.appendChild(toolbar);\n",
+ "\n",
+ " function on_click_closure(name) {\n",
+ " return function (_event) {\n",
+ " return fig.toolbar_button_onclick(name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " function on_mouseover_closure(tooltip) {\n",
+ " return function (event) {\n",
+ " if (!event.currentTarget.disabled) {\n",
+ " return fig.toolbar_button_onmouseover(tooltip);\n",
+ " }\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " fig.buttons = {};\n",
+ " var buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'mpl-button-group';\n",
+ " for (var toolbar_ind in mpl.toolbar_items) {\n",
+ " var name = mpl.toolbar_items[toolbar_ind][0];\n",
+ " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+ " var image = mpl.toolbar_items[toolbar_ind][2];\n",
+ " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+ "\n",
+ " if (!name) {\n",
+ " /* Instead of a spacer, we start a new button group. */\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ " buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'mpl-button-group';\n",
+ " continue;\n",
+ " }\n",
+ "\n",
+ " var button = (fig.buttons[name] = document.createElement('button'));\n",
+ " button.classList = 'mpl-widget';\n",
+ " button.setAttribute('role', 'button');\n",
+ " button.setAttribute('aria-disabled', 'false');\n",
+ " button.addEventListener('click', on_click_closure(method_name));\n",
+ " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+ "\n",
+ " var icon_img = document.createElement('img');\n",
+ " icon_img.src = '_images/' + image + '.png';\n",
+ " icon_img.srcset = '_images/' + image + '_large.png 2x';\n",
+ " icon_img.alt = tooltip;\n",
+ " button.appendChild(icon_img);\n",
+ "\n",
+ " buttonGroup.appendChild(button);\n",
+ " }\n",
+ "\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ "\n",
+ " var fmt_picker = document.createElement('select');\n",
+ " fmt_picker.classList = 'mpl-widget';\n",
+ " toolbar.appendChild(fmt_picker);\n",
+ " this.format_dropdown = fmt_picker;\n",
+ "\n",
+ " for (var ind in mpl.extensions) {\n",
+ " var fmt = mpl.extensions[ind];\n",
+ " var option = document.createElement('option');\n",
+ " option.selected = fmt === mpl.default_extension;\n",
+ " option.innerHTML = fmt;\n",
+ " fmt_picker.appendChild(option);\n",
+ " }\n",
+ "\n",
+ " var status_bar = document.createElement('span');\n",
+ " status_bar.classList = 'mpl-message';\n",
+ " toolbar.appendChild(status_bar);\n",
+ " this.message = status_bar;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n",
+ " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
+ " // which will in turn request a refresh of the image.\n",
+ " this.send_message('resize', { width: x_pixels, height: y_pixels });\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.send_message = function (type, properties) {\n",
+ " properties['type'] = type;\n",
+ " properties['figure_id'] = this.id;\n",
+ " this.ws.send(JSON.stringify(properties));\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.send_draw_message = function () {\n",
+ " if (!this.waiting) {\n",
+ " this.waiting = true;\n",
+ " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+ " var format_dropdown = fig.format_dropdown;\n",
+ " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
+ " fig.ondownload(fig, format);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_resize = function (fig, msg) {\n",
+ " var size = msg['size'];\n",
+ " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n",
+ " fig._resize_canvas(size[0], size[1], msg['forward']);\n",
+ " fig.send_message('refresh', {});\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n",
+ " var x0 = msg['x0'] / fig.ratio;\n",
+ " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n",
+ " var x1 = msg['x1'] / fig.ratio;\n",
+ " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n",
+ " x0 = Math.floor(x0) + 0.5;\n",
+ " y0 = Math.floor(y0) + 0.5;\n",
+ " x1 = Math.floor(x1) + 0.5;\n",
+ " y1 = Math.floor(y1) + 0.5;\n",
+ " var min_x = Math.min(x0, x1);\n",
+ " var min_y = Math.min(y0, y1);\n",
+ " var width = Math.abs(x1 - x0);\n",
+ " var height = Math.abs(y1 - y0);\n",
+ "\n",
+ " fig.rubberband_context.clearRect(\n",
+ " 0,\n",
+ " 0,\n",
+ " fig.canvas.width / fig.ratio,\n",
+ " fig.canvas.height / fig.ratio\n",
+ " );\n",
+ "\n",
+ " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n",
+ " // Updates the figure title.\n",
+ " fig.header.textContent = msg['label'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n",
+ " fig.rubberband_canvas.style.cursor = msg['cursor'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_message = function (fig, msg) {\n",
+ " fig.message.textContent = msg['message'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n",
+ " // Request the server to send over a new figure.\n",
+ " fig.send_draw_message();\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n",
+ " fig.image_mode = msg['mode'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n",
+ " for (var key in msg) {\n",
+ " if (!(key in fig.buttons)) {\n",
+ " continue;\n",
+ " }\n",
+ " fig.buttons[key].disabled = !msg[key];\n",
+ " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n",
+ " if (msg['mode'] === 'PAN') {\n",
+ " fig.buttons['Pan'].classList.add('active');\n",
+ " fig.buttons['Zoom'].classList.remove('active');\n",
+ " } else if (msg['mode'] === 'ZOOM') {\n",
+ " fig.buttons['Pan'].classList.remove('active');\n",
+ " fig.buttons['Zoom'].classList.add('active');\n",
+ " } else {\n",
+ " fig.buttons['Pan'].classList.remove('active');\n",
+ " fig.buttons['Zoom'].classList.remove('active');\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.updated_canvas_event = function () {\n",
+ " // Called whenever the canvas gets updated.\n",
+ " this.send_message('ack', {});\n",
+ "};\n",
+ "\n",
+ "// A function to construct a web socket function for onmessage handling.\n",
+ "// Called in the figure constructor.\n",
+ "mpl.figure.prototype._make_on_message_function = function (fig) {\n",
+ " return function socket_on_message(evt) {\n",
+ " if (evt.data instanceof Blob) {\n",
+ " var img = evt.data;\n",
+ " if (img.type !== 'image/png') {\n",
+ " /* FIXME: We get \"Resource interpreted as Image but\n",
+ " * transferred with MIME type text/plain:\" errors on\n",
+ " * Chrome. But how to set the MIME type? It doesn't seem\n",
+ " * to be part of the websocket stream */\n",
+ " img.type = 'image/png';\n",
+ " }\n",
+ "\n",
+ " /* Free the memory for the previous frames */\n",
+ " if (fig.imageObj.src) {\n",
+ " (window.URL || window.webkitURL).revokeObjectURL(\n",
+ " fig.imageObj.src\n",
+ " );\n",
+ " }\n",
+ "\n",
+ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
+ " img\n",
+ " );\n",
+ " fig.updated_canvas_event();\n",
+ " fig.waiting = false;\n",
+ " return;\n",
+ " } else if (\n",
+ " typeof evt.data === 'string' &&\n",
+ " evt.data.slice(0, 21) === 'data:image/png;base64'\n",
+ " ) {\n",
+ " fig.imageObj.src = evt.data;\n",
+ " fig.updated_canvas_event();\n",
+ " fig.waiting = false;\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " var msg = JSON.parse(evt.data);\n",
+ " var msg_type = msg['type'];\n",
+ "\n",
+ " // Call the \"handle_{type}\" callback, which takes\n",
+ " // the figure and JSON message as its only arguments.\n",
+ " try {\n",
+ " var callback = fig['handle_' + msg_type];\n",
+ " } catch (e) {\n",
+ " console.log(\n",
+ " \"No handler for the '\" + msg_type + \"' message type: \",\n",
+ " msg\n",
+ " );\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " if (callback) {\n",
+ " try {\n",
+ " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
+ " callback(fig, msg);\n",
+ " } catch (e) {\n",
+ " console.log(\n",
+ " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n",
+ " e,\n",
+ " e.stack,\n",
+ " msg\n",
+ " );\n",
+ " }\n",
+ " }\n",
+ " };\n",
+ "};\n",
+ "\n",
+ "// from https://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
+ "mpl.findpos = function (e) {\n",
+ " //this section is from http://www.quirksmode.org/js/events_properties.html\n",
+ " var targ;\n",
+ " if (!e) {\n",
+ " e = window.event;\n",
+ " }\n",
+ " if (e.target) {\n",
+ " targ = e.target;\n",
+ " } else if (e.srcElement) {\n",
+ " targ = e.srcElement;\n",
+ " }\n",
+ " if (targ.nodeType === 3) {\n",
+ " // defeat Safari bug\n",
+ " targ = targ.parentNode;\n",
+ " }\n",
+ "\n",
+ " // pageX,Y are the mouse positions relative to the document\n",
+ " var boundingRect = targ.getBoundingClientRect();\n",
+ " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n",
+ " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n",
+ "\n",
+ " return { x: x, y: y };\n",
+ "};\n",
+ "\n",
+ "/*\n",
+ " * return a copy of an object with only non-object keys\n",
+ " * we need this to avoid circular references\n",
+ " * https://stackoverflow.com/a/24161582/3208463\n",
+ " */\n",
+ "function simpleKeys(original) {\n",
+ " return Object.keys(original).reduce(function (obj, key) {\n",
+ " if (typeof original[key] !== 'object') {\n",
+ " obj[key] = original[key];\n",
+ " }\n",
+ " return obj;\n",
+ " }, {});\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.mouse_event = function (event, name) {\n",
+ " var canvas_pos = mpl.findpos(event);\n",
+ "\n",
+ " if (name === 'button_press') {\n",
+ " this.canvas.focus();\n",
+ " this.canvas_div.focus();\n",
+ " }\n",
+ "\n",
+ " var x = canvas_pos.x * this.ratio;\n",
+ " var y = canvas_pos.y * this.ratio;\n",
+ "\n",
+ " this.send_message(name, {\n",
+ " x: x,\n",
+ " y: y,\n",
+ " button: event.button,\n",
+ " step: event.step,\n",
+ " guiEvent: simpleKeys(event),\n",
+ " });\n",
+ "\n",
+ " /* This prevents the web browser from automatically changing to\n",
+ " * the text insertion cursor when the button is pressed. We want\n",
+ " * to control all of the cursor setting manually through the\n",
+ " * 'cursor' event from matplotlib */\n",
+ " event.preventDefault();\n",
+ " return false;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n",
+ " // Handle any extra behaviour associated with a key event\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.key_event = function (event, name) {\n",
+ " // Prevent repeat events\n",
+ " if (name === 'key_press') {\n",
+ " if (event.key === this._key) {\n",
+ " return;\n",
+ " } else {\n",
+ " this._key = event.key;\n",
+ " }\n",
+ " }\n",
+ " if (name === 'key_release') {\n",
+ " this._key = null;\n",
+ " }\n",
+ "\n",
+ " var value = '';\n",
+ " if (event.ctrlKey && event.key !== 'Control') {\n",
+ " value += 'ctrl+';\n",
+ " }\n",
+ " else if (event.altKey && event.key !== 'Alt') {\n",
+ " value += 'alt+';\n",
+ " }\n",
+ " else if (event.shiftKey && event.key !== 'Shift') {\n",
+ " value += 'shift+';\n",
+ " }\n",
+ "\n",
+ " value += 'k' + event.key;\n",
+ "\n",
+ " this._key_event_extra(event, name);\n",
+ "\n",
+ " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n",
+ " return false;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n",
+ " if (name === 'download') {\n",
+ " this.handle_save(this, null);\n",
+ " } else {\n",
+ " this.send_message('toolbar_button', { name: name });\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n",
+ " this.message.textContent = tooltip;\n",
+ "};\n",
+ "\n",
+ "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n",
+ "// prettier-ignore\n",
+ "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n",
+ "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
+ "\n",
+ "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
+ "\n",
+ "mpl.default_extension = \"png\";/* global mpl */\n",
+ "\n",
+ "var comm_websocket_adapter = function (comm) {\n",
+ " // Create a \"websocket\"-like object which calls the given IPython comm\n",
+ " // object with the appropriate methods. Currently this is a non binary\n",
+ " // socket, so there is still some room for performance tuning.\n",
+ " var ws = {};\n",
+ "\n",
+ " ws.binaryType = comm.kernel.ws.binaryType;\n",
+ " ws.readyState = comm.kernel.ws.readyState;\n",
+ " function updateReadyState(_event) {\n",
+ " if (comm.kernel.ws) {\n",
+ " ws.readyState = comm.kernel.ws.readyState;\n",
+ " } else {\n",
+ " ws.readyState = 3; // Closed state.\n",
+ " }\n",
+ " }\n",
+ " comm.kernel.ws.addEventListener('open', updateReadyState);\n",
+ " comm.kernel.ws.addEventListener('close', updateReadyState);\n",
+ " comm.kernel.ws.addEventListener('error', updateReadyState);\n",
+ "\n",
+ " ws.close = function () {\n",
+ " comm.close();\n",
+ " };\n",
+ " ws.send = function (m) {\n",
+ " //console.log('sending', m);\n",
+ " comm.send(m);\n",
+ " };\n",
+ " // Register the callback with on_msg.\n",
+ " comm.on_msg(function (msg) {\n",
+ " //console.log('receiving', msg['content']['data'], msg);\n",
+ " var data = msg['content']['data'];\n",
+ " if (data['blob'] !== undefined) {\n",
+ " data = {\n",
+ " data: new Blob(msg['buffers'], { type: data['blob'] }),\n",
+ " };\n",
+ " }\n",
+ " // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
+ " ws.onmessage(data);\n",
+ " });\n",
+ " return ws;\n",
+ "};\n",
+ "\n",
+ "mpl.mpl_figure_comm = function (comm, msg) {\n",
+ " // This is the function which gets called when the mpl process\n",
+ " // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
+ "\n",
+ " var id = msg.content.data.id;\n",
+ " // Get hold of the div created by the display call when the Comm\n",
+ " // socket was opened in Python.\n",
+ " var element = document.getElementById(id);\n",
+ " var ws_proxy = comm_websocket_adapter(comm);\n",
+ "\n",
+ " function ondownload(figure, _format) {\n",
+ " window.open(figure.canvas.toDataURL());\n",
+ " }\n",
+ "\n",
+ " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n",
+ "\n",
+ " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
+ " // web socket which is closed, not our websocket->open comm proxy.\n",
+ " ws_proxy.onopen();\n",
+ "\n",
+ " fig.parent_element = element;\n",
+ " fig.cell_info = mpl.find_output_cell(\"\");\n",
+ " if (!fig.cell_info) {\n",
+ " console.error('Failed to find cell for figure', id, fig);\n",
+ " return;\n",
+ " }\n",
+ " fig.cell_info[0].output_area.element.on(\n",
+ " 'cleared',\n",
+ " { fig: fig },\n",
+ " fig._remove_fig_handler\n",
+ " );\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_close = function (fig, msg) {\n",
+ " var width = fig.canvas.width / fig.ratio;\n",
+ " fig.cell_info[0].output_area.element.off(\n",
+ " 'cleared',\n",
+ " fig._remove_fig_handler\n",
+ " );\n",
+ " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n",
+ "\n",
+ " // Update the output cell to use the data from the current canvas.\n",
+ " fig.push_to_output();\n",
+ " var dataURL = fig.canvas.toDataURL();\n",
+ " // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
+ " // the notebook keyboard shortcuts fail.\n",
+ " IPython.keyboard_manager.enable();\n",
+ " fig.parent_element.innerHTML =\n",
+ " '
';\n",
+ " fig.close_ws(fig, msg);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.close_ws = function (fig, msg) {\n",
+ " fig.send_message('closing', msg);\n",
+ " // fig.ws.close()\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n",
+ " // Turn the data on the canvas into data in the output cell.\n",
+ " var width = this.canvas.width / this.ratio;\n",
+ " var dataURL = this.canvas.toDataURL();\n",
+ " this.cell_info[1]['text/html'] =\n",
+ " '
';\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.updated_canvas_event = function () {\n",
+ " // Tell IPython that the notebook contents must change.\n",
+ " IPython.notebook.set_dirty(true);\n",
+ " this.send_message('ack', {});\n",
+ " var fig = this;\n",
+ " // Wait a second, then push the new image to the DOM so\n",
+ " // that it is saved nicely (might be nice to debounce this).\n",
+ " setTimeout(function () {\n",
+ " fig.push_to_output();\n",
+ " }, 1000);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_toolbar = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var toolbar = document.createElement('div');\n",
+ " toolbar.classList = 'btn-toolbar';\n",
+ " this.root.appendChild(toolbar);\n",
+ "\n",
+ " function on_click_closure(name) {\n",
+ " return function (_event) {\n",
+ " return fig.toolbar_button_onclick(name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " function on_mouseover_closure(tooltip) {\n",
+ " return function (event) {\n",
+ " if (!event.currentTarget.disabled) {\n",
+ " return fig.toolbar_button_onmouseover(tooltip);\n",
+ " }\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " fig.buttons = {};\n",
+ " var buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'btn-group';\n",
+ " var button;\n",
+ " for (var toolbar_ind in mpl.toolbar_items) {\n",
+ " var name = mpl.toolbar_items[toolbar_ind][0];\n",
+ " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+ " var image = mpl.toolbar_items[toolbar_ind][2];\n",
+ " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+ "\n",
+ " if (!name) {\n",
+ " /* Instead of a spacer, we start a new button group. */\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ " buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'btn-group';\n",
+ " continue;\n",
+ " }\n",
+ "\n",
+ " button = fig.buttons[name] = document.createElement('button');\n",
+ " button.classList = 'btn btn-default';\n",
+ " button.href = '#';\n",
+ " button.title = name;\n",
+ " button.innerHTML = '';\n",
+ " button.addEventListener('click', on_click_closure(method_name));\n",
+ " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+ " buttonGroup.appendChild(button);\n",
+ " }\n",
+ "\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ "\n",
+ " // Add the status bar.\n",
+ " var status_bar = document.createElement('span');\n",
+ " status_bar.classList = 'mpl-message pull-right';\n",
+ " toolbar.appendChild(status_bar);\n",
+ " this.message = status_bar;\n",
+ "\n",
+ " // Add the close button to the window.\n",
+ " var buttongrp = document.createElement('div');\n",
+ " buttongrp.classList = 'btn-group inline pull-right';\n",
+ " button = document.createElement('button');\n",
+ " button.classList = 'btn btn-mini btn-primary';\n",
+ " button.href = '#';\n",
+ " button.title = 'Stop Interaction';\n",
+ " button.innerHTML = '';\n",
+ " button.addEventListener('click', function (_evt) {\n",
+ " fig.handle_close(fig, {});\n",
+ " });\n",
+ " button.addEventListener(\n",
+ " 'mouseover',\n",
+ " on_mouseover_closure('Stop Interaction')\n",
+ " );\n",
+ " buttongrp.appendChild(button);\n",
+ " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n",
+ " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._remove_fig_handler = function (event) {\n",
+ " var fig = event.data.fig;\n",
+ " if (event.target !== this) {\n",
+ " // Ignore bubbled events from children.\n",
+ " return;\n",
+ " }\n",
+ " fig.close_ws(fig, {});\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._root_extra_style = function (el) {\n",
+ " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._canvas_extra_style = function (el) {\n",
+ " // this is important to make the div 'focusable\n",
+ " el.setAttribute('tabindex', 0);\n",
+ " // reach out to IPython and tell the keyboard manager to turn it's self\n",
+ " // off when our div gets focus\n",
+ "\n",
+ " // location in version 3\n",
+ " if (IPython.notebook.keyboard_manager) {\n",
+ " IPython.notebook.keyboard_manager.register_events(el);\n",
+ " } else {\n",
+ " // location in version 2\n",
+ " IPython.keyboard_manager.register_events(el);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._key_event_extra = function (event, _name) {\n",
+ " // Check for shift+enter\n",
+ " if (event.shiftKey && event.which === 13) {\n",
+ " this.canvas_div.blur();\n",
+ " // select the cell after this one\n",
+ " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n",
+ " IPython.notebook.select(index + 1);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+ " fig.ondownload(fig, null);\n",
+ "};\n",
+ "\n",
+ "mpl.find_output_cell = function (html_output) {\n",
+ " // Return the cell and output element which can be found *uniquely* in the notebook.\n",
+ " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
+ " // IPython event is triggered only after the cells have been serialised, which for\n",
+ " // our purposes (turning an active figure into a static one), is too late.\n",
+ " var cells = IPython.notebook.get_cells();\n",
+ " var ncells = cells.length;\n",
+ " for (var i = 0; i < ncells; i++) {\n",
+ " var cell = cells[i];\n",
+ " if (cell.cell_type === 'code') {\n",
+ " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n",
+ " var data = cell.output_area.outputs[j];\n",
+ " if (data.data) {\n",
+ " // IPython >= 3 moved mimebundle to data attribute of output\n",
+ " data = data.data;\n",
+ " }\n",
+ " if (data['text/html'] === html_output) {\n",
+ " return [cell, data, j];\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "// Register the function which deals with the matplotlib target/channel.\n",
+ "// The kernel may be null if the page has been refreshed.\n",
+ "if (IPython.notebook.kernel !== null) {\n",
+ " IPython.notebook.kernel.comm_manager.register_target(\n",
+ " 'matplotlib',\n",
+ " mpl.mpl_figure_comm\n",
+ " );\n",
+ "}\n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "
"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " mean std num\n",
+ "1x0 netapp 38.019479 4.514666 20\n",
+ " irods_on_netapp 39.473920 1.297394 10\n",
+ " irods_via_davrods_on_netapp 39.422446 0.920041 10\n",
+ "1x1K netapp 61.750140 5.591803 19\n",
+ " irods_on_netapp 100.081593 1.281383 10\n",
+ " irods_via_davrods_on_netapp 100.206922 0.858756 9\n",
+ "1x1M netapp 61.496473 5.694692 19\n",
+ " irods_on_netapp 100.311454 1.300512 9\n",
+ " irods_via_davrods_on_netapp 100.568214 1.335967 10\n",
+ "1x1G netapp 63.104740 5.135069 18\n",
+ " irods_on_netapp 123.341776 3.075267 9\n",
+ " irods_via_davrods_on_netapp 100.337795 1.061627 9\n",
+ "10x1G netapp 183.547638 5.057323 9\n",
+ " irods_on_netapp 900.939860 12.974536 9\n",
+ " irods_via_davrods_on_netapp 352.437443 6.116535 9\n"
+ ]
+ },
+ {
+ "data": {
+ "application/javascript": [
+ "/* Put everything inside the global mpl namespace */\n",
+ "/* global mpl */\n",
+ "window.mpl = {};\n",
+ "\n",
+ "mpl.get_websocket_type = function () {\n",
+ " if (typeof WebSocket !== 'undefined') {\n",
+ " return WebSocket;\n",
+ " } else if (typeof MozWebSocket !== 'undefined') {\n",
+ " return MozWebSocket;\n",
+ " } else {\n",
+ " alert(\n",
+ " 'Your browser does not have WebSocket support. ' +\n",
+ " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
+ " 'Firefox 4 and 5 are also supported but you ' +\n",
+ " 'have to enable WebSockets in about:config.'\n",
+ " );\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n",
+ " this.id = figure_id;\n",
+ "\n",
+ " this.ws = websocket;\n",
+ "\n",
+ " this.supports_binary = this.ws.binaryType !== undefined;\n",
+ "\n",
+ " if (!this.supports_binary) {\n",
+ " var warnings = document.getElementById('mpl-warnings');\n",
+ " if (warnings) {\n",
+ " warnings.style.display = 'block';\n",
+ " warnings.textContent =\n",
+ " 'This browser does not support binary websocket messages. ' +\n",
+ " 'Performance may be slow.';\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " this.imageObj = new Image();\n",
+ "\n",
+ " this.context = undefined;\n",
+ " this.message = undefined;\n",
+ " this.canvas = undefined;\n",
+ " this.rubberband_canvas = undefined;\n",
+ " this.rubberband_context = undefined;\n",
+ " this.format_dropdown = undefined;\n",
+ "\n",
+ " this.image_mode = 'full';\n",
+ "\n",
+ " this.root = document.createElement('div');\n",
+ " this.root.setAttribute('style', 'display: inline-block');\n",
+ " this._root_extra_style(this.root);\n",
+ "\n",
+ " parent_element.appendChild(this.root);\n",
+ "\n",
+ " this._init_header(this);\n",
+ " this._init_canvas(this);\n",
+ " this._init_toolbar(this);\n",
+ "\n",
+ " var fig = this;\n",
+ "\n",
+ " this.waiting = false;\n",
+ "\n",
+ " this.ws.onopen = function () {\n",
+ " fig.send_message('supports_binary', { value: fig.supports_binary });\n",
+ " fig.send_message('send_image_mode', {});\n",
+ " if (fig.ratio !== 1) {\n",
+ " fig.send_message('set_device_pixel_ratio', {\n",
+ " device_pixel_ratio: fig.ratio,\n",
+ " });\n",
+ " }\n",
+ " fig.send_message('refresh', {});\n",
+ " };\n",
+ "\n",
+ " this.imageObj.onload = function () {\n",
+ " if (fig.image_mode === 'full') {\n",
+ " // Full images could contain transparency (where diff images\n",
+ " // almost always do), so we need to clear the canvas so that\n",
+ " // there is no ghosting.\n",
+ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
+ " }\n",
+ " fig.context.drawImage(fig.imageObj, 0, 0);\n",
+ " };\n",
+ "\n",
+ " this.imageObj.onunload = function () {\n",
+ " fig.ws.close();\n",
+ " };\n",
+ "\n",
+ " this.ws.onmessage = this._make_on_message_function(this);\n",
+ "\n",
+ " this.ondownload = ondownload;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_header = function () {\n",
+ " var titlebar = document.createElement('div');\n",
+ " titlebar.classList =\n",
+ " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n",
+ " var titletext = document.createElement('div');\n",
+ " titletext.classList = 'ui-dialog-title';\n",
+ " titletext.setAttribute(\n",
+ " 'style',\n",
+ " 'width: 100%; text-align: center; padding: 3px;'\n",
+ " );\n",
+ " titlebar.appendChild(titletext);\n",
+ " this.root.appendChild(titlebar);\n",
+ " this.header = titletext;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n",
+ "\n",
+ "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n",
+ "\n",
+ "mpl.figure.prototype._init_canvas = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var canvas_div = (this.canvas_div = document.createElement('div'));\n",
+ " canvas_div.setAttribute(\n",
+ " 'style',\n",
+ " 'border: 1px solid #ddd;' +\n",
+ " 'box-sizing: content-box;' +\n",
+ " 'clear: both;' +\n",
+ " 'min-height: 1px;' +\n",
+ " 'min-width: 1px;' +\n",
+ " 'outline: 0;' +\n",
+ " 'overflow: hidden;' +\n",
+ " 'position: relative;' +\n",
+ " 'resize: both;'\n",
+ " );\n",
+ "\n",
+ " function on_keyboard_event_closure(name) {\n",
+ " return function (event) {\n",
+ " return fig.key_event(event, name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " canvas_div.addEventListener(\n",
+ " 'keydown',\n",
+ " on_keyboard_event_closure('key_press')\n",
+ " );\n",
+ " canvas_div.addEventListener(\n",
+ " 'keyup',\n",
+ " on_keyboard_event_closure('key_release')\n",
+ " );\n",
+ "\n",
+ " this._canvas_extra_style(canvas_div);\n",
+ " this.root.appendChild(canvas_div);\n",
+ "\n",
+ " var canvas = (this.canvas = document.createElement('canvas'));\n",
+ " canvas.classList.add('mpl-canvas');\n",
+ " canvas.setAttribute('style', 'box-sizing: content-box;');\n",
+ "\n",
+ " this.context = canvas.getContext('2d');\n",
+ "\n",
+ " var backingStore =\n",
+ " this.context.backingStorePixelRatio ||\n",
+ " this.context.webkitBackingStorePixelRatio ||\n",
+ " this.context.mozBackingStorePixelRatio ||\n",
+ " this.context.msBackingStorePixelRatio ||\n",
+ " this.context.oBackingStorePixelRatio ||\n",
+ " this.context.backingStorePixelRatio ||\n",
+ " 1;\n",
+ "\n",
+ " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
+ "\n",
+ " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n",
+ " 'canvas'\n",
+ " ));\n",
+ " rubberband_canvas.setAttribute(\n",
+ " 'style',\n",
+ " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n",
+ " );\n",
+ "\n",
+ " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n",
+ " if (this.ResizeObserver === undefined) {\n",
+ " if (window.ResizeObserver !== undefined) {\n",
+ " this.ResizeObserver = window.ResizeObserver;\n",
+ " } else {\n",
+ " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n",
+ " this.ResizeObserver = obs.ResizeObserver;\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n",
+ " var nentries = entries.length;\n",
+ " for (var i = 0; i < nentries; i++) {\n",
+ " var entry = entries[i];\n",
+ " var width, height;\n",
+ " if (entry.contentBoxSize) {\n",
+ " if (entry.contentBoxSize instanceof Array) {\n",
+ " // Chrome 84 implements new version of spec.\n",
+ " width = entry.contentBoxSize[0].inlineSize;\n",
+ " height = entry.contentBoxSize[0].blockSize;\n",
+ " } else {\n",
+ " // Firefox implements old version of spec.\n",
+ " width = entry.contentBoxSize.inlineSize;\n",
+ " height = entry.contentBoxSize.blockSize;\n",
+ " }\n",
+ " } else {\n",
+ " // Chrome <84 implements even older version of spec.\n",
+ " width = entry.contentRect.width;\n",
+ " height = entry.contentRect.height;\n",
+ " }\n",
+ "\n",
+ " // Keep the size of the canvas and rubber band canvas in sync with\n",
+ " // the canvas container.\n",
+ " if (entry.devicePixelContentBoxSize) {\n",
+ " // Chrome 84 implements new version of spec.\n",
+ " canvas.setAttribute(\n",
+ " 'width',\n",
+ " entry.devicePixelContentBoxSize[0].inlineSize\n",
+ " );\n",
+ " canvas.setAttribute(\n",
+ " 'height',\n",
+ " entry.devicePixelContentBoxSize[0].blockSize\n",
+ " );\n",
+ " } else {\n",
+ " canvas.setAttribute('width', width * fig.ratio);\n",
+ " canvas.setAttribute('height', height * fig.ratio);\n",
+ " }\n",
+ " canvas.setAttribute(\n",
+ " 'style',\n",
+ " 'width: ' + width + 'px; height: ' + height + 'px;'\n",
+ " );\n",
+ "\n",
+ " rubberband_canvas.setAttribute('width', width);\n",
+ " rubberband_canvas.setAttribute('height', height);\n",
+ "\n",
+ " // And update the size in Python. We ignore the initial 0/0 size\n",
+ " // that occurs as the element is placed into the DOM, which should\n",
+ " // otherwise not happen due to the minimum size styling.\n",
+ " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n",
+ " fig.request_resize(width, height);\n",
+ " }\n",
+ " }\n",
+ " });\n",
+ " this.resizeObserverInstance.observe(canvas_div);\n",
+ "\n",
+ " function on_mouse_event_closure(name) {\n",
+ " return function (event) {\n",
+ " return fig.mouse_event(event, name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mousedown',\n",
+ " on_mouse_event_closure('button_press')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseup',\n",
+ " on_mouse_event_closure('button_release')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'dblclick',\n",
+ " on_mouse_event_closure('dblclick')\n",
+ " );\n",
+ " // Throttle sequential mouse events to 1 every 20ms.\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mousemove',\n",
+ " on_mouse_event_closure('motion_notify')\n",
+ " );\n",
+ "\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseenter',\n",
+ " on_mouse_event_closure('figure_enter')\n",
+ " );\n",
+ " rubberband_canvas.addEventListener(\n",
+ " 'mouseleave',\n",
+ " on_mouse_event_closure('figure_leave')\n",
+ " );\n",
+ "\n",
+ " canvas_div.addEventListener('wheel', function (event) {\n",
+ " if (event.deltaY < 0) {\n",
+ " event.step = 1;\n",
+ " } else {\n",
+ " event.step = -1;\n",
+ " }\n",
+ " on_mouse_event_closure('scroll')(event);\n",
+ " });\n",
+ "\n",
+ " canvas_div.appendChild(canvas);\n",
+ " canvas_div.appendChild(rubberband_canvas);\n",
+ "\n",
+ " this.rubberband_context = rubberband_canvas.getContext('2d');\n",
+ " this.rubberband_context.strokeStyle = '#000000';\n",
+ "\n",
+ " this._resize_canvas = function (width, height, forward) {\n",
+ " if (forward) {\n",
+ " canvas_div.style.width = width + 'px';\n",
+ " canvas_div.style.height = height + 'px';\n",
+ " }\n",
+ " };\n",
+ "\n",
+ " // Disable right mouse context menu.\n",
+ " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n",
+ " event.preventDefault();\n",
+ " return false;\n",
+ " });\n",
+ "\n",
+ " function set_focus() {\n",
+ " canvas.focus();\n",
+ " canvas_div.focus();\n",
+ " }\n",
+ "\n",
+ " window.setTimeout(set_focus, 100);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_toolbar = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var toolbar = document.createElement('div');\n",
+ " toolbar.classList = 'mpl-toolbar';\n",
+ " this.root.appendChild(toolbar);\n",
+ "\n",
+ " function on_click_closure(name) {\n",
+ " return function (_event) {\n",
+ " return fig.toolbar_button_onclick(name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " function on_mouseover_closure(tooltip) {\n",
+ " return function (event) {\n",
+ " if (!event.currentTarget.disabled) {\n",
+ " return fig.toolbar_button_onmouseover(tooltip);\n",
+ " }\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " fig.buttons = {};\n",
+ " var buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'mpl-button-group';\n",
+ " for (var toolbar_ind in mpl.toolbar_items) {\n",
+ " var name = mpl.toolbar_items[toolbar_ind][0];\n",
+ " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+ " var image = mpl.toolbar_items[toolbar_ind][2];\n",
+ " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+ "\n",
+ " if (!name) {\n",
+ " /* Instead of a spacer, we start a new button group. */\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ " buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'mpl-button-group';\n",
+ " continue;\n",
+ " }\n",
+ "\n",
+ " var button = (fig.buttons[name] = document.createElement('button'));\n",
+ " button.classList = 'mpl-widget';\n",
+ " button.setAttribute('role', 'button');\n",
+ " button.setAttribute('aria-disabled', 'false');\n",
+ " button.addEventListener('click', on_click_closure(method_name));\n",
+ " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+ "\n",
+ " var icon_img = document.createElement('img');\n",
+ " icon_img.src = '_images/' + image + '.png';\n",
+ " icon_img.srcset = '_images/' + image + '_large.png 2x';\n",
+ " icon_img.alt = tooltip;\n",
+ " button.appendChild(icon_img);\n",
+ "\n",
+ " buttonGroup.appendChild(button);\n",
+ " }\n",
+ "\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ "\n",
+ " var fmt_picker = document.createElement('select');\n",
+ " fmt_picker.classList = 'mpl-widget';\n",
+ " toolbar.appendChild(fmt_picker);\n",
+ " this.format_dropdown = fmt_picker;\n",
+ "\n",
+ " for (var ind in mpl.extensions) {\n",
+ " var fmt = mpl.extensions[ind];\n",
+ " var option = document.createElement('option');\n",
+ " option.selected = fmt === mpl.default_extension;\n",
+ " option.innerHTML = fmt;\n",
+ " fmt_picker.appendChild(option);\n",
+ " }\n",
+ "\n",
+ " var status_bar = document.createElement('span');\n",
+ " status_bar.classList = 'mpl-message';\n",
+ " toolbar.appendChild(status_bar);\n",
+ " this.message = status_bar;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n",
+ " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
+ " // which will in turn request a refresh of the image.\n",
+ " this.send_message('resize', { width: x_pixels, height: y_pixels });\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.send_message = function (type, properties) {\n",
+ " properties['type'] = type;\n",
+ " properties['figure_id'] = this.id;\n",
+ " this.ws.send(JSON.stringify(properties));\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.send_draw_message = function () {\n",
+ " if (!this.waiting) {\n",
+ " this.waiting = true;\n",
+ " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+ " var format_dropdown = fig.format_dropdown;\n",
+ " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
+ " fig.ondownload(fig, format);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_resize = function (fig, msg) {\n",
+ " var size = msg['size'];\n",
+ " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n",
+ " fig._resize_canvas(size[0], size[1], msg['forward']);\n",
+ " fig.send_message('refresh', {});\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n",
+ " var x0 = msg['x0'] / fig.ratio;\n",
+ " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n",
+ " var x1 = msg['x1'] / fig.ratio;\n",
+ " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n",
+ " x0 = Math.floor(x0) + 0.5;\n",
+ " y0 = Math.floor(y0) + 0.5;\n",
+ " x1 = Math.floor(x1) + 0.5;\n",
+ " y1 = Math.floor(y1) + 0.5;\n",
+ " var min_x = Math.min(x0, x1);\n",
+ " var min_y = Math.min(y0, y1);\n",
+ " var width = Math.abs(x1 - x0);\n",
+ " var height = Math.abs(y1 - y0);\n",
+ "\n",
+ " fig.rubberband_context.clearRect(\n",
+ " 0,\n",
+ " 0,\n",
+ " fig.canvas.width / fig.ratio,\n",
+ " fig.canvas.height / fig.ratio\n",
+ " );\n",
+ "\n",
+ " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n",
+ " // Updates the figure title.\n",
+ " fig.header.textContent = msg['label'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n",
+ " fig.rubberband_canvas.style.cursor = msg['cursor'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_message = function (fig, msg) {\n",
+ " fig.message.textContent = msg['message'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n",
+ " // Request the server to send over a new figure.\n",
+ " fig.send_draw_message();\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n",
+ " fig.image_mode = msg['mode'];\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n",
+ " for (var key in msg) {\n",
+ " if (!(key in fig.buttons)) {\n",
+ " continue;\n",
+ " }\n",
+ " fig.buttons[key].disabled = !msg[key];\n",
+ " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n",
+ " if (msg['mode'] === 'PAN') {\n",
+ " fig.buttons['Pan'].classList.add('active');\n",
+ " fig.buttons['Zoom'].classList.remove('active');\n",
+ " } else if (msg['mode'] === 'ZOOM') {\n",
+ " fig.buttons['Pan'].classList.remove('active');\n",
+ " fig.buttons['Zoom'].classList.add('active');\n",
+ " } else {\n",
+ " fig.buttons['Pan'].classList.remove('active');\n",
+ " fig.buttons['Zoom'].classList.remove('active');\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.updated_canvas_event = function () {\n",
+ " // Called whenever the canvas gets updated.\n",
+ " this.send_message('ack', {});\n",
+ "};\n",
+ "\n",
+ "// A function to construct a web socket function for onmessage handling.\n",
+ "// Called in the figure constructor.\n",
+ "mpl.figure.prototype._make_on_message_function = function (fig) {\n",
+ " return function socket_on_message(evt) {\n",
+ " if (evt.data instanceof Blob) {\n",
+ " var img = evt.data;\n",
+ " if (img.type !== 'image/png') {\n",
+ " /* FIXME: We get \"Resource interpreted as Image but\n",
+ " * transferred with MIME type text/plain:\" errors on\n",
+ " * Chrome. But how to set the MIME type? It doesn't seem\n",
+ " * to be part of the websocket stream */\n",
+ " img.type = 'image/png';\n",
+ " }\n",
+ "\n",
+ " /* Free the memory for the previous frames */\n",
+ " if (fig.imageObj.src) {\n",
+ " (window.URL || window.webkitURL).revokeObjectURL(\n",
+ " fig.imageObj.src\n",
+ " );\n",
+ " }\n",
+ "\n",
+ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
+ " img\n",
+ " );\n",
+ " fig.updated_canvas_event();\n",
+ " fig.waiting = false;\n",
+ " return;\n",
+ " } else if (\n",
+ " typeof evt.data === 'string' &&\n",
+ " evt.data.slice(0, 21) === 'data:image/png;base64'\n",
+ " ) {\n",
+ " fig.imageObj.src = evt.data;\n",
+ " fig.updated_canvas_event();\n",
+ " fig.waiting = false;\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " var msg = JSON.parse(evt.data);\n",
+ " var msg_type = msg['type'];\n",
+ "\n",
+ " // Call the \"handle_{type}\" callback, which takes\n",
+ " // the figure and JSON message as its only arguments.\n",
+ " try {\n",
+ " var callback = fig['handle_' + msg_type];\n",
+ " } catch (e) {\n",
+ " console.log(\n",
+ " \"No handler for the '\" + msg_type + \"' message type: \",\n",
+ " msg\n",
+ " );\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " if (callback) {\n",
+ " try {\n",
+ " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
+ " callback(fig, msg);\n",
+ " } catch (e) {\n",
+ " console.log(\n",
+ " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n",
+ " e,\n",
+ " e.stack,\n",
+ " msg\n",
+ " );\n",
+ " }\n",
+ " }\n",
+ " };\n",
+ "};\n",
+ "\n",
+ "// from https://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
+ "mpl.findpos = function (e) {\n",
+ " //this section is from http://www.quirksmode.org/js/events_properties.html\n",
+ " var targ;\n",
+ " if (!e) {\n",
+ " e = window.event;\n",
+ " }\n",
+ " if (e.target) {\n",
+ " targ = e.target;\n",
+ " } else if (e.srcElement) {\n",
+ " targ = e.srcElement;\n",
+ " }\n",
+ " if (targ.nodeType === 3) {\n",
+ " // defeat Safari bug\n",
+ " targ = targ.parentNode;\n",
+ " }\n",
+ "\n",
+ " // pageX,Y are the mouse positions relative to the document\n",
+ " var boundingRect = targ.getBoundingClientRect();\n",
+ " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n",
+ " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n",
+ "\n",
+ " return { x: x, y: y };\n",
+ "};\n",
+ "\n",
+ "/*\n",
+ " * return a copy of an object with only non-object keys\n",
+ " * we need this to avoid circular references\n",
+ " * https://stackoverflow.com/a/24161582/3208463\n",
+ " */\n",
+ "function simpleKeys(original) {\n",
+ " return Object.keys(original).reduce(function (obj, key) {\n",
+ " if (typeof original[key] !== 'object') {\n",
+ " obj[key] = original[key];\n",
+ " }\n",
+ " return obj;\n",
+ " }, {});\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.mouse_event = function (event, name) {\n",
+ " var canvas_pos = mpl.findpos(event);\n",
+ "\n",
+ " if (name === 'button_press') {\n",
+ " this.canvas.focus();\n",
+ " this.canvas_div.focus();\n",
+ " }\n",
+ "\n",
+ " var x = canvas_pos.x * this.ratio;\n",
+ " var y = canvas_pos.y * this.ratio;\n",
+ "\n",
+ " this.send_message(name, {\n",
+ " x: x,\n",
+ " y: y,\n",
+ " button: event.button,\n",
+ " step: event.step,\n",
+ " guiEvent: simpleKeys(event),\n",
+ " });\n",
+ "\n",
+ " /* This prevents the web browser from automatically changing to\n",
+ " * the text insertion cursor when the button is pressed. We want\n",
+ " * to control all of the cursor setting manually through the\n",
+ " * 'cursor' event from matplotlib */\n",
+ " event.preventDefault();\n",
+ " return false;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n",
+ " // Handle any extra behaviour associated with a key event\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.key_event = function (event, name) {\n",
+ " // Prevent repeat events\n",
+ " if (name === 'key_press') {\n",
+ " if (event.key === this._key) {\n",
+ " return;\n",
+ " } else {\n",
+ " this._key = event.key;\n",
+ " }\n",
+ " }\n",
+ " if (name === 'key_release') {\n",
+ " this._key = null;\n",
+ " }\n",
+ "\n",
+ " var value = '';\n",
+ " if (event.ctrlKey && event.key !== 'Control') {\n",
+ " value += 'ctrl+';\n",
+ " }\n",
+ " else if (event.altKey && event.key !== 'Alt') {\n",
+ " value += 'alt+';\n",
+ " }\n",
+ " else if (event.shiftKey && event.key !== 'Shift') {\n",
+ " value += 'shift+';\n",
+ " }\n",
+ "\n",
+ " value += 'k' + event.key;\n",
+ "\n",
+ " this._key_event_extra(event, name);\n",
+ "\n",
+ " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n",
+ " return false;\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n",
+ " if (name === 'download') {\n",
+ " this.handle_save(this, null);\n",
+ " } else {\n",
+ " this.send_message('toolbar_button', { name: name });\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n",
+ " this.message.textContent = tooltip;\n",
+ "};\n",
+ "\n",
+ "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n",
+ "// prettier-ignore\n",
+ "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n",
+ "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
+ "\n",
+ "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
+ "\n",
+ "mpl.default_extension = \"png\";/* global mpl */\n",
+ "\n",
+ "var comm_websocket_adapter = function (comm) {\n",
+ " // Create a \"websocket\"-like object which calls the given IPython comm\n",
+ " // object with the appropriate methods. Currently this is a non binary\n",
+ " // socket, so there is still some room for performance tuning.\n",
+ " var ws = {};\n",
+ "\n",
+ " ws.binaryType = comm.kernel.ws.binaryType;\n",
+ " ws.readyState = comm.kernel.ws.readyState;\n",
+ " function updateReadyState(_event) {\n",
+ " if (comm.kernel.ws) {\n",
+ " ws.readyState = comm.kernel.ws.readyState;\n",
+ " } else {\n",
+ " ws.readyState = 3; // Closed state.\n",
+ " }\n",
+ " }\n",
+ " comm.kernel.ws.addEventListener('open', updateReadyState);\n",
+ " comm.kernel.ws.addEventListener('close', updateReadyState);\n",
+ " comm.kernel.ws.addEventListener('error', updateReadyState);\n",
+ "\n",
+ " ws.close = function () {\n",
+ " comm.close();\n",
+ " };\n",
+ " ws.send = function (m) {\n",
+ " //console.log('sending', m);\n",
+ " comm.send(m);\n",
+ " };\n",
+ " // Register the callback with on_msg.\n",
+ " comm.on_msg(function (msg) {\n",
+ " //console.log('receiving', msg['content']['data'], msg);\n",
+ " var data = msg['content']['data'];\n",
+ " if (data['blob'] !== undefined) {\n",
+ " data = {\n",
+ " data: new Blob(msg['buffers'], { type: data['blob'] }),\n",
+ " };\n",
+ " }\n",
+ " // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
+ " ws.onmessage(data);\n",
+ " });\n",
+ " return ws;\n",
+ "};\n",
+ "\n",
+ "mpl.mpl_figure_comm = function (comm, msg) {\n",
+ " // This is the function which gets called when the mpl process\n",
+ " // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
+ "\n",
+ " var id = msg.content.data.id;\n",
+ " // Get hold of the div created by the display call when the Comm\n",
+ " // socket was opened in Python.\n",
+ " var element = document.getElementById(id);\n",
+ " var ws_proxy = comm_websocket_adapter(comm);\n",
+ "\n",
+ " function ondownload(figure, _format) {\n",
+ " window.open(figure.canvas.toDataURL());\n",
+ " }\n",
+ "\n",
+ " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n",
+ "\n",
+ " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
+ " // web socket which is closed, not our websocket->open comm proxy.\n",
+ " ws_proxy.onopen();\n",
+ "\n",
+ " fig.parent_element = element;\n",
+ " fig.cell_info = mpl.find_output_cell(\"\");\n",
+ " if (!fig.cell_info) {\n",
+ " console.error('Failed to find cell for figure', id, fig);\n",
+ " return;\n",
+ " }\n",
+ " fig.cell_info[0].output_area.element.on(\n",
+ " 'cleared',\n",
+ " { fig: fig },\n",
+ " fig._remove_fig_handler\n",
+ " );\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_close = function (fig, msg) {\n",
+ " var width = fig.canvas.width / fig.ratio;\n",
+ " fig.cell_info[0].output_area.element.off(\n",
+ " 'cleared',\n",
+ " fig._remove_fig_handler\n",
+ " );\n",
+ " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n",
+ "\n",
+ " // Update the output cell to use the data from the current canvas.\n",
+ " fig.push_to_output();\n",
+ " var dataURL = fig.canvas.toDataURL();\n",
+ " // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
+ " // the notebook keyboard shortcuts fail.\n",
+ " IPython.keyboard_manager.enable();\n",
+ " fig.parent_element.innerHTML =\n",
+ " '
';\n",
+ " fig.close_ws(fig, msg);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.close_ws = function (fig, msg) {\n",
+ " fig.send_message('closing', msg);\n",
+ " // fig.ws.close()\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n",
+ " // Turn the data on the canvas into data in the output cell.\n",
+ " var width = this.canvas.width / this.ratio;\n",
+ " var dataURL = this.canvas.toDataURL();\n",
+ " this.cell_info[1]['text/html'] =\n",
+ " '
';\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.updated_canvas_event = function () {\n",
+ " // Tell IPython that the notebook contents must change.\n",
+ " IPython.notebook.set_dirty(true);\n",
+ " this.send_message('ack', {});\n",
+ " var fig = this;\n",
+ " // Wait a second, then push the new image to the DOM so\n",
+ " // that it is saved nicely (might be nice to debounce this).\n",
+ " setTimeout(function () {\n",
+ " fig.push_to_output();\n",
+ " }, 1000);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._init_toolbar = function () {\n",
+ " var fig = this;\n",
+ "\n",
+ " var toolbar = document.createElement('div');\n",
+ " toolbar.classList = 'btn-toolbar';\n",
+ " this.root.appendChild(toolbar);\n",
+ "\n",
+ " function on_click_closure(name) {\n",
+ " return function (_event) {\n",
+ " return fig.toolbar_button_onclick(name);\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " function on_mouseover_closure(tooltip) {\n",
+ " return function (event) {\n",
+ " if (!event.currentTarget.disabled) {\n",
+ " return fig.toolbar_button_onmouseover(tooltip);\n",
+ " }\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " fig.buttons = {};\n",
+ " var buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'btn-group';\n",
+ " var button;\n",
+ " for (var toolbar_ind in mpl.toolbar_items) {\n",
+ " var name = mpl.toolbar_items[toolbar_ind][0];\n",
+ " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+ " var image = mpl.toolbar_items[toolbar_ind][2];\n",
+ " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+ "\n",
+ " if (!name) {\n",
+ " /* Instead of a spacer, we start a new button group. */\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ " buttonGroup = document.createElement('div');\n",
+ " buttonGroup.classList = 'btn-group';\n",
+ " continue;\n",
+ " }\n",
+ "\n",
+ " button = fig.buttons[name] = document.createElement('button');\n",
+ " button.classList = 'btn btn-default';\n",
+ " button.href = '#';\n",
+ " button.title = name;\n",
+ " button.innerHTML = '';\n",
+ " button.addEventListener('click', on_click_closure(method_name));\n",
+ " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+ " buttonGroup.appendChild(button);\n",
+ " }\n",
+ "\n",
+ " if (buttonGroup.hasChildNodes()) {\n",
+ " toolbar.appendChild(buttonGroup);\n",
+ " }\n",
+ "\n",
+ " // Add the status bar.\n",
+ " var status_bar = document.createElement('span');\n",
+ " status_bar.classList = 'mpl-message pull-right';\n",
+ " toolbar.appendChild(status_bar);\n",
+ " this.message = status_bar;\n",
+ "\n",
+ " // Add the close button to the window.\n",
+ " var buttongrp = document.createElement('div');\n",
+ " buttongrp.classList = 'btn-group inline pull-right';\n",
+ " button = document.createElement('button');\n",
+ " button.classList = 'btn btn-mini btn-primary';\n",
+ " button.href = '#';\n",
+ " button.title = 'Stop Interaction';\n",
+ " button.innerHTML = '';\n",
+ " button.addEventListener('click', function (_evt) {\n",
+ " fig.handle_close(fig, {});\n",
+ " });\n",
+ " button.addEventListener(\n",
+ " 'mouseover',\n",
+ " on_mouseover_closure('Stop Interaction')\n",
+ " );\n",
+ " buttongrp.appendChild(button);\n",
+ " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n",
+ " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._remove_fig_handler = function (event) {\n",
+ " var fig = event.data.fig;\n",
+ " if (event.target !== this) {\n",
+ " // Ignore bubbled events from children.\n",
+ " return;\n",
+ " }\n",
+ " fig.close_ws(fig, {});\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._root_extra_style = function (el) {\n",
+ " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._canvas_extra_style = function (el) {\n",
+ " // this is important to make the div 'focusable\n",
+ " el.setAttribute('tabindex', 0);\n",
+ " // reach out to IPython and tell the keyboard manager to turn it's self\n",
+ " // off when our div gets focus\n",
+ "\n",
+ " // location in version 3\n",
+ " if (IPython.notebook.keyboard_manager) {\n",
+ " IPython.notebook.keyboard_manager.register_events(el);\n",
+ " } else {\n",
+ " // location in version 2\n",
+ " IPython.keyboard_manager.register_events(el);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype._key_event_extra = function (event, _name) {\n",
+ " // Check for shift+enter\n",
+ " if (event.shiftKey && event.which === 13) {\n",
+ " this.canvas_div.blur();\n",
+ " // select the cell after this one\n",
+ " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n",
+ " IPython.notebook.select(index + 1);\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+ " fig.ondownload(fig, null);\n",
+ "};\n",
+ "\n",
+ "mpl.find_output_cell = function (html_output) {\n",
+ " // Return the cell and output element which can be found *uniquely* in the notebook.\n",
+ " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
+ " // IPython event is triggered only after the cells have been serialised, which for\n",
+ " // our purposes (turning an active figure into a static one), is too late.\n",
+ " var cells = IPython.notebook.get_cells();\n",
+ " var ncells = cells.length;\n",
+ " for (var i = 0; i < ncells; i++) {\n",
+ " var cell = cells[i];\n",
+ " if (cell.cell_type === 'code') {\n",
+ " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n",
+ " var data = cell.output_area.outputs[j];\n",
+ " if (data.data) {\n",
+ " // IPython >= 3 moved mimebundle to data attribute of output\n",
+ " data = data.data;\n",
+ " }\n",
+ " if (data['text/html'] === html_output) {\n",
+ " return [cell, data, j];\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "// Register the function which deals with the matplotlib target/channel.\n",
+ "// The kernel may be null if the page has been refreshed.\n",
+ "if (IPython.notebook.kernel !== null) {\n",
+ " IPython.notebook.kernel.comm_manager.register_target(\n",
+ " 'matplotlib',\n",
+ " mpl.mpl_figure_comm\n",
+ " );\n",
+ "}\n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "
"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "%matplotlib notebook\n",
+ "\n",
+ "# Read in results\n",
+ "## Params\n",
+ "folder = Path(\"./results/galaxy\")\n",
+ "\n",
+ "## Plot code\n",
+ "res = {}\n",
+ "\n",
+ "\n",
+ "def _load():\n",
+ " # res: dict[benchmark, results]\n",
+ " # results: dict[target, list[total_runtime_in_s]]\n",
+ " for file in folder.iterdir():\n",
+ " date_str, _, _, benchmark, *target, _ = file.stem.split(\"_\")\n",
+ " target = \"_\".join(target)\n",
+ " file_content = json.loads(file.read_text())\n",
+ "\n",
+ " results = file_content[\"results\"]\n",
+ " key = f\"benchmark_galaxy_{benchmark}_{target}_backend\"\n",
+ " if key not in results or len(results[key]) == 0:\n",
+ " print(\"No result for:\", date_str, benchmark, target)\n",
+ " continue\n",
+ " value = float(results[key][0][\"total_runtime_in_s\"])\n",
+ "\n",
+ " if benchmark not in res:\n",
+ " res[benchmark] = {}\n",
+ "\n",
+ " if target not in res[benchmark]:\n",
+ " res[benchmark][target] = []\n",
+ " res[benchmark][target].append(value)\n",
+ "\n",
+ " labels = sorted(k for k in res.keys())\n",
+ " return labels, res\n",
+ "\n",
+ "def _extract_results(data, labels):\n",
+ " pd_data = []\n",
+ " for label in labels:\n",
+ " values = data[label] if label in data else [0]\n",
+ " mean = np.mean(values)\n",
+ " #conf = confidence_interval(values)\n",
+ " std = np.std(values)\n",
+ " num = len(values)\n",
+ " pd_data.append([mean, std, num])\n",
+ "\n",
+ "\n",
+ " df = pd.DataFrame(data=pd_data, index=labels, columns=[\"mean\", \"std\", \"num\"])\n",
+ " return df\n",
+ "\n",
+ "def generate_plot(res, configs, benchmarks, title):\n",
+ " data = {}\n",
+ " for b in benchmarks:\n",
+ " data[b] = _extract_results(res[b], configs)\n",
+ " df = pd.concat(data)\n",
+ "\n",
+ " def fix_df(df):\n",
+ " \"\"\"unstack changes the order, fix order and rename index'\"\"\"\n",
+ " df = df.reindex(index=configs, columns=benchmarks)\n",
+ " df.rename(index={\"isilon\": \"glx_nfs_isilon\", \"netapp\": \"glx_nfs_netapp\", \"s3\": \"glx_s3_netapp\"}, inplace=True)\n",
+ " df.rename(index={\"irods_on_isilon\": \"glx_irods_isilon\", \"irods_on_netapp\": \"glx_irods_netapp\", \"irods_on_s3\": \"glx_irods_s3\"}, inplace=True)\n",
+ " df.rename(index={\"irods_via_davrods_on_isilon\": \"glx_webdav_isilon\", \"irods_via_davrods_on_netapp\": \"glx_webdav_netapp\", \"irods_via_davrods_on_s3\": \"glx_webdav_s3\"}, inplace=True)\n",
+ " return df\n",
+ "\n",
+ " print(df)\n",
+ " df_mean = fix_df(df[\"mean\"].unstack(level=0))\n",
+ " df_std = fix_df(df[\"std\"].unstack(level=0))\n",
+ " df_mean.plot(\n",
+ " kind='bar', rot=0,\n",
+ " xlabel='Configuration',\n",
+ " ylabel='Time to disk (s)',\n",
+ " figsize=(8, 4),\n",
+ " yerr=df_std\n",
+ " )\n",
+ "\n",
+ " plt.legend()\n",
+ " plt.title(title)\n",
+ " plt.savefig(title.lower().replace(\" \", \"_\").replace(\":\",\"\")+\".png\",bbox_inches='tight')\n",
+ " plt.show()\n",
+ "\n",
+ "labels, res = _load()\n",
+ "print(labels)\n",
+ "# print(res[\"1x0\"].keys())\n",
+ "many_files = [\"1x0\", \"1x1K\", \"10x1K\", \"100x1K\", \"1000x1K\", \"2000x1K\"]\n",
+ "large_files = [\"1x0\", \"1x1K\", \"1x1M\", \"1x1G\", \"10x1G\"]\n",
+ "\n",
+ "generate_plot(res, [\"isilon\", \"netapp\", \"s3\"], many_files, \"Galaxy with direct access: Many files\")\n",
+ "generate_plot(res, [\"isilon\", \"netapp\", \"s3\"], large_files, \"Galaxy with direct access: Large files\")\n",
+ "\n",
+ "generate_plot(res, [\"irods_on_isilon\", \"irods_on_netapp\", \"irods_on_s3\"], many_files, \"Galaxy via iRODS: Many files\")\n",
+ "generate_plot(res, [\"irods_on_isilon\", \"irods_on_netapp\", \"irods_on_s3\"], large_files, \"Galaxy via iRODS: Large files\")\n",
+ "\n",
+ "generate_plot(res, [\"irods_via_davrods_on_isilon\", \"irods_via_davrods_on_netapp\", \"irods_via_davrods_on_s3\"], many_files, \"Galaxy via Davrods: Many files\")\n",
+ "generate_plot(res, [\"irods_via_davrods_on_isilon\", \"irods_via_davrods_on_netapp\", \"irods_via_davrods_on_s3\"], large_files, \"Galaxy via Davrods: Large files\")\n",
+ "\n",
+ "generate_plot(res, [\"isilon\", \"irods_on_isilon\", \"irods_via_davrods_on_isilon\"], many_files, \"Galaxy comparison for Isilon: Many files\")\n",
+ "generate_plot(res, [\"isilon\", \"irods_on_isilon\", \"irods_via_davrods_on_isilon\"], large_files, \"Galaxy comparison for Isilon: Large files\")\n",
+ "\n",
+ "generate_plot(res, [\"netapp\", \"irods_on_netapp\", \"irods_via_davrods_on_netapp\"], many_files, \"Galaxy comparison for Netapp: Many files\")\n",
+ "generate_plot(res, [\"netapp\", \"irods_on_netapp\", \"irods_via_davrods_on_netapp\"], large_files, \"Galaxy comparison for Netapp: Large files\")\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "7a5a2bf0",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.8.10"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/examples/00_available_options.yml b/examples/00_available_options.yml
new file mode 100644
index 00000000..718210c7
--- /dev/null
+++ b/examples/00_available_options.yml
@@ -0,0 +1,38 @@
+config:
+
+ results_path: "optional str, default: results/"
+ results_save_to_file: "optional bool, default: True"
+ results_print: "optional bool, default True"
+
+ log_ansible_output: "optional bool, default False"
+
+ openstack: "optional obj, see OpenStackComputeConfig"
+ galaxy: "optional obj, see GalaxyConfig"
+
+# Preconfigure some ansible tasks, can be referenced by name
+tasks:
+ task1:
+ playbook: "required str"
+ playbook_folder: "optional str, default: ansible/"
+ # Extra vars used in the playbook
+ extra_vars:
+ key1: value1
+
+ host: "optional str, can also be a ansible host pattern"
+
+benchmarks:
+ benchmark1:
+ type: "required str, refers to Benchmark class"
+ repetitions: "required int"
+
+ # Ansible task(s) executed before the benchmark
+ pre_task: "str OR obj"
+ # OR
+ pre_tasks:
+ - "str OR obj"
+
+ # Ansible task(s) executed after the benchmark
+ post_task: "str OR obj"
+ # OR
+ post_tasks:
+ - "str OR obj"
\ No newline at end of file
diff --git a/examples/01_verify_setup.yml b/examples/01_verify_setup.yml
new file mode 100644
index 00000000..8a0fe46b
--- /dev/null
+++ b/examples/01_verify_setup.yml
@@ -0,0 +1,17 @@
+config:
+ log_ansible_output: true
+ results_print: false
+ results_save_to_file: false
+
+benchmarks:
+ verify_setup_benchmark:
+ type: SetupTimeBenchmark
+ repetitions: 1
+
+ pre_tasks:
+ - playbook: install_docker.yml
+ host: "irods_client,irods_server"
+
+ hosts:
+ - irods_client
+ - irods_server
diff --git a/examples/02_parameval_dd.yml b/examples/02_parameval_dd.yml
new file mode 100644
index 00000000..e8418e09
--- /dev/null
+++ b/examples/02_parameval_dd.yml
@@ -0,0 +1,62 @@
+config:
+ log_ansible_output: false
+ results_print: false
+ results_save_raw_results: false
+ results_save_to_file: true
+
+
+DestIsilon: &dest_isilon
+ destination:
+ host: irods_server
+ target_folder: /mnt/isilon
+
+DestNetapp: &dest_netapp
+ destination:
+ host: irods_server
+ target_folder: /mnt/netapp
+
+ConfigDdSingle: &conf_dd_single
+ repetitions: 1
+ type: DdOneDimParams
+ dd:
+ blocksize: 1024k
+
+ dim_key: blockcount
+ dim_values:
+ - 1024
+ - 2048
+ - 4096
+ - 8192
+ - 16384
+
+ConfigDdParallel: &conf_dd_parallel
+ repetitions: 1
+ type: DdOneDimParams
+ dd:
+ blocksize: 1024k
+ parallel: True
+
+ dim_key: blockcount
+ dim_values:
+ - 1024
+ - 2048
+ - 4096
+ - 8192
+ - 16384
+
+benchmarks:
+ parameval_dd_isilon:
+ type: BenchmarkCompare
+ repetitions: 1
+ bench_a:
+ <<: [*dest_isilon, *conf_dd_single]
+ bench_b:
+ <<: [*dest_isilon, *conf_dd_parallel]
+
+ parameval_dd_netapp:
+ type: BenchmarkCompare
+ repetitions: 1
+ bench_a:
+ <<: [*dest_netapp, *conf_dd_single]
+ bench_b:
+ <<: [*dest_netapp, *conf_dd_parallel]
diff --git a/examples/02_parameval_fio_bandwidth.yml b/examples/02_parameval_fio_bandwidth.yml
new file mode 100644
index 00000000..8f9dbd7c
--- /dev/null
+++ b/examples/02_parameval_fio_bandwidth.yml
@@ -0,0 +1,164 @@
+config:
+ log_ansible_output: false
+ results_print: false
+ results_save_to_file: true
+
+
+DestIsilon: &dest_isilon
+ destination:
+ host: irods_server
+ target_folder: /mnt/isilon
+
+DestNetapp: &dest_netapp
+ destination:
+ host: irods_server
+ target_folder: /mnt/netapp
+
+ConfigFioRuntime: &conf_fio_runtime
+ repetitions: 1
+ type: FioOneDimParams
+ fio:
+ mode: write
+ blocksize: 1024k
+ filesize: 4G
+ numjobs: 4
+ iodepth: 32
+ time_based: true
+
+ dim_key: runtime_in_s
+ dim_values:
+ - 10
+ - 20
+ - 60
+ - 120
+ - 300
+
+ConfigFioRuntime3: &conf_fio_ramptime
+ repetitions: 1
+ type: FioOneDimParams
+ fio:
+ mode: write
+ blocksize: 1024k
+ filesize: 4G
+ numjobs: 4
+ iodepth: 32
+ time_based: true
+ runtime_in_s: 60
+
+ dim_key: ramptime_in_s
+ dim_values:
+ - 10
+ - 20
+ - 60
+ - 120
+ - 300
+
+ConfigFioIoDepth: &conf_fio_iodepth
+ repetitions: 1
+ type: FioOneDimParams
+ fio:
+ mode: write
+ blocksize: 1024k
+ filesize: 4G
+ numjobs: 4
+ time_based: true
+ runtime_in_s: 60
+
+ dim_key: iodepth
+ dim_values:
+ - 1
+ - 2
+ - 4
+ - 8
+ - 16
+ - 32
+ - 64
+
+ConfigFioBlocksize: &conf_fio_blocksize
+ repetitions: 1
+ type: FioOneDimParams
+ fio:
+ mode: write
+ filesize: 4G
+ numjobs: 4
+ iodepth: 32
+ time_based: true
+ runtime_in_s: 60
+
+ dim_key: blocksize
+ dim_values:
+ - 4k
+ - 16k
+ - 64k
+ - 256k
+ - 1024k
+ - 4096k
+ - 16384k
+
+ConfigFioJobs: &conf_fio_numjobs
+ repetitions: 1
+ type: FioOneDimParams
+ fio:
+ mode: write
+ blocksize: 1024k
+ filesize: 4G
+ iodepth: 32
+ time_based: true
+ runtime_in_s: 60
+
+ dim_key: numjobs
+ dim_values:
+ - 1
+ - 2
+ - 4
+ - 8
+ - 16
+
+ConfigFioFilesize: &conf_fio_filesize
+ repetitions: 1
+ type: FioOneDimParams
+ fio:
+ mode: write
+ blocksize: 1024k
+ numjobs: 4
+ iodepth: 32
+ time_based: true
+ runtime_in_s: 60
+
+ dim_key: filesize
+ dim_values:
+ - 256M
+ - 1G
+ - 4G
+ - 16G
+
+benchmarks:
+ parameval_fio_bandwidth_runtime_isilon:
+ <<: [*dest_isilon, *conf_fio_runtime]
+ parameval_fio_bandwidth_runtime_netapp:
+ <<: [*dest_netapp, *conf_fio_runtime]
+
+ parameval_fio_bandwidth_ramptime_isilon:
+ <<: [*dest_isilon, *conf_fio_ramptime]
+ parameval_fio_bandwidth_ramptime_netapp:
+ <<: [*dest_netapp, *conf_fio_ramptime]
+
+ parameval_fio_bandwidth_iodepth_isilon:
+ <<: [*dest_isilon, *conf_fio_iodepth]
+ parameval_fio_bandwidth_iodepth_netapp:
+ <<: [*dest_netapp, *conf_fio_iodepth]
+
+ parameval_fio_bandwidth_blocksize_isilon:
+ <<: [*dest_isilon, *conf_fio_blocksize]
+ parameval_fio_bandwidth_blocksize_netapp:
+ <<: [*dest_netapp, *conf_fio_blocksize]
+
+ parameval_fio_bandwidth_numjobs_isilon:
+ <<: [*dest_isilon, *conf_fio_numjobs]
+ parameval_fio_bandwidth_numjobs_netapp:
+ <<: [*dest_netapp, *conf_fio_numjobs]
+
+ parameval_fio_bandwidth_filesize_isilon:
+ <<: [*dest_isilon, *conf_fio_filesize]
+ parameval_fio_bandwidth_filesize_netapp:
+ <<: [*dest_netapp, *conf_fio_filesize]
diff --git a/examples/02_parameval_fio_iops.yml b/examples/02_parameval_fio_iops.yml
new file mode 100644
index 00000000..80250a1a
--- /dev/null
+++ b/examples/02_parameval_fio_iops.yml
@@ -0,0 +1,112 @@
+config:
+ log_ansible_output: false
+ results_print: false
+ results_save_to_file: true
+
+
+DestIsilon: &dest_isilon
+ destination:
+ host: irods_server
+ target_folder: /mnt/isilon
+
+DestNetapp: &dest_netapp
+ destination:
+ host: irods_server
+ target_folder: /mnt/netapp
+
+ConfigFioIoDepth: &conf_fio_iodepth
+ repetitions: 1
+ type: FioOneDimParams
+ fio:
+ mode: randwrite
+ blocksize: 4k
+ filesize: 4G
+ numjobs: 4
+ time_based: true
+ runtime_in_s: 60
+
+ dim_key: iodepth
+ dim_values:
+ - 1
+ - 2
+ - 4
+ - 8
+ - 16
+ - 32
+ - 64
+
+ConfigFioBlocksize: &conf_fio_blocksize
+ repetitions: 1
+ type: FioOneDimParams
+ fio:
+ mode: randwrite
+ filesize: 4G
+ numjobs: 4
+ iodepth: 32
+ time_based: true
+ runtime_in_s: 60
+
+ dim_key: blocksize
+ dim_values:
+ - 1k
+ - 4k
+ - 16k
+ - 64k
+
+ConfigFioJobs: &conf_fio_numjobs
+ repetitions: 1
+ type: FioOneDimParams
+ fio:
+ mode: randwrite
+ blocksize: 4k
+ filesize: 4G
+ iodepth: 32
+ time_based: true
+ runtime_in_s: 60
+
+ dim_key: numjobs
+ dim_values:
+ - 1
+ - 2
+ - 4
+ - 8
+ - 16
+
+ConfigFioFilesize: &conf_fio_filesize
+ repetitions: 1
+ type: FioOneDimParams
+ fio:
+ mode: randwrite
+ blocksize: 4k
+ numjobs: 4
+ iodepth: 32
+ time_based: true
+ runtime_in_s: 60
+
+ dim_key: filesize
+ dim_values:
+ - 256M
+ - 1G
+ - 4G
+ - 16G
+
+benchmarks:
+ parameval_fio_iops_iodepth_isilon:
+ <<: [*dest_isilon, *conf_fio_iodepth]
+ parameval_fio_iops_iodepth_netapp:
+ <<: [*dest_netapp, *conf_fio_iodepth]
+
+ parameval_fio_iops_blocksize_isilon:
+ <<: [*dest_isilon, *conf_fio_blocksize]
+ parameval_fio_iops_blocksize_netapp:
+ <<: [*dest_netapp, *conf_fio_blocksize]
+
+ parameval_fio_iops_numjobs_isilon:
+ <<: [*dest_isilon, *conf_fio_numjobs]
+ parameval_fio_iops_numjobs_netapp:
+ <<: [*dest_netapp, *conf_fio_numjobs]
+
+ parameval_fio_iops_filesize_isilon:
+ <<: [*dest_isilon, *conf_fio_filesize]
+ parameval_fio_iops_filesize_netapp:
+ <<: [*dest_netapp, *conf_fio_filesize]
diff --git a/examples/02_parameval_mdtest.yml b/examples/02_parameval_mdtest.yml
new file mode 100644
index 00000000..e02d65e2
--- /dev/null
+++ b/examples/02_parameval_mdtest.yml
@@ -0,0 +1,26 @@
+config:
+ log_ansible_output: false
+ results_print: false
+ results_save_to_file: true
+
+
+Default: &default
+ type: MdtestOneDimParams
+ repetitions: 1
+ mdtest:
+ num_files: 1
+
+ dim_key: num_files
+ dim_values:
+ - 10
+ - 100
+ - 1000
+ - 10000
+ - 100000
+
+benchmarks:
+ parameval_mdtest_isilon:
+ <<: *default
+ destination:
+ host: irods_server
+ target_folder: /mnt/isilon
diff --git a/examples/02_parameval_s3benchmark.yml b/examples/02_parameval_s3benchmark.yml
new file mode 100644
index 00000000..27dcd7b3
--- /dev/null
+++ b/examples/02_parameval_s3benchmark.yml
@@ -0,0 +1,50 @@
+config:
+ log_ansible_output: false
+ results_print: false
+ results_save_raw_results: false
+ results_save_to_file: true
+
+benchmarks:
+ parameval_s3benchmark_threads:
+ type: S3BenchmarkOneDimParams
+ repetitions: 1
+ destination:
+ host: irods_server
+ s3benchmark:
+ base_url: https://s3.bwsfs.uni-freiburg.de/
+ bucket_name: frct-smoe-bench-ec61-01
+ region: fr-repl
+ filesize: 10M
+
+ dim_key: threads
+ dim_values:
+ - 1
+ - 2
+ - 4
+ - 8
+ - 16
+ - 32
+ - 64
+ - 65
+ - 66
+ - 67
+ - 128
+
+ parameval_s3benchmark_runtime:
+ type: S3BenchmarkOneDimParams
+ repetitions: 1
+ destination:
+ host: irods_server
+ s3benchmark:
+ base_url: https://s3.bwsfs.uni-freiburg.de/
+ bucket_name: frct-smoe-bench-ec61-01
+ region: fr-repl
+ filesize: 10M
+
+ dim_key: runtime_in_s
+ dim_values:
+ - 10
+ - 20
+ - 60
+ - 120
+ - 300
diff --git a/examples/02_parameval_warp.yml b/examples/02_parameval_warp.yml
new file mode 100644
index 00000000..b9008354
--- /dev/null
+++ b/examples/02_parameval_warp.yml
@@ -0,0 +1,51 @@
+config:
+ log_ansible_output: false
+ results_print: false
+ results_save_raw_results: false
+ results_save_to_file: true
+
+benchmarks:
+ parameval_warp_concurrent:
+ type: WarpOneDimParams
+ repetitions: 1
+ destination:
+ host: irods_server
+ warp:
+ mode: put
+ base_url: s3.bwsfs.uni-freiburg.de
+ bucket_name: frct-smoe-bench-ec61-01
+ region: fr-repl
+ filesize: 10MiB
+
+ dim_key: concurrent_ops
+ dim_values:
+ - 1
+ - 2
+ - 4
+ - 8
+ - 16
+ - 32
+ - 33
+ - 34
+ - 35
+ - 64
+
+ parameval_warp_runtime:
+ type: WarpOneDimParams
+ repetitions: 1
+ destination:
+ host: irods_server
+ warp:
+ mode: put
+ base_url: s3.bwsfs.uni-freiburg.de
+ bucket_name: frct-smoe-bench-ec61-01
+ region: fr-repl
+ filesize: 10MiB
+
+ dim_key: runtime
+ dim_values:
+ - 10s
+ - 20s
+ - 1m0s
+ - 2m0s
+ - 5m0s
diff --git a/examples/03_container_vs_native.yml b/examples/03_container_vs_native.yml
new file mode 100644
index 00000000..7ab1b613
--- /dev/null
+++ b/examples/03_container_vs_native.yml
@@ -0,0 +1,121 @@
+config:
+ log_ansible_output: false
+ results_print: false
+ results_save_to_file: true
+
+
+ThroughputWrite: &throughputWrite
+ repetitions: 1
+ fio:
+ mode: write
+ blocksize: 1024k
+ numjobs: 4
+ iodepth: 32
+ destination:
+ host: irods_server
+ target_folder: /mnt/isilon
+
+ThroughputRead: &throughputRead
+ repetitions: 1
+ fio:
+ mode: read
+ blocksize: 1024k
+ numjobs: 4
+ iodepth: 32
+ destination:
+ host: irods_server
+ target_folder: /mnt/isilon
+
+IopsWrite: &iopsWrite
+ repetitions: 1
+ fio:
+ mode: randwrite
+ blocksize: 4k
+ numjobs: 4
+ iodepth: 32
+ destination:
+ host: irods_server
+ target_folder: /mnt/isilon
+
+IopsRead: &iopsRead
+ repetitions: 1
+ fio:
+ mode: randread
+ blocksize: 4k
+ numjobs: 4
+ iodepth: 32
+ destination:
+ host: irods_server
+ target_folder: /mnt/isilon
+
+LatencyWrite: &latencyWrite
+ repetitions: 1
+ fio:
+ mode: randwrite
+ blocksize: 4k
+ numjobs: 1
+ iodepth: 1
+ destination:
+ host: irods_server
+ target_folder: /mnt/isilon
+
+LatencyRead: &latencyRead
+ repetitions: 1
+ fio:
+ mode: randread
+ blocksize: 4k
+ numjobs: 1
+ iodepth: 1
+ destination:
+ host: irods_server
+ target_folder: /mnt/isilon
+
+
+benchmarks:
+ con_vs_nativ_throughput_write_con:
+ <<: *throughputWrite
+ type: FioFixedParams
+
+ con_vs_nativ_throughput_write_nativ:
+ <<: *throughputWrite
+ type: FioNotContainerized
+
+ con_vs_nativ_throughput_read_con:
+ <<: *throughputRead
+ type: FioFixedParams
+
+ con_vs_nativ_throughput_read_nativ:
+ <<: *throughputRead
+ type: FioNotContainerized
+
+ con_vs_nativ_iops_write_con:
+ <<: *iopsWrite
+ type: FioFixedParams
+
+ con_vs_nativ_iops_write_nativ:
+ <<: *iopsWrite
+ type: FioNotContainerized
+
+ con_vs_nativ_iops_read_con:
+ <<: *iopsRead
+ type: FioFixedParams
+
+ con_vs_nativ_iops_read_nativ:
+ <<: *iopsRead
+ type: FioNotContainerized
+
+ con_vs_nativ_latency_write_con:
+ <<: *latencyWrite
+ type: FioFixedParams
+
+ con_vs_nativ_latency_write_nativ:
+ <<: *latencyWrite
+ type: FioNotContainerized
+
+ con_vs_nativ_latency_read_con:
+ <<: *latencyRead
+ type: FioFixedParams
+
+ con_vs_nativ_latency_read_nativ:
+ <<: *latencyRead
+ type: FioNotContainerized
diff --git a/examples/03_dd_vs_fio.yml b/examples/03_dd_vs_fio.yml
new file mode 100644
index 00000000..32694415
--- /dev/null
+++ b/examples/03_dd_vs_fio.yml
@@ -0,0 +1,66 @@
+config:
+ log_ansible_output: false
+ results_print: false
+ results_save_raw_results: false
+ results_save_to_file: true
+
+
+DestIsilon: &dest_isilon
+ destination:
+ host: irods_server
+ target_folder: /mnt/isilon
+
+DestNetapp: &dest_netapp
+ destination:
+ host: irods_server
+ target_folder: /mnt/netapp
+
+ConfigFio: &conf_fio
+ repetitions: 1
+ type: FioOneDimParams
+ fio:
+ mode: write
+ blocksize: 1024k
+ numjobs: 4
+ iodepth: 32
+ time_based: false
+
+ dim_key: filesize
+ dim_values:
+ - 1G
+ - 2G
+ - 4G
+ - 8G
+ - 16G
+
+ConfigDd: &conf_dd
+ repetitions: 1
+ type: DdOneDimParams
+ dd:
+ blocksize: 1024k
+ parallel: True
+
+ dim_key: blockcount
+ dim_values:
+ - 1024
+ - 2048
+ - 4096
+ - 8192
+ - 16384
+
+benchmarks:
+ dd_vs_fio_isilon:
+ type: BenchmarkCompare
+ repetitions: 1
+ bench_a:
+ <<: [*dest_isilon, *conf_fio]
+ bench_b:
+ <<: [*dest_isilon, *conf_dd]
+
+ dd_vs_fio_netapp:
+ type: BenchmarkCompare
+ repetitions: 1
+ bench_a:
+ <<: [*dest_netapp, *conf_fio]
+ bench_b:
+ <<: [*dest_netapp, *conf_dd]
diff --git a/examples/03_s3benchmark_vs_warp.yml b/examples/03_s3benchmark_vs_warp.yml
new file mode 100644
index 00000000..322f1ab1
--- /dev/null
+++ b/examples/03_s3benchmark_vs_warp.yml
@@ -0,0 +1,80 @@
+config:
+ log_ansible_output: false
+ results_print: false
+ results_save_raw_results: false
+ results_save_to_file: true
+
+Dest: &dest
+ destination:
+ host: irods_server
+
+ConfigS3Benchmark: &conf_s3bench
+ repetitions: 1
+ type: S3BenchmarkFixedParams
+ s3benchmark: &conf_s3bench_defaults
+ base_url: https://s3.bwsfs.uni-freiburg.de/
+ bucket_name: frct-smoe-bench-ec61-01
+ filesize: 10M
+ region: fr-repl
+ runtime_in_s: 60
+ threads: 64
+
+ConfigS3BenchmarkOps: &conf_s3bench_ops
+ repetitions: 1
+ type: S3BenchmarkFixedParams
+ s3benchmark:
+ <<: *conf_s3bench_defaults
+ filesize: 1K
+
+ConfigWarpGet: &conf_warp_get_put
+ type: WarpFixedParams
+ repetitions: 1
+ warp: &conf_warp_defaults
+ mode: get
+ base_url: s3.bwsfs.uni-freiburg.de
+ bucket_name: frct-smoe-bench-ec61-01
+ filesize: 10MiB
+ region: fr-repl
+ runtime: 60s
+ concurrent_ops: 32
+
+ConfigWarpDelOps: &conf_warp_del_ops
+ type: WarpFixedParams
+ repetitions: 1
+ warp:
+ <<: *conf_warp_defaults
+ mode: delete
+ filesize: 1KiB
+
+ConfigWarpGetOps: &conf_warp_get_ops
+ type: WarpFixedParams
+ repetitions: 1
+ warp:
+ <<: *conf_warp_defaults
+ mode: get
+ filesize: 1KiB
+
+benchmarks:
+ s3benchmark_vs_warp_get_put:
+ type: BenchmarkCompare
+ repetitions: 1
+ bench_a:
+ <<: [*dest, *conf_s3bench]
+ bench_b:
+ <<: [*dest, *conf_warp_get_put]
+
+ s3benchmark_vs_warp_get_put_ops:
+ type: BenchmarkCompare
+ repetitions: 1
+ bench_a:
+ <<: [*dest, *conf_s3bench_ops]
+ bench_b:
+ <<: [*dest, *conf_warp_get_ops]
+
+ s3benchmark_vs_warp_delete:
+ type: BenchmarkCompare
+ repetitions: 1
+ bench_a:
+ <<: [*dest, *conf_s3bench_ops]
+ bench_b:
+ <<: [*dest, *conf_warp_del_ops]
diff --git a/examples/04_daytime_evaluation.yml b/examples/04_daytime_evaluation.yml
new file mode 100644
index 00000000..b2473b43
--- /dev/null
+++ b/examples/04_daytime_evaluation.yml
@@ -0,0 +1,72 @@
+config:
+ log_ansible_output: false
+ results_print: false
+ results_save_to_file: true
+
+ConfigFio: &conf_fio
+ repetitions: 1
+ type: FioFixedParams
+ fio:
+ mode: write
+ blocksize: 1024k
+ numjobs: 4
+ iodepth: 32
+
+ConfigWarp: &conf_warp
+ repetitions: 1
+ type: WarpFixedParams
+ warp:
+ mode: put
+ base_url: s3.bwsfs.uni-freiburg.de
+ bucket_name: frct-smoe-bench-ec61-01
+ concurrent_ops: 32
+ filesize: 10MiB
+ region: fr-repl
+ runtime: 60s
+
+benchmarks:
+ daytime_evaluation_isilon_01:
+ <<: *conf_fio
+ destination:
+ host: irods_server
+ target_folder: /mnt/isilon
+
+ daytime_evaluation_netapp_01:
+ <<: *conf_fio
+ destination:
+ host: irods_server
+ target_folder: /mnt/netapp
+
+ daytime_evaluation_ssdtank_01:
+ <<: *conf_fio
+ destination:
+ host: irods_server
+ target_folder: /mnt/ssd_tank/test_moehrles
+
+ daytime_evaluation_s3_01:
+ <<: *conf_warp
+ destination:
+ host: irods_server
+
+ daytime_evaluation_isilon_02:
+ <<: *conf_fio
+ destination:
+ host: irods_server
+ target_folder: /mnt/isilon
+
+ daytime_evaluation_netapp_02:
+ <<: *conf_fio
+ destination:
+ host: irods_server
+ target_folder: /mnt/netapp
+
+ daytime_evaluation_ssdtank_02:
+ <<: *conf_fio
+ destination:
+ host: irods_server
+ target_folder: /mnt/ssd_tank/test_moehrles
+
+ daytime_evaluation_s3_02:
+ <<: *conf_warp
+ destination:
+ host: irods_server
diff --git a/examples/05_benchmark_metadata.yml b/examples/05_benchmark_metadata.yml
new file mode 100644
index 00000000..fd98e9b7
--- /dev/null
+++ b/examples/05_benchmark_metadata.yml
@@ -0,0 +1,226 @@
+config:
+ log_ansible_output: false
+ results_print: false
+ results_save_to_file: true
+
+Default_100k: &default_100k
+ type: MdtestOneDimParams
+ repetitions: 1
+ mdtest:
+ num_files: 1
+
+ dim_key: num_files
+ dim_values:
+ - 10
+ - 100
+ - 1000
+ - 10000
+ - 100000
+
+Default_1k: &default_1k
+ type: MdtestOneDimParams
+ repetitions: 1
+ mdtest:
+ num_files: 1
+
+ dim_key: num_files
+ dim_values:
+ - 10
+ - 100
+ - 1000
+
+
+benchmarks:
+ benchmark_metadata_isilon:
+ <<: *default_100k
+ destination:
+ host: irods_server
+ target_folder: /mnt/isilon
+
+ benchmark_metadata_netapp:
+ <<: *default_100k
+ destination:
+ host: irods_server
+ target_folder: /mnt/netapp
+
+ benchmark_metadata_ssd_tank:
+ <<: *default_100k
+ destination:
+ host: irods_server
+ target_folder: /mnt/ssd_tank/test_moehrles
+
+ benchmark_metadata_local:
+ <<: *default_100k
+ destination:
+ host: irods_server
+ target_folder: /mnt/local/ubuntu
+
+ benchmark_metadata_irods_fuse_on_isilon:
+ <<: *default_1k
+ destination:
+ host: irods_client
+ target_folder: /mnt/volume_under_test/irods-fuse
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+ - playbook: setup_irods_fuse_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ irods_server_host: "132.230.223.139"
+ irods_host_volume_owner_uid: "1000"
+ post_tasks:
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+ - playbook: cleanup_irods_fuse_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+
+ benchmark_metadata_irods_fuse_on_netapp:
+ <<: *default_1k
+ destination:
+ host: irods_client
+ target_folder: /mnt/volume_under_test/irods-fuse
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+ - playbook: setup_irods_fuse_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ irods_server_host: "132.230.223.139"
+ irods_host_volume_owner_uid: "1000"
+ post_tasks:
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+ - playbook: cleanup_irods_fuse_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+
+ benchmark_metadata_irods_fuse_on_s3:
+ <<: *default_1k
+ destination:
+ host: irods_client
+ target_folder: /mnt/volume_under_test/irods-fuse
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_use_s3: true
+ - playbook: setup_irods_fuse_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ irods_server_host: "132.230.223.139"
+ irods_host_volume_owner_uid: "1000"
+ post_tasks:
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ - playbook: cleanup_irods_fuse_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+
+ benchmark_metadata_irods_davrods_on_isilon:
+ <<: *default_1k
+ destination:
+ host: irods_client
+ target_folder: /mnt/volume_under_test/irods-webdav
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+ irods_use_davrods: true
+ davrods_owner_uid: "1000"
+ - playbook: setup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ irods_server_host: "132.230.223.139"
+ davrods_owner_uid: "1000"
+
+ post_tasks:
+ - playbook: cleanup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+ irods_use_davrods: true
+
+ benchmark_metadata_irods_davrods_on_netapp:
+ <<: *default_1k
+ destination:
+ host: irods_client
+ target_folder: /mnt/volume_under_test/irods-webdav
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+ irods_use_davrods: true
+ davrods_owner_uid: "1000"
+ - playbook: setup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ irods_server_host: "132.230.223.139"
+ davrods_owner_uid: "1000"
+
+ post_tasks:
+ - playbook: cleanup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+ irods_use_davrods: true
+
+ benchmark_metadata_irods_davrods_on_s3:
+ <<: *default_1k
+ destination:
+ host: irods_client
+ target_folder: /mnt/volume_under_test/irods-webdav
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_use_s3: true
+ irods_use_davrods: true
+ davrods_owner_uid: "1000"
+ - playbook: setup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ irods_server_host: "132.230.223.139"
+ davrods_owner_uid: "1000"
+ post_tasks:
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_use_davrods: true
+ - playbook: cleanup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
diff --git a/examples/05_benchmark_posix.yml b/examples/05_benchmark_posix.yml
new file mode 100644
index 00000000..1e23962c
--- /dev/null
+++ b/examples/05_benchmark_posix.yml
@@ -0,0 +1,213 @@
+config:
+ log_ansible_output: false
+ results_print: false
+ results_save_to_file: true
+
+Default: &default
+ type: FioFullPosix
+ repetitions: 1
+ fio:
+ runtime_in_s: 60
+
+benchmarks:
+ benchmark_posix_isilon:
+ <<: *default
+ destination:
+ host: irods_server
+ target_folder: /mnt/isilon
+
+ benchmark_posix_netapp:
+ <<: *default
+ destination:
+ host: irods_server
+ target_folder: /mnt/netapp
+
+ benchmark_posix_ssd_tank:
+ <<: *default
+ destination:
+ host: irods_server
+ target_folder: /mnt/ssd_tank/test_moehrles
+
+ benchmark_posix_local:
+ <<: *default
+ destination:
+ host: irods_server
+ target_folder: /mnt/local/ubuntu
+
+ benchmark_posix_irods_fuse_on_isilon:
+ <<: *default
+ destination:
+ host: irods_client
+ target_folder: /mnt/volume_under_test/irods-fuse
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+ - playbook: setup_irods_fuse_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ irods_server_host: "132.230.223.139"
+ irods_host_volume_owner_uid: "1000"
+ post_tasks:
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+ - playbook: cleanup_irods_fuse_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+
+ benchmark_posix_irods_fuse_on_netapp:
+ <<: *default
+ destination:
+ host: irods_client
+ target_folder: /mnt/volume_under_test/irods-fuse
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+ - playbook: setup_irods_fuse_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ irods_server_host: "132.230.223.139"
+ irods_host_volume_owner_uid: "1000"
+ post_tasks:
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+ - playbook: cleanup_irods_fuse_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+
+ benchmark_posix_irods_fuse_on_s3:
+ <<: *default
+ destination:
+ host: irods_client
+ target_folder: /mnt/volume_under_test/irods-fuse
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_use_s3: true
+ - playbook: setup_irods_fuse_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ irods_server_host: "132.230.223.139"
+ irods_host_volume_owner_uid: "1000"
+ post_tasks:
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ - playbook: cleanup_irods_fuse_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+
+ benchmark_posix_irods_davrods_on_isilon:
+ <<: *default
+ destination:
+ host: irods_client
+ target_folder: /mnt/volume_under_test/irods-webdav
+
+ fio:
+ runtime_in_s: 60
+ prepare_read_benchmark_in_tmp: true
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+ irods_use_davrods: true
+ davrods_owner_uid: "1000"
+ - playbook: setup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ irods_server_host: "132.230.223.139"
+ davrods_owner_uid: "1000"
+
+ post_tasks:
+ - playbook: cleanup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+ irods_use_davrods: true
+
+ benchmark_posix_irods_davrods_on_netapp:
+ <<: *default
+ destination:
+ host: irods_client
+ target_folder: /mnt/volume_under_test/irods-webdav
+
+ fio:
+ runtime_in_s: 60
+ prepare_read_benchmark_in_tmp: true
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+ irods_use_davrods: true
+ davrods_owner_uid: "1000"
+ - playbook: setup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ irods_server_host: "132.230.223.139"
+ davrods_owner_uid: "1000"
+
+ post_tasks:
+ - playbook: cleanup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+ irods_use_davrods: true
+
+ benchmark_posix_irods_davrods_on_s3:
+ <<: *default
+ destination:
+ host: irods_client
+ target_folder: /mnt/volume_under_test/irods-webdav
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_use_s3: true
+ irods_use_davrods: true
+ davrods_owner_uid: "1000"
+ - playbook: setup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ irods_server_host: "132.230.223.139"
+ davrods_owner_uid: "1000"
+ post_tasks:
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_use_davrods: true
+ - playbook: cleanup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
diff --git a/examples/05_benchmark_s3.yml b/examples/05_benchmark_s3.yml
new file mode 100644
index 00000000..f311e420
--- /dev/null
+++ b/examples/05_benchmark_s3.yml
@@ -0,0 +1,48 @@
+config:
+ log_ansible_output: false
+ results_print: false
+ results_save_raw_results: false
+ results_save_to_file: true
+
+benchmarks:
+ benchmark_s3_warp_get_put:
+ type: WarpFixedParams
+ repetitions: 1
+ destination:
+ host: irods_server
+ warp:
+ mode: get
+ base_url: s3.bwsfs.uni-freiburg.de
+ bucket_name: frct-smoe-bench-ec61-01
+ concurrent_ops: 32
+ filesize: 10MiB
+ region: fr-repl
+ runtime: 5m0s
+
+ benchmark_s3_warp_get_put_ops:
+ type: WarpFixedParams
+ repetitions: 1
+ destination:
+ host: irods_server
+ warp:
+ mode: get
+ base_url: s3.bwsfs.uni-freiburg.de
+ bucket_name: frct-smoe-bench-ec61-01
+ concurrent_ops: 32
+ filesize: 1KiB
+ region: fr-repl
+ runtime: 5m0s
+
+ benchmark_s3_warp_delete:
+ type: WarpFixedParams
+ repetitions: 1
+ destination:
+ host: irods_server
+ warp:
+ mode: delete
+ base_url: s3.bwsfs.uni-freiburg.de
+ bucket_name: frct-smoe-bench-ec61-01
+ concurrent_ops: 32
+ filesize: 1KiB
+ region: fr-repl
+ runtime: 5m0s
diff --git a/examples/06_benchmark_galaxy_10000x1K.yml b/examples/06_benchmark_galaxy_10000x1K.yml
new file mode 100644
index 00000000..fb45fbc7
--- /dev/null
+++ b/examples/06_benchmark_galaxy_10000x1K.yml
@@ -0,0 +1,242 @@
+config:
+ log_ansible_output: false
+ results_print: false
+ results_save_raw_results: false
+ results_save_to_file: true
+
+Default: &default
+ input:
+ num_files: 10000
+ file_size_in_bytes: 1024
+ verification_timeout_in_s: 9000
+ export_volume: /mnt/local
+
+
+benchmarks:
+ benchmark_galaxy_10000x1K_isilon_backend:
+ type: GalaxyFileGenOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ target_folder: /mnt/isilon
+ galaxy_job:
+ <<: *default
+ path_to_files: /mnt/isilon/galaxy_in_progress/files
+
+ benchmark_galaxy_10000x1K_netapp_backend:
+ type: GalaxyFileGenOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ target_folder: /mnt/netapp
+ galaxy_job:
+ <<: *default
+ path_to_files: /mnt/netapp/galaxy_in_progress/files
+
+ benchmark_galaxy_10000x1K_s3_backend:
+ type: GalaxyFileGenOnS3Job
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ base_url: https://s3.bwsfs.uni-freiburg.de
+ bucket_name: frct-smoe-bench-ec61-01
+
+ # benchmark_galaxy_10000x1K_irods_via_fuse_on_isilon_backend:
+ # type: GalaxyFileGenOnMountVolumeJob
+ # repetitions: 1
+ # destination:
+ # host: irods_client
+ # target_folder: /mnt/volume_under_test/irods-fuse
+ # galaxy_job:
+ # <<: *default
+ # path_to_files: /mnt/isilon/irods/home/rods/galaxy_in_progress/files
+
+ # pre_tasks:
+ # - playbook: setup_irods_server.yml
+ # host: irods_server
+ # extra_vars:
+ # irods_host_volume: /mnt/isilon
+ # - playbook: setup_irods_fuse_client.yml
+ # host: irods_client
+ # extra_vars:
+ # irods_host_volume: /mnt/volume_under_test
+ # irods_server_host: "132.230.223.139"
+ # irods_host_volume_owner_uid: "1000"
+ # post_tasks:
+ # - playbook: cleanup_irods_server.yml
+ # host: irods_server
+ # extra_vars:
+ # irods_host_volume: /mnt/isilon
+ # - playbook: cleanup_irods_fuse_client.yml
+ # host: irods_client
+ # extra_vars:
+ # irods_host_volume: /mnt/volume_under_test
+
+ benchmark_galaxy_10000x1K_irods_via_davrods_on_isilon_backend:
+ type: GalaxyFileGenOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ target_folder: /mnt/volume_under_test/irods-webdav
+ galaxy_job:
+ <<: *default
+ path_to_files: /mnt/isilon/irods/home/rods/galaxy_in_progress/files
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+ irods_use_davrods: true
+ davrods_owner_uid: "1450"
+ - playbook: setup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ irods_server_host: "132.230.223.139"
+ davrods_owner_uid: "1450"
+
+ post_tasks:
+ - playbook: cleanup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+ irods_use_davrods: true
+
+ benchmark_galaxy_10000x1K_irods_via_davrods_on_netapp_backend:
+ type: GalaxyFileGenOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ target_folder: /mnt/volume_under_test/irods-webdav
+ galaxy_job:
+ <<: *default
+ path_to_files: /mnt/netapp/irods/home/rods/galaxy_in_progress/files
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+ irods_use_davrods: true
+ davrods_owner_uid: "1450"
+ - playbook: setup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ irods_server_host: "132.230.223.139"
+ davrods_owner_uid: "1450"
+
+ post_tasks:
+ - playbook: cleanup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+ irods_use_davrods: true
+
+ benchmark_galaxy_10000x1K_irods_via_davrods_on_s3_backend:
+ type: GalaxyFileGenOnIrodsOnS3Job
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ irods_server_host: "132.230.223.139"
+ base_url: https://s3.bwsfs.uni-freiburg.de
+ bucket_name: frct-smoe-bench-ec61-01
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_use_s3: true
+ irods_use_davrods: true
+ davrods_owner_uid: "1450"
+ - playbook: setup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ irods_server_host: "132.230.223.139"
+ davrods_owner_uid: "1450"
+
+ post_tasks:
+ - playbook: cleanup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_use_davrods: true
+
+ benchmark_galaxy_10000x1K_irods_on_isilon_backend:
+ type: GalaxyFileGenOnIrodsOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ irods_server_host: "132.230.223.139"
+ path_to_files: /mnt/isilon/irods/home/rods
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+ post_tasks:
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+
+ benchmark_galaxy_10000x1K_irods_on_netapp_backend:
+ type: GalaxyFileGenOnIrodsOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ irods_server_host: "132.230.223.139"
+ path_to_files: /mnt/netapp/irods/home/rods
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+ post_tasks:
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+
+ benchmark_galaxy_10000x1K_irods_on_s3_backend:
+ type: GalaxyFileGenOnIrodsOnS3Job
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ irods_server_host: "132.230.223.139"
+ base_url: https://s3.bwsfs.uni-freiburg.de
+ bucket_name: frct-smoe-bench-ec61-01
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_use_s3: true
+ post_tasks:
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
diff --git a/examples/06_benchmark_galaxy_1000x1K.yml b/examples/06_benchmark_galaxy_1000x1K.yml
new file mode 100644
index 00000000..94bf0727
--- /dev/null
+++ b/examples/06_benchmark_galaxy_1000x1K.yml
@@ -0,0 +1,242 @@
+config:
+ log_ansible_output: false
+ results_print: false
+ results_save_raw_results: false
+ results_save_to_file: true
+
+Default: &default
+ input:
+ num_files: 1000
+ file_size_in_bytes: 1024
+ verification_timeout_in_s: 1000
+ export_volume: /mnt/local
+
+
+benchmarks:
+ benchmark_galaxy_1000x1K_isilon_backend:
+ type: GalaxyFileGenOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ target_folder: /mnt/isilon
+ galaxy_job:
+ <<: *default
+ path_to_files: /mnt/isilon/galaxy_in_progress/files
+
+ benchmark_galaxy_1000x1K_netapp_backend:
+ type: GalaxyFileGenOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ target_folder: /mnt/netapp
+ galaxy_job:
+ <<: *default
+ path_to_files: /mnt/netapp/galaxy_in_progress/files
+
+ benchmark_galaxy_1000x1K_s3_backend:
+ type: GalaxyFileGenOnS3Job
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ base_url: https://s3.bwsfs.uni-freiburg.de
+ bucket_name: frct-smoe-bench-ec61-01
+
+ # benchmark_galaxy_1000x1K_irods_via_fuse_on_isilon_backend:
+ # type: GalaxyFileGenOnMountVolumeJob
+ # repetitions: 1
+ # destination:
+ # host: irods_client
+ # target_folder: /mnt/volume_under_test/irods-fuse
+ # galaxy_job:
+ # <<: *default
+ # path_to_files: /mnt/isilon/irods/home/rods/galaxy_in_progress/files
+
+ # pre_tasks:
+ # - playbook: setup_irods_server.yml
+ # host: irods_server
+ # extra_vars:
+ # irods_host_volume: /mnt/isilon
+ # - playbook: setup_irods_fuse_client.yml
+ # host: irods_client
+ # extra_vars:
+ # irods_host_volume: /mnt/volume_under_test
+ # irods_server_host: "132.230.223.139"
+ # irods_host_volume_owner_uid: "1000"
+ # post_tasks:
+ # - playbook: cleanup_irods_server.yml
+ # host: irods_server
+ # extra_vars:
+ # irods_host_volume: /mnt/isilon
+ # - playbook: cleanup_irods_fuse_client.yml
+ # host: irods_client
+ # extra_vars:
+ # irods_host_volume: /mnt/volume_under_test
+
+ benchmark_galaxy_1000x1K_irods_via_davrods_on_isilon_backend:
+ type: GalaxyFileGenOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ target_folder: /mnt/volume_under_test/irods-webdav
+ galaxy_job:
+ <<: *default
+ path_to_files: /mnt/isilon/irods/home/rods/galaxy_in_progress/files
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+ irods_use_davrods: true
+ davrods_owner_uid: "1450"
+ - playbook: setup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ irods_server_host: "132.230.223.139"
+ davrods_owner_uid: "1450"
+
+ post_tasks:
+ - playbook: cleanup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+ irods_use_davrods: true
+
+ benchmark_galaxy_1000x1K_irods_via_davrods_on_netapp_backend:
+ type: GalaxyFileGenOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ target_folder: /mnt/volume_under_test/irods-webdav
+ galaxy_job:
+ <<: *default
+ path_to_files: /mnt/netapp/irods/home/rods/galaxy_in_progress/files
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+ irods_use_davrods: true
+ davrods_owner_uid: "1450"
+ - playbook: setup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ irods_server_host: "132.230.223.139"
+ davrods_owner_uid: "1450"
+
+ post_tasks:
+ - playbook: cleanup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+ irods_use_davrods: true
+
+ benchmark_galaxy_1000x1K_irods_via_davrods_on_s3_backend:
+ type: GalaxyFileGenOnIrodsOnS3Job
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ irods_server_host: "132.230.223.139"
+ base_url: https://s3.bwsfs.uni-freiburg.de
+ bucket_name: frct-smoe-bench-ec61-01
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_use_s3: true
+ irods_use_davrods: true
+ davrods_owner_uid: "1450"
+ - playbook: setup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ irods_server_host: "132.230.223.139"
+ davrods_owner_uid: "1450"
+
+ post_tasks:
+ - playbook: cleanup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_use_davrods: true
+
+ benchmark_galaxy_1000x1K_irods_on_isilon_backend:
+ type: GalaxyFileGenOnIrodsOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ irods_server_host: "132.230.223.139"
+ path_to_files: /mnt/isilon/irods/home/rods
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+ post_tasks:
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+
+ benchmark_galaxy_1000x1K_irods_on_netapp_backend:
+ type: GalaxyFileGenOnIrodsOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ irods_server_host: "132.230.223.139"
+ path_to_files: /mnt/netapp/irods/home/rods
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+ post_tasks:
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+
+ benchmark_galaxy_1000x1K_irods_on_s3_backend:
+ type: GalaxyFileGenOnIrodsOnS3Job
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ irods_server_host: "132.230.223.139"
+ base_url: https://s3.bwsfs.uni-freiburg.de
+ bucket_name: frct-smoe-bench-ec61-01
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_use_s3: true
+ post_tasks:
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
diff --git a/examples/06_benchmark_galaxy_100x1K.yml b/examples/06_benchmark_galaxy_100x1K.yml
new file mode 100644
index 00000000..19441780
--- /dev/null
+++ b/examples/06_benchmark_galaxy_100x1K.yml
@@ -0,0 +1,242 @@
+config:
+ log_ansible_output: false
+ results_print: false
+ results_save_raw_results: false
+ results_save_to_file: true
+
+Default: &default
+ input:
+ num_files: 100
+ file_size_in_bytes: 1024
+ verification_timeout_in_s: 500
+ export_volume: /mnt/local
+
+
+benchmarks:
+ benchmark_galaxy_100x1K_isilon_backend:
+ type: GalaxyFileGenOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ target_folder: /mnt/isilon
+ galaxy_job:
+ <<: *default
+ path_to_files: /mnt/isilon/galaxy_in_progress/files
+
+ benchmark_galaxy_100x1K_netapp_backend:
+ type: GalaxyFileGenOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ target_folder: /mnt/netapp
+ galaxy_job:
+ <<: *default
+ path_to_files: /mnt/netapp/galaxy_in_progress/files
+
+ benchmark_galaxy_100x1K_s3_backend:
+ type: GalaxyFileGenOnS3Job
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ base_url: https://s3.bwsfs.uni-freiburg.de
+ bucket_name: frct-smoe-bench-ec61-01
+
+ # benchmark_galaxy_100x1K_irods_via_fuse_on_isilon_backend:
+ # type: GalaxyFileGenOnMountVolumeJob
+ # repetitions: 1
+ # destination:
+ # host: irods_client
+ # target_folder: /mnt/volume_under_test/irods-fuse
+ # galaxy_job:
+ # <<: *default
+ # path_to_files: /mnt/isilon/irods/home/rods/galaxy_in_progress/files
+
+ # pre_tasks:
+ # - playbook: setup_irods_server.yml
+ # host: irods_server
+ # extra_vars:
+ # irods_host_volume: /mnt/isilon
+ # - playbook: setup_irods_fuse_client.yml
+ # host: irods_client
+ # extra_vars:
+ # irods_host_volume: /mnt/volume_under_test
+ # irods_server_host: "132.230.223.139"
+ # irods_host_volume_owner_uid: "1000"
+ # post_tasks:
+ # - playbook: cleanup_irods_server.yml
+ # host: irods_server
+ # extra_vars:
+ # irods_host_volume: /mnt/isilon
+ # - playbook: cleanup_irods_fuse_client.yml
+ # host: irods_client
+ # extra_vars:
+ # irods_host_volume: /mnt/volume_under_test
+
+ benchmark_galaxy_100x1K_irods_via_davrods_on_isilon_backend:
+ type: GalaxyFileGenOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ target_folder: /mnt/volume_under_test/irods-webdav
+ galaxy_job:
+ <<: *default
+ path_to_files: /mnt/isilon/irods/home/rods/galaxy_in_progress/files
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+ irods_use_davrods: true
+ davrods_owner_uid: "1450"
+ - playbook: setup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ irods_server_host: "132.230.223.139"
+ davrods_owner_uid: "1450"
+
+ post_tasks:
+ - playbook: cleanup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+ irods_use_davrods: true
+
+ benchmark_galaxy_100x1K_irods_via_davrods_on_netapp_backend:
+ type: GalaxyFileGenOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ target_folder: /mnt/volume_under_test/irods-webdav
+ galaxy_job:
+ <<: *default
+ path_to_files: /mnt/netapp/irods/home/rods/galaxy_in_progress/files
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+ irods_use_davrods: true
+ davrods_owner_uid: "1450"
+ - playbook: setup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ irods_server_host: "132.230.223.139"
+ davrods_owner_uid: "1450"
+
+ post_tasks:
+ - playbook: cleanup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+ irods_use_davrods: true
+
+ benchmark_galaxy_100x1K_irods_via_davrods_on_s3_backend:
+ type: GalaxyFileGenOnIrodsOnS3Job
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ irods_server_host: "132.230.223.139"
+ base_url: https://s3.bwsfs.uni-freiburg.de
+ bucket_name: frct-smoe-bench-ec61-01
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_use_s3: true
+ irods_use_davrods: true
+ davrods_owner_uid: "1450"
+ - playbook: setup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ irods_server_host: "132.230.223.139"
+ davrods_owner_uid: "1450"
+
+ post_tasks:
+ - playbook: cleanup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_use_davrods: true
+
+ benchmark_galaxy_100x1K_irods_on_isilon_backend:
+ type: GalaxyFileGenOnIrodsOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ irods_server_host: "132.230.223.139"
+ path_to_files: /mnt/isilon/irods/home/rods
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+ post_tasks:
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+
+ benchmark_galaxy_100x1K_irods_on_netapp_backend:
+ type: GalaxyFileGenOnIrodsOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ irods_server_host: "132.230.223.139"
+ path_to_files: /mnt/netapp/irods/home/rods
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+ post_tasks:
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+
+ benchmark_galaxy_100x1K_irods_on_s3_backend:
+ type: GalaxyFileGenOnIrodsOnS3Job
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ irods_server_host: "132.230.223.139"
+ base_url: https://s3.bwsfs.uni-freiburg.de
+ bucket_name: frct-smoe-bench-ec61-01
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_use_s3: true
+ post_tasks:
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
diff --git a/examples/06_benchmark_galaxy_10x1G.yml b/examples/06_benchmark_galaxy_10x1G.yml
new file mode 100644
index 00000000..931526a7
--- /dev/null
+++ b/examples/06_benchmark_galaxy_10x1G.yml
@@ -0,0 +1,242 @@
+config:
+ log_ansible_output: false
+ results_print: false
+ results_save_raw_results: false
+ results_save_to_file: true
+
+Default: &default
+ input:
+ num_files: 10
+ file_size_in_bytes: 1073741824 # 1 Gib
+ verification_timeout_in_s: 1000
+ export_volume: /mnt/local
+
+
+benchmarks:
+ benchmark_galaxy_10x1G_isilon_backend:
+ type: GalaxyFileGenOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ target_folder: /mnt/isilon
+ galaxy_job:
+ <<: *default
+ path_to_files: /mnt/isilon/galaxy_in_progress/files
+
+ benchmark_galaxy_10x1G_netapp_backend:
+ type: GalaxyFileGenOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ target_folder: /mnt/netapp
+ galaxy_job:
+ <<: *default
+ path_to_files: /mnt/netapp/galaxy_in_progress/files
+
+ # benchmark_galaxy_10x1G_s3_backend:
+ # type: GalaxyFileGenOnS3Job
+ # repetitions: 1
+ # destination:
+ # host: irods_client
+ # galaxy_job:
+ # <<: *default
+ # base_url: https://s3.bwsfs.uni-freiburg.de
+ # bucket_name: frct-smoe-bench-ec61-01
+
+ # benchmark_galaxy_10x1G_irods_via_fuse_on_isilon_backend:
+ # type: GalaxyFileGenOnMountVolumeJob
+ # repetitions: 1
+ # destination:
+ # host: irods_client
+ # target_folder: /mnt/volume_under_test/irods-fuse
+ # galaxy_job:
+ # <<: *default
+ # path_to_files: /mnt/isilon/irods/home/rods/galaxy_in_progress/files
+
+ # pre_tasks:
+ # - playbook: setup_irods_server.yml
+ # host: irods_server
+ # extra_vars:
+ # irods_host_volume: /mnt/isilon
+ # - playbook: setup_irods_fuse_client.yml
+ # host: irods_client
+ # extra_vars:
+ # irods_host_volume: /mnt/volume_under_test
+ # irods_server_host: "132.230.223.139"
+ # irods_host_volume_owner_uid: "1000"
+ # post_tasks:
+ # - playbook: cleanup_irods_server.yml
+ # host: irods_server
+ # extra_vars:
+ # irods_host_volume: /mnt/isilon
+ # - playbook: cleanup_irods_fuse_client.yml
+ # host: irods_client
+ # extra_vars:
+ # irods_host_volume: /mnt/volume_under_test
+
+ benchmark_galaxy_10x1G_irods_via_davrods_on_isilon_backend:
+ type: GalaxyFileGenOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ target_folder: /mnt/volume_under_test/irods-webdav
+ galaxy_job:
+ <<: *default
+ path_to_files: /mnt/isilon/irods/home/rods/galaxy_in_progress/files
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+ irods_use_davrods: true
+ davrods_owner_uid: "1450"
+ - playbook: setup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ irods_server_host: "132.230.223.139"
+ davrods_owner_uid: "1450"
+
+ post_tasks:
+ - playbook: cleanup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+ irods_use_davrods: true
+
+ benchmark_galaxy_10x1G_irods_via_davrods_on_netapp_backend:
+ type: GalaxyFileGenOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ target_folder: /mnt/volume_under_test/irods-webdav
+ galaxy_job:
+ <<: *default
+ path_to_files: /mnt/netapp/irods/home/rods/galaxy_in_progress/files
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+ irods_use_davrods: true
+ davrods_owner_uid: "1450"
+ - playbook: setup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ irods_server_host: "132.230.223.139"
+ davrods_owner_uid: "1450"
+
+ post_tasks:
+ - playbook: cleanup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+ irods_use_davrods: true
+
+ benchmark_galaxy_10x1G_irods_via_davrods_on_s3_backend:
+ type: GalaxyFileGenOnIrodsOnS3Job
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ irods_server_host: "132.230.223.139"
+ base_url: https://s3.bwsfs.uni-freiburg.de
+ bucket_name: frct-smoe-bench-ec61-01
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_use_s3: true
+ irods_use_davrods: true
+ davrods_owner_uid: "1450"
+ - playbook: setup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ irods_server_host: "132.230.223.139"
+ davrods_owner_uid: "1450"
+
+ post_tasks:
+ - playbook: cleanup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_use_davrods: true
+
+ benchmark_galaxy_10x1G_irods_on_isilon_backend:
+ type: GalaxyFileGenOnIrodsOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ irods_server_host: "132.230.223.139"
+ path_to_files: /mnt/isilon/irods/home/rods
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+ post_tasks:
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+
+ benchmark_galaxy_10x1G_irods_on_netapp_backend:
+ type: GalaxyFileGenOnIrodsOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ irods_server_host: "132.230.223.139"
+ path_to_files: /mnt/netapp/irods/home/rods
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+ post_tasks:
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+
+ benchmark_galaxy_10x1G_irods_on_s3_backend:
+ type: GalaxyFileGenOnIrodsOnS3Job
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ irods_server_host: "132.230.223.139"
+ base_url: https://s3.bwsfs.uni-freiburg.de
+ bucket_name: frct-smoe-bench-ec61-01
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_use_s3: true
+ post_tasks:
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
diff --git a/examples/06_benchmark_galaxy_10x1K.yml b/examples/06_benchmark_galaxy_10x1K.yml
new file mode 100644
index 00000000..53f83798
--- /dev/null
+++ b/examples/06_benchmark_galaxy_10x1K.yml
@@ -0,0 +1,242 @@
+config:
+ log_ansible_output: false
+ results_print: false
+ results_save_raw_results: false
+ results_save_to_file: true
+
+Default: &default
+ input:
+ num_files: 10
+ file_size_in_bytes: 1024
+ verification_timeout_in_s: 100
+ export_volume: /mnt/local
+
+
+benchmarks:
+ benchmark_galaxy_10x1K_isilon_backend:
+ type: GalaxyFileGenOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ target_folder: /mnt/isilon
+ galaxy_job:
+ <<: *default
+ path_to_files: /mnt/isilon/galaxy_in_progress/files
+
+ benchmark_galaxy_10x1K_netapp_backend:
+ type: GalaxyFileGenOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ target_folder: /mnt/netapp
+ galaxy_job:
+ <<: *default
+ path_to_files: /mnt/netapp/galaxy_in_progress/files
+
+ benchmark_galaxy_10x1K_s3_backend:
+ type: GalaxyFileGenOnS3Job
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ base_url: https://s3.bwsfs.uni-freiburg.de
+ bucket_name: frct-smoe-bench-ec61-01
+
+ # benchmark_galaxy_10x1K_irods_via_fuse_on_isilon_backend:
+ # type: GalaxyFileGenOnMountVolumeJob
+ # repetitions: 1
+ # destination:
+ # host: irods_client
+ # target_folder: /mnt/volume_under_test/irods-fuse
+ # galaxy_job:
+ # <<: *default
+ # path_to_files: /mnt/isilon/irods/home/rods/galaxy_in_progress/files
+
+ # pre_tasks:
+ # - playbook: setup_irods_server.yml
+ # host: irods_server
+ # extra_vars:
+ # irods_host_volume: /mnt/isilon
+ # - playbook: setup_irods_fuse_client.yml
+ # host: irods_client
+ # extra_vars:
+ # irods_host_volume: /mnt/volume_under_test
+ # irods_server_host: "132.230.223.139"
+ # irods_host_volume_owner_uid: "1000"
+ # post_tasks:
+ # - playbook: cleanup_irods_server.yml
+ # host: irods_server
+ # extra_vars:
+ # irods_host_volume: /mnt/isilon
+ # - playbook: cleanup_irods_fuse_client.yml
+ # host: irods_client
+ # extra_vars:
+ # irods_host_volume: /mnt/volume_under_test
+
+ benchmark_galaxy_10x1K_irods_via_davrods_on_isilon_backend:
+ type: GalaxyFileGenOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ target_folder: /mnt/volume_under_test/irods-webdav
+ galaxy_job:
+ <<: *default
+ path_to_files: /mnt/isilon/irods/home/rods/galaxy_in_progress/files
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+ irods_use_davrods: true
+ davrods_owner_uid: "1450"
+ - playbook: setup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ irods_server_host: "132.230.223.139"
+ davrods_owner_uid: "1450"
+
+ post_tasks:
+ - playbook: cleanup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+ irods_use_davrods: true
+
+ benchmark_galaxy_10x1K_irods_via_davrods_on_netapp_backend:
+ type: GalaxyFileGenOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ target_folder: /mnt/volume_under_test/irods-webdav
+ galaxy_job:
+ <<: *default
+ path_to_files: /mnt/netapp/irods/home/rods/galaxy_in_progress/files
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+ irods_use_davrods: true
+ davrods_owner_uid: "1450"
+ - playbook: setup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ irods_server_host: "132.230.223.139"
+ davrods_owner_uid: "1450"
+
+ post_tasks:
+ - playbook: cleanup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+ irods_use_davrods: true
+
+ benchmark_galaxy_10x1K_irods_via_davrods_on_s3_backend:
+ type: GalaxyFileGenOnIrodsOnS3Job
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ irods_server_host: "132.230.223.139"
+ base_url: https://s3.bwsfs.uni-freiburg.de
+ bucket_name: frct-smoe-bench-ec61-01
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_use_s3: true
+ irods_use_davrods: true
+ davrods_owner_uid: "1450"
+ - playbook: setup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ irods_server_host: "132.230.223.139"
+ davrods_owner_uid: "1450"
+
+ post_tasks:
+ - playbook: cleanup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_use_davrods: true
+
+ benchmark_galaxy_10x1K_irods_on_isilon_backend:
+ type: GalaxyFileGenOnIrodsOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ irods_server_host: "132.230.223.139"
+ path_to_files: /mnt/isilon/irods/home/rods
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+ post_tasks:
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+
+ benchmark_galaxy_10x1K_irods_on_netapp_backend:
+ type: GalaxyFileGenOnIrodsOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ irods_server_host: "132.230.223.139"
+ path_to_files: /mnt/netapp/irods/home/rods
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+ post_tasks:
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+
+ benchmark_galaxy_10x1K_irods_on_s3_backend:
+ type: GalaxyFileGenOnIrodsOnS3Job
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ irods_server_host: "132.230.223.139"
+ base_url: https://s3.bwsfs.uni-freiburg.de
+ bucket_name: frct-smoe-bench-ec61-01
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_use_s3: true
+ post_tasks:
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
diff --git a/examples/06_benchmark_galaxy_1x0.yml b/examples/06_benchmark_galaxy_1x0.yml
new file mode 100644
index 00000000..810f514a
--- /dev/null
+++ b/examples/06_benchmark_galaxy_1x0.yml
@@ -0,0 +1,242 @@
+config:
+ log_ansible_output: false
+ results_print: false
+ results_save_raw_results: false
+ results_save_to_file: true
+
+Default: &default
+ input:
+ num_files: 1
+ file_size_in_bytes: 0
+ verification_timeout_in_s: 100
+ export_volume: /mnt/local
+
+
+benchmarks:
+ benchmark_galaxy_1x0_isilon_backend:
+ type: GalaxyFileGenOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ target_folder: /mnt/isilon
+ galaxy_job:
+ <<: *default
+ path_to_files: /mnt/isilon/galaxy_in_progress/files
+
+ benchmark_galaxy_1x0_netapp_backend:
+ type: GalaxyFileGenOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ target_folder: /mnt/netapp
+ galaxy_job:
+ <<: *default
+ path_to_files: /mnt/netapp/galaxy_in_progress/files
+
+ benchmark_galaxy_1x0_s3_backend:
+ type: GalaxyFileGenOnS3Job
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ base_url: https://s3.bwsfs.uni-freiburg.de
+ bucket_name: frct-smoe-bench-ec61-01
+
+ # benchmark_galaxy_1x0_irods_via_fuse_on_isilon_backend:
+ # type: GalaxyFileGenOnMountVolumeJob
+ # repetitions: 1
+ # destination:
+ # host: irods_client
+ # target_folder: /mnt/volume_under_test/irods-fuse
+ # galaxy_job:
+ # <<: *default
+ # path_to_files: /mnt/isilon/irods/home/rods/galaxy_in_progress/files
+
+ # pre_tasks:
+ # - playbook: setup_irods_server.yml
+ # host: irods_server
+ # extra_vars:
+ # irods_host_volume: /mnt/isilon
+ # - playbook: setup_irods_fuse_client.yml
+ # host: irods_client
+ # extra_vars:
+ # irods_host_volume: /mnt/volume_under_test
+ # irods_server_host: "132.230.223.139"
+ # irods_host_volume_owner_uid: "1000"
+ # post_tasks:
+ # - playbook: cleanup_irods_server.yml
+ # host: irods_server
+ # extra_vars:
+ # irods_host_volume: /mnt/isilon
+ # - playbook: cleanup_irods_fuse_client.yml
+ # host: irods_client
+ # extra_vars:
+ # irods_host_volume: /mnt/volume_under_test
+
+ benchmark_galaxy_1x0_irods_via_davrods_on_isilon_backend:
+ type: GalaxyFileGenOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ target_folder: /mnt/volume_under_test/irods-webdav
+ galaxy_job:
+ <<: *default
+ path_to_files: /mnt/isilon/irods/home/rods/galaxy_in_progress/files
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+ irods_use_davrods: true
+ davrods_owner_uid: "1450"
+ - playbook: setup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ irods_server_host: "132.230.223.139"
+ davrods_owner_uid: "1450"
+
+ post_tasks:
+ - playbook: cleanup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+ irods_use_davrods: true
+
+ benchmark_galaxy_1x0_irods_via_davrods_on_netapp_backend:
+ type: GalaxyFileGenOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ target_folder: /mnt/volume_under_test/irods-webdav
+ galaxy_job:
+ <<: *default
+ path_to_files: /mnt/netapp/irods/home/rods/galaxy_in_progress/files
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+ irods_use_davrods: true
+ davrods_owner_uid: "1450"
+ - playbook: setup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ irods_server_host: "132.230.223.139"
+ davrods_owner_uid: "1450"
+
+ post_tasks:
+ - playbook: cleanup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+ irods_use_davrods: true
+
+ benchmark_galaxy_1x0_irods_via_davrods_on_s3_backend:
+ type: GalaxyFileGenOnIrodsOnS3Job
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ irods_server_host: "132.230.223.139"
+ base_url: https://s3.bwsfs.uni-freiburg.de
+ bucket_name: frct-smoe-bench-ec61-01
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_use_s3: true
+ irods_use_davrods: true
+ davrods_owner_uid: "1450"
+ - playbook: setup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ irods_server_host: "132.230.223.139"
+ davrods_owner_uid: "1450"
+
+ post_tasks:
+ - playbook: cleanup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_use_davrods: true
+
+ benchmark_galaxy_1x0_irods_on_isilon_backend:
+ type: GalaxyFileGenOnIrodsOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ irods_server_host: "132.230.223.139"
+ path_to_files: /mnt/isilon/irods/home/rods
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+ post_tasks:
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+
+ benchmark_galaxy_1x0_irods_on_netapp_backend:
+ type: GalaxyFileGenOnIrodsOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ irods_server_host: "132.230.223.139"
+ path_to_files: /mnt/netapp/irods/home/rods
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+ post_tasks:
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+
+ benchmark_galaxy_1x0_irods_on_s3_backend:
+ type: GalaxyFileGenOnIrodsOnS3Job
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ irods_server_host: "132.230.223.139"
+ base_url: https://s3.bwsfs.uni-freiburg.de
+ bucket_name: frct-smoe-bench-ec61-01
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_use_s3: true
+ post_tasks:
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
diff --git a/examples/06_benchmark_galaxy_1x1G.yml b/examples/06_benchmark_galaxy_1x1G.yml
new file mode 100644
index 00000000..f64e5a38
--- /dev/null
+++ b/examples/06_benchmark_galaxy_1x1G.yml
@@ -0,0 +1,242 @@
+config:
+ log_ansible_output: false
+ results_print: false
+ results_save_raw_results: false
+ results_save_to_file: true
+
+Default: &default
+ input:
+ num_files: 1
+ file_size_in_bytes: 1073741824 # 1 Gib
+ verification_timeout_in_s: 1000
+ export_volume: /mnt/local
+
+
+benchmarks:
+ benchmark_galaxy_1x1G_isilon_backend:
+ type: GalaxyFileGenOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ target_folder: /mnt/isilon
+ galaxy_job:
+ <<: *default
+ path_to_files: /mnt/isilon/galaxy_in_progress/files
+
+ benchmark_galaxy_1x1G_netapp_backend:
+ type: GalaxyFileGenOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ target_folder: /mnt/netapp
+ galaxy_job:
+ <<: *default
+ path_to_files: /mnt/netapp/galaxy_in_progress/files
+
+ # benchmark_galaxy_1x1G_s3_backend:
+ # type: GalaxyFileGenOnS3Job
+ # repetitions: 1
+ # destination:
+ # host: irods_client
+ # galaxy_job:
+ # <<: *default
+ # base_url: https://s3.bwsfs.uni-freiburg.de
+ # bucket_name: frct-smoe-bench-ec61-01
+
+ # benchmark_galaxy_1x1G_irods_via_fuse_on_isilon_backend:
+ # type: GalaxyFileGenOnMountVolumeJob
+ # repetitions: 1
+ # destination:
+ # host: irods_client
+ # target_folder: /mnt/volume_under_test/irods-fuse
+ # galaxy_job:
+ # <<: *default
+ # path_to_files: /mnt/isilon/irods/home/rods/galaxy_in_progress/files
+
+ # pre_tasks:
+ # - playbook: setup_irods_server.yml
+ # host: irods_server
+ # extra_vars:
+ # irods_host_volume: /mnt/isilon
+ # - playbook: setup_irods_fuse_client.yml
+ # host: irods_client
+ # extra_vars:
+ # irods_host_volume: /mnt/volume_under_test
+ # irods_server_host: "132.230.223.139"
+ # irods_host_volume_owner_uid: "1000"
+ # post_tasks:
+ # - playbook: cleanup_irods_server.yml
+ # host: irods_server
+ # extra_vars:
+ # irods_host_volume: /mnt/isilon
+ # - playbook: cleanup_irods_fuse_client.yml
+ # host: irods_client
+ # extra_vars:
+ # irods_host_volume: /mnt/volume_under_test
+
+ benchmark_galaxy_1x1G_irods_via_davrods_on_isilon_backend:
+ type: GalaxyFileGenOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ target_folder: /mnt/volume_under_test/irods-webdav
+ galaxy_job:
+ <<: *default
+ path_to_files: /mnt/isilon/irods/home/rods/galaxy_in_progress/files
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+ irods_use_davrods: true
+ davrods_owner_uid: "1450"
+ - playbook: setup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ irods_server_host: "132.230.223.139"
+ davrods_owner_uid: "1450"
+
+ post_tasks:
+ - playbook: cleanup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+ irods_use_davrods: true
+
+ benchmark_galaxy_1x1G_irods_via_davrods_on_netapp_backend:
+ type: GalaxyFileGenOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ target_folder: /mnt/volume_under_test/irods-webdav
+ galaxy_job:
+ <<: *default
+ path_to_files: /mnt/netapp/irods/home/rods/galaxy_in_progress/files
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+ irods_use_davrods: true
+ davrods_owner_uid: "1450"
+ - playbook: setup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ irods_server_host: "132.230.223.139"
+ davrods_owner_uid: "1450"
+
+ post_tasks:
+ - playbook: cleanup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+ irods_use_davrods: true
+
+ benchmark_galaxy_1x1G_irods_via_davrods_on_s3_backend:
+ type: GalaxyFileGenOnIrodsOnS3Job
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ irods_server_host: "132.230.223.139"
+ base_url: https://s3.bwsfs.uni-freiburg.de
+ bucket_name: frct-smoe-bench-ec61-01
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_use_s3: true
+ irods_use_davrods: true
+ davrods_owner_uid: "1450"
+ - playbook: setup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ irods_server_host: "132.230.223.139"
+ davrods_owner_uid: "1450"
+
+ post_tasks:
+ - playbook: cleanup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_use_davrods: true
+
+ benchmark_galaxy_1x1G_irods_on_isilon_backend:
+ type: GalaxyFileGenOnIrodsOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ irods_server_host: "132.230.223.139"
+ path_to_files: /mnt/isilon/irods/home/rods
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+ post_tasks:
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+
+ benchmark_galaxy_1x1G_irods_on_netapp_backend:
+ type: GalaxyFileGenOnIrodsOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ irods_server_host: "132.230.223.139"
+ path_to_files: /mnt/netapp/irods/home/rods
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+ post_tasks:
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+
+ benchmark_galaxy_1x1G_irods_on_s3_backend:
+ type: GalaxyFileGenOnIrodsOnS3Job
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ irods_server_host: "132.230.223.139"
+ base_url: https://s3.bwsfs.uni-freiburg.de
+ bucket_name: frct-smoe-bench-ec61-01
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_use_s3: true
+ post_tasks:
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
diff --git a/examples/06_benchmark_galaxy_1x1K.yml b/examples/06_benchmark_galaxy_1x1K.yml
new file mode 100644
index 00000000..cdc5384e
--- /dev/null
+++ b/examples/06_benchmark_galaxy_1x1K.yml
@@ -0,0 +1,242 @@
+config:
+ log_ansible_output: false
+ results_print: false
+ results_save_raw_results: false
+ results_save_to_file: true
+
+Default: &default
+ input:
+ num_files: 1
+ file_size_in_bytes: 1024
+ verification_timeout_in_s: 100
+ export_volume: /mnt/local
+
+
+benchmarks:
+ benchmark_galaxy_1x1K_isilon_backend:
+ type: GalaxyFileGenOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ target_folder: /mnt/isilon
+ galaxy_job:
+ <<: *default
+ path_to_files: /mnt/isilon/galaxy_in_progress/files
+
+ benchmark_galaxy_1x1K_netapp_backend:
+ type: GalaxyFileGenOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ target_folder: /mnt/netapp
+ galaxy_job:
+ <<: *default
+ path_to_files: /mnt/netapp/galaxy_in_progress/files
+
+ benchmark_galaxy_1x1K_s3_backend:
+ type: GalaxyFileGenOnS3Job
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ base_url: https://s3.bwsfs.uni-freiburg.de
+ bucket_name: frct-smoe-bench-ec61-01
+
+ # benchmark_galaxy_1x1K_irods_via_fuse_on_isilon_backend:
+ # type: GalaxyFileGenOnMountVolumeJob
+ # repetitions: 1
+ # destination:
+ # host: irods_client
+ # target_folder: /mnt/volume_under_test/irods-fuse
+ # galaxy_job:
+ # <<: *default
+ # path_to_files: /mnt/isilon/irods/home/rods/galaxy_in_progress/files
+
+ # pre_tasks:
+ # - playbook: setup_irods_server.yml
+ # host: irods_server
+ # extra_vars:
+ # irods_host_volume: /mnt/isilon
+ # - playbook: setup_irods_fuse_client.yml
+ # host: irods_client
+ # extra_vars:
+ # irods_host_volume: /mnt/volume_under_test
+ # irods_server_host: "132.230.223.139"
+ # irods_host_volume_owner_uid: "1000"
+ # post_tasks:
+ # - playbook: cleanup_irods_server.yml
+ # host: irods_server
+ # extra_vars:
+ # irods_host_volume: /mnt/isilon
+ # - playbook: cleanup_irods_fuse_client.yml
+ # host: irods_client
+ # extra_vars:
+ # irods_host_volume: /mnt/volume_under_test
+
+ benchmark_galaxy_1x1K_irods_via_davrods_on_isilon_backend:
+ type: GalaxyFileGenOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ target_folder: /mnt/volume_under_test/irods-webdav
+ galaxy_job:
+ <<: *default
+ path_to_files: /mnt/isilon/irods/home/rods/galaxy_in_progress/files
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+ irods_use_davrods: true
+ davrods_owner_uid: "1450"
+ - playbook: setup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ irods_server_host: "132.230.223.139"
+ davrods_owner_uid: "1450"
+
+ post_tasks:
+ - playbook: cleanup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+ irods_use_davrods: true
+
+ benchmark_galaxy_1x1K_irods_via_davrods_on_netapp_backend:
+ type: GalaxyFileGenOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ target_folder: /mnt/volume_under_test/irods-webdav
+ galaxy_job:
+ <<: *default
+ path_to_files: /mnt/netapp/irods/home/rods/galaxy_in_progress/files
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+ irods_use_davrods: true
+ davrods_owner_uid: "1450"
+ - playbook: setup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ irods_server_host: "132.230.223.139"
+ davrods_owner_uid: "1450"
+
+ post_tasks:
+ - playbook: cleanup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+ irods_use_davrods: true
+
+ benchmark_galaxy_1x1K_irods_via_davrods_on_s3_backend:
+ type: GalaxyFileGenOnIrodsOnS3Job
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ irods_server_host: "132.230.223.139"
+ base_url: https://s3.bwsfs.uni-freiburg.de
+ bucket_name: frct-smoe-bench-ec61-01
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_use_s3: true
+ irods_use_davrods: true
+ davrods_owner_uid: "1450"
+ - playbook: setup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ irods_server_host: "132.230.223.139"
+ davrods_owner_uid: "1450"
+
+ post_tasks:
+ - playbook: cleanup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_use_davrods: true
+
+ benchmark_galaxy_1x1K_irods_on_isilon_backend:
+ type: GalaxyFileGenOnIrodsOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ irods_server_host: "132.230.223.139"
+ path_to_files: /mnt/isilon/irods/home/rods
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+ post_tasks:
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+
+ benchmark_galaxy_1x1K_irods_on_netapp_backend:
+ type: GalaxyFileGenOnIrodsOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ irods_server_host: "132.230.223.139"
+ path_to_files: /mnt/netapp/irods/home/rods
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+ post_tasks:
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+
+ benchmark_galaxy_1x1K_irods_on_s3_backend:
+ type: GalaxyFileGenOnIrodsOnS3Job
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ irods_server_host: "132.230.223.139"
+ base_url: https://s3.bwsfs.uni-freiburg.de
+ bucket_name: frct-smoe-bench-ec61-01
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_use_s3: true
+ post_tasks:
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
diff --git a/examples/06_benchmark_galaxy_1x1M.yml b/examples/06_benchmark_galaxy_1x1M.yml
new file mode 100644
index 00000000..ddc73d77
--- /dev/null
+++ b/examples/06_benchmark_galaxy_1x1M.yml
@@ -0,0 +1,242 @@
+config:
+ log_ansible_output: false
+ results_print: false
+ results_save_raw_results: false
+ results_save_to_file: true
+
+Default: &default
+ input:
+ num_files: 1
+ file_size_in_bytes: 1048576
+ verification_timeout_in_s: 100
+ export_volume: /mnt/local
+
+
+benchmarks:
+ benchmark_galaxy_1x1M_isilon_backend:
+ type: GalaxyFileGenOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ target_folder: /mnt/isilon
+ galaxy_job:
+ <<: *default
+ path_to_files: /mnt/isilon/galaxy_in_progress/files
+
+ benchmark_galaxy_1x1M_netapp_backend:
+ type: GalaxyFileGenOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ target_folder: /mnt/netapp
+ galaxy_job:
+ <<: *default
+ path_to_files: /mnt/netapp/galaxy_in_progress/files
+
+ benchmark_galaxy_1x1M_s3_backend:
+ type: GalaxyFileGenOnS3Job
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ base_url: https://s3.bwsfs.uni-freiburg.de
+ bucket_name: frct-smoe-bench-ec61-01
+
+ # benchmark_galaxy_1x1M_irods_via_fuse_on_isilon_backend:
+ # type: GalaxyFileGenOnMountVolumeJob
+ # repetitions: 1
+ # destination:
+ # host: irods_client
+ # target_folder: /mnt/volume_under_test/irods-fuse
+ # galaxy_job:
+ # <<: *default
+ # path_to_files: /mnt/isilon/irods/home/rods/galaxy_in_progress/files
+
+ # pre_tasks:
+ # - playbook: setup_irods_server.yml
+ # host: irods_server
+ # extra_vars:
+ # irods_host_volume: /mnt/isilon
+ # - playbook: setup_irods_fuse_client.yml
+ # host: irods_client
+ # extra_vars:
+ # irods_host_volume: /mnt/volume_under_test
+ # irods_server_host: "132.230.223.139"
+ # irods_host_volume_owner_uid: "1000"
+ # post_tasks:
+ # - playbook: cleanup_irods_server.yml
+ # host: irods_server
+ # extra_vars:
+ # irods_host_volume: /mnt/isilon
+ # - playbook: cleanup_irods_fuse_client.yml
+ # host: irods_client
+ # extra_vars:
+ # irods_host_volume: /mnt/volume_under_test
+
+ benchmark_galaxy_1x1M_irods_via_davrods_on_isilon_backend:
+ type: GalaxyFileGenOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ target_folder: /mnt/volume_under_test/irods-webdav
+ galaxy_job:
+ <<: *default
+ path_to_files: /mnt/isilon/irods/home/rods/galaxy_in_progress/files
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+ irods_use_davrods: true
+ davrods_owner_uid: "1450"
+ - playbook: setup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ irods_server_host: "132.230.223.139"
+ davrods_owner_uid: "1450"
+
+ post_tasks:
+ - playbook: cleanup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+ irods_use_davrods: true
+
+ benchmark_galaxy_1x1M_irods_via_davrods_on_netapp_backend:
+ type: GalaxyFileGenOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ target_folder: /mnt/volume_under_test/irods-webdav
+ galaxy_job:
+ <<: *default
+ path_to_files: /mnt/netapp/irods/home/rods/galaxy_in_progress/files
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+ irods_use_davrods: true
+ davrods_owner_uid: "1450"
+ - playbook: setup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ irods_server_host: "132.230.223.139"
+ davrods_owner_uid: "1450"
+
+ post_tasks:
+ - playbook: cleanup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+ irods_use_davrods: true
+
+ benchmark_galaxy_1x1M_irods_via_davrods_on_s3_backend:
+ type: GalaxyFileGenOnIrodsOnS3Job
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ irods_server_host: "132.230.223.139"
+ base_url: https://s3.bwsfs.uni-freiburg.de
+ bucket_name: frct-smoe-bench-ec61-01
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_use_s3: true
+ irods_use_davrods: true
+ davrods_owner_uid: "1450"
+ - playbook: setup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ irods_server_host: "132.230.223.139"
+ davrods_owner_uid: "1450"
+
+ post_tasks:
+ - playbook: cleanup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_use_davrods: true
+
+ benchmark_galaxy_1x1M_irods_on_isilon_backend:
+ type: GalaxyFileGenOnIrodsOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ irods_server_host: "132.230.223.139"
+ path_to_files: /mnt/isilon/irods/home/rods
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+ post_tasks:
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+
+ benchmark_galaxy_1x1M_irods_on_netapp_backend:
+ type: GalaxyFileGenOnIrodsOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ irods_server_host: "132.230.223.139"
+ path_to_files: /mnt/netapp/irods/home/rods
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+ post_tasks:
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+
+ benchmark_galaxy_1x1M_irods_on_s3_backend:
+ type: GalaxyFileGenOnIrodsOnS3Job
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ irods_server_host: "132.230.223.139"
+ base_url: https://s3.bwsfs.uni-freiburg.de
+ bucket_name: frct-smoe-bench-ec61-01
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_use_s3: true
+ post_tasks:
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
diff --git a/examples/06_benchmark_galaxy_2000x1K.yml b/examples/06_benchmark_galaxy_2000x1K.yml
new file mode 100644
index 00000000..787566e2
--- /dev/null
+++ b/examples/06_benchmark_galaxy_2000x1K.yml
@@ -0,0 +1,242 @@
+config:
+ log_ansible_output: false
+ results_print: false
+ results_save_raw_results: false
+ results_save_to_file: true
+
+Default: &default
+ input:
+ num_files: 2000
+ file_size_in_bytes: 1024
+ verification_timeout_in_s: 3600
+ export_volume: /mnt/local
+
+
+benchmarks:
+ benchmark_galaxy_2000x1K_isilon_backend:
+ type: GalaxyFileGenOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ target_folder: /mnt/isilon
+ galaxy_job:
+ <<: *default
+ path_to_files: /mnt/isilon/galaxy_in_progress/files
+
+ benchmark_galaxy_2000x1K_netapp_backend:
+ type: GalaxyFileGenOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ target_folder: /mnt/netapp
+ galaxy_job:
+ <<: *default
+ path_to_files: /mnt/netapp/galaxy_in_progress/files
+
+ benchmark_galaxy_2000x1K_s3_backend:
+ type: GalaxyFileGenOnS3Job
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ base_url: https://s3.bwsfs.uni-freiburg.de
+ bucket_name: frct-smoe-bench-ec61-01
+
+ # benchmark_galaxy_2000x1K_irods_via_fuse_on_isilon_backend:
+ # type: GalaxyFileGenOnMountVolumeJob
+ # repetitions: 1
+ # destination:
+ # host: irods_client
+ # target_folder: /mnt/volume_under_test/irods-fuse
+ # galaxy_job:
+ # <<: *default
+ # path_to_files: /mnt/isilon/irods/home/rods/galaxy_in_progress/files
+
+ # pre_tasks:
+ # - playbook: setup_irods_server.yml
+ # host: irods_server
+ # extra_vars:
+ # irods_host_volume: /mnt/isilon
+ # - playbook: setup_irods_fuse_client.yml
+ # host: irods_client
+ # extra_vars:
+ # irods_host_volume: /mnt/volume_under_test
+ # irods_server_host: "132.230.223.139"
+ # irods_host_volume_owner_uid: "1000"
+ # post_tasks:
+ # - playbook: cleanup_irods_server.yml
+ # host: irods_server
+ # extra_vars:
+ # irods_host_volume: /mnt/isilon
+ # - playbook: cleanup_irods_fuse_client.yml
+ # host: irods_client
+ # extra_vars:
+ # irods_host_volume: /mnt/volume_under_test
+
+ benchmark_galaxy_2000x1K_irods_via_davrods_on_isilon_backend:
+ type: GalaxyFileGenOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ target_folder: /mnt/volume_under_test/irods-webdav
+ galaxy_job:
+ <<: *default
+ path_to_files: /mnt/isilon/irods/home/rods/galaxy_in_progress/files
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+ irods_use_davrods: true
+ davrods_owner_uid: "1450"
+ - playbook: setup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ irods_server_host: "132.230.223.139"
+ davrods_owner_uid: "1450"
+
+ post_tasks:
+ - playbook: cleanup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+ irods_use_davrods: true
+
+ benchmark_galaxy_2000x1K_irods_via_davrods_on_netapp_backend:
+ type: GalaxyFileGenOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ target_folder: /mnt/volume_under_test/irods-webdav
+ galaxy_job:
+ <<: *default
+ path_to_files: /mnt/netapp/irods/home/rods/galaxy_in_progress/files
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+ irods_use_davrods: true
+ davrods_owner_uid: "1450"
+ - playbook: setup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ irods_server_host: "132.230.223.139"
+ davrods_owner_uid: "1450"
+
+ post_tasks:
+ - playbook: cleanup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+ irods_use_davrods: true
+
+ benchmark_galaxy_2000x1K_irods_via_davrods_on_s3_backend:
+ type: GalaxyFileGenOnIrodsOnS3Job
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ irods_server_host: "132.230.223.139"
+ base_url: https://s3.bwsfs.uni-freiburg.de
+ bucket_name: frct-smoe-bench-ec61-01
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_use_s3: true
+ irods_use_davrods: true
+ davrods_owner_uid: "1450"
+ - playbook: setup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ irods_server_host: "132.230.223.139"
+ davrods_owner_uid: "1450"
+
+ post_tasks:
+ - playbook: cleanup_irods_webdav_client.yml
+ host: irods_client
+ extra_vars:
+ irods_host_volume: /mnt/volume_under_test
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_use_davrods: true
+
+ benchmark_galaxy_2000x1K_irods_on_isilon_backend:
+ type: GalaxyFileGenOnIrodsOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ irods_server_host: "132.230.223.139"
+ path_to_files: /mnt/isilon/irods/home/rods
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+ post_tasks:
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/isilon
+
+ benchmark_galaxy_2000x1K_irods_on_netapp_backend:
+ type: GalaxyFileGenOnIrodsOnMountVolumeJob
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ irods_server_host: "132.230.223.139"
+ path_to_files: /mnt/netapp/irods/home/rods
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+ post_tasks:
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_host_volume: /mnt/netapp
+
+ benchmark_galaxy_2000x1K_irods_on_s3_backend:
+ type: GalaxyFileGenOnIrodsOnS3Job
+ repetitions: 1
+ destination:
+ host: irods_client
+ galaxy_job:
+ <<: *default
+ irods_server_host: "132.230.223.139"
+ base_url: https://s3.bwsfs.uni-freiburg.de
+ bucket_name: frct-smoe-bench-ec61-01
+
+ pre_tasks:
+ - playbook: setup_irods_server.yml
+ host: irods_server
+ extra_vars:
+ irods_use_s3: true
+ post_tasks:
+ - playbook: cleanup_irods_server.yml
+ host: irods_server
diff --git a/examples/20_netapp_evaluation.yml b/examples/20_netapp_evaluation.yml
new file mode 100644
index 00000000..a1db6ad8
--- /dev/null
+++ b/examples/20_netapp_evaluation.yml
@@ -0,0 +1,28 @@
+config:
+ log_ansible_output: false
+ results_print: false
+ results_save_to_file: true
+
+Default: &default
+ type: DdNetappRead
+ repetitions: 5
+ destination:
+ host: irods_server
+ target_folder: /mnt/netapp/read_benchmark
+ dd:
+ blocksize: 1024k
+
+ dim_key: blockcount
+ dim_values:
+ - 1024
+ - 2048
+ - 4096
+ - 8192
+ - 16384
+
+benchmarks:
+ netapp_cold_read:
+ <<: *default
+
+ netapp_hot_read:
+ <<: *default
diff --git a/examples/20_prepare_netapp_evaluation.yml b/examples/20_prepare_netapp_evaluation.yml
new file mode 100644
index 00000000..f8f5e6f2
--- /dev/null
+++ b/examples/20_prepare_netapp_evaluation.yml
@@ -0,0 +1,22 @@
+config:
+ log_ansible_output: false
+ results_print: false
+ results_save_to_file: false
+
+benchmarks:
+ setup_directory_struture:
+ type: DdPrepareNetappRead
+ repetitions: 5
+ destination:
+ host: irods_server
+ target_folder: /mnt/netapp/read_benchmark
+ dd:
+ blocksize: 1024k
+
+ dim_key: blockcount
+ dim_values:
+ - 1024
+ - 2048
+ - 4096
+ - 8192
+ - 16384
diff --git a/examples/99_evaluate_benchmark_tools.yml b/examples/99_evaluate_benchmark_tools.yml
new file mode 100644
index 00000000..7b1385fd
--- /dev/null
+++ b/examples/99_evaluate_benchmark_tools.yml
@@ -0,0 +1,93 @@
+config:
+ log_ansible_output: false
+ results_print: false
+ results_save_to_file: true
+
+
+DefaultValues: &fio_defaults
+ type: FioFixedParams
+ repetitions: 5
+ destinations:
+ - host: irods_server
+ target_folder: /mnt/netapp
+
+
+benchmarks:
+ # fio_throughput_read:
+ # <<: *fio_defaults
+ # fio:
+ # mode: read
+ # blocksize: "1024k"
+ # numjobs: 4
+ # iodepth: 32
+
+ # fio_throughput_write:
+ # <<: *fio_defaults
+ # fio:
+ # mode: write
+ # blocksize: "1024k"
+ # numjobs: 4
+ # iodepth: 32
+
+ # fio_throughput_rw:
+ # <<: *fio_defaults
+ # fio:
+ # mode: rw
+ # blocksize: "1024k"
+ # numjobs: 4
+ # iodepth: 32
+
+ # fio_iops_read:
+ # <<: *fio_defaults
+ # fio:
+ # mode: randread
+ # blocksize: "4k"
+ # numjobs: 4
+ # iodepth: 32
+
+ # fio_iops_write:
+ # <<: *fio_defaults
+ # fio:
+ # mode: randwrite
+ # blocksize: "4k"
+ # numjobs: 4
+ # iodepth: 32
+
+ # fio_iops_rw:
+ # <<: *fio_defaults
+ # fio:
+ # mode: randrw
+ # blocksize: "4k"
+ # numjobs: 4
+ # iodepth: 32
+
+ # fio_latency_read:
+ # <<: *fio_defaults
+ # fio:
+ # mode: randread
+ # blocksize: "4k"
+ # numjobs: 1
+ # iodepth: 1
+
+ # fio_latency_write:
+ # <<: *fio_defaults
+ # fio:
+ # mode: randwrite
+ # blocksize: "4k"
+ # numjobs: 1
+ # iodepth: 1
+
+ # fio_latency_rw:
+ # <<: *fio_defaults
+ # fio:
+ # mode: randrw
+ # blocksize: "4k"
+ # numjobs: 1
+ # iodepth: 1
+
+ dd_throughput_read:
+ type: DdBenchmark
+ repetitions: 5
+ destinations:
+ - host: irods_server
+ target_folder: /mnt/netapp
diff --git a/examples/99_evaluate_ram_cache_effect_on_iops.yml b/examples/99_evaluate_ram_cache_effect_on_iops.yml
new file mode 100644
index 00000000..c13690ee
--- /dev/null
+++ b/examples/99_evaluate_ram_cache_effect_on_iops.yml
@@ -0,0 +1,30 @@
+config:
+ log_ansible_output: true
+ results_print: false
+
+
+benchmarks:
+ fio_iops_over_time:
+ type: FioOneDimParams
+ repetitions: 1
+ destinations:
+ - host: irods_server
+ target_folder: /mnt/netapp
+
+ fio:
+ mode: randread
+ blocksize: "4k"
+ numjobs: 4
+ iodepth: 32
+ filesize: "1G"
+ refill_buffers: false
+
+ dim_key: runtime_in_s
+ dim_values:
+ - 10
+ - 20
+ - 40
+ - 80
+ - 160
+ - 320
+ - 640
diff --git a/galaxy_benchmarker/__main__.py b/galaxy_benchmarker/__main__.py
index 6db237e1..20782958 100644
--- a/galaxy_benchmarker/__main__.py
+++ b/galaxy_benchmarker/__main__.py
@@ -1,68 +1,101 @@
-import yaml
import argparse
-import sys
-import bioblend
-from time import sleep
-from benchmarker import Benchmarker
import logging
-import time
-import requests
import os
-from requests.adapters import HTTPAdapter
+from datetime import datetime
+from pathlib import Path
-logging.basicConfig()
-log = logging.getLogger("GalaxyBenchmarker")
-log.setLevel(logging.INFO)
-log_handler = logging.StreamHandler(sys.stdout)
-log_handler.setLevel(logging.DEBUG)
+from serde.yaml import from_yaml
-# Log to file
-log_filename = r'logs/{filename}.log'.format(filename=time.time())
-os.makedirs(os.path.dirname(log_filename), exist_ok=True)
-fh = logging.FileHandler(log_filename, mode='w')
-log.addHandler(fh)
+from galaxy_benchmarker.benchmarker import Benchmarker, BenchmarkerConfig, GlobalConfig
+from galaxy_benchmarker.utils import ansible
-s = requests.Session()
-s.mount('http://', HTTPAdapter(max_retries=20))
-
-def main():
+def main() -> None:
parser = argparse.ArgumentParser()
- parser.add_argument("--config", type=str, default="benchmark_config.yml", help="Path to config file")
+ parser.add_argument(
+ "--cfgs", nargs="+", default=[], help="Path(s) to config file(s)"
+ )
+ parser.add_argument("--only-pre-tasks", dest="only_pre_tasks", action="store_true")
+ parser.add_argument("--only-benchmark", dest="only_benchmark", action="store_true")
+ parser.add_argument(
+ "--only-post-tasks", dest="only_post_tasks", action="store_true"
+ )
+ parser.add_argument("--verbose", dest="verbose", action="store_true")
+ parser.set_defaults(
+ verbose=False, only_pre_tasks=False, only_benchmark=False, only_post_tasks=False
+ )
+ parser.add_argument(
+ "--benchmarks", nargs="+", default=[], help="List of benchmark(s)"
+ )
+
args = parser.parse_args()
- log.debug("Loading Configuration from file {filename}".format(filename=args.config))
- with open(args.config, "r") as stream:
- try:
- config = yaml.safe_load(stream)
+ log = configure_logger(args.verbose)
+
+ for config_name in args.cfgs:
+ cfg_path = Path(config_name)
+ if not cfg_path.is_file():
+ raise ValueError(f"Path to config '{config_name}' is not a file")
+
+ log.info(
+ "Loading Configuration from file {filename}".format(filename=config_name)
+ )
+
+ cfg = from_yaml(GlobalConfig, cfg_path.read_text())
+
+ ansible.AnsibleTask.register(cfg.tasks or {})
+
+ log.info("Initializing Benchmarker.")
+ benchmarker = Benchmarker(cfg.config or BenchmarkerConfig(), cfg.benchmarks)
- log.info("Initializing Benchmarker.")
- benchmarker = Benchmarker(config)
+ log.info("Start benchmarker.")
+ if args.only_pre_tasks:
+ flags = (True, False, False)
+ elif args.only_benchmark:
+ flags = (False, True, False)
+ elif args.only_post_tasks:
+ flags = (False, False, True)
+ else:
+ flags = (True, True, True)
- benchmarker.run_pre_tasks()
+ pre, bench, post = flags
+ benchmarker.run(
+ run_pretasks=pre,
+ run_benchmarks=bench,
+ run_posttasks=post,
+ filter_benchmarks=args.benchmarks,
+ )
- log.info("Starting to run benchmarks.")
- try:
- benchmarker.run()
- except bioblend.ConnectionError:
- log.error("There was a problem with the connection. Benchmark canceled.")
- results_filename = "results/results_{time}".format(time=time.time())
- log.info("Saving results to file: '{filename}.json'.".format(filename=results_filename))
- os.makedirs(os.path.dirname(results_filename), exist_ok=True)
- benchmarker.save_results(results_filename)
+def configure_logger(verbose: bool) -> logging.Logger:
+ # Formatter
+ fmt_with_time = logging.Formatter(
+ "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
+ )
+ fmt_no_time = logging.Formatter("%(name)s - %(levelname)s - %(message)s")
- if benchmarker.inflx_db is not None:
- log.info("Sending results to influxDB.")
- benchmarker.send_results_to_influxdb()
+ # Create logger
+ log = logging.getLogger("galaxy_benchmarker")
+ if verbose:
+ log.setLevel(logging.DEBUG)
+ else:
+ log.setLevel(logging.INFO)
- benchmarker.run_post_tasks()
+ # Create console handler
+ stream_handler = logging.StreamHandler()
+ stream_handler.setLevel(logging.DEBUG)
+ stream_handler.setFormatter(fmt_no_time)
+ log.addHandler(stream_handler)
- except yaml.YAMLError as exc:
- print(exc)
- except IOError as err:
- print(err)
+ # Create file handler
+ log_filename = f"logs/{datetime.now().replace(microsecond=0).isoformat()}.log"
+ os.makedirs(os.path.dirname(log_filename), exist_ok=True)
+ file_handler = logging.FileHandler(log_filename, mode="w")
+ file_handler.setLevel(logging.DEBUG)
+ file_handler.setFormatter(fmt_with_time)
+ log.addHandler(file_handler)
+ return log
-if __name__ == '__main__':
+if __name__ == "__main__":
main()
diff --git a/galaxy_benchmarker/ansible_bridge.py b/galaxy_benchmarker/ansible_bridge.py
deleted file mode 100644
index 747ba67f..00000000
--- a/galaxy_benchmarker/ansible_bridge.py
+++ /dev/null
@@ -1,17 +0,0 @@
-import os
-import subprocess
-from typing import Dict
-
-
-def run_playbook(playbook_path, host, user, private_key, values: Dict = None):
- """
- Run ansible-playbook with the given parameters. Additional variables can be given in values as a dict.
- """
- commands = ["ansible-playbook", playbook_path, "-i", host+",", "-u", user, "--private-key", private_key]
- if values is not None:
- for key, value in values.items():
- commands.append("-e")
- commands.append(key + "=" + value)
-
- with open(os.devnull, 'w') as devnull:
- subprocess.check_call(commands)
diff --git a/galaxy_benchmarker/benchmark.py b/galaxy_benchmarker/benchmark.py
deleted file mode 100644
index 56ced3c9..00000000
--- a/galaxy_benchmarker/benchmark.py
+++ /dev/null
@@ -1,528 +0,0 @@
-"""
-Definition of different benchmark-types.
-"""
-import logging
-import time
-import threading
-import random
-from datetime import datetime
-from destination import BaseDestination, GalaxyDestination, PulsarMQDestination, GalaxyCondorDestination, CondorDestination
-from workflow import BaseWorkflow, GalaxyWorkflow, CondorWorkflow
-from task import BaseTask, AnsiblePlaybookTask, BenchmarkerTask
-from typing import List, Dict, Union
-from task import configure_task
-from influxdb_bridge import InfluxDB
-from bioblend import ConnectionError
-
-
-log = logging.getLogger("GalaxyBenchmarker")
-
-
-class BaseBenchmark:
- """
- The Base-Class of Benchmark. All Benchmarks should inherit from it.
- """
- allowed_dest_types = []
- allowed_workflow_types = []
- benchmarker = None
- galaxy = None
- benchmark_results = dict()
- pre_tasks: List[BaseTask] = None
- post_tasks: List[BaseTask] = None
-
- def __init__(self, name, benchmarker, destinations: List[BaseDestination],
- workflows: List[BaseWorkflow], runs_per_workflow=1):
- self.name = name
- self.benchmarker = benchmarker
- self.uuid = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f") + "_" + name
- self.destinations = destinations
- self.workflows = workflows
- self.runs_per_workflow = runs_per_workflow
-
- def run_pre_task(self):
- """
- Runs a Task before starting the actual Benchmark.
- """
- if self.pre_tasks is None:
- return
- for task in self.pre_tasks:
- log.info("Running task {task}".format(task=task))
- task.run()
-
- def run_post_task(self):
- """
- Runs a Task after Benchmark finished (useful for cleaning up etc.).
- """
- if self.post_tasks is None:
- return
- for task in self.post_tasks:
- log.info("Running task {task}".format(task=task))
- task.run()
-
- def run(self, benchmarker):
- raise NotImplementedError
-
- def save_results_to_influxdb(self, inflxdb: InfluxDB):
- """
- Sends all the metrics of the benchmark_results to influxDB.
- """
- for run_type, per_dest_results in self.benchmark_results.items():
- for dest_name, workflows in per_dest_results.items():
- for workflow_name, runs in workflows.items():
- for run in runs:
- if run is None:
- continue
-
- if "workflow_metrics" in run:
- # Save metrics per workflow-run
- tags = {
- "benchmark_name": self.name,
- "benchmark_uuid": self.uuid,
- "benchmark_type": type(self),
- "destination_name": dest_name,
- "workflow_name": workflow_name,
- "history_name": run["history_name"] if "history_name" in run else None,
- "run_type": run_type,
- }
-
- inflxdb.save_workflow_metrics(tags, run["workflow_metrics"])
-
- # Save job-metrics if workflow succeeded
- if runs is None or run["status"] == "error" or "jobs" not in run or run["jobs"] is None:
- continue
-
- for job in run["jobs"].values():
- tags = {
- "benchmark_name": self.name,
- "benchmark_uuid": self.uuid,
- "benchmark_type": type(self),
- "destination_name": dest_name,
- "workflow_name": workflow_name,
- "history_name": run["history_name"] if "history_name" in run else None,
- "run_type": run_type,
- }
- inflxdb.save_job_metrics(tags, job)
-
- def __str__(self):
- return self.name
-
- __repr__ = __str__
-
-
-class ColdWarmBenchmark(BaseBenchmark):
- allowed_dest_types = [GalaxyDestination, PulsarMQDestination]
- allowed_workflow_types = [GalaxyWorkflow]
- cold_pre_task: AnsiblePlaybookTask = None
- warm_pre_task: AnsiblePlaybookTask = None
-
- def __init__(self, name, benchmarker, destinations: List[Union[PulsarMQDestination, GalaxyDestination]],
- workflows: List[GalaxyWorkflow], galaxy, runs_per_workflow=1):
- super().__init__(name, benchmarker, destinations, workflows, runs_per_workflow)
- self.destinations = destinations
- self.workflows = workflows
- self.galaxy = galaxy
-
- def run(self, benchmarker):
- """
- Runs the Workflow on each Destinations. First runs all Workflows "cold" (cleaning Pulsar up before each run),
- after that the "warm"-runs begin.
- """
-
- for run_type in ["cold", "warm"]:
- try:
- self.benchmark_results[run_type] = run_galaxy_benchmark(self, benchmarker.glx, self.destinations,
- self.workflows,
- self.runs_per_workflow, run_type)
- except KeyboardInterrupt as e:
- self.benchmark_results[run_type] = e.args[0]
- break
-
-
-class DestinationComparisonBenchmark(BaseBenchmark):
- allowed_dest_types = [GalaxyDestination, PulsarMQDestination, GalaxyCondorDestination]
- allowed_workflow_types = [GalaxyWorkflow]
-
- def __init__(self, name, benchmarker, destinations: List[Union[PulsarMQDestination, GalaxyDestination]],
- workflows: List[GalaxyWorkflow], galaxy, runs_per_workflow=1, warmup=True):
- super().__init__(name, benchmarker, destinations, workflows, runs_per_workflow)
- self.destinations = destinations
- self.workflows = workflows
- self.galaxy = galaxy
- self.warmup = warmup
-
- def run(self, benchmarker):
- """
- Runs the Workflows on each Destination. Uses "warm"-run, so for each Destination and Workflow, each Workflow
- runs for the first time without consideration (so Pulsar has opportunity to install all tools) to not spoil
- any metrics.
- """
- try:
- self.benchmark_results["warm"] = run_galaxy_benchmark(self, benchmarker.glx, self.destinations,
- self.workflows,
- self.runs_per_workflow, "warm", self.warmup)
- except KeyboardInterrupt as e:
- self.benchmark_results["warm"] = e.args[0]
-
-
-class BurstBenchmark(BaseBenchmark):
- allowed_dest_types = [GalaxyDestination, GalaxyCondorDestination, PulsarMQDestination, CondorDestination]
- allowed_workflow_types = [GalaxyWorkflow, CondorWorkflow]
-
- background_tasks: List[Dict] = list()
-
- def __init__(self, name, benchmarker, destinations: List[BaseDestination],
- workflows: List[BaseWorkflow], runs_per_workflow=1, burst_rate=1):
- super().__init__(name, benchmarker, destinations, workflows, runs_per_workflow)
- self.burst_rate = burst_rate
-
- if len(self.destinations) != 1:
- raise ValueError("BurstBenchmark can only be used with exactly one Destination.")
-
- if len(self.workflows) != 1:
- raise ValueError("BurstBenchmark can only be used with exactly one Workflow.")
-
- self.destination_type = type(self.destinations[0])
-
- if self.destination_type is CondorDestination:
- self.workflows: List[CondorWorkflow]
- self.destinations: List[CondorDestination]
-
- # Deploy Workflow to Destination
- for destination in self.destinations:
- for workflow in self.workflows:
- if type(workflow) is not CondorWorkflow:
- raise ValueError("CondorDestination can only work with CondorWorkflow!")
-
- destination.deploy_workflow(workflow)
-
- def run(self, benchmarker):
- background_task_process = self.BackgroundTaskThread(self)
- background_task_process.start()
-
- threads = []
- results = [None]*self.runs_per_workflow
- total_runs = next_runs = 0
- while total_runs < self.runs_per_workflow:
- next_runs += self.burst_rate
- # If burst_rate < 1, workflow should be run less than 1x per second. So just wait, until next_runs > 1
- if next_runs < 1:
- time.sleep(1)
- continue
-
- # Make sure, runs_per_workflow won't be exceeded
- if total_runs + next_runs >= self.runs_per_workflow:
- next_runs = self.runs_per_workflow - total_runs
-
- for _ in range(0, int(next_runs)):
- process = self.BurstThread(self, total_runs, results)
- process.start()
- threads.append(process)
- total_runs += 1
-
- next_runs = 0
- time.sleep(1)
-
- # Wait for all Benchmarks being executed
- finished_jobs = 0
- for process in threads:
- process.join()
- finished_jobs += 1
- log.info("{finished} out of {total} workflows are finished.".format(finished=finished_jobs,
- total=total_runs))
- background_task_process.stop = True
-
- self.benchmark_results = {
- "warm": {
- self.destinations[0].name: {
- self.workflows[0].name: results
- }
- }
- }
-
- class BackgroundTaskThread(threading.Thread):
- stop = False
-
- def __init__(self, bm):
- threading.Thread.__init__(self)
- self.bm = bm
-
- def run(self):
- if len(self.bm.background_tasks) == 0:
- return
-
- log.info("Starting to run BackgroundTaskThread")
- for task in self.bm.background_tasks:
- task["next_run"] = time.monotonic() + task["first_run_after"]
- if "run_until" in task:
- task["run_until"] += time.monotonic()
-
- while True:
- for task in self.bm.background_tasks:
- if task["next_run"] <= time.monotonic():
- log.info("Running background task {task}".format(task=task))
- task["task"].run()
- task["next_run"] = time.monotonic() + task["run_every"]
- if "run_until" in task:
- if task["run_until"] <= time.monotonic() and task["next_run"] < float("inf"):
- task["next_run"] = float("inf")
- log.info("Stopped background task {task}, as run_until passed".format(task=task))
- if self.stop:
- break
-
- time.sleep(1)
-
- class BurstThread(threading.Thread):
- """
- Class to run a Workflow within a thread to allow multiple runs a the same time.
- """
- def __init__(self, bm, thread_id, results: List):
- threading.Thread.__init__(self)
- self.bm = bm
- self.thread_id = thread_id
- self.results = results
-
- def run(self):
- """
- Runs a GalaxyWorkflow or a CondorWorkflow.
- """
- log.info("Running with thread_id {thread_id}".format(thread_id=self.thread_id))
- if self.bm.destination_type is PulsarMQDestination:
- try:
- res = run_galaxy_benchmark(self, self.bm.galaxy, self.bm.destinations, self.bm.workflows,
- 1, "warm", False)
- self.results[self.thread_id] = res[self.bm.destinations[0].name][self.bm.workflows[0].name][0] # TODO: Handle error-responses
- except ConnectionError:
- log.error("ConnectionError!")
- self.results[self.thread_id] = {"status": "error"}
-
- if self.bm.destination_type is CondorDestination:
- for destination in self.bm.destinations:
- for workflow in self.bm.workflows:
- result = destination.run_workflow(workflow)
- result["history_name"] = str(time.time_ns()) + str(random.randrange(0, 99999))
- result["workflow_metrics"] = {
- "status": {
- "name": "workflow_status",
- "type": "string",
- "plugin": "benchmarker",
- "value": result["status"]
- },
- "total_runtime": {
- "name": "total_workflow_runtime",
- "type": "float",
- "plugin": "benchmarker",
- "value": result["total_workflow_runtime"]
- },
- "submit_time": {
- "name": "submit_time",
- "type": "float",
- "plugin": "benchmarker",
- "value": result["submit_time"]
- }
- }
-
- self.results[self.thread_id] = result
-
-
-def run_galaxy_benchmark(benchmark, galaxy, destinations: List[PulsarMQDestination],
- workflows: List[GalaxyWorkflow], runs_per_workflow=1, run_type="warm", warmup=True):
- """
- Runs the given list of Workflows on the given list of Destinations as a cold or warm benchmark on a
- PulsarMQDestination for runs_per_workflow times. Handles failures too and retries up to one time.
- """
- if run_type not in ["cold", "warm"]:
- raise ValueError("'run_type' must be of type 'cold' or 'warm'.")
-
- benchmark_results = dict()
-
- # Add +1 to warm up Pulsar, if run_type is "warm" and warmup should happen
- if warmup and run_type == "warm":
- runs_per_workflow += 1
-
- log.info("Starting to run {type} benchmarks.".format(type=run_type))
- try:
- for destination in destinations:
- benchmark_results[destination.name] = dict()
-
- log.info("Running {type} benchmark for destination: {dest}.".format(type=run_type, dest=destination.name))
- for workflow in workflows:
- benchmark_results[destination.name][workflow.name] = list()
- retries = 0
- i = 0
- while i < runs_per_workflow:
- if warmup and run_type == "warm" and i == 0:
- log.info("First run! Warming up. Results won't be considered for the first time.")
- result = destination.run_workflow(workflow)
- if result["status"] == "error":
- retries += 1
- else:
- if run_type == "cold" and benchmark.cold_pre_task is not None:
- log.info("Running cold pre-task for Cleanup.")
- destination.run_task(benchmark.cold_pre_task)
-
- log.info("Running {type} '{workflow}' for the {i} time on {dest}.".format(type=run_type,
- workflow=workflow.name,
- i=i + 1,
- dest=destination.name))
- result = destination.run_workflow(workflow)
-
- if "history_name" in result and result["status"] == "success":
- result["jobs"] = destination.get_jobs(result["history_name"])
-
- result["workflow_metrics"] = {
- "status": {
- "name": "workflow_status",
- "type": "string",
- "plugin": "benchmarker",
- "value": result["status"]
- },
- "total_runtime": {
- "name": "total_workflow_runtime",
- "type": "float",
- "plugin": "benchmarker",
- "value": result["total_workflow_runtime"]
- }
- }
-
- log.info("Finished running '{workflow}' with status '{status}' in {time} seconds."
- .format(workflow=workflow.name, status=result["status"],
- time=result["total_workflow_runtime"]))
-
- # Handle possible errors and maybe retry
- if result["status"] == "error":
- log.info("Result won't be considered.")
-
- if retries < 4:
- retry_wait = 60 * 2 ** retries
- log.info("Retrying after {wait} seconds..".format(wait=retry_wait))
- time.sleep(retry_wait)
- retries += 1
- i -= 1
- # If too many retries, continue with next workflow
- else:
- break
- else:
- benchmark_results[destination.name][workflow.name].append(result)
- retries = 0
-
- i += 1
- except KeyboardInterrupt:
- log.info("Received KeyboardInterrupt. Stopping benchmark and saving current results.")
- # So previous results are saved
- raise KeyboardInterrupt(benchmark_results)
-
- return benchmark_results
-
-
-def configure_benchmark(bm_config: Dict, destinations: Dict, workflows: Dict, glx, benchmarker) -> BaseBenchmark:
- """
- Initializes and configures a Benchmark according to the given configuration. Returns the configured Benchmark.
- """
- # Check, if all set properly
- if bm_config["type"] not in ["ColdvsWarm", "DestinationComparison", "Burst"]:
- raise ValueError("Benchmark-Type '{type}' not valid".format(type=bm_config["type"]))
-
- runs_per_workflow = bm_config["runs_per_workflow"] if "runs_per_workflow" in bm_config else 1
-
- if bm_config["type"] == "ColdvsWarm":
- benchmark = ColdWarmBenchmark(bm_config["name"], benchmarker,
- _get_needed_destinations(bm_config, destinations, ColdWarmBenchmark),
- _get_needed_workflows(bm_config, workflows, ColdWarmBenchmark), glx,
- runs_per_workflow)
- benchmark.galaxy = glx
- if "cold_pre_task" in bm_config:
- if bm_config["cold_pre_task"]["type"] == "AnsiblePlaybook":
- benchmark.cold_pre_task = AnsiblePlaybookTask(benchmark, bm_config["cold_pre_task"]["playbook"])
-
- if "warm_pre_task" in bm_config:
- if bm_config["warm_pre_task"]["type"] == "AnsiblePlaybook":
- benchmark.warm_pre_task = AnsiblePlaybookTask(benchmark, bm_config["warm_pre_task"]["playbook"])
-
- if bm_config["type"] == "DestinationComparison":
- warmup = True if "warmup" not in bm_config else bm_config["warmup"]
- benchmark = DestinationComparisonBenchmark(bm_config["name"], benchmarker,
- _get_needed_destinations(bm_config, destinations,
- DestinationComparisonBenchmark),
- _get_needed_workflows(bm_config, workflows,
- DestinationComparisonBenchmark),
- glx, runs_per_workflow, warmup)
-
- if bm_config["type"] == "Burst":
- benchmark = BurstBenchmark(bm_config["name"], benchmarker,
- _get_needed_destinations(bm_config, destinations, BurstBenchmark),
- _get_needed_workflows(bm_config, workflows, BurstBenchmark),
- runs_per_workflow, bm_config["burst_rate"])
- benchmark.galaxy = glx
-
- if "background_tasks" in bm_config:
- benchmark.background_tasks = list()
- for task_conf in bm_config["background_tasks"]:
- task_conf["task"] = configure_task(task_conf, benchmark)
- benchmark.background_tasks.append(task_conf)
-
- if "pre_tasks" in bm_config:
- benchmark.pre_tasks = list()
- for task in bm_config["pre_tasks"]:
- if task["type"] == "AnsiblePlaybook":
- benchmark.pre_tasks.append(AnsiblePlaybookTask(benchmark, task["playbook"]))
- elif task["type"] == "BenchmarkerTask":
- benchmark.pre_tasks.append(BenchmarkerTask(benchmark, task["name"]))
- else:
- raise ValueError("Task of type '{type}' is not supported".format(type=task["type"]))
-
- if "post_tasks" in bm_config:
- benchmark.post_tasks = list()
- for task in bm_config["post_tasks"]:
- if task["type"] == "AnsiblePlaybook":
- benchmark.post_tasks.append(AnsiblePlaybookTask(benchmark, task["playbook"]))
- elif task["type"] == "BenchmarkerTask":
- benchmark.post_tasks.append(BenchmarkerTask(benchmark, task["name"]))
- else:
- raise ValueError("Task of type '{type}' is not supported".format(type=task["type"]))
-
- return benchmark
-
-
-def _get_needed_destinations(bm_config: Dict, destinations: Dict, bm_type) -> List:
- """
- Returns a list of the destinations that were set in the configuration of the benchmark.
- """
- if "destinations" not in bm_config or bm_config["destinations"] is None or len(bm_config["destinations"]) == 0:
- raise ValueError("No destination set in benchmark '{name}'".format(name=bm_config["name"]))
-
- needed_destinations = list()
- for dest_name in bm_config["destinations"]:
- # Make sure, that destination exists
- if dest_name not in destinations:
- raise ValueError("Destination '{name}' not set in workflows-configuration.".format(name=dest_name))
- # Make sure, that destination-type is allowed
- if type(destinations[dest_name]) not in bm_type.allowed_dest_types:
- raise ValueError("Destination-Type {dest} is not allowed in benchmark-type {bm}. \
- Error in benchmark name {bm_name}".format(dest=type(destinations[dest_name]),
- bm=type(bm_type), bm_name=bm_config["name"]))
- needed_destinations.append(destinations[dest_name])
-
- return needed_destinations
-
-
-def _get_needed_workflows(bm_config: Dict, workflows: Dict, bm_type) -> List:
- """
- Returns a list of the workflows that were set in the configuration of the benchmark.
- """
- if "workflows" not in bm_config or bm_config["workflows"] is None or len(bm_config["workflows"]) == 0:
- raise ValueError("No workflow set in benchmark '{name}'".format(name=bm_config["name"]))
-
- needed_workflows = list()
- for wf_name in bm_config["workflows"]:
- # Make sure, that workflow exists
- if wf_name not in workflows:
- raise ValueError("Workflow '{name}' not set in workflows-configuration.".format(name=wf_name))
- # Make sure, that workflow-type is allowed
- if type(workflows[wf_name]) not in bm_type.allowed_workflow_types:
- raise ValueError("Workflow-Type {wf} is not allowed in benchmark-type {bm}. \
- Error in benchmark name {bm_name}".format(wf=type(workflows[wf_name]),
- bm=type(bm_type),
- bm_name=bm_config["name"]))
- needed_workflows.append(workflows[wf_name])
-
- return needed_workflows
diff --git a/galaxy_benchmarker/benchmarker.py b/galaxy_benchmarker/benchmarker.py
index ce5f6b96..7da146d1 100644
--- a/galaxy_benchmarker/benchmarker.py
+++ b/galaxy_benchmarker/benchmarker.py
@@ -1,94 +1,130 @@
-from typing import Dict
-import workflow
-import destination
-import benchmark
-from galaxy_bridge import Galaxy
-import logging
-import json
-from influxdb_bridge import InfluxDB
-from openstack_bridge import OpenStackCompute
-
-log = logging.getLogger("GalaxyBenchmarker")
-
-
-class Benchmarker:
- glx: Galaxy
- inflx_db: InfluxDB
- workflows: Dict[str, workflow.BaseWorkflow]
- destinations: Dict[str, destination.BaseDestination]
- benchmarks: Dict[str, benchmark.BaseBenchmark]
-
- def __init__(self, config):
- glx_conf = config["galaxy"]
- self.glx = Galaxy(glx_conf["url"], glx_conf["user_key"], glx_conf.get("shed_install", False),
- glx_conf.get("ssh_user", None), glx_conf.get("ssh_key", None),
- glx_conf.get("galaxy_root_path", None), glx_conf.get("galaxy_config_dir", None),
- glx_conf.get("galaxy_user", None))
-
- if "influxdb" in config:
- inf_conf = config["influxdb"]
- self.inflx_db = InfluxDB(inf_conf["host"], inf_conf["port"], inf_conf["username"], inf_conf["password"],
- inf_conf["db_name"])
- else:
- self.inflx_db = None
+from __future__ import annotations
- if "openstack" in config:
- os_conf = config["openstack"]
- self.openstack = OpenStackCompute(os_conf["auth_url"], os_conf["compute_endpoint_version"],
- os_conf["username"], os_conf["password"], os_conf["project_id"],
- os_conf["region_name"], os_conf["user_domain_name"])
-
- self.workflows = dict()
- for wf_config in config["workflows"]:
- self.workflows[wf_config["name"]] = workflow.configure_workflow(wf_config)
-
- self.destinations = dict()
- for dest_config in config["destinations"]:
- self.destinations[dest_config["name"]] = destination.configure_destination(dest_config, self.glx)
-
- self.benchmarks = dict()
- for bm_config in config["benchmarks"]:
- self.benchmarks[bm_config["name"]] = benchmark.configure_benchmark(bm_config, self.destinations,
- self.workflows, self.glx, self)
+import logging
+import signal
+from dataclasses import dataclass
+from datetime import datetime
+from pathlib import Path
+from types import FrameType
+from typing import Optional
- if glx_conf.get("configure_job_destinations", False):
- log.info("Creating job_conf for Galaxy and deploying it")
- destination.create_galaxy_job_conf(self.glx, self.destinations)
- self.glx.deploy_job_conf()
+from serde import serde
- if glx_conf["shed_install"]:
- self.glx.install_tools_for_workflows(list(self.workflows.values()))
+from galaxy_benchmarker.benchmarks.base import Benchmark
+from galaxy_benchmarker.typing import NamedConfigDicts
+from galaxy_benchmarker.utils import ansible
- def run_pre_tasks(self):
- log.info("Running pre-tasks for benchmarks")
- for bm in self.benchmarks.values():
- bm.run_pre_task()
+log = logging.getLogger(__name__)
- def run_post_tasks(self):
- log.info("Running post-tasks for benchmarks")
- for bm in self.benchmarks.values():
- bm.run_post_task()
- def run(self):
- for bm in self.benchmarks.values():
- log.info("Running benchmark '{bm_name}'".format(bm_name=bm.name))
- bm.run(self)
+@serde
+@dataclass
+class BenchmarkerConfig:
+ results_path: str = "results/"
+ results_save_to_file: bool = True
+ results_save_raw_results: bool = False
+ results_print: bool = True
- def get_results(self):
- for bm in self.benchmarks.values():
- print(bm.benchmark_results)
+ log_ansible_output: bool = False
- def save_results(self, filename="results"):
- results = list()
- for bm in self.benchmarks.values():
- results.append(bm.benchmark_results)
- json_results = json.dumps(results, indent=2)
- with open(filename+".json", "w") as fh:
- fh.write(json_results)
+@serde
+@dataclass
+class GlobalConfig:
+ config: Optional[BenchmarkerConfig]
- def send_results_to_influxdb(self):
- for bm in self.benchmarks.values():
- bm.save_results_to_influxdb(self.inflx_db)
+ tasks: Optional[NamedConfigDicts]
+ benchmarks: NamedConfigDicts
+class Benchmarker:
+ def __init__(self, config: BenchmarkerConfig, benchmarks: NamedConfigDicts):
+ self.config = config
+ self.current_benchmark: Optional[Benchmark] = None
+
+ self.benchmarks: list[Benchmark] = []
+ for name, b_config in benchmarks.items():
+ self.benchmarks.append(Benchmark.create(name, b_config, self))
+
+ self.results = Path(config.results_path)
+ if self.results.exists():
+ if not self.results.is_dir():
+ raise ValueError("'results_path' has to be a folder")
+ else:
+ self.results.mkdir(parents=True)
+
+ if config.log_ansible_output:
+ ansible.LOG_ANSIBLE_OUTPUT = True
+
+ # Safe results in case of interrupt
+ def handle_signal(signum: int, frame: Optional[FrameType]) -> None:
+ ansible.stop_playbook(signum)
+ self.save_results_of_current_benchmark()
+ exit(0)
+
+ signal.signal(signal.SIGINT, handle_signal)
+
+ def run(
+ self,
+ run_pretasks: bool = True,
+ run_benchmarks: bool = True,
+ run_posttasks: bool = True,
+ filter_benchmarks: list[str] = [],
+ ) -> None:
+ """Run all benchmarks sequentially
+
+ Steps:
+ - Run pre_tasks
+ - Run benchmark
+ - Print results (optional)
+ - Save results to file (optional)
+ - Run post_tasks
+ """
+
+ for i, benchmark in enumerate(self.benchmarks):
+ self.current_benchmark = benchmark
+ current_run = f"({i+1}/{len(self.benchmarks)})"
+
+ if filter_benchmarks and benchmark.name not in filter_benchmarks:
+ log.info("%s Skipping %s", current_run, benchmark.name)
+ continue
+
+ try:
+ if run_pretasks:
+ log.info("%s Pre task for %s", current_run, benchmark.name)
+ benchmark.run_pre_tasks()
+
+ if run_benchmarks:
+ log.info("%s Start run for %s", current_run, benchmark.name)
+ benchmark.run()
+ except Exception as e:
+ log.exception(
+ "Benchmark run failed with exception. Continuing with next benchmark"
+ )
+
+ if run_benchmarks:
+ # Save results only when actual benchmark ran
+ self.save_results_of_current_benchmark()
+
+ if run_posttasks:
+ log.info("%s Post task for %s", current_run, benchmark.name)
+ benchmark.run_post_tasks()
+
+ time = datetime.now().replace(microsecond=0).isoformat()
+ log.info("%s Finished benchmark run at %s", current_run, time)
+
+ def save_results_of_current_benchmark(self) -> None:
+ """Save the result of the current benchmark
+
+ Extracted as function for SIGNAL-handling"""
+ if not self.current_benchmark:
+ log.warning("Nothing to save.")
+ return
+
+ if self.config.results_print:
+ print(f"#### Results for benchmark {self.current_benchmark}")
+ print(self.current_benchmark.benchmark_results)
+
+ if self.config.results_save_to_file:
+ file = self.current_benchmark.save_results_to_file(self.results)
+ log.info("Saving results to file: '%s'.", file)
diff --git a/galaxy_benchmarker/benchmarks/__init__.py b/galaxy_benchmarker/benchmarks/__init__.py
new file mode 100644
index 00000000..e668db7d
--- /dev/null
+++ b/galaxy_benchmarker/benchmarks/__init__.py
@@ -0,0 +1,8 @@
+from galaxy_benchmarker.benchmarks.base import *
+from galaxy_benchmarker.benchmarks.dd import *
+from galaxy_benchmarker.benchmarks.fio import *
+from galaxy_benchmarker.benchmarks.galaxy import *
+from galaxy_benchmarker.benchmarks.mdtest import *
+from galaxy_benchmarker.benchmarks.s3benchmark import *
+from galaxy_benchmarker.benchmarks.utils import *
+from galaxy_benchmarker.benchmarks.warp import *
diff --git a/galaxy_benchmarker/benchmarks/base.py b/galaxy_benchmarker/benchmarks/base.py
new file mode 100644
index 00000000..b7f71ee7
--- /dev/null
+++ b/galaxy_benchmarker/benchmarks/base.py
@@ -0,0 +1,253 @@
+from __future__ import annotations
+
+import dataclasses
+import json
+import logging
+import tempfile
+import time
+from datetime import datetime
+from pathlib import Path
+from typing import TYPE_CHECKING, Type, TypeVar
+
+from galaxy_benchmarker.typing import BenchmarkResults
+from galaxy_benchmarker.utils.ansible import AnsibleTask
+
+if TYPE_CHECKING:
+ from galaxy_benchmarker.benchmarker import Benchmarker
+
+log = logging.getLogger(__name__)
+
+
+_registered_benchmarks: dict[str, Type["Benchmark"]] = {}
+
+
+SubClass = TypeVar("SubClass", bound="Type[Benchmark]")
+
+
+def register_benchmark(cls: SubClass) -> SubClass:
+ """Register a benchmark for factory method"""
+
+ name = cls.__name__
+ log.debug("Registering benchmark %s", name)
+
+ if name in _registered_benchmarks:
+ module = cls.__module__
+ raise ValueError(f"Already registered. Use another name for {module}.{name}")
+
+ _registered_benchmarks[name] = cls
+
+ return cls
+
+
+class BenchmarkConfig:
+ def asdict(self):
+ return {k: v for k, v in dataclasses.asdict(self).items() if v is not None}
+
+
+class Benchmark:
+ """
+ The Base-Class of Benchmark. All Benchmarks should inherit from it.
+ """
+
+ def __init__(self, name: str, config: dict, benchmarker: Benchmarker):
+ self.name = name
+ self.benchmarker = benchmarker
+
+ repetitions = config.get("repetitions", None)
+ if not repetitions:
+ raise ValueError(f"'repetitions' property is missing for '{name}'")
+
+ try:
+ self.repetitions = int(repetitions)
+ except ValueError as e:
+ raise ValueError(f"'repetitions' has to be a number for '{name}'") from e
+
+ self.id = datetime.now().replace(microsecond=0).isoformat()
+ self.benchmark_results: BenchmarkResults = {}
+ self.config = BenchmarkConfig()
+
+ # Parse pre tasks
+ self._pre_tasks: list[AnsibleTask] = []
+ if "pre_task" in config:
+ if "pre_tasks" in config:
+ raise ValueError(f"'pre_task' and 'pre_tasks' given for '{name}'")
+
+ pre_task = AnsibleTask.from_config(config["pre_task"], f"{name}_pre_task")
+ self._pre_tasks.append(pre_task)
+ elif "pre_tasks" in config:
+ for i, t_config in enumerate(config.get("pre_tasks", [])):
+ pre_task = AnsibleTask.from_config(t_config, f"{name}_pre_task_{i}")
+ self._pre_tasks.append(pre_task)
+
+ # Parse post tasks
+ self._post_tasks: list[AnsibleTask] = []
+ if "post_task" in config:
+ if "post_tasks" in config:
+ raise ValueError(f"'post_task' and 'post_tasks' given for '{name}'")
+
+ post_task = AnsibleTask.from_config(
+ config["post_task"], f"{name}_post_task"
+ )
+ self._post_tasks.append(post_task)
+ elif "post_tasks" in config:
+ for i, t_config in enumerate(config.get("post_tasks", [])):
+ post_task = AnsibleTask.from_config(t_config, f"{name}_post_task_{i}")
+ self._post_tasks.append(post_task)
+
+ @staticmethod
+ def create(name: str, config: dict, benchmarker: Benchmarker):
+ """Factory method for benchmarks
+
+ name: benchmark name
+ config: benchmark specific config
+ global_config: global config for lookups
+ """
+
+ benchmark_type = config.get("type", None)
+ if benchmark_type is None:
+ raise ValueError(f"'type' property is missing for benchmark {config}")
+
+ if benchmark_type not in _registered_benchmarks:
+ raise ValueError(f"Unkown benchmark type: {benchmark_type}")
+
+ benchmark_class = _registered_benchmarks[benchmark_type]
+ return benchmark_class(name, config, benchmarker)
+
+ def run_pre_tasks(self) -> None:
+ """Run setup tasks"""
+ for task in self._pre_tasks:
+ task.run()
+
+ def run_post_tasks(self) -> None:
+ """Run clean up task"""
+ for task in self._post_tasks:
+ task.run()
+
+ def run(self):
+ """Run benchmark"""
+
+ with tempfile.TemporaryDirectory() as temp_dir:
+ log.info("Start %s", self.name)
+ self.benchmark_results[self.name] = []
+ for i in range(self.repetitions):
+ log.info("Run %d of %d", i + 1, self.repetitions)
+ result_file = Path(temp_dir) / f"{self.name}_{i}.json"
+
+ result = self._run_at(result_file, i, self.config)
+ self.benchmark_results[self.name].append(result)
+
+ def _run_at(
+ self, result_file: Path, repetition: int, config: BenchmarkConfig
+ ) -> dict:
+ raise NotImplementedError(
+ "Benchmark._run_at is not defined. Overwrite in child class"
+ )
+
+ def save_results_to_file(self, directory: Path) -> str:
+ """Write all metrics to a file."""
+ file = directory / self.result_file.name
+ results = {"tags": self.get_tags(), "results": self.benchmark_results}
+ json_results = json.dumps(results, indent=2)
+ file.write_text(json_results)
+
+ return str(file)
+
+ @property
+ def result_file(self) -> Path:
+ return Path(f"{self.id}_{self.name}.json")
+
+ def get_tags(self) -> dict[str, str]:
+ return {
+ "plugin": "benchmarker",
+ "benchmark_name": self.name,
+ "benchmark_id": self.id,
+ "benchmark_type": self.__class__.__name__,
+ }
+
+ def __str__(self):
+ return self.name
+
+ __repr__ = __str__
+
+
+class BenchmarkOneDimMixin:
+ """Run benchmark with multiple values for a singel dimension"""
+
+ def __init__(self, name: str, config: dict, benchmarker: Benchmarker):
+ super().__init__(name, config, benchmarker)
+
+ self.dim_key = config.get("dim_key", None)
+ if not self.dim_key:
+ raise ValueError(
+ f"Property 'dim_key' (str) is missing for {name}. Must be a vaild config property name"
+ )
+
+ self.dim_values = config.get("dim_values", [])
+ if not self.dim_values:
+ raise ValueError(
+ f"Property 'dim_values' (list) is missing for {name}. Must be a list of values for 'dim_key'"
+ )
+
+ def run(self):
+ """Run benchmark with one changing parameter"""
+
+ with tempfile.TemporaryDirectory() as temp_dir:
+ key = self.dim_key
+ for value in self.dim_values:
+ log.info("Run with %s set to %s", key, value)
+
+ current_config = dataclasses.replace(self.config, **{key: value})
+
+ self.benchmark_results[value] = []
+ for i in range(self.repetitions):
+ log.info("Run %d of %d", i + 1, self.repetitions)
+
+ result_file = Path(temp_dir) / f"{self.name}_{value}_{i}.json"
+ result = self._run_at(result_file, i, current_config)
+ self.benchmark_results[value].append(result)
+
+ def get_tags(self) -> dict[str, str]:
+ return {
+ **super().get_tags(),
+ "dim_key": self.dim_key,
+ "dim_values": self.dim_values,
+ }
+
+
+@register_benchmark
+class SetupTimeBenchmark(Benchmark):
+ """Compare the setuptime/ansible connection time between different destinations.
+
+ Useful for basic setup tests and to check if everything is configured correctly
+ """
+
+ def __init__(self, name: str, config: dict, benchmarker: Benchmarker):
+ super().__init__(name, config, benchmarker)
+
+ if "hosts" not in config:
+ raise ValueError(
+ f"'hosts' property (type: list[str]) is missing for '{name}'"
+ )
+
+ self.hosts: list[str] = config["hosts"]
+
+ if not all(isinstance(value, str) for value in self.hosts):
+ raise ValueError("'hosts' property has to be of type list[str]")
+
+ self._run_task = AnsibleTask(playbook="connection_test.yml")
+
+ def run(self):
+ """Run the connection_test playbook on each destination"""
+
+ for host in self.hosts:
+ log.info("Start %s for %s", self.name, host)
+ results = []
+ for i in range(self.repetitions):
+ log.info("Run %d of %d", i + 1, self.repetitions)
+ start_time = time.monotonic()
+
+ self._run_task.run_at(host)
+
+ total_runtime = time.monotonic() - start_time
+ results.append({"runtime_in_s": total_runtime})
+ self.benchmark_results[host] = results
diff --git a/galaxy_benchmarker/benchmarks/dd.py b/galaxy_benchmarker/benchmarks/dd.py
new file mode 100644
index 00000000..3d2de54f
--- /dev/null
+++ b/galaxy_benchmarker/benchmarks/dd.py
@@ -0,0 +1,207 @@
+"""
+Definition of dd-based benchmarks
+"""
+from __future__ import annotations
+
+import dataclasses
+import logging
+import re
+import shutil
+import time
+from pathlib import Path
+from typing import TYPE_CHECKING, Any
+
+from galaxy_benchmarker.benchmarks import base
+from galaxy_benchmarker.utils import ansible
+from galaxy_benchmarker.utils.destinations import PosixBenchmarkDestination
+
+if TYPE_CHECKING:
+ from galaxy_benchmarker.benchmarker import Benchmarker
+
+log = logging.getLogger(__name__)
+
+
+@dataclasses.dataclass
+class DdConfig(base.BenchmarkConfig):
+ blocksize: str = ""
+ blockcount: str = ""
+ input: str = "/dev/zero"
+ output: str = "/mnt/volume_under_test/dd-testfile.bin"
+ flush: bool = True
+ cleanup: bool = True
+ parallel: bool = False
+
+
+def parse_result_file(file: Path) -> dict[str, Any]:
+ if not file.is_file():
+ raise ValueError(f"{file} is not a file.")
+
+ # Example output
+ # 512+0 records in
+ # 512+0 records out
+ # 512+0 records in
+ # 512+0 records out
+ # 536870912 bytes (537 MB, 512 MiB) copied, 2.32072 s, 231 MB/s
+ # 536870912 bytes (537 MB, 512 MiB) copied, 2.3709 s, 226 MB/s
+
+ pattern = re.compile(r"([0-9]+) bytes .* copied, ([0-9\.]+) s, ([0-9\.]+) MB/s$")
+
+ matches = []
+ with file.open() as file_handle:
+ for line in file_handle:
+ match = pattern.findall(line)
+ if match:
+ matches.extend(match)
+
+ total_bw_in_MiB = 0.0
+ total_bw_in_mb = 0.0
+ for bytes, time, bw_in_MB in matches:
+ bytes, time, bw_in_MB = int(bytes), float(time), float(bw_in_MB)
+ bw_in_MiB = (bytes / 1024**2) / time
+ bw_in_MB_calulated = (bytes / 1000**2) / time
+
+ if bw_in_MB not in [round(bw_in_MB_calulated, 0), round(bw_in_MB_calulated, 1)]:
+ log.warning(
+ "Missmatch between calculated and parsed bandwidth in MB: Parsed: %.2f, Calculated %.2f",
+ bw_in_MB,
+ bw_in_MB_calulated,
+ )
+ total_bw_in_MiB += bw_in_MiB
+ total_bw_in_mb += bw_in_MB
+
+ return {
+ "bw_in_MiB": total_bw_in_MiB,
+ "bw_in_mb": total_bw_in_mb,
+ "detected_matches": len(matches),
+ }
+
+
+@base.register_benchmark
+class DdFixedParams(base.Benchmark):
+ """Benchmarking system with 'dd'"""
+
+ def __init__(self, name: str, config: dict, benchmarker: Benchmarker):
+ super().__init__(name, config, benchmarker)
+
+ if not "dd" in config:
+ raise ValueError(f"'dd' property (type: dict) is missing for '{self.name}'")
+ self.config = DdConfig(**config.get("dd", {}))
+
+ dest = config.get("destination", {})
+ if not dest:
+ raise ValueError(
+ f"'destination' property (type: dict) is missing for '{self.name}'"
+ )
+ self.destination = PosixBenchmarkDestination(**dest)
+
+ self._run_task = ansible.AnsibleTask(playbook="run_dd_benchmark.yml")
+
+ def _run_at(self, result_file: Path, repetition: int, dd_config: DdConfig) -> dict:
+ """Perform a single run"""
+
+ start_time = time.monotonic()
+
+ self._run_task.run_at(
+ self.destination.host,
+ {
+ "dd_dir": self.destination.target_folder,
+ "dd_result_file": result_file.name,
+ "controller_dir": result_file.parent,
+ **{f"dd_{key}": value for key, value in dd_config.asdict().items()},
+ },
+ )
+
+ total_runtime = time.monotonic() - start_time
+
+ if self.benchmarker.config.results_save_raw_results:
+ new_path = self.benchmarker.results / self.result_file.stem
+ new_path.mkdir(exist_ok=True)
+ shutil.copy(result_file, new_path / result_file.name)
+
+ result = parse_result_file(result_file)
+ log.info("Run took %d s", total_runtime)
+
+ return result
+
+ def get_tags(self) -> dict[str, str]:
+ return {**super().get_tags(), "dd": self.config.asdict()}
+
+
+@base.register_benchmark
+class DdOneDimParams(base.BenchmarkOneDimMixin, DdFixedParams):
+ """Run dd with multiple values for a singel dimension"""
+
+
+@base.register_benchmark
+class DdPrepareNetappRead(DdOneDimParams):
+ """Setup directory struture for DD read benchmark"""
+
+ def _run_at(self, result_file: Path, repetition: int, dd_config: DdConfig) -> dict:
+ """Perform a single run"""
+
+ start_time = time.monotonic()
+
+ filename = f"{dd_config.blockcount}x{dd_config.blocksize}.{repetition}.bin"
+ current_config = dataclasses.replace(
+ dd_config,
+ input="/dev/urandom",
+ output=f"/mnt/volume_under_test/{filename}",
+ cleanup=False,
+ )
+
+ self._run_task.run_at(
+ self.destination.host,
+ {
+ "dd_dir": self.destination.target_folder,
+ "dd_result_file": result_file.name,
+ "controller_dir": result_file.parent,
+ **{
+ f"dd_{key}": value for key, value in current_config.asdict().items()
+ },
+ },
+ )
+
+ total_runtime = time.monotonic() - start_time
+ log.info("Run took %d s", total_runtime)
+ return {"runtime_in_s": total_runtime}
+
+
+@base.register_benchmark
+class DdNetappRead(DdOneDimParams):
+ def _run_at(self, result_file: Path, repetition: int, dd_config: DdConfig) -> dict:
+ """Perform a single run"""
+
+ start_time = time.monotonic()
+
+ filename = f"{dd_config.blockcount}x{dd_config.blocksize}.{repetition}.bin"
+ current_config = dataclasses.replace(
+ dd_config,
+ input=f"/mnt/volume_under_test/{filename}",
+ output="/dev/null",
+ flush=False,
+ cleanup=False,
+ )
+
+ self._run_task.run_at(
+ self.destination.host,
+ {
+ "dd_dir": self.destination.target_folder,
+ "dd_result_file": result_file.name,
+ "controller_dir": result_file.parent,
+ **{
+ f"dd_{key}": value for key, value in current_config.asdict().items()
+ },
+ },
+ )
+
+ total_runtime = time.monotonic() - start_time
+
+ if self.benchmarker.config.results_save_raw_results:
+ new_path = self.benchmarker.results / self.result_file.stem
+ new_path.mkdir(exist_ok=True)
+ shutil.copy(result_file, new_path / result_file.name)
+
+ result = parse_result_file(result_file)
+ log.info("Run took %d s", total_runtime)
+
+ return result
diff --git a/galaxy_benchmarker/benchmarks/fio.py b/galaxy_benchmarker/benchmarks/fio.py
new file mode 100644
index 00000000..2e450e2b
--- /dev/null
+++ b/galaxy_benchmarker/benchmarks/fio.py
@@ -0,0 +1,270 @@
+"""
+Definition of fio-based benchmarks
+"""
+from __future__ import annotations
+
+import dataclasses
+import json
+import logging
+import shutil
+import tempfile
+import time
+from pathlib import Path
+from typing import TYPE_CHECKING, Any
+
+from galaxy_benchmarker.benchmarks import base
+from galaxy_benchmarker.typing import RunResult
+from galaxy_benchmarker.utils import ansible
+from galaxy_benchmarker.utils.destinations import PosixBenchmarkDestination
+
+if TYPE_CHECKING:
+ from galaxy_benchmarker.benchmarker import Benchmarker
+
+log = logging.getLogger(__name__)
+
+
+@dataclasses.dataclass
+class FioConfig(base.BenchmarkConfig):
+ """Available parameters for fio"""
+
+ blocksize: str = ""
+ filesize: str = "5G"
+ iodepth: int = 32
+ ioengine: str = "libaio"
+ mode: str = ""
+ numjobs: int = 4
+ ramptime_in_s: int = 0
+ refill_buffers: bool = True
+ runtime_in_s: int = 60
+ time_based: bool = True
+ prepare_read_benchmark_in_tmp: bool = False
+
+
+@base.register_benchmark
+class FioFixedParams(base.Benchmark):
+ """Run fio with fixed params"""
+
+ def __init__(self, name: str, config: dict, benchmarker: Benchmarker):
+ super().__init__(name, config, benchmarker)
+
+ if not "fio" in config:
+ raise ValueError(
+ f"'fio' property (type: dict) is missing for '{self.name}'"
+ )
+ self.config = FioConfig(**config.get("fio", {}))
+
+ dest = config.get("destination", {})
+ if not dest:
+ raise ValueError(
+ f"'destination' property (type: dict) is missing for '{self.name}'"
+ )
+ self.destination = PosixBenchmarkDestination(**dest)
+
+ self._run_task = ansible.AnsibleTask(playbook="run_fio_benchmark.yml")
+
+ def _run_at(
+ self, result_file: Path, repetition: int, fio_config: FioConfig
+ ) -> RunResult:
+ """Perform a single run"""
+
+ start_time = time.monotonic()
+
+ self._run_task.run_at(
+ self.destination.host,
+ {
+ "fio_dir": self.destination.target_folder,
+ "fio_result_file": result_file.name,
+ "controller_dir": result_file.parent,
+ "fio_jobname": self.name,
+ "fio_filesize_in_bytes": self._filesize_to_bytes(fio_config.filesize),
+ **{f"fio_{key}": value for key, value in fio_config.asdict().items()},
+ },
+ )
+
+ total_runtime = time.monotonic() - start_time
+
+ if self.benchmarker.config.results_save_raw_results:
+ new_path = self.benchmarker.results / self.result_file.stem
+ new_path.mkdir(exist_ok=True)
+ shutil.copy(result_file, new_path / result_file.name)
+
+ result = parse_result_file(result_file, self.name)
+ log.info("Run took %d s", total_runtime)
+
+ return result
+
+ def get_tags(self) -> dict[str, str]:
+ return {**super().get_tags(), "fio": self.config.asdict()}
+
+ @staticmethod
+ def _filesize_to_bytes(filesize: str) -> str:
+ """
+ Examples:
+ "5G" -> "5368709120"
+ """
+ unit = filesize.strip()[-1]
+ number = filesize.strip()[:-1]
+ match unit.upper():
+ case "G":
+ multiplier = 1024**3
+ case "M":
+ multiplier = 1024**2
+ case "K":
+ multiplier = 1024**1
+ case _:
+ assert filesize.isnumeric(), "Unknown unit for filesize"
+ multiplier = 1024**0
+ number = filesize.strip()
+ return str(int(number) * multiplier)
+
+
+@base.register_benchmark
+class FioOneDimParams(base.BenchmarkOneDimMixin, FioFixedParams):
+ """Run fio with multiple values for a singel dimension"""
+
+
+@base.register_benchmark
+class FioNotContainerized(FioFixedParams):
+ """Run fio outside of a container"""
+
+ def __init__(self, name: str, config: dict, benchmarker: Benchmarker):
+ super().__init__(name, config, benchmarker)
+
+ self._pre_task = ansible.AnsibleTask(
+ playbook="run_fio_benchmark_not_containerized_check.yml",
+ host=f"{self.destination.host},",
+ )
+
+ self._run_task = ansible.AnsibleTask(
+ playbook="run_fio_benchmark_not_containerized.yml"
+ )
+
+
+@base.register_benchmark
+class FioFullPosix(FioFixedParams):
+ def run(self):
+ """Run 'fio'"""
+
+ with tempfile.TemporaryDirectory() as temp_dir:
+ log.info("Start %s", self.name)
+
+ throughput_write_config = dataclasses.replace(
+ self.config,
+ mode="write",
+ blocksize="1024k",
+ numjobs=4,
+ iodepth=32,
+ )
+ throughput_read_config = dataclasses.replace(
+ self.config,
+ mode="read",
+ blocksize="1024k",
+ numjobs=4,
+ iodepth=32,
+ )
+ iops_write_config = dataclasses.replace(
+ self.config,
+ mode="randwrite",
+ blocksize="4k",
+ numjobs=4,
+ iodepth=32,
+ )
+ iops_read_config = dataclasses.replace(
+ self.config,
+ mode="randread",
+ blocksize="4k",
+ numjobs=4,
+ iodepth=32,
+ )
+ latency_write_config = dataclasses.replace(
+ self.config,
+ mode="randwrite",
+ blocksize="4k",
+ numjobs=1,
+ iodepth=1,
+ )
+ latency_read_config = dataclasses.replace(
+ self.config,
+ mode="randread",
+ blocksize="4k",
+ numjobs=1,
+ iodepth=1,
+ )
+
+ runs = [
+ ("throughput_read", throughput_read_config),
+ ("throughput_write", throughput_write_config),
+ ("iops_read", iops_read_config),
+ ("iops_write", iops_write_config),
+ ("latency_read", latency_read_config),
+ ("latency_write", latency_write_config),
+ ]
+
+ for name, config in runs:
+ self.benchmark_results[name] = []
+ log.info("Start %s", name)
+ for i in range(self.repetitions):
+ log.info("Run %d of %d", i + 1, self.repetitions)
+ result_file = Path(temp_dir) / f"{name}_{i}.json"
+
+ try:
+ result = self._run_at(result_file, i, config)
+ self.benchmark_results[name].append(result)
+ except Exception as e:
+ log.exception("Error during '%s' run: %s", name, e)
+
+
+def parse_result_file(file: Path, jobname: str) -> dict[str, Any]:
+ if not file.is_file():
+ raise ValueError(f"{file} is not a fio result file.")
+
+ with file.open() as file_handle:
+ lines = file_handle.readlines()
+ lines = [l for l in lines if not l.startswith("fio: pid=")]
+ result_json = json.loads("\n".join(lines))
+
+ jobs = [job for job in result_json["jobs"] if job["jobname"] == jobname]
+
+ if len(jobs) != 1:
+ raise ValueError(f"Job '{jobname}' is missing in result {file}")
+
+ result = {}
+ mode = result_json["global options"].get("rw", None)
+ if mode is None:
+ # Fallback
+ mode = jobs[0]["job options"]["rw"]
+
+ if mode in ["read", "randread", "rw", "randrw"]:
+ read = result_json["jobs"][0]["read"]
+ result.update(_parse_job_result(read, "read"))
+ if mode in ["write", "randwrite", "rw", "randrw"]:
+ write = result_json["jobs"][0]["write"]
+ result.update(_parse_job_result(write, "write"))
+
+ return result
+
+
+def _parse_job_result(result: dict, prefix: str) -> dict[str, Any]:
+ # Sometimes latency has some values even when the run crashes and all other metrics have 0.0
+ # -> Set latency also to 0 in that case
+ return {
+ f"{prefix}_bw_min_in_MiB": result["bw_min"] / 1024,
+ f"{prefix}_bw_max_in_MiB": result["bw_max"] / 1024,
+ f"{prefix}_bw_mean_in_MiB": result["bw_mean"] / 1024,
+ f"{prefix}_iops_min": result["iops_min"],
+ f"{prefix}_iops_max": result["iops_max"],
+ f"{prefix}_iops_mean": result["iops_mean"],
+ f"{prefix}_iops_stddev": result["iops_stddev"],
+ f"{prefix}_lat_min_in_ms": result["lat_ns"]["min"] / 1_000_000
+ if result["bw_mean"] > 0
+ else 0.0,
+ f"{prefix}_lat_max_in_ms": result["lat_ns"]["max"] / 1_000_000
+ if result["bw_mean"] > 0
+ else 0.0,
+ f"{prefix}_lat_mean_in_ms": result["lat_ns"]["mean"] / 1_000_000
+ if result["bw_mean"] > 0
+ else 0.0,
+ f"{prefix}_lat_stddev_in_ms": result["lat_ns"]["stddev"] / 1_000_000
+ if result["bw_mean"] > 0
+ else 0.0,
+ }
diff --git a/galaxy_benchmarker/benchmarks/galaxy.py b/galaxy_benchmarker/benchmarks/galaxy.py
new file mode 100644
index 00000000..0f39199b
--- /dev/null
+++ b/galaxy_benchmarker/benchmarks/galaxy.py
@@ -0,0 +1,331 @@
+"""
+Definition of galaxyjob-based benchmarks
+"""
+from __future__ import annotations
+
+import dataclasses
+import json
+import logging
+import shlex
+import time
+from pathlib import Path
+from typing import TYPE_CHECKING
+
+from galaxy_benchmarker.benchmarks import base
+from galaxy_benchmarker.utils import ansible, s3
+from galaxy_benchmarker.utils.destinations import (
+ BenchmarkDestination,
+ PosixBenchmarkDestination,
+)
+
+if TYPE_CHECKING:
+ from galaxy_benchmarker.benchmarker import Benchmarker
+
+log = logging.getLogger(__name__)
+
+
+@dataclasses.dataclass
+class GalaxyFileGenInput:
+ num_files: int
+ file_size_in_bytes: int
+
+
+@dataclasses.dataclass
+class GalaxyFileGenConfig(base.BenchmarkConfig):
+ input: GalaxyFileGenInput
+ verification_timeout_in_s: int
+ export_volume: str
+
+
+class GalaxyFileGenJob(base.Benchmark):
+ """Benchmarking galaxy with the file_gen job"""
+
+ galaxy_tool_id = "file_gen"
+ galaxy_job_config_class = GalaxyFileGenConfig
+
+ def __init__(self, name: str, config: dict, benchmarker: Benchmarker):
+ super().__init__(name, config, benchmarker)
+
+ if not self.galaxy_tool_id:
+ raise ValueError(
+ "Subclass of GalaxyJob has to specify class property 'galaxy_tool_id' (str)"
+ )
+
+ if not "galaxy_job" in config:
+ raise ValueError(
+ f"'galaxy_job' property (type: dict) is missing for '{self.name}'"
+ )
+ glx_job_config = config.get("galaxy_job")
+
+ if not "input" in glx_job_config:
+ raise ValueError(
+ f"'galaxy_job'->'input' property (type: dict) is missing for '{self.name}'"
+ )
+
+ self.config = self.galaxy_job_config_class(
+ input=GalaxyFileGenInput(**glx_job_config.pop("input")),
+ **glx_job_config,
+ )
+
+ dest = config.get("destination", {})
+ if not dest:
+ raise ValueError(
+ f"'destination' property (type: dict) is missing for '{self.name}'"
+ )
+ self.destination = BenchmarkDestination(**dest)
+
+ self._run_task = ansible.AnsibleTask(playbook="run_galaxy_job.yml")
+
+ def _run_at(
+ self, result_file: Path, repetition: int, galaxy_job_config: GalaxyFileGenConfig
+ ) -> dict:
+ """Perform a single run"""
+
+ start_time = time.monotonic()
+
+ input_str = json.dumps(dataclasses.asdict(galaxy_job_config.input))
+
+ self._run_task.run_at(
+ self.destination.host,
+ {
+ "glx_tool_id": self.galaxy_tool_id,
+ "glx_tool_input": shlex.quote(input_str),
+ "glx_expected_num_files": galaxy_job_config.input.num_files,
+ "glx_expected_size_in_bytes": galaxy_job_config.input.file_size_in_bytes,
+ **{
+ f"glx_{key}": value
+ for key, value in galaxy_job_config.asdict().items()
+ },
+ },
+ )
+
+ total_runtime = time.monotonic() - start_time
+
+ result = {"total_runtime_in_s": total_runtime}
+ log.info("Run took %d s", total_runtime)
+
+ return result
+
+ def get_tags(self) -> dict[str, str]:
+ return {
+ **super().get_tags(),
+ "galaxy_tool_id": self.galaxy_tool_id,
+ "galaxy_tool_config": self.config.asdict(),
+ }
+
+
+@dataclasses.dataclass
+class GalaxyFileGenOnMountVolumeConfig(GalaxyFileGenConfig):
+ path_to_files: str
+
+
+@base.register_benchmark
+class GalaxyFileGenOnMountVolumeJob(GalaxyFileGenJob):
+ galaxy_job_config_class = GalaxyFileGenOnMountVolumeConfig
+
+ def __init__(self, name: str, config: dict, benchmarker: Benchmarker):
+ # Store destination
+ dest_conf = config.pop("destination", {})
+ config["destination"] = {"host": "dummy"}
+
+ super().__init__(name, config, benchmarker)
+
+ # Restore destination
+ config["destination"] = dest_conf
+ self.destination = PosixBenchmarkDestination(**dest_conf)
+
+ self._run_task = ansible.AnsibleTask(playbook="run_galaxy_mount_benchmark.yml")
+ self._pre_tasks.append(
+ ansible.AnsibleTask(
+ playbook="setup_galaxy_server.yml",
+ host=self.destination.host,
+ extra_vars={
+ "galaxy_use_mount": True,
+ "galaxy_host_volume": self.destination.target_folder,
+ "galaxy_export_volume": self.config.export_volume,
+ },
+ )
+ )
+ self._post_tasks.append(
+ ansible.AnsibleTask(
+ playbook="cleanup_galaxy_server.yml",
+ host=self.destination.host,
+ extra_vars={
+ "galaxy_host_volume": self.destination.target_folder,
+ "galaxy_export_volume": self.config.export_volume,
+ },
+ )
+ )
+
+
+@dataclasses.dataclass
+class GalaxyFileGenOnS3Config(s3.S3Config, GalaxyFileGenConfig):
+ pass
+
+
+@base.register_benchmark
+class GalaxyFileGenOnS3Job(GalaxyFileGenJob):
+ galaxy_job_config_class = GalaxyFileGenOnS3Config
+ config: GalaxyFileGenOnS3Config
+
+ def __init__(self, name: str, config: dict, benchmarker: Benchmarker):
+ super().__init__(name, config, benchmarker)
+
+ self._pre_tasks.append(
+ ansible.AnsibleTask(
+ playbook="setup_galaxy_server.yml",
+ host=self.destination.host,
+ extra_vars={
+ "galaxy_use_s3": True,
+ "galaxy_export_volume": self.config.export_volume,
+ "S3_ACCESS_KEY": self.config.access_key_id,
+ "S3_SECRET_KEY": self.config.secret_access_key,
+ "S3_BASE_URL": self.config.base_url.split("://")[1],
+ "S3_BUCKET_NAME": self.config.bucket_name,
+ },
+ )
+ )
+ self._post_tasks.append(
+ ansible.AnsibleTask(
+ playbook="cleanup_galaxy_server.yml",
+ host=self.destination.host,
+ extra_vars={
+ "galaxy_export_volume": self.config.export_volume,
+ },
+ )
+ )
+
+ def _run_at(
+ self,
+ result_file: Path,
+ repetition: int,
+ galaxy_job_config: GalaxyFileGenOnS3Config,
+ ) -> dict:
+ """Perform a single run"""
+
+ # Make sure bucket is empty
+ log.info("Empty s3 bucket")
+ s3.empty_bucket(galaxy_job_config)
+ log.info("Empty s3 bucket done")
+
+ start_time = time.monotonic()
+
+ # Trigger galaxy job
+ super()._run_at(result_file, repetition, galaxy_job_config)
+
+ # Check s3 bucket for files
+ s3.check_bucket_for_files(
+ galaxy_job_config,
+ galaxy_job_config.input.num_files,
+ galaxy_job_config.input.file_size_in_bytes,
+ galaxy_job_config.verification_timeout_in_s,
+ )
+
+ total_runtime = time.monotonic() - start_time
+ result = {"total_runtime_in_s": total_runtime}
+ log.info("Run took %d s", total_runtime)
+
+ return result
+
+
+@dataclasses.dataclass
+class GalaxyFileGenOnIrodsOnMountVolumeConfig(GalaxyFileGenConfig):
+ irods_server_host: str
+ path_to_files: str
+
+
+@base.register_benchmark
+class GalaxyFileGenOnIrodsOnMountVolumeJob(GalaxyFileGenJob):
+ galaxy_job_config_class = GalaxyFileGenOnIrodsOnMountVolumeConfig
+
+ def __init__(self, name: str, config: dict, benchmarker: Benchmarker):
+ super().__init__(name, config, benchmarker)
+
+ self._run_task = ansible.AnsibleTask(playbook="run_galaxy_mount_benchmark.yml")
+ self._pre_tasks.append(
+ ansible.AnsibleTask(
+ playbook="setup_galaxy_server.yml",
+ host=self.destination.host,
+ extra_vars={
+ "galaxy_use_irods": True,
+ "galaxy_export_volume": self.config.export_volume,
+ "IRODS_HOST": self.config.irods_server_host,
+ },
+ )
+ )
+ self._post_tasks.append(
+ ansible.AnsibleTask(
+ playbook="cleanup_galaxy_server.yml",
+ host=self.destination.host,
+ extra_vars={
+ "galaxy_export_volume": self.config.export_volume,
+ },
+ )
+ )
+
+
+@dataclasses.dataclass
+class GalaxyFileGenOnIrodsOnS3Config(s3.S3Config, GalaxyFileGenConfig):
+ irods_server_host: str
+
+
+@base.register_benchmark
+class GalaxyFileGenOnIrodsOnS3Job(GalaxyFileGenJob):
+ galaxy_job_config_class = GalaxyFileGenOnIrodsOnS3Config
+ config: GalaxyFileGenOnIrodsOnS3Config
+
+ def __init__(self, name: str, config: dict, benchmarker: Benchmarker):
+ super().__init__(name, config, benchmarker)
+
+ self._pre_tasks.append(
+ ansible.AnsibleTask(
+ playbook="setup_galaxy_server.yml",
+ host=self.destination.host,
+ extra_vars={
+ "galaxy_use_irods": True,
+ "galaxy_export_volume": self.config.export_volume,
+ "IRODS_HOST": self.config.irods_server_host,
+ },
+ )
+ )
+ self._post_tasks.append(
+ ansible.AnsibleTask(
+ playbook="cleanup_galaxy_server.yml",
+ host=self.destination.host,
+ extra_vars={
+ "galaxy_export_volume": self.config.export_volume,
+ },
+ )
+ )
+
+ def _run_at(
+ self,
+ result_file: Path,
+ repetition: int,
+ galaxy_job_config: GalaxyFileGenOnS3Config,
+ ) -> dict:
+ """Perform a single run"""
+
+ # Make sure bucket is empty
+ log.info("Empty s3 bucket")
+ s3.empty_bucket(galaxy_job_config)
+ log.info("Empty s3 bucket done")
+
+ start_time = time.monotonic()
+
+ # Trigger galaxy job
+ super()._run_at(result_file, repetition, galaxy_job_config)
+
+ # Check s3 bucket for files
+ s3.check_bucket_for_files(
+ galaxy_job_config,
+ galaxy_job_config.input.num_files,
+ galaxy_job_config.input.file_size_in_bytes,
+ galaxy_job_config.verification_timeout_in_s,
+ )
+
+ total_runtime = time.monotonic() - start_time
+ result = {"total_runtime_in_s": total_runtime}
+ log.info("Run took %d s", total_runtime)
+
+ return result
diff --git a/galaxy_benchmarker/benchmarks/mdtest.py b/galaxy_benchmarker/benchmarks/mdtest.py
new file mode 100644
index 00000000..f702446a
--- /dev/null
+++ b/galaxy_benchmarker/benchmarks/mdtest.py
@@ -0,0 +1,140 @@
+"""
+Definition of mdtest-based benchmarks
+"""
+from __future__ import annotations
+
+import dataclasses
+import logging
+import shutil
+import time
+from pathlib import Path
+from typing import TYPE_CHECKING, Any
+
+from galaxy_benchmarker.benchmarks import base
+from galaxy_benchmarker.typing import RunResult
+from galaxy_benchmarker.utils import ansible
+from galaxy_benchmarker.utils.destinations import PosixBenchmarkDestination
+
+if TYPE_CHECKING:
+ from galaxy_benchmarker.benchmarker import Benchmarker
+
+log = logging.getLogger(__name__)
+
+
+@dataclasses.dataclass
+class MdtestConfig(base.BenchmarkConfig):
+ """Available parameters for mdtest"""
+
+ num_files: int
+
+
+@base.register_benchmark
+class MdtestFixedParams(base.Benchmark):
+ """Run mdtest with fixed params"""
+
+ def __init__(self, name: str, config: dict, benchmarker: Benchmarker):
+ super().__init__(name, config, benchmarker)
+
+ if not "mdtest" in config:
+ raise ValueError(
+ f"'mdtest' property (type: dict) is missing for '{self.name}'"
+ )
+ mdtest_conf = config.get("mdtest")
+ if mdtest_conf is None:
+ mdtest_conf = {}
+ self.config = MdtestConfig(**mdtest_conf)
+
+ dest = config.get("destination", {})
+ if not dest:
+ raise ValueError(
+ f"'destination' property (type: dict) is missing for '{self.name}'"
+ )
+ self.destination = PosixBenchmarkDestination(**dest)
+
+ self._run_task = ansible.AnsibleTask(playbook="run_mdtest_benchmark.yml")
+
+ def _run_at(
+ self, result_file: Path, repetition: int, mdtest_config: MdtestConfig
+ ) -> RunResult:
+ """Perform a single run"""
+
+ start_time = time.monotonic()
+
+ self._run_task.run_at(
+ self.destination.host,
+ {
+ "mdtest_dir": self.destination.target_folder,
+ "mdtest_result_file": result_file.name,
+ "controller_dir": result_file.parent,
+ **{
+ f"mdtest_{key}": value
+ for key, value in mdtest_config.asdict().items()
+ },
+ },
+ )
+
+ total_runtime = time.monotonic() - start_time
+
+ if self.benchmarker.config.results_save_raw_results:
+ new_path = self.benchmarker.results / self.result_file.stem
+ new_path.mkdir(exist_ok=True)
+ shutil.copy(result_file, new_path / result_file.name)
+
+ result = parse_result_file(result_file)
+ log.info("Run took %d s", total_runtime)
+
+ return result
+
+ def get_tags(self) -> dict[str, str]:
+ return {**super().get_tags(), "mdtest": self.config.asdict()}
+
+
+@base.register_benchmark
+class MdtestOneDimParams(base.BenchmarkOneDimMixin, MdtestFixedParams):
+ """Run mdtest with multiple values for a singel dimension"""
+
+
+def parse_result_file(file: Path) -> dict[str, Any]:
+ if not file.is_file():
+ raise ValueError(f"{file} is not a mdtest result file.")
+
+ with file.open() as file_handle:
+ lines = file_handle.readlines()
+
+ metrics = {
+ "Directory creation": "dir_create",
+ "Directory stat": "dir_stat",
+ "Directory rename": "dir_rename",
+ "Directory removal": "dir_remove",
+ "File creation": "file_create",
+ "File stat": "file_stat",
+ "File read": "file_read",
+ "File removal": "file_remove",
+ "Tree creation": "tree_create",
+ "Tree removal": "tree_remove",
+ }
+
+ result = {}
+
+ for line in lines:
+ l = line.split()
+ if len(l) < 2:
+ continue
+ if (key := f"{l[0]} {l[1]}") not in metrics:
+ continue
+ # Line has to be of format:
+ # Op Max Min Mean Std Dev
+ # "Directory creation 48.473 48.473 48.473 0.000"
+ def is_float(value):
+ try:
+ float(value)
+ except ValueError:
+ return False
+ return True
+
+ floats = [float(item) for item in l if is_float(item)]
+ assert len(floats) == 4
+ metric = metrics[key]
+ result[metric] = floats[2]
+
+ return result
diff --git a/galaxy_benchmarker/benchmarks/s3benchmark.py b/galaxy_benchmarker/benchmarks/s3benchmark.py
new file mode 100644
index 00000000..aee2d04d
--- /dev/null
+++ b/galaxy_benchmarker/benchmarks/s3benchmark.py
@@ -0,0 +1,157 @@
+"""
+Definition of s3benchmark-based benchmarks
+"""
+from __future__ import annotations
+
+import dataclasses
+import logging
+import os
+import re
+import shutil
+import time
+from pathlib import Path
+from typing import TYPE_CHECKING, Any
+
+from galaxy_benchmarker.benchmarks import base
+from galaxy_benchmarker.utils import ansible
+from galaxy_benchmarker.utils.destinations import BenchmarkDestination
+
+if TYPE_CHECKING:
+ from galaxy_benchmarker.benchmarker import Benchmarker
+
+log = logging.getLogger(__name__)
+
+
+@dataclasses.dataclass
+class S3BenchmarkConfig(base.BenchmarkConfig):
+ ## Credentials are loaded from AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY
+ # access_key_id: str = ""
+ base_url: str = ""
+ bucket_name: str = ""
+ filesize: str = ""
+ region: str = ""
+ runtime_in_s: int = 60
+ # secret_access_key: str = ""
+ threads: int = 8
+
+
+def parse_result_file(file: Path) -> dict[str, Any]:
+ if not file.is_file():
+ raise ValueError(f"{file} is not a file.")
+
+ # Example output
+ # Loop 1: PUT time 10.2 secs, objects = 1074, speed = 105.3MB/sec, 105.3 operations/sec. Slowdowns = 0
+ # Loop 1: GET time 10.1 secs, objects = 1124, speed = 111.4MB/sec, 111.4 operations/sec. Slowdowns = 0
+ # Loop 1: DELETE time 0.8 secs, 1286.9 deletes/sec. Slowdowns = 0
+
+ l_get, l_put, l_delete = "", "", ""
+ with file.open() as file_handle:
+ for line in file_handle:
+ if not line.startswith("Loop"):
+ continue
+
+ striped = line.split(":")[1].lstrip()
+ if striped.startswith("GET"):
+ l_get = striped
+ elif striped.startswith("PUT"):
+ l_put = striped
+ elif striped.startswith("DELETE"):
+ l_delete = striped
+ else:
+ raise ValueError(f"Unknown linestart: {striped}")
+
+ pattern = re.compile(
+ r", objects = ([0-9]+), speed = ([0-9\.]+)([MK]B)/sec, ([0-9\.]+) operations/sec"
+ )
+ get_num_obj, get_bw, get_bw_unit, get_ops = pattern.search(l_get).groups()
+ put_num_obj, put_bw, put_bw_unit, put_ops = pattern.search(l_put).groups()
+ del_ops = re.search(r", ([0-9\.]+) deletes/sec", l_delete).groups()[0]
+
+ if get_bw_unit == "KB":
+ get_bw = str(float(get_bw) / 1024.0)
+ if put_bw_unit == "KB":
+ put_bw = str(float(put_bw) / 1024.0)
+
+ return {
+ "get_num_objects": get_num_obj,
+ "get_bw_in_mibi": get_bw,
+ "get_op_per_s": get_ops,
+ "put_num_objects": put_num_obj,
+ "put_bw_in_mibi": put_bw,
+ "put_op_per_s": put_ops,
+ "del_op_per_s": del_ops,
+ }
+
+
+@base.register_benchmark
+class S3BenchmarkFixedParams(base.Benchmark):
+ """Benchmarking system with 's3benchmark'"""
+
+ def __init__(self, name: str, config: dict, benchmarker: Benchmarker):
+ super().__init__(name, config, benchmarker)
+
+ if not "s3benchmark" in config:
+ raise ValueError(
+ f"'s3benchmark' property (type: dict) is missing for '{self.name}'"
+ )
+ self.config = S3BenchmarkConfig(**config.get("s3benchmark"))
+
+ dest = config.get("destination", {})
+ if not dest:
+ raise ValueError(
+ f"'destination' property (type: dict) is missing for '{self.name}'"
+ )
+ self.destination = BenchmarkDestination(**dest)
+
+ self._run_task = ansible.AnsibleTask(playbook="run_s3-benchmark_benchmark.yml")
+
+ def _run_at(
+ self, result_file: Path, repetition: int, s3benchmark_config: S3BenchmarkConfig
+ ) -> dict:
+ """Perform a single run"""
+
+ start_time = time.monotonic()
+
+ access_key = os.getenv("AWS_ACCESS_KEY_ID")
+ if access_key is None:
+ raise ValueError("Missing S3 credentials in env vars: AWS_ACCESS_KEY_ID")
+
+ secret_key = os.getenv("AWS_SECRET_ACCESS_KEY")
+ if secret_key is None:
+ raise ValueError(
+ "Missing S3 credentials in env vars: AWS_SECRET_ACCESS_KEY"
+ )
+
+ self._run_task.run_at(
+ self.destination.host,
+ {
+ "s3b_result_file": result_file.name,
+ "controller_dir": result_file.parent,
+ "s3b_access_key_id": access_key,
+ "s3b_secret_access_key": secret_key,
+ **{
+ f"s3b_{key}": value
+ for key, value in s3benchmark_config.asdict().items()
+ },
+ },
+ )
+
+ total_runtime = time.monotonic() - start_time
+
+ if self.benchmarker.config.results_save_raw_results:
+ new_path = self.benchmarker.results / self.result_file.stem
+ new_path.mkdir(exist_ok=True)
+ shutil.copy(result_file, new_path / result_file.name)
+
+ result = parse_result_file(result_file)
+ log.info("Run took %d s", total_runtime)
+
+ return result
+
+ def get_tags(self) -> dict[str, str]:
+ return {**super().get_tags(), "s3b": self.config.asdict()}
+
+
+@base.register_benchmark
+class S3BenchmarkOneDimParams(base.BenchmarkOneDimMixin, S3BenchmarkFixedParams):
+ """Run s3benchmark with multiple values for a singel dimension"""
diff --git a/galaxy_benchmarker/benchmarks/utils.py b/galaxy_benchmarker/benchmarks/utils.py
new file mode 100644
index 00000000..018671d5
--- /dev/null
+++ b/galaxy_benchmarker/benchmarks/utils.py
@@ -0,0 +1,134 @@
+from __future__ import annotations
+
+import json
+import logging
+import threading
+from functools import wraps
+from pathlib import Path
+from typing import TYPE_CHECKING
+
+from galaxy_benchmarker.benchmarks import base
+
+if TYPE_CHECKING:
+ from galaxy_benchmarker.benchmarker import Benchmarker
+
+log = logging.getLogger(__name__)
+
+
+@base.register_benchmark
+class BenchmarkCompare(base.Benchmark):
+ """
+ Compare two benchmarks side by side.
+ """
+
+ def __init__(self, name: str, config: dict, benchmarker: Benchmarker):
+ super().__init__(name, config, benchmarker)
+
+ conf_a = config.get("bench_a", {})
+ if not conf_a:
+ raise ValueError(
+ f"Property 'bench_a' (dict) is missing for {name}. Must be a valid benchmark config"
+ )
+ conf_b = config.get("bench_b", {})
+ if not conf_b:
+ raise ValueError(
+ f"Property 'bench_b' (dict) is missing for {name}. Must be a valid benchmark config"
+ )
+ self.bench_a = base.Benchmark.create(f"{name}_a", conf_a, benchmarker)
+ self.bench_b = base.Benchmark.create(f"{name}_b", conf_b, benchmarker)
+
+ def run_pre_tasks(self) -> None:
+ """Run setup tasks"""
+ self.bench_a.run_pre_tasks()
+ self.bench_b.run_pre_tasks()
+
+ def run_post_tasks(self) -> None:
+ """Run clean up task"""
+ self.bench_a.run_post_tasks()
+ self.bench_b.run_post_tasks()
+
+ def run(self):
+ """Run two benchmarks in the following order: a1, b1, a2, b2, ..."""
+ sync_lock = threading.Condition()
+ sync_required = True
+
+ def run_at_wrapper(method, note):
+ """Wrapper for _ran_at
+
+ Wait after each _run_at-call so the other benchmark can run
+ """
+
+ @wraps(method)
+ def _impl(self, *method_args, **method_kwargs):
+ with sync_lock:
+ log.info("Start run in thread %s", note)
+ method_output = method(self, *method_args, **method_kwargs)
+ sync_lock.notify()
+ if sync_required:
+ sync_lock.wait()
+ return method_output
+
+ return _impl
+
+ def run_in_thread(method):
+ """Wrapper for thread call
+
+ After call to run() has finished notify the other thread so it can
+ also finish
+ """
+
+ def _impl():
+ method_output = method()
+ global sync_required
+ sync_required = False
+ with sync_lock:
+ sync_lock.notify()
+ return method_output
+
+ return _impl
+
+ _bench_a_run_at = self.bench_a._run_at
+ _bench_b_run_at = self.bench_b._run_at
+
+ try:
+ # Inject threading mechanism to interleave execution of both benchmarks
+ self.bench_a._run_at = run_at_wrapper(_bench_a_run_at, "BenchA")
+ self.bench_b._run_at = run_at_wrapper(_bench_b_run_at, "BenchB")
+
+ # creating threads
+ t1 = threading.Thread(target=run_in_thread(self.bench_a.run))
+ t2 = threading.Thread(target=run_in_thread(self.bench_b.run))
+ t1.start()
+ t2.start()
+
+ # wait until threads finish their job
+ t1.join()
+ t2.join()
+ finally:
+ # Restore original functions
+ self.bench_a._run_at = _bench_a_run_at
+ self.bench_b._run_at = _bench_b_run_at
+
+ def save_results_to_file(self, directory: Path) -> str:
+ """Write all metrics to a file."""
+ file = directory / self.result_file.name
+ results = {"tags": self.get_tags()}
+ json_results = json.dumps(results, indent=2)
+ file.write_text(json_results)
+
+ self.bench_a.save_results_to_file(directory)
+ self.bench_b.save_results_to_file(directory)
+
+ return str(file)
+
+ def get_tags(self) -> dict[str, str]:
+ return {
+ "plugin": "benchmarker",
+ "benchmark_name": self.name,
+ "benchmark_id": self.id,
+ "benchmark_type": self.__class__.__name__,
+ "bench_a": self.bench_a.get_tags(),
+ "bench_a_results": self.bench_a.result_file.name,
+ "bench_b": self.bench_b.get_tags(),
+ "bench_b_results": self.bench_b.result_file.name,
+ }
diff --git a/galaxy_benchmarker/benchmarks/warp.py b/galaxy_benchmarker/benchmarks/warp.py
new file mode 100644
index 00000000..de7116b1
--- /dev/null
+++ b/galaxy_benchmarker/benchmarks/warp.py
@@ -0,0 +1,162 @@
+"""
+Definition of warp-based benchmarks
+"""
+from __future__ import annotations
+
+import dataclasses
+import logging
+import os
+import re
+import shutil
+import time
+from pathlib import Path
+from typing import TYPE_CHECKING, Any
+
+from galaxy_benchmarker.benchmarks import base
+from galaxy_benchmarker.utils import ansible
+from galaxy_benchmarker.utils.destinations import BenchmarkDestination
+
+if TYPE_CHECKING:
+ from galaxy_benchmarker.benchmarker import Benchmarker
+
+log = logging.getLogger(__name__)
+
+
+@dataclasses.dataclass
+class WarpConfig(base.BenchmarkConfig):
+ mode: str = ""
+ ## Credentials are loaded from AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY
+ # access_key_id: str = ""
+ base_url: str = ""
+ bucket_name: str = ""
+ filesize: str = ""
+ region: str = ""
+ runtime: str = "60s"
+ # secret_access_key: str = ""
+ concurrent_ops: int = 20
+
+
+def parse_result_file(file: Path) -> dict[str, Any]:
+
+ if not file.is_file():
+ raise ValueError(f"{file} is not a file.")
+
+ # Example output
+ # Operation: DELETE, 11%, Concurrency: 20, Ran 3m47s.
+ # * Throughput: 0.28 obj/s
+
+ # Operation: GET, 38%, Concurrency: 20, Ran 4m15s.
+ # * Throughput: 8.21 MiB/s, 0.82 obj/s
+
+ # Operation: PUT, 13%, Concurrency: 20, Ran 4m19s.
+ # * Throughput: 2.85 MiB/s, 0.29 obj/s
+
+ # Operation: STAT, 33%, Concurrency: 20, Ran 4m2s.
+ # * Throughput: 0.65 obj/s
+ result = {}
+ op = ""
+ pattern_op = re.compile(r"Operation: ([A-Z]+),?")
+ pattern_throughput = re.compile(r"([0-9\.]+) ([KM]iB)/s,")
+ pattern_ops = re.compile(r"([0-9\.]+) obj/s")
+
+ with file.open() as file_handle:
+ for line in file_handle:
+ if line.startswith("Operation: "):
+ # Get op to parse next line
+ op = pattern_op.match(line).groups()[0]
+ continue
+ if not op:
+ continue
+
+ throughput_match = pattern_throughput.search(line)
+ ops_match = pattern_ops.search(line)
+
+ if throughput_match:
+ throughput_value, unit = throughput_match.groups()
+ if unit == "KiB":
+ throughput_value = str(float(throughput_value) / 1024.0)
+
+ if op == "GET":
+ result["get_bw_in_MiB"] = throughput_value
+ result["get_ops"] = ops_match.groups()[0]
+ elif op == "PUT":
+ result["put_bw_in_MiB"] = throughput_value
+ result["put_ops"] = ops_match.groups()[0]
+ elif op == "DELETE":
+ result["delete_ops"] = ops_match.groups()[0]
+ elif op == "STAT":
+ result["stat_ops"] = ops_match.groups()[0]
+ op = ""
+
+ return result
+
+
+@base.register_benchmark
+class WarpFixedParams(base.Benchmark):
+ """Benchmarking system with 'warp'"""
+
+ def __init__(self, name: str, config: dict, benchmarker: Benchmarker):
+ super().__init__(name, config, benchmarker)
+
+ if not "warp" in config:
+ raise ValueError(
+ f"'warp' property (type: dict) is missing for '{self.name}'"
+ )
+ self.config = WarpConfig(**config.get("warp"))
+
+ dest = config.get("destination", {})
+ if not dest:
+ raise ValueError(
+ f"'destination' property (type: dict) is missing for '{self.name}'"
+ )
+ self.destination = BenchmarkDestination(**dest)
+
+ self._run_task = ansible.AnsibleTask(playbook="run_warp_benchmark.yml")
+
+ def _run_at(
+ self, result_file: Path, repetition: int, warp_config: WarpConfig
+ ) -> dict:
+ """Perform a single run"""
+
+ start_time = time.monotonic()
+
+ access_key = os.getenv("AWS_ACCESS_KEY_ID")
+ if access_key is None:
+ raise ValueError("Missing S3 credentials in env vars: AWS_ACCESS_KEY_ID")
+
+ secret_key = os.getenv("AWS_SECRET_ACCESS_KEY")
+ if secret_key is None:
+ raise ValueError(
+ "Missing S3 credentials in env vars: AWS_SECRET_ACCESS_KEY"
+ )
+
+ self._run_task.run_at(
+ self.destination.host,
+ {
+ "warp_result_file": result_file.name,
+ "controller_dir": result_file.parent,
+ "warp_access_key_id": access_key,
+ "warp_secret_access_key": secret_key,
+ **{f"warp_{key}": value for key, value in warp_config.asdict().items()},
+ },
+ )
+
+ total_runtime = time.monotonic() - start_time
+
+ if self.benchmarker.config.results_save_raw_results:
+ new_path = self.benchmarker.results / self.result_file.stem
+ new_path.mkdir(exist_ok=True)
+ shutil.copy(result_file, new_path / result_file.name)
+
+ result = parse_result_file(result_file)
+ log.info("Run took %d s", total_runtime)
+
+ return result
+
+ def get_tags(self) -> dict[str, str]:
+ return {**super().get_tags(), "warp": self.config.asdict()}
+
+
+@base.register_benchmark
+class WarpOneDimParams(base.BenchmarkOneDimMixin, WarpFixedParams):
+ """Run warp with multiple values for a singel dimension"""
diff --git a/galaxy_benchmarker/condor_bridge.py b/galaxy_benchmarker/condor_bridge.py
deleted file mode 100644
index a7569626..00000000
--- a/galaxy_benchmarker/condor_bridge.py
+++ /dev/null
@@ -1,96 +0,0 @@
-import paramiko
-import re
-from typing import List, Dict
-from datetime import datetime
-import json
-import metrics
-
-
-def get_paramiko_client(host, username, key_file):
- key = paramiko.RSAKey.from_private_key_file(key_file)
-
- client = paramiko.SSHClient()
- client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
- client.connect(host, username=username, pkey=key)
- return client
-
-
-def submit_job(client: paramiko.SSHClient, workflow_dir, job_file):
- """
- Submits Condor-Job and returns the ID and a (start, end) of the sub-id range as a Dict.
- """
- stdin, stdout, stderr = client.exec_command("cd {wf_dir}; condor_submit {job} -terse".format(wf_dir=workflow_dir,
- job=job_file))
-
- error = ""
- for err in stderr:
- error += err
-
- if error != "":
- raise ValueError("An error with condor_submit occurred: {error}".format(error=error))
-
- for output in stdout:
- if output.find("ERROR") != -1:
- raise Exception(output)
- job_id = output.split(".")[0]
- id_range = tuple(output.replace("\n", "").split(" - "))
-
- return {
- "id": job_id,
- "range": id_range
- }
-
-
-def get_job_status(client: paramiko.SSHClient, job_id):
- """
- Determines, job-status. If all jobs were run (status="done"), total_jobs and everything else will be 0
- """
- stdin, stdout, stderr = client.exec_command("condor_q {job_id}".format(job_id=job_id))
-
- status = None
- for output in stdout:
- if output.find("jobs;") != -1:
- status = list(map(int, re.findall(r'\d+', output)))
-
- if status is None or len(status) != 7:
- raise Exception("Couldn't parse condor_q")
-
- return {
- "status": "done" if status[0] == 0 or (status[3] == 0 and status[4] == 0) else "running",
- "total_jobs": status[0],
- "completed": status[1],
- "idle": status[3],
- "running": status[4],
- "held": status[5]
- }
-
-
-def get_condor_history(client: paramiko.SSHClient, first_id: float, last_id: float = float("inf")) -> Dict[str, Dict]:
- """
- Returns condor_history as a list of jobs. Returns all job_ids >= first_id
- """
- output_filename = "condor_history_" + str(datetime.now().timestamp()) + ".json"
- stdin, stdout, stderr = client.exec_command("condor_history -backwards -json -since {i} > {filename}"
- .format(i=int(first_id)-1, filename=output_filename))
-
- error = ""
- for err in stderr:
- error += err
-
- if error != "":
- raise ValueError("An error with condor_history occurred: {error}".format(error=error))
-
- # Get output file
- ftp_client = client.open_sftp()
- ftp_client.get(output_filename, "results/"+output_filename)
- ftp_client.close()
- with open("results/"+output_filename) as json_file:
- job_list = json.load(json_file)
-
- result = {}
- for job in job_list:
- job["parsed_job_metrics"] = metrics.parse_condor_job_metrics(job)
- job["id"] = job["GlobalJobId"]
- result[job["id"]] = job
-
- return result
diff --git a/galaxy_benchmarker/destination.py b/galaxy_benchmarker/destination.py
deleted file mode 100644
index 025f20d1..00000000
--- a/galaxy_benchmarker/destination.py
+++ /dev/null
@@ -1,312 +0,0 @@
-"""
-Definition of different destination-types for workflows.
-"""
-from __future__ import annotations
-import ansible_bridge
-import planemo_bridge
-import condor_bridge
-import metrics
-import logging
-import time
-from multiprocessing import Pool, TimeoutError
-from typing import Dict
-from task import BaseTask, AnsiblePlaybookTask, BenchmarkerTask
-from galaxy_bridge import Galaxy
-from bioblend.galaxy import GalaxyInstance
-from jinja2 import Template
-# from workflow import GalaxyWorkflow, CondorWorkflow
-
-log = logging.getLogger("GalaxyBenchmarker")
-
-
-class BaseDestination:
- def __init__(self, name):
- self.name = name
-
- def run_workflow(self, workflow: BaseWorkflow) -> Dict:
- """
- Runs the given workflow on Destination. Should return a Dict describing the result.
- Needs to be implemented by child.
- """
- raise NotImplementedError
-
- def run_task(self, task: BaseTask):
- """
- Runs a given task on the Destination.
- """
- if task is None:
- return
- if type(task) is AnsiblePlaybookTask:
- self.run_ansible_playbook_task(task)
-
- def run_ansible_playbook_task(self, task: AnsiblePlaybookTask):
- raise NotImplementedError
-
- def __str__(self):
- return self.name
-
- __repr__ = __str__
-
-
-class GalaxyDestination(BaseDestination):
- host = ""
- host_user = ""
- ssh_key = ""
- tool_dependency_dir = "/data/share/tools"
- jobs_directory_dir = "/data/share/staging"
- persistence_dir = "/data/share/persisted_data"
- galaxy_user_name = ""
- galaxy_user_key = ""
-
- def __init__(self, name, glx: Galaxy, galaxy_user_name=None, galaxy_user_key=None):
- super().__init__(name)
- self.galaxy = glx
- self.galaxy_user_name = galaxy_user_name
- self.galaxy_user_key = galaxy_user_key
-
- if self.galaxy_user_name is None or self.galaxy_user_key is None:
- self._create_galaxy_destination_user(glx)
-
- def _create_galaxy_destination_user(self, glx):
- """
- Creates a user specifically for this Destination-Instance. This one is later used for routing the jobs
- to the right Pulsar-Server.
- """
- self.galaxy_user_name = str.lower("dest_user_" + self.name)
- _, self.galaxy_user_key = glx.create_user(self.galaxy_user_name)
-
- def run_ansible_playbook_task(self, task: AnsiblePlaybookTask):
- """
- Runs the given AnsiblePlaybookTask on the destination.
- """
- ansible_bridge.run_playbook(task.playbook, self.host, self.host_user, self.ssh_key,
- {"tool_dependency_dir": self.tool_dependency_dir,
- "jobs_directory_dir": self.jobs_directory_dir,
- "persistence_dir": self.persistence_dir})
-
- def get_jobs(self, history_name) -> Dict:
- """
- Get all jobs together with their details from a given history_name.
- """
- glx_instance = self.galaxy.impersonate(user_key=self.galaxy_user_key)
- job_ids = get_job_ids_from_history_name(history_name, glx_instance)
-
- infos = dict()
- for job_id in job_ids:
- infos[job_id] = self.galaxy.instance.jobs.show_job(job_id, full_details=True)
-
- # Get JobMetrics and parse them for future usage in influxDB
- infos[job_id]["job_metrics"] = self.galaxy.instance.jobs.get_metrics(job_id)
- infos[job_id]["parsed_job_metrics"] = metrics.parse_galaxy_job_metrics(infos[job_id]["job_metrics"])
-
- return infos
-
- def run_workflow(self, workflow: GalaxyWorkflow) -> Dict:
- """
- Runs the given workflow on PulsarMQDestination. Returns Dict of the status and
- history_name of the finished workflow.
- """
- log.info("Running workflow '{wf_name}' using Planemo".format(wf_name=workflow.name))
-
- start_time = time.monotonic()
-
- if workflow.timeout is None:
- result = planemo_bridge.run_planemo(self.galaxy, self, workflow.path)
- else:
- # Run inside a Process to enable timeout
- pool = Pool(processes=1)
- pool_result = pool.apply_async(planemo_bridge.run_planemo, (self.galaxy, self, workflow.path))
-
- try:
- result = pool_result.get(timeout=workflow.timeout)
- except TimeoutError:
- log.info("Timeout after {timeout} seconds".format(timeout=workflow.timeout))
- result = {"status": "error"}
-
- result["total_workflow_runtime"] = time.monotonic() - start_time
-
- return result
-
-
-class PulsarMQDestination(GalaxyDestination):
- def __init__(self, name, glx: Galaxy, job_plugin_params: Dict, job_destination_params: Dict, amqp_url="", galaxy_user_name="", galaxy_user_key=""):
- self.amqp_url = amqp_url
- self.job_plugin_params = job_plugin_params
- self.job_destination_params = job_destination_params
- super().__init__(name, glx, galaxy_user_name, galaxy_user_key)
-
-
-class GalaxyCondorDestination(GalaxyDestination):
- def __init__(self, name, glx: Galaxy, job_plugin_params: Dict, job_destination_params: Dict, galaxy_user_name="", galaxy_user_key=""):
- self.job_plugin_params = job_plugin_params
- self.job_destination_params = job_destination_params
- super().__init__(name, glx, galaxy_user_name, galaxy_user_key)
-
-
-class CondorDestination(BaseDestination):
- status_refresh_time = 0.5 # TODO: Figure out, if that timing is to fast
-
- def __init__(self, name, host, host_user, ssh_key, jobs_directory_dir):
- super().__init__(name)
- self.host = host
- self.host_user = host_user
- self.ssh_key = ssh_key
- self.jobs_directory_dir = jobs_directory_dir
-
- def deploy_workflow(self, workflow: CondorWorkflow):
- """
- Deploys the given workflow to the Condor-Server with Ansible.
- """
- log.info("Deploying {workflow} to {destination}".format(workflow=workflow.name, destination=self.name))
- # Use ansible-playbook to upload *.job-file to Condor-Manager
- values = {
- "jobs_directory_dir": self.jobs_directory_dir,
- "workflow_name": workflow.name,
- "workflow_directory_path": workflow.path,
- "condor_user": self.host_user
- }
- ansible_bridge.run_playbook("deploy_condor_workflow.yml", self.host, self.host_user, self.ssh_key, values)
-
- def run_workflow(self, workflow: CondorWorkflow) -> Dict:
- """
- Runs the given workflow on CondorDestination. Returns Dict of ...
- """
- ssh_client = condor_bridge.get_paramiko_client(self.host, self.host_user, self.ssh_key)
-
- remote_workflow_dir = "{jobs_dir}/{wf_name}".format(jobs_dir=self.jobs_directory_dir,
- wf_name=workflow.name)
-
- log.info("Submitting workflow '{wf}' to '{dest}'".format(wf=workflow, dest=self))
- start_time = time.monotonic()
- job_ids = condor_bridge.submit_job(ssh_client, remote_workflow_dir, workflow.job_file)
- submit_time = time.monotonic() - start_time
- log.info("Submitted in {seconds} seconds".format(seconds=submit_time))
-
- # Check every 0.1s if status has changed
- status = "unknown"
- while status != "done":
- try:
- job_status = condor_bridge.get_job_status(ssh_client, job_ids["id"])
- except ValueError as error:
- status = "error"
- log.error("There was an error with run of {workflow}: {error}".format(workflow=self.name,
- error=error))
- break
- status = job_status["status"]
- time.sleep(self.status_refresh_time)
-
- total_workflow_runtime = time.monotonic() - start_time
-
- log.info("Fetching condor_history")
- jobs = condor_bridge.get_condor_history(ssh_client, float(job_ids["id"]), float(job_ids["id"]))
-
- result = {
- "id": job_ids["id"],
- "id_range": job_ids["range"],
- "status": "success" if status == "done" else "error",
- "total_workflow_runtime": total_workflow_runtime,
- "submit_time": submit_time,
- "jobs": jobs
- }
-
- ssh_client.close()
-
- return result
-
-
-def configure_destination(dest_config, glx):
- """
- Initializes and configures a Destination according to the given configuration. Returns the configured Destination.
- """
- # Check, if all set properly
- if "name" not in dest_config:
- raise ValueError("No Destination-Name set! Config: '{config}'".format(config=dest_config))
- if "type" not in dest_config:
- raise ValueError("No Destination-Type set for '{dest}'".format(dest=dest_config["name"]))
- if dest_config["type"] not in ["Galaxy", "PulsarMQ", "Condor", "GalaxyCondor"]:
- raise ValueError("Destination-Type '{type}' not valid".format(type=dest_config["type"]))
-
- job_plugin_params = dict() if "job_plugin_params" not in dest_config else dest_config["job_plugin_params"]
- job_destination_params = dict() if "job_destination_params" not in dest_config else dest_config["job_destination_params"]
-
- galaxy_user_name = None if "galaxy_user_name" not in dest_config else dest_config["galaxy_user_name"]
- galaxy_user_key = None if "galaxy_user_key" not in dest_config else dest_config["galaxy_user_key"]
-
- if dest_config["type"] == "Galaxy":
- destination = GalaxyDestination(dest_config["name"], glx, galaxy_user_name, galaxy_user_key)
-
- if dest_config["type"] == "PulsarMQ":
- destination = PulsarMQDestination(dest_config["name"], glx, job_plugin_params, job_destination_params,
- dest_config["amqp_url"],
- galaxy_user_name, galaxy_user_key)
- if "host" in dest_config:
- destination.host = dest_config["host"]
- destination.host_user = dest_config["host_user"]
- destination.ssh_key = dest_config["ssh_key"]
- destination.tool_dependency_dir = dest_config["tool_dependency_dir"]
-
- if dest_config["type"] == "Condor":
- destination = CondorDestination(dest_config["name"], dest_config["host"], dest_config["host_user"],
- dest_config["ssh_key"], dest_config["jobs_directory_dir"])
- if "status_refresh_time" in dest_config:
- destination.status_refresh_time = dest_config["status_refresh_time"]
-
- if dest_config["type"] == "GalaxyCondor":
- destination = GalaxyCondorDestination(dest_config["name"], glx, job_plugin_params, job_destination_params,
- galaxy_user_name,
- galaxy_user_key)
-
- return destination
-
-
-def create_galaxy_job_conf(glx: Galaxy, destinations: Dict[str, BaseDestination]):
- """
- Creates the job_conf.xml-file for Galaxy using the given Dict (key: dest_name, value dest) and saves it to
- galaxy_files/job_conf.xml.tmp.
- """
- with open('galaxy_files/job_conf.xml') as file_:
- template = Template(file_.read())
-
- pulsar_destinations = list()
- galaxy_condor_destinations = list()
- job_plugin_params = dict()
- job_destination_params = dict()
-
- for dest in destinations.values():
- if type(dest) is PulsarMQDestination:
- pulsar_destinations.append(dest)
- if type(dest) is GalaxyCondorDestination:
- galaxy_condor_destinations.append(dest)
- if issubclass(type(dest), PulsarMQDestination) or issubclass(type(dest), GalaxyCondorDestination):
- job_plugin_params[dest.name] = dest.job_plugin_params
- job_destination_params[dest.name] = dest.job_destination_params
-
- job_conf = template.render(galaxy=glx,
- pulsar_destinations=pulsar_destinations,
- galaxy_condor_destinations=galaxy_condor_destinations,
- job_plugin_params=job_plugin_params,
- job_destination_params=job_destination_params)
-
- with open("galaxy_files/job_conf.xml.tmp", "w") as fh:
- fh.write(job_conf)
-
-
-def get_job_ids_from_history_name(history_name, impersonated_instance: GalaxyInstance):
- """
- For a given history_name return all its associated job-ids. As a history is only accessible from the user it
- was created by, an impersonated_instance is needed.
- """
- histories = impersonated_instance.histories.get_histories(name=history_name)
-
- if len(histories) >= 1:
- history_id = histories[0]["id"]
- dataset_ids = impersonated_instance.histories.show_history(history_id)["state_ids"]["ok"]
- job_ids = list()
-
- for dataset_id in dataset_ids:
- job_ids.append(impersonated_instance.histories.show_dataset(history_id, dataset_id)["creating_job"])
-
- return job_ids
-
- return []
-
diff --git a/galaxy_benchmarker/galaxy_bridge.py b/galaxy_benchmarker/galaxy_bridge.py
deleted file mode 100644
index dba8fad2..00000000
--- a/galaxy_benchmarker/galaxy_bridge.py
+++ /dev/null
@@ -1,93 +0,0 @@
-from bioblend.galaxy import GalaxyInstance
-from typing import Tuple, List
-from workflow import BaseWorkflow, GalaxyWorkflow
-import ansible_bridge
-import planemo_bridge
-import logging
-import string
-import random
-import re
-
-log = logging.getLogger("GalaxyBenchmarker")
-
-
-class Galaxy:
- def __init__(self, url, user_key, shed_install=False,
- ssh_user=None, ssh_key=None, galaxy_root_path=None,
- galaxy_config_dir=None, galaxy_user=None):
- self.url = url
- self.user_key = user_key
- self.shed_install = shed_install
- self.ssh_user = ssh_user
- self.ssh_key = ssh_key
-
- self.galaxy_root_path = galaxy_root_path
- self.galaxy_config_dir = galaxy_config_dir
- self.galaxy_user = galaxy_user
-
- self.instance = GalaxyInstance(url, key=user_key)
-
- def impersonate(self, user=None, user_key=None) -> GalaxyInstance:
- """
- Returns a GalaxyInstance for the given user_key. If user is provided,
- user_key is fetched from Galaxy.
- """
- if user is not None:
- user_id = self.instance.users.get_users(f_name=user)[0]["id"]
- user_key = self.instance.users.get_user_apikey(user_id)
- return GalaxyInstance(self.url, key=user_key)
-
- def create_user(self, username) -> Tuple:
- """
- Creates a new user (if not already created) with username and a random password and returns
- its user_id and api_key as a tuple.
- """
- if len(self.instance.users.get_users(f_name=username)) == 0:
- password = ''.join([random.choice(string.ascii_letters + string.digits) for _ in range(32)])
- self.instance.users.create_local_user(username,
- "{username}@galaxy.uni.andreas-sk.de".format(username=username),
- password)
-
- user_id = self.instance.users.get_users(f_name=username)[0]["id"]
- user_key = self.instance.users.get_user_apikey(user_id)
-
- if user_key == "Not available.":
- user_key = self.instance.users.create_user_apikey(user_id)
-
- return user_id, user_key
-
- def delete_all_histories_for_user(self, user, purge=True):
- """
- Deletes and - if not set otherwise - purges for a given username all its histories.
- """
- impersonated = self.impersonate(user)
- histories = impersonated.histories.get_histories()
-
- for history in histories:
- impersonated.histories.delete_history(history["id"], purge)
-
- def install_tools_for_workflows(self, workflows: List[BaseWorkflow]):
- log.info("Installing all necessary workflow-tools on Galaxy.")
- for workflow in workflows:
- if type(workflow) is GalaxyWorkflow:
- log.info("Installing tools for workflow '{workflow}'".format(workflow=workflow.name))
- planemo_bridge.install_workflow([workflow.path], self.instance)
-
- def deploy_job_conf(self):
- """
- Deploys the job_conf.xml-file to the Galaxy-Server.
- """
- # Hostname parsed from the Galaxy-URL
- host = re.findall("^[a-z][a-z0-9+\-.]*://([a-z0-9\-._~%!$&'()*+,;=]+@)?([a-z0-9\-._~%]+|\[[a-z0-9\-."
- + "_~%!$&'()*+,;=:]+\])", self.url)[0][1]
-
- if None in (self.ssh_user, self.ssh_key, self.galaxy_root_path, self.galaxy_config_dir, self.galaxy_user):
- raise ValueError("ssh_user, ssh_key, galaxy_root_path, galaxy_config_dir, and galaxy_user need "
- "to be set in order to deploy the job_conf.xml-file!")
-
- values = {
- "galaxy_root_path": self.galaxy_root_path,
- "galaxy_config_dir": self.galaxy_config_dir,
- "galaxy_user": self.galaxy_user
- }
- ansible_bridge.run_playbook("prepare_galaxy.yml", host, self.ssh_user, self.ssh_key, values)
diff --git a/galaxy_benchmarker/influxdb_bridge.py b/galaxy_benchmarker/influxdb_bridge.py
deleted file mode 100644
index 1f82cc43..00000000
--- a/galaxy_benchmarker/influxdb_bridge.py
+++ /dev/null
@@ -1,60 +0,0 @@
-from influxdb import InfluxDBClient
-from typing import Dict
-
-
-class InfluxDB:
- def __init__(self, host, port, username, password, db_name):
- self.client = InfluxDBClient(host=host, port=port, username=username, password=password,
- ssl=False, database=db_name, retries=20)
-
- def save_job_metrics(self, tags: Dict, job_results: Dict):
- """
- Saves the parsed job-specific metrics (see metrics.py) to InfluxDB.
- """
- if "parsed_job_metrics" not in job_results:
- return []
-
- json_points = []
-
- for metric in job_results["parsed_job_metrics"].values():
- metric_tags = tags.copy()
-
- if "job_id" in job_results:
- metric_tags["job_id"] = job_results["id"]
- if "tool_id" in job_results:
- metric_tags["tool_id"] = job_results["tool_id"]
-
- if "plugin" in metric:
- metric_tags["plugin"] = metric["plugin"]
-
- json_points.append({
- "measurement": metric["name"],
- "tags": metric_tags,
- "fields": {
- "value": metric["value"]
- }
- })
-
- self.client.write_points(json_points)
-
- def save_workflow_metrics(self, tags: Dict, metrics: Dict):
- """
- Saves the workflow-specific metrics to InfluxDB.
- """
- json_points = []
-
- for metric in metrics.values():
- metric_tags = tags.copy()
-
- if "plugin" in metric:
- metric_tags["plugin"] = metric["plugin"]
-
- json_points.append({
- "measurement": metric["name"],
- "tags": metric_tags,
- "fields": {
- "value": metric["value"]
- }
- })
-
- self.client.write_points(json_points)
diff --git a/galaxy_benchmarker/metrics.py b/galaxy_benchmarker/metrics.py
deleted file mode 100644
index b1fc0866..00000000
--- a/galaxy_benchmarker/metrics.py
+++ /dev/null
@@ -1,140 +0,0 @@
-from typing import List, Dict
-from datetime import datetime
-import logging
-
-log = logging.getLogger("GalaxyBenchmarker")
-
-# All the metrics that can safely be parsed as a float_metric (see parse_galaxy_job_metrics)
-galaxy_float_metrics = {"processor_count", "memtotal", "swaptotal", "runtime_seconds", "memory.stat.pgmajfault",
- "cpu.stat.nr_throttled", "memory.stat.total_rss_huge", "memory.memsw.failcnt",
- "memory.oom_control.under_oom", "memory.kmem.failcnt", "memory.stat.total_pgfault",
- "cpu.stat.nr_periods", "cpuacct.stat.user", "memory.stat.active_file", "memory.stat.mapped_file",
- "memory.stat.rss_huge", "memory.memsw.limit_in_bytes", "memory.use_hierarchy", "memory.stat.cache",
- "memory.stat.hierarchical_memsw_limit", "memory.kmem.tcp.failcnt", "cpu.rt_runtime_us",
- "memory.stat.hierarchical_memory_limit", "memory.stat.total_cache", "memory.kmem.max_usage_in_bytes",
- "cpuacct.usage", "memory.stat.total_pgmajfault", "memory.kmem.usage_in_bytes",
- "memory.stat.inactive_file", "memory.swappiness", "memory.move_charge_at_immigrate",
- "memory.memsw.usage_in_bytes", "cpu.rt_period_us", "memory.stat.inactive_anon", "memory.stat.swap",
- "memory.stat.active_anon", "memory.stat.pgpgin", "memory.stat.total_inactive_file",
- "memory.oom_control.oom_kill_disable", "memory.stat.total_pgpgout", "memory.stat.total_unevictable",
- "memory.kmem.tcp.limit_in_bytes", "memory.stat.pgpgout", "memory.usage_in_bytes",
- "memory.failcnt", "memory.memsw.max_usage_in_bytes", "memory.limit_in_bytes",
- "memory.soft_limit_in_bytes", "memory.kmem.tcp.max_usage_in_bytes", "memory.stat.total_rss",
- "cpu.shares", "memory.stat.total_swap", "memory.stat.rss", "memory.kmem.tcp.usage_in_bytes",
- "cpu.stat.throttled_time", "memory.stat.unevictable", "memory.kmem.limit_in_bytes",
- "cpu.cfs_quota_us", "cpuacct.stat.system", "memory.stat.total_active_anon",
- "memory.max_usage_in_bytes", "memory.stat.total_active_file", "memory.stat.total_mapped_file",
- "cpu.cfs_period_us", "memory.stat.pgfault", "memory.stat.total_pgpgin",
- "memory.stat.total_inactive_anon", "preprocessing_time", "tool_preparation_time",
- "down_collection_time"}
-galaxy_string_metrics = {"cpuacct.usage_percpu"}
-condor_float_metrics = {"NumRestarts", "NumJobRestarts", "JobStatus"}
-condor_string_metrics = {"LastRemoteHost", "GlobalJobId", "Cmd"}
-condor_time_metrics = {"JobStartDate", "JobCurrentStartDate", "CompletionDate"}
-
-
-def parse_galaxy_job_metrics(job_metrics: List) -> Dict[str, Dict]:
- """
- Parses the more or less "raw" metrics from Galaxy, so they can later be ingested by InfluxDB.
- """
- parsed_metrics = {
- "staging_time": {
- "name": "staging_time",
- "type": "float",
- "value": float(0)
- },
- }
-
- jobstatus_queued = jobstatus_running = None
- for metric in job_metrics:
- try:
- if metric["name"] in galaxy_float_metrics:
- parsed_metrics[metric["name"]] = {
- "name": metric["name"],
- "type": "float",
- "plugin": metric["plugin"],
- "value": float(metric["raw_value"])
- }
- if metric["name"] in galaxy_string_metrics:
- parsed_metrics[metric["name"]] = {
- "name": metric["name"],
- "type": "string",
- "plugin": metric["plugin"],
- "value": metric["raw_value"]
- }
- # For calculating the staging time (if the metrics exist)
- if metric["plugin"] == "jobstatus" and metric["name"] == "queued":
- jobstatus_queued = datetime.strptime(metric["value"], "%Y-%m-%d %H:%M:%S.%f")
- if metric["plugin"] == "jobstatus" and metric["name"] == "running":
- jobstatus_running = datetime.strptime(metric["value"], "%Y-%m-%d %H:%M:%S.%f")
- except ValueError as e:
- log.error("Error while trying to parse Galaxy job metrics '{name} = {value}': {error}. Ignoring.."
- .format(error=e, name=metric["name"], value=metric["raw_value"]))
-
- # Calculate staging time
- if jobstatus_queued is not None and jobstatus_running is not None:
- parsed_metrics["staging_time"]["value"] = float((jobstatus_running - jobstatus_queued).seconds +
- (jobstatus_running - jobstatus_queued).microseconds * 0.000001)
-
- return parsed_metrics
-
-
-def parse_condor_job_metrics(job_metrics: Dict) -> Dict[str, Dict]:
- parsed_metrics = {}
-
- for key, value in job_metrics.items():
- try:
- if key in condor_float_metrics:
- parsed_metrics[key] = {
- "name": key,
- "type": "float",
- "plugin": "condor_history",
- "value": float(value)
- }
- if key in condor_string_metrics:
- parsed_metrics[key] = {
- "name": key,
- "type": "string",
- "plugin": "condor_history",
- "value": value
- }
- if key in condor_time_metrics:
- parsed_metrics[key] = {
- "name": key,
- "type": "timestamp",
- "plugin": "condor_history",
- "value": value * 1000
- }
- if key == "JobStatus":
- if value == 1:
- status = "idle"
- elif value == 2:
- status = "running"
- elif value == 3:
- status = "removed"
- elif value == 4:
- status = "success"
- elif value == 5:
- status = "held"
- elif value == 6:
- status = "transferring output"
- else:
- status = "unknown"
- parsed_metrics["job_status"] = {
- "name": "job_status",
- "type": "string",
- "plugin": "condor_history",
- "value": status
- }
- if key == "RemoteWallClockTime":
- parsed_metrics["runtime_seconds"] = {
- "name": "runtime_seconds",
- "type": "float",
- "plugin": "condor_history",
- "value": float(value)
- }
- except ValueError as e:
- log.error("Error while trying to parse Condor job metrics '{key} = {value}': {error}. Ignoring.."
- .format(error=e, key=key, value=value))
-
- return parsed_metrics
diff --git a/galaxy_benchmarker/openstack_bridge.py b/galaxy_benchmarker/openstack_bridge.py
deleted file mode 100644
index 3702eb22..00000000
--- a/galaxy_benchmarker/openstack_bridge.py
+++ /dev/null
@@ -1,48 +0,0 @@
-import novaclient.v2.servers
-import novaclient.client
-from typing import List
-import logging
-
-log = logging.getLogger("GalaxyBenchmarker")
-
-
-class OpenStackCompute:
- def __init__(self, auth_url, compute_endpoint_version, username, password,
- project_id, region_name, user_domain_name):
- self.client = novaclient.client.Client(compute_endpoint_version, username=username, password=password,
- project_id=project_id, auth_url=auth_url,
- user_domain_name=user_domain_name, region_name=region_name)
-
- def get_servers(self, name_contains="") -> List[novaclient.v2.servers.Server]:
- """
- Returns all servers that contain name_contains in their name.
- """
- result = []
- servers = self.client.servers.list()
-
- for server in servers:
- if server.name.find(name_contains) != -1:
- result.append(server)
-
- return result
-
- def reboot_servers(self, servers: List[novaclient.v2.servers.Server], hard=False):
- """
- Reboots the given servers.
- Inspired by: https://gist.github.com/gangwang2/9228989
- """
- reboot_type = "HARD" if hard else "SOFT"
- for server in servers:
- if server.status == 'ACTIVE':
- log.info("Rebooting server {name}".format(name=server.name))
- server.reboot(reboot_type)
-
- def rebuild_servers(self, servers: List[novaclient.v2.servers.Server]):
- """
- Rebuilds the given servers with the current settings (images, cloud-init, etc).
- """
- for server in servers:
- if server.status == 'ACTIVE':
- log.info("Rebuilding server {name}".format(name=server.name))
- image = server.image["id"]
- server.rebuild(image)
diff --git a/galaxy_benchmarker/planemo_bridge.py b/galaxy_benchmarker/planemo_bridge.py
deleted file mode 100644
index 194421f8..00000000
--- a/galaxy_benchmarker/planemo_bridge.py
+++ /dev/null
@@ -1,68 +0,0 @@
-"""
-Bridge between Planemo and GalaxyBenchmarker
-"""
-from __future__ import annotations
-import random
-import bioblend
-import time
-import logging
-import urllib3
-from galaxy_bridge import Galaxy
-# from destination import PulsarMQDestination
-from planemo import options
-from planemo.cli import Context
-from planemo.engine import engine_context
-from planemo.galaxy.test import handle_reports_and_summary
-from planemo.runnable import for_paths
-from planemo.galaxy.workflows import install_shed_repos
-from typing import Dict
-
-log = logging.getLogger("GalaxyBenchmarker")
-
-
-def run_planemo(glx: Galaxy, dest: PulsarMQDestination, workflow_path) -> Dict:
- """
- Runs workflow with Planemo and returns a dict of the status and history_name of the finished workflow.
- """
- return _cli(Context(), [workflow_path], glx, dest.galaxy_user_key)
-
-
-def install_workflow(workflow_path, glx_instance):
- """
- Installs the tools necessary to run a given workflow (given as a path to the workflow).
- """
- runnable = for_paths(workflow_path)[0]
- install_shed_repos(runnable, glx_instance, False)
-
-
-@options.galaxy_target_options()
-@options.galaxy_config_options()
-@options.test_options()
-@options.engine_options()
-def _cli(ctx, paths, glx, user_key, **kwds) -> Dict:
- """
- Run specified tool's tests within Galaxy.
- Returns a dict of the status and history_name of the finished workflow.
- See https://github.com/galaxyproject/planemo/blob/master/planemo/commands/cmd_test.py
- """
- kwds["engine"] = "external_galaxy"
- kwds["shed_install"] = False
- kwds["galaxy_url"] = glx.url
- kwds["galaxy_admin_key"] = glx.user_key
- kwds["history_name"] = "galaxy_benchmarker-" + str(time.time_ns()) + str(random.randrange(0, 99999))
-
- if user_key is not None:
- kwds["galaxy_user_key"] = user_key
-
- runnables = for_paths(paths)
-
- try:
- with engine_context(ctx, **kwds) as engine:
- test_data = engine.test(runnables)
- exit_code = handle_reports_and_summary(ctx, test_data.structured_data, kwds=kwds)
- status = "success" if exit_code == 0 else "error"
- except Exception as e:
- log.error("There was an error: {e}".format(e=e))
- status = "error"
-
- return {"status": status, "history_name": kwds["history_name"]}
diff --git a/galaxy_benchmarker/task.py b/galaxy_benchmarker/task.py
deleted file mode 100644
index 417d9fa3..00000000
--- a/galaxy_benchmarker/task.py
+++ /dev/null
@@ -1,93 +0,0 @@
-from random import randrange
-from typing import Dict
-
-class BaseTask:
- def __init__(self, benchmark):
- self.benchmark = benchmark
-
- def run(self):
- raise NotImplementedError
-
-
-class AnsiblePlaybookTask(BaseTask):
- def __init__(self, benchmark, playbook):
- self.playbook = playbook
- super().__init__(benchmark)
-
- def run(self):
- for destination in self.benchmark.destinations:
- destination.run_ansible_playbook_task(self)
-
- def __str__(self):
- return "Ansible Playbook: " + self.playbook
-
- __repr__ = __str__
-
-
-class BenchmarkerTask(BaseTask):
- def __init__(self, benchmark, name, params=dict()):
- self.name = name
- self.params = params
- super().__init__(benchmark)
-
- def run(self):
- if self.name == "delete_old_histories":
- for destination in self.benchmark.destinations:
- self._delete_old_histories(destination)
- elif self.name == "reboot_openstack_servers":
- self._reboot_openstack_servers()
- elif self.name == "reboot_random_openstack_server":
- self._reboot_random_openstack_server()
- elif self.name == "rebuild_random_openstack_server":
- self._rebuild_random_openstack_server()
- else:
- raise ValueError("{name} is not a valid BenchmarkerTask!".format(name=self.name))
-
- def _delete_old_histories(self, destination):
- destination.galaxy.delete_all_histories_for_user(destination.galaxy_user_name, True)
-
- def _reboot_openstack_servers(self):
- if "name_contains" not in self.params:
- raise ValueError("'name_contains' is needed for rebooting openstack servers")
- reboot_type = self.params["reboot_type"] if "reboot_type" in self.params else "soft"
-
- os = self.benchmark.benchmarker.openstack
- servers = os.get_servers(self.params["name_contains"])
- os.reboot_servers(servers, reboot_type == "hard")
-
- def _reboot_random_openstack_server(self):
- if "name_contains" not in self.params:
- raise ValueError("'name_contains' is needed for rebooting openstack servers")
- reboot_type = self.params["reboot_type"] if "reboot_type" in self.params else "soft"
-
- os = self.benchmark.benchmarker.openstack
- servers = os.get_servers(self.params["name_contains"])
-
- rand_index = randrange(0, len(servers))
- os.reboot_servers([servers[rand_index]], reboot_type == "hard")
-
- def _rebuild_random_openstack_server(self):
- if "name_contains" not in self.params:
- raise ValueError("'name_contains' is needed for rebuilding openstack servers")
-
- os = self.benchmark.benchmarker.openstack
- servers = os.get_servers(self.params["name_contains"])
-
- rand_index = randrange(0, len(servers))
- os.rebuild_servers([servers[rand_index]])
-
- def __str__(self):
- return self.name
-
- __repr__ = __str__
-
-
-def configure_task(task_conf: Dict, benchmark):
- if task_conf["type"] == "AnsiblePlaybook":
- return AnsiblePlaybookTask(benchmark, task_conf["playbook"])
-
- if task_conf["type"] == "BenchmarkerTask":
- params = task_conf["params"] if "params" in task_conf else {}
- return BenchmarkerTask(benchmark, task_conf["name"], params)
-
- raise ValueError("Task type '{type}' not allowed!".format(type=task_conf["type"]))
diff --git a/galaxy_benchmarker/typing.py b/galaxy_benchmarker/typing.py
new file mode 100644
index 00000000..d16c5128
--- /dev/null
+++ b/galaxy_benchmarker/typing.py
@@ -0,0 +1,10 @@
+from typing import Any
+
+# Mapping { name -> configdict }
+NamedConfigDicts = dict[str, dict[str, Any]]
+
+RunName = str
+RunResult = dict[str, str | int | float]
+RunResults = list[RunResult]
+
+BenchmarkResults = dict[RunName, RunResults]
diff --git a/galaxy_benchmarker/utils/__init__.py b/galaxy_benchmarker/utils/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/galaxy_benchmarker/utils/ansible.py b/galaxy_benchmarker/utils/ansible.py
new file mode 100644
index 00000000..6e77e81d
--- /dev/null
+++ b/galaxy_benchmarker/utils/ansible.py
@@ -0,0 +1,158 @@
+from __future__ import annotations
+
+import logging
+import subprocess
+import tempfile
+from pathlib import Path
+from typing import TYPE_CHECKING, Any, Dict, Optional
+
+if TYPE_CHECKING:
+ from galaxy_benchmarker.typing import NamedConfigDicts
+
+log = logging.getLogger(__name__)
+
+LOG_ANSIBLE_OUTPUT = False
+
+_running_playbook: Optional[subprocess.Popen] = None
+
+
+def run_playbook(playbook: Path, host: str, extra_vars: Dict = {}):
+ """Run ansible-playbook with the given parameters. Additional variables
+ can be given in values as a dict.
+ """
+ commands = [
+ "ansible-playbook",
+ str(playbook),
+ "-i",
+ "ansible/inventory",
+ "-e",
+ f"host={host}",
+ ]
+
+ for key, value in extra_vars.items():
+ commands.append("-e")
+ commands.append(f"{key}={value}")
+
+ log.log(
+ logging.INFO if LOG_ANSIBLE_OUTPUT else logging.DEBUG,
+ "Run ansible: %s",
+ commands,
+ )
+
+ with tempfile.TemporaryFile() as cached_output:
+ global _running_playbook
+ process = subprocess.Popen(
+ commands, stdout=subprocess.PIPE, stderr=subprocess.PIPE
+ )
+ _running_playbook = process
+
+ while process.poll() is None:
+ assert process.stdout, "Stdout of ansible subprocess is None"
+ output = process.stdout.readline()
+ cached_output.write(output)
+ if LOG_ANSIBLE_OUTPUT:
+ log.info(output.decode("utf-8"))
+
+ if process.returncode != 0:
+ if not LOG_ANSIBLE_OUTPUT:
+ cached_output.seek(0)
+ for line in cached_output.readlines():
+ log.error(line.decode("utf-8"))
+ raise RuntimeError(
+ f"Ansible exited with non-zero exit code: {process.returncode}"
+ )
+
+
+def stop_playbook(signal: int) -> None:
+ """Stop possible running playbook"""
+ if _running_playbook is None:
+ return
+ _running_playbook.send_signal(signal)
+
+
+_tasks: NamedConfigDicts = {}
+
+
+class AnsibleTask:
+ def __init__(
+ self,
+ playbook: str,
+ playbook_folder: str = "ansible/",
+ name: Optional[str] = None,
+ host: str = "",
+ extra_vars: dict = {},
+ *args,
+ **kwargs,
+ ) -> None:
+ """
+ playbook: Name of the playbook which will be executed
+ playbook_folder: path to the playbook folder
+ name: Displayname used for logging and stuff
+ host: ansible host or host_pattern
+ extra_vars: Default extra_vars used when calling run()
+ """
+ if name:
+ self.name = name
+ else:
+ self.name = playbook.split(".")[0]
+
+ if not playbook:
+ raise ValueError(f"'playbook' property is missing for task {self.name}")
+
+ self.playbook = Path(playbook_folder) / playbook
+ if not self.playbook.is_file():
+ raise ValueError(
+ f"Playbook for task {self.name} is not a vaild file. Path: '{self.playbook}'"
+ )
+
+ if not isinstance(host, str):
+ raise ValueError(
+ f"'host' property has to be of type 'str' for task {self.name}"
+ )
+ self.host = host
+
+ if not isinstance(extra_vars, dict):
+ raise ValueError(
+ f"'extra_vars' property has to be of type 'dict' for task {self.name}"
+ )
+ self.extra_vars = extra_vars
+
+ @staticmethod
+ def register(configs: NamedConfigDicts) -> None:
+ """Register name -> config pairs for later use. Tasks can be referenced by
+ name and will be substituted with the values given here"""
+ global _tasks
+ _tasks = configs
+
+ @staticmethod
+ def from_config(task_config: Any, name: str) -> AnsibleTask:
+ """Create a task from task_config. Config can be:
+ - an object defining the task
+ - a string refering to a task definition
+ """
+ t_name, t_config = name, task_config
+ if isinstance(task_config, str):
+ if task_config not in _tasks:
+ raise ValueError(f"Unknown task reference {task_config}")
+ t_config = _tasks[task_config]
+ t_name = task_config
+
+ if not isinstance(t_config, dict):
+ raise ValueError(
+ f"Expected dict as task config for {t_name}. Received {type(t_config)}"
+ )
+
+ return AnsibleTask(**t_config)
+
+ def run(self) -> None:
+ """Run AnsibleTask on hosts given during creation"""
+ if not self.host:
+ raise ValueError(
+ f"'host' is required, when task is executed through 'run()' ({self.name})"
+ )
+
+ self.run_at(self.host, self.extra_vars)
+
+ def run_at(self, host: str, extra_vars: dict = {}) -> None:
+ """Run AnsibleTask on specified host/hosts"""
+ run_playbook(self.playbook, host, extra_vars)
diff --git a/galaxy_benchmarker/utils/destinations.py b/galaxy_benchmarker/utils/destinations.py
new file mode 100644
index 00000000..e1abea5f
--- /dev/null
+++ b/galaxy_benchmarker/utils/destinations.py
@@ -0,0 +1,30 @@
+import dataclasses
+
+
+@dataclasses.dataclass
+class BenchmarkDestination:
+ host: str = ""
+
+ def __post_init__(self):
+ if not self.host:
+ raise ValueError(f"Property 'host' is missing for BenchmarkDestination")
+
+ @property
+ def name(self):
+ return f"{self.host}"
+
+
+@dataclasses.dataclass
+class PosixBenchmarkDestination(BenchmarkDestination):
+ target_folder: str = ""
+
+ def __post_init__(self):
+ super().__post_init__()
+ if not self.target_folder:
+ raise ValueError(
+ f"Property 'target_folder' is missing for host '{self.host}'"
+ )
+
+ @property
+ def name(self):
+ return f"{self.host}_{self.target_folder.replace('/','_')}"
diff --git a/galaxy_benchmarker/utils/s3.py b/galaxy_benchmarker/utils/s3.py
new file mode 100644
index 00000000..7623f642
--- /dev/null
+++ b/galaxy_benchmarker/utils/s3.py
@@ -0,0 +1,100 @@
+import dataclasses
+import logging
+import os
+import time
+
+import boto3
+
+log = logging.getLogger(__name__)
+
+
+@dataclasses.dataclass
+class S3Config:
+ base_url: str
+ bucket_name: str
+
+ @property
+ def access_key_id(self):
+ access_key = os.getenv("AWS_ACCESS_KEY_ID")
+ if access_key is None:
+ raise ValueError("Missing S3 credentials in env vars: AWS_ACCESS_KEY_ID")
+ return access_key
+
+ @property
+ def secret_access_key(self):
+ secret_key = os.getenv("AWS_SECRET_ACCESS_KEY")
+ if secret_key is None:
+ raise ValueError(
+ "Missing S3 credentials in env vars: AWS_SECRET_ACCESS_KEY"
+ )
+ return secret_key
+
+
+def empty_bucket(config: S3Config) -> None:
+ client = boto3.resource(
+ "s3",
+ aws_access_key_id=config.access_key_id,
+ aws_secret_access_key=config.secret_access_key,
+ endpoint_url=config.base_url,
+ )
+ bucket = client.Bucket(config.bucket_name)
+ bucket.objects.all().delete()
+
+
+def check_bucket_for_files(
+ config: S3Config, expected_num_files: int, expected_size_in_bytes: int, timeout: int
+) -> None:
+ start_time = time.monotonic()
+ num_files = _get_current_num_files(config, expected_size_in_bytes)
+
+ while num_files < expected_num_files:
+ log.info("Currently %d files present", num_files)
+ current_runtime = time.monotonic() - start_time
+ if current_runtime >= timeout:
+ raise RuntimeError("Verification timed out")
+
+ time.sleep(5)
+ num_files = _get_current_num_files(config, expected_size_in_bytes)
+
+
+def _get_current_num_files(config: S3Config, expected_size: int) -> int:
+ client = boto3.client(
+ "s3",
+ aws_access_key_id=config.access_key_id,
+ aws_secret_access_key=config.secret_access_key,
+ endpoint_url=config.base_url,
+ )
+
+ prefix = ""
+ common_pre = None
+
+ # Search for the prefix where the fanout happens
+ ## i.e. /000,/001 -> "/"
+ ## /home/rods/000,/home/rods/000 -> "/home/rods"
+ while True:
+ result = client.list_objects_v2(
+ Bucket=config.bucket_name, Delimiter="/", Prefix=prefix
+ )
+ if "CommonPrefixes" not in result:
+ break
+
+ common_pre = result["CommonPrefixes"]
+ if len(common_pre) == 1:
+ prefix = common_pre[0]["Prefix"]
+ continue
+ break
+
+ if common_pre is None:
+ # Bucket is empty, no prefixes -> No files
+ return 0
+
+ # Each prefix contains 1000 files, except the last one
+ num = (len(common_pre[:-1])) * 1000
+ # Subtract one because prefix 000 only has 999 files
+ num = max(0, num - 1)
+
+ last_prefix = common_pre[-1]["Prefix"]
+ resp = client.list_objects_v2(Bucket=config.bucket_name, Prefix=last_prefix)
+ num += len(list(item for item in resp["Contents"] if item["Size"] == expected_size))
+
+ return num
diff --git a/galaxy_benchmarker/workflow.py b/galaxy_benchmarker/workflow.py
deleted file mode 100644
index 53363576..00000000
--- a/galaxy_benchmarker/workflow.py
+++ /dev/null
@@ -1,75 +0,0 @@
-"""
-Definition of different workflow-types.
-"""
-import os
-import logging
-from typing import Dict
-
-log = logging.getLogger("GalaxyBenchmarker")
-
-
-class BaseWorkflow:
- description = ""
-
- def __init__(self, name, path):
- self.path = path
- self.name = name
-
- def __str__(self):
- return self.name
-
- __repr__ = __str__
-
-
-class GalaxyWorkflow(BaseWorkflow):
- timeout = None
-
- def __init__(self, name, path):
- # Make sure that workflow file exists
- if not os.path.isfile(path):
- raise IOError("Workflow-File at '{path}' in workflow '{wf_name}' could not be found".format(path=path,
- wf_name=name))
- super().__init__(name, path)
-
-
-class CondorWorkflow(BaseWorkflow):
- def __init__(self, name, path, job_file):
- super().__init__(name, path)
-
- # Check, if all workflow-directory exist
- if not os.path.isdir(path):
- raise IOError("Workflow-Directory at '{path}' in workflow '{wf_name}' could not be found"
- .format(path=self.path, wf_name=name))
-
- # Check, if condor-job_file exists
- job_file_path = path + "/" + job_file
- if not os.path.isfile(job_file_path):
- raise IOError("Job-File at '{path}' in workflow '{wf_name}' could not be found".format(path=job_file_path,
- wf_name=name))
- self.job_file = job_file
-
-
-def configure_workflow(wf_config: Dict) -> BaseWorkflow:
- """
- Initializes and configures a Workflow according to the given configuration. Returns the configured Workflow.
- """
- # Check, if all set properly
- if "name" not in wf_config:
- raise ValueError("No Workflow-Name set! Config: '{config}'".format(config=wf_config))
- if "path" not in wf_config:
- raise ValueError("No Workflow-Path set for '{workflow}'".format(workflow=wf_config["name"]))
- if "type" not in wf_config:
- raise ValueError("No Workflow-Type set for '{workflow}'".format(workflow=wf_config["name"]))
- if wf_config["type"] not in ["Galaxy", "Condor"]:
- raise ValueError("Workflow-Type '{type}' not valid".format(type=wf_config["type"]))
-
- if wf_config["type"] == "Galaxy":
- workflow = GalaxyWorkflow(wf_config["name"], wf_config["path"])
- if "description" in wf_config:
- workflow.description = wf_config["description"]
- workflow.timeout = None if "timeout" not in wf_config else wf_config["timeout"]
-
- if wf_config["type"] == "Condor":
- workflow = CondorWorkflow(wf_config["name"], wf_config["path"], wf_config["job_file"])
-
- return workflow
diff --git a/galaxy_files/dynamic_destination.py b/galaxy_files/dynamic_destination.py
deleted file mode 100644
index c51add0a..00000000
--- a/galaxy_files/dynamic_destination.py
+++ /dev/null
@@ -1,6 +0,0 @@
-def dynamic_destination(user):
- username = user.username
- if username.startswith("dest_user_"):
- return username[10:]
-
- return "local"
diff --git a/galaxy_files/job_conf.xml b/galaxy_files/job_conf.xml
deleted file mode 100644
index cf2425a0..00000000
--- a/galaxy_files/job_conf.xml
+++ /dev/null
@@ -1,47 +0,0 @@
-
-
-
-
- {% for destination in pulsar_destinations %}
-
- {{ galaxy.url }}
- {{ destination.amqp_url }}
- {% for key, value in job_plugin_params[destination.name].items() %}
- {{ value }}
- {% endfor %}
-
- {% endfor %}
- {% for destination in galaxy_condor_destinations %}
-
- {% endfor %}
-
-
-
- python
- dynamic_destination
-
-
- {% for destination in pulsar_destinations %}
-
- /data/share/staging
- {{ destination.jobs_directory_dir }}
- {{ destination.persistence_dir }}
- {% for key, value in job_destination_params[destination.name].items() %}
- {{ value }}
- {% endfor %}
- $_JAVA_OPTIONS -Xmx2048M -Xms256m
- {{ galaxy.galaxy_config_dir }}/pulsar_actions.yml
-
- {% endfor %}
- {% for destination in galaxy_condor_destinations %}
-
- {% endfor %}
-
-
-
-
-
-
-
-
-
diff --git a/galaxy_files/job_metrics_conf.xml b/galaxy_files/job_metrics_conf.xml
deleted file mode 100644
index 3ca13b4c..00000000
--- a/galaxy_files/job_metrics_conf.xml
+++ /dev/null
@@ -1,140 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/galaxy_files/job_status.py b/galaxy_files/job_status.py
deleted file mode 100644
index 48f62717..00000000
--- a/galaxy_files/job_status.py
+++ /dev/null
@@ -1,43 +0,0 @@
-import logging
-from sqlalchemy import create_engine
-from sqlalchemy.sql import text
-
-from . import InstrumentPlugin
-from .. import formatting
-
-
-log = logging.getLogger(__name__)
-
-
-class StatusFormatter(formatting.JobMetricFormatter):
-
- def format(self, key, value):
- return key, value
-
-
-class JobStatusPlugin(InstrumentPlugin):
- """ Gather status
- """
- plugin_type = "jobstatus"
- formatter = StatusFormatter()
-
- def __init__(self, **kwargs):
- pass
-
- def job_properties(self, job_id, job_directory):
- return self._get_job_state_history(job_id)
-
- def _get_job_state_history(self, job_id):
- engine = create_engine('postgresql:///galaxy?host=/var/run/postgresql') # TODO: Get it from Galaxy.yml
- with engine.connect() as con:
- query = text("SELECT update_time, state FROM job_state_history WHERE job_id = :job_id order by create_time")
- query_res = con.execute(query, {"job_id": job_id})
-
- job_state_history = dict()
- for res in query_res:
- job_state_history[res[1]] = res[0]
-
- return job_state_history
-
-
-__all__ = ('JobStatusPlugin', )
\ No newline at end of file
diff --git a/galaxy_files/pulsar_actions.yml b/galaxy_files/pulsar_actions.yml
deleted file mode 100644
index 44ef8f59..00000000
--- a/galaxy_files/pulsar_actions.yml
+++ /dev/null
@@ -1,11 +0,0 @@
-paths:
-# - path: /galaxy/data
-# path_types: unstructured
-# action: rewrite
-# source_directory: /srv/galaxy/jobs/**/**/metadata/**.bai
-# destination_directory: /work/galaxy/data
- - path: /cvmfs/data.galaxyproject.org/byhand/dm3/bwa_index_v0.5.9-r16/dm3.fa
- path_types: unstructured
- action: rewrite
- source_directory: /cvmfs/data.galaxyproject.org/byhand/dm3/bwa_index_v0.5.9-r16
- destination_directory: /cvmfs/data.galaxyproject.org/byhand/dm3/bwa_index
diff --git a/galaxy_files/sleep.xml b/galaxy_files/sleep.xml
deleted file mode 100644
index cda47a88..00000000
--- a/galaxy_files/sleep.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
- sleep
-
-
-
-
-
-
-
-
-sleep $time; echo "finished after $time seconds" > $out_file1
-
- Sleep for x seconds. Arbitrary data can be uploaded just for fun..
-
\ No newline at end of file
diff --git a/galaxy_files/staging_time.py b/galaxy_files/staging_time.py
deleted file mode 100644
index 9278383f..00000000
--- a/galaxy_files/staging_time.py
+++ /dev/null
@@ -1,50 +0,0 @@
-"""The module describes the ``uname`` job metrics plugin."""
-from . import InstrumentPlugin
-from .. import formatting
-import os.path
-import logging
-
-log = logging.getLogger(__name__)
-
-
-class StagingTimeFormatter(formatting.JobMetricFormatter):
-
- def format(self, key, value):
- return key, value
-
-
-class StagingTimePlugin(InstrumentPlugin):
- """ Use uname to gather operating system information about remote system
- job is running on. Linux only.
- """
- plugin_type = "staging_time"
- formatter = StagingTimeFormatter()
-
- def __init__(self, **kwargs):
- pass
-
- def pre_execute_instrument(self, job_directory):
- return "touch %s" % self._instrument_file_path(job_directory, "down_collection_time")
-
- def job_properties(self, job_id, job_directory):
- result = {}
-
- preprocess_time_path = self._instrument_file_path(job_directory, "preprocessing_time")
- if os.path.isfile(preprocess_time_path):
- with open(preprocess_time_path) as f:
- result["preprocessing_time"] = f.read()
-
- tool_preparation_time_path = self._instrument_file_path(job_directory, "tool_preparation_time")
- if os.path.isfile(tool_preparation_time_path):
- with open(tool_preparation_time_path) as f:
- result["tool_preparation_time"] = f.read()
-
- collection_time_path = self._instrument_file_path(job_directory, "down_collection_time")
- if os.path.isfile(collection_time_path):
- with open(collection_time_path) as f:
- result["down_collection_time"] = f.read()
-
- return result
-
-
-__all__ = ('StagingTimePlugin', )
\ No newline at end of file
diff --git a/grafana/config.ini b/grafana/config.ini
deleted file mode 100644
index 6a2c0347..00000000
--- a/grafana/config.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[paths]
-provisioning = /etc/grafana/provisioning
\ No newline at end of file
diff --git a/grafana/dashboards/Burst Benchmarks.json b/grafana/dashboards/Burst Benchmarks.json
deleted file mode 100644
index ad112c30..00000000
--- a/grafana/dashboards/Burst Benchmarks.json
+++ /dev/null
@@ -1,1645 +0,0 @@
-{
- "annotations": {
- "list": [
- {
- "builtIn": 1,
- "datasource": "-- Grafana --",
- "enable": true,
- "hide": true,
- "iconColor": "rgba(0, 211, 255, 1)",
- "name": "Annotations & Alerts",
- "type": "dashboard"
- }
- ]
- },
- "editable": true,
- "gnetId": null,
- "graphTooltip": 0,
- "id": 2,
- "iteration": 1563785924621,
- "links": [],
- "panels": [
- {
- "collapsed": false,
- "gridPos": {
- "h": 1,
- "w": 24,
- "x": 0,
- "y": 0
- },
- "id": 35,
- "panels": [],
- "title": "Status",
- "type": "row"
- },
- {
- "aliasColors": {},
- "bars": true,
- "dashLength": 10,
- "dashes": false,
- "datasource": "glx_benchmarker",
- "fill": 1,
- "gridPos": {
- "h": 6,
- "w": 24,
- "x": 0,
- "y": 1
- },
- "id": 39,
- "legend": {
- "alignAsTable": false,
- "avg": false,
- "current": false,
- "hideEmpty": false,
- "hideZero": false,
- "max": false,
- "min": false,
- "show": false,
- "total": false,
- "values": false
- },
- "lines": false,
- "linewidth": 1,
- "links": [],
- "nullPointMode": "null",
- "options": {},
- "percentage": false,
- "pointradius": 2,
- "points": false,
- "renderer": "flot",
- "seriesOverrides": [
- {
- "alias": "/error/",
- "color": "#E02F44"
- },
- {
- "alias": "/success/",
- "color": "#56A64B"
- }
- ],
- "spaceLength": 10,
- "stack": false,
- "steppedLine": false,
- "targets": [
- {
- "alias": "$tag_benchmark_uuid: $col",
- "groupBy": [
- {
- "params": [
- "benchmark_uuid"
- ],
- "type": "tag"
- }
- ],
- "measurement": "workflow_status",
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT success, error FROM (SELECT count(\"value\") as success FROM \"workflow_status\" WHERE (\"value\" = 'success') AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND $timeFilter GROUP BY \"benchmark_uuid\"), (SELECT count(\"value\") as error FROM \"workflow_status\" WHERE (\"value\" = 'error') AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND $timeFilter GROUP BY \"benchmark_uuid\") WHERE $timeFilter GROUP BY benchmark_uuid",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "time_series",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "count"
- }
- ]
- ],
- "tags": [
- {
- "key": "value",
- "operator": "=",
- "value": "error"
- }
- ]
- }
- ],
- "thresholds": [],
- "timeFrom": null,
- "timeRegions": [],
- "timeShift": null,
- "title": "Workflow Status",
- "tooltip": {
- "shared": false,
- "sort": 0,
- "value_type": "individual"
- },
- "type": "graph",
- "xaxis": {
- "buckets": null,
- "mode": "series",
- "name": null,
- "show": true,
- "values": [
- "total"
- ]
- },
- "yaxes": [
- {
- "format": "short",
- "label": null,
- "logBase": 1,
- "max": null,
- "min": null,
- "show": true
- },
- {
- "format": "short",
- "label": null,
- "logBase": 1,
- "max": null,
- "min": null,
- "show": true
- }
- ],
- "yaxis": {
- "align": false,
- "alignLevel": null
- }
- },
- {
- "aliasColors": {},
- "bars": true,
- "dashLength": 10,
- "dashes": false,
- "datasource": "glx_benchmarker",
- "fill": 1,
- "gridPos": {
- "h": 6,
- "w": 24,
- "x": 0,
- "y": 7
- },
- "id": 43,
- "legend": {
- "alignAsTable": false,
- "avg": false,
- "current": false,
- "hideEmpty": false,
- "hideZero": false,
- "max": false,
- "min": false,
- "show": false,
- "total": false,
- "values": false
- },
- "lines": false,
- "linewidth": 1,
- "links": [],
- "nullPointMode": "null",
- "options": {},
- "percentage": false,
- "pointradius": 2,
- "points": false,
- "renderer": "flot",
- "seriesOverrides": [
- {
- "alias": "/error/",
- "color": "#E02F44"
- },
- {
- "alias": "/success/",
- "color": "#56A64B"
- }
- ],
- "spaceLength": 10,
- "stack": false,
- "steppedLine": false,
- "targets": [
- {
- "alias": "$tag_benchmark_uuid: $col",
- "groupBy": [
- {
- "params": [
- "benchmark_uuid"
- ],
- "type": "tag"
- }
- ],
- "measurement": "workflow_status",
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT success, error FROM (SELECT count(\"value\") as success FROM \"job_status\" WHERE (\"value\" = 'success') AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND $timeFilter GROUP BY \"benchmark_uuid\"), (SELECT count(\"value\") as error FROM \"job_status\" WHERE (\"value\" != 'success') AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND $timeFilter GROUP BY \"benchmark_uuid\") WHERE $timeFilter GROUP BY benchmark_uuid",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "time_series",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "count"
- }
- ]
- ],
- "tags": [
- {
- "key": "value",
- "operator": "=",
- "value": "error"
- }
- ]
- }
- ],
- "thresholds": [],
- "timeFrom": null,
- "timeRegions": [],
- "timeShift": null,
- "title": "Job Status",
- "tooltip": {
- "shared": false,
- "sort": 0,
- "value_type": "individual"
- },
- "type": "graph",
- "xaxis": {
- "buckets": null,
- "mode": "series",
- "name": null,
- "show": true,
- "values": [
- "total"
- ]
- },
- "yaxes": [
- {
- "format": "short",
- "label": null,
- "logBase": 1,
- "max": null,
- "min": null,
- "show": true
- },
- {
- "format": "short",
- "label": null,
- "logBase": 1,
- "max": null,
- "min": null,
- "show": true
- }
- ],
- "yaxis": {
- "align": false,
- "alignLevel": null
- }
- },
- {
- "collapsed": false,
- "gridPos": {
- "h": 1,
- "w": 24,
- "x": 0,
- "y": 13
- },
- "id": 25,
- "panels": [],
- "title": "Runtime",
- "type": "row"
- },
- {
- "columns": [],
- "datasource": "glx_benchmarker",
- "fontSize": "100%",
- "gridPos": {
- "h": 16,
- "w": 12,
- "x": 0,
- "y": 14
- },
- "id": 16,
- "links": [],
- "options": {},
- "pageSize": null,
- "scroll": true,
- "showHeader": true,
- "sort": {
- "col": 0,
- "desc": true
- },
- "styles": [
- {
- "alias": "Time",
- "dateFormat": "YYYY-MM-DD HH:mm:ss",
- "pattern": "Time",
- "type": "hidden"
- },
- {
- "alias": "",
- "colorMode": null,
- "colors": [
- "rgba(245, 54, 54, 0.9)",
- "rgba(237, 129, 40, 0.89)",
- "rgba(50, 172, 45, 0.97)"
- ],
- "decimals": 2,
- "pattern": "/.*/",
- "thresholds": [],
- "type": "number",
- "unit": "s"
- }
- ],
- "targets": [
- {
- "alias": "CPU-Time per Workflow",
- "groupBy": [
- {
- "params": [
- "benchmark_uid"
- ],
- "type": "tag"
- },
- {
- "params": [
- "destination_name"
- ],
- "type": "tag"
- },
- {
- "params": [
- "workflow_name"
- ],
- "type": "tag"
- }
- ],
- "measurement": "staging_time",
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT value FROM \"runtime_seconds\" WHERE (\"benchmark_type\" = '') AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND $timeFilter GROUP BY \"destination_name\", \"workflow_name\", \"benchmark_uuid\"",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "table",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "sum"
- }
- ]
- ],
- "tags": [
- {
- "key": "run_type",
- "operator": "=",
- "value": "warm"
- },
- {
- "condition": "AND",
- "key": "benchmark_type",
- "operator": "=",
- "value": ""
- }
- ]
- }
- ],
- "timeFrom": null,
- "timeShift": null,
- "title": "Job runtime_seconds",
- "transform": "table",
- "type": "table"
- },
- {
- "columns": [],
- "datasource": "glx_benchmarker",
- "fontSize": "100%",
- "gridPos": {
- "h": 8,
- "w": 12,
- "x": 12,
- "y": 14
- },
- "id": 19,
- "links": [],
- "options": {},
- "pageSize": null,
- "scroll": true,
- "showHeader": true,
- "sort": {
- "col": 0,
- "desc": true
- },
- "styles": [
- {
- "alias": "Time",
- "dateFormat": "YYYY-MM-DD HH:mm:ss",
- "pattern": "Time",
- "type": "date"
- },
- {
- "alias": "",
- "colorMode": null,
- "colors": [
- "rgba(245, 54, 54, 0.9)",
- "rgba(237, 129, 40, 0.89)",
- "rgba(50, 172, 45, 0.97)"
- ],
- "dateFormat": "YYYY-MM-DD HH:mm:ss",
- "decimals": 2,
- "mappingType": 1,
- "pattern": "(median|mean|max|min)",
- "thresholds": [],
- "type": "number",
- "unit": "s"
- },
- {
- "alias": "",
- "colorMode": null,
- "colors": [
- "rgba(245, 54, 54, 0.9)",
- "rgba(237, 129, 40, 0.89)",
- "rgba(50, 172, 45, 0.97)"
- ],
- "decimals": 2,
- "pattern": "/.*/",
- "thresholds": [],
- "type": "number",
- "unit": "short"
- }
- ],
- "targets": [
- {
- "alias": "$tag_ benchmark_uuid: $tag_workflow_name",
- "groupBy": [],
- "measurement": "runtime_seconds",
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT median(\"value\"), mean(\"value\"), max(\"value\"), min(\"value\") FROM (SELECT value FROM \"runtime_seconds\" WHERE (\"benchmark_type\" = '') AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND $timeFilter GROUP BY \"workflow_name\", \"benchmark_uuid\", \"history_name\") WHERE $timeFilter GROUP BY \"benchmark_uuid\", \"workflow_name\" ",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "table",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- }
- ]
- ],
- "tags": [
- {
- "key": "benchmark_type",
- "operator": "=",
- "value": ""
- }
- ]
- }
- ],
- "timeFrom": null,
- "timeShift": null,
- "title": "Runtime per Benchmark and Workflow",
- "transform": "table",
- "type": "table"
- },
- {
- "aliasColors": {},
- "bars": true,
- "dashLength": 10,
- "dashes": false,
- "datasource": "glx_benchmarker",
- "fill": 1,
- "gridPos": {
- "h": 8,
- "w": 12,
- "x": 12,
- "y": 22
- },
- "id": 30,
- "legend": {
- "avg": false,
- "current": false,
- "max": false,
- "min": false,
- "show": false,
- "total": false,
- "values": false
- },
- "lines": false,
- "linewidth": 1,
- "links": [],
- "nullPointMode": "null",
- "options": {},
- "percentage": false,
- "pointradius": 2,
- "points": false,
- "renderer": "flot",
- "seriesOverrides": [],
- "spaceLength": 10,
- "stack": false,
- "steppedLine": false,
- "targets": [
- {
- "alias": "CPU-Time per Workflow",
- "groupBy": [
- {
- "params": [
- "benchmark_uid"
- ],
- "type": "tag"
- },
- {
- "params": [
- "destination_name"
- ],
- "type": "tag"
- },
- {
- "params": [
- "workflow_name"
- ],
- "type": "tag"
- }
- ],
- "measurement": "staging_time",
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT value FROM \"runtime_seconds\" WHERE (\"benchmark_type\" = '') AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND $timeFilter GROUP BY \"workflow_name\", \"benchmark_uuid\", \"history_name\"",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "time_series",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "sum"
- }
- ]
- ],
- "tags": [
- {
- "key": "run_type",
- "operator": "=",
- "value": "warm"
- },
- {
- "condition": "AND",
- "key": "benchmark_type",
- "operator": "=",
- "value": ""
- }
- ]
- }
- ],
- "thresholds": [],
- "timeFrom": null,
- "timeRegions": [],
- "timeShift": null,
- "title": "Runtime distribution",
- "tooltip": {
- "shared": false,
- "sort": 0,
- "value_type": "individual"
- },
- "type": "graph",
- "xaxis": {
- "buckets": null,
- "mode": "histogram",
- "name": null,
- "show": true,
- "values": []
- },
- "yaxes": [
- {
- "decimals": -1,
- "format": "none",
- "label": "",
- "logBase": 1,
- "max": null,
- "min": null,
- "show": true
- },
- {
- "format": "short",
- "label": null,
- "logBase": 1,
- "max": null,
- "min": null,
- "show": false
- }
- ],
- "yaxis": {
- "align": false,
- "alignLevel": null
- }
- },
- {
- "columns": [],
- "datasource": "glx_benchmarker",
- "fontSize": "100%",
- "gridPos": {
- "h": 16,
- "w": 12,
- "x": 0,
- "y": 30
- },
- "id": 40,
- "links": [],
- "options": {},
- "pageSize": null,
- "scroll": true,
- "showHeader": true,
- "sort": {
- "col": 0,
- "desc": true
- },
- "styles": [
- {
- "alias": "Time",
- "dateFormat": "YYYY-MM-DD HH:mm:ss",
- "pattern": "Time",
- "type": "hidden"
- },
- {
- "alias": "",
- "colorMode": null,
- "colors": [
- "rgba(245, 54, 54, 0.9)",
- "rgba(237, 129, 40, 0.89)",
- "rgba(50, 172, 45, 0.97)"
- ],
- "decimals": 2,
- "pattern": "/.*/",
- "thresholds": [],
- "type": "number",
- "unit": "s"
- }
- ],
- "targets": [
- {
- "alias": "CPU-Time per Workflow",
- "groupBy": [
- {
- "params": [
- "benchmark_uid"
- ],
- "type": "tag"
- },
- {
- "params": [
- "destination_name"
- ],
- "type": "tag"
- },
- {
- "params": [
- "workflow_name"
- ],
- "type": "tag"
- }
- ],
- "measurement": "staging_time",
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT sum(\"value\") FROM \"total_workflow_runtime\" WHERE (\"benchmark_type\" = '') AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND $timeFilter GROUP BY \"destination_name\", \"workflow_name\", \"benchmark_uuid\", \"history_name\"",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "table",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "sum"
- }
- ]
- ],
- "tags": [
- {
- "key": "run_type",
- "operator": "=",
- "value": "warm"
- },
- {
- "condition": "AND",
- "key": "benchmark_type",
- "operator": "=",
- "value": ""
- }
- ]
- }
- ],
- "timeFrom": null,
- "timeShift": null,
- "title": "Total Workflow Runtimes",
- "transform": "table",
- "type": "table"
- },
- {
- "columns": [],
- "datasource": "glx_benchmarker",
- "fontSize": "100%",
- "gridPos": {
- "h": 8,
- "w": 12,
- "x": 12,
- "y": 30
- },
- "id": 41,
- "links": [],
- "options": {},
- "pageSize": null,
- "scroll": true,
- "showHeader": true,
- "sort": {
- "col": 0,
- "desc": true
- },
- "styles": [
- {
- "alias": "Time",
- "dateFormat": "YYYY-MM-DD HH:mm:ss",
- "pattern": "Time",
- "type": "date"
- },
- {
- "alias": "",
- "colorMode": null,
- "colors": [
- "rgba(245, 54, 54, 0.9)",
- "rgba(237, 129, 40, 0.89)",
- "rgba(50, 172, 45, 0.97)"
- ],
- "dateFormat": "YYYY-MM-DD HH:mm:ss",
- "decimals": 2,
- "mappingType": 1,
- "pattern": "(median|mean|max|min)",
- "thresholds": [],
- "type": "number",
- "unit": "s"
- },
- {
- "alias": "",
- "colorMode": null,
- "colors": [
- "rgba(245, 54, 54, 0.9)",
- "rgba(237, 129, 40, 0.89)",
- "rgba(50, 172, 45, 0.97)"
- ],
- "decimals": 2,
- "pattern": "/.*/",
- "thresholds": [],
- "type": "number",
- "unit": "short"
- }
- ],
- "targets": [
- {
- "alias": "$tag_ benchmark_uuid: $tag_workflow_name",
- "groupBy": [],
- "measurement": "runtime_seconds",
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT median(\"sum\"), mean(\"sum\"), max(\"sum\"), min(\"sum\") FROM (SELECT sum(\"value\") FROM \"total_workflow_runtime\" WHERE (\"benchmark_type\" = '') AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND $timeFilter GROUP BY \"workflow_name\", \"benchmark_uuid\", \"history_name\") WHERE $timeFilter GROUP BY \"benchmark_uuid\", \"workflow_name\" ",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "table",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- }
- ]
- ],
- "tags": [
- {
- "key": "benchmark_type",
- "operator": "=",
- "value": ""
- }
- ]
- }
- ],
- "timeFrom": null,
- "timeShift": null,
- "title": "Total Workflow Runtime per Benchmark and Workflow",
- "transform": "table",
- "type": "table"
- },
- {
- "aliasColors": {},
- "bars": true,
- "dashLength": 10,
- "dashes": false,
- "datasource": "glx_benchmarker",
- "fill": 1,
- "gridPos": {
- "h": 8,
- "w": 12,
- "x": 12,
- "y": 38
- },
- "id": 42,
- "legend": {
- "avg": false,
- "current": false,
- "max": false,
- "min": false,
- "show": false,
- "total": false,
- "values": false
- },
- "lines": false,
- "linewidth": 1,
- "links": [],
- "nullPointMode": "null",
- "options": {},
- "percentage": false,
- "pointradius": 2,
- "points": false,
- "renderer": "flot",
- "seriesOverrides": [],
- "spaceLength": 10,
- "stack": false,
- "steppedLine": false,
- "targets": [
- {
- "alias": "CPU-Time per Workflow",
- "groupBy": [
- {
- "params": [
- "benchmark_uid"
- ],
- "type": "tag"
- },
- {
- "params": [
- "destination_name"
- ],
- "type": "tag"
- },
- {
- "params": [
- "workflow_name"
- ],
- "type": "tag"
- }
- ],
- "measurement": "staging_time",
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT sum FROM (SELECT sum(\"value\") FROM \"total_workflow_runtime\" WHERE (\"benchmark_type\" = '') AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND $timeFilter GROUP BY \"destination_name\", \"workflow_name\", \"benchmark_uuid\", \"history_name\")",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "time_series",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "sum"
- }
- ]
- ],
- "tags": [
- {
- "key": "run_type",
- "operator": "=",
- "value": "warm"
- },
- {
- "condition": "AND",
- "key": "benchmark_type",
- "operator": "=",
- "value": ""
- }
- ]
- }
- ],
- "thresholds": [],
- "timeFrom": null,
- "timeRegions": [],
- "timeShift": null,
- "title": "Total Workflow Runtime distribution",
- "tooltip": {
- "shared": false,
- "sort": 0,
- "value_type": "individual"
- },
- "type": "graph",
- "xaxis": {
- "buckets": null,
- "mode": "histogram",
- "name": null,
- "show": true,
- "values": []
- },
- "yaxes": [
- {
- "decimals": -1,
- "format": "none",
- "label": "",
- "logBase": 1,
- "max": null,
- "min": null,
- "show": true
- },
- {
- "format": "short",
- "label": null,
- "logBase": 1,
- "max": null,
- "min": null,
- "show": false
- }
- ],
- "yaxis": {
- "align": false,
- "alignLevel": null
- }
- },
- {
- "collapsed": true,
- "gridPos": {
- "h": 1,
- "w": 24,
- "x": 0,
- "y": 46
- },
- "id": 23,
- "panels": [
- {
- "columns": [],
- "datasource": "glx_benchmarker",
- "fontSize": "100%",
- "gridPos": {
- "h": 13,
- "w": 12,
- "x": 0,
- "y": 47
- },
- "id": 5,
- "links": [],
- "options": {},
- "pageSize": null,
- "scroll": true,
- "showHeader": true,
- "sort": {
- "col": 0,
- "desc": true
- },
- "styles": [
- {
- "alias": "Time",
- "dateFormat": "YYYY-MM-DD HH:mm:ss",
- "pattern": "Time",
- "type": "hidden"
- },
- {
- "alias": "",
- "colorMode": null,
- "colors": [
- "rgba(245, 54, 54, 0.9)",
- "rgba(237, 129, 40, 0.89)",
- "rgba(50, 172, 45, 0.97)"
- ],
- "dateFormat": "YYYY-MM-DD HH:mm:ss",
- "decimals": 2,
- "mappingType": 1,
- "pattern": "sum",
- "thresholds": [],
- "type": "number",
- "unit": "ns"
- },
- {
- "alias": "",
- "colorMode": null,
- "colors": [
- "rgba(245, 54, 54, 0.9)",
- "rgba(237, 129, 40, 0.89)",
- "rgba(50, 172, 45, 0.97)"
- ],
- "decimals": 2,
- "pattern": "/.*/",
- "thresholds": [],
- "type": "number",
- "unit": "s"
- }
- ],
- "targets": [
- {
- "alias": "CPU-Time per Workflow",
- "groupBy": [
- {
- "params": [
- "benchmark_uid"
- ],
- "type": "tag"
- },
- {
- "params": [
- "destination_name"
- ],
- "type": "tag"
- },
- {
- "params": [
- "workflow_name"
- ],
- "type": "tag"
- }
- ],
- "measurement": "cpuacct.usage",
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT sum(\"value\") FROM \"cpuacct.usage\" WHERE (\"benchmark_type\" = '') AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND $timeFilter GROUP BY \"destination_name\", \"workflow_name\", \"benchmark_uuid\", \"history_name\"",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "table",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "sum"
- }
- ]
- ],
- "tags": [
- {
- "key": "run_type",
- "operator": "=",
- "value": "warm"
- },
- {
- "condition": "AND",
- "key": "benchmark_type",
- "operator": "=",
- "value": ""
- }
- ]
- }
- ],
- "timeFrom": null,
- "timeShift": null,
- "title": "CPU-Usage",
- "transform": "table",
- "type": "table"
- },
- {
- "columns": [],
- "datasource": "glx_benchmarker",
- "fontSize": "100%",
- "gridPos": {
- "h": 5,
- "w": 12,
- "x": 12,
- "y": 47
- },
- "id": 31,
- "links": [],
- "options": {},
- "pageSize": null,
- "scroll": true,
- "showHeader": true,
- "sort": {
- "col": 0,
- "desc": true
- },
- "styles": [
- {
- "alias": "Time",
- "dateFormat": "YYYY-MM-DD HH:mm:ss",
- "pattern": "Time",
- "type": "hidden"
- },
- {
- "alias": "",
- "colorMode": null,
- "colors": [
- "rgba(245, 54, 54, 0.9)",
- "rgba(237, 129, 40, 0.89)",
- "rgba(50, 172, 45, 0.97)"
- ],
- "decimals": 2,
- "pattern": "/.*/",
- "thresholds": [],
- "type": "number",
- "unit": "ns"
- }
- ],
- "targets": [
- {
- "alias": "CPU-Time per Workflow",
- "groupBy": [
- {
- "params": [
- "benchmark_uid"
- ],
- "type": "tag"
- },
- {
- "params": [
- "destination_name"
- ],
- "type": "tag"
- },
- {
- "params": [
- "workflow_name"
- ],
- "type": "tag"
- }
- ],
- "measurement": "cpuacct.usage",
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT median(\"sum\"), mean(\"sum\"), max(\"sum\"), min(\"sum\") FROM (SELECT sum(\"value\") FROM \"cpuacct.usage\" WHERE (\"benchmark_type\" = '') AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND $timeFilter GROUP BY \"destination_name\", \"workflow_name\", \"benchmark_uuid\", \"history_name\") WHERE $timeFilter GROUP BY \"benchmark_uuid\", \"workflow_name\" ",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "table",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "sum"
- }
- ]
- ],
- "tags": [
- {
- "key": "run_type",
- "operator": "=",
- "value": "warm"
- },
- {
- "condition": "AND",
- "key": "benchmark_type",
- "operator": "=",
- "value": ""
- }
- ]
- }
- ],
- "timeFrom": null,
- "timeShift": null,
- "title": "CPU-Usage per Benchmark and Workflow",
- "transform": "table",
- "type": "table"
- }
- ],
- "title": "CPU Time",
- "type": "row"
- },
- {
- "collapsed": true,
- "gridPos": {
- "h": 1,
- "w": 24,
- "x": 0,
- "y": 47
- },
- "id": 21,
- "panels": [
- {
- "columns": [],
- "datasource": "glx_benchmarker",
- "fontSize": "100%",
- "gridPos": {
- "h": 13,
- "w": 12,
- "x": 0,
- "y": 61
- },
- "id": 29,
- "links": [],
- "options": {},
- "pageSize": null,
- "scroll": true,
- "showHeader": true,
- "sort": {
- "col": 0,
- "desc": true
- },
- "styles": [
- {
- "alias": "Time",
- "dateFormat": "YYYY-MM-DD HH:mm:ss",
- "pattern": "Time",
- "type": "date"
- },
- {
- "alias": "",
- "colorMode": null,
- "colors": [
- "rgba(245, 54, 54, 0.9)",
- "rgba(237, 129, 40, 0.89)",
- "rgba(50, 172, 45, 0.97)"
- ],
- "decimals": 2,
- "pattern": "/.*/",
- "thresholds": [],
- "type": "number",
- "unit": "short"
- }
- ],
- "targets": [
- {
- "groupBy": [
- {
- "params": [
- "$__interval"
- ],
- "type": "time"
- },
- {
- "params": [
- "null"
- ],
- "type": "fill"
- }
- ],
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT sum(\"value\") FROM \"staging_time\" WHERE (\"benchmark_type\" = '') AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND $timeFilter GROUP BY \"destination_name\", \"workflow_name\", \"benchmark_uuid\", \"history_name\"",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "table",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "mean"
- }
- ]
- ],
- "tags": []
- }
- ],
- "timeFrom": null,
- "timeShift": null,
- "title": "Staging-Time per Workflow-Run",
- "transform": "table",
- "type": "table"
- },
- {
- "columns": [],
- "datasource": "glx_benchmarker",
- "fontSize": "100%",
- "gridPos": {
- "h": 5,
- "w": 12,
- "x": 12,
- "y": 61
- },
- "id": 32,
- "links": [],
- "options": {},
- "pageSize": null,
- "scroll": true,
- "showHeader": true,
- "sort": {
- "col": 0,
- "desc": true
- },
- "styles": [
- {
- "alias": "Time",
- "dateFormat": "YYYY-MM-DD HH:mm:ss",
- "pattern": "Time",
- "type": "hidden"
- },
- {
- "alias": "",
- "colorMode": null,
- "colors": [
- "rgba(245, 54, 54, 0.9)",
- "rgba(237, 129, 40, 0.89)",
- "rgba(50, 172, 45, 0.97)"
- ],
- "decimals": 2,
- "pattern": "/.*/",
- "thresholds": [],
- "type": "number",
- "unit": "s"
- }
- ],
- "targets": [
- {
- "alias": "CPU-Time per Workflow",
- "groupBy": [
- {
- "params": [
- "benchmark_uid"
- ],
- "type": "tag"
- },
- {
- "params": [
- "destination_name"
- ],
- "type": "tag"
- },
- {
- "params": [
- "workflow_name"
- ],
- "type": "tag"
- }
- ],
- "measurement": "staging_time",
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT median(\"sum\"), mean(\"sum\"), max(\"sum\"), min(\"sum\") FROM (SELECT sum(\"value\") FROM \"staging_time\" WHERE (\"benchmark_type\" = '') AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND $timeFilter GROUP BY \"workflow_name\", \"benchmark_uuid\", \"history_name\") WHERE $timeFilter GROUP BY \"benchmark_uuid\", \"workflow_name\" ",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "table",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "sum"
- }
- ]
- ],
- "tags": [
- {
- "key": "run_type",
- "operator": "=",
- "value": "warm"
- },
- {
- "condition": "AND",
- "key": "benchmark_type",
- "operator": "=",
- "value": ""
- }
- ]
- }
- ],
- "timeFrom": null,
- "timeShift": null,
- "title": "Staging-Time per Benchmark and Workflow",
- "transform": "table",
- "type": "table"
- },
- {
- "aliasColors": {},
- "bars": true,
- "dashLength": 10,
- "dashes": false,
- "datasource": "glx_benchmarker",
- "fill": 1,
- "gridPos": {
- "h": 8,
- "w": 12,
- "x": 12,
- "y": 66
- },
- "id": 33,
- "legend": {
- "avg": false,
- "current": false,
- "max": false,
- "min": false,
- "show": false,
- "total": false,
- "values": false
- },
- "lines": false,
- "linewidth": 1,
- "links": [],
- "nullPointMode": "null",
- "options": {},
- "percentage": false,
- "pointradius": 2,
- "points": false,
- "renderer": "flot",
- "seriesOverrides": [],
- "spaceLength": 10,
- "stack": false,
- "steppedLine": false,
- "targets": [
- {
- "alias": "CPU-Time per Workflow",
- "groupBy": [
- {
- "params": [
- "benchmark_uid"
- ],
- "type": "tag"
- },
- {
- "params": [
- "destination_name"
- ],
- "type": "tag"
- },
- {
- "params": [
- "workflow_name"
- ],
- "type": "tag"
- }
- ],
- "measurement": "staging_time",
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT sum FROM (SELECT sum(\"value\") FROM \"staging_time\" WHERE (\"benchmark_type\" = '') AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND $timeFilter GROUP BY \"destination_name\", \"workflow_name\", \"benchmark_uuid\", \"history_name\")",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "time_series",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "sum"
- }
- ]
- ],
- "tags": [
- {
- "key": "run_type",
- "operator": "=",
- "value": "warm"
- },
- {
- "condition": "AND",
- "key": "benchmark_type",
- "operator": "=",
- "value": ""
- }
- ]
- }
- ],
- "thresholds": [],
- "timeFrom": null,
- "timeRegions": [],
- "timeShift": null,
- "title": "Staging-Time distribution",
- "tooltip": {
- "shared": false,
- "sort": 0,
- "value_type": "individual"
- },
- "type": "graph",
- "xaxis": {
- "buckets": null,
- "mode": "histogram",
- "name": null,
- "show": true,
- "values": []
- },
- "yaxes": [
- {
- "format": "none",
- "label": null,
- "logBase": 1,
- "max": null,
- "min": null,
- "show": true
- },
- {
- "format": "short",
- "label": null,
- "logBase": 1,
- "max": null,
- "min": null,
- "show": false
- }
- ],
- "yaxis": {
- "align": false,
- "alignLevel": null
- }
- }
- ],
- "title": "Staging Time",
- "type": "row"
- }
- ],
- "refresh": false,
- "schemaVersion": 18,
- "style": "dark",
- "tags": [],
- "templating": {
- "list": [
- {
- "allValue": null,
- "current": {
- "text": "CondorBurstBenchmark_2019-07-23 12:20:14.249471",
- "value": [
- "CondorBurstBenchmark_2019-07-23 12:20:14.249471"
- ]
- },
- "datasource": "glx_benchmarker",
- "definition": "SELECT benchmark_uuid , value FROM \"runtime_seconds\" WHERE \"benchmark_type\" = ''",
- "hide": 0,
- "includeAll": true,
- "label": "Benchmark UUID",
- "multi": true,
- "name": "selected_benchmark_uuid",
- "options": [],
- "query": "SELECT benchmark_uuid , value FROM \"runtime_seconds\" WHERE \"benchmark_type\" = ''",
- "refresh": 2,
- "regex": "",
- "skipUrlSync": false,
- "sort": 4,
- "tagValuesQuery": "",
- "tags": [],
- "tagsQuery": "",
- "type": "query",
- "useTags": false
- }
- ]
- },
- "time": {
- "from": "now-24h",
- "to": "now"
- },
- "timepicker": {
- "refresh_intervals": [
- "5s",
- "10s",
- "30s",
- "1m",
- "5m",
- "15m",
- "30m",
- "1h",
- "2h",
- "1d"
- ],
- "time_options": [
- "5m",
- "15m",
- "1h",
- "6h",
- "12h",
- "24h",
- "2d",
- "7d",
- "30d"
- ]
- },
- "timezone": "",
- "title": "Condor Benchmark",
- "uid": "P1T8PsnWk",
- "version": 10
-}
\ No newline at end of file
diff --git a/grafana/dashboards/Cold vs Warm Benchmarks.json b/grafana/dashboards/Cold vs Warm Benchmarks.json
deleted file mode 100644
index c904cf23..00000000
--- a/grafana/dashboards/Cold vs Warm Benchmarks.json
+++ /dev/null
@@ -1,1825 +0,0 @@
-{
- "annotations": {
- "list": [
- {
- "builtIn": 1,
- "datasource": "-- Grafana --",
- "enable": true,
- "hide": true,
- "iconColor": "rgba(0, 211, 255, 1)",
- "name": "Annotations & Alerts",
- "type": "dashboard"
- }
- ]
- },
- "editable": true,
- "gnetId": null,
- "graphTooltip": 0,
- "id": 6,
- "iteration": 1564648082706,
- "links": [],
- "panels": [
- {
- "collapsed": false,
- "gridPos": {
- "h": 1,
- "w": 24,
- "x": 0,
- "y": 0
- },
- "id": 21,
- "panels": [],
- "title": "Runtime",
- "type": "row"
- },
- {
- "columns": [],
- "datasource": "glx_benchmarker",
- "fontSize": "100%",
- "gridPos": {
- "h": 8,
- "w": 12,
- "x": 0,
- "y": 1
- },
- "id": 23,
- "links": [],
- "options": {},
- "pageSize": null,
- "scroll": true,
- "showHeader": true,
- "sort": {
- "col": 0,
- "desc": true
- },
- "styles": [
- {
- "alias": "Time",
- "dateFormat": "YYYY-MM-DD HH:mm:ss",
- "pattern": "Time",
- "type": "date"
- },
- {
- "alias": "",
- "colorMode": null,
- "colors": [
- "rgba(245, 54, 54, 0.9)",
- "rgba(237, 129, 40, 0.89)",
- "rgba(50, 172, 45, 0.97)"
- ],
- "decimals": 2,
- "pattern": "/.*/",
- "thresholds": [],
- "type": "number",
- "unit": "short"
- }
- ],
- "targets": [
- {
- "groupBy": [
- {
- "params": [
- "$__interval"
- ],
- "type": "time"
- },
- {
- "params": [
- "null"
- ],
- "type": "fill"
- }
- ],
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT sum(\"value\") FROM \"runtime_seconds\" WHERE (\"benchmark_type\" = '' AND \"run_type\" = 'cold' AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND \"workflow_name\" =~ /$selected_workflow_name/) AND $timeFilter GROUP BY \"destination_name\", \"workflow_name\", \"benchmark_uuid\", \"history_name\"",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "table",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "mean"
- }
- ]
- ],
- "tags": []
- }
- ],
- "timeFrom": null,
- "timeShift": null,
- "title": "Runtime for Cold-Run",
- "transform": "table",
- "type": "table"
- },
- {
- "columns": [],
- "datasource": "glx_benchmarker",
- "fontSize": "100%",
- "gridPos": {
- "h": 8,
- "w": 12,
- "x": 12,
- "y": 1
- },
- "id": 24,
- "links": [],
- "options": {},
- "pageSize": null,
- "scroll": true,
- "showHeader": true,
- "sort": {
- "col": 0,
- "desc": true
- },
- "styles": [
- {
- "alias": "Time",
- "dateFormat": "YYYY-MM-DD HH:mm:ss",
- "pattern": "Time",
- "type": "date"
- },
- {
- "alias": "",
- "colorMode": null,
- "colors": [
- "rgba(245, 54, 54, 0.9)",
- "rgba(237, 129, 40, 0.89)",
- "rgba(50, 172, 45, 0.97)"
- ],
- "decimals": 2,
- "pattern": "/.*/",
- "thresholds": [],
- "type": "number",
- "unit": "short"
- }
- ],
- "targets": [
- {
- "groupBy": [
- {
- "params": [
- "$__interval"
- ],
- "type": "time"
- },
- {
- "params": [
- "null"
- ],
- "type": "fill"
- }
- ],
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT sum(\"value\") FROM \"runtime_seconds\" WHERE (\"benchmark_type\" = '' AND \"run_type\" = 'warm' AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND \"workflow_name\" =~ /$selected_workflow_name/) AND $timeFilter GROUP BY \"destination_name\", \"workflow_name\", \"benchmark_uuid\", \"history_name\"",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "table",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "mean"
- }
- ]
- ],
- "tags": []
- }
- ],
- "timeFrom": null,
- "timeShift": null,
- "title": "Runtime for Warm-Run",
- "transform": "table",
- "type": "table"
- },
- {
- "columns": [],
- "datasource": "glx_benchmarker",
- "fontSize": "100%",
- "gridPos": {
- "h": 6,
- "w": 12,
- "x": 0,
- "y": 9
- },
- "id": 26,
- "links": [],
- "options": {},
- "pageSize": null,
- "scroll": true,
- "showHeader": true,
- "sort": {
- "col": 0,
- "desc": true
- },
- "styles": [
- {
- "alias": "Time",
- "dateFormat": "YYYY-MM-DD HH:mm:ss",
- "pattern": "Time",
- "type": "hidden"
- },
- {
- "alias": "",
- "colorMode": null,
- "colors": [
- "rgba(245, 54, 54, 0.9)",
- "rgba(237, 129, 40, 0.89)",
- "rgba(50, 172, 45, 0.97)"
- ],
- "decimals": 2,
- "pattern": "/.*/",
- "thresholds": [],
- "type": "number",
- "unit": "short"
- }
- ],
- "targets": [
- {
- "groupBy": [
- {
- "params": [
- "$__interval"
- ],
- "type": "time"
- },
- {
- "params": [
- "null"
- ],
- "type": "fill"
- }
- ],
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT mean(\"sum\") FROM (SELECT sum(\"value\") FROM \"runtime_seconds\" WHERE (\"benchmark_type\" = '' AND \"run_type\" = 'warm' AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND \"workflow_name\" =~ /$selected_workflow_name/) AND $timeFilter GROUP BY \"destination_name\", \"workflow_name\", \"benchmark_uuid\", \"history_name\") GROUP BY benchmark_uuid, workflow_name",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "table",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "mean"
- }
- ]
- ],
- "tags": []
- }
- ],
- "timeFrom": null,
- "timeShift": null,
- "title": "Average Runtime for Warm-Run",
- "transform": "table",
- "type": "table"
- },
- {
- "columns": [],
- "datasource": "glx_benchmarker",
- "fontSize": "100%",
- "gridPos": {
- "h": 6,
- "w": 12,
- "x": 12,
- "y": 9
- },
- "id": 25,
- "links": [],
- "options": {},
- "pageSize": null,
- "scroll": true,
- "showHeader": true,
- "sort": {
- "col": 0,
- "desc": true
- },
- "styles": [
- {
- "alias": "Time",
- "dateFormat": "YYYY-MM-DD HH:mm:ss",
- "pattern": "Time",
- "type": "hidden"
- },
- {
- "alias": "",
- "colorMode": null,
- "colors": [
- "rgba(245, 54, 54, 0.9)",
- "rgba(237, 129, 40, 0.89)",
- "rgba(50, 172, 45, 0.97)"
- ],
- "decimals": 2,
- "pattern": "/.*/",
- "thresholds": [],
- "type": "number",
- "unit": "short"
- }
- ],
- "targets": [
- {
- "groupBy": [
- {
- "params": [
- "$__interval"
- ],
- "type": "time"
- },
- {
- "params": [
- "null"
- ],
- "type": "fill"
- }
- ],
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT mean(\"sum\") FROM (SELECT sum(\"value\") FROM \"runtime_seconds\" WHERE (\"benchmark_type\" = '' AND \"run_type\" = 'cold' AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND \"workflow_name\" =~ /$selected_workflow_name/) AND $timeFilter GROUP BY \"destination_name\", \"workflow_name\", \"benchmark_uuid\", \"history_name\") GROUP BY benchmark_uuid, workflow_name",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "table",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "mean"
- }
- ]
- ],
- "tags": []
- }
- ],
- "timeFrom": null,
- "timeShift": null,
- "title": "Average Runtime for Cold-Run",
- "transform": "table",
- "type": "table"
- },
- {
- "aliasColors": {},
- "bars": true,
- "dashLength": 10,
- "dashes": false,
- "datasource": "glx_benchmarker",
- "fill": 1,
- "gridPos": {
- "h": 8,
- "w": 24,
- "x": 0,
- "y": 15
- },
- "id": 28,
- "legend": {
- "avg": false,
- "current": false,
- "max": false,
- "min": false,
- "show": false,
- "total": false,
- "values": false
- },
- "lines": false,
- "linewidth": 1,
- "links": [],
- "nullPointMode": "null",
- "options": {},
- "paceLength": 10,
- "percentage": false,
- "pointradius": 2,
- "points": false,
- "renderer": "flot",
- "seriesOverrides": [],
- "spaceLength": 10,
- "stack": false,
- "steppedLine": false,
- "targets": [
- {
- "alias": "Cold $tag_workflow_name",
- "groupBy": [
- {
- "params": [
- "$__interval"
- ],
- "type": "time"
- },
- {
- "params": [
- "null"
- ],
- "type": "fill"
- }
- ],
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT mean(\"sum\") FROM (SELECT sum(\"value\") FROM \"runtime_seconds\" WHERE (\"benchmark_type\" = '' AND \"run_type\" = 'cold' AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND \"workflow_name\" =~ /$selected_workflow_name/) AND $timeFilter GROUP BY \"destination_name\", \"workflow_name\", \"benchmark_uuid\", \"history_name\") WHERE $timeFilter GROUP BY benchmark_uuid, workflow_name",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "time_series",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "mean"
- }
- ]
- ],
- "tags": []
- },
- {
- "alias": "Warm $tag_workflow_name",
- "groupBy": [
- {
- "params": [
- "$__interval"
- ],
- "type": "time"
- },
- {
- "params": [
- "null"
- ],
- "type": "fill"
- }
- ],
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT mean(\"sum\") FROM (SELECT sum(\"value\") FROM \"runtime_seconds\" WHERE (\"benchmark_type\" = '' AND \"run_type\" = 'warm' AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND \"workflow_name\" =~ /$selected_workflow_name/) AND $timeFilter GROUP BY \"destination_name\", \"workflow_name\", \"benchmark_uuid\", \"history_name\") WHERE $timeFilter GROUP BY benchmark_uuid, workflow_name",
- "rawQuery": true,
- "refId": "B",
- "resultFormat": "time_series",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "mean"
- }
- ]
- ],
- "tags": []
- }
- ],
- "thresholds": [],
- "timeFrom": null,
- "timeRegions": [],
- "timeShift": null,
- "title": "Average Runtime Cold vs Warm per Workflow",
- "tooltip": {
- "shared": false,
- "sort": 0,
- "value_type": "individual"
- },
- "type": "graph",
- "xaxis": {
- "buckets": null,
- "mode": "series",
- "name": null,
- "show": true,
- "values": [
- "total"
- ]
- },
- "yaxes": [
- {
- "format": "s",
- "label": null,
- "logBase": 1,
- "max": null,
- "min": "0",
- "show": true
- },
- {
- "format": "short",
- "label": null,
- "logBase": 1,
- "max": null,
- "min": null,
- "show": false
- }
- ],
- "yaxis": {
- "align": false,
- "alignLevel": null
- }
- },
- {
- "collapsed": false,
- "gridPos": {
- "h": 1,
- "w": 24,
- "x": 0,
- "y": 23
- },
- "id": 19,
- "panels": [],
- "title": "CPU Time",
- "type": "row"
- },
- {
- "columns": [],
- "datasource": "glx_benchmarker",
- "description": "",
- "fontSize": "100%",
- "gridPos": {
- "h": 8,
- "w": 12,
- "x": 0,
- "y": 24
- },
- "id": 2,
- "links": [],
- "options": {},
- "pageSize": null,
- "scroll": true,
- "showHeader": true,
- "sort": {
- "col": 0,
- "desc": true
- },
- "styles": [
- {
- "alias": "Time",
- "dateFormat": "YYYY-MM-DD HH:mm:ss",
- "pattern": "Time",
- "type": "hidden"
- },
- {
- "alias": "",
- "colorMode": null,
- "colors": [
- "rgba(245, 54, 54, 0.9)",
- "rgba(237, 129, 40, 0.89)",
- "rgba(50, 172, 45, 0.97)"
- ],
- "decimals": 2,
- "pattern": "/.*/",
- "thresholds": [],
- "type": "number",
- "unit": "s"
- }
- ],
- "targets": [
- {
- "alias": "CPU-Time per Workflow",
- "groupBy": [
- {
- "params": [
- "benchmark_uid"
- ],
- "type": "tag"
- },
- {
- "params": [
- "destination_name"
- ],
- "type": "tag"
- },
- {
- "params": [
- "workflow_name"
- ],
- "type": "tag"
- }
- ],
- "measurement": "cpuacct.usage",
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT sum(\"value\") FROM \"cpuacct.usage\" WHERE (\"run_type\" = 'cold') AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND \"workflow_name\" =~ /$selected_workflow_name/ AND $timeFilter GROUP BY \"destination_name\", \"workflow_name\", \"benchmark_uuid\", \"history_name\"",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "table",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "sum"
- }
- ]
- ],
- "tags": [
- {
- "key": "run_type",
- "operator": "=",
- "value": "cold"
- }
- ]
- }
- ],
- "timeFrom": null,
- "timeShift": null,
- "title": "CPU-Usage for Cold-Run",
- "transform": "table",
- "type": "table"
- },
- {
- "columns": [],
- "datasource": "glx_benchmarker",
- "fontSize": "100%",
- "gridPos": {
- "h": 8,
- "w": 12,
- "x": 12,
- "y": 24
- },
- "id": 5,
- "links": [],
- "options": {},
- "pageSize": null,
- "scroll": true,
- "showHeader": true,
- "sort": {
- "col": 0,
- "desc": true
- },
- "styles": [
- {
- "alias": "Time",
- "dateFormat": "YYYY-MM-DD HH:mm:ss",
- "pattern": "Time",
- "type": "hidden"
- },
- {
- "alias": "",
- "colorMode": null,
- "colors": [
- "rgba(245, 54, 54, 0.9)",
- "rgba(237, 129, 40, 0.89)",
- "rgba(50, 172, 45, 0.97)"
- ],
- "decimals": 2,
- "pattern": "/.*/",
- "thresholds": [],
- "type": "number",
- "unit": "s"
- }
- ],
- "targets": [
- {
- "alias": "CPU-Time per Workflow",
- "groupBy": [
- {
- "params": [
- "benchmark_uid"
- ],
- "type": "tag"
- },
- {
- "params": [
- "destination_name"
- ],
- "type": "tag"
- },
- {
- "params": [
- "workflow_name"
- ],
- "type": "tag"
- }
- ],
- "measurement": "cpuacct.usage",
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT sum(\"value\") FROM \"cpuacct.usage\" WHERE (\"run_type\" = 'warm' AND \"benchmark_type\" = '' AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND \"workflow_name\" =~ /$selected_workflow_name/) AND $timeFilter GROUP BY \"destination_name\", \"workflow_name\", \"benchmark_uuid\", \"history_name\"",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "table",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "sum"
- }
- ]
- ],
- "tags": [
- {
- "key": "run_type",
- "operator": "=",
- "value": "warm"
- },
- {
- "condition": "AND",
- "key": "benchmark_type",
- "operator": "=",
- "value": ""
- }
- ]
- }
- ],
- "timeFrom": null,
- "timeShift": null,
- "title": "CPU-Usage for Warm-Run",
- "transform": "table",
- "type": "table"
- },
- {
- "columns": [],
- "datasource": "glx_benchmarker",
- "description": "",
- "fontSize": "100%",
- "gridPos": {
- "h": 5,
- "w": 12,
- "x": 0,
- "y": 32
- },
- "id": 14,
- "links": [],
- "options": {},
- "pageSize": null,
- "scroll": true,
- "showHeader": true,
- "sort": {
- "col": 0,
- "desc": true
- },
- "styles": [
- {
- "alias": "Time",
- "dateFormat": "YYYY-MM-DD HH:mm:ss",
- "pattern": "Time",
- "type": "hidden"
- },
- {
- "alias": "",
- "colorMode": null,
- "colors": [
- "rgba(245, 54, 54, 0.9)",
- "rgba(237, 129, 40, 0.89)",
- "rgba(50, 172, 45, 0.97)"
- ],
- "decimals": 2,
- "pattern": "/.*/",
- "thresholds": [],
- "type": "number",
- "unit": "s"
- }
- ],
- "targets": [
- {
- "alias": "CPU-Time per Workflow",
- "groupBy": [
- {
- "params": [
- "benchmark_uid"
- ],
- "type": "tag"
- },
- {
- "params": [
- "destination_name"
- ],
- "type": "tag"
- },
- {
- "params": [
- "workflow_name"
- ],
- "type": "tag"
- }
- ],
- "measurement": "cpuacct.usage",
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT mean(\"sum\") FROM (SELECT sum(\"value\") FROM \"cpuacct.usage\" WHERE (\"run_type\" = 'cold') AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND \"workflow_name\" =~ /$selected_workflow_name/ AND $timeFilter GROUP BY \"destination_name\", \"workflow_name\", \"benchmark_uuid\", \"history_name\") GROUP BY \"benchmark_uuid\", workflow_name",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "table",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "sum"
- }
- ]
- ],
- "tags": [
- {
- "key": "run_type",
- "operator": "=",
- "value": "cold"
- }
- ]
- }
- ],
- "timeFrom": null,
- "timeShift": null,
- "title": "Average CPU-Usage for Cold-Run per Benchmark",
- "transform": "table",
- "type": "table"
- },
- {
- "columns": [],
- "datasource": "glx_benchmarker",
- "fontSize": "100%",
- "gridPos": {
- "h": 5,
- "w": 12,
- "x": 12,
- "y": 32
- },
- "id": 15,
- "links": [],
- "options": {},
- "pageSize": null,
- "scroll": true,
- "showHeader": true,
- "sort": {
- "col": 0,
- "desc": true
- },
- "styles": [
- {
- "alias": "Time",
- "dateFormat": "YYYY-MM-DD HH:mm:ss",
- "pattern": "Time",
- "type": "hidden"
- },
- {
- "alias": "",
- "colorMode": null,
- "colors": [
- "rgba(245, 54, 54, 0.9)",
- "rgba(237, 129, 40, 0.89)",
- "rgba(50, 172, 45, 0.97)"
- ],
- "decimals": 2,
- "pattern": "/.*/",
- "thresholds": [],
- "type": "number",
- "unit": "s"
- }
- ],
- "targets": [
- {
- "alias": "CPU-Time per Workflow",
- "groupBy": [
- {
- "params": [
- "benchmark_uid"
- ],
- "type": "tag"
- },
- {
- "params": [
- "destination_name"
- ],
- "type": "tag"
- },
- {
- "params": [
- "workflow_name"
- ],
- "type": "tag"
- }
- ],
- "measurement": "cpuacct.usage",
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT mean(\"sum\") FROM (SELECT sum(\"value\") FROM \"cpuacct.usage\" WHERE (\"run_type\" = 'warm' AND \"benchmark_type\" = '' AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND \"workflow_name\" =~ /$selected_workflow_name/) AND $timeFilter GROUP BY \"destination_name\", \"workflow_name\", \"benchmark_uuid\", \"history_name\") GROUP BY \"benchmark_uuid\", workflow_name",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "table",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "sum"
- }
- ]
- ],
- "tags": [
- {
- "key": "run_type",
- "operator": "=",
- "value": "warm"
- },
- {
- "condition": "AND",
- "key": "benchmark_type",
- "operator": "=",
- "value": ""
- }
- ]
- }
- ],
- "timeFrom": null,
- "timeShift": null,
- "title": "Average CPU-Usage for Warm-Run per Benchmark",
- "transform": "table",
- "type": "table"
- },
- {
- "aliasColors": {},
- "bars": true,
- "dashLength": 10,
- "dashes": false,
- "datasource": "glx_benchmarker",
- "fill": 1,
- "gridPos": {
- "h": 8,
- "w": 24,
- "x": 0,
- "y": 37
- },
- "id": 29,
- "legend": {
- "avg": false,
- "current": false,
- "max": false,
- "min": false,
- "show": false,
- "total": false,
- "values": false
- },
- "lines": false,
- "linewidth": 1,
- "links": [],
- "nullPointMode": "null",
- "options": {},
- "paceLength": 10,
- "percentage": false,
- "pointradius": 2,
- "points": false,
- "renderer": "flot",
- "seriesOverrides": [],
- "spaceLength": 10,
- "stack": false,
- "steppedLine": false,
- "targets": [
- {
- "alias": "Cold $tag_workflow_name",
- "groupBy": [
- {
- "params": [
- "$__interval"
- ],
- "type": "time"
- },
- {
- "params": [
- "null"
- ],
- "type": "fill"
- }
- ],
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT mean(\"sum\") FROM (SELECT sum(\"value\") FROM \"cpuacct.usage\" WHERE (\"run_type\" = 'cold') AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND \"workflow_name\" =~ /$selected_workflow_name/ AND $timeFilter GROUP BY \"destination_name\", \"workflow_name\", \"benchmark_uuid\", \"history_name\" ) WHERE $timeFilter GROUP BY \"benchmark_uuid\", workflow_name",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "time_series",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "mean"
- }
- ]
- ],
- "tags": []
- },
- {
- "alias": "Warm $tag_workflow_name",
- "groupBy": [
- {
- "params": [
- "$__interval"
- ],
- "type": "time"
- },
- {
- "params": [
- "null"
- ],
- "type": "fill"
- }
- ],
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT mean(\"sum\") FROM (SELECT sum(\"value\") FROM \"cpuacct.usage\" WHERE (\"run_type\" = 'warm' AND \"benchmark_type\" = '' AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND \"workflow_name\" =~ /$selected_workflow_name/) AND $timeFilter GROUP BY \"destination_name\", \"workflow_name\", \"benchmark_uuid\", \"history_name\") WHERE $timeFilter GROUP BY \"benchmark_uuid\", workflow_name",
- "rawQuery": true,
- "refId": "B",
- "resultFormat": "time_series",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "mean"
- }
- ]
- ],
- "tags": []
- }
- ],
- "thresholds": [],
- "timeFrom": null,
- "timeRegions": [],
- "timeShift": null,
- "title": "Average CPU-Time Cold vs Warm per Workflow",
- "tooltip": {
- "shared": false,
- "sort": 0,
- "value_type": "individual"
- },
- "type": "graph",
- "xaxis": {
- "buckets": null,
- "mode": "series",
- "name": null,
- "show": true,
- "values": [
- "total"
- ]
- },
- "yaxes": [
- {
- "format": "s",
- "label": null,
- "logBase": 1,
- "max": null,
- "min": "0",
- "show": true
- },
- {
- "format": "short",
- "label": null,
- "logBase": 1,
- "max": null,
- "min": null,
- "show": false
- }
- ],
- "yaxis": {
- "align": false,
- "alignLevel": null
- }
- },
- {
- "collapsed": false,
- "gridPos": {
- "h": 1,
- "w": 24,
- "x": 0,
- "y": 45
- },
- "id": 17,
- "panels": [],
- "title": "Staging Time",
- "type": "row"
- },
- {
- "columns": [],
- "datasource": "glx_benchmarker",
- "fontSize": "100%",
- "gridPos": {
- "h": 8,
- "w": 12,
- "x": 0,
- "y": 46
- },
- "id": 6,
- "links": [],
- "options": {},
- "pageSize": null,
- "scroll": true,
- "showHeader": true,
- "sort": {
- "col": 0,
- "desc": true
- },
- "styles": [
- {
- "alias": "Time",
- "dateFormat": "YYYY-MM-DD HH:mm:ss",
- "pattern": "Time",
- "type": "hidden"
- },
- {
- "alias": "",
- "colorMode": null,
- "colors": [
- "rgba(245, 54, 54, 0.9)",
- "rgba(237, 129, 40, 0.89)",
- "rgba(50, 172, 45, 0.97)"
- ],
- "decimals": 2,
- "pattern": "/.*/",
- "thresholds": [],
- "type": "number",
- "unit": "s"
- }
- ],
- "targets": [
- {
- "alias": "CPU-Time per Workflow",
- "groupBy": [
- {
- "params": [
- "destination_name"
- ],
- "type": "tag"
- },
- {
- "params": [
- "workflow_name"
- ],
- "type": "tag"
- },
- {
- "params": [
- "benchmark_uuid"
- ],
- "type": "tag"
- },
- {
- "params": [
- "history_name"
- ],
- "type": "tag"
- }
- ],
- "measurement": "staging_time",
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT sum(\"value\") FROM \"staging_time\" WHERE (\"run_type\" = 'cold' AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND \"workflow_name\" =~ /$selected_workflow_name/) AND $timeFilter GROUP BY \"destination_name\", \"workflow_name\", \"benchmark_uuid\", \"history_name\"",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "table",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "sum"
- }
- ]
- ],
- "tags": [
- {
- "key": "run_type",
- "operator": "=",
- "value": "cold"
- }
- ]
- }
- ],
- "timeFrom": null,
- "timeShift": null,
- "title": "Staging-Time for Cold-Run",
- "transform": "table",
- "type": "table"
- },
- {
- "columns": [],
- "datasource": "glx_benchmarker",
- "fontSize": "100%",
- "gridPos": {
- "h": 8,
- "w": 12,
- "x": 12,
- "y": 46
- },
- "id": 7,
- "links": [],
- "options": {},
- "pageSize": null,
- "scroll": true,
- "showHeader": true,
- "sort": {
- "col": 0,
- "desc": true
- },
- "styles": [
- {
- "alias": "Time",
- "dateFormat": "YYYY-MM-DD HH:mm:ss",
- "pattern": "Time",
- "type": "hidden"
- },
- {
- "alias": "",
- "colorMode": null,
- "colors": [
- "rgba(245, 54, 54, 0.9)",
- "rgba(237, 129, 40, 0.89)",
- "rgba(50, 172, 45, 0.97)"
- ],
- "decimals": 2,
- "pattern": "/.*/",
- "thresholds": [],
- "type": "number",
- "unit": "s"
- }
- ],
- "targets": [
- {
- "alias": "CPU-Time per Workflow",
- "groupBy": [
- {
- "params": [
- "benchmark_uid"
- ],
- "type": "tag"
- },
- {
- "params": [
- "destination_name"
- ],
- "type": "tag"
- },
- {
- "params": [
- "workflow_name"
- ],
- "type": "tag"
- }
- ],
- "measurement": "staging_time",
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT sum(\"value\") FROM \"staging_time\" WHERE (\"run_type\" = 'warm' AND \"benchmark_type\" = '' AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND \"workflow_name\" =~ /$selected_workflow_name/) AND $timeFilter GROUP BY \"destination_name\", \"workflow_name\", \"benchmark_uuid\", \"history_name\"",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "table",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "sum"
- }
- ]
- ],
- "tags": [
- {
- "key": "run_type",
- "operator": "=",
- "value": "warm"
- },
- {
- "condition": "AND",
- "key": "benchmark_type",
- "operator": "=",
- "value": ""
- }
- ]
- }
- ],
- "timeFrom": null,
- "timeShift": null,
- "title": "Staging-Time for Warm-Run",
- "transform": "table",
- "type": "table"
- },
- {
- "columns": [],
- "datasource": "glx_benchmarker",
- "fontSize": "100%",
- "gridPos": {
- "h": 6,
- "w": 12,
- "x": 0,
- "y": 54
- },
- "id": 13,
- "links": [],
- "options": {},
- "pageSize": null,
- "scroll": true,
- "showHeader": true,
- "sort": {
- "col": 0,
- "desc": true
- },
- "styles": [
- {
- "alias": "Time",
- "dateFormat": "YYYY-MM-DD HH:mm:ss",
- "pattern": "Time",
- "type": "hidden"
- },
- {
- "alias": "",
- "colorMode": null,
- "colors": [
- "rgba(245, 54, 54, 0.9)",
- "rgba(237, 129, 40, 0.89)",
- "rgba(50, 172, 45, 0.97)"
- ],
- "decimals": 2,
- "pattern": "/.*/",
- "thresholds": [],
- "type": "number",
- "unit": "s"
- }
- ],
- "targets": [
- {
- "alias": "CPU-Time per Workflow",
- "groupBy": [
- {
- "params": [
- "destination_name"
- ],
- "type": "tag"
- },
- {
- "params": [
- "workflow_name"
- ],
- "type": "tag"
- },
- {
- "params": [
- "benchmark_uuid"
- ],
- "type": "tag"
- },
- {
- "params": [
- "history_name"
- ],
- "type": "tag"
- }
- ],
- "measurement": "staging_time",
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT mean(\"sum\") FROM (SELECT sum(\"value\") FROM \"staging_time\" WHERE (\"run_type\" = 'cold' AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND \"workflow_name\" =~ /$selected_workflow_name/) AND $timeFilter GROUP BY \"destination_name\", \"workflow_name\", \"benchmark_uuid\", \"history_name\") GROUP BY \"benchmark_uuid\", workflow_name",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "table",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "sum"
- }
- ]
- ],
- "tags": [
- {
- "key": "run_type",
- "operator": "=",
- "value": "cold"
- }
- ]
- }
- ],
- "timeFrom": null,
- "timeShift": null,
- "title": "Average Staging-Time for Cold-Run per Benchmark",
- "transform": "table",
- "type": "table"
- },
- {
- "columns": [],
- "datasource": "glx_benchmarker",
- "fontSize": "100%",
- "gridPos": {
- "h": 6,
- "w": 12,
- "x": 12,
- "y": 54
- },
- "id": 12,
- "links": [],
- "options": {},
- "pageSize": null,
- "scroll": true,
- "showHeader": true,
- "sort": {
- "col": 0,
- "desc": true
- },
- "styles": [
- {
- "alias": "Time",
- "dateFormat": "YYYY-MM-DD HH:mm:ss",
- "pattern": "Time",
- "type": "hidden"
- },
- {
- "alias": "",
- "colorMode": null,
- "colors": [
- "rgba(245, 54, 54, 0.9)",
- "rgba(237, 129, 40, 0.89)",
- "rgba(50, 172, 45, 0.97)"
- ],
- "decimals": 2,
- "pattern": "/.*/",
- "thresholds": [],
- "type": "number",
- "unit": "s"
- }
- ],
- "targets": [
- {
- "alias": "CPU-Time per Workflow",
- "groupBy": [
- {
- "params": [
- "benchmark_uid"
- ],
- "type": "tag"
- },
- {
- "params": [
- "destination_name"
- ],
- "type": "tag"
- },
- {
- "params": [
- "workflow_name"
- ],
- "type": "tag"
- }
- ],
- "measurement": "staging_time",
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT mean(\"sum\") FROM (SELECT sum(\"value\") FROM \"staging_time\" WHERE (\"run_type\" = 'warm' AND \"benchmark_type\" = '' AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND \"workflow_name\" =~ /$selected_workflow_name/) AND $timeFilter GROUP BY \"destination_name\", \"workflow_name\", \"benchmark_uuid\", \"history_name\") GROUP BY benchmark_uuid, workflow_name",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "table",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "sum"
- }
- ]
- ],
- "tags": [
- {
- "key": "run_type",
- "operator": "=",
- "value": "warm"
- },
- {
- "condition": "AND",
- "key": "benchmark_type",
- "operator": "=",
- "value": ""
- }
- ]
- }
- ],
- "timeFrom": null,
- "timeShift": null,
- "title": "Average Staging-Time for Warm-Run per Benchmark",
- "transform": "table",
- "type": "table"
- },
- {
- "aliasColors": {},
- "bars": true,
- "dashLength": 10,
- "dashes": false,
- "datasource": "glx_benchmarker",
- "fill": 1,
- "gridPos": {
- "h": 8,
- "w": 24,
- "x": 0,
- "y": 60
- },
- "id": 30,
- "legend": {
- "alignAsTable": false,
- "avg": false,
- "current": false,
- "max": false,
- "min": false,
- "rightSide": false,
- "show": false,
- "total": false,
- "values": false
- },
- "lines": false,
- "linewidth": 1,
- "links": [],
- "nullPointMode": "null",
- "options": {},
- "paceLength": 10,
- "percentage": false,
- "pointradius": 2,
- "points": false,
- "renderer": "flot",
- "seriesOverrides": [],
- "spaceLength": 10,
- "stack": false,
- "steppedLine": false,
- "targets": [
- {
- "alias": "Cold $tag_workflow_name",
- "groupBy": [
- {
- "params": [
- "$__interval"
- ],
- "type": "time"
- },
- {
- "params": [
- "null"
- ],
- "type": "fill"
- }
- ],
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT mean(\"sum\") FROM (SELECT sum(\"value\") FROM \"staging_time\" WHERE (\"run_type\" = 'cold' AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND \"workflow_name\" =~ /$selected_workflow_name/) AND $timeFilter GROUP BY \"destination_name\", \"workflow_name\", \"benchmark_uuid\", \"history_name\") WHERE $timeFilter GROUP BY \"benchmark_uuid\", workflow_name",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "time_series",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "mean"
- }
- ]
- ],
- "tags": []
- },
- {
- "alias": "Warm $tag_workflow_name",
- "groupBy": [
- {
- "params": [
- "$__interval"
- ],
- "type": "time"
- },
- {
- "params": [
- "null"
- ],
- "type": "fill"
- }
- ],
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT mean(\"sum\") FROM (SELECT sum(\"value\") FROM \"staging_time\" WHERE (\"run_type\" = 'warm' AND \"benchmark_type\" = '' AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND \"workflow_name\" =~ /$selected_workflow_name/) AND $timeFilter GROUP BY \"destination_name\", \"workflow_name\", \"benchmark_uuid\", \"history_name\") WHERE $timeFilter GROUP BY benchmark_uuid, workflow_name",
- "rawQuery": true,
- "refId": "B",
- "resultFormat": "time_series",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "mean"
- }
- ]
- ],
- "tags": []
- }
- ],
- "thresholds": [],
- "timeFrom": null,
- "timeRegions": [],
- "timeShift": null,
- "title": "Average Staging-Time Cold vs Warm per Workflow",
- "tooltip": {
- "shared": false,
- "sort": 0,
- "value_type": "individual"
- },
- "type": "graph",
- "xaxis": {
- "buckets": null,
- "mode": "series",
- "name": null,
- "show": true,
- "values": [
- "total"
- ]
- },
- "yaxes": [
- {
- "format": "s",
- "label": null,
- "logBase": 1,
- "max": null,
- "min": "0",
- "show": true
- },
- {
- "format": "short",
- "label": null,
- "logBase": 1,
- "max": null,
- "min": null,
- "show": false
- }
- ],
- "yaxis": {
- "align": false,
- "alignLevel": null
- }
- }
- ],
- "schemaVersion": 18,
- "style": "dark",
- "tags": [],
- "templating": {
- "list": [
- {
- "allValue": null,
- "current": {
- "tags": [],
- "text": "All",
- "value": [
- "$__all"
- ]
- },
- "datasource": "glx_benchmarker",
- "definition": "SELECT benchmark_uuid , value FROM \"runtime_seconds\" WHERE \"benchmark_type\" = '' ",
- "hide": 0,
- "includeAll": true,
- "label": "Benchmark UUID",
- "multi": true,
- "name": "selected_benchmark_uuid",
- "options": [],
- "query": "SELECT benchmark_uuid , value FROM \"runtime_seconds\" WHERE \"benchmark_type\" = '' ",
- "refresh": 2,
- "regex": "",
- "skipUrlSync": false,
- "sort": 0,
- "tagValuesQuery": "",
- "tags": [],
- "tagsQuery": "",
- "type": "query",
- "useTags": false
- },
- {
- "allValue": null,
- "current": {
- "text": "All",
- "value": "$__all"
- },
- "datasource": "glx_benchmarker",
- "definition": "SELECT workflow_name, value FROM \"runtime_seconds\" WHERE \"benchmark_type\" = '' GROUP BY workflow_name",
- "hide": 0,
- "includeAll": true,
- "label": "Workflow",
- "multi": true,
- "name": "selected_workflow_name",
- "options": [],
- "query": "",
- "refresh": 2,
- "regex": "",
- "skipUrlSync": false,
- "sort": 0,
- "tagValuesQuery": "",
- "tags": [],
- "tagsQuery": "",
- "type": "query",
- "useTags": false
- }
- ]
- },
- "time": {
- "from": "now-30d",
- "to": "now"
- },
- "timepicker": {
- "refresh_intervals": [
- "5s",
- "10s",
- "30s",
- "1m",
- "5m",
- "15m",
- "30m",
- "1h",
- "2h",
- "1d"
- ],
- "time_options": [
- "5m",
- "15m",
- "1h",
- "6h",
- "12h",
- "24h",
- "2d",
- "7d",
- "30d"
- ]
- },
- "timezone": "",
- "title": "Cold vs Warm Benchmarks",
- "uid": "JwXRTNnZz",
- "version": 2
-}
\ No newline at end of file
diff --git a/grafana/dashboards/Destination-Comparison Benchmarks.json b/grafana/dashboards/Destination-Comparison Benchmarks.json
deleted file mode 100644
index f1034a41..00000000
--- a/grafana/dashboards/Destination-Comparison Benchmarks.json
+++ /dev/null
@@ -1,2510 +0,0 @@
-{
- "annotations": {
- "list": [
- {
- "builtIn": 1,
- "datasource": "-- Grafana --",
- "enable": true,
- "hide": true,
- "iconColor": "rgba(0, 211, 255, 1)",
- "name": "Annotations & Alerts",
- "type": "dashboard"
- }
- ]
- },
- "editable": true,
- "gnetId": null,
- "graphTooltip": 0,
- "id": 4,
- "iteration": 1563885333107,
- "links": [],
- "panels": [
- {
- "collapsed": true,
- "gridPos": {
- "h": 1,
- "w": 24,
- "x": 0,
- "y": 0
- },
- "id": 39,
- "panels": [
- {
- "aliasColors": {},
- "bars": true,
- "dashLength": 10,
- "dashes": false,
- "datasource": "glx_benchmarker",
- "fill": 1,
- "gridPos": {
- "h": 6,
- "w": 24,
- "x": 0,
- "y": 1
- },
- "id": 34,
- "legend": {
- "alignAsTable": false,
- "avg": false,
- "current": false,
- "hideEmpty": false,
- "hideZero": false,
- "max": false,
- "min": false,
- "show": false,
- "total": false,
- "values": false
- },
- "lines": false,
- "linewidth": 1,
- "links": [],
- "nullPointMode": "null",
- "options": {},
- "percentage": false,
- "pointradius": 2,
- "points": false,
- "renderer": "flot",
- "seriesOverrides": [
- {
- "alias": "/error/",
- "color": "#E02F44"
- },
- {
- "alias": "/success/",
- "color": "#56A64B"
- }
- ],
- "spaceLength": 10,
- "stack": false,
- "steppedLine": false,
- "targets": [
- {
- "alias": "$tag_benchmark_uuid: $col",
- "groupBy": [
- {
- "params": [
- "benchmark_uuid"
- ],
- "type": "tag"
- }
- ],
- "measurement": "workflow_status",
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT success, error FROM (SELECT count(\"value\") as success FROM \"workflow_status\" WHERE (\"value\" = 'success') AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND \"workflow_name\" =~ /$selected_workflow_name/ AND \"destination_name\" =~ /$selected_destination_name/ AND $timeFilter GROUP BY \"benchmark_uuid\"), (SELECT count(\"value\") as error FROM \"workflow_status\" WHERE (\"value\" = 'error') AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND \"workflow_name\" =~ /$selected_workflow_name/ AND \"destination_name\" =~ /$selected_destination_name/ AND $timeFilter GROUP BY \"benchmark_uuid\") GROUP BY benchmark_uuid",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "time_series",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "count"
- }
- ]
- ],
- "tags": [
- {
- "key": "value",
- "operator": "=",
- "value": "error"
- }
- ]
- }
- ],
- "thresholds": [],
- "timeFrom": null,
- "timeRegions": [],
- "timeShift": null,
- "title": "Workflow Status",
- "tooltip": {
- "shared": false,
- "sort": 0,
- "value_type": "individual"
- },
- "type": "graph",
- "xaxis": {
- "buckets": null,
- "mode": "series",
- "name": null,
- "show": true,
- "values": [
- "total"
- ]
- },
- "yaxes": [
- {
- "format": "short",
- "label": null,
- "logBase": 1,
- "max": null,
- "min": null,
- "show": true
- },
- {
- "format": "short",
- "label": null,
- "logBase": 1,
- "max": null,
- "min": null,
- "show": true
- }
- ],
- "yaxis": {
- "align": false,
- "alignLevel": null
- }
- },
- {
- "columns": [],
- "datasource": "glx_benchmarker",
- "fontSize": "100%",
- "gridPos": {
- "h": 8,
- "w": 12,
- "x": 0,
- "y": 7
- },
- "id": 36,
- "links": [],
- "options": {},
- "pageSize": null,
- "scroll": true,
- "showHeader": true,
- "sort": {
- "col": 0,
- "desc": true
- },
- "styles": [
- {
- "alias": "Time",
- "dateFormat": "YYYY-MM-DD HH:mm:ss",
- "pattern": "Time",
- "type": "date"
- },
- {
- "alias": "",
- "colorMode": null,
- "colors": [
- "rgba(245, 54, 54, 0.9)",
- "rgba(237, 129, 40, 0.89)",
- "rgba(50, 172, 45, 0.97)"
- ],
- "decimals": 2,
- "pattern": "/.*/",
- "thresholds": [],
- "type": "number",
- "unit": "none"
- }
- ],
- "targets": [
- {
- "groupBy": [
- {
- "params": [
- "$__interval"
- ],
- "type": "time"
- },
- {
- "params": [
- "null"
- ],
- "type": "fill"
- }
- ],
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT value FROM /$selected_measurement/ WHERE (\"benchmark_type\" = '' AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND \"workflow_name\" =~ /$selected_workflow_name/ AND \"destination_name\" =~ /$selected_destination_name/) AND $timeFilter GROUP BY benchmark_uuid, destination_name, workflow_name",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "table",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "mean"
- }
- ]
- ],
- "tags": []
- }
- ],
- "timeFrom": null,
- "timeShift": null,
- "title": "Selected Measurements",
- "transform": "table",
- "type": "table"
- },
- {
- "columns": [],
- "datasource": "glx_benchmarker",
- "fontSize": "100%",
- "gridPos": {
- "h": 8,
- "w": 12,
- "x": 12,
- "y": 7
- },
- "id": 37,
- "links": [],
- "options": {},
- "pageSize": null,
- "scroll": true,
- "showHeader": true,
- "sort": {
- "col": 0,
- "desc": true
- },
- "styles": [
- {
- "alias": "Time",
- "dateFormat": "YYYY-MM-DD HH:mm:ss",
- "pattern": "Time",
- "type": "date"
- },
- {
- "alias": "",
- "colorMode": null,
- "colors": [
- "rgba(245, 54, 54, 0.9)",
- "rgba(237, 129, 40, 0.89)",
- "rgba(50, 172, 45, 0.97)"
- ],
- "decimals": 2,
- "pattern": "/.*/",
- "thresholds": [],
- "type": "number",
- "unit": "none"
- }
- ],
- "targets": [
- {
- "groupBy": [
- {
- "params": [
- "$__interval"
- ],
- "type": "time"
- },
- {
- "params": [
- "null"
- ],
- "type": "fill"
- }
- ],
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT min(\"sum\"), max(\"sum\"), mean(\"sum\"), stddev(\"sum\") FROM (SELECT sum(\"value\") FROM /$selected_measurement/ WHERE (\"benchmark_type\" = '' AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND \"workflow_name\" =~ /$selected_workflow_name/ AND \"destination_name\" =~ /$selected_destination_name/) AND $timeFilter GROUP BY \"destination_name\", \"workflow_name\", \"benchmark_uuid\", \"history_name\") GROUP BY \"benchmark_uuid\", \"destination_name\", \"workflow_name\" ",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "table",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "mean"
- }
- ]
- ],
- "tags": []
- }
- ],
- "timeFrom": null,
- "timeShift": null,
- "title": "Average over selected measurements",
- "transform": "table",
- "type": "table"
- }
- ],
- "title": "Custom Metrics",
- "type": "row"
- },
- {
- "collapsed": false,
- "gridPos": {
- "h": 1,
- "w": 24,
- "x": 0,
- "y": 1
- },
- "id": 25,
- "panels": [],
- "title": "Runtime",
- "type": "row"
- },
- {
- "columns": [],
- "datasource": "glx_benchmarker",
- "fontSize": "100%",
- "gridPos": {
- "h": 8,
- "w": 12,
- "x": 0,
- "y": 2
- },
- "id": 16,
- "links": [],
- "options": {},
- "pageSize": null,
- "scroll": true,
- "showHeader": true,
- "sort": {
- "col": 0,
- "desc": true
- },
- "styles": [
- {
- "alias": "Time",
- "dateFormat": "YYYY-MM-DD HH:mm:ss",
- "pattern": "Time",
- "type": "hidden"
- },
- {
- "alias": "",
- "colorMode": null,
- "colors": [
- "rgba(245, 54, 54, 0.9)",
- "rgba(237, 129, 40, 0.89)",
- "rgba(50, 172, 45, 0.97)"
- ],
- "decimals": 2,
- "pattern": "/.*/",
- "thresholds": [],
- "type": "number",
- "unit": "s"
- }
- ],
- "targets": [
- {
- "alias": "CPU-Time per Workflow",
- "groupBy": [
- {
- "params": [
- "benchmark_uid"
- ],
- "type": "tag"
- },
- {
- "params": [
- "destination_name"
- ],
- "type": "tag"
- },
- {
- "params": [
- "workflow_name"
- ],
- "type": "tag"
- }
- ],
- "measurement": "staging_time",
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT sum(\"value\") FROM \"runtime_seconds\" WHERE (\"benchmark_type\" = '' AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND \"workflow_name\" =~ /$selected_workflow_name/ AND \"destination_name\" =~ /$selected_destination_name/) AND $timeFilter GROUP BY \"destination_name\", \"workflow_name\", \"benchmark_uuid\", \"history_name\"",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "table",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "sum"
- }
- ]
- ],
- "tags": [
- {
- "key": "run_type",
- "operator": "=",
- "value": "warm"
- },
- {
- "condition": "AND",
- "key": "benchmark_type",
- "operator": "=",
- "value": ""
- }
- ]
- }
- ],
- "timeFrom": null,
- "timeShift": null,
- "title": "Runtime",
- "transform": "table",
- "type": "table"
- },
- {
- "columns": [],
- "datasource": "glx_benchmarker",
- "fontSize": "100%",
- "gridPos": {
- "h": 8,
- "w": 12,
- "x": 12,
- "y": 2
- },
- "id": 17,
- "links": [],
- "options": {},
- "pageSize": null,
- "scroll": true,
- "showHeader": true,
- "sort": {
- "col": 0,
- "desc": true
- },
- "styles": [
- {
- "alias": "Time",
- "dateFormat": "YYYY-MM-DD HH:mm:ss",
- "pattern": "Time",
- "type": "hidden"
- },
- {
- "alias": "",
- "colorMode": null,
- "colors": [
- "rgba(245, 54, 54, 0.9)",
- "rgba(237, 129, 40, 0.89)",
- "rgba(50, 172, 45, 0.97)"
- ],
- "decimals": 2,
- "pattern": "/.*/",
- "thresholds": [],
- "type": "number",
- "unit": "s"
- }
- ],
- "targets": [
- {
- "alias": "CPU-Time per Workflow",
- "groupBy": [
- {
- "params": [
- "benchmark_uid"
- ],
- "type": "tag"
- },
- {
- "params": [
- "destination_name"
- ],
- "type": "tag"
- },
- {
- "params": [
- "workflow_name"
- ],
- "type": "tag"
- }
- ],
- "measurement": "staging_time",
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT min(\"sum\"), max(\"sum\"), mean(\"sum\"), stddev(\"sum\") FROM (SELECT sum(\"value\") FROM \"runtime_seconds\" WHERE (\"benchmark_type\" = '' AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND \"workflow_name\" =~ /$selected_workflow_name/ AND \"destination_name\" =~ /$selected_destination_name/) AND $timeFilter GROUP BY \"destination_name\", \"workflow_name\", \"benchmark_uuid\", \"history_name\") GROUP BY \"benchmark_uuid\", \"destination_name\", \"workflow_name\" ",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "table",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "sum"
- }
- ]
- ],
- "tags": [
- {
- "key": "run_type",
- "operator": "=",
- "value": "warm"
- },
- {
- "condition": "AND",
- "key": "benchmark_type",
- "operator": "=",
- "value": ""
- }
- ]
- }
- ],
- "timeFrom": null,
- "timeShift": null,
- "title": "Average Runtime per Benchmark, Destination and Workflow",
- "transform": "table",
- "type": "table"
- },
- {
- "columns": [],
- "datasource": "glx_benchmarker",
- "fontSize": "100%",
- "gridPos": {
- "h": 8,
- "w": 12,
- "x": 0,
- "y": 10
- },
- "id": 41,
- "links": [],
- "options": {},
- "pageSize": null,
- "scroll": true,
- "showHeader": true,
- "sort": {
- "col": 0,
- "desc": true
- },
- "styles": [
- {
- "alias": "Time",
- "dateFormat": "YYYY-MM-DD HH:mm:ss",
- "pattern": "Time",
- "type": "hidden"
- },
- {
- "alias": "",
- "colorMode": null,
- "colors": [
- "rgba(245, 54, 54, 0.9)",
- "rgba(237, 129, 40, 0.89)",
- "rgba(50, 172, 45, 0.97)"
- ],
- "decimals": 2,
- "pattern": "/.*/",
- "thresholds": [],
- "type": "number",
- "unit": "s"
- }
- ],
- "targets": [
- {
- "alias": "CPU-Time per Workflow",
- "groupBy": [
- {
- "params": [
- "benchmark_uid"
- ],
- "type": "tag"
- },
- {
- "params": [
- "destination_name"
- ],
- "type": "tag"
- },
- {
- "params": [
- "workflow_name"
- ],
- "type": "tag"
- }
- ],
- "measurement": "staging_time",
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT min(\"sum\"), max(\"sum\"), mean(\"sum\"), stddev(\"sum\") FROM (SELECT sum(\"value\") FROM \"runtime_seconds\" WHERE (\"benchmark_type\" = '' AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND \"workflow_name\" =~ /$selected_workflow_name/ AND \"destination_name\" =~ /$selected_destination_name/) AND $timeFilter GROUP BY \"destination_name\", \"workflow_name\", \"history_name\") GROUP BY destination_name, workflow_name",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "table",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "sum"
- }
- ]
- ],
- "tags": [
- {
- "key": "run_type",
- "operator": "=",
- "value": "warm"
- },
- {
- "condition": "AND",
- "key": "benchmark_type",
- "operator": "=",
- "value": ""
- }
- ]
- }
- ],
- "timeFrom": null,
- "timeShift": null,
- "title": "Average Runtime per Destination and Workflow",
- "transform": "table",
- "type": "table"
- },
- {
- "aliasColors": {},
- "bars": true,
- "dashLength": 10,
- "dashes": false,
- "datasource": "glx_benchmarker",
- "fill": 1,
- "gridPos": {
- "h": 8,
- "w": 12,
- "x": 12,
- "y": 10
- },
- "id": 19,
- "legend": {
- "alignAsTable": false,
- "avg": false,
- "current": false,
- "max": false,
- "min": false,
- "rightSide": false,
- "show": false,
- "total": false,
- "values": false
- },
- "lines": false,
- "linewidth": 1,
- "links": [],
- "nullPointMode": "null",
- "options": {},
- "paceLength": 10,
- "percentage": false,
- "pointradius": 2,
- "points": false,
- "renderer": "flot",
- "seriesOverrides": [],
- "spaceLength": 10,
- "stack": false,
- "steppedLine": false,
- "targets": [
- {
- "alias": "$tag_destination_name: $tag_workflow_name",
- "groupBy": [
- {
- "params": [
- "$__interval"
- ],
- "type": "time"
- },
- {
- "params": [
- "null"
- ],
- "type": "fill"
- }
- ],
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT mean(\"sum\") FROM (SELECT sum(\"value\") FROM \"runtime_seconds\" WHERE (\"benchmark_type\" = '' AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND \"workflow_name\" =~ /$selected_workflow_name/ AND \"destination_name\" =~ /$selected_destination_name/) AND $timeFilter GROUP BY \"destination_name\", \"workflow_name\", \"history_name\") WHERE $timeFilter GROUP BY destination_name, workflow_name",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "time_series",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "mean"
- }
- ]
- ],
- "tags": []
- }
- ],
- "thresholds": [],
- "timeFrom": null,
- "timeRegions": [],
- "timeShift": null,
- "title": "Average Runtime per Destination and Workflow",
- "tooltip": {
- "shared": false,
- "sort": 0,
- "value_type": "individual"
- },
- "type": "graph",
- "xaxis": {
- "buckets": null,
- "mode": "series",
- "name": null,
- "show": true,
- "values": [
- "total"
- ]
- },
- "yaxes": [
- {
- "format": "s",
- "label": null,
- "logBase": 1,
- "max": null,
- "min": null,
- "show": true
- },
- {
- "format": "short",
- "label": null,
- "logBase": 1,
- "max": null,
- "min": null,
- "show": false
- }
- ],
- "yaxis": {
- "align": false,
- "alignLevel": null
- }
- },
- {
- "columns": [],
- "datasource": "glx_benchmarker",
- "fontSize": "100%",
- "gridPos": {
- "h": 8,
- "w": 12,
- "x": 0,
- "y": 18
- },
- "id": 30,
- "links": [],
- "options": {},
- "pageSize": null,
- "scroll": true,
- "showHeader": true,
- "sort": {
- "col": 0,
- "desc": true
- },
- "styles": [
- {
- "alias": "Time",
- "dateFormat": "YYYY-MM-DD HH:mm:ss",
- "pattern": "Time",
- "type": "hidden"
- },
- {
- "alias": "",
- "colorMode": null,
- "colors": [
- "rgba(245, 54, 54, 0.9)",
- "rgba(237, 129, 40, 0.89)",
- "rgba(50, 172, 45, 0.97)"
- ],
- "decimals": 2,
- "pattern": "/.*/",
- "thresholds": [],
- "type": "number",
- "unit": "s"
- }
- ],
- "targets": [
- {
- "alias": "CPU-Time per Workflow",
- "groupBy": [
- {
- "params": [
- "benchmark_uid"
- ],
- "type": "tag"
- },
- {
- "params": [
- "destination_name"
- ],
- "type": "tag"
- },
- {
- "params": [
- "workflow_name"
- ],
- "type": "tag"
- }
- ],
- "measurement": "staging_time",
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT value FROM \"total_workflow_runtime\" WHERE (\"benchmark_type\" = '' AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND \"workflow_name\" =~ /$selected_workflow_name/ AND \"destination_name\" =~ /$selected_destination_name/) AND $timeFilter GROUP BY \"destination_name\", \"workflow_name\", \"benchmark_uuid\", \"history_name\"",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "table",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "sum"
- }
- ]
- ],
- "tags": [
- {
- "key": "run_type",
- "operator": "=",
- "value": "warm"
- },
- {
- "condition": "AND",
- "key": "benchmark_type",
- "operator": "=",
- "value": ""
- }
- ]
- }
- ],
- "timeFrom": null,
- "timeShift": null,
- "title": "Total Workflow Runtime",
- "transform": "table",
- "type": "table"
- },
- {
- "columns": [],
- "datasource": "glx_benchmarker",
- "fontSize": "100%",
- "gridPos": {
- "h": 8,
- "w": 12,
- "x": 12,
- "y": 18
- },
- "id": 31,
- "links": [],
- "options": {},
- "pageSize": null,
- "scroll": true,
- "showHeader": true,
- "sort": {
- "col": 0,
- "desc": true
- },
- "styles": [
- {
- "alias": "Time",
- "dateFormat": "YYYY-MM-DD HH:mm:ss",
- "pattern": "Time",
- "type": "hidden"
- },
- {
- "alias": "",
- "colorMode": null,
- "colors": [
- "rgba(245, 54, 54, 0.9)",
- "rgba(237, 129, 40, 0.89)",
- "rgba(50, 172, 45, 0.97)"
- ],
- "decimals": 2,
- "pattern": "/.*/",
- "thresholds": [],
- "type": "number",
- "unit": "s"
- }
- ],
- "targets": [
- {
- "alias": "CPU-Time per Workflow",
- "groupBy": [
- {
- "params": [
- "benchmark_uid"
- ],
- "type": "tag"
- },
- {
- "params": [
- "destination_name"
- ],
- "type": "tag"
- },
- {
- "params": [
- "workflow_name"
- ],
- "type": "tag"
- }
- ],
- "measurement": "staging_time",
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT min(\"value\"), max(\"value\"), mean(\"value\"), stddev(\"sum\") FROM (SELECT value FROM \"total_workflow_runtime\" WHERE (\"benchmark_type\" = '' AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND \"workflow_name\" =~ /$selected_workflow_name/ AND \"destination_name\" =~ /$selected_destination_name/) AND $timeFilter GROUP BY \"destination_name\", \"workflow_name\", \"benchmark_uuid\", \"history_name\") GROUP BY \"benchmark_uuid\", \"destination_name\", \"workflow_name\"",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "table",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "sum"
- }
- ]
- ],
- "tags": [
- {
- "key": "run_type",
- "operator": "=",
- "value": "warm"
- },
- {
- "condition": "AND",
- "key": "benchmark_type",
- "operator": "=",
- "value": ""
- }
- ]
- }
- ],
- "timeFrom": null,
- "timeShift": null,
- "title": "Average Total Workflow Runtime per Benchmark, Destination and Workflow",
- "transform": "table",
- "type": "table"
- },
- {
- "columns": [],
- "datasource": "glx_benchmarker",
- "fontSize": "100%",
- "gridPos": {
- "h": 8,
- "w": 12,
- "x": 0,
- "y": 26
- },
- "id": 43,
- "links": [],
- "options": {},
- "pageSize": null,
- "scroll": true,
- "showHeader": true,
- "sort": {
- "col": 0,
- "desc": true
- },
- "styles": [
- {
- "alias": "Time",
- "dateFormat": "YYYY-MM-DD HH:mm:ss",
- "pattern": "Time",
- "type": "hidden"
- },
- {
- "alias": "",
- "colorMode": null,
- "colors": [
- "rgba(245, 54, 54, 0.9)",
- "rgba(237, 129, 40, 0.89)",
- "rgba(50, 172, 45, 0.97)"
- ],
- "decimals": 2,
- "pattern": "/.*/",
- "thresholds": [],
- "type": "number",
- "unit": "s"
- }
- ],
- "targets": [
- {
- "alias": "CPU-Time per Workflow",
- "groupBy": [
- {
- "params": [
- "benchmark_uid"
- ],
- "type": "tag"
- },
- {
- "params": [
- "destination_name"
- ],
- "type": "tag"
- },
- {
- "params": [
- "workflow_name"
- ],
- "type": "tag"
- }
- ],
- "measurement": "staging_time",
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT min(\"value\"), max(\"value\"), mean(\"value\"), stddev(\"sum\") FROM (SELECT value FROM \"total_workflow_runtime\" WHERE (\"benchmark_type\" = '' AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND \"workflow_name\" =~ /$selected_workflow_name/ AND \"destination_name\" =~ /$selected_destination_name/) AND $timeFilter GROUP BY \"destination_name\", \"workflow_name\", \"history_name\") GROUP BY \"destination_name\", \"workflow_name\"",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "table",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "sum"
- }
- ]
- ],
- "tags": [
- {
- "key": "run_type",
- "operator": "=",
- "value": "warm"
- },
- {
- "condition": "AND",
- "key": "benchmark_type",
- "operator": "=",
- "value": ""
- }
- ]
- }
- ],
- "timeFrom": null,
- "timeShift": null,
- "title": "Average Total Workflow Runtime per Destination and Workflow",
- "transform": "table",
- "type": "table"
- },
- {
- "aliasColors": {},
- "bars": true,
- "dashLength": 10,
- "dashes": false,
- "datasource": "glx_benchmarker",
- "fill": 1,
- "gridPos": {
- "h": 8,
- "w": 12,
- "x": 12,
- "y": 26
- },
- "id": 32,
- "legend": {
- "alignAsTable": false,
- "avg": false,
- "current": false,
- "max": false,
- "min": false,
- "rightSide": false,
- "show": false,
- "total": false,
- "values": false
- },
- "lines": false,
- "linewidth": 1,
- "links": [],
- "nullPointMode": "null",
- "options": {},
- "paceLength": 10,
- "percentage": false,
- "pointradius": 2,
- "points": false,
- "renderer": "flot",
- "seriesOverrides": [],
- "spaceLength": 10,
- "stack": false,
- "steppedLine": false,
- "targets": [
- {
- "alias": "$tag_destination_name: $tag_workflow_name",
- "groupBy": [
- {
- "params": [
- "$__interval"
- ],
- "type": "time"
- },
- {
- "params": [
- "null"
- ],
- "type": "fill"
- }
- ],
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT mean(\"value\") FROM (SELECT value FROM \"total_workflow_runtime\" WHERE (\"benchmark_type\" = '' AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND \"workflow_name\" =~ /$selected_workflow_name/ AND \"destination_name\" =~ /$selected_destination_name/) AND $timeFilter GROUP BY \"destination_name\", \"workflow_name\", \"benchmark_uuid\", \"history_name\") WHERE $timeFilter GROUP BY \"benchmark_uuid\", \"destination_name\", \"workflow_name\"",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "time_series",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "mean"
- }
- ]
- ],
- "tags": []
- }
- ],
- "thresholds": [],
- "timeFrom": null,
- "timeRegions": [],
- "timeShift": null,
- "title": "Average Total Workflow Runtime per Destination and Workflow",
- "tooltip": {
- "shared": false,
- "sort": 0,
- "value_type": "individual"
- },
- "type": "graph",
- "xaxis": {
- "buckets": null,
- "mode": "series",
- "name": null,
- "show": true,
- "values": [
- "total"
- ]
- },
- "yaxes": [
- {
- "format": "s",
- "label": null,
- "logBase": 1,
- "max": null,
- "min": null,
- "show": true
- },
- {
- "format": "short",
- "label": null,
- "logBase": 1,
- "max": null,
- "min": null,
- "show": false
- }
- ],
- "yaxis": {
- "align": false,
- "alignLevel": null
- }
- },
- {
- "collapsed": false,
- "gridPos": {
- "h": 1,
- "w": 24,
- "x": 0,
- "y": 34
- },
- "id": 23,
- "panels": [],
- "title": "CPU Time",
- "type": "row"
- },
- {
- "columns": [],
- "datasource": "glx_benchmarker",
- "fontSize": "100%",
- "gridPos": {
- "h": 7,
- "w": 12,
- "x": 0,
- "y": 35
- },
- "id": 5,
- "links": [],
- "options": {},
- "pageSize": null,
- "scroll": true,
- "showHeader": true,
- "sort": {
- "col": 0,
- "desc": true
- },
- "styles": [
- {
- "alias": "Time",
- "dateFormat": "YYYY-MM-DD HH:mm:ss",
- "pattern": "Time",
- "type": "hidden"
- },
- {
- "alias": "",
- "colorMode": null,
- "colors": [
- "rgba(245, 54, 54, 0.9)",
- "rgba(237, 129, 40, 0.89)",
- "rgba(50, 172, 45, 0.97)"
- ],
- "decimals": 2,
- "pattern": "/.*/",
- "thresholds": [],
- "type": "number",
- "unit": "ns"
- }
- ],
- "targets": [
- {
- "alias": "CPU-Time per Workflow",
- "groupBy": [
- {
- "params": [
- "benchmark_uid"
- ],
- "type": "tag"
- },
- {
- "params": [
- "destination_name"
- ],
- "type": "tag"
- },
- {
- "params": [
- "workflow_name"
- ],
- "type": "tag"
- }
- ],
- "measurement": "cpuacct.usage",
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT sum(\"value\") FROM \"cpuacct.usage\" WHERE (\"benchmark_type\" = '' AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND \"workflow_name\" =~ /$selected_workflow_name/ AND \"destination_name\" =~ /$selected_destination_name/) AND $timeFilter GROUP BY \"destination_name\", \"workflow_name\", \"benchmark_uuid\", \"history_name\"",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "table",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "sum"
- }
- ]
- ],
- "tags": [
- {
- "key": "run_type",
- "operator": "=",
- "value": "warm"
- },
- {
- "condition": "AND",
- "key": "benchmark_type",
- "operator": "=",
- "value": ""
- }
- ]
- }
- ],
- "timeFrom": null,
- "timeShift": null,
- "title": "CPU-Usage",
- "transform": "table",
- "type": "table"
- },
- {
- "columns": [],
- "datasource": "glx_benchmarker",
- "fontSize": "100%",
- "gridPos": {
- "h": 7,
- "w": 12,
- "x": 12,
- "y": 35
- },
- "id": 15,
- "links": [],
- "options": {},
- "pageSize": null,
- "scroll": true,
- "showHeader": true,
- "sort": {
- "col": 0,
- "desc": true
- },
- "styles": [
- {
- "alias": "Time",
- "dateFormat": "YYYY-MM-DD HH:mm:ss",
- "pattern": "Time",
- "type": "hidden"
- },
- {
- "alias": "",
- "colorMode": null,
- "colors": [
- "rgba(245, 54, 54, 0.9)",
- "rgba(237, 129, 40, 0.89)",
- "rgba(50, 172, 45, 0.97)"
- ],
- "decimals": 2,
- "pattern": "/.*/",
- "thresholds": [],
- "type": "number",
- "unit": "ns"
- }
- ],
- "targets": [
- {
- "alias": "CPU-Time per Workflow",
- "groupBy": [
- {
- "params": [
- "benchmark_uid"
- ],
- "type": "tag"
- },
- {
- "params": [
- "destination_name"
- ],
- "type": "tag"
- },
- {
- "params": [
- "workflow_name"
- ],
- "type": "tag"
- }
- ],
- "measurement": "cpuacct.usage",
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT min(\"sum\"), max(\"sum\"), mean(\"sum\"), stddev(\"sum\") FROM (SELECT sum(\"value\") FROM \"cpuacct.usage\" WHERE (\"benchmark_type\" = '' AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND \"workflow_name\" =~ /$selected_workflow_name/ AND \"destination_name\" =~ /$selected_destination_name/) AND $timeFilter GROUP BY \"destination_name\", \"workflow_name\", \"benchmark_uuid\", \"history_name\") GROUP BY \"benchmark_uuid\", destination_name, workflow_name",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "table",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "sum"
- }
- ]
- ],
- "tags": [
- {
- "key": "run_type",
- "operator": "=",
- "value": "warm"
- },
- {
- "condition": "AND",
- "key": "benchmark_type",
- "operator": "=",
- "value": ""
- }
- ]
- }
- ],
- "timeFrom": null,
- "timeShift": null,
- "title": "Average CPU-Usage per Benchmark, Destination and Workflow",
- "transform": "table",
- "type": "table"
- },
- {
- "columns": [],
- "datasource": "glx_benchmarker",
- "fontSize": "100%",
- "gridPos": {
- "h": 6,
- "w": 12,
- "x": 0,
- "y": 42
- },
- "id": 42,
- "links": [],
- "options": {},
- "pageSize": null,
- "scroll": true,
- "showHeader": true,
- "sort": {
- "col": 0,
- "desc": true
- },
- "styles": [
- {
- "alias": "Time",
- "dateFormat": "YYYY-MM-DD HH:mm:ss",
- "pattern": "Time",
- "type": "hidden"
- },
- {
- "alias": "",
- "colorMode": null,
- "colors": [
- "rgba(245, 54, 54, 0.9)",
- "rgba(237, 129, 40, 0.89)",
- "rgba(50, 172, 45, 0.97)"
- ],
- "decimals": 2,
- "pattern": "/.*/",
- "thresholds": [],
- "type": "number",
- "unit": "ns"
- }
- ],
- "targets": [
- {
- "alias": "CPU-Time per Workflow",
- "groupBy": [
- {
- "params": [
- "benchmark_uid"
- ],
- "type": "tag"
- },
- {
- "params": [
- "destination_name"
- ],
- "type": "tag"
- },
- {
- "params": [
- "workflow_name"
- ],
- "type": "tag"
- }
- ],
- "measurement": "cpuacct.usage",
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT mean(\"sum\") FROM (SELECT sum(\"value\") FROM \"cpuacct.usage\" WHERE (\"benchmark_type\" = '' AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND \"workflow_name\" =~ /$selected_workflow_name/ AND \"destination_name\" =~ /$selected_destination_name/) AND $timeFilter GROUP BY \"destination_name\", \"workflow_name\", \"history_name\") WHERE $timeFilter GROUP BY destination_name, workflow_name",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "table",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "sum"
- }
- ]
- ],
- "tags": [
- {
- "key": "run_type",
- "operator": "=",
- "value": "warm"
- },
- {
- "condition": "AND",
- "key": "benchmark_type",
- "operator": "=",
- "value": ""
- }
- ]
- }
- ],
- "timeFrom": null,
- "timeShift": null,
- "title": "Average CPU-Usage per Destination and Workflow",
- "transform": "table",
- "type": "table"
- },
- {
- "aliasColors": {},
- "bars": true,
- "dashLength": 10,
- "dashes": false,
- "datasource": "glx_benchmarker",
- "fill": 1,
- "gridPos": {
- "h": 6,
- "w": 12,
- "x": 12,
- "y": 42
- },
- "id": 26,
- "legend": {
- "alignAsTable": false,
- "avg": false,
- "current": false,
- "max": false,
- "min": false,
- "rightSide": false,
- "show": false,
- "total": false,
- "values": false
- },
- "lines": false,
- "linewidth": 1,
- "links": [],
- "nullPointMode": "null",
- "options": {},
- "paceLength": 10,
- "percentage": false,
- "pointradius": 2,
- "points": false,
- "renderer": "flot",
- "seriesOverrides": [],
- "spaceLength": 10,
- "stack": false,
- "steppedLine": false,
- "targets": [
- {
- "alias": "$tag_destination_name: $tag_workflow_name",
- "groupBy": [
- {
- "params": [
- "$__interval"
- ],
- "type": "time"
- },
- {
- "params": [
- "null"
- ],
- "type": "fill"
- }
- ],
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT mean(\"sum\") FROM (SELECT sum(\"value\") FROM \"cpuacct.usage\" WHERE (\"benchmark_type\" = '' AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND \"workflow_name\" =~ /$selected_workflow_name/ AND \"destination_name\" =~ /$selected_destination_name/) AND $timeFilter GROUP BY \"destination_name\", \"workflow_name\", \"history_name\") WHERE $timeFilter GROUP BY destination_name, workflow_name",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "time_series",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "mean"
- }
- ]
- ],
- "tags": []
- }
- ],
- "thresholds": [],
- "timeFrom": null,
- "timeRegions": [],
- "timeShift": null,
- "title": "Average CPU-Time per Destination and Workflow",
- "tooltip": {
- "shared": false,
- "sort": 0,
- "value_type": "individual"
- },
- "type": "graph",
- "xaxis": {
- "buckets": null,
- "mode": "series",
- "name": null,
- "show": true,
- "values": [
- "total"
- ]
- },
- "yaxes": [
- {
- "format": "ns",
- "label": null,
- "logBase": 1,
- "max": null,
- "min": null,
- "show": true
- },
- {
- "format": "short",
- "label": null,
- "logBase": 1,
- "max": null,
- "min": null,
- "show": false
- }
- ],
- "yaxis": {
- "align": false,
- "alignLevel": null
- }
- },
- {
- "collapsed": false,
- "gridPos": {
- "h": 1,
- "w": 24,
- "x": 0,
- "y": 48
- },
- "id": 21,
- "panels": [],
- "title": "Staging Time",
- "type": "row"
- },
- {
- "columns": [],
- "datasource": "glx_benchmarker",
- "fontSize": "100%",
- "gridPos": {
- "h": 6,
- "w": 12,
- "x": 0,
- "y": 49
- },
- "id": 29,
- "links": [],
- "options": {},
- "pageSize": null,
- "scroll": true,
- "showHeader": true,
- "sort": {
- "col": 0,
- "desc": true
- },
- "styles": [
- {
- "alias": "Time",
- "dateFormat": "YYYY-MM-DD HH:mm:ss",
- "pattern": "Time",
- "type": "hidden"
- },
- {
- "alias": "",
- "colorMode": null,
- "colors": [
- "rgba(245, 54, 54, 0.9)",
- "rgba(237, 129, 40, 0.89)",
- "rgba(50, 172, 45, 0.97)"
- ],
- "decimals": 2,
- "pattern": "/.*/",
- "thresholds": [],
- "type": "number",
- "unit": "s"
- }
- ],
- "targets": [
- {
- "groupBy": [
- {
- "params": [
- "$__interval"
- ],
- "type": "time"
- },
- {
- "params": [
- "null"
- ],
- "type": "fill"
- }
- ],
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT sum(\"value\") FROM \"staging_time\" WHERE (\"benchmark_type\" = '' AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND \"workflow_name\" =~ /$selected_workflow_name/ AND \"destination_name\" =~ /$selected_destination_name/) AND $timeFilter GROUP BY \"destination_name\", \"workflow_name\", \"benchmark_uuid\", \"history_name\"",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "table",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "mean"
- }
- ]
- ],
- "tags": []
- }
- ],
- "timeFrom": null,
- "timeShift": null,
- "title": "Staging-Time per Workflow-Run",
- "transform": "table",
- "type": "table"
- },
- {
- "columns": [],
- "datasource": "glx_benchmarker",
- "fontSize": "100%",
- "gridPos": {
- "h": 6,
- "w": 12,
- "x": 12,
- "y": 49
- },
- "id": 12,
- "links": [],
- "options": {},
- "pageSize": null,
- "scroll": true,
- "showHeader": true,
- "sort": {
- "col": 0,
- "desc": true
- },
- "styles": [
- {
- "alias": "Time",
- "dateFormat": "YYYY-MM-DD HH:mm:ss",
- "pattern": "Time",
- "type": "hidden"
- },
- {
- "alias": "",
- "colorMode": null,
- "colors": [
- "rgba(245, 54, 54, 0.9)",
- "rgba(237, 129, 40, 0.89)",
- "rgba(50, 172, 45, 0.97)"
- ],
- "decimals": 2,
- "pattern": "/.*/",
- "thresholds": [],
- "type": "number",
- "unit": "s"
- }
- ],
- "targets": [
- {
- "alias": "CPU-Time per Workflow",
- "groupBy": [
- {
- "params": [
- "benchmark_uid"
- ],
- "type": "tag"
- },
- {
- "params": [
- "destination_name"
- ],
- "type": "tag"
- },
- {
- "params": [
- "workflow_name"
- ],
- "type": "tag"
- }
- ],
- "measurement": "staging_time",
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT min(\"sum\"), max(\"sum\"), mean(\"sum\"), stddev(\"sum\") FROM (SELECT sum(\"value\") FROM \"staging_time\" WHERE (\"benchmark_type\" = '' AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND \"workflow_name\" =~ /$selected_workflow_name/ AND \"destination_name\" =~ /$selected_destination_name/) AND $timeFilter GROUP BY \"destination_name\", \"workflow_name\", \"benchmark_uuid\", \"history_name\") GROUP BY benchmark_uuid, destination_name, workflow_name",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "table",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "sum"
- }
- ]
- ],
- "tags": [
- {
- "key": "run_type",
- "operator": "=",
- "value": "warm"
- },
- {
- "condition": "AND",
- "key": "benchmark_type",
- "operator": "=",
- "value": ""
- }
- ]
- }
- ],
- "timeFrom": null,
- "timeShift": null,
- "title": "Average Staging-Time per Benchmark, Destination and Workflow",
- "transform": "table",
- "type": "table"
- },
- {
- "columns": [],
- "datasource": "glx_benchmarker",
- "fontSize": "100%",
- "gridPos": {
- "h": 7,
- "w": 12,
- "x": 0,
- "y": 55
- },
- "id": 40,
- "links": [],
- "options": {},
- "pageSize": null,
- "scroll": true,
- "showHeader": true,
- "sort": {
- "col": 0,
- "desc": true
- },
- "styles": [
- {
- "alias": "Time",
- "dateFormat": "YYYY-MM-DD HH:mm:ss",
- "pattern": "Time",
- "type": "hidden"
- },
- {
- "alias": "",
- "colorMode": null,
- "colors": [
- "rgba(245, 54, 54, 0.9)",
- "rgba(237, 129, 40, 0.89)",
- "rgba(50, 172, 45, 0.97)"
- ],
- "decimals": 2,
- "pattern": "/.*/",
- "thresholds": [],
- "type": "number",
- "unit": "s"
- }
- ],
- "targets": [
- {
- "alias": "CPU-Time per Workflow",
- "groupBy": [
- {
- "params": [
- "benchmark_uid"
- ],
- "type": "tag"
- },
- {
- "params": [
- "destination_name"
- ],
- "type": "tag"
- },
- {
- "params": [
- "workflow_name"
- ],
- "type": "tag"
- }
- ],
- "measurement": "staging_time",
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT min(\"sum\"), max(\"sum\"), mean(\"sum\"), stddev(\"sum\") FROM (SELECT sum(\"value\") FROM \"staging_time\" WHERE (\"benchmark_type\" = '' AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND \"workflow_name\" =~ /$selected_workflow_name/ AND \"destination_name\" =~ /$selected_destination_name/) AND $timeFilter GROUP BY \"destination_name\", \"workflow_name\", \"history_name\") GROUP BY destination_name, workflow_name",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "table",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "sum"
- }
- ]
- ],
- "tags": [
- {
- "key": "run_type",
- "operator": "=",
- "value": "warm"
- },
- {
- "condition": "AND",
- "key": "benchmark_type",
- "operator": "=",
- "value": ""
- }
- ]
- }
- ],
- "timeFrom": null,
- "timeShift": null,
- "title": "Average Staging-Time per Destination and Workflow",
- "transform": "table",
- "type": "table"
- },
- {
- "aliasColors": {},
- "bars": true,
- "dashLength": 10,
- "dashes": false,
- "datasource": "glx_benchmarker",
- "fill": 1,
- "gridPos": {
- "h": 7,
- "w": 12,
- "x": 12,
- "y": 55
- },
- "id": 27,
- "legend": {
- "alignAsTable": false,
- "avg": false,
- "current": false,
- "max": false,
- "min": false,
- "rightSide": false,
- "show": false,
- "total": false,
- "values": false
- },
- "lines": false,
- "linewidth": 1,
- "links": [],
- "nullPointMode": "null",
- "options": {},
- "paceLength": 10,
- "percentage": false,
- "pointradius": 2,
- "points": false,
- "renderer": "flot",
- "seriesOverrides": [],
- "spaceLength": 10,
- "stack": false,
- "steppedLine": false,
- "targets": [
- {
- "alias": "$tag_destination_name: $tag_workflow_name",
- "groupBy": [
- {
- "params": [
- "$__interval"
- ],
- "type": "time"
- },
- {
- "params": [
- "null"
- ],
- "type": "fill"
- }
- ],
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT mean(\"sum\") FROM (SELECT sum(\"value\") FROM \"staging_time\" WHERE (\"benchmark_type\" = '' AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND \"workflow_name\" =~ /$selected_workflow_name/ AND \"destination_name\" =~ /$selected_destination_name/) AND $timeFilter GROUP BY \"destination_name\", \"workflow_name\", \"benchmark_uuid\", \"history_name\") WHERE $timeFilter GROUP BY benchmark_uuid, destination_name, workflow_name",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "time_series",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "mean"
- }
- ]
- ],
- "tags": []
- }
- ],
- "thresholds": [],
- "timeFrom": null,
- "timeRegions": [],
- "timeShift": null,
- "title": "Average Staging-Time per Destination and Workflow",
- "tooltip": {
- "shared": false,
- "sort": 0,
- "value_type": "individual"
- },
- "type": "graph",
- "xaxis": {
- "buckets": null,
- "mode": "series",
- "name": null,
- "show": true,
- "values": [
- "total"
- ]
- },
- "yaxes": [
- {
- "format": "s",
- "label": null,
- "logBase": 1,
- "max": null,
- "min": null,
- "show": true
- },
- {
- "format": "short",
- "label": null,
- "logBase": 1,
- "max": null,
- "min": null,
- "show": false
- }
- ],
- "yaxis": {
- "align": false,
- "alignLevel": null
- }
- },
- {
- "columns": [],
- "datasource": "glx_benchmarker",
- "fontSize": "100%",
- "gridPos": {
- "h": 7,
- "w": 12,
- "x": 0,
- "y": 62
- },
- "id": 44,
- "links": [],
- "options": {},
- "pageSize": null,
- "scroll": true,
- "showHeader": true,
- "sort": {
- "col": 0,
- "desc": true
- },
- "styles": [
- {
- "alias": "Time",
- "dateFormat": "YYYY-MM-DD HH:mm:ss",
- "pattern": "Time",
- "type": "hidden"
- },
- {
- "alias": "",
- "colorMode": null,
- "colors": [
- "rgba(245, 54, 54, 0.9)",
- "rgba(237, 129, 40, 0.89)",
- "rgba(50, 172, 45, 0.97)"
- ],
- "decimals": 2,
- "pattern": "/.*/",
- "thresholds": [],
- "type": "number",
- "unit": "s"
- }
- ],
- "targets": [
- {
- "alias": "CPU-Time per Workflow",
- "groupBy": [
- {
- "params": [
- "benchmark_uid"
- ],
- "type": "tag"
- },
- {
- "params": [
- "destination_name"
- ],
- "type": "tag"
- },
- {
- "params": [
- "workflow_name"
- ],
- "type": "tag"
- }
- ],
- "measurement": "staging_time",
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT min(\"value\"), max(\"value\"), mean(\"value\"), stddev(\"value\") FROM (SELECT value FROM \"staging_time\" WHERE (\"benchmark_type\" = '' AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND \"workflow_name\" =~ /$selected_workflow_name/ AND \"destination_name\" =~ /$selected_destination_name/) AND $timeFilter GROUP BY \"destination_name\", \"workflow_name\", \"history_name\") GROUP BY destination_name, workflow_name",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "table",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "sum"
- }
- ]
- ],
- "tags": [
- {
- "key": "run_type",
- "operator": "=",
- "value": "warm"
- },
- {
- "condition": "AND",
- "key": "benchmark_type",
- "operator": "=",
- "value": ""
- }
- ]
- }
- ],
- "timeFrom": null,
- "timeShift": null,
- "title": "Average Staging-Time per Destination and Job",
- "transform": "table",
- "type": "table"
- },
- {
- "aliasColors": {},
- "bars": true,
- "dashLength": 10,
- "dashes": false,
- "datasource": "glx_benchmarker",
- "fill": 1,
- "gridPos": {
- "h": 7,
- "w": 12,
- "x": 12,
- "y": 62
- },
- "id": 45,
- "legend": {
- "alignAsTable": false,
- "avg": false,
- "current": false,
- "max": false,
- "min": false,
- "rightSide": false,
- "show": false,
- "total": false,
- "values": false
- },
- "lines": false,
- "linewidth": 1,
- "links": [],
- "nullPointMode": "null",
- "options": {},
- "paceLength": 10,
- "percentage": false,
- "pointradius": 2,
- "points": false,
- "renderer": "flot",
- "seriesOverrides": [],
- "spaceLength": 10,
- "stack": false,
- "steppedLine": false,
- "targets": [
- {
- "alias": "$tag_destination_name: $tag_workflow_name",
- "groupBy": [
- {
- "params": [
- "$__interval"
- ],
- "type": "time"
- },
- {
- "params": [
- "null"
- ],
- "type": "fill"
- }
- ],
- "orderByTime": "ASC",
- "policy": "default",
- "query": "SELECT mean(\"value\") FROM (SELECT value FROM \"staging_time\" WHERE (\"benchmark_type\" = '' AND \"benchmark_uuid\" =~ /$selected_benchmark_uuid/ AND \"workflow_name\" =~ /$selected_workflow_name/ AND \"destination_name\" =~ /$selected_destination_name/) AND $timeFilter GROUP BY \"destination_name\", \"workflow_name\", \"history_name\") WHERE $timeFilter GROUP BY destination_name, workflow_name",
- "rawQuery": true,
- "refId": "A",
- "resultFormat": "time_series",
- "select": [
- [
- {
- "params": [
- "value"
- ],
- "type": "field"
- },
- {
- "params": [],
- "type": "mean"
- }
- ]
- ],
- "tags": []
- }
- ],
- "thresholds": [],
- "timeFrom": null,
- "timeRegions": [],
- "timeShift": null,
- "title": "Average Staging-Time per Destination and Job",
- "tooltip": {
- "shared": false,
- "sort": 0,
- "value_type": "individual"
- },
- "type": "graph",
- "xaxis": {
- "buckets": null,
- "mode": "series",
- "name": null,
- "show": true,
- "values": [
- "total"
- ]
- },
- "yaxes": [
- {
- "format": "s",
- "label": null,
- "logBase": 1,
- "max": null,
- "min": null,
- "show": true
- },
- {
- "format": "short",
- "label": null,
- "logBase": 1,
- "max": null,
- "min": null,
- "show": false
- }
- ],
- "yaxis": {
- "align": false,
- "alignLevel": null
- }
- }
- ],
- "refresh": "",
- "schemaVersion": 18,
- "style": "dark",
- "tags": [],
- "templating": {
- "list": [
- {
- "allValue": null,
- "current": {
- "text": "Docking_with_VinaTest_2019-07-23 09:29:52.930519",
- "value": [
- "Docking_with_VinaTest_2019-07-23 09:29:52.930519"
- ]
- },
- "datasource": "glx_benchmarker",
- "definition": "SELECT benchmark_uuid , value FROM \"runtime_seconds\" WHERE \"benchmark_type\" = ''",
- "hide": 0,
- "includeAll": true,
- "label": "Benchmark UUID",
- "multi": true,
- "name": "selected_benchmark_uuid",
- "options": [],
- "query": "SELECT benchmark_uuid , value FROM \"runtime_seconds\" WHERE \"benchmark_type\" = ''",
- "refresh": 2,
- "regex": "",
- "skipUrlSync": false,
- "sort": 4,
- "tagValuesQuery": "",
- "tags": [],
- "tagsQuery": "",
- "type": "query",
- "useTags": false
- },
- {
- "allValue": null,
- "current": {
- "text": "All",
- "value": [
- "$__all"
- ]
- },
- "datasource": "glx_benchmarker",
- "definition": "SELECT workflow_name, value FROM \"runtime_seconds\" WHERE \"benchmark_type\" = '' GROUP BY workflow_name",
- "hide": 0,
- "includeAll": true,
- "label": "Workflow",
- "multi": true,
- "name": "selected_workflow_name",
- "options": [],
- "query": "SELECT workflow_name, value FROM \"runtime_seconds\" WHERE \"benchmark_type\" = '' GROUP BY workflow_name",
- "refresh": 2,
- "regex": "",
- "skipUrlSync": false,
- "sort": 1,
- "tagValuesQuery": "",
- "tags": [],
- "tagsQuery": "",
- "type": "query",
- "useTags": false
- },
- {
- "allValue": null,
- "current": {
- "text": "All",
- "value": [
- "$__all"
- ]
- },
- "datasource": "glx_benchmarker",
- "definition": "SELECT destination_name, value FROM \"runtime_seconds\" WHERE \"benchmark_type\" = '' GROUP BY destination_name",
- "hide": 0,
- "includeAll": true,
- "label": "Destination",
- "multi": true,
- "name": "selected_destination_name",
- "options": [],
- "query": "SELECT destination_name, value FROM \"runtime_seconds\" WHERE \"benchmark_type\" = '' GROUP BY destination_name",
- "refresh": 2,
- "regex": "",
- "skipUrlSync": false,
- "sort": 0,
- "tagValuesQuery": "",
- "tags": [],
- "tagsQuery": "",
- "type": "query",
- "useTags": false
- },
- {
- "allValue": null,
- "current": {
- "text": "workflow_status",
- "value": "workflow_status"
- },
- "datasource": "glx_benchmarker",
- "definition": "SHOW MEASUREMENTS ",
- "hide": 0,
- "includeAll": false,
- "label": "Measurement",
- "multi": false,
- "name": "selected_measurement",
- "options": [],
- "query": "SHOW MEASUREMENTS ",
- "refresh": 2,
- "regex": "",
- "skipUrlSync": false,
- "sort": 0,
- "tagValuesQuery": "",
- "tags": [],
- "tagsQuery": "",
- "type": "query",
- "useTags": false
- }
- ]
- },
- "time": {
- "from": "now-3h",
- "to": "now"
- },
- "timepicker": {
- "refresh_intervals": [
- "5s",
- "10s",
- "30s",
- "1m",
- "5m",
- "15m",
- "30m",
- "1h",
- "2h",
- "1d"
- ],
- "time_options": [
- "5m",
- "15m",
- "1h",
- "6h",
- "12h",
- "24h",
- "2d",
- "7d",
- "30d"
- ]
- },
- "timezone": "",
- "title": "Destination-Comparison Benchmarks",
- "uid": "Ve6R3OnZz",
- "version": 27
-}
\ No newline at end of file
diff --git a/grafana/provisioning/dashboards/all.yml b/grafana/provisioning/dashboards/all.yml
deleted file mode 100644
index e23a61d1..00000000
--- a/grafana/provisioning/dashboards/all.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-- name: 'default'
- org_id: 1
- folder: ''
- type: 'file'
- options:
- folder: '/var/lib/grafana/dashboards'
\ No newline at end of file
diff --git a/grafana/provisioning/datasources/all.yml b/grafana/provisioning/datasources/all.yml
deleted file mode 100644
index 049d99a5..00000000
--- a/grafana/provisioning/datasources/all.yml
+++ /dev/null
@@ -1,10 +0,0 @@
-apiVersion: 1
-datasources:
- - name: glx_benchmarker
- type: influxdb
- access: proxy
- url: http://influxdb:8086
- user: glx_benchmarker
- password: glx_benchmarker
- database: glx_benchmarker
- isDefault: true
\ No newline at end of file
diff --git a/poetry.lock b/poetry.lock
new file mode 100644
index 00000000..036da981
--- /dev/null
+++ b/poetry.lock
@@ -0,0 +1,2827 @@
+[[package]]
+name = "aiohttp"
+version = "3.8.1"
+description = "Async http client/server framework (asyncio)"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+aiosignal = ">=1.1.2"
+async-timeout = ">=4.0.0a3,<5.0"
+attrs = ">=17.3.0"
+charset-normalizer = ">=2.0,<3.0"
+frozenlist = ">=1.1.1"
+multidict = ">=4.5,<7.0"
+yarl = ">=1.0,<2.0"
+
+[package.extras]
+speedups = ["aiodns", "brotli", "cchardet"]
+
+[[package]]
+name = "aiosignal"
+version = "1.2.0"
+description = "aiosignal: a list of registered asynchronous callbacks"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+frozenlist = ">=1.1.0"
+
+[[package]]
+name = "allure-python-commons"
+version = "2.9.45"
+description = "Common module for integrate allure with python-based frameworks"
+category = "main"
+optional = false
+python-versions = ">=3.5"
+
+[package.dependencies]
+attrs = ">=16.0.0"
+pluggy = ">=0.4.0"
+six = ">=1.9.0"
+
+[[package]]
+name = "ansible"
+version = "5.7.1"
+description = "Radically simple IT automation"
+category = "main"
+optional = false
+python-versions = ">=3.8"
+
+[package.dependencies]
+ansible-core = ">=2.12.5,<2.13.0"
+
+[[package]]
+name = "ansible-core"
+version = "2.12.5"
+description = "Radically simple IT automation"
+category = "main"
+optional = false
+python-versions = ">=3.8"
+
+[package.dependencies]
+cryptography = "*"
+jinja2 = "*"
+packaging = "*"
+PyYAML = "*"
+resolvelib = ">=0.5.3,<0.6.0"
+
+[[package]]
+name = "appdirs"
+version = "1.4.4"
+description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "argcomplete"
+version = "2.0.0"
+description = "Bash tab completion for argparse"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.extras]
+test = ["coverage", "flake8", "pexpect", "wheel"]
+
+[[package]]
+name = "async-timeout"
+version = "4.0.2"
+description = "Timeout context manager for asyncio programs"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "attrs"
+version = "21.4.0"
+description = "Classes Without Boilerplate"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[package.extras]
+dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"]
+docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"]
+tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"]
+tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"]
+
+[[package]]
+name = "autopage"
+version = "0.5.0"
+description = "A library to provide automatic paging for console output"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "bagit"
+version = "1.8.1"
+description = "Create and validate BagIt packages"
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "bcrypt"
+version = "3.2.2"
+description = "Modern password hashing for your software and your servers"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+cffi = ">=1.1"
+
+[package.extras]
+tests = ["pytest (>=3.2.1,!=3.3.0)"]
+typecheck = ["mypy"]
+
+[[package]]
+name = "beautifulsoup4"
+version = "4.11.1"
+description = "Screen-scraping library"
+category = "main"
+optional = false
+python-versions = ">=3.6.0"
+
+[package.dependencies]
+soupsieve = ">1.2"
+
+[package.extras]
+html5lib = ["html5lib"]
+lxml = ["lxml"]
+
+[[package]]
+name = "bioblend"
+version = "0.18.0"
+description = "Galaxy and CloudMan API library"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+develop = false
+
+[package.dependencies]
+boto = ">=2.9.7"
+pyyaml = "*"
+requests = ">=2.20.0"
+requests-toolbelt = ">=0.5.1,<0.9.0 || >0.9.0"
+tuspy = "*"
+typing-extensions = "*"
+
+[package.extras]
+testing = ["pytest"]
+
+[package.source]
+type = "git"
+url = "https://github.com/galaxyproject/bioblend.git"
+reference = "main"
+resolved_reference = "17c5e32f3c31416319337fe173569887cccd537e"
+
+[[package]]
+name = "black"
+version = "22.3.0"
+description = "The uncompromising code formatter."
+category = "dev"
+optional = false
+python-versions = ">=3.6.2"
+
+[package.dependencies]
+click = ">=8.0.0"
+mypy-extensions = ">=0.4.3"
+pathspec = ">=0.9.0"
+platformdirs = ">=2"
+tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
+
+[package.extras]
+colorama = ["colorama (>=0.4.3)"]
+d = ["aiohttp (>=3.7.4)"]
+jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
+uvloop = ["uvloop (>=0.15.2)"]
+
+[[package]]
+name = "bleach"
+version = "5.0.0"
+description = "An easy safelist-based HTML-sanitizing tool."
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+six = ">=1.9.0"
+webencodings = "*"
+
+[package.extras]
+css = ["tinycss2 (>=1.1.0)"]
+dev = ["pip-tools (==6.5.1)", "pytest (==7.1.1)", "flake8 (==4.0.1)", "tox (==3.24.5)", "sphinx (==4.3.2)", "twine (==4.0.0)", "wheel (==0.37.1)", "hashin (==0.17.0)", "black (==22.3.0)", "mypy (==0.942)"]
+
+[[package]]
+name = "boltons"
+version = "21.0.0"
+description = "When they're not builtins, they're boltons."
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "boto"
+version = "2.49.0"
+description = "Amazon Web Services Library"
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "boto3"
+version = "1.24.38"
+description = "The AWS SDK for Python"
+category = "main"
+optional = false
+python-versions = ">= 3.7"
+
+[package.dependencies]
+botocore = ">=1.27.38,<1.28.0"
+jmespath = ">=0.7.1,<2.0.0"
+s3transfer = ">=0.6.0,<0.7.0"
+
+[package.extras]
+crt = ["botocore[crt] (>=1.21.0,<2.0a0)"]
+
+[[package]]
+name = "botocore"
+version = "1.27.38"
+description = "Low-level, data-driven core of boto 3."
+category = "main"
+optional = false
+python-versions = ">= 3.7"
+
+[package.dependencies]
+jmespath = ">=0.7.1,<2.0.0"
+python-dateutil = ">=2.1,<3.0.0"
+urllib3 = ">=1.25.4,<1.27"
+
+[package.extras]
+crt = ["awscrt (==0.13.8)"]
+
+[[package]]
+name = "cachecontrol"
+version = "0.12.11"
+description = "httplib2 caching for requests"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+msgpack = ">=0.5.2"
+requests = "*"
+
+[package.extras]
+filecache = ["lockfile (>=0.9)"]
+redis = ["redis (>=2.10.5)"]
+
+[[package]]
+name = "certifi"
+version = "2021.10.8"
+description = "Python package for providing Mozilla's CA Bundle."
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "cffi"
+version = "1.15.0"
+description = "Foreign Function Interface for Python calling C code."
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+pycparser = "*"
+
+[[package]]
+name = "charset-normalizer"
+version = "2.0.12"
+description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
+category = "main"
+optional = false
+python-versions = ">=3.5.0"
+
+[package.extras]
+unicode_backport = ["unicodedata2"]
+
+[[package]]
+name = "click"
+version = "8.1.3"
+description = "Composable command line interface toolkit"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+colorama = {version = "*", markers = "platform_system == \"Windows\""}
+
+[[package]]
+name = "cliff"
+version = "3.10.1"
+description = "Command Line Interface Formulation Framework"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+autopage = ">=0.4.0"
+cmd2 = ">=1.0.0"
+pbr = ">=2.0.0,<2.1.0 || >2.1.0"
+PrettyTable = ">=0.7.2"
+pyparsing = ">=2.1.0"
+PyYAML = ">=3.12"
+stevedore = ">=2.0.1"
+
+[[package]]
+name = "cmd2"
+version = "2.4.1"
+description = "cmd2 - quickly build feature-rich and user-friendly interactive command line applications in Python"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+attrs = ">=16.3.0"
+pyperclip = ">=1.6"
+pyreadline3 = {version = "*", markers = "sys_platform == \"win32\""}
+wcwidth = ">=0.1.7"
+
+[package.extras]
+dev = ["codecov", "doc8", "flake8", "invoke", "mypy (==0.902)", "nox", "pytest (>=4.6)", "pytest-cov", "pytest-mock", "sphinx", "sphinx-rtd-theme", "sphinx-autobuild", "twine (>=1.11)"]
+test = ["codecov", "coverage", "pytest (>=4.6)", "pytest-cov", "pytest-mock", "gnureadline"]
+validate = ["flake8", "mypy (==0.902)", "types-pkg-resources"]
+
+[[package]]
+name = "colorama"
+version = "0.4.4"
+description = "Cross-platform colored terminal text."
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[[package]]
+name = "coloredlogs"
+version = "15.0.1"
+description = "Colored terminal output for Python's logging module"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[package.dependencies]
+humanfriendly = ">=9.1"
+
+[package.extras]
+cron = ["capturer (>=2.4)"]
+
+[[package]]
+name = "configparser"
+version = "5.2.0"
+description = "Updated configparser from Python 3.8 for Python 2.6+."
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.extras]
+docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "jaraco.tidelift (>=1.4)"]
+testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "types-backports", "pytest-black (>=0.3.7)", "pytest-mypy"]
+
+[[package]]
+name = "cryptography"
+version = "37.0.2"
+description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+cffi = ">=1.12"
+
+[package.extras]
+docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"]
+docstest = ["pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"]
+pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"]
+sdist = ["setuptools_rust (>=0.11.4)"]
+ssh = ["bcrypt (>=3.1.5)"]
+test = ["pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"]
+
+[[package]]
+name = "cwltool"
+version = "3.1.20220502060230"
+description = "Common workflow language reference implementation"
+category = "main"
+optional = false
+python-versions = ">=3.6, <4"
+
+[package.dependencies]
+argcomplete = "*"
+bagit = ">=1.6.4"
+coloredlogs = "*"
+mypy-extensions = "*"
+prov = "1.5.1"
+psutil = ">=5.6.6"
+pydot = ">=1.4.1"
+pyparsing = "!=3.0.2"
+rdflib = ">=4.2.2,<6.2.0"
+requests = ">=2.6.1"
+"ruamel.yaml" = ">=0.15,<0.17.22"
+schema-salad = ">=8.2.20211104054942,<9"
+shellescape = ">=3.4.1,<3.9"
+typing-extensions = "*"
+
+[package.extras]
+deps = ["galaxy-tool-util (>=21.1.0)"]
+
+[[package]]
+name = "debtcollector"
+version = "2.5.0"
+description = "A collection of Python deprecation patterns and strategies that help you collect your technical debt in a non-destructive manner."
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+wrapt = ">=1.7.0"
+
+[[package]]
+name = "decorator"
+version = "5.1.1"
+description = "Decorators for Humans"
+category = "main"
+optional = false
+python-versions = ">=3.5"
+
+[[package]]
+name = "distlib"
+version = "0.3.4"
+description = "Distribution utilities"
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "docutils"
+version = "0.18.1"
+description = "Docutils -- Python Documentation Utilities"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[[package]]
+name = "dogpile.cache"
+version = "1.1.5"
+description = "A caching front-end based on the Dogpile lock."
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+decorator = ">=4.0.0"
+stevedore = ">=3.0.0"
+
+[[package]]
+name = "ephemeris"
+version = "0.10.7"
+description = "Ephemeris is an opinionated library and set of scripts for managing the bootstrapping of Galaxy project plugins - tools, index data, and workflows."
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+bioblend = ">=0.10.0"
+galaxy-tool-util = ">=20.9.1"
+galaxy-util = ">=20.9.0"
+Jinja2 = "*"
+pysam = "*"
+PyYAML = "*"
+six = ">=1.9.0"
+
+[[package]]
+name = "filelock"
+version = "3.6.0"
+description = "A platform independent file lock."
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.extras]
+docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"]
+testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"]
+
+[[package]]
+name = "frozenlist"
+version = "1.3.0"
+description = "A list-like structure which implements collections.abc.MutableSequence"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "future"
+version = "0.18.2"
+description = "Clean single-source support for Python 3 and 2"
+category = "main"
+optional = false
+python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
+
+[[package]]
+name = "galaxy-containers"
+version = "21.9.0"
+description = "Galaxy Container Modeling and Interaction Abstractions"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+galaxy-util = "*"
+requests = "*"
+
+[package.extras]
+docker = ["docker"]
+
+[[package]]
+name = "galaxy-tool-util"
+version = "21.9.2"
+description = "Galaxy Tool and Tool Dependency Utilities"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+galaxy-containers = "*"
+galaxy-util = ">=20.1.0.dev0"
+lxml = "*"
+pydantic = "*"
+sortedcontainers = "*"
+typing-extensions = "*"
+
+[package.extras]
+edam = ["edam-ontology"]
+mulled = ["conda", "cytoolz", "jinja2", "whoosh"]
+
+[[package]]
+name = "galaxy-util"
+version = "21.9.0"
+description = "Galaxy Generic Utilities"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+bleach = "*"
+boltons = "*"
+docutils = "*"
+markupsafe = "*"
+packaging = "*"
+pycryptodome = "*"
+pyyaml = "*"
+requests = "*"
+routes = "*"
+six = ">=1.9.0"
+zipstream-new = "*"
+
+[package.extras]
+jstree = ["dictobj"]
+template = ["future", "cheetah3"]
+
+[[package]]
+name = "glob2"
+version = "0.7"
+description = "Version of the glob module that can capture patterns and supports recursive wildcards"
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "gxformat2"
+version = "0.15.0"
+description = "Galaxy Workflow Format 2 Descriptions"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+bioblend = "*"
+pyyaml = "*"
+six = ">=1.9.0"
+
+[[package]]
+name = "humanfriendly"
+version = "10.0"
+description = "Human friendly output for text interfaces using Python"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[package.dependencies]
+pyreadline3 = {version = "*", markers = "sys_platform == \"win32\" and python_version >= \"3.8\""}
+
+[[package]]
+name = "idna"
+version = "3.3"
+description = "Internationalized Domain Names in Applications (IDNA)"
+category = "main"
+optional = false
+python-versions = ">=3.5"
+
+[[package]]
+name = "iso8601"
+version = "1.0.2"
+description = "Simple module to parse ISO 8601 dates"
+category = "main"
+optional = false
+python-versions = ">=3.6.2,<4.0"
+
+[[package]]
+name = "isodate"
+version = "0.6.1"
+description = "An ISO 8601 date/time/duration parser and formatter"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+six = "*"
+
+[[package]]
+name = "isort"
+version = "5.10.1"
+description = "A Python utility / library to sort Python imports."
+category = "dev"
+optional = false
+python-versions = ">=3.6.1,<4.0"
+
+[package.extras]
+pipfile_deprecated_finder = ["pipreqs", "requirementslib"]
+requirements_deprecated_finder = ["pipreqs", "pip-api"]
+colors = ["colorama (>=0.4.3,<0.5.0)"]
+plugins = ["setuptools"]
+
+[[package]]
+name = "jinja2"
+version = "3.1.2"
+description = "A very fast and expressive template engine."
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+MarkupSafe = ">=2.0"
+
+[package.extras]
+i18n = ["Babel (>=2.7)"]
+
+[[package]]
+name = "jmespath"
+version = "1.0.0"
+description = "JSON Matching Expressions"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "jsonpatch"
+version = "1.32"
+description = "Apply JSON-Patches (RFC 6902)"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[package.dependencies]
+jsonpointer = ">=1.9"
+
+[[package]]
+name = "jsonpointer"
+version = "2.3"
+description = "Identify specific nodes in a JSON document (RFC 6901)"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[[package]]
+name = "keystoneauth1"
+version = "4.5.0"
+description = "Authentication Library for OpenStack Identity"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+iso8601 = ">=0.1.11"
+os-service-types = ">=1.2.0"
+pbr = ">=2.0.0,<2.1.0 || >2.1.0"
+requests = ">=2.14.2"
+six = ">=1.10.0"
+stevedore = ">=1.20.0"
+
+[package.extras]
+betamax = ["betamax (>=0.7.0)", "fixtures (>=3.0.0)", "mock (>=2.0.0)"]
+kerberos = ["requests-kerberos (>=0.8.0)"]
+oauth1 = ["oauthlib (>=0.6.2)"]
+saml2 = ["lxml (>=4.2.0)"]
+test = ["PyYAML (>=3.12)", "bandit (>=1.1.0,<1.6.0)", "betamax (>=0.7.0)", "coverage (>=4.0,!=4.4)", "fixtures (>=3.0.0)", "flake8-docstrings (==0.2.1.post1)", "flake8-import-order (>=0.17.1)", "hacking (>=3.0.1,<3.1.0)", "lxml (>=4.2.0)", "oauthlib (>=0.6.2)", "oslo.config (>=5.2.0)", "oslo.utils (>=3.33.0)", "oslotest (>=3.2.0)", "pycodestyle (>=2.0.0,<2.6.0)", "reno (>=3.1.0)", "requests-kerberos (>=0.8.0)", "requests-mock (>=1.2.0)", "stestr (>=1.0.0)", "testresources (>=2.0.0)", "testtools (>=2.2.0)"]
+
+[[package]]
+name = "lockfile"
+version = "0.12.2"
+description = "Platform-independent file locking module"
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "lxml"
+version = "4.8.0"
+description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API."
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*"
+
+[package.extras]
+cssselect = ["cssselect (>=0.7)"]
+html5 = ["html5lib"]
+htmlsoup = ["beautifulsoup4"]
+source = ["Cython (>=0.29.7)"]
+
+[[package]]
+name = "markupsafe"
+version = "2.1.1"
+description = "Safely add untrusted strings to HTML/XML markup."
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "mistune"
+version = "0.8.4"
+description = "The fastest markdown parser in pure Python"
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "msgpack"
+version = "1.0.3"
+description = "MessagePack (de)serializer."
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "multidict"
+version = "6.0.2"
+description = "multidict implementation"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "munch"
+version = "2.5.0"
+description = "A dot-accessible dictionary (a la JavaScript objects)"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+six = "*"
+
+[package.extras]
+testing = ["pytest", "coverage", "astroid (>=1.5.3,<1.6.0)", "pylint (>=1.7.2,<1.8.0)", "astroid (>=2.0)", "pylint (>=2.3.1,<2.4.0)"]
+yaml = ["PyYAML (>=5.1.0)"]
+
+[[package]]
+name = "mypy-extensions"
+version = "0.4.3"
+description = "Experimental type system extensions for programs checked with the mypy typechecker."
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "netaddr"
+version = "0.8.0"
+description = "A network address manipulation library for Python"
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "netifaces"
+version = "0.11.0"
+description = "Portable network interface information."
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "networkx"
+version = "2.8"
+description = "Python package for creating and manipulating graphs and networks"
+category = "main"
+optional = false
+python-versions = ">=3.8"
+
+[package.extras]
+default = ["numpy (>=1.19)", "scipy (>=1.8)", "matplotlib (>=3.4)", "pandas (>=1.3)"]
+developer = ["pre-commit (>=2.18)", "mypy (>=0.942)"]
+doc = ["sphinx (>=4.5)", "pydata-sphinx-theme (>=0.8.1)", "sphinx-gallery (>=0.10)", "numpydoc (>=1.2)", "pillow (>=9.1)", "nb2plots (>=0.6)", "texext (>=0.6.6)"]
+extra = ["lxml (>=4.6)", "pygraphviz (>=1.9)", "pydot (>=1.4.2)", "sympy (>=1.10)"]
+test = ["pytest (>=7.1)", "pytest-cov (>=3.0)", "codecov (>=2.1)"]
+
+[[package]]
+name = "openstacksdk"
+version = "0.61.0"
+description = "An SDK for building applications to work with OpenStack"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+appdirs = ">=1.3.0"
+cryptography = ">=2.7"
+decorator = ">=4.4.1"
+"dogpile.cache" = ">=0.6.5"
+iso8601 = ">=0.1.11"
+jmespath = ">=0.9.0"
+jsonpatch = ">=1.16,<1.20 || >1.20"
+keystoneauth1 = ">=3.18.0"
+munch = ">=2.1.0"
+netifaces = ">=0.10.4"
+os-service-types = ">=1.7.0"
+pbr = ">=2.0.0,<2.1.0 || >2.1.0"
+PyYAML = ">=3.13"
+requestsexceptions = ">=1.2.0"
+
+[[package]]
+name = "os-service-types"
+version = "1.7.0"
+description = "Python library for consuming OpenStack sevice-types-authority data"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+pbr = ">=2.0.0,<2.1.0 || >2.1.0"
+
+[[package]]
+name = "osc-lib"
+version = "2.5.0"
+description = "OpenStackClient Library"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+cliff = ">=3.2.0"
+keystoneauth1 = ">=3.14.0"
+openstacksdk = ">=0.15.0"
+"oslo.i18n" = ">=3.15.3"
+"oslo.utils" = ">=3.33.0"
+pbr = ">=2.0.0,<2.1.0 || >2.1.0"
+simplejson = ">=3.5.1"
+stevedore = ">=1.20.0"
+
+[[package]]
+name = "oslo.config"
+version = "8.8.0"
+description = "Oslo Configuration API"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+debtcollector = ">=1.2.0"
+netaddr = ">=0.7.18"
+"oslo.i18n" = ">=3.15.3"
+PyYAML = ">=5.1"
+requests = ">=2.18.0"
+rfc3986 = ">=1.2.0"
+stevedore = ">=1.20.0"
+
+[package.extras]
+rst_generator = ["rst2txt (>=1.1.0)", "sphinx (>=1.8.0,!=2.1.0)"]
+test = ["bandit (>=1.6.0,<1.7.0)", "coverage (>=4.0,!=4.4)", "fixtures (>=3.0.0)", "hacking (>=3.0.1,<3.1.0)", "mypy (>=0.720)", "oslo.log (>=3.36.0)", "oslotest (>=3.2.0)", "pre-commit (>=2.6.0)", "requests-mock (>=1.5.0)", "stestr (>=2.1.0)", "testscenarios (>=0.4)", "testtools (>=2.2.0)"]
+
+[[package]]
+name = "oslo.i18n"
+version = "5.1.0"
+description = "Oslo i18n library"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+pbr = ">=2.0.0,<2.1.0 || >2.1.0"
+
+[[package]]
+name = "oslo.serialization"
+version = "4.3.0"
+description = "Oslo Serialization library"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+msgpack = ">=0.5.2"
+"oslo.utils" = ">=3.33.0"
+pbr = ">=2.0.0,<2.1.0 || >2.1.0"
+pytz = ">=2013.6"
+
+[[package]]
+name = "oslo.utils"
+version = "4.13.0"
+description = "Oslo Utility library"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+debtcollector = ">=1.2.0"
+iso8601 = ">=0.1.11"
+netaddr = ">=0.7.18"
+netifaces = ">=0.10.4"
+"oslo.i18n" = ">=3.15.3"
+packaging = ">=20.4"
+pbr = ">=2.0.0,<2.1.0 || >2.1.0"
+pyparsing = ">=2.1.0"
+pytz = ">=2013.6"
+
+[[package]]
+name = "oyaml"
+version = "1.0"
+description = "Ordered YAML: drop-in replacement for PyYAML which preserves dict ordering"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+pyyaml = "*"
+
+[[package]]
+name = "packaging"
+version = "21.3"
+description = "Core utilities for Python packages"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
+
+[[package]]
+name = "paramiko"
+version = "2.10.4"
+description = "SSH2 protocol library"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+bcrypt = ">=3.1.3"
+cryptography = ">=2.5"
+pynacl = ">=1.0.1"
+six = "*"
+
+[package.extras]
+all = ["pyasn1 (>=0.1.7)", "pynacl (>=1.0.1)", "bcrypt (>=3.1.3)", "invoke (>=1.3)", "gssapi (>=1.4.1)", "pywin32 (>=2.1.8)"]
+ed25519 = ["pynacl (>=1.0.1)", "bcrypt (>=3.1.3)"]
+gssapi = ["pyasn1 (>=0.1.7)", "gssapi (>=1.4.1)", "pywin32 (>=2.1.8)"]
+invoke = ["invoke (>=1.3)"]
+
+[[package]]
+name = "pathspec"
+version = "0.9.0"
+description = "Utility library for gitignore style pattern matching of file paths."
+category = "dev"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
+
+[[package]]
+name = "pbr"
+version = "5.9.0"
+description = "Python Build Reasonableness"
+category = "main"
+optional = false
+python-versions = ">=2.6"
+
+[[package]]
+name = "planemo"
+version = "0.74.9"
+description = "Command-line utilities to assist in building tools for the Galaxy project (http://galaxyproject.org/)."
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+allure-python-commons = "*"
+BeautifulSoup4 = "*"
+bioblend = ">=0.14.0"
+click = "!=8.0.2"
+configparser = "*"
+cwltool = ">=1.0.20191225192155"
+docutils = "*"
+ephemeris = ">=0.10.3"
+galaxy-containers = "*"
+galaxy-tool-util = ">=21.1.0.dev4"
+galaxy-util = ">=20.5.0"
+glob2 = "*"
+gxformat2 = ">=0.12.0"
+jinja2 = "*"
+lxml = "*"
+oyaml = "*"
+pyaml = "*"
+pyyaml = "*"
+six = ">=1.7.0"
+tabulate = "*"
+virtualenv = "*"
+
+[[package]]
+name = "platformdirs"
+version = "2.5.2"
+description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.extras]
+docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"]
+test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"]
+
+[[package]]
+name = "pluggy"
+version = "1.0.0"
+description = "plugin and hook calling mechanisms for python"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.extras]
+dev = ["pre-commit", "tox"]
+testing = ["pytest", "pytest-benchmark"]
+
+[[package]]
+name = "prettytable"
+version = "3.3.0"
+description = "A simple Python library for easily displaying tabular data in a visually appealing ASCII table format"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+wcwidth = "*"
+
+[package.extras]
+tests = ["pytest", "pytest-cov", "pytest-lazy-fixture"]
+
+[[package]]
+name = "prov"
+version = "1.5.1"
+description = "A library for W3C Provenance Data Model supporting PROV-JSON, PROV-XML and PROV-O (RDF)"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+lxml = "*"
+networkx = "*"
+python-dateutil = "*"
+rdflib = ">=4.2.1"
+six = ">=1.9.0"
+
+[package.extras]
+dot = ["pydot (>=1.2.0)"]
+
+[[package]]
+name = "psutil"
+version = "5.9.0"
+description = "Cross-platform lib for process and system monitoring in Python."
+category = "main"
+optional = false
+python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[package.extras]
+test = ["ipaddress", "mock", "unittest2", "enum34", "pywin32", "wmi"]
+
+[[package]]
+name = "pyaml"
+version = "21.10.1"
+description = "PyYAML-based module to produce pretty and readable YAML-serialized data"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+PyYAML = "*"
+
+[[package]]
+name = "pycparser"
+version = "2.21"
+description = "C parser in Python"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[[package]]
+name = "pycryptodome"
+version = "3.14.1"
+description = "Cryptographic library for Python"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[[package]]
+name = "pydantic"
+version = "1.9.0"
+description = "Data validation and settings management using python 3.6 type hinting"
+category = "main"
+optional = false
+python-versions = ">=3.6.1"
+
+[package.dependencies]
+typing-extensions = ">=3.7.4.3"
+
+[package.extras]
+dotenv = ["python-dotenv (>=0.10.4)"]
+email = ["email-validator (>=1.0.3)"]
+
+[[package]]
+name = "pydot"
+version = "1.4.2"
+description = "Python interface to Graphviz's Dot"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[package.dependencies]
+pyparsing = ">=2.1.4"
+
+[[package]]
+name = "pynacl"
+version = "1.5.0"
+description = "Python binding to the Networking and Cryptography (NaCl) library"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+cffi = ">=1.4.1"
+
+[package.extras]
+docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"]
+tests = ["pytest (>=3.2.1,!=3.3.0)", "hypothesis (>=3.27.0)"]
+
+[[package]]
+name = "pyparsing"
+version = "3.0.8"
+description = "pyparsing module - Classes and methods to define and execute parsing grammars"
+category = "main"
+optional = false
+python-versions = ">=3.6.8"
+
+[package.extras]
+diagrams = ["railroad-diagrams", "jinja2"]
+
+[[package]]
+name = "pyperclip"
+version = "1.8.2"
+description = "A cross-platform clipboard module for Python. (Only handles plain text for now.)"
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "pyreadline3"
+version = "3.4.1"
+description = "A python implementation of GNU readline."
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "pysam"
+version = "0.19.0"
+description = "pysam"
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "pyserde"
+version = "0.6.0"
+description = "Yet another serialization library on top of dataclasses"
+category = "main"
+optional = false
+python-versions = ">=3.6.1,<4.0.0"
+
+[package.dependencies]
+jinja2 = "*"
+stringcase = "*"
+typing_inspect = ">=0.4.0"
+
+[package.extras]
+msgpack = ["msgpack"]
+all = ["msgpack", "toml", "pyyaml"]
+toml = ["toml"]
+yaml = ["pyyaml"]
+
+[[package]]
+name = "python-cinderclient"
+version = "8.3.0"
+description = "OpenStack Block Storage API Client Library"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+keystoneauth1 = ">=4.3.1"
+"oslo.i18n" = ">=5.0.1"
+"oslo.utils" = ">=4.8.0"
+pbr = ">=5.5.0"
+PrettyTable = ">=0.7.2"
+requests = ">=2.25.1"
+simplejson = ">=3.5.1"
+stevedore = ">=3.3.0"
+
+[[package]]
+name = "python-dateutil"
+version = "2.8.2"
+description = "Extensions to the standard Python datetime module"
+category = "main"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
+
+[package.dependencies]
+six = ">=1.5"
+
+[[package]]
+name = "python-keystoneclient"
+version = "4.4.0"
+description = "Client Library for OpenStack Identity"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+debtcollector = ">=1.2.0"
+keystoneauth1 = ">=3.4.0"
+"oslo.config" = ">=5.2.0"
+"oslo.i18n" = ">=3.15.3"
+"oslo.serialization" = ">=2.18.0,<2.19.1 || >2.19.1"
+"oslo.utils" = ">=3.33.0"
+pbr = ">=2.0.0,<2.1.0 || >2.1.0"
+requests = ">=2.14.2"
+six = ">=1.10.0"
+stevedore = ">=1.20.0"
+
+[[package]]
+name = "python-novaclient"
+version = "17.7.0"
+description = "Client library for OpenStack Compute API"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+iso8601 = ">=0.1.11"
+keystoneauth1 = ">=3.5.0"
+"oslo.i18n" = ">=3.15.3"
+"oslo.serialization" = ">=2.18.0,<2.19.1 || >2.19.1"
+"oslo.utils" = ">=3.33.0"
+pbr = ">=2.0.0,<2.1.0 || >2.1.0"
+PrettyTable = ">=0.7.2"
+stevedore = ">=2.0.1"
+
+[[package]]
+name = "python-openstackclient"
+version = "5.8.0"
+description = "OpenStack Command-line Client"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+cliff = ">=3.5.0"
+iso8601 = ">=0.1.11"
+openstacksdk = ">=0.61.0"
+osc-lib = ">=2.3.0"
+"oslo.i18n" = ">=3.15.3"
+"oslo.utils" = ">=3.33.0"
+pbr = ">=2.0.0,<2.1.0 || >2.1.0"
+python-cinderclient = ">=3.3.0"
+python-keystoneclient = ">=3.22.0"
+python-novaclient = ">=17.0.0"
+stevedore = ">=2.0.1"
+
+[[package]]
+name = "pytz"
+version = "2022.1"
+description = "World timezone definitions, modern and historical"
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "pyyaml"
+version = "6.0"
+description = "YAML parser and emitter for Python"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "rdflib"
+version = "6.1.1"
+description = "RDFLib is a Python library for working with RDF, a simple yet powerful language for representing information."
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+isodate = "*"
+pyparsing = "*"
+
+[package.extras]
+docs = ["sphinx (<5)", "sphinxcontrib-apidoc"]
+html = ["html5lib"]
+tests = ["berkeleydb", "html5lib", "networkx", "pytest", "pytest-cov", "pytest-subtests"]
+
+[[package]]
+name = "repoze.lru"
+version = "0.7"
+description = "A tiny LRU cache implementation and decorator"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.extras]
+docs = ["sphinx"]
+testing = ["coverage", "nose"]
+
+[[package]]
+name = "requests"
+version = "2.27.1"
+description = "Python HTTP for Humans."
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
+
+[package.dependencies]
+certifi = ">=2017.4.17"
+charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""}
+idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""}
+urllib3 = ">=1.21.1,<1.27"
+
+[package.extras]
+socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
+use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"]
+
+[[package]]
+name = "requests-toolbelt"
+version = "0.9.1"
+description = "A utility belt for advanced users of python-requests"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+requests = ">=2.0.1,<3.0.0"
+
+[[package]]
+name = "requestsexceptions"
+version = "1.4.0"
+description = "Import exceptions from potentially bundled packages in requests."
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "resolvelib"
+version = "0.5.5"
+description = "Resolve abstract dependencies into concrete ones"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.extras]
+examples = ["html5lib", "packaging", "pygraphviz", "requests"]
+lint = ["black", "flake8"]
+release = ["setl", "towncrier"]
+test = ["commentjson", "packaging", "pytest"]
+
+[[package]]
+name = "rfc3986"
+version = "2.0.0"
+description = "Validating URI References per RFC 3986"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.extras]
+idna2008 = ["idna"]
+
+[[package]]
+name = "routes"
+version = "2.5.1"
+description = "Routing Recognition and Generation Tools"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+"repoze.lru" = ">=0.3"
+six = "*"
+
+[package.extras]
+docs = ["sphinx", "webob"]
+middleware = ["webob"]
+
+[[package]]
+name = "ruamel.yaml"
+version = "0.17.21"
+description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order"
+category = "main"
+optional = false
+python-versions = ">=3"
+
+[package.dependencies]
+"ruamel.yaml.clib" = {version = ">=0.2.6", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.11\""}
+
+[package.extras]
+docs = ["ryd"]
+jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"]
+
+[[package]]
+name = "ruamel.yaml.clib"
+version = "0.2.6"
+description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml"
+category = "main"
+optional = false
+python-versions = ">=3.5"
+
+[[package]]
+name = "s3transfer"
+version = "0.6.0"
+description = "An Amazon S3 Transfer Manager"
+category = "main"
+optional = false
+python-versions = ">= 3.7"
+
+[package.dependencies]
+botocore = ">=1.12.36,<2.0a.0"
+
+[package.extras]
+crt = ["botocore[crt] (>=1.20.29,<2.0a.0)"]
+
+[[package]]
+name = "schema-salad"
+version = "8.2.20220204150214"
+description = "Schema Annotations for Linked Avro Data (SALAD)"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+CacheControl = ">=0.11.7,<0.13"
+lockfile = ">=0.9"
+mistune = ">=0.8.1,<0.9"
+rdflib = ">=4.2.2,<7.0.0"
+requests = ">=1.0"
+"ruamel.yaml" = ">=0.12.4,<0.16.6 || >0.16.6,<0.18"
+
+[package.extras]
+docs = ["sphinx (>=2.2)", "sphinx-rtd-theme", "pytest (<7)"]
+pycodegen = ["black"]
+
+[[package]]
+name = "shellescape"
+version = "3.8.1"
+description = "Shell escape a string to safely use it as a token in a shell command (backport of cPython shlex.quote for Python versions 2.x & < 3.3)"
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "simplejson"
+version = "3.17.6"
+description = "Simple, fast, extensible JSON encoder/decoder for Python"
+category = "main"
+optional = false
+python-versions = ">=2.5, !=3.0.*, !=3.1.*, !=3.2.*"
+
+[[package]]
+name = "six"
+version = "1.16.0"
+description = "Python 2 and 3 compatibility utilities"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
+
+[[package]]
+name = "sortedcontainers"
+version = "2.4.0"
+description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set"
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "soupsieve"
+version = "2.3.2.post1"
+description = "A modern CSS selector implementation for Beautiful Soup."
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "stevedore"
+version = "3.5.0"
+description = "Manage dynamic plugins for Python applications"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+pbr = ">=2.0.0,<2.1.0 || >2.1.0"
+
+[[package]]
+name = "stringcase"
+version = "1.2.0"
+description = "String case converter."
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "tabulate"
+version = "0.8.9"
+description = "Pretty-print tabular data"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.extras]
+widechars = ["wcwidth"]
+
+[[package]]
+name = "tinydb"
+version = "4.7.0"
+description = "TinyDB is a tiny, document oriented database optimized for your happiness :)"
+category = "main"
+optional = false
+python-versions = ">=3.6,<4.0"
+
+[[package]]
+name = "tomli"
+version = "2.0.1"
+description = "A lil' TOML parser"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "tuspy"
+version = "1.0.0"
+description = "A Python client for the tus resumable upload protocol -> http://tus.io"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+aiohttp = ">=3.6.2"
+future = ">=0.16.0"
+requests = ">=2.18.4"
+six = ">=1.11.0"
+tinydb = ">=3.5.0"
+
+[package.extras]
+dev = ["Sphinx (==1.7.1)", "sphinx-autobuild (==2021.3.14)", "tox (>=2.3.1)"]
+test = ["aioresponses (>=0.6.2)", "coverage (>=4.2)", "pytest-cov (>=2.3.1,<2.6)", "pytest (>=3.0.3)", "responses (>=0.5.1)"]
+
+[[package]]
+name = "types-cryptography"
+version = "3.3.21"
+description = "Typing stubs for cryptography"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "types-paramiko"
+version = "2.10.0"
+description = "Typing stubs for paramiko"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+types-cryptography = "*"
+
+[[package]]
+name = "types-requests"
+version = "2.27.25"
+description = "Typing stubs for requests"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+types-urllib3 = "<1.27"
+
+[[package]]
+name = "types-urllib3"
+version = "1.26.14"
+description = "Typing stubs for urllib3"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "typing-extensions"
+version = "4.2.0"
+description = "Backported and Experimental Type Hints for Python 3.7+"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "typing-inspect"
+version = "0.7.1"
+description = "Runtime inspection utilities for typing module."
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+mypy-extensions = ">=0.3.0"
+typing-extensions = ">=3.7.4"
+
+[[package]]
+name = "urllib3"
+version = "1.26.9"
+description = "HTTP library with thread-safe connection pooling, file post, and more."
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
+
+[package.extras]
+brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"]
+secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
+socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
+
+[[package]]
+name = "virtualenv"
+version = "20.14.1"
+description = "Virtual Python Environment builder"
+category = "main"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
+
+[package.dependencies]
+distlib = ">=0.3.1,<1"
+filelock = ">=3.2,<4"
+platformdirs = ">=2,<3"
+six = ">=1.9.0,<2"
+
+[package.extras]
+docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"]
+testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)"]
+
+[[package]]
+name = "wcwidth"
+version = "0.2.5"
+description = "Measures the displayed width of unicode strings in a terminal"
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "webencodings"
+version = "0.5.1"
+description = "Character encoding aliases for legacy web content"
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "wrapt"
+version = "1.14.1"
+description = "Module for decorators, wrappers and monkey patching."
+category = "main"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
+
+[[package]]
+name = "yarl"
+version = "1.7.2"
+description = "Yet another URL library"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+idna = ">=2.0"
+multidict = ">=4.0"
+
+[[package]]
+name = "zipstream-new"
+version = "1.1.8"
+description = "Zipfile generator that takes input files as well as streams"
+category = "main"
+optional = false
+python-versions = "*"
+
+[metadata]
+lock-version = "1.1"
+python-versions = "^3.10"
+content-hash = "7011e476e1c8fd579aee0c55c89afb64bd00084548eef8e71ad94f0cb646932f"
+
+[metadata.files]
+aiohttp = []
+aiosignal = []
+allure-python-commons = [
+ {file = "allure-python-commons-2.9.45.tar.gz", hash = "sha256:c238d28aeac35e8c7c517d8a2327e25ae5bbf2c30b5e2313d20ef11d75f5549d"},
+ {file = "allure_python_commons-2.9.45-py3-none-any.whl", hash = "sha256:3572f0526db3946fb14470c58b0b41d343483aad91d37d414e4641815e13691a"},
+]
+ansible = [
+ {file = "ansible-5.7.1.tar.gz", hash = "sha256:90a09a34510d194d63556895a225a89e4a8b748b9ae215b55e517a37de063a61"},
+]
+ansible-core = [
+ {file = "ansible-core-2.12.5.tar.gz", hash = "sha256:1ccc9944f101331adad1ed40d5ac66a8149133052ae34db0267a74aa73beebb3"},
+]
+appdirs = [
+ {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"},
+ {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"},
+]
+argcomplete = [
+ {file = "argcomplete-2.0.0-py2.py3-none-any.whl", hash = "sha256:cffa11ea77999bb0dd27bb25ff6dc142a6796142f68d45b1a26b11f58724561e"},
+ {file = "argcomplete-2.0.0.tar.gz", hash = "sha256:6372ad78c89d662035101418ae253668445b391755cfe94ea52f1b9d22425b20"},
+]
+async-timeout = [
+ {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"},
+ {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"},
+]
+attrs = [
+ {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"},
+ {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"},
+]
+autopage = [
+ {file = "autopage-0.5.0-py3-none-any.whl", hash = "sha256:57232860f28a1867cdd54b5ea510e292c53d6dfb613f781c5120200666550b06"},
+ {file = "autopage-0.5.0.tar.gz", hash = "sha256:5305b43cc0798170d7124e5a2feecf969e45f4a0baf75cb351138114eaf76b83"},
+]
+bagit = [
+ {file = "bagit-1.8.1-py2.py3-none-any.whl", hash = "sha256:d14dd7e373dd24d41f6748c42f123f7db77098dfa4a0125dbacb4c8bdf767c09"},
+ {file = "bagit-1.8.1.tar.gz", hash = "sha256:37df1330d2e8640c8dee8ab6d0073ac701f0614d25f5252f9e05263409cee60c"},
+]
+bcrypt = [
+ {file = "bcrypt-3.2.2-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:7180d98a96f00b1050e93f5b0f556e658605dd9f524d0b0e68ae7944673f525e"},
+ {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:61bae49580dce88095d669226d5076d0b9d927754cedbdf76c6c9f5099ad6f26"},
+ {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88273d806ab3a50d06bc6a2fc7c87d737dd669b76ad955f449c43095389bc8fb"},
+ {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:6d2cb9d969bfca5bc08e45864137276e4c3d3d7de2b162171def3d188bf9d34a"},
+ {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b02d6bfc6336d1094276f3f588aa1225a598e27f8e3388f4db9948cb707b521"},
+ {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a2c46100e315c3a5b90fdc53e429c006c5f962529bc27e1dfd656292c20ccc40"},
+ {file = "bcrypt-3.2.2-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7d9ba2e41e330d2af4af6b1b6ec9e6128e91343d0b4afb9282e54e5508f31baa"},
+ {file = "bcrypt-3.2.2-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cd43303d6b8a165c29ec6756afd169faba9396a9472cdff753fe9f19b96ce2fa"},
+ {file = "bcrypt-3.2.2-cp36-abi3-win32.whl", hash = "sha256:4e029cef560967fb0cf4a802bcf4d562d3d6b4b1bf81de5ec1abbe0f1adb027e"},
+ {file = "bcrypt-3.2.2-cp36-abi3-win_amd64.whl", hash = "sha256:7ff2069240c6bbe49109fe84ca80508773a904f5a8cb960e02a977f7f519b129"},
+ {file = "bcrypt-3.2.2.tar.gz", hash = "sha256:433c410c2177057705da2a9f2cd01dd157493b2a7ac14c8593a16b3dab6b6bfb"},
+]
+beautifulsoup4 = [
+ {file = "beautifulsoup4-4.11.1-py3-none-any.whl", hash = "sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30"},
+ {file = "beautifulsoup4-4.11.1.tar.gz", hash = "sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693"},
+]
+bioblend = []
+black = [
+ {file = "black-22.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2497f9c2386572e28921fa8bec7be3e51de6801f7459dffd6e62492531c47e09"},
+ {file = "black-22.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5795a0375eb87bfe902e80e0c8cfaedf8af4d49694d69161e5bd3206c18618bb"},
+ {file = "black-22.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e3556168e2e5c49629f7b0f377070240bd5511e45e25a4497bb0073d9dda776a"},
+ {file = "black-22.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67c8301ec94e3bcc8906740fe071391bce40a862b7be0b86fb5382beefecd968"},
+ {file = "black-22.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:fd57160949179ec517d32ac2ac898b5f20d68ed1a9c977346efbac9c2f1e779d"},
+ {file = "black-22.3.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cc1e1de68c8e5444e8f94c3670bb48a2beef0e91dddfd4fcc29595ebd90bb9ce"},
+ {file = "black-22.3.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2fc92002d44746d3e7db7cf9313cf4452f43e9ea77a2c939defce3b10b5c82"},
+ {file = "black-22.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:a6342964b43a99dbc72f72812bf88cad8f0217ae9acb47c0d4f141a6416d2d7b"},
+ {file = "black-22.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:328efc0cc70ccb23429d6be184a15ce613f676bdfc85e5fe8ea2a9354b4e9015"},
+ {file = "black-22.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06f9d8846f2340dfac80ceb20200ea5d1b3f181dd0556b47af4e8e0b24fa0a6b"},
+ {file = "black-22.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4efa5fad66b903b4a5f96d91461d90b9507a812b3c5de657d544215bb7877a"},
+ {file = "black-22.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8477ec6bbfe0312c128e74644ac8a02ca06bcdb8982d4ee06f209be28cdf163"},
+ {file = "black-22.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:637a4014c63fbf42a692d22b55d8ad6968a946b4a6ebc385c5505d9625b6a464"},
+ {file = "black-22.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:863714200ada56cbc366dc9ae5291ceb936573155f8bf8e9de92aef51f3ad0f0"},
+ {file = "black-22.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10dbe6e6d2988049b4655b2b739f98785a884d4d6b85bc35133a8fb9a2233176"},
+ {file = "black-22.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:cee3e11161dde1b2a33a904b850b0899e0424cc331b7295f2a9698e79f9a69a0"},
+ {file = "black-22.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5891ef8abc06576985de8fa88e95ab70641de6c1fca97e2a15820a9b69e51b20"},
+ {file = "black-22.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:30d78ba6bf080eeaf0b7b875d924b15cd46fec5fd044ddfbad38c8ea9171043a"},
+ {file = "black-22.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ee8f1f7228cce7dffc2b464f07ce769f478968bfb3dd1254a4c2eeed84928aad"},
+ {file = "black-22.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ee227b696ca60dd1c507be80a6bc849a5a6ab57ac7352aad1ffec9e8b805f21"},
+ {file = "black-22.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:9b542ced1ec0ceeff5b37d69838106a6348e60db7b8fdd245294dc1d26136265"},
+ {file = "black-22.3.0-py3-none-any.whl", hash = "sha256:bc58025940a896d7e5356952228b68f793cf5fcb342be703c3a2669a1488cb72"},
+ {file = "black-22.3.0.tar.gz", hash = "sha256:35020b8886c022ced9282b51b5a875b6d1ab0c387b31a065b84db7c33085ca79"},
+]
+bleach = [
+ {file = "bleach-5.0.0-py3-none-any.whl", hash = "sha256:08a1fe86d253b5c88c92cc3d810fd8048a16d15762e1e5b74d502256e5926aa1"},
+ {file = "bleach-5.0.0.tar.gz", hash = "sha256:c6d6cc054bdc9c83b48b8083e236e5f00f238428666d2ce2e083eaa5fd568565"},
+]
+boltons = [
+ {file = "boltons-21.0.0-py2.py3-none-any.whl", hash = "sha256:b9bb7b58b2b420bbe11a6025fdef6d3e5edc9f76a42fb467afe7ca212ef9948b"},
+ {file = "boltons-21.0.0.tar.gz", hash = "sha256:65e70a79a731a7fe6e98592ecfb5ccf2115873d01dbc576079874629e5c90f13"},
+]
+boto = [
+ {file = "boto-2.49.0-py2.py3-none-any.whl", hash = "sha256:147758d41ae7240dc989f0039f27da8ca0d53734be0eb869ef16e3adcfa462e8"},
+ {file = "boto-2.49.0.tar.gz", hash = "sha256:ea0d3b40a2d852767be77ca343b58a9e3a4b00d9db440efb8da74b4e58025e5a"},
+]
+boto3 = []
+botocore = []
+cachecontrol = [
+ {file = "CacheControl-0.12.11-py2.py3-none-any.whl", hash = "sha256:2c75d6a8938cb1933c75c50184549ad42728a27e9f6b92fd677c3151aa72555b"},
+ {file = "CacheControl-0.12.11.tar.gz", hash = "sha256:a5b9fcc986b184db101aa280b42ecdcdfc524892596f606858e0b7a8b4d9e144"},
+]
+certifi = [
+ {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"},
+ {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"},
+]
+cffi = [
+ {file = "cffi-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962"},
+ {file = "cffi-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0"},
+ {file = "cffi-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14"},
+ {file = "cffi-1.15.0-cp27-cp27m-win32.whl", hash = "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474"},
+ {file = "cffi-1.15.0-cp27-cp27m-win_amd64.whl", hash = "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6"},
+ {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27"},
+ {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023"},
+ {file = "cffi-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2"},
+ {file = "cffi-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e"},
+ {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7"},
+ {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3"},
+ {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c"},
+ {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962"},
+ {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382"},
+ {file = "cffi-1.15.0-cp310-cp310-win32.whl", hash = "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55"},
+ {file = "cffi-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0"},
+ {file = "cffi-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e"},
+ {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39"},
+ {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc"},
+ {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032"},
+ {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8"},
+ {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605"},
+ {file = "cffi-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e"},
+ {file = "cffi-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc"},
+ {file = "cffi-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636"},
+ {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4"},
+ {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997"},
+ {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b"},
+ {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2"},
+ {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7"},
+ {file = "cffi-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66"},
+ {file = "cffi-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029"},
+ {file = "cffi-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880"},
+ {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20"},
+ {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024"},
+ {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e"},
+ {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728"},
+ {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6"},
+ {file = "cffi-1.15.0-cp38-cp38-win32.whl", hash = "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c"},
+ {file = "cffi-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443"},
+ {file = "cffi-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a"},
+ {file = "cffi-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37"},
+ {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a"},
+ {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e"},
+ {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796"},
+ {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df"},
+ {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8"},
+ {file = "cffi-1.15.0-cp39-cp39-win32.whl", hash = "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a"},
+ {file = "cffi-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139"},
+ {file = "cffi-1.15.0.tar.gz", hash = "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954"},
+]
+charset-normalizer = [
+ {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"},
+ {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"},
+]
+click = [
+ {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
+ {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
+]
+cliff = [
+ {file = "cliff-3.10.1-py3-none-any.whl", hash = "sha256:a21da482714b9f0b0e9bafaaf2f6a8b3b14161bb47f62e10e28d2fe4ff4b1626"},
+ {file = "cliff-3.10.1.tar.gz", hash = "sha256:045aee3f3c64471965d7ad507ce8474a4e2f20815fbb5405a770f8596a2a00a0"},
+]
+cmd2 = [
+ {file = "cmd2-2.4.1-py3-none-any.whl", hash = "sha256:e6f49b0854b6aec2f20073bae99f1deede16c24b36fde682045d73c80c4cfb51"},
+ {file = "cmd2-2.4.1.tar.gz", hash = "sha256:f3b0467daca18fca0dc7838de7726a72ab64127a018a377a86a6ed8ebfdbb25f"},
+]
+colorama = [
+ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
+ {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
+]
+coloredlogs = [
+ {file = "coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934"},
+ {file = "coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0"},
+]
+configparser = [
+ {file = "configparser-5.2.0-py3-none-any.whl", hash = "sha256:e8b39238fb6f0153a069aa253d349467c3c4737934f253ef6abac5fe0eca1e5d"},
+ {file = "configparser-5.2.0.tar.gz", hash = "sha256:1b35798fdf1713f1c3139016cfcbc461f09edbf099d1fb658d4b7479fcaa3daa"},
+]
+cryptography = [
+ {file = "cryptography-37.0.2-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:ef15c2df7656763b4ff20a9bc4381d8352e6640cfeb95c2972c38ef508e75181"},
+ {file = "cryptography-37.0.2-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:3c81599befb4d4f3d7648ed3217e00d21a9341a9a688ecdd615ff72ffbed7336"},
+ {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2bd1096476aaac820426239ab534b636c77d71af66c547b9ddcd76eb9c79e004"},
+ {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:31fe38d14d2e5f787e0aecef831457da6cec68e0bb09a35835b0b44ae8b988fe"},
+ {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:093cb351031656d3ee2f4fa1be579a8c69c754cf874206be1d4cf3b542042804"},
+ {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59b281eab51e1b6b6afa525af2bd93c16d49358404f814fe2c2410058623928c"},
+ {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:0cc20f655157d4cfc7bada909dc5cc228211b075ba8407c46467f63597c78178"},
+ {file = "cryptography-37.0.2-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:f8ec91983e638a9bcd75b39f1396e5c0dc2330cbd9ce4accefe68717e6779e0a"},
+ {file = "cryptography-37.0.2-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:46f4c544f6557a2fefa7ac8ac7d1b17bf9b647bd20b16decc8fbcab7117fbc15"},
+ {file = "cryptography-37.0.2-cp36-abi3-win32.whl", hash = "sha256:731c8abd27693323b348518ed0e0705713a36d79fdbd969ad968fbef0979a7e0"},
+ {file = "cryptography-37.0.2-cp36-abi3-win_amd64.whl", hash = "sha256:471e0d70201c069f74c837983189949aa0d24bb2d751b57e26e3761f2f782b8d"},
+ {file = "cryptography-37.0.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a68254dd88021f24a68b613d8c51d5c5e74d735878b9e32cc0adf19d1f10aaf9"},
+ {file = "cryptography-37.0.2-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:a7d5137e556cc0ea418dca6186deabe9129cee318618eb1ffecbd35bee55ddc1"},
+ {file = "cryptography-37.0.2-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:aeaba7b5e756ea52c8861c133c596afe93dd716cbcacae23b80bc238202dc023"},
+ {file = "cryptography-37.0.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95e590dd70642eb2079d280420a888190aa040ad20f19ec8c6e097e38aa29e06"},
+ {file = "cryptography-37.0.2-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:1b9362d34363f2c71b7853f6251219298124aa4cc2075ae2932e64c91a3e2717"},
+ {file = "cryptography-37.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e53258e69874a306fcecb88b7534d61820db8a98655662a3dd2ec7f1afd9132f"},
+ {file = "cryptography-37.0.2-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:1f3bfbd611db5cb58ca82f3deb35e83af34bb8cf06043fa61500157d50a70982"},
+ {file = "cryptography-37.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:419c57d7b63f5ec38b1199a9521d77d7d1754eb97827bbb773162073ccd8c8d4"},
+ {file = "cryptography-37.0.2-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:dc26bb134452081859aa21d4990474ddb7e863aa39e60d1592800a8865a702de"},
+ {file = "cryptography-37.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3b8398b3d0efc420e777c40c16764d6870bcef2eb383df9c6dbb9ffe12c64452"},
+ {file = "cryptography-37.0.2.tar.gz", hash = "sha256:f224ad253cc9cea7568f49077007d2263efa57396a2f2f78114066fd54b5c68e"},
+]
+cwltool = [
+ {file = "cwltool-3.1.20220502060230-py3-none-any.whl", hash = "sha256:7577c55fbb9be139e1502a25dec7d71951089200daed599a2c5dbd0f88ad47db"},
+ {file = "cwltool-3.1.20220502060230.tar.gz", hash = "sha256:c8df57394d2a9b0581444cf6dfa4754047ec20fdb3073c57154f46b205b85758"},
+]
+debtcollector = [
+ {file = "debtcollector-2.5.0-py3-none-any.whl", hash = "sha256:1393a527d2c72f143ffa6a629e9c33face6642634eece475b48cab7b04ba61f3"},
+ {file = "debtcollector-2.5.0.tar.gz", hash = "sha256:dc9d1ad3f745c43f4bbedbca30f9ffe8905a8c028c9926e61077847d5ea257ab"},
+]
+decorator = [
+ {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"},
+ {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"},
+]
+distlib = [
+ {file = "distlib-0.3.4-py2.py3-none-any.whl", hash = "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b"},
+ {file = "distlib-0.3.4.zip", hash = "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579"},
+]
+docutils = [
+ {file = "docutils-0.18.1-py2.py3-none-any.whl", hash = "sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c"},
+ {file = "docutils-0.18.1.tar.gz", hash = "sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06"},
+]
+"dogpile.cache" = [
+ {file = "dogpile.cache-1.1.5-py3-none-any.whl", hash = "sha256:5f9dcf99087240c7733fad5539b0806b52555917dccad1ef43499eaca8b459d9"},
+ {file = "dogpile.cache-1.1.5.tar.gz", hash = "sha256:0f01bdc329329a8289af9705ff40fadb1f82a28c336f3174e12142b70d31c756"},
+]
+ephemeris = [
+ {file = "ephemeris-0.10.7-py2.py3-none-any.whl", hash = "sha256:964c3ef2ce0c0365b5099604eb467db4969f0bd1aaeac0df9579a0e3c8cdbcf4"},
+ {file = "ephemeris-0.10.7-py3.7.egg", hash = "sha256:3ed4700655b19d7141b1c3367b741a40bd7a376f08eca0512f58bd8407cf06e7"},
+ {file = "ephemeris-0.10.7.tar.gz", hash = "sha256:dd7309159ba03a4446ce7e48da9145c706be5822514c72b70e38b094d8d7f89f"},
+]
+filelock = [
+ {file = "filelock-3.6.0-py3-none-any.whl", hash = "sha256:f8314284bfffbdcfa0ff3d7992b023d4c628ced6feb957351d4c48d059f56bc0"},
+ {file = "filelock-3.6.0.tar.gz", hash = "sha256:9cd540a9352e432c7246a48fe4e8712b10acb1df2ad1f30e8c070b82ae1fed85"},
+]
+frozenlist = []
+future = []
+galaxy-containers = [
+ {file = "galaxy-containers-21.9.0.tar.gz", hash = "sha256:23fb30f7d90451f037d8b30977640e766c11d2421bab1273624c62c09e9f8a57"},
+ {file = "galaxy_containers-21.9.0-py2.py3-none-any.whl", hash = "sha256:befcda1c7d1bf7ad641fd2c871c602c0cd23deb892114b9b25e199972fa40f58"},
+]
+galaxy-tool-util = [
+ {file = "galaxy-tool-util-21.9.2.tar.gz", hash = "sha256:d14d472466ea227d8b69b58856fa86f6641f1b59326da6b488c9dd94e6e0c752"},
+ {file = "galaxy_tool_util-21.9.2-py2.py3-none-any.whl", hash = "sha256:5fa7670f51f7e9c3cdea07f424f3789264f4b70ea3e5d69e6c752ed9e9fc1495"},
+]
+galaxy-util = [
+ {file = "galaxy-util-21.9.0.tar.gz", hash = "sha256:604ab29115f95b5a671c3ee047f12eb10ea77342a8d2aed0c289224694a493ba"},
+ {file = "galaxy_util-21.9.0-py2.py3-none-any.whl", hash = "sha256:dabc821ee33323408a345f96f8a75f0963facbb86f295128c0c4e3131339a093"},
+]
+glob2 = [
+ {file = "glob2-0.7.tar.gz", hash = "sha256:85c3dbd07c8aa26d63d7aacee34fa86e9a91a3873bc30bf62ec46e531f92ab8c"},
+]
+gxformat2 = [
+ {file = "gxformat2-0.15.0-py2.py3-none-any.whl", hash = "sha256:e60997d25dc745408b1f4a453fdbaf28d1486de201cb7abf271e261f2d157728"},
+ {file = "gxformat2-0.15.0-py3.6.egg", hash = "sha256:90810384151ac5b7bfebceaec10648bd68c12730980b476a0a672b1b16c66f8f"},
+ {file = "gxformat2-0.15.0.tar.gz", hash = "sha256:a6c399d3deaae535b66fac9ac1e31e19a68570a6730fe5e2f27086b8980545d5"},
+]
+humanfriendly = [
+ {file = "humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477"},
+ {file = "humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc"},
+]
+idna = [
+ {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
+ {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
+]
+iso8601 = [
+ {file = "iso8601-1.0.2-py3-none-any.whl", hash = "sha256:d7bc01b1c2a43b259570bb307f057abc578786ea734ba2b87b836c5efc5bd443"},
+ {file = "iso8601-1.0.2.tar.gz", hash = "sha256:27f503220e6845d9db954fb212b95b0362d8b7e6c1b2326a87061c3de93594b1"},
+]
+isodate = [
+ {file = "isodate-0.6.1-py2.py3-none-any.whl", hash = "sha256:0751eece944162659049d35f4f549ed815792b38793f07cf73381c1c87cbed96"},
+ {file = "isodate-0.6.1.tar.gz", hash = "sha256:48c5881de7e8b0a0d648cb024c8062dc84e7b840ed81e864c7614fd3c127bde9"},
+]
+isort = [
+ {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"},
+ {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"},
+]
+jinja2 = [
+ {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"},
+ {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"},
+]
+jmespath = [
+ {file = "jmespath-1.0.0-py3-none-any.whl", hash = "sha256:e8dcd576ed616f14ec02eed0005c85973b5890083313860136657e24784e4c04"},
+ {file = "jmespath-1.0.0.tar.gz", hash = "sha256:a490e280edd1f57d6de88636992d05b71e97d69a26a19f058ecf7d304474bf5e"},
+]
+jsonpatch = [
+ {file = "jsonpatch-1.32-py2.py3-none-any.whl", hash = "sha256:26ac385719ac9f54df8a2f0827bb8253aa3ea8ab7b3368457bcdb8c14595a397"},
+ {file = "jsonpatch-1.32.tar.gz", hash = "sha256:b6ddfe6c3db30d81a96aaeceb6baf916094ffa23d7dd5fa2c13e13f8b6e600c2"},
+]
+jsonpointer = [
+ {file = "jsonpointer-2.3-py2.py3-none-any.whl", hash = "sha256:51801e558539b4e9cd268638c078c6c5746c9ac96bc38152d443400e4f3793e9"},
+ {file = "jsonpointer-2.3.tar.gz", hash = "sha256:97cba51526c829282218feb99dab1b1e6bdf8efd1c43dc9d57be093c0d69c99a"},
+]
+keystoneauth1 = [
+ {file = "keystoneauth1-4.5.0-py3-none-any.whl", hash = "sha256:47b526d2e813482bd1018916a1c768a5ac6d83c0865a4dc904cb8d6ffd530f1c"},
+ {file = "keystoneauth1-4.5.0.tar.gz", hash = "sha256:49b3488966a43eeb0200ea511b997e6403c25d563a984c6330e82a0ebfc4540c"},
+]
+lockfile = [
+ {file = "lockfile-0.12.2-py2.py3-none-any.whl", hash = "sha256:6c3cb24f344923d30b2785d5ad75182c8ea7ac1b6171b08657258ec7429d50fa"},
+ {file = "lockfile-0.12.2.tar.gz", hash = "sha256:6aed02de03cba24efabcd600b30540140634fc06cfa603822d508d5361e9f799"},
+]
+lxml = [
+ {file = "lxml-4.8.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:e1ab2fac607842ac36864e358c42feb0960ae62c34aa4caaf12ada0a1fb5d99b"},
+ {file = "lxml-4.8.0-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28d1af847786f68bec57961f31221125c29d6f52d9187c01cd34dc14e2b29430"},
+ {file = "lxml-4.8.0-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b92d40121dcbd74831b690a75533da703750f7041b4bf951befc657c37e5695a"},
+ {file = "lxml-4.8.0-cp27-cp27m-win32.whl", hash = "sha256:e01f9531ba5420838c801c21c1b0f45dbc9607cb22ea2cf132844453bec863a5"},
+ {file = "lxml-4.8.0-cp27-cp27m-win_amd64.whl", hash = "sha256:6259b511b0f2527e6d55ad87acc1c07b3cbffc3d5e050d7e7bcfa151b8202df9"},
+ {file = "lxml-4.8.0-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1010042bfcac2b2dc6098260a2ed022968dbdfaf285fc65a3acf8e4eb1ffd1bc"},
+ {file = "lxml-4.8.0-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fa56bb08b3dd8eac3a8c5b7d075c94e74f755fd9d8a04543ae8d37b1612dd170"},
+ {file = "lxml-4.8.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:31ba2cbc64516dcdd6c24418daa7abff989ddf3ba6d3ea6f6ce6f2ed6e754ec9"},
+ {file = "lxml-4.8.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:31499847fc5f73ee17dbe1b8e24c6dafc4e8d5b48803d17d22988976b0171f03"},
+ {file = "lxml-4.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:5f7d7d9afc7b293147e2d506a4596641d60181a35279ef3aa5778d0d9d9123fe"},
+ {file = "lxml-4.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:a3c5f1a719aa11866ffc530d54ad965063a8cbbecae6515acbd5f0fae8f48eaa"},
+ {file = "lxml-4.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6268e27873a3d191849204d00d03f65c0e343b3bcb518a6eaae05677c95621d1"},
+ {file = "lxml-4.8.0-cp310-cp310-win32.whl", hash = "sha256:330bff92c26d4aee79c5bc4d9967858bdbe73fdbdbacb5daf623a03a914fe05b"},
+ {file = "lxml-4.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:b2582b238e1658c4061ebe1b4df53c435190d22457642377fd0cb30685cdfb76"},
+ {file = "lxml-4.8.0-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a2bfc7e2a0601b475477c954bf167dee6d0f55cb167e3f3e7cefad906e7759f6"},
+ {file = "lxml-4.8.0-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a1547ff4b8a833511eeaceacbcd17b043214fcdb385148f9c1bc5556ca9623e2"},
+ {file = "lxml-4.8.0-cp35-cp35m-win32.whl", hash = "sha256:a9f1c3489736ff8e1c7652e9dc39f80cff820f23624f23d9eab6e122ac99b150"},
+ {file = "lxml-4.8.0-cp35-cp35m-win_amd64.whl", hash = "sha256:530f278849031b0eb12f46cca0e5db01cfe5177ab13bd6878c6e739319bae654"},
+ {file = "lxml-4.8.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:078306d19a33920004addeb5f4630781aaeabb6a8d01398045fcde085091a169"},
+ {file = "lxml-4.8.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:86545e351e879d0b72b620db6a3b96346921fa87b3d366d6c074e5a9a0b8dadb"},
+ {file = "lxml-4.8.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24f5c5ae618395ed871b3d8ebfcbb36e3f1091fd847bf54c4de623f9107942f3"},
+ {file = "lxml-4.8.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:bbab6faf6568484707acc052f4dfc3802bdb0cafe079383fbaa23f1cdae9ecd4"},
+ {file = "lxml-4.8.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7993232bd4044392c47779a3c7e8889fea6883be46281d45a81451acfd704d7e"},
+ {file = "lxml-4.8.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6d6483b1229470e1d8835e52e0ff3c6973b9b97b24cd1c116dca90b57a2cc613"},
+ {file = "lxml-4.8.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:ad4332a532e2d5acb231a2e5d33f943750091ee435daffca3fec0a53224e7e33"},
+ {file = "lxml-4.8.0-cp36-cp36m-win32.whl", hash = "sha256:db3535733f59e5605a88a706824dfcb9bd06725e709ecb017e165fc1d6e7d429"},
+ {file = "lxml-4.8.0-cp36-cp36m-win_amd64.whl", hash = "sha256:5f148b0c6133fb928503cfcdfdba395010f997aa44bcf6474fcdd0c5398d9b63"},
+ {file = "lxml-4.8.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:8a31f24e2a0b6317f33aafbb2f0895c0bce772980ae60c2c640d82caac49628a"},
+ {file = "lxml-4.8.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:719544565c2937c21a6f76d520e6e52b726d132815adb3447ccffbe9f44203c4"},
+ {file = "lxml-4.8.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:c0b88ed1ae66777a798dc54f627e32d3b81c8009967c63993c450ee4cbcbec15"},
+ {file = "lxml-4.8.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fa9b7c450be85bfc6cd39f6df8c5b8cbd76b5d6fc1f69efec80203f9894b885f"},
+ {file = "lxml-4.8.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e9f84ed9f4d50b74fbc77298ee5c870f67cb7e91dcdc1a6915cb1ff6a317476c"},
+ {file = "lxml-4.8.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1d650812b52d98679ed6c6b3b55cbb8fe5a5460a0aef29aeb08dc0b44577df85"},
+ {file = "lxml-4.8.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:80bbaddf2baab7e6de4bc47405e34948e694a9efe0861c61cdc23aa774fcb141"},
+ {file = "lxml-4.8.0-cp37-cp37m-win32.whl", hash = "sha256:6f7b82934c08e28a2d537d870293236b1000d94d0b4583825ab9649aef7ddf63"},
+ {file = "lxml-4.8.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e1fd7d2fe11f1cb63d3336d147c852f6d07de0d0020d704c6031b46a30b02ca8"},
+ {file = "lxml-4.8.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:5045ee1ccd45a89c4daec1160217d363fcd23811e26734688007c26f28c9e9e7"},
+ {file = "lxml-4.8.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:0c1978ff1fd81ed9dcbba4f91cf09faf1f8082c9d72eb122e92294716c605428"},
+ {file = "lxml-4.8.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cbf2ff155b19dc4d4100f7442f6a697938bf4493f8d3b0c51d45568d5666b5"},
+ {file = "lxml-4.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:ce13d6291a5f47c1c8dbd375baa78551053bc6b5e5c0e9bb8e39c0a8359fd52f"},
+ {file = "lxml-4.8.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e11527dc23d5ef44d76fef11213215c34f36af1608074561fcc561d983aeb870"},
+ {file = "lxml-4.8.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:60d2f60bd5a2a979df28ab309352cdcf8181bda0cca4529769a945f09aba06f9"},
+ {file = "lxml-4.8.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:62f93eac69ec0f4be98d1b96f4d6b964855b8255c345c17ff12c20b93f247b68"},
+ {file = "lxml-4.8.0-cp38-cp38-win32.whl", hash = "sha256:20b8a746a026017acf07da39fdb10aa80ad9877046c9182442bf80c84a1c4696"},
+ {file = "lxml-4.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:891dc8f522d7059ff0024cd3ae79fd224752676447f9c678f2a5c14b84d9a939"},
+ {file = "lxml-4.8.0-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:b6fc2e2fb6f532cf48b5fed57567ef286addcef38c28874458a41b7837a57807"},
+ {file = "lxml-4.8.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:74eb65ec61e3c7c019d7169387d1b6ffcfea1b9ec5894d116a9a903636e4a0b1"},
+ {file = "lxml-4.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:627e79894770783c129cc5e89b947e52aa26e8e0557c7e205368a809da4b7939"},
+ {file = "lxml-4.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:545bd39c9481f2e3f2727c78c169425efbfb3fbba6e7db4f46a80ebb249819ca"},
+ {file = "lxml-4.8.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5a58d0b12f5053e270510bf12f753a76aaf3d74c453c00942ed7d2c804ca845c"},
+ {file = "lxml-4.8.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ec4b4e75fc68da9dc0ed73dcdb431c25c57775383fec325d23a770a64e7ebc87"},
+ {file = "lxml-4.8.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5804e04feb4e61babf3911c2a974a5b86f66ee227cc5006230b00ac6d285b3a9"},
+ {file = "lxml-4.8.0-cp39-cp39-win32.whl", hash = "sha256:aa0cf4922da7a3c905d000b35065df6184c0dc1d866dd3b86fd961905bbad2ea"},
+ {file = "lxml-4.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:dd10383f1d6b7edf247d0960a3db274c07e96cf3a3fc7c41c8448f93eac3fb1c"},
+ {file = "lxml-4.8.0-pp37-pypy37_pp73-macosx_10_14_x86_64.whl", hash = "sha256:2403a6d6fb61c285969b71f4a3527873fe93fd0abe0832d858a17fe68c8fa507"},
+ {file = "lxml-4.8.0-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:986b7a96228c9b4942ec420eff37556c5777bfba6758edcb95421e4a614b57f9"},
+ {file = "lxml-4.8.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:6fe4ef4402df0250b75ba876c3795510d782def5c1e63890bde02d622570d39e"},
+ {file = "lxml-4.8.0-pp38-pypy38_pp73-macosx_10_14_x86_64.whl", hash = "sha256:f10ce66fcdeb3543df51d423ede7e238be98412232fca5daec3e54bcd16b8da0"},
+ {file = "lxml-4.8.0-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:730766072fd5dcb219dd2b95c4c49752a54f00157f322bc6d71f7d2a31fecd79"},
+ {file = "lxml-4.8.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:8b99ec73073b37f9ebe8caf399001848fced9c08064effdbfc4da2b5a8d07b93"},
+ {file = "lxml-4.8.0.tar.gz", hash = "sha256:f63f62fc60e6228a4ca9abae28228f35e1bd3ce675013d1dfb828688d50c6e23"},
+]
+markupsafe = [
+ {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"},
+ {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"},
+ {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"},
+ {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"},
+ {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"},
+ {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"},
+ {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"},
+ {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"},
+ {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"},
+ {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"},
+ {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"},
+ {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"},
+ {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"},
+ {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"},
+ {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"},
+ {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"},
+ {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"},
+ {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"},
+ {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"},
+ {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"},
+ {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"},
+ {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"},
+ {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"},
+ {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"},
+ {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"},
+ {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"},
+ {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"},
+ {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"},
+ {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"},
+ {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"},
+ {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"},
+ {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"},
+ {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"},
+ {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"},
+ {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"},
+ {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"},
+ {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"},
+ {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"},
+ {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"},
+ {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"},
+]
+mistune = [
+ {file = "mistune-0.8.4-py2.py3-none-any.whl", hash = "sha256:88a1051873018da288eee8538d476dffe1262495144b33ecb586c4ab266bb8d4"},
+ {file = "mistune-0.8.4.tar.gz", hash = "sha256:59a3429db53c50b5c6bcc8a07f8848cb00d7dc8bdb431a4ab41920d201d4756e"},
+]
+msgpack = [
+ {file = "msgpack-1.0.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:96acc674bb9c9be63fa8b6dabc3248fdc575c4adc005c440ad02f87ca7edd079"},
+ {file = "msgpack-1.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2c3ca57c96c8e69c1a0d2926a6acf2d9a522b41dc4253a8945c4c6cd4981a4e3"},
+ {file = "msgpack-1.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0a792c091bac433dfe0a70ac17fc2087d4595ab835b47b89defc8bbabcf5c73"},
+ {file = "msgpack-1.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c58cdec1cb5fcea8c2f1771d7b5fec79307d056874f746690bd2bdd609ab147"},
+ {file = "msgpack-1.0.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f97c0f35b3b096a330bb4a1a9247d0bd7e1f3a2eba7ab69795501504b1c2c39"},
+ {file = "msgpack-1.0.3-cp310-cp310-win32.whl", hash = "sha256:36a64a10b16c2ab31dcd5f32d9787ed41fe68ab23dd66957ca2826c7f10d0b85"},
+ {file = "msgpack-1.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c1ba333b4024c17c7591f0f372e2daa3c31db495a9b2af3cf664aef3c14354f7"},
+ {file = "msgpack-1.0.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c2140cf7a3ec475ef0938edb6eb363fa704159e0bf71dde15d953bacc1cf9d7d"},
+ {file = "msgpack-1.0.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f4c22717c74d44bcd7af353024ce71c6b55346dad5e2cc1ddc17ce8c4507c6b"},
+ {file = "msgpack-1.0.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d733a15ade190540c703de209ffbc42a3367600421b62ac0c09fde594da6ec"},
+ {file = "msgpack-1.0.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7e03b06f2982aa98d4ddd082a210c3db200471da523f9ac197f2828e80e7770"},
+ {file = "msgpack-1.0.3-cp36-cp36m-win32.whl", hash = "sha256:3d875631ecab42f65f9dce6f55ce6d736696ced240f2634633188de2f5f21af9"},
+ {file = "msgpack-1.0.3-cp36-cp36m-win_amd64.whl", hash = "sha256:40fb89b4625d12d6027a19f4df18a4de5c64f6f3314325049f219683e07e678a"},
+ {file = "msgpack-1.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6eef0cf8db3857b2b556213d97dd82de76e28a6524853a9beb3264983391dc1a"},
+ {file = "msgpack-1.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d8c332f53ffff01953ad25131272506500b14750c1d0ce8614b17d098252fbc"},
+ {file = "msgpack-1.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c0903bd93cbd34653dd63bbfcb99d7539c372795201f39d16fdfde4418de43a"},
+ {file = "msgpack-1.0.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bf1e6bfed4860d72106f4e0a1ab519546982b45689937b40257cfd820650b920"},
+ {file = "msgpack-1.0.3-cp37-cp37m-win32.whl", hash = "sha256:d02cea2252abc3756b2ac31f781f7a98e89ff9759b2e7450a1c7a0d13302ff50"},
+ {file = "msgpack-1.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:2f30dd0dc4dfe6231ad253b6f9f7128ac3202ae49edd3f10d311adc358772dba"},
+ {file = "msgpack-1.0.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:f201d34dc89342fabb2a10ed7c9a9aaaed9b7af0f16a5923f1ae562b31258dea"},
+ {file = "msgpack-1.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bb87f23ae7d14b7b3c21009c4b1705ec107cb21ee71975992f6aca571fb4a42a"},
+ {file = "msgpack-1.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a3a5c4b16e9d0edb823fe54b59b5660cc8d4782d7bf2c214cb4b91a1940a8ef"},
+ {file = "msgpack-1.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f74da1e5fcf20ade12c6bf1baa17a2dc3604958922de8dc83cbe3eff22e8b611"},
+ {file = "msgpack-1.0.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:73a80bd6eb6bcb338c1ec0da273f87420829c266379c8c82fa14c23fb586cfa1"},
+ {file = "msgpack-1.0.3-cp38-cp38-win32.whl", hash = "sha256:9fce00156e79af37bb6db4e7587b30d11e7ac6a02cb5bac387f023808cd7d7f4"},
+ {file = "msgpack-1.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:9b6f2d714c506e79cbead331de9aae6837c8dd36190d02da74cb409b36162e8a"},
+ {file = "msgpack-1.0.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:89908aea5f46ee1474cc37fbc146677f8529ac99201bc2faf4ef8edc023c2bf3"},
+ {file = "msgpack-1.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:973ad69fd7e31159eae8f580f3f707b718b61141838321c6fa4d891c4a2cca52"},
+ {file = "msgpack-1.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da24375ab4c50e5b7486c115a3198d207954fe10aaa5708f7b65105df09109b2"},
+ {file = "msgpack-1.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a598d0685e4ae07a0672b59792d2cc767d09d7a7f39fd9bd37ff84e060b1a996"},
+ {file = "msgpack-1.0.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4c309a68cb5d6bbd0c50d5c71a25ae81f268c2dc675c6f4ea8ab2feec2ac4e2"},
+ {file = "msgpack-1.0.3-cp39-cp39-win32.whl", hash = "sha256:494471d65b25a8751d19c83f1a482fd411d7ca7a3b9e17d25980a74075ba0e88"},
+ {file = "msgpack-1.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:f01b26c2290cbd74316990ba84a14ac3d599af9cebefc543d241a66e785cf17d"},
+ {file = "msgpack-1.0.3.tar.gz", hash = "sha256:51fdc7fb93615286428ee7758cecc2f374d5ff363bdd884c7ea622a7a327a81e"},
+]
+multidict = []
+munch = [
+ {file = "munch-2.5.0-py2.py3-none-any.whl", hash = "sha256:6f44af89a2ce4ed04ff8de41f70b226b984db10a91dcc7b9ac2efc1c77022fdd"},
+ {file = "munch-2.5.0.tar.gz", hash = "sha256:2d735f6f24d4dba3417fa448cae40c6e896ec1fdab6cdb5e6510999758a4dbd2"},
+]
+mypy-extensions = [
+ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
+ {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
+]
+netaddr = [
+ {file = "netaddr-0.8.0-py2.py3-none-any.whl", hash = "sha256:9666d0232c32d2656e5e5f8d735f58fd6c7457ce52fc21c98d45f2af78f990ac"},
+ {file = "netaddr-0.8.0.tar.gz", hash = "sha256:d6cc57c7a07b1d9d2e917aa8b36ae8ce61c35ba3fcd1b83ca31c5a0ee2b5a243"},
+]
+netifaces = [
+ {file = "netifaces-0.11.0-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:eb4813b77d5df99903af4757ce980a98c4d702bbcb81f32a0b305a1537bdf0b1"},
+ {file = "netifaces-0.11.0-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:5f9ca13babe4d845e400921973f6165a4c2f9f3379c7abfc7478160e25d196a4"},
+ {file = "netifaces-0.11.0-cp27-cp27m-win32.whl", hash = "sha256:7dbb71ea26d304e78ccccf6faccef71bb27ea35e259fb883cfd7fd7b4f17ecb1"},
+ {file = "netifaces-0.11.0-cp27-cp27m-win_amd64.whl", hash = "sha256:0f6133ac02521270d9f7c490f0c8c60638ff4aec8338efeff10a1b51506abe85"},
+ {file = "netifaces-0.11.0-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:08e3f102a59f9eaef70948340aeb6c89bd09734e0dca0f3b82720305729f63ea"},
+ {file = "netifaces-0.11.0-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c03fb2d4ef4e393f2e6ffc6376410a22a3544f164b336b3a355226653e5efd89"},
+ {file = "netifaces-0.11.0-cp34-cp34m-win32.whl", hash = "sha256:73ff21559675150d31deea8f1f8d7e9a9a7e4688732a94d71327082f517fc6b4"},
+ {file = "netifaces-0.11.0-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:815eafdf8b8f2e61370afc6add6194bd5a7252ae44c667e96c4c1ecf418811e4"},
+ {file = "netifaces-0.11.0-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:50721858c935a76b83dd0dd1ab472cad0a3ef540a1408057624604002fcfb45b"},
+ {file = "netifaces-0.11.0-cp35-cp35m-win32.whl", hash = "sha256:c9a3a47cd3aaeb71e93e681d9816c56406ed755b9442e981b07e3618fb71d2ac"},
+ {file = "netifaces-0.11.0-cp36-cp36m-macosx_10_15_x86_64.whl", hash = "sha256:aab1dbfdc55086c789f0eb37affccf47b895b98d490738b81f3b2360100426be"},
+ {file = "netifaces-0.11.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c37a1ca83825bc6f54dddf5277e9c65dec2f1b4d0ba44b8fd42bc30c91aa6ea1"},
+ {file = "netifaces-0.11.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:28f4bf3a1361ab3ed93c5ef360c8b7d4a4ae060176a3529e72e5e4ffc4afd8b0"},
+ {file = "netifaces-0.11.0-cp36-cp36m-win32.whl", hash = "sha256:2650beee182fed66617e18474b943e72e52f10a24dc8cac1db36c41ee9c041b7"},
+ {file = "netifaces-0.11.0-cp36-cp36m-win_amd64.whl", hash = "sha256:cb925e1ca024d6f9b4f9b01d83215fd00fe69d095d0255ff3f64bffda74025c8"},
+ {file = "netifaces-0.11.0-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:84e4d2e6973eccc52778735befc01638498781ce0e39aa2044ccfd2385c03246"},
+ {file = "netifaces-0.11.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:18917fbbdcb2d4f897153c5ddbb56b31fa6dd7c3fa9608b7e3c3a663df8206b5"},
+ {file = "netifaces-0.11.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:48324183af7f1bc44f5f197f3dad54a809ad1ef0c78baee2c88f16a5de02c4c9"},
+ {file = "netifaces-0.11.0-cp37-cp37m-win32.whl", hash = "sha256:8f7da24eab0d4184715d96208b38d373fd15c37b0dafb74756c638bd619ba150"},
+ {file = "netifaces-0.11.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2479bb4bb50968089a7c045f24d120f37026d7e802ec134c4490eae994c729b5"},
+ {file = "netifaces-0.11.0-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:3ecb3f37c31d5d51d2a4d935cfa81c9bc956687c6f5237021b36d6fdc2815b2c"},
+ {file = "netifaces-0.11.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:96c0fe9696398253f93482c84814f0e7290eee0bfec11563bd07d80d701280c3"},
+ {file = "netifaces-0.11.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c92ff9ac7c2282009fe0dcb67ee3cd17978cffbe0c8f4b471c00fe4325c9b4d4"},
+ {file = "netifaces-0.11.0-cp38-cp38-win32.whl", hash = "sha256:d07b01c51b0b6ceb0f09fc48ec58debd99d2c8430b09e56651addeaf5de48048"},
+ {file = "netifaces-0.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:469fc61034f3daf095e02f9f1bbac07927b826c76b745207287bc594884cfd05"},
+ {file = "netifaces-0.11.0-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:5be83986100ed1fdfa78f11ccff9e4757297735ac17391b95e17e74335c2047d"},
+ {file = "netifaces-0.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:54ff6624eb95b8a07e79aa8817288659af174e954cca24cdb0daeeddfc03c4ff"},
+ {file = "netifaces-0.11.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:841aa21110a20dc1621e3dd9f922c64ca64dd1eb213c47267a2c324d823f6c8f"},
+ {file = "netifaces-0.11.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e76c7f351e0444721e85f975ae92718e21c1f361bda946d60a214061de1f00a1"},
+ {file = "netifaces-0.11.0.tar.gz", hash = "sha256:043a79146eb2907edf439899f262b3dfe41717d34124298ed281139a8b93ca32"},
+]
+networkx = [
+ {file = "networkx-2.8-py3-none-any.whl", hash = "sha256:1a1e8fe052cc1b4e0339b998f6795099562a264a13a5af7a32cad45ab9d4e126"},
+ {file = "networkx-2.8.tar.gz", hash = "sha256:4a52cf66aed221955420e11b3e2e05ca44196b4829aab9576d4d439212b0a14f"},
+]
+openstacksdk = [
+ {file = "openstacksdk-0.61.0-py3-none-any.whl", hash = "sha256:9894d3d510563dcfc50c4755287dbfbf98def1f37caf2cfc15e9d0e1fd5d9a41"},
+ {file = "openstacksdk-0.61.0.tar.gz", hash = "sha256:3eed308871230f0c53a8f58b6c5a358b184080c6b2c6bc69ab088eea057aa127"},
+]
+os-service-types = [
+ {file = "os-service-types-1.7.0.tar.gz", hash = "sha256:31800299a82239363995b91f1ebf9106ac7758542a1e4ef6dc737a5932878c6c"},
+ {file = "os_service_types-1.7.0-py2.py3-none-any.whl", hash = "sha256:0505c72205690910077fb72b88f2a1f07533c8d39f2fe75b29583481764965d6"},
+]
+osc-lib = [
+ {file = "osc-lib-2.5.0.tar.gz", hash = "sha256:d8f8a450fab2a1294e0aef8cdc9a255a1bcc5b58e1b2f6098e35e6db1e13e9d1"},
+ {file = "osc_lib-2.5.0-py3-none-any.whl", hash = "sha256:d6ad4f39a4582bc0ac9fba93a73b29d5ac22cb36e325db646194039fed5fa33b"},
+]
+"oslo.config" = [
+ {file = "oslo.config-8.8.0-py3-none-any.whl", hash = "sha256:b1e2a398450ea35a8e5630d8b23057b8939838c4433cd25a20cc3a36d5df9e3b"},
+ {file = "oslo.config-8.8.0.tar.gz", hash = "sha256:96933d3011dae15608a11616bfb00d947e22da3cb09b6ff37ddd7576abd4764c"},
+]
+"oslo.i18n" = [
+ {file = "oslo.i18n-5.1.0-py3-none-any.whl", hash = "sha256:75086cfd898819638ca741159f677e2073a78ca86a9c9be8d38b46800cdf2dc9"},
+ {file = "oslo.i18n-5.1.0.tar.gz", hash = "sha256:6bf111a6357d5449640852de4640eae4159b5562bbba4c90febb0034abc095d0"},
+]
+"oslo.serialization" = [
+ {file = "oslo.serialization-4.3.0-py3-none-any.whl", hash = "sha256:6c1c483231c3827787af9b6ca4a45f4e45fe364772a24692b02de78fe48eafb1"},
+ {file = "oslo.serialization-4.3.0.tar.gz", hash = "sha256:3aa472f434aee8bbcc0725312b7f409aa1fa54bbc134904124cf49b0e86b9115"},
+]
+"oslo.utils" = [
+ {file = "oslo.utils-4.13.0-py3-none-any.whl", hash = "sha256:dab26f205980a379fe7068dd4f9010809a2ae7dddcbecde53e18cf8fa4a251d9"},
+ {file = "oslo.utils-4.13.0.tar.gz", hash = "sha256:45ba8aaa5ed056a8e8e46059ef93d5c2d7b9c99bc7480e361cf5783e47f28fba"},
+]
+oyaml = [
+ {file = "oyaml-1.0-py2.py3-none-any.whl", hash = "sha256:3a378747b7fb2425533d1ce41962d6921cda075d46bb480a158d45242d156323"},
+ {file = "oyaml-1.0.tar.gz", hash = "sha256:ed8fc096811f4763e1907dce29c35895d6d5936c4d0400fe843a91133d4744ed"},
+]
+packaging = [
+ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
+ {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
+]
+paramiko = [
+ {file = "paramiko-2.10.4-py2.py3-none-any.whl", hash = "sha256:3c9ed6084f4b671ab66dc3c729092d32d96c3258f1426071301cb33654b09027"},
+ {file = "paramiko-2.10.4.tar.gz", hash = "sha256:3d2e650b6812ce6d160abff701d6ef4434ec97934b13e95cf1ad3da70ffb5c58"},
+]
+pathspec = [
+ {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"},
+ {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"},
+]
+pbr = [
+ {file = "pbr-5.9.0-py2.py3-none-any.whl", hash = "sha256:e547125940bcc052856ded43be8e101f63828c2d94239ffbe2b327ba3d5ccf0a"},
+ {file = "pbr-5.9.0.tar.gz", hash = "sha256:e8dca2f4b43560edef58813969f52a56cef023146cbb8931626db80e6c1c4308"},
+]
+planemo = [
+ {file = "planemo-0.74.9-py2.py3-none-any.whl", hash = "sha256:3fac2eef24c35b43b1949d8ed8d7b3928edb5f47e820cf58defda825c70ccc1c"},
+ {file = "planemo-0.74.9.tar.gz", hash = "sha256:4d8850c2e967810846f51a290f8f9adc9296c9e5531a23d8328fd3aea414155c"},
+]
+platformdirs = [
+ {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"},
+ {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"},
+]
+pluggy = [
+ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
+ {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
+]
+prettytable = [
+ {file = "prettytable-3.3.0-py3-none-any.whl", hash = "sha256:d1c34d72ea2c0ffd6ce5958e71c428eb21a3d40bf3133afe319b24aeed5af407"},
+ {file = "prettytable-3.3.0.tar.gz", hash = "sha256:118eb54fd2794049b810893653b20952349df6d3bc1764e7facd8a18064fa9b0"},
+]
+prov = [
+ {file = "prov-1.5.1-py2.py3-none-any.whl", hash = "sha256:5c930cbbd05424aa3066d336dc31d314dd9fa0280caeab064288e592ed716bea"},
+ {file = "prov-1.5.1.tar.gz", hash = "sha256:7a2d72b0df43cd9c6e374d815c8ce3cd5ca371d54f98f837853ac9fcc98aee4c"},
+]
+psutil = [
+ {file = "psutil-5.9.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:55ce319452e3d139e25d6c3f85a1acf12d1607ddedea5e35fb47a552c051161b"},
+ {file = "psutil-5.9.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:7336292a13a80eb93c21f36bde4328aa748a04b68c13d01dfddd67fc13fd0618"},
+ {file = "psutil-5.9.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:cb8d10461c1ceee0c25a64f2dd54872b70b89c26419e147a05a10b753ad36ec2"},
+ {file = "psutil-5.9.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:7641300de73e4909e5d148e90cc3142fb890079e1525a840cf0dfd39195239fd"},
+ {file = "psutil-5.9.0-cp27-none-win32.whl", hash = "sha256:ea42d747c5f71b5ccaa6897b216a7dadb9f52c72a0fe2b872ef7d3e1eacf3ba3"},
+ {file = "psutil-5.9.0-cp27-none-win_amd64.whl", hash = "sha256:ef216cc9feb60634bda2f341a9559ac594e2eeaadd0ba187a4c2eb5b5d40b91c"},
+ {file = "psutil-5.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:90a58b9fcae2dbfe4ba852b57bd4a1dded6b990a33d6428c7614b7d48eccb492"},
+ {file = "psutil-5.9.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff0d41f8b3e9ebb6b6110057e40019a432e96aae2008951121ba4e56040b84f3"},
+ {file = "psutil-5.9.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:742c34fff804f34f62659279ed5c5b723bb0195e9d7bd9907591de9f8f6558e2"},
+ {file = "psutil-5.9.0-cp310-cp310-win32.whl", hash = "sha256:8293942e4ce0c5689821f65ce6522ce4786d02af57f13c0195b40e1edb1db61d"},
+ {file = "psutil-5.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:9b51917c1af3fa35a3f2dabd7ba96a2a4f19df3dec911da73875e1edaf22a40b"},
+ {file = "psutil-5.9.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e9805fed4f2a81de98ae5fe38b75a74c6e6ad2df8a5c479594c7629a1fe35f56"},
+ {file = "psutil-5.9.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c51f1af02334e4b516ec221ee26b8fdf105032418ca5a5ab9737e8c87dafe203"},
+ {file = "psutil-5.9.0-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32acf55cb9a8cbfb29167cd005951df81b567099295291bcfd1027365b36591d"},
+ {file = "psutil-5.9.0-cp36-cp36m-win32.whl", hash = "sha256:e5c783d0b1ad6ca8a5d3e7b680468c9c926b804be83a3a8e95141b05c39c9f64"},
+ {file = "psutil-5.9.0-cp36-cp36m-win_amd64.whl", hash = "sha256:d62a2796e08dd024b8179bd441cb714e0f81226c352c802fca0fd3f89eeacd94"},
+ {file = "psutil-5.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3d00a664e31921009a84367266b35ba0aac04a2a6cad09c550a89041034d19a0"},
+ {file = "psutil-5.9.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7779be4025c540d1d65a2de3f30caeacc49ae7a2152108adeaf42c7534a115ce"},
+ {file = "psutil-5.9.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:072664401ae6e7c1bfb878c65d7282d4b4391f1bc9a56d5e03b5a490403271b5"},
+ {file = "psutil-5.9.0-cp37-cp37m-win32.whl", hash = "sha256:df2c8bd48fb83a8408c8390b143c6a6fa10cb1a674ca664954de193fdcab36a9"},
+ {file = "psutil-5.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1d7b433519b9a38192dfda962dd8f44446668c009833e1429a52424624f408b4"},
+ {file = "psutil-5.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c3400cae15bdb449d518545cbd5b649117de54e3596ded84aacabfbb3297ead2"},
+ {file = "psutil-5.9.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2237f35c4bbae932ee98902a08050a27821f8f6dfa880a47195e5993af4702d"},
+ {file = "psutil-5.9.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1070a9b287846a21a5d572d6dddd369517510b68710fca56b0e9e02fd24bed9a"},
+ {file = "psutil-5.9.0-cp38-cp38-win32.whl", hash = "sha256:76cebf84aac1d6da5b63df11fe0d377b46b7b500d892284068bacccf12f20666"},
+ {file = "psutil-5.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:3151a58f0fbd8942ba94f7c31c7e6b310d2989f4da74fcbf28b934374e9bf841"},
+ {file = "psutil-5.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:539e429da49c5d27d5a58e3563886057f8fc3868a5547b4f1876d9c0f007bccf"},
+ {file = "psutil-5.9.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58c7d923dc209225600aec73aa2c4ae8ea33b1ab31bc11ef8a5933b027476f07"},
+ {file = "psutil-5.9.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3611e87eea393f779a35b192b46a164b1d01167c9d323dda9b1e527ea69d697d"},
+ {file = "psutil-5.9.0-cp39-cp39-win32.whl", hash = "sha256:4e2fb92e3aeae3ec3b7b66c528981fd327fb93fd906a77215200404444ec1845"},
+ {file = "psutil-5.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:7d190ee2eaef7831163f254dc58f6d2e2a22e27382b936aab51c835fc080c3d3"},
+ {file = "psutil-5.9.0.tar.gz", hash = "sha256:869842dbd66bb80c3217158e629d6fceaecc3a3166d3d1faee515b05dd26ca25"},
+]
+pyaml = [
+ {file = "pyaml-21.10.1-py2.py3-none-any.whl", hash = "sha256:19985ed303c3a985de4cf8fd329b6d0a5a5b5c9035ea240eccc709ebacbaf4a0"},
+ {file = "pyaml-21.10.1.tar.gz", hash = "sha256:c6519fee13bf06e3bb3f20cacdea8eba9140385a7c2546df5dbae4887f768383"},
+]
+pycparser = [
+ {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"},
+ {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
+]
+pycryptodome = [
+ {file = "pycryptodome-3.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:75a3a364fee153e77ed889c957f6f94ec6d234b82e7195b117180dcc9fc16f96"},
+ {file = "pycryptodome-3.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:aae395f79fa549fb1f6e3dc85cf277f0351e15a22e6547250056c7f0c990d6a5"},
+ {file = "pycryptodome-3.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f403a3e297a59d94121cb3ee4b1cf41f844332940a62d71f9e4a009cc3533493"},
+ {file = "pycryptodome-3.14.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ce7a875694cd6ccd8682017a7c06c6483600f151d8916f2b25cf7a439e600263"},
+ {file = "pycryptodome-3.14.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:a36ab51674b014ba03da7f98b675fcb8eabd709a2d8e18219f784aba2db73b72"},
+ {file = "pycryptodome-3.14.1-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:50a5346af703330944bea503106cd50c9c2212174cfcb9939db4deb5305a8367"},
+ {file = "pycryptodome-3.14.1-cp27-cp27m-win32.whl", hash = "sha256:36e3242c4792e54ed906c53f5d840712793dc68b726ec6baefd8d978c5282d30"},
+ {file = "pycryptodome-3.14.1-cp27-cp27m-win_amd64.whl", hash = "sha256:c880a98376939165b7dc504559f60abe234b99e294523a273847f9e7756f4132"},
+ {file = "pycryptodome-3.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:dcd65355acba9a1d0fc9b923875da35ed50506e339b35436277703d7ace3e222"},
+ {file = "pycryptodome-3.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:766a8e9832128c70012e0c2b263049506cbf334fb21ff7224e2704102b6ef59e"},
+ {file = "pycryptodome-3.14.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:2562de213960693b6d657098505fd4493c45f3429304da67efcbeb61f0edfe89"},
+ {file = "pycryptodome-3.14.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d1b7739b68a032ad14c5e51f7e4e1a5f92f3628bba024a2bda1f30c481fc85d8"},
+ {file = "pycryptodome-3.14.1-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:27e92c1293afcb8d2639baf7eb43f4baada86e4de0f1fb22312bfc989b95dae2"},
+ {file = "pycryptodome-3.14.1-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:f2772af1c3ef8025c85335f8b828d0193fa1e43256621f613280e2c81bfad423"},
+ {file = "pycryptodome-3.14.1-cp35-abi3-manylinux1_i686.whl", hash = "sha256:9ec761a35dbac4a99dcbc5cd557e6e57432ddf3e17af8c3c86b44af9da0189c0"},
+ {file = "pycryptodome-3.14.1-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:e64738207a02a83590df35f59d708bf1e7ea0d6adce712a777be2967e5f7043c"},
+ {file = "pycryptodome-3.14.1-cp35-abi3-manylinux2010_i686.whl", hash = "sha256:e24d4ec4b029611359566c52f31af45c5aecde7ef90bf8f31620fd44c438efe7"},
+ {file = "pycryptodome-3.14.1-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:8b5c28058102e2974b9868d72ae5144128485d466ba8739abd674b77971454cc"},
+ {file = "pycryptodome-3.14.1-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:924b6aad5386fb54f2645f22658cb0398b1f25bc1e714a6d1522c75d527deaa5"},
+ {file = "pycryptodome-3.14.1-cp35-abi3-win32.whl", hash = "sha256:53dedbd2a6a0b02924718b520a723e88bcf22e37076191eb9b91b79934fb2192"},
+ {file = "pycryptodome-3.14.1-cp35-abi3-win_amd64.whl", hash = "sha256:ea56a35fd0d13121417d39a83f291017551fa2c62d6daa6b04af6ece7ed30d84"},
+ {file = "pycryptodome-3.14.1-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:028dcbf62d128b4335b61c9fbb7dd8c376594db607ef36d5721ee659719935d5"},
+ {file = "pycryptodome-3.14.1-pp27-pypy_73-manylinux1_x86_64.whl", hash = "sha256:69f05aaa90c99ac2f2af72d8d7f185f729721ad7c4be89e9e3d0ab101b0ee875"},
+ {file = "pycryptodome-3.14.1-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:12ef157eb1e01a157ca43eda275fa68f8db0dd2792bc4fe00479ab8f0e6ae075"},
+ {file = "pycryptodome-3.14.1-pp27-pypy_73-win32.whl", hash = "sha256:f572a3ff7b6029dd9b904d6be4e0ce9e309dcb847b03e3ac8698d9d23bb36525"},
+ {file = "pycryptodome-3.14.1-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9924248d6920b59c260adcae3ee231cd5af404ac706ad30aa4cd87051bf09c50"},
+ {file = "pycryptodome-3.14.1-pp36-pypy36_pp73-manylinux1_x86_64.whl", hash = "sha256:e0c04c41e9ade19fbc0eff6aacea40b831bfcb2c91c266137bcdfd0d7b2f33ba"},
+ {file = "pycryptodome-3.14.1-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:893f32210de74b9f8ac869ed66c97d04e7d351182d6d39ebd3b36d3db8bda65d"},
+ {file = "pycryptodome-3.14.1-pp36-pypy36_pp73-win32.whl", hash = "sha256:7fb90a5000cc9c9ff34b4d99f7f039e9c3477700e309ff234eafca7b7471afc0"},
+ {file = "pycryptodome-3.14.1.tar.gz", hash = "sha256:e04e40a7f8c1669195536a37979dd87da2c32dbdc73d6fe35f0077b0c17c803b"},
+]
+pydantic = [
+ {file = "pydantic-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cb23bcc093697cdea2708baae4f9ba0e972960a835af22560f6ae4e7e47d33f5"},
+ {file = "pydantic-1.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1d5278bd9f0eee04a44c712982343103bba63507480bfd2fc2790fa70cd64cf4"},
+ {file = "pydantic-1.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab624700dc145aa809e6f3ec93fb8e7d0f99d9023b713f6a953637429b437d37"},
+ {file = "pydantic-1.9.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c8d7da6f1c1049eefb718d43d99ad73100c958a5367d30b9321b092771e96c25"},
+ {file = "pydantic-1.9.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3c3b035103bd4e2e4a28da9da7ef2fa47b00ee4a9cf4f1a735214c1bcd05e0f6"},
+ {file = "pydantic-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3011b975c973819883842c5ab925a4e4298dffccf7782c55ec3580ed17dc464c"},
+ {file = "pydantic-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:086254884d10d3ba16da0588604ffdc5aab3f7f09557b998373e885c690dd398"},
+ {file = "pydantic-1.9.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0fe476769acaa7fcddd17cadd172b156b53546ec3614a4d880e5d29ea5fbce65"},
+ {file = "pydantic-1.9.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8e9dcf1ac499679aceedac7e7ca6d8641f0193c591a2d090282aaf8e9445a46"},
+ {file = "pydantic-1.9.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1e4c28f30e767fd07f2ddc6f74f41f034d1dd6bc526cd59e63a82fe8bb9ef4c"},
+ {file = "pydantic-1.9.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:c86229333cabaaa8c51cf971496f10318c4734cf7b641f08af0a6fbf17ca3054"},
+ {file = "pydantic-1.9.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:c0727bda6e38144d464daec31dff936a82917f431d9c39c39c60a26567eae3ed"},
+ {file = "pydantic-1.9.0-cp36-cp36m-win_amd64.whl", hash = "sha256:dee5ef83a76ac31ab0c78c10bd7d5437bfdb6358c95b91f1ba7ff7b76f9996a1"},
+ {file = "pydantic-1.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9c9bdb3af48e242838f9f6e6127de9be7063aad17b32215ccc36a09c5cf1070"},
+ {file = "pydantic-1.9.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ee7e3209db1e468341ef41fe263eb655f67f5c5a76c924044314e139a1103a2"},
+ {file = "pydantic-1.9.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b6037175234850ffd094ca77bf60fb54b08b5b22bc85865331dd3bda7a02fa1"},
+ {file = "pydantic-1.9.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b2571db88c636d862b35090ccf92bf24004393f85c8870a37f42d9f23d13e032"},
+ {file = "pydantic-1.9.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8b5ac0f1c83d31b324e57a273da59197c83d1bb18171e512908fe5dc7278a1d6"},
+ {file = "pydantic-1.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:bbbc94d0c94dd80b3340fc4f04fd4d701f4b038ebad72c39693c794fd3bc2d9d"},
+ {file = "pydantic-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e0896200b6a40197405af18828da49f067c2fa1f821491bc8f5bde241ef3f7d7"},
+ {file = "pydantic-1.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bdfdadb5994b44bd5579cfa7c9b0e1b0e540c952d56f627eb227851cda9db77"},
+ {file = "pydantic-1.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:574936363cd4b9eed8acdd6b80d0143162f2eb654d96cb3a8ee91d3e64bf4cf9"},
+ {file = "pydantic-1.9.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c556695b699f648c58373b542534308922c46a1cda06ea47bc9ca45ef5b39ae6"},
+ {file = "pydantic-1.9.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f947352c3434e8b937e3aa8f96f47bdfe6d92779e44bb3f41e4c213ba6a32145"},
+ {file = "pydantic-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5e48ef4a8b8c066c4a31409d91d7ca372a774d0212da2787c0d32f8045b1e034"},
+ {file = "pydantic-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:96f240bce182ca7fe045c76bcebfa0b0534a1bf402ed05914a6f1dadff91877f"},
+ {file = "pydantic-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:815ddebb2792efd4bba5488bc8fde09c29e8ca3227d27cf1c6990fc830fd292b"},
+ {file = "pydantic-1.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6c5b77947b9e85a54848343928b597b4f74fc364b70926b3c4441ff52620640c"},
+ {file = "pydantic-1.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c68c3bc88dbda2a6805e9a142ce84782d3930f8fdd9655430d8576315ad97ce"},
+ {file = "pydantic-1.9.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a79330f8571faf71bf93667d3ee054609816f10a259a109a0738dac983b23c3"},
+ {file = "pydantic-1.9.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f5a64b64ddf4c99fe201ac2724daada8595ada0d102ab96d019c1555c2d6441d"},
+ {file = "pydantic-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a733965f1a2b4090a5238d40d983dcd78f3ecea221c7af1497b845a9709c1721"},
+ {file = "pydantic-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cc6a4cb8a118ffec2ca5fcb47afbacb4f16d0ab8b7350ddea5e8ef7bcc53a16"},
+ {file = "pydantic-1.9.0-py3-none-any.whl", hash = "sha256:085ca1de245782e9b46cefcf99deecc67d418737a1fd3f6a4f511344b613a5b3"},
+ {file = "pydantic-1.9.0.tar.gz", hash = "sha256:742645059757a56ecd886faf4ed2441b9c0cd406079c2b4bee51bcc3fbcd510a"},
+]
+pydot = [
+ {file = "pydot-1.4.2-py2.py3-none-any.whl", hash = "sha256:66c98190c65b8d2e2382a441b4c0edfdb4f4c025ef9cb9874de478fb0793a451"},
+ {file = "pydot-1.4.2.tar.gz", hash = "sha256:248081a39bcb56784deb018977e428605c1c758f10897a339fce1dd728ff007d"},
+]
+pynacl = [
+ {file = "PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1"},
+ {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92"},
+ {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394"},
+ {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d"},
+ {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858"},
+ {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b"},
+ {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff"},
+ {file = "PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543"},
+ {file = "PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93"},
+ {file = "PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba"},
+]
+pyparsing = [
+ {file = "pyparsing-3.0.8-py3-none-any.whl", hash = "sha256:ef7b523f6356f763771559412c0d7134753f037822dad1b16945b7b846f7ad06"},
+ {file = "pyparsing-3.0.8.tar.gz", hash = "sha256:7bf433498c016c4314268d95df76c81b842a4cb2b276fa3312cfb1e1d85f6954"},
+]
+pyperclip = [
+ {file = "pyperclip-1.8.2.tar.gz", hash = "sha256:105254a8b04934f0bc84e9c24eb360a591aaf6535c9def5f29d92af107a9bf57"},
+]
+pyreadline3 = [
+ {file = "pyreadline3-3.4.1-py3-none-any.whl", hash = "sha256:b0efb6516fd4fb07b45949053826a62fa4cb353db5be2bbb4a7aa1fdd1e345fb"},
+ {file = "pyreadline3-3.4.1.tar.gz", hash = "sha256:6f3d1f7b8a31ba32b73917cefc1f28cc660562f39aea8646d30bd6eff21f7bae"},
+]
+pysam = [
+ {file = "pysam-0.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:44de1a3af7c7eb5f404d6337f0c9c4ee88c34c2d2fee1a7896ccd8e7d2aa475a"},
+ {file = "pysam-0.19.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0ceb07c6253598ec70fef6ac0c0f7ab0d299562c1a91e737adb07239afba22d6"},
+ {file = "pysam-0.19.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2057f3b8cc20562fd010e7971e83ab78978f17975563a711c94bca583ce8a2d3"},
+ {file = "pysam-0.19.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a1a9ee6cd6dfa50973dcb51cd2245ea7d4d64d4e962d60e5e1a088f7b790e3e"},
+ {file = "pysam-0.19.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b831170ff810bfd1242dbce4ddf8e693e95e39a4332d5903d416233d3d1be50a"},
+ {file = "pysam-0.19.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:fa4a5d334986d0696522246820f295cbf6c18dc1b78798f800a2d57d56207789"},
+ {file = "pysam-0.19.0-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ab58d7d9b140e2e8a4918fc00661aa901ba461d9bccd4b499102b0828f2d897e"},
+ {file = "pysam-0.19.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1d964c29fedf55d2a5d095227d19d915c2e0e5e42b1e0bdf7fab30cd1d2a850"},
+ {file = "pysam-0.19.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8c78f9b84f7fe69530eaccf5b7f08636b3419f0017b5050aa7013f1c59d3d654"},
+ {file = "pysam-0.19.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ada92b376717ac4c9c9924a096af9186035115d29a113eaa997d1c020ce421b9"},
+ {file = "pysam-0.19.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:80b8f5b929c7f660b6e1497790a13c61f386b310a31ca54ad6ba110674d11c55"},
+ {file = "pysam-0.19.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30017bed8d002d41f83fa7e10569525c811a8e3860d73a880ee653ef29fd4fbc"},
+ {file = "pysam-0.19.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5e2e465522ba2c34cb96c013a28f9c9db303f8ab1350b4c11cca73677f1e9594"},
+ {file = "pysam-0.19.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2103b4b6d9b0cc0b4aaccf64e87a92bfdabb7dc92810cf84be35ffe78fafa002"},
+ {file = "pysam-0.19.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:eaf342b9c71ed83a63237df2000e3bc1f0236165d48fd7240c4c78b66f28d63b"},
+ {file = "pysam-0.19.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc0021b154206edfbaa13515cb67523c76c576b7a670e72a793f2da4f03139f4"},
+ {file = "pysam-0.19.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3101e0fcd2f6accbfa72a554a71baf83f1c096bb0f9045059b3ead35901ce128"},
+ {file = "pysam-0.19.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5118dcabac574638df43739941f0ee20cc4e9197aee4a8f10f195542a13f18e3"},
+ {file = "pysam-0.19.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4fe1f1fae0e559d3412625dc7d4d08b477e80211b3fe5b267ba341a84f78b8be"},
+ {file = "pysam-0.19.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2911e3760dd2b709c686f075146787b8bda35629093352c28a7e52ac00ddaffc"},
+ {file = "pysam-0.19.0.tar.gz", hash = "sha256:dcc052566f9509fd93b2a2664f094a13f016fd60cdd189e05fb4eafa0c89505b"},
+]
+pyserde = [
+ {file = "pyserde-0.6.0-py3-none-any.whl", hash = "sha256:89775789b20b9e03ee773502a619361063613c284762b77c5d27cfa000f3a94c"},
+ {file = "pyserde-0.6.0.tar.gz", hash = "sha256:428fdb97824626e83f962c5b26ef67a3880fe11c5ab722f731fce481142d28b4"},
+]
+python-cinderclient = [
+ {file = "python-cinderclient-8.3.0.tar.gz", hash = "sha256:e00103875029dc85cbb59131d00ccc8534f692956acde32b5a3cc5af4c24580b"},
+ {file = "python_cinderclient-8.3.0-py3-none-any.whl", hash = "sha256:a19ce34f8efad8eabe874494295e6f8251e4b5f02a4c3d648c522259f3b5ae0a"},
+]
+python-dateutil = [
+ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
+ {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
+]
+python-keystoneclient = [
+ {file = "python-keystoneclient-4.4.0.tar.gz", hash = "sha256:fc17ca9a1aa493104b496ba347f12507f271b5b6e819f4de4aef6574918aa071"},
+ {file = "python_keystoneclient-4.4.0-py3-none-any.whl", hash = "sha256:1a9f30bfa3a5bf004159736d8cec91f8ce47301a0e5ad976b9cd064f5de39295"},
+]
+python-novaclient = [
+ {file = "python-novaclient-17.7.0.tar.gz", hash = "sha256:4ebc27f4ce06c155b8e991a44463b9358596e6856cf171c9af8dc7568d868bed"},
+ {file = "python_novaclient-17.7.0-py3-none-any.whl", hash = "sha256:7f3cbe33a65f8fc3be4d34b29c09c16f50faa5465fd78fc9615b03c1880a8e6b"},
+]
+python-openstackclient = [
+ {file = "python-openstackclient-5.8.0.tar.gz", hash = "sha256:334852df8897b95f0581ec12ee287de8c7a9289a208a18f0a8b38777019fd986"},
+ {file = "python_openstackclient-5.8.0-py3-none-any.whl", hash = "sha256:93054487ad1235c3f413f50aef4f7981b532c81d7e9f1d9ea4dd2f28705621db"},
+]
+pytz = [
+ {file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"},
+ {file = "pytz-2022.1.tar.gz", hash = "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7"},
+]
+pyyaml = [
+ {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"},
+ {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"},
+ {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"},
+ {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"},
+ {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"},
+ {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"},
+ {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"},
+ {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"},
+ {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"},
+ {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"},
+ {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"},
+ {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"},
+ {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"},
+ {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"},
+ {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"},
+ {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"},
+ {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"},
+ {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"},
+ {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"},
+ {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"},
+ {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"},
+ {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"},
+ {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"},
+ {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"},
+ {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"},
+ {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"},
+ {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"},
+ {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"},
+ {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"},
+ {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"},
+ {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"},
+ {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"},
+ {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"},
+]
+rdflib = [
+ {file = "rdflib-6.1.1-py3-none-any.whl", hash = "sha256:fc81cef513cd552d471f2926141396b633207109d0154c8e77926222c70367fe"},
+ {file = "rdflib-6.1.1.tar.gz", hash = "sha256:8dbfa0af2990b98471dacbc936d6494c997ede92fd8ed693fb84ee700ef6f754"},
+]
+"repoze.lru" = [
+ {file = "repoze.lru-0.7-py3-none-any.whl", hash = "sha256:f77bf0e1096ea445beadd35f3479c5cff2aa1efe604a133e67150bc8630a62ea"},
+ {file = "repoze.lru-0.7.tar.gz", hash = "sha256:0429a75e19380e4ed50c0694e26ac8819b4ea7851ee1fc7583c8572db80aff77"},
+]
+requests = [
+ {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"},
+ {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"},
+]
+requests-toolbelt = [
+ {file = "requests-toolbelt-0.9.1.tar.gz", hash = "sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"},
+ {file = "requests_toolbelt-0.9.1-py2.py3-none-any.whl", hash = "sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f"},
+]
+requestsexceptions = [
+ {file = "requestsexceptions-1.4.0-py2.py3-none-any.whl", hash = "sha256:3083d872b6e07dc5c323563ef37671d992214ad9a32b0ca4a3d7f5500bf38ce3"},
+ {file = "requestsexceptions-1.4.0.tar.gz", hash = "sha256:b095cbc77618f066d459a02b137b020c37da9f46d9b057704019c9f77dba3065"},
+]
+resolvelib = [
+ {file = "resolvelib-0.5.5-py2.py3-none-any.whl", hash = "sha256:b0143b9d074550a6c5163a0f587e49c49017434e3cdfe853941725f5455dd29c"},
+ {file = "resolvelib-0.5.5.tar.gz", hash = "sha256:123de56548c90df85137425a3f51eb93df89e2ba719aeb6a8023c032758be950"},
+]
+rfc3986 = [
+ {file = "rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd"},
+ {file = "rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c"},
+]
+routes = [
+ {file = "Routes-2.5.1-py2.py3-none-any.whl", hash = "sha256:fab5a042a3a87778eb271d053ca2723cadf43c95b471532a191a48539cb606ea"},
+ {file = "Routes-2.5.1.tar.gz", hash = "sha256:b6346459a15f0cbab01a45a90c3d25caf980d4733d628b4cc1952b865125d053"},
+]
+"ruamel.yaml" = [
+ {file = "ruamel.yaml-0.17.21-py3-none-any.whl", hash = "sha256:742b35d3d665023981bd6d16b3d24248ce5df75fdb4e2924e93a05c1f8b61ca7"},
+ {file = "ruamel.yaml-0.17.21.tar.gz", hash = "sha256:8b7ce697a2f212752a35c1ac414471dc16c424c9573be4926b56ff3f5d23b7af"},
+]
+"ruamel.yaml.clib" = [
+ {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6e7be2c5bcb297f5b82fee9c665eb2eb7001d1050deaba8471842979293a80b0"},
+ {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:221eca6f35076c6ae472a531afa1c223b9c29377e62936f61bc8e6e8bdc5f9e7"},
+ {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-win32.whl", hash = "sha256:1070ba9dd7f9370d0513d649420c3b362ac2d687fe78c6e888f5b12bf8bc7bee"},
+ {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:77df077d32921ad46f34816a9a16e6356d8100374579bc35e15bab5d4e9377de"},
+ {file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:cfdb9389d888c5b74af297e51ce357b800dd844898af9d4a547ffc143fa56751"},
+ {file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7b2927e92feb51d830f531de4ccb11b320255ee95e791022555971c466af4527"},
+ {file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-win32.whl", hash = "sha256:ada3f400d9923a190ea8b59c8f60680c4ef8a4b0dfae134d2f2ff68429adfab5"},
+ {file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-win_amd64.whl", hash = "sha256:de9c6b8a1ba52919ae919f3ae96abb72b994dd0350226e28f3686cb4f142165c"},
+ {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d67f273097c368265a7b81e152e07fb90ed395df6e552b9fa858c6d2c9f42502"},
+ {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:72a2b8b2ff0a627496aad76f37a652bcef400fd861721744201ef1b45199ab78"},
+ {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-win32.whl", hash = "sha256:9efef4aab5353387b07f6b22ace0867032b900d8e91674b5d8ea9150db5cae94"},
+ {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-win_amd64.whl", hash = "sha256:846fc8336443106fe23f9b6d6b8c14a53d38cef9a375149d61f99d78782ea468"},
+ {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0847201b767447fc33b9c235780d3aa90357d20dd6108b92be544427bea197dd"},
+ {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:78988ed190206672da0f5d50c61afef8f67daa718d614377dcd5e3ed85ab4a99"},
+ {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-win32.whl", hash = "sha256:a49e0161897901d1ac9c4a79984b8410f450565bbad64dbfcbf76152743a0cdb"},
+ {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-win_amd64.whl", hash = "sha256:bf75d28fa071645c529b5474a550a44686821decebdd00e21127ef1fd566eabe"},
+ {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a32f8d81ea0c6173ab1b3da956869114cae53ba1e9f72374032e33ba3118c233"},
+ {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7f7ecb53ae6848f959db6ae93bdff1740e651809780822270eab111500842a84"},
+ {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-win32.whl", hash = "sha256:89221ec6d6026f8ae859c09b9718799fea22c0e8da8b766b0b2c9a9ba2db326b"},
+ {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-win_amd64.whl", hash = "sha256:31ea73e564a7b5fbbe8188ab8b334393e06d997914a4e184975348f204790277"},
+ {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dc6a613d6c74eef5a14a214d433d06291526145431c3b964f5e16529b1842bed"},
+ {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:1866cf2c284a03b9524a5cc00daca56d80057c5ce3cdc86a52020f4c720856f0"},
+ {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-win32.whl", hash = "sha256:3fb9575a5acd13031c57a62cc7823e5d2ff8bc3835ba4d94b921b4e6ee664104"},
+ {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-win_amd64.whl", hash = "sha256:825d5fccef6da42f3c8eccd4281af399f21c02b32d98e113dbc631ea6a6ecbc7"},
+ {file = "ruamel.yaml.clib-0.2.6.tar.gz", hash = "sha256:4ff604ce439abb20794f05613c374759ce10e3595d1867764dd1ae675b85acbd"},
+]
+s3transfer = [
+ {file = "s3transfer-0.6.0-py3-none-any.whl", hash = "sha256:06176b74f3a15f61f1b4f25a1fc29a4429040b7647133a463da8fa5bd28d5ecd"},
+ {file = "s3transfer-0.6.0.tar.gz", hash = "sha256:2ed07d3866f523cc561bf4a00fc5535827981b117dd7876f036b0c1aca42c947"},
+]
+schema-salad = [
+ {file = "schema-salad-8.2.20220204150214.tar.gz", hash = "sha256:3e53dbfe7137796b9e5135920e96bb2713ded9e7be2859dae554d1dc8b029704"},
+ {file = "schema_salad-8.2.20220204150214-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:609ba090fb40f493cedf64bb124c1d208af7388b6663af9d76a91a03ec7d8710"},
+ {file = "schema_salad-8.2.20220204150214-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecf2c386cff04f7bc08b0ec4f3f3f0c6196043d746799c64a98ca4cb596e4c9f"},
+ {file = "schema_salad-8.2.20220204150214-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8ec4ad0f1fd56637ad3f935d647adceaebc28bbe7c204cf24165c231320c09b8"},
+ {file = "schema_salad-8.2.20220204150214-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77ff2a0d76e490f5c39375ca4d3f625fffa51a93aa719966a5c06b5375f76fc8"},
+ {file = "schema_salad-8.2.20220204150214-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86addbf36269646914c666035318ca38a5e84a4ae5732c013cde3f03550ca5f7"},
+ {file = "schema_salad-8.2.20220204150214-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b72181040f4330decdad422130fac6d7953d0415843a6bb02d6e7835f20e10e7"},
+ {file = "schema_salad-8.2.20220204150214-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b27300acf4d71547dae3c0f065789075c157391de74e71fdbf1daf318ef958dc"},
+ {file = "schema_salad-8.2.20220204150214-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd1f72602c21fb4e201cecba36c60f896f0cc95edaeb49bb9b720ed9869af372"},
+ {file = "schema_salad-8.2.20220204150214-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2de190abaccd39408e384982c0bbb871708502ebb81682cf0a6e522bcdf86dc3"},
+ {file = "schema_salad-8.2.20220204150214-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c8d4b433f745e3c51fa8ea83ab319c45269af3003cb180d53e6eb719c1f8606"},
+ {file = "schema_salad-8.2.20220204150214-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4976cbb5c4801b29295bdadb3e234e7b9572b1b9716b063f79806cb30820181"},
+ {file = "schema_salad-8.2.20220204150214-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1627e1666e8f835d337926fac65d5484128ce5798fefa4bb1a0ae862e1f9ec82"},
+ {file = "schema_salad-8.2.20220204150214-py3-none-any.whl", hash = "sha256:ce49adf0bc97fd44d99df98bf00d6bc66b38a2483b3cc721cbd200d010add3c5"},
+]
+shellescape = [
+ {file = "shellescape-3.8.1-py2.py3-none-any.whl", hash = "sha256:f17127e390fa3f9aaa80c69c16ea73615fd9b5318fd8309c1dca6168ae7d85bf"},
+ {file = "shellescape-3.8.1.tar.gz", hash = "sha256:40b310b30479be771bf3ab28bd8d40753778488bd46ea0969ba0b35038c3ec26"},
+]
+simplejson = [
+ {file = "simplejson-3.17.6-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a89acae02b2975b1f8e4974cb8cdf9bf9f6c91162fb8dec50c259ce700f2770a"},
+ {file = "simplejson-3.17.6-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:82ff356ff91be0ab2293fc6d8d262451eb6ac4fd999244c4b5f863e049ba219c"},
+ {file = "simplejson-3.17.6-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:0de783e9c2b87bdd75b57efa2b6260c24b94605b5c9843517577d40ee0c3cc8a"},
+ {file = "simplejson-3.17.6-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:d24a9e61df7a7787b338a58abfba975414937b609eb6b18973e25f573bc0eeeb"},
+ {file = "simplejson-3.17.6-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:e8603e691580487f11306ecb066c76f1f4a8b54fb3bdb23fa40643a059509366"},
+ {file = "simplejson-3.17.6-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:9b01e7b00654115965a206e3015f0166674ec1e575198a62a977355597c0bef5"},
+ {file = "simplejson-3.17.6-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:37bc0cf0e5599f36072077e56e248f3336917ded1d33d2688624d8ed3cefd7d2"},
+ {file = "simplejson-3.17.6-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:cf6e7d5fe2aeb54898df18db1baf479863eae581cce05410f61f6b4188c8ada1"},
+ {file = "simplejson-3.17.6-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:bdfc54b4468ed4cd7415928cbe782f4d782722a81aeb0f81e2ddca9932632211"},
+ {file = "simplejson-3.17.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:dd16302d39c4d6f4afde80edd0c97d4db643327d355a312762ccd9bd2ca515ed"},
+ {file = "simplejson-3.17.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:deac4bdafa19bbb89edfb73b19f7f69a52d0b5bd3bb0c4ad404c1bbfd7b4b7fd"},
+ {file = "simplejson-3.17.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a8bbdb166e2fb816e43ab034c865147edafe28e1b19c72433147789ac83e2dda"},
+ {file = "simplejson-3.17.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7854326920d41c3b5d468154318fe6ba4390cb2410480976787c640707e0180"},
+ {file = "simplejson-3.17.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:04e31fa6ac8e326480703fb6ded1488bfa6f1d3f760d32e29dbf66d0838982ce"},
+ {file = "simplejson-3.17.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f63600ec06982cdf480899026f4fda622776f5fabed9a869fdb32d72bc17e99a"},
+ {file = "simplejson-3.17.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e03c3b8cc7883a54c3f34a6a135c4a17bc9088a33f36796acdb47162791b02f6"},
+ {file = "simplejson-3.17.6-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a2d30d6c1652140181dc6861f564449ad71a45e4f165a6868c27d36745b65d40"},
+ {file = "simplejson-3.17.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a1aa6e4cae8e3b8d5321be4f51c5ce77188faf7baa9fe1e78611f93a8eed2882"},
+ {file = "simplejson-3.17.6-cp310-cp310-win32.whl", hash = "sha256:97202f939c3ff341fc3fa84d15db86156b1edc669424ba20b0a1fcd4a796a045"},
+ {file = "simplejson-3.17.6-cp310-cp310-win_amd64.whl", hash = "sha256:80d3bc9944be1d73e5b1726c3bbfd2628d3d7fe2880711b1eb90b617b9b8ac70"},
+ {file = "simplejson-3.17.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9fa621b3c0c05d965882c920347b6593751b7ab20d8fa81e426f1735ca1a9fc7"},
+ {file = "simplejson-3.17.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd2fb11922f58df8528adfca123f6a84748ad17d066007e7ac977720063556bd"},
+ {file = "simplejson-3.17.6-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:724c1fe135aa437d5126138d977004d165a3b5e2ee98fc4eb3e7c0ef645e7e27"},
+ {file = "simplejson-3.17.6-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4ff4ac6ff3aa8f814ac0f50bf218a2e1a434a17aafad4f0400a57a8cc62ef17f"},
+ {file = "simplejson-3.17.6-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:67093a526e42981fdd954868062e56c9b67fdd7e712616cc3265ad0c210ecb51"},
+ {file = "simplejson-3.17.6-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:5d6b4af7ad7e4ac515bc6e602e7b79e2204e25dbd10ab3aa2beef3c5a9cad2c7"},
+ {file = "simplejson-3.17.6-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:1c9b1ed7ed282b36571638297525f8ef80f34b3e2d600a56f962c6044f24200d"},
+ {file = "simplejson-3.17.6-cp36-cp36m-win32.whl", hash = "sha256:632ecbbd2228575e6860c9e49ea3cc5423764d5aa70b92acc4e74096fb434044"},
+ {file = "simplejson-3.17.6-cp36-cp36m-win_amd64.whl", hash = "sha256:4c09868ddb86bf79b1feb4e3e7e4a35cd6e61ddb3452b54e20cf296313622566"},
+ {file = "simplejson-3.17.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4b6bd8144f15a491c662f06814bd8eaa54b17f26095bb775411f39bacaf66837"},
+ {file = "simplejson-3.17.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5decdc78849617917c206b01e9fc1d694fd58caa961be816cb37d3150d613d9a"},
+ {file = "simplejson-3.17.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:521877c7bd060470806eb6335926e27453d740ac1958eaf0d8c00911bc5e1802"},
+ {file = "simplejson-3.17.6-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:65b998193bd7b0c7ecdfffbc825d808eac66279313cb67d8892bb259c9d91494"},
+ {file = "simplejson-3.17.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ac786f6cb7aa10d44e9641c7a7d16d7f6e095b138795cd43503769d4154e0dc2"},
+ {file = "simplejson-3.17.6-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3ff5b3464e1ce86a8de8c88e61d4836927d5595c2162cab22e96ff551b916e81"},
+ {file = "simplejson-3.17.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:69bd56b1d257a91e763256d63606937ae4eb890b18a789b66951c00062afec33"},
+ {file = "simplejson-3.17.6-cp37-cp37m-win32.whl", hash = "sha256:b81076552d34c27e5149a40187a8f7e2abb2d3185576a317aaf14aeeedad862a"},
+ {file = "simplejson-3.17.6-cp37-cp37m-win_amd64.whl", hash = "sha256:07ecaafc1b1501f275bf5acdee34a4ad33c7c24ede287183ea77a02dc071e0c0"},
+ {file = "simplejson-3.17.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:068670af975247acbb9fc3d5393293368cda17026db467bf7a51548ee8f17ee1"},
+ {file = "simplejson-3.17.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4d1c135af0c72cb28dd259cf7ba218338f4dc027061262e46fe058b4e6a4c6a3"},
+ {file = "simplejson-3.17.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:23fe704da910ff45e72543cbba152821685a889cf00fc58d5c8ee96a9bad5f94"},
+ {file = "simplejson-3.17.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f444762fed1bc1fd75187ef14a20ed900c1fbb245d45be9e834b822a0223bc81"},
+ {file = "simplejson-3.17.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:681eb4d37c9a9a6eb9b3245a5e89d7f7b2b9895590bb08a20aa598c1eb0a1d9d"},
+ {file = "simplejson-3.17.6-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8e8607d8f6b4f9d46fee11447e334d6ab50e993dd4dbfb22f674616ce20907ab"},
+ {file = "simplejson-3.17.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b10556817f09d46d420edd982dd0653940b90151d0576f09143a8e773459f6fe"},
+ {file = "simplejson-3.17.6-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e1ec8a9ee0987d4524ffd6299e778c16cc35fef6d1a2764e609f90962f0b293a"},
+ {file = "simplejson-3.17.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0b4126cac7d69ac06ff22efd3e0b3328a4a70624fcd6bca4fc1b4e6d9e2e12bf"},
+ {file = "simplejson-3.17.6-cp38-cp38-win32.whl", hash = "sha256:35a49ebef25f1ebdef54262e54ae80904d8692367a9f208cdfbc38dbf649e00a"},
+ {file = "simplejson-3.17.6-cp38-cp38-win_amd64.whl", hash = "sha256:743cd768affaa508a21499f4858c5b824ffa2e1394ed94eb85caf47ac0732198"},
+ {file = "simplejson-3.17.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:fb62d517a516128bacf08cb6a86ecd39fb06d08e7c4980251f5d5601d29989ba"},
+ {file = "simplejson-3.17.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:12133863178a8080a3dccbf5cb2edfab0001bc41e5d6d2446af2a1131105adfe"},
+ {file = "simplejson-3.17.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5540fba2d437edaf4aa4fbb80f43f42a8334206ad1ad3b27aef577fd989f20d9"},
+ {file = "simplejson-3.17.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d74ee72b5071818a1a5dab47338e87f08a738cb938a3b0653b9e4d959ddd1fd9"},
+ {file = "simplejson-3.17.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:28221620f4dcabdeac310846629b976e599a13f59abb21616356a85231ebd6ad"},
+ {file = "simplejson-3.17.6-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b09bc62e5193e31d7f9876220fb429ec13a6a181a24d897b9edfbbdbcd678851"},
+ {file = "simplejson-3.17.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7255a37ff50593c9b2f1afa8fafd6ef5763213c1ed5a9e2c6f5b9cc925ab979f"},
+ {file = "simplejson-3.17.6-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:401d40969cee3df7bda211e57b903a534561b77a7ade0dd622a8d1a31eaa8ba7"},
+ {file = "simplejson-3.17.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a649d0f66029c7eb67042b15374bd93a26aae202591d9afd71e111dd0006b198"},
+ {file = "simplejson-3.17.6-cp39-cp39-win32.whl", hash = "sha256:522fad7be85de57430d6d287c4b635813932946ebf41b913fe7e880d154ade2e"},
+ {file = "simplejson-3.17.6-cp39-cp39-win_amd64.whl", hash = "sha256:3fe87570168b2ae018391e2b43fbf66e8593a86feccb4b0500d134c998983ccc"},
+ {file = "simplejson-3.17.6.tar.gz", hash = "sha256:cf98038d2abf63a1ada5730e91e84c642ba6c225b0198c3684151b1f80c5f8a6"},
+]
+six = [
+ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
+ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
+]
+sortedcontainers = [
+ {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"},
+ {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"},
+]
+soupsieve = [
+ {file = "soupsieve-2.3.2.post1-py3-none-any.whl", hash = "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759"},
+ {file = "soupsieve-2.3.2.post1.tar.gz", hash = "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d"},
+]
+stevedore = [
+ {file = "stevedore-3.5.0-py3-none-any.whl", hash = "sha256:a547de73308fd7e90075bb4d301405bebf705292fa90a90fc3bcf9133f58616c"},
+ {file = "stevedore-3.5.0.tar.gz", hash = "sha256:f40253887d8712eaa2bb0ea3830374416736dc8ec0e22f5a65092c1174c44335"},
+]
+stringcase = [
+ {file = "stringcase-1.2.0.tar.gz", hash = "sha256:48a06980661908efe8d9d34eab2b6c13aefa2163b3ced26972902e3bdfd87008"},
+]
+tabulate = [
+ {file = "tabulate-0.8.9-py3-none-any.whl", hash = "sha256:d7c013fe7abbc5e491394e10fa845f8f32fe54f8dc60c6622c6cf482d25d47e4"},
+ {file = "tabulate-0.8.9.tar.gz", hash = "sha256:eb1d13f25760052e8931f2ef80aaf6045a6cceb47514db8beab24cded16f13a7"},
+]
+tinydb = []
+tomli = [
+ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
+ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
+]
+tuspy = []
+types-cryptography = [
+ {file = "types-cryptography-3.3.21.tar.gz", hash = "sha256:ad1b9c63159c009f8676c7e41a4d595dfb96e8c03affa2e693e1617908bb409e"},
+ {file = "types_cryptography-3.3.21-py3-none-any.whl", hash = "sha256:bdeb6dd07280ac724e05f02e0d8ef01fdef729b18bb07d635d64de83171a4e70"},
+]
+types-paramiko = [
+ {file = "types-paramiko-2.10.0.tar.gz", hash = "sha256:ab6893d5fce5ed06964d61939ed6a7168ab14952945a90995a628a5e82a5e161"},
+ {file = "types_paramiko-2.10.0-py3-none-any.whl", hash = "sha256:f558bb26ff3b085f7282125559927ebf9e1c08410cd4aac5766addbf4d15c8bc"},
+]
+types-requests = [
+ {file = "types-requests-2.27.25.tar.gz", hash = "sha256:805ae7e38fd9d157153066dc4381cf585fd34dfa212f2fc1fece248c05aac571"},
+ {file = "types_requests-2.27.25-py3-none-any.whl", hash = "sha256:2444905c89731dbcb6bbcd6d873a04252445df7623917c640e463b2b28d2a708"},
+]
+types-urllib3 = [
+ {file = "types-urllib3-1.26.14.tar.gz", hash = "sha256:2a2578e4b36341ccd240b00fccda9826988ff0589a44ba4a664bbd69ef348d27"},
+ {file = "types_urllib3-1.26.14-py3-none-any.whl", hash = "sha256:5d2388aa76395b1e3999ff789ea5b3283677dad8e9bcf3d9117ba19271fd35d9"},
+]
+typing-extensions = [
+ {file = "typing_extensions-4.2.0-py3-none-any.whl", hash = "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708"},
+ {file = "typing_extensions-4.2.0.tar.gz", hash = "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376"},
+]
+typing-inspect = [
+ {file = "typing_inspect-0.7.1-py2-none-any.whl", hash = "sha256:b1f56c0783ef0f25fb064a01be6e5407e54cf4a4bf4f3ba3fe51e0bd6dcea9e5"},
+ {file = "typing_inspect-0.7.1-py3-none-any.whl", hash = "sha256:3cd7d4563e997719a710a3bfe7ffb544c6b72069b6812a02e9b414a8fa3aaa6b"},
+ {file = "typing_inspect-0.7.1.tar.gz", hash = "sha256:047d4097d9b17f46531bf6f014356111a1b6fb821a24fe7ac909853ca2a782aa"},
+]
+urllib3 = [
+ {file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"},
+ {file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"},
+]
+virtualenv = [
+ {file = "virtualenv-20.14.1-py2.py3-none-any.whl", hash = "sha256:e617f16e25b42eb4f6e74096b9c9e37713cf10bf30168fb4a739f3fa8f898a3a"},
+ {file = "virtualenv-20.14.1.tar.gz", hash = "sha256:ef589a79795589aada0c1c5b319486797c03b67ac3984c48c669c0e4f50df3a5"},
+]
+wcwidth = [
+ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"},
+ {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"},
+]
+webencodings = [
+ {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"},
+ {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"},
+]
+wrapt = [
+ {file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"},
+ {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"},
+ {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28"},
+ {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59"},
+ {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87"},
+ {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1"},
+ {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b"},
+ {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462"},
+ {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1"},
+ {file = "wrapt-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320"},
+ {file = "wrapt-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2"},
+ {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4"},
+ {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069"},
+ {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310"},
+ {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f"},
+ {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656"},
+ {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c"},
+ {file = "wrapt-1.14.1-cp310-cp310-win32.whl", hash = "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8"},
+ {file = "wrapt-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164"},
+ {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907"},
+ {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3"},
+ {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3"},
+ {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d"},
+ {file = "wrapt-1.14.1-cp35-cp35m-win32.whl", hash = "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7"},
+ {file = "wrapt-1.14.1-cp35-cp35m-win_amd64.whl", hash = "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00"},
+ {file = "wrapt-1.14.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4"},
+ {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1"},
+ {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1"},
+ {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff"},
+ {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d"},
+ {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1"},
+ {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569"},
+ {file = "wrapt-1.14.1-cp36-cp36m-win32.whl", hash = "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed"},
+ {file = "wrapt-1.14.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471"},
+ {file = "wrapt-1.14.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248"},
+ {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68"},
+ {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d"},
+ {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77"},
+ {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7"},
+ {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015"},
+ {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a"},
+ {file = "wrapt-1.14.1-cp37-cp37m-win32.whl", hash = "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853"},
+ {file = "wrapt-1.14.1-cp37-cp37m-win_amd64.whl", hash = "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c"},
+ {file = "wrapt-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456"},
+ {file = "wrapt-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f"},
+ {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc"},
+ {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1"},
+ {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"},
+ {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b"},
+ {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0"},
+ {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57"},
+ {file = "wrapt-1.14.1-cp38-cp38-win32.whl", hash = "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5"},
+ {file = "wrapt-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d"},
+ {file = "wrapt-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383"},
+ {file = "wrapt-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7"},
+ {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86"},
+ {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735"},
+ {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b"},
+ {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3"},
+ {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3"},
+ {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe"},
+ {file = "wrapt-1.14.1-cp39-cp39-win32.whl", hash = "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5"},
+ {file = "wrapt-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb"},
+ {file = "wrapt-1.14.1.tar.gz", hash = "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d"},
+]
+yarl = []
+zipstream-new = [
+ {file = "zipstream-new-1.1.8.tar.gz", hash = "sha256:b031fe181b94e51678389d26b174bc76382605a078d7d5d8f5beae083f111c76"},
+ {file = "zipstream_new-1.1.8-py3-none-any.whl", hash = "sha256:0662eb3ebe764fa168a5883cd8819ef83b94bd9e39955537188459d2264a7f60"},
+]
diff --git a/prepare_galaxy.yml b/prepare_galaxy.yml
deleted file mode 100644
index d54da18a..00000000
--- a/prepare_galaxy.yml
+++ /dev/null
@@ -1,50 +0,0 @@
-- hosts: all
- become: yes
- handlers:
- - name: restart galaxy
- supervisorctl:
- name: galaxy
- state: restarted
- tasks:
- - name: Copy pulsar_actions.yml
- copy:
- src: galaxy_files/pulsar_actions.yml
- dest: "{{ galaxy_config_dir }}/pulsar_actions.yml"
- owner: "{{ galaxy_user }}"
- group: "{{ galaxy_user }}"
- notify: restart galaxy
- - name: Copy job_conf.xml
- copy:
- src: galaxy_files/job_conf.xml.tmp
- dest: "{{ galaxy_config_dir }}/job_conf.xml"
- owner: "{{ galaxy_user }}"
- group: "{{ galaxy_user }}"
- notify: restart galaxy
- - name: Copy dynamic_destination.py
- copy:
- src: galaxy_files/dynamic_destination.py
- dest: "{{ galaxy_root_path }}/server/lib/galaxy/jobs/rules/dynamic_destination.py"
- owner: "{{ galaxy_user }}"
- group: "{{ galaxy_user }}"
- notify: restart galaxy
- - name: Copy JobStatus-MetricPlugin
- copy:
- src: galaxy_files/job_status.py
- dest: "{{ galaxy_root_path }}/server/lib/galaxy/jobs/metrics/instrumenters/job_status.py"
- owner: "{{ galaxy_user }}"
- group: "{{ galaxy_user }}"
- notify: restart galaxy
- - name: Copy StagingTime-MetricPlugin
- copy:
- src: galaxy_files/staging_time.py
- dest: "{{ galaxy_root_path }}/server/lib/galaxy/jobs/metrics/instrumenters/staging_time.py"
- owner: "{{ galaxy_user }}"
- group: "{{ galaxy_user }}"
- notify: restart galaxy
- - name: Copy job_metrics_conf.yml
- copy:
- src: galaxy_files/job_metrics_conf.xml
- dest: "{{ galaxy_config_dir }}/job_metrics_conf.xml"
- owner: "{{ galaxy_user }}"
- group: "{{ galaxy_user }}"
- notify: restart galaxy
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 00000000..f8e1e492
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,46 @@
+[tool.poetry]
+name = "galaxy_benchmarker"
+version = "0.1.0"
+description = ""
+authors = [
+ "Andreas Skorczyk ",
+ "Stefan Möhrle <26185187+smoehrle@users.noreply.github.com>"
+]
+
+[tool.poetry.dependencies]
+python = "^3.10"
+
+# Connectors/Bridges
+ansible = "^5"
+bioblend = { git = "https://github.com/galaxyproject/bioblend.git", branch = "main" }
+paramiko = "^2"
+planemo = "^0.74"
+python-openstackclient = "^5"
+
+# File operations
+## Templates
+Jinja2 = "^3"
+## Serialization/Deserialization
+pyserde = "^0.6"
+pyyaml = "^6"
+boto3 = "^1.24.38"
+
+[tool.poetry.dev-dependencies]
+isort = "^5"
+black = "*"
+types-requests = "^2"
+types-paramiko = "^2"
+
+[build-system]
+# Fix for editable installs
+requires = ["poetry-core@https://github.com/python-poetry/poetry-core/archive/325312c016d69189ac93c945ba0c1b69296c5e54.zip"]
+# requires = ["poetry-core>=1.0.0"]
+build-backend = "poetry.core.masonry.api"
+
+[tool.isort]
+multi_line_output = 3
+include_trailing_comma = true
+force_grid_wrap = 0
+use_parentheses = true
+ensure_newline_before_comments = true
+line_length = 88
diff --git a/requirements.txt b/requirements.txt
deleted file mode 100644
index 03a70e45..00000000
--- a/requirements.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-urllib3
-planemo
-pyyaml
-bioblend
-Jinja2
-influxdb
-paramiko
-ansible
-python-openstackclient
\ No newline at end of file
diff --git a/scripts/entrypoint.sh b/scripts/entrypoint.sh
new file mode 100755
index 00000000..e36fe21c
--- /dev/null
+++ b/scripts/entrypoint.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+
+echo "#############################"
+echo "######## New run ########"
+echo "#############################"
+
+if [ -d "/root/.ssh-host" ]; then
+ # Host ssh-config is mounted -> fix permissions
+ echo "Fixing permissions for ssh config"
+
+ mkdir -p ~/.ssh
+ cp -r /root/.ssh-host/* ~/.ssh/
+ chown -R $(id -u):$(id -g) ~/.ssh
+fi
+
+# Send SIGNALS to child processes
+trap 'echo entrypoint.sh: signal received, stoping child processes; kill -SIGINT $(jobs -p); wait;' SIGINT SIGTERM
+
+# Run benchmarker
+python3 -m galaxy_benchmarker "$@" &
+
+wait
\ No newline at end of file