diff options
| author | Fabian Mastenbroek <mail.fabianm@gmail.com> | 2021-10-25 14:53:54 +0200 |
|---|---|---|
| committer | Fabian Mastenbroek <mail.fabianm@gmail.com> | 2021-10-25 14:53:54 +0200 |
| commit | aa9b32f8cd1467e9718959f400f6777e5d71737d (patch) | |
| tree | b88bbede15108c6855d7f94ded4c7054df186a72 | |
| parent | eb0e0a3bc557c05a70eead388797ab850ea87366 (diff) | |
| parent | b7a71e5b4aa77b41ef41deec2ace42b67a5a13a7 (diff) | |
merge: Integrate v2.1 progress into public repository
This pull request integrates the changes planned for the v2.1 release of
OpenDC into the public Github repository in order to sync the progress
of both repositories.
1028 files changed, 52109 insertions, 36627 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cbf2f80d..9bd42254 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,6 +3,8 @@ name: Build on: pull_request: branches: [master] + push: + branches: [master] jobs: build-simulator: @@ -11,7 +13,10 @@ jobs: strategy: matrix: os: [ubuntu-latest] - java: [8, 15] + java: [11, 16] + include: + - os: windows-latest + java: 16 steps: - name: Checkout repository uses: actions/checkout@v2 @@ -23,16 +28,25 @@ jobs: java-version: ${{ matrix.java }} - name: Grant execute permission for gradlew run: chmod +x gradlew - - uses: actions/cache@v1 + - uses: actions/cache@v2 with: - path: ~/.gradle/caches - key: ${{ runner.os }}-${{ matrix.java }}-gradle-${{ hashFiles('**/*.gradle*') }} + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-${{ matrix.java }}-gradle-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }} restore-keys: | ${{ runner.os }}-${{ matrix.java }}-gradle- - name: Build with Gradle - run: ./gradlew assemble + run: ./gradlew classes - name: Check with Gradle run: ./gradlew check codeCoverageReport + - name: Cleanup Gradle Daemons + run: ./gradlew --stop + - name: Cleanup Gradle Cache + run: | + rm -f ~/.gradle/caches/modules-2/modules-2.lock + rm -f ~/.gradle/caches/modules-2/gc.properties + shell: bash - name: Publish report if: always() uses: mikepenz/action-junit-report@v2 @@ -61,7 +75,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - python: [3.8] + python: [3.9] defaults: run: working-directory: opendc-web/opendc-web-api @@ -78,19 +92,19 @@ jobs: - name: Lint with pylint run: ./check.sh - name: Test with pytest - run: pytest --cov=opendc/ --junitxml=.junit-report.xml + run: pytest --cov --cov-report=xml --junitxml=junit-report.xml - name: Publish report if: always() uses: mikepenz/action-junit-report@v2 with: check_name: test (Python ${{ matrix.python }}) - report_paths: '**/.junit-report.xml' + report_paths: '**/junit-report.xml' github_token: ${{ secrets.GITHUB_TOKEN }} - name: Upload code coverage uses: codecov/codecov-action@v1 with: token: ${{ secrets.CODECOV_TOKEN }} - files: opendc-web/opendc-web-api/.coverage + files: opendc-web/opendc-web-api/coverage.xml flags: api build-ui: name: Build UI (Node ${{ matrix.node }}) @@ -98,18 +112,20 @@ jobs: strategy: matrix: os: [ubuntu-latest] - node: [14.x] + node: [16] defaults: run: working-directory: opendc-web/opendc-web-ui steps: - uses: actions/checkout@v2 - name: Set up Node.js - uses: actions/setup-node@v1 + uses: actions/setup-node@v2 with: node-version: ${{ matrix.node }} - - run: npm install - - run: npm run build --if-present - - run: npm test + - run: yarn install --frozen-lockfile + - run: yarn build + env: + CI: true + - run: yarn next lint env: CI: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f76cdd09..c525eb97 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java: [ 15 ] + java: [ 16 ] steps: - name: Checkout repository uses: actions/checkout@v2 @@ -22,10 +22,12 @@ jobs: java-version: ${{ matrix.java }} - name: Grant execute permission for gradlew run: chmod +x gradlew - - uses: actions/cache@v1 + - uses: actions/cache@v2 with: - path: ~/.gradle/caches - key: ${{ runner.os }}-${{ matrix.java }}-gradle-${{ hashFiles('**/*.gradle*') }} + path:| + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-${{ matrix.java }}-gradle-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }} restore-keys: | ${{ runner.os }}-${{ matrix.java }}-gradle- - name: Build with Gradle @@ -46,3 +48,10 @@ jobs: files: build/distributions/* env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Cleanup Gradle Daemons + run: ./gradlew --stop + - name: Cleanup Gradle Cache + run: | + rm -f ~/.gradle/caches/modules-2/modules-2.lock + rm -f ~/.gradle/caches/modules-2/gc.properties + shell: bash @@ -18,7 +18,7 @@ keys.json hs_err_pid* # data -data/ +/data/ ### JetBrains # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm @@ -1,4 +1,4 @@ -FROM openjdk:15-slim +FROM openjdk:16-slim MAINTAINER OpenDC Maintainers <opendc@atlarge-research.com> # Obtain (cache) Gradle wrapper @@ -11,7 +11,7 @@ RUN ./gradlew --version COPY ./ /app/ RUN ./gradlew --no-daemon :installDist -FROM openjdk:15-slim +FROM openjdk:16-slim COPY --from=0 /app/build/install /opt/ COPY --from=0 /app/traces /opt/opendc/traces WORKDIR /opt/opendc diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 3b793a61..4eb8ac31 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -20,8 +20,6 @@ * SOFTWARE. */ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - plugins { `kotlin-dsl` } @@ -33,16 +31,10 @@ repositories { } dependencies { - implementation(kotlin("gradle-plugin", version = "1.4.31")) - implementation("org.jlleitschuh.gradle:ktlint-gradle:10.0.0") - implementation("org.jetbrains.kotlin:kotlin-allopen:1.4.31") - implementation("org.jetbrains.kotlinx:kotlinx-benchmark-plugin:0.3.0") - implementation("org.jetbrains.dokka:dokka-gradle-plugin:1.4.32") + implementation(kotlin("gradle-plugin", version = "1.5.30")) + implementation("org.jlleitschuh.gradle:ktlint-gradle:10.1.0") + implementation("org.jetbrains.kotlin:kotlin-allopen:1.5.30") + implementation("me.champeau.jmh:jmh-gradle-plugin:0.6.6") + implementation("org.jetbrains.dokka:dokka-gradle-plugin:1.5.0") implementation("gradle.plugin.com.github.jengelman.gradle.plugins:shadow:7.0.0") } - -tasks.withType<KotlinCompile>().configureEach { - kotlinOptions { - allWarningsAsErrors = true - } -} diff --git a/buildSrc/src/main/kotlin/Libs.kt b/buildSrc/src/main/kotlin/Libs.kt index 9567845a..f538b1ad 100644 --- a/buildSrc/src/main/kotlin/Libs.kt +++ b/buildSrc/src/main/kotlin/Libs.kt @@ -58,6 +58,6 @@ public class Libs(project: Project) { /** * The JVM version to target. */ - val jvmTarget = JavaVersion.VERSION_1_8 + val jvmTarget = JavaVersion.VERSION_11 } } diff --git a/buildSrc/src/main/kotlin/benchmark-conventions.gradle.kts b/buildSrc/src/main/kotlin/benchmark-conventions.gradle.kts index 4e73d4d3..65608e8f 100644 --- a/buildSrc/src/main/kotlin/benchmark-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/benchmark-conventions.gradle.kts @@ -20,49 +20,21 @@ * SOFTWARE. */ -import kotlinx.benchmark.gradle.* import org.jetbrains.kotlin.allopen.gradle.* plugins { - id("org.jetbrains.kotlinx.benchmark") `java-library` kotlin("plugin.allopen") -} - -sourceSets { - register("jmh") { - compileClasspath += sourceSets["main"].output - runtimeClasspath += sourceSets["main"].output - } -} - -configurations { - named("jmhImplementation") { - extendsFrom(configurations["implementation"]) - } + id("me.champeau.jmh") } configure<AllOpenExtension> { annotation("org.openjdk.jmh.annotations.State") } -benchmark { - targets { - register("jmh") { - this as JvmBenchmarkTarget - jmhVersion = "1.21" - } - } -} - -dependencies { - val libs = Libs(project) - implementation(libs["kotlinx-benchmark-runtime-jvm"]) -} +jmh { + jmhVersion.set("1.33") -// Workaround for https://github.com/Kotlin/kotlinx-benchmark/issues/39 -afterEvaluate { - tasks.named<org.gradle.jvm.tasks.Jar>("jmhBenchmarkJar") { - duplicatesStrategy = DuplicatesStrategy.EXCLUDE - } + profilers.add("stack") + profilers.add("gc") } diff --git a/buildSrc/src/main/kotlin/experiment-conventions.gradle.kts b/buildSrc/src/main/kotlin/experiment-conventions.gradle.kts index 26780205..c1750a40 100644 --- a/buildSrc/src/main/kotlin/experiment-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/experiment-conventions.gradle.kts @@ -1,5 +1,3 @@ -import gradle.kotlin.dsl.accessors._9bf86420fccbde1948375f641de89b70.sourceSets - /* * Copyright (c) 2021 AtLarge Research * diff --git a/buildSrc/src/main/kotlin/jacoco-aggregation.gradle.kts b/buildSrc/src/main/kotlin/jacoco-aggregation.gradle.kts index 3e8aa741..5afd3e0d 100644 --- a/buildSrc/src/main/kotlin/jacoco-aggregation.gradle.kts +++ b/buildSrc/src/main/kotlin/jacoco-aggregation.gradle.kts @@ -1,7 +1,5 @@ /* - * MIT License - * - * Copyright (c) 2019 atlarge-research + * Copyright (c) 2021 AtLarge Research * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -37,10 +35,10 @@ tasks.register<JacocoReport>("codeCoverageReport") { description = "Generates an aggregate report based on all subprojects" reports { - xml.isEnabled = true - xml.destination = file("${buildDir}/reports/jacoco/report.xml") + xml.required.set(true) + xml.outputLocation.set(file("${buildDir}/reports/jacoco/report.xml")) - html.isEnabled = true + html.required.set(true) } subprojects { diff --git a/buildSrc/src/main/kotlin/jacoco-conventions.gradle.kts b/buildSrc/src/main/kotlin/jacoco-conventions.gradle.kts index d0534d4f..6fb9ccc3 100644 --- a/buildSrc/src/main/kotlin/jacoco-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/jacoco-conventions.gradle.kts @@ -27,39 +27,6 @@ plugins { tasks.jacocoTestReport { reports { - html.isEnabled = true + html.required.set(true) } } - -/* Share sources folder with other projects for aggregated JaCoCo reports */ -configurations.create("transitiveSourcesElements") { - isVisible = false - isCanBeResolved = false - isCanBeConsumed = true - extendsFrom(configurations.implementation.get()) - attributes { - attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.JAVA_RUNTIME)) - attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.DOCUMENTATION)) - attribute(DocsType.DOCS_TYPE_ATTRIBUTE, objects.named("source-folders")) - } - sourceSets.main.get().java.srcDirs.forEach { - outgoing.artifact(it) - } -} - -/* Share the coverage data to be aggregated for the whole product */ -configurations.create("coverageDataElements") { - isVisible = false - isCanBeResolved = false - isCanBeConsumed = true - extendsFrom(configurations.implementation.get()) - attributes { - attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.JAVA_RUNTIME)) - attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.DOCUMENTATION)) - attribute(DocsType.DOCS_TYPE_ATTRIBUTE, objects.named("jacoco-coverage-data")) - } - // This will cause the test task to run if the coverage data is requested by the aggregation task - outgoing.artifact(tasks.test.map { task -> - task.extensions.getByType<JacocoTaskExtension>().destinationFile!! - }) -} diff --git a/buildSrc/src/main/kotlin/kotlin-conventions.gradle.kts b/buildSrc/src/main/kotlin/kotlin-conventions.gradle.kts index 703e9938..6e4cab89 100644 --- a/buildSrc/src/main/kotlin/kotlin-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/kotlin-conventions.gradle.kts @@ -39,6 +39,6 @@ java { tasks.withType<KotlinCompile>().configureEach { kotlinOptions.jvmTarget = Libs.jvmTarget.toString() - kotlinOptions.useIR = true kotlinOptions.freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn" + kotlinOptions.freeCompilerArgs += "-Xjvm-default=all" } diff --git a/buildSrc/src/main/kotlin/testing-conventions.gradle.kts b/buildSrc/src/main/kotlin/testing-conventions.gradle.kts index 7d344500..ebeb58a4 100644 --- a/buildSrc/src/main/kotlin/testing-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/testing-conventions.gradle.kts @@ -28,16 +28,16 @@ tasks.test { useJUnitPlatform() reports { - html.isEnabled = true - junitXml.isEnabled = true + html.required.set(true) + junitXml.required.set(true) } } dependencies { val libs = Libs(project) - testImplementation(libs["junit-jupiter-api"]) - testImplementation(libs["junit-jupiter-params"]) + testImplementation(libs["junit.jupiter.api"]) + testImplementation(libs["junit.jupiter.params"]) testImplementation(libs["mockk"]) - testRuntimeOnly(libs["junit-jupiter-engine"]) + testRuntimeOnly(libs["junit.jupiter.engine"]) } diff --git a/database/mongo-init-opendc-db.sh b/database/mongo-init-opendc-db.sh index bd07f5ad..d55b8990 100644 --- a/database/mongo-init-opendc-db.sh +++ b/database/mongo-init-opendc-db.sh @@ -13,7 +13,7 @@ MONGO_CMD="mongo $OPENDC_DB -u $OPENDC_DB_USERNAME -p $OPENDC_DB_PASSWORD --auth echo 'Creating collections' -$MONGO_CMD --eval 'db.createCollection("users");' +$MONGO_CMD --eval 'db.createCollection("authorizations");' $MONGO_CMD --eval 'db.createCollection("projects");' $MONGO_CMD --eval 'db.createCollection("topologies");' $MONGO_CMD --eval 'db.createCollection("portfolios");' diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 2f4d8e7b..6202e299 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -3,18 +3,21 @@ version: "3.8" # Docker Compose overrides for development environments services: frontend: + build: opendc-web/opendc-web-ui ports: - - "8080:80" + - "8080:3000" environment: - REACT_APP_API_BASE_URL: http://localhost:8081 + NEXT_PUBLIC_API_BASE_URL: http://localhost:8081 api: + build: opendc-web/opendc-web-api ports: - - "8081:8081" + - "8081:80" environment: SENTRY_ENVIRONMENT: "development" simulator: + build: . environment: SENTRY_ENVIRONMENT: "development" diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 3a8c1cba..c4f43298 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -4,13 +4,13 @@ version: "3.8" services: frontend: ports: - - "8080:80" + - "8080:3000" environment: - REACT_APP_API_BASE_URL: ${OPENDC_API_BASE_URL} + NEXT_PUBLIC_API_BASE_URL: ${OPENDC_API_BASE_URL} api: ports: - - "8081:8081" + - "8081:80" environment: SENTRY_ENVIRONMENT: "production" diff --git a/docker-compose.yml b/docker-compose.yml index ac2e6041..18847736 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,19 +1,18 @@ version: "3.8" services: frontend: - build: - context: opendc-web/opendc-web-ui - image: frontend + image: atlargeresearch/opendc-web-ui:v2.1 restart: on-failure networks: - backend environment: - REACT_APP_OAUTH_CLIENT_ID: ${OPENDC_OAUTH_CLIENT_ID} - REACT_APP_SENTRY_DSN: ${OPENDC_FRONTEND_SENTRY_DSN-} + NEXT_PUBLIC_AUTH0_DOMAIN: ${OPENDC_AUTH0_DOMAIN} + NEXT_PUBLIC_AUTH0_CLIENT_ID: ${OPENDC_AUTH0_CLIENT_ID} + NEXT_PUBLIC_AUTH0_AUDIENCE: ${OPENDC_AUTH0_AUDIENCE} + NEXT_PUBLIC_SENTRY_DSN: ${OPENDC_FRONTEND_SENTRY_DSN-} api: - build: opendc-web/opendc-web-api - image: api + image: atlargeresearch/opendc-web-api:v2.1 restart: on-failure networks: - backend @@ -33,8 +32,7 @@ services: - SENTRY_ENVIRONMENT simulator: - build: . - image: simulator + image: atlargeresearch/opendc:v2.1 restart: on-failure networks: - backend @@ -45,16 +43,16 @@ services: source: ./traces target: /opt/opendc/traces environment: - - OPENDC_DB - - OPENDC_DB_USERNAME - - OPENDC_DB_PASSWORD - - OPENDC_DB_HOST=mongo + - OPENDC_API_URL=${OPENDC_API_BASE_URL} + - AUTH0_DOMAIN=${OPENDC_AUTH0_DOMAIN} + - AUTH0_AUDIENCE=${OPENDC_AUTH0_AUDIENCE} + - AUTH0_CLIENT_ID=${OPENDC_AUTH0_CLIENT_ID_RUNNER} + - AUTH0_CLIENT_SECRET=${OPENDC_AUTH0_CLIENT_SECRET_RUNNER} - SENTRY_DSN=${OPENDC_SIMULATOR_SENTRY_DSN-} - SENTRY_ENVIRONMENT mongo: - build: - context: database + build: database restart: on-failure environment: - MONGO_INITDB_ROOT_USERNAME diff --git a/docs/deploy.md b/docs/deploy.md index 48149595..f68705cf 100644 --- a/docs/deploy.md +++ b/docs/deploy.md @@ -5,18 +5,32 @@ running to deploy on a server. ## Contents -1. [Preamble](#preamble) +1. [Setting up Auth0](#setting-up-auth0) 1. [Installing Docker](#installing-docker) 1. [Running OpenDC from source](#running-opendc-from-source) -## Preamble +## Setting up Auth0 + +OpenDC uses [Auth0](https://auth0.com) as Identity Provider so that OpenDC does not have to manage user data itself, +which greatly simplifies our frontend and backend implementation. We have chosen to use Auth0 as it is a well-known +Identity Provider with good software support and a free tier for users to experiment with. + +To deploy OpenDC yourself, you need to have an [Auth0 tenant](https://auth0.com/docs/get-started/learn-the-basics) and +create: + +1. **A Single Page Application (SPA)** + You need to define the OpenDC frontend application in Auth0. Please see the [following guide](https://auth0.com/docs/quickstart/spa/react#configure-auth0) + on how you can define an SPA in Auth0. Make sure you have added the necessary URLs to the _Allowed Callback URLs_: + for a local deployment, you should add at least `http://localhost:3000, http://localhost:8080`. + + Once your application has been created, you should have a _Domain_ and _Client ID_ which we need to pass to the + frontend application (as `OPENDC_AUTH0_DOMAIN` and `OPENDC_AUTH0_CLIENT_ID` respectively). +2. **An API** + You need to define the OpenDC API server in Auth0. Please refer to the [following guide](https://auth0.com/docs/quickstart/backend/python/01-authorization#create-an-api) + on how to define an API in Auth0. + + Remember the identifier you created the API with, as we need it in the next steps (as `OPENDC_AUTH0_AUDIENCE`). -To run OpenDC, you have to create a Google API Console project and client ID, which the OpenDC frontend and -web server will use to authenticate users and requests. -Follow [these steps](https://developers.google.com/identity/sign-in/web/sign-in) to make such a project. In the ' -Authorized JavaScript origins' and 'Authorized redirect URI' fields, be sure to add `http://localhost:8080` (frontend) -, `http://localhost:8081` (api) and `https://localhost:3000` (frontend dev). Download the JSON of the OAuth 2.0 client -ID you created from the Credentials tab, and specifically note the `client_id`, which you'll need to build OpenDC. ## Installing Docker @@ -36,8 +50,8 @@ cd opendc/ ``` In the directory you just entered, you need to set up a set of environment variables. To do this, create a file -called `.env` in the `opendc` folder. In this file, replace `your-google-oauth-client-id` with your `client_id` from the -OAuth client ID you created. For a standard setup, you can leave the other settings as-is. +called `.env` in the `opendc` folder. In this file, replace `your-auth0-*` with the Auth0 details you got from the first +step. For a standard setup, you can leave the other settings as-is. ```.env MONGO_INITDB_ROOT_USERNAME=root @@ -47,7 +61,9 @@ OPENDC_DB=opendc OPENDC_DB_USERNAME=opendc OPENDC_DB_PASSWORD=opendcpassword OPENDC_FLASK_SECRET="This is a secret flask key, please change" -OPENDC_OAUTH_CLIENT_ID=your-google-oauth-client-id +OPENDC_AUTH0_DOMAIN=your-auth0-domain +OPENDC_AUTH0_CLIENT_ID=your-auth0-client-id +OPENDC_AUTH0_AUDIENCE=your-auth0-api-identifier OPENDC_API_BASE_URL=http://localhost:8081 ``` diff --git a/docs/toolchain.md b/docs/toolchain.md index 4b029ebc..016c8201 100644 --- a/docs/toolchain.md +++ b/docs/toolchain.md @@ -12,13 +12,13 @@ Follow the steps below to get it all set up! ## Contents 1. [Installing Java](#1-installing-java) -1. [Building and Developing](#2-building-and-developing) - 1. [Setup with IntelliJ IDEA](#21-setup-with-intellij-idea) - 1. [Setup with Command Line](#22-setup-with-command-line) +2. [Building and Developing](#2-building-and-developing) +3. [Setup with IntelliJ IDEA](#21-setup-with-intellij-idea) +4. [Setup with Command Line](#22-setup-with-command-line) ## 1. Installing Java -Kotlin requires a Java installation of version 8 or higher. Make sure to install +OpenDC requires a Java installation of version 11 or higher. Make sure to install the [JDK](https://www.oracle.com/technetwork/java/javase/downloads/index.html), not only the JRE (the JDK also includes a JRE). diff --git a/gradle.properties b/gradle.properties index 52648593..06c56148 100644 --- a/gradle.properties +++ b/gradle.properties @@ -22,3 +22,6 @@ # For Dokka https://github.com/Kotlin/dokka/issues/1405 org.gradle.jvmargs=-XX:MaxMetaspaceSize=2G + +org.gradle.parallel=true +org.gradle.caching=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 12cbfb4e..03402b9a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,25 +1,41 @@ [versions] -junit-jupiter = "5.7.1" -junit-platform = "1.7.1" -slf4j = "1.7.30" +classgraph = "4.8.115" +clikt = "3.2.0" +config = "1.4.1" +commons-math3 = "3.6.1" +hadoop = "3.3.1" +jackson = "2.12.5" +junit-jupiter = "5.7.2" +junit-platform = "1.7.2" +kotlin-logging = "2.0.11" +kotlinx-coroutines = "1.5.1" +ktor = "1.6.3" log4j = "2.14.1" -opentelemetry-main = "1.1.0" -opentelemetry-metrics = "1.1.0-alpha" +mockk = "1.12.0" +opentelemetry-main = "1.6.0" +opentelemetry-metrics = "1.6.0-alpha" +opentelemetry-semconv = "1.6.0-alpha" +parquet = "1.12.0" +progressbar = "0.9.0" +sentry = "5.1.2" +slf4j = "1.7.32" [libraries] -kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version = "1.4.3" } +# Kotlin +kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" } # Logging -kotlin-logging = { module = "io.github.microutils:kotlin-logging", version = "2.0.6" } +kotlin-logging = { module = "io.github.microutils:kotlin-logging", version.ref = "kotlin-logging" } slf4j-simple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j" } log4j-slf4j = { module = "org.apache.logging.log4j:log4j-slf4j-impl", version.ref = "log4j" } -sentry-log4j2 = { module = "io.sentry:sentry-log4j2", version = "4.3.0" } +sentry-log4j2 = { module = "io.sentry:sentry-log4j2", version.ref = "sentry" } # Telemetry opentelemetry-api-main = { module = "io.opentelemetry:opentelemetry-api", version.ref = "opentelemetry-main" } opentelemetry-sdk-main = { module = "io.opentelemetry:opentelemetry-sdk", version.ref = "opentelemetry-main" } opentelemetry-api-metrics = { module = "io.opentelemetry:opentelemetry-api-metrics", version.ref = "opentelemetry-metrics" } opentelemetry-sdk-metrics = { module = "io.opentelemetry:opentelemetry-sdk-metrics", version.ref = "opentelemetry-metrics" } +opentelemetry-semconv = { module = "io.opentelemetry:opentelemetry-semconv", version.ref = "opentelemetry-semconv" } # Testing junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit-jupiter" } @@ -27,22 +43,29 @@ junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", vers junit-jupiter-params = { module = "org.junit.jupiter:junit-jupiter-params", version.ref = "junit-jupiter" } junit-platform-commons = { module = "org.junit.platform:junit-platform-commons", version.ref = "junit-platform" } junit-platform-engine = { module = "org.junit.platform:junit-platform-engine", version.ref = "junit-platform" } -mockk = { module = "io.mockk:mockk", version = "1.11.0" } +mockk = { module = "io.mockk:mockk", version.ref = "mockk" } # CLI -clikt = { module = "com.github.ajalt.clikt:clikt", version = "3.1.0" } -progressbar = { module = "me.tongfei:progressbar", version = "0.9.0" } +clikt = { module = "com.github.ajalt.clikt:clikt", version.ref = "clikt" } +progressbar = { module = "me.tongfei:progressbar", version.ref = "progressbar" } # Format -jackson-module-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version = "2.12.0" } -parquet = { module = "org.apache.parquet:parquet-avro", version = "1.12.0" } -yaml = { module = "org.yaml:snakeyaml", version = "1.28" } -config = { module = "com.typesafe:config", version = "1.4.1" } +jackson-core = { module = "com.fasterxml.jackson.core:jackson-core", version.ref = "jackson" } +jackson-databind = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref = "jackson" } +jackson-module-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jackson" } +jackson-datatype-jsr310 = { module = "com.fasterxml.jackson.datatype:jackson-datatype-jsr310", version.ref = "jackson" } +jackson-dataformat-csv = { module = "com.fasterxml.jackson.dataformat:jackson-dataformat-csv", version.ref = "jackson" } +parquet = { module = "org.apache.parquet:parquet-avro", version.ref = "parquet" } +config = { module = "com.typesafe:config", version.ref = "config" } -# Benchmark -kotlinx-benchmark-runtime-jvm = { module = "org.jetbrains.kotlinx:kotlinx-benchmark-runtime-jvm", version = "0.3.0" } +# HTTP client +ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" } +ktor-client-auth = { module = "io.ktor:ktor-client-auth", version.ref = "ktor" } +ktor-client-jackson = { module = "io.ktor:ktor-client-jackson", version.ref = "ktor" } +ktor-client-mock = { module = "io.ktor:ktor-client-mock", version.ref = "ktor" } # Other -mongodb = { module = "org.mongodb:mongodb-driver-sync", version = "4.2.3" } -classgraph = { module = "io.github.classgraph:classgraph", version = "4.8.105" } -hadoop-client = { module = "org.apache.hadoop:hadoop-client", version = "3.2.1" } +classgraph = { module = "io.github.classgraph:classgraph", version.ref = "classgraph" } +hadoop-common = { module = "org.apache.hadoop:hadoop-common", version.ref = "hadoop" } +hadoop-mapreduce-client-core = { module = "org.apache.hadoop:hadoop-mapreduce-client-core", version.ref = "hadoop" } +commons-math3 = { module = "org.apache.commons:commons-math3", version.ref = "commons-math3" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar Binary files differindex e708b1c0..7454180f 100644 --- a/gradle/wrapper/gradle-wrapper.jar +++ b/gradle/wrapper/gradle-wrapper.jar diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f371643e..ffed3a25 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists @@ -72,7 +72,7 @@ case "`uname`" in Darwin* ) darwin=true ;; - MINGW* ) + MSYS* | MINGW* ) msys=true ;; NONSTOP* ) diff --git a/opendc-api-spec.yml b/opendc-api-spec.yml index f195983b..ca5a3b0c 100644..120000 --- a/opendc-api-spec.yml +++ b/opendc-api-spec.yml @@ -1,937 +1 @@ -swagger: '2.0' -info: - version: 2.0.0 - title: OpenDC API - description: 'OpenDC is an open-source datacenter simulator for education, featuring real-time online collaboration, diverse simulation models, and detailed performance feedback statistics.' -host: opendc.org -basePath: /v2 -schemes: - - https - -paths: - '/users': - get: - tags: - - users - description: Search for a User using their email address. - parameters: - - name: email - in: query - description: User's email address. - required: true - type: string - responses: - '200': - description: Successfully searched Users. - schema: - $ref: '#/definitions/User' - '400': - description: Missing or incorrectly typed parameter. - '401': - description: Unauthorized. - '404': - description: User not found. - post: - tags: - - users - description: Add a new User. - parameters: - - name: user - in: body - description: The new User. - required: true - schema: - $ref: '#/definitions/User' - responses: - '200': - description: Successfully added User. - schema: - $ref: '#/definitions/User' - '400': - description: Missing or incorrectly typed parameter. - '401': - description: Unauthorized. - '409': - description: User already exists. - '/users/{userId}': - get: - tags: - - users - description: Get this User. - parameters: - - name: userId - in: path - description: User's ID. - required: true - type: string - responses: - '200': - description: Successfully retrieved User. - schema: - $ref: '#/definitions/User' - '400': - description: Missing or incorrectly typed parameter. - '401': - description: Unauthorized. - '404': - description: User not found. - put: - tags: - - users - description: Update this User's given name and/ or family name. - parameters: - - name: userId - in: path - description: User's ID. - required: true - type: string - - name: user - in: body - description: User's new properties. - required: true - schema: - properties: - givenName: - type: string - familyName: - type: string - responses: - '200': - description: Successfully updated User. - schema: - $ref: '#/definitions/User' - '400': - description: Missing or incorrectly typed parameter. - '401': - description: Unauthorized. - '403': - description: Forbidden from updating User. - '404': - description: User not found. - delete: - tags: - - users - description: Delete this User. - parameters: - - name: userId - in: path - description: User's ID. - required: true - type: string - responses: - '200': - description: Successfully deleted User. - schema: - $ref: '#/definitions/User' - '400': - description: Missing or incorrectly typed parameter. - '401': - description: Unauthorized. - '403': - description: Forbidden from deleting User. - '404': - description: User not found. - '/projects': - post: - tags: - - projects - description: Add a Project. - parameters: - - name: project - in: body - description: The new Project. - required: true - schema: - properties: - name: - type: string - responses: - '200': - description: Successfully added Project. - schema: - $ref: '#/definitions/Project' - '400': - description: Missing or incorrectly typed parameter. - '401': - description: Unauthorized. - '/projects/{projectId}': - get: - tags: - - projects - description: Get this Project. - parameters: - - name: projectId - in: path - description: Project's ID. - required: true - type: string - responses: - '200': - description: Successfully retrieved Project. - schema: - $ref: '#/definitions/Project' - '400': - description: Missing or incorrectly typed parameter. - '401': - description: Unauthorized. - '403': - description: Forbidden from retrieving Project. - '404': - description: Project not found - put: - tags: - - projects - description: Update this Project. - parameters: - - name: projectId - in: path - description: Project's ID. - required: true - type: string - - name: project - in: body - description: Project's new properties. - required: true - schema: - properties: - project: - $ref: '#/definitions/Project' - responses: - '200': - description: Successfully updated Project. - schema: - $ref: '#/definitions/Project' - '400': - description: Missing or incorrectly typed parameter. - '401': - description: Unauthorized. - '403': - description: Forbidden from updating Project. - '404': - description: Project not found. - delete: - tags: - - projects - description: Delete this project. - parameters: - - name: projectId - in: path - description: Project's ID. - required: true - type: string - responses: - '200': - description: Successfully deleted Project. - schema: - $ref: '#/definitions/Project' - '400': - description: Missing or incorrectly typed parameter. - '401': - description: Unauthorized. - '403': - description: Forbidden from deleting Project. - '404': - description: Project not found. - '/projects/{projectId}/authorizations': - get: - tags: - - projects - description: Get this Project's Authorizations. - parameters: - - name: projectId - in: path - description: Project's ID. - required: true - type: string - responses: - '200': - description: Successfully retrieved Project's Authorizations. - schema: - type: array - items: - type: object - properties: - userId: - type: string - projectId: - type: string - authorizationLevel: - type: string - '400': - description: Missing or incorrectly typed parameter. - '401': - description: Unauthorized. - '403': - description: Forbidden from retrieving this Project's Authorizations. - '404': - description: Project not found. - '/projects/{projectId}/topologies': - post: - tags: - - projects - description: Add a Topology. - parameters: - - name: projectId - in: path - description: Project's ID. - required: true - type: string - - name: topology - in: body - description: The new Topology. - required: true - schema: - properties: - topology: - $ref: '#/definitions/Topology' - responses: - '200': - description: Successfully added Topology. - schema: - $ref: '#/definitions/Topology' - '400': - description: Missing or incorrectly typed parameter. - '401': - description: Unauthorized. - '/projects/{projectId}/portfolios': - post: - tags: - - portfolios - description: Add a Portfolio. - parameters: - - name: projectId - in: path - description: Project's ID. - required: true - type: string - - name: portfolio - in: body - description: The new Portfolio. - required: true - schema: - properties: - topology: - $ref: '#/definitions/Portfolio' - responses: - '200': - description: Successfully added Portfolio. - schema: - $ref: '#/definitions/Portfolio' - '400': - description: Missing or incorrectly typed parameter. - '401': - description: Unauthorized. - '/topologies/{topologyId}': - get: - tags: - - topologies - description: Get this Topology. - parameters: - - name: topologyId - in: path - description: Topology's ID. - required: true - type: string - responses: - '200': - description: Successfully retrieved Topology. - schema: - $ref: '#/definitions/Topology' - '400': - description: Missing or incorrectly typed parameter. - '401': - description: Unauthorized. - '403': - description: Forbidden from retrieving Topology. - '404': - description: Topology not found. - put: - tags: - - topologies - description: Update this Topology's name. - parameters: - - name: topologyId - in: path - description: Topology's ID. - required: true - type: string - - name: topology - in: body - description: Topology's new properties. - required: true - schema: - properties: - topology: - $ref: '#/definitions/Topology' - responses: - '200': - description: Successfully updated Topology. - schema: - $ref: '#/definitions/Topology' - '400': - description: Missing or incorrectly typed parameter. - '401': - description: Unauthorized. - '403': - description: Forbidden from updating Topology. - '404': - description: Topology not found. - delete: - tags: - - topologies - description: Delete this Topology. - parameters: - - name: topologyId - in: path - description: Topology's ID. - required: true - type: string - responses: - '200': - description: Successfully deleted Topology. - schema: - $ref: '#/definitions/Topology' - '400': - description: Missing or incorrectly typed parameter. - '401': - description: Unauthorized. - '403': - description: Forbidden from deleting Topology. - '404': - description: Topology not found. - '/portfolios/{portfolioId}': - get: - tags: - - portfolios - description: Get this Portfolio. - parameters: - - name: portfolioId - in: path - description: Portfolio's ID. - required: true - type: string - responses: - '200': - description: Successfully retrieved Portfolio. - schema: - $ref: '#/definitions/Portfolio' - '400': - description: Missing or incorrectly typed parameter. - '401': - description: Unauthorized. - '403': - description: Forbidden from retrieving Portfolio. - '404': - description: Portfolio not found. - put: - tags: - - portfolios - description: "Update this Portfolio." - parameters: - - name: portfolioId - in: path - description: Portfolio's ID. - required: true - type: string - - name: portfolio - in: body - description: Portfolio's new properties. - required: true - schema: - $ref: '#/definitions/Portfolio' - responses: - '200': - description: Successfully updated Portfolio. - schema: - $ref: '#/definitions/Portfolio' - '400': - description: Missing or incorrectly typed parameter. - '401': - description: Unauthorized. - '403': - description: Forbidden from updating Portfolio. - '404': - description: 'Portfolio not found.' - delete: - tags: - - portfolios - description: Delete this Portfolio. - parameters: - - name: portfolioId - in: path - description: Portfolio's ID. - required: true - type: string - responses: - '200': - description: Successfully deleted Portfolio. - schema: - $ref: '#/definitions/Portfolio' - '401': - description: Unauthorized. - '403': - description: Forbidden from deleting Portfolio. - '404': - description: Portfolio not found. - '/scenarios/{scenarioId}': - get: - tags: - - scenarios - description: Get this Scenario. - parameters: - - name: scenarioId - in: path - description: Scenario's ID. - required: true - type: string - responses: - '200': - description: Successfully retrieved Scenario. - schema: - $ref: '#/definitions/Scenario' - '400': - description: Missing or incorrectly typed parameter. - '401': - description: Unauthorized. - '403': - description: Forbidden from retrieving Scenario. - '404': - description: Scenario not found. - put: - tags: - - scenarios - description: "Update this Scenario's name (other properties are read-only)." - parameters: - - name: scenarioId - in: path - description: Scenario's ID. - required: true - type: string - - name: scenario - in: body - description: Scenario with new name. - required: true - schema: - $ref: '#/definitions/Scenario' - responses: - '200': - description: Successfully updated Scenario. - schema: - $ref: '#/definitions/Scenario' - '400': - description: Missing or incorrectly typed parameter. - '401': - description: Unauthorized. - '403': - description: Forbidden from updating Scenario. - '404': - description: 'Scenario not found.' - delete: - tags: - - scenarios - description: Delete this Scenario. - parameters: - - name: scenarioId - in: path - description: Scenario's ID. - required: true - type: string - responses: - '200': - description: Successfully deleted Scenario. - schema: - $ref: '#/definitions/Scenario' - '401': - description: Unauthorized. - '403': - description: Forbidden from deleting Scenario. - '404': - description: Scenario not found. - /schedulers: - get: - tags: - - simulation - description: Get all available Schedulers - responses: - '200': - description: Successfully retrieved Schedulers. - schema: - type: array - items: - $ref: '#/definitions/Scheduler' - '401': - description: Unauthorized. - /traces: - get: - tags: - - simulation - description: Get all available Traces (non-populated). - responses: - '200': - description: Successfully retrieved Traces (non-populated). - schema: - type: array - items: - type: object - properties: - _id: - type: string - name: - type: string - '401': - description: Unauthorized. - '/traces/{traceId}': - get: - tags: - - simulation - description: Get this Trace. - parameters: - - name: traceId - in: path - description: Trace's ID. - required: true - type: string - responses: - '200': - description: Successfully retrieved Trace. - schema: - $ref: '#/definitions/Trace' - '401': - description: Unauthorized. - '404': - description: Trace not found. - /prefabs: - post: - tags: - - prefabs - description: Add a Prefab. - parameters: - - name: prefab - in: body - description: The new Prefab. - required: true - schema: - properties: - name: - type: string - responses: - '200': - description: Successfully added Prefab. - schema: - $ref: '#/definitions/Prefab' - '400': - description: Missing or incorrectly typed parameter. - '401': - description: Unauthorized. - '/prefabs/{prefabId}': - get: - tags: - - prefabs - description: Get this Prefab. - parameters: - - name: prefabId - in: path - description: Prefab's ID. - required: true - type: string - responses: - '200': - description: Successfully retrieved Prefab. - schema: - $ref: '#/definitions/Prefab' - '400': - description: Missing or incorrectly typed parameter. - '401': - description: Unauthorized. - '403': - description: Forbidden from retrieving Prefab. - '404': - description: Prefab not found - put: - tags: - - prefabs - description: Update this Prefab. - parameters: - - name: prefabId - in: path - description: Prefab's ID. - required: true - type: string - - name: prefab - in: body - description: Prefab's new properties. - required: true - schema: - properties: - project: - $ref: '#/definitions/Prefab' - responses: - '200': - description: Successfully updated Prefab. - schema: - $ref: '#/definitions/Prefab' - '400': - description: Missing or incorrectly typed parameter. - '401': - description: Unauthorized. - '403': - description: Forbidden from updating Prefab. - '404': - description: Prefab not found. - delete: - tags: - - prefabs - description: Delete this prefab. - parameters: - - name: prefabId - in: path - description: Prefab's ID. - required: true - type: string - responses: - '200': - description: Successfully deleted Prefab. - schema: - $ref: '#/definitions/Prefab' - '400': - description: Missing or incorrectly typed parameter. - '401': - description: Unauthorized. - '403': - description: Forbidden from deleting Prefab. - '404': - description: Prefab not found. - '/prefabs/authorizations': - get: - tags: - - prefabs - description: Get all Prefabs the user has rights to view. - responses: - '200': - description: Successfully retrieved prefabs the user is authorized on. - schema: - $ref: '#/definitions/Prefab' - '400': - description: Missing or incorrectly typed parameter. - '404': - description: Prefab or userId not found - -definitions: - Scheduler: - type: object - properties: - name: - type: string - Project: - type: object - properties: - _id: - type: string - name: - type: string - datetimeCreated: - type: string - format: dateTime - datetimeLastEdited: - type: string - format: dateTime - topologyIds: - type: array - items: - type: string - portfolioIds: - type: array - items: - type: string - Topology: - type: object - properties: - _id: - type: string - projectId: - type: string - name: - type: string - rooms: - type: array - items: - type: object - properties: - _id: - type: string - name: - type: string - tiles: - type: array - items: - type: object - properties: - _id: - type: string - positionX: - type: integer - positionY: - type: integer - object: - type: object - properties: - capacity: - type: integer - powerCapacityW: - type: integer - machines: - type: array - items: - type: object - properties: - position: - type: integer - cpuItems: - type: array - items: - type: object - properties: - name: - type: string - clockRateMhz: - type: integer - numberOfCores: - type: integer - gpuItems: - type: array - items: - type: object - properties: - name: - type: string - clockRateMhz: - type: integer - numberOfCores: - type: integer - memoryItems: - type: array - items: - type: object - properties: - name: - type: string - speedMbPerS: - type: integer - sizeMb: - type: integer - storageItems: - type: array - items: - type: integer - properties: - name: - type: string - speedMbPerS: - type: integer - sizeMb: - type: integer - Portfolio: - type: object - properties: - _id: - type: string - projectId: - type: string - name: - type: string - scenarioIds: - type: array - items: - type: string - targets: - type: object - properties: - enabledMetrics: - type: array - items: - type: string - repeatsPerScenario: - type: integer - Scenario: - type: object - properties: - _id: - type: string - portfolioId: - type: string - name: - type: string - simulation: - type: object - properties: - state: - type: string - results: - type: object - trace: - type: object - properties: - traceId: - type: string - loadSamplingFraction: - type: number - topology: - type: object - properties: - topologyId: - type: string - operational: - type: object - properties: - failuresEnabled: - type: boolean - performanceInterferenceEnabled: - type: boolean - schedulerName: - type: string - Trace: - type: object - properties: - _id: - type: string - name: - type: string - path: - type: string - type: - type: string - User: - type: object - properties: - _id: - type: string - googleId: - type: integer - email: - type: string - givenName: - type: string - familyName: - type: string - authorizations: - type: array - items: - type: object - properties: - projectId: - type: string - authorizationLevel: - type: string - Prefab: - type: object - properties: - _id: - type: string - name: - type: string - datetimeCreated: - type: string - format: dateTime - datetimeLastEdited: - type: string - format: dateTime +opendc-web/opendc-web-api/static/schema.yml
\ No newline at end of file diff --git a/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/ComputeClient.kt b/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/ComputeClient.kt index baa1ba2f..577fbc73 100644 --- a/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/ComputeClient.kt +++ b/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/ComputeClient.kt @@ -45,7 +45,7 @@ public interface ComputeClient : AutoCloseable { * * @param name The name of the flavor. * @param cpuCount The amount of CPU cores for this flavor. - * @param memorySize The size of the memory. + * @param memorySize The size of the memory in MB. * @param labels The identifying labels of the image. * @param meta The non-identifying meta-data of the image. */ diff --git a/opendc-compute/opendc-compute-service/build.gradle.kts b/opendc-compute/opendc-compute-service/build.gradle.kts index e0e48b0f..33cafc45 100644 --- a/opendc-compute/opendc-compute-service/build.gradle.kts +++ b/opendc-compute/opendc-compute-service/build.gradle.kts @@ -35,6 +35,7 @@ dependencies { api(projects.opendcTelemetry.opendcTelemetryApi) implementation(projects.opendcUtils) implementation(libs.kotlin.logging) + implementation(libs.opentelemetry.semconv) testImplementation(projects.opendcSimulator.opendcSimulatorCore) testRuntimeOnly(libs.log4j.slf4j) diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/ComputeService.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/ComputeService.kt index 1873eb99..2a1fbaa0 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/ComputeService.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/ComputeService.kt @@ -23,11 +23,13 @@ package org.opendc.compute.service import io.opentelemetry.api.metrics.Meter +import io.opentelemetry.api.metrics.MeterProvider import org.opendc.compute.api.ComputeClient import org.opendc.compute.service.driver.Host import org.opendc.compute.service.internal.ComputeServiceImpl import org.opendc.compute.service.scheduler.ComputeScheduler import java.time.Clock +import java.time.Duration import kotlin.coroutines.CoroutineContext /** @@ -70,16 +72,18 @@ public interface ComputeService : AutoCloseable { * * @param context The [CoroutineContext] to use in the service. * @param clock The clock instance to use. + * @param meterProvider The [MeterProvider] for creating a [Meter] for the service. * @param scheduler The scheduler implementation to use. + * @param schedulingQuantum The interval between scheduling cycles. */ public operator fun invoke( context: CoroutineContext, clock: Clock, - meter: Meter, + meterProvider: MeterProvider, scheduler: ComputeScheduler, - schedulingQuantum: Long = 300000, + schedulingQuantum: Duration = Duration.ofMinutes(5), ): ComputeService { - return ComputeServiceImpl(context, clock, meter, scheduler, schedulingQuantum) + return ComputeServiceImpl(context, clock, meterProvider, scheduler, schedulingQuantum) } } } diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/HostModel.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/HostModel.kt index 5632a55e..fc092a3f 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/HostModel.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/HostModel.kt @@ -25,7 +25,7 @@ package org.opendc.compute.service.driver /** * Describes the static machine properties of the host. * - * @property vcpuCount The number of logical processing cores available for this host. + * @property cpuCount The number of logical processing cores available for this host. * @property memorySize The amount of memory available for this host in MB. */ public data class HostModel(public val cpuCount: Int, public val memorySize: Long) diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt index 8af5f86e..57e70fcd 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt @@ -22,7 +22,10 @@ package org.opendc.compute.service.internal +import io.opentelemetry.api.common.AttributeKey +import io.opentelemetry.api.common.Attributes import io.opentelemetry.api.metrics.Meter +import io.opentelemetry.api.metrics.MeterProvider import kotlinx.coroutines.* import mu.KotlinLogging import org.opendc.compute.api.* @@ -33,6 +36,7 @@ import org.opendc.compute.service.driver.HostState import org.opendc.compute.service.scheduler.ComputeScheduler import org.opendc.utils.TimerScheduler import java.time.Clock +import java.time.Duration import java.util.* import kotlin.coroutines.CoroutineContext import kotlin.math.max @@ -40,15 +44,18 @@ import kotlin.math.max /** * Internal implementation of the OpenDC Compute service. * - * @param context The [CoroutineContext] to use. - * @param clock The clock instance to keep track of time. + * @param context The [CoroutineContext] to use in the service. + * @param clock The clock instance to use. + * @param meterProvider The [MeterProvider] for creating a [Meter] for the service. + * @param scheduler The scheduler implementation to use. + * @param schedulingQuantum The interval between scheduling cycles. */ internal class ComputeServiceImpl( private val context: CoroutineContext, private val clock: Clock, - private val meter: Meter, + meterProvider: MeterProvider, private val scheduler: ComputeScheduler, - private val schedulingQuantum: Long + private val schedulingQuantum: Duration ) : ComputeService, HostListener { /** * The [CoroutineScope] of the service bounded by the lifecycle of the service. @@ -61,6 +68,11 @@ internal class ComputeServiceImpl( private val logger = KotlinLogging.logger {} /** + * The [Meter] to track metrics of the [ComputeService]. + */ + private val meter = meterProvider.get("org.opendc.compute.service") + + /** * The [Random] instance used to generate unique identifiers for the objects. */ private val random = Random(0) @@ -104,60 +116,37 @@ internal class ComputeServiceImpl( private var maxMemory = 0L /** - * The number of servers that have been submitted to the service for provisioning. - */ - private val _submittedServers = meter.longCounterBuilder("servers.submitted") - .setDescription("Number of start requests") - .setUnit("1") - .build() - - /** - * The number of servers that failed to be scheduled. - */ - private val _unscheduledServers = meter.longCounterBuilder("servers.unscheduled") - .setDescription("Number of unscheduled servers") - .setUnit("1") - .build() - - /** - * The number of servers that are waiting to be provisioned. - */ - private val _waitingServers = meter.longUpDownCounterBuilder("servers.waiting") - .setDescription("Number of servers waiting to be provisioned") - .setUnit("1") - .build() - - /** - * The number of servers that are waiting to be provisioned. + * The number of scheduling attempts. */ - private val _runningServers = meter.longUpDownCounterBuilder("servers.active") - .setDescription("Number of servers currently running") + private val _schedulingAttempts = meter.counterBuilder("scheduler.attempts") + .setDescription("Number of scheduling attempts") .setUnit("1") .build() + private val _schedulingAttemptsSuccess = _schedulingAttempts + .bind(Attributes.of(AttributeKey.stringKey("result"), "success")) + private val _schedulingAttemptsFailure = _schedulingAttempts + .bind(Attributes.of(AttributeKey.stringKey("result"), "failure")) + private val _schedulingAttemptsError = _schedulingAttempts + .bind(Attributes.of(AttributeKey.stringKey("result"), "error")) /** - * The number of servers that have finished running. + * The response time of the service. */ - private val _finishedServers = meter.longCounterBuilder("servers.finished") - .setDescription("Number of servers that finished running") - .setUnit("1") + private val _schedulingLatency = meter.histogramBuilder("scheduler.latency") + .setDescription("End to end latency for a server to be scheduled (in multiple attempts)") + .ofLongs() + .setUnit("ms") .build() /** - * The number of hosts registered at the compute service. + * The number of servers that are pending. */ - private val _hostCount = meter.longUpDownCounterBuilder("hosts.total") - .setDescription("Number of hosts") - .setUnit("1") - .build() - - /** - * The number of available hosts registered at the compute service. - */ - private val _availableHostCount = meter.longUpDownCounterBuilder("hosts.available") - .setDescription("Number of available hosts") + private val _servers = meter.upDownCounterBuilder("scheduler.servers") + .setDescription("Number of servers managed by the scheduler") .setUnit("1") .build() + private val _serversPending = _servers.bind(Attributes.of(AttributeKey.stringKey("state"), "pending")) + private val _serversActive = _servers.bind(Attributes.of(AttributeKey.stringKey("state"), "active")) /** * The [TimerScheduler] to use for scheduling the scheduler cycles. @@ -170,6 +159,22 @@ internal class ComputeServiceImpl( override val hostCount: Int get() = hostToView.size + init { + val upState = Attributes.of(AttributeKey.stringKey("state"), "up") + val downState = Attributes.of(AttributeKey.stringKey("state"), "down") + + meter.upDownCounterBuilder("scheduler.hosts") + .setDescription("Number of hosts registered with the scheduler") + .setUnit("1") + .buildWithCallback { result -> + val total = hostCount + val available = availableHosts.size.toLong() + + result.observe(available, upState) + result.observe(total - available, downState) + } + } + override fun newClient(): ComputeClient { check(scope.isActive) { "Service is already closed" } return object : ComputeClient { @@ -297,24 +302,19 @@ internal class ComputeServiceImpl( hostToView[host] = hv if (host.state == HostState.UP) { - _availableHostCount.add(1) availableHosts += hv } scheduler.addHost(hv) - _hostCount.add(1) host.addListener(this) } override fun removeHost(host: Host) { val view = hostToView.remove(host) if (view != null) { - if (availableHosts.remove(view)) { - _availableHostCount.add(-1) - } + availableHosts.remove(view) scheduler.removeHost(view) host.removeListener(this) - _hostCount.add(-1) } } @@ -325,10 +325,9 @@ internal class ComputeServiceImpl( internal fun schedule(server: InternalServer): SchedulingRequest { logger.debug { "Enqueueing server ${server.uid} to be assigned to host." } - val request = SchedulingRequest(server) + val request = SchedulingRequest(server, clock.millis()) queue.add(request) - _submittedServers.add(1) - _waitingServers.add(1) + _serversPending.add(1) requestSchedulingCycle() return request } @@ -354,10 +353,12 @@ internal class ComputeServiceImpl( return } + val quantum = schedulingQuantum.toMillis() + // We assume that the provisioner runs at a fixed slot every time quantum (e.g t=0, t=60, t=120). // This is important because the slices of the VMs need to be aligned. // We calculate here the delay until the next scheduling slot. - val delay = schedulingQuantum - (clock.millis() % schedulingQuantum) + val delay = quantum - (clock.millis() % quantum) timerScheduler.startSingleTimer(Unit, delay) { doSchedule() @@ -368,12 +369,13 @@ internal class ComputeServiceImpl( * Run a single scheduling iteration. */ private fun doSchedule() { + val now = clock.millis() while (queue.isNotEmpty()) { val request = queue.peek() if (request.isCancelled) { queue.poll() - _waitingServers.add(-1) + _serversPending.add(-1) continue } @@ -385,12 +387,12 @@ internal class ComputeServiceImpl( if (server.flavor.memorySize > maxMemory || server.flavor.cpuCount > maxCores) { // Remove the incoming image queue.poll() - _waitingServers.add(-1) - _unscheduledServers.add(1) + _serversPending.add(-1) + _schedulingAttemptsFailure.add(1) - logger.warn("Failed to spawn $server: does not fit [${clock.millis()}]") + logger.warn { "Failed to spawn $server: does not fit [${clock.instant()}]" } - server.state = ServerState.ERROR + server.state = ServerState.TERMINATED continue } else { break @@ -401,7 +403,8 @@ internal class ComputeServiceImpl( // Remove request from queue queue.poll() - _waitingServers.add(-1) + _serversPending.add(-1) + _schedulingLatency.record(now - request.submitTime, server.attributes) logger.info { "Assigned server $server to host $host." } @@ -416,12 +419,17 @@ internal class ComputeServiceImpl( server.host = host host.spawn(server) activeServers[server] = host + + _serversActive.add(1) + _schedulingAttemptsSuccess.add(1) } catch (e: Throwable) { - logger.error("Failed to deploy VM", e) + logger.error(e) { "Failed to deploy VM" } hv.instanceCount-- hv.provisionedCores -= server.flavor.cpuCount hv.availableMemory += server.flavor.memorySize + + _schedulingAttemptsError.add(1) } } } @@ -430,7 +438,7 @@ internal class ComputeServiceImpl( /** * A request to schedule an [InternalServer] onto one of the [Host]s. */ - internal data class SchedulingRequest(val server: InternalServer) { + internal data class SchedulingRequest(val server: InternalServer, val submitTime: Long) { /** * A flag to indicate that the request is cancelled. */ @@ -440,24 +448,22 @@ internal class ComputeServiceImpl( override fun onStateChanged(host: Host, newState: HostState) { when (newState) { HostState.UP -> { - logger.debug { "[${clock.millis()}] Host ${host.uid} state changed: $newState" } + logger.debug { "[${clock.instant()}] Host ${host.uid} state changed: $newState" } val hv = hostToView[host] if (hv != null) { // Corner case for when the hypervisor already exists availableHosts += hv - _availableHostCount.add(1) } // Re-schedule on the new machine requestSchedulingCycle() } HostState.DOWN -> { - logger.debug { "[${clock.millis()}] Host ${host.uid} state changed: $newState" } + logger.debug { "[${clock.instant()}] Host ${host.uid} state changed: $newState" } val hv = hostToView[host] ?: return availableHosts -= hv - _availableHostCount.add(-1) requestSchedulingCycle() } @@ -475,14 +481,12 @@ internal class ComputeServiceImpl( server.state = newState - if (newState == ServerState.RUNNING) { - _runningServers.add(1) - } else if (newState == ServerState.TERMINATED || newState == ServerState.DELETED) { - logger.info { "[${clock.millis()}] Server ${server.uid} ${server.name} ${server.flavor} finished." } + if (newState == ServerState.TERMINATED || newState == ServerState.DELETED) { + logger.info { "[${clock.instant()}] Server ${server.uid} ${server.name} ${server.flavor} finished." } - activeServers -= server - _runningServers.add(-1) - _finishedServers.add(1) + if (activeServers.remove(server) != null) { + _serversActive.add(-1) + } val hv = hostToView[host] if (hv != null) { diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalServer.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalServer.kt index d9d0f3fc..05a7e1bf 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalServer.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalServer.kt @@ -22,6 +22,9 @@ package org.opendc.compute.service.internal +import io.opentelemetry.api.common.AttributeKey +import io.opentelemetry.api.common.Attributes +import io.opentelemetry.semconv.resource.attributes.ResourceAttributes import mu.KotlinLogging import org.opendc.compute.api.* import org.opendc.compute.service.driver.Host @@ -50,6 +53,21 @@ internal class InternalServer( private val watchers = mutableListOf<ServerWatcher>() /** + * The attributes of a server. + */ + internal val attributes: Attributes = Attributes.builder() + .put(ResourceAttributes.HOST_NAME, name) + .put(ResourceAttributes.HOST_ID, uid.toString()) + .put(ResourceAttributes.HOST_TYPE, flavor.name) + .put(AttributeKey.longKey("host.num_cpus"), flavor.cpuCount.toLong()) + .put(AttributeKey.longKey("host.mem_capacity"), flavor.memorySize) + .put(AttributeKey.stringArrayKey("host.labels"), labels.map { (k, v) -> "$k:$v" }) + .put(ResourceAttributes.HOST_ARCH, ResourceAttributes.HostArchValues.AMD64) + .put(ResourceAttributes.HOST_IMAGE_NAME, image.name) + .put(ResourceAttributes.HOST_IMAGE_ID, image.uid.toString()) + .build() + + /** * The [Host] that has been assigned to host the server. */ internal var host: Host? = null diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/FilterScheduler.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/FilterScheduler.kt index 0fd5b2a4..8c2d4715 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/FilterScheduler.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/FilterScheduler.kt @@ -26,6 +26,8 @@ import org.opendc.compute.api.Server import org.opendc.compute.service.internal.HostView import org.opendc.compute.service.scheduler.filters.HostFilter import org.opendc.compute.service.scheduler.weights.HostWeigher +import java.util.* +import kotlin.math.min /** * A [ComputeScheduler] implementation that uses filtering and weighing passes to select @@ -33,13 +35,27 @@ import org.opendc.compute.service.scheduler.weights.HostWeigher * * This implementation is based on the filter scheduler from OpenStack Nova. * See: https://docs.openstack.org/nova/latest/user/filter-scheduler.html + * + * @param filters The list of filters to apply when searching for an appropriate host. + * @param weighers The list of weighers to apply when searching for an appropriate host. + * @param subsetSize The size of the subset of best hosts from which a target is randomly chosen. + * @param random A [Random] instance for selecting */ -public class FilterScheduler(private val filters: List<HostFilter>, private val weighers: List<Pair<HostWeigher, Double>>) : ComputeScheduler { +public class FilterScheduler( + private val filters: List<HostFilter>, + private val weighers: List<HostWeigher>, + private val subsetSize: Int = 1, + private val random: Random = Random(0) +) : ComputeScheduler { /** * The pool of hosts available to the scheduler. */ private val hosts = mutableListOf<HostView>() + init { + require(subsetSize >= 1) { "Subset size must be one or greater" } + } + override fun addHost(host: HostView) { hosts.add(host) } @@ -49,18 +65,44 @@ public class FilterScheduler(private val filters: List<HostFilter>, private val } override fun select(server: Server): HostView? { - return hosts.asSequence() - .filter { host -> - for (filter in filters) { - if (!filter.test(host, server)) - return@filter false + val hosts = hosts + val filteredHosts = hosts.filter { host -> filters.all { filter -> filter.test(host, server) } } + + val subset = if (weighers.isNotEmpty()) { + val results = weighers.map { it.getWeights(filteredHosts, server) } + val weights = DoubleArray(filteredHosts.size) + + for (result in results) { + val min = result.min + val range = (result.max - min) + + // Skip result if all weights are the same + if (range == 0.0) { + continue } - true - } - .sortedByDescending { host -> - weighers.sumByDouble { (weigher, factor) -> weigher.getWeight(host, server) * factor } + val multiplier = result.multiplier + val factor = multiplier / range + + for ((i, weight) in result.weights.withIndex()) { + weights[i] += factor * (weight - min) + } } - .firstOrNull() + + weights.indices + .asSequence() + .sortedByDescending { weights[it] } + .map { filteredHosts[it] } + .take(subsetSize) + .toList() + } else { + filteredHosts + } + + return when (val maxSize = min(subsetSize, subset.size)) { + 0 -> null + 1 -> subset[0] + else -> subset[random.nextInt(maxSize)] + } } } diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/ComputeCapabilitiesFilter.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/ComputeCapabilitiesFilter.kt deleted file mode 100644 index 072440c5..00000000 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/ComputeCapabilitiesFilter.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.compute.service.scheduler.filters - -import org.opendc.compute.api.Server -import org.opendc.compute.service.internal.HostView - -/** - * A [HostFilter] that checks whether the capabilities provided by the host satisfies the requirements of the server - * flavor. - */ -public class ComputeCapabilitiesFilter : HostFilter { - override fun test(host: HostView, server: Server): Boolean { - val fitsMemory = host.availableMemory >= server.flavor.memorySize - val fitsCpu = host.host.model.cpuCount >= server.flavor.cpuCount - return fitsMemory && fitsCpu - } - - override fun toString(): String = "ComputeCapabilitiesFilter" -} diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/RamFilter.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/RamFilter.kt new file mode 100644 index 00000000..a470a453 --- /dev/null +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/RamFilter.kt @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.service.scheduler.filters + +import org.opendc.compute.api.Server +import org.opendc.compute.service.internal.HostView + +/** + * A [HostFilter] that filters hosts based on the memory requirements of a [Server] and the RAM available on the host. + * + * @param allocationRatio Virtual RAM to physical RAM allocation ratio. + */ +public class RamFilter(private val allocationRatio: Double) : HostFilter { + override fun test(host: HostView, server: Server): Boolean { + val requested = server.flavor.memorySize + val available = host.availableMemory + val total = host.host.model.memorySize + + // Do not allow an instance to overcommit against itself, only against + // other instances. + if (requested > total) { + return false + } + + val limit = total * allocationRatio + val used = total - available + val usable = limit - used + return usable >= requested + } +} diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/VCpuFilter.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/VCpuFilter.kt new file mode 100644 index 00000000..abdd79f1 --- /dev/null +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/VCpuFilter.kt @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.service.scheduler.filters + +import org.opendc.compute.api.Server +import org.opendc.compute.service.internal.HostView + +/** + * A [HostFilter] that filters hosts based on the vCPU requirements of a [Server] and the available vCPUs on the host. + * + * @param allocationRatio Virtual CPU to physical CPU allocation ratio. + */ +public class VCpuFilter(private val allocationRatio: Double) : HostFilter { + override fun test(host: HostView, server: Server): Boolean { + val requested = server.flavor.cpuCount + val total = host.host.model.cpuCount + val limit = total * allocationRatio + + // Do not allow an instance to overcommit against itself, only against other instances + if (requested > total) { + return false + } + + val free = limit - host.provisionedCores + return free >= requested + } +} diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/CoreMemoryWeigher.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/CoreMemoryWeigher.kt deleted file mode 100644 index 12e6510e..00000000 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/CoreMemoryWeigher.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.compute.service.scheduler.weights - -import org.opendc.compute.api.Server -import org.opendc.compute.service.internal.HostView - -/** - * A [HostWeigher] that weighs the hosts based on the available memory per core on the host. - */ -public class CoreMemoryWeigher : HostWeigher { - override fun getWeight(host: HostView, server: Server): Double { - return host.availableMemory.toDouble() / host.host.model.cpuCount - } - - override fun toString(): String = "CoreMemoryWeigher" -} diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/CoreRamWeigher.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/CoreRamWeigher.kt new file mode 100644 index 00000000..d668fdaf --- /dev/null +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/CoreRamWeigher.kt @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.service.scheduler.weights + +import org.opendc.compute.api.Server +import org.opendc.compute.service.internal.HostView + +/** + * A [HostWeigher] that weighs the hosts based on the available memory per core on the host. + * + * @param multiplier Weight multiplier ratio. A positive value will result in the scheduler preferring hosts with more + * available core memory, and a negative number will result in the scheduler preferring hosts with less available core + * memory. + */ +public class CoreRamWeigher(override val multiplier: Double = 1.0) : HostWeigher { + override fun getWeight(host: HostView, server: Server): Double { + return host.availableMemory.toDouble() / host.host.model.cpuCount + } + + override fun toString(): String = "CoreRamWeigher" +} diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/HostWeigher.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/HostWeigher.kt index d48ee9e0..aca8c4e6 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/HostWeigher.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/HostWeigher.kt @@ -29,9 +29,47 @@ import org.opendc.compute.service.scheduler.FilterScheduler /** * An interface used by the [FilterScheduler] to weigh the pool of host for a scheduling request. */ -public fun interface HostWeigher { +public interface HostWeigher { + /** + * The multiplier for the weigher. + */ + public val multiplier: Double + /** * Obtain the weight of the specified [host] when scheduling the specified [server]. */ public fun getWeight(host: HostView, server: Server): Double + + /** + * Obtain the weights for [hosts] when scheduling the specified [server]. + */ + public fun getWeights(hosts: List<HostView>, server: Server): Result { + val weights = DoubleArray(hosts.size) + var min = Double.MAX_VALUE + var max = Double.MIN_VALUE + + for ((i, host) in hosts.withIndex()) { + val weight = getWeight(host, server) + weights[i] = weight + min = kotlin.math.min(min, weight) + max = kotlin.math.max(max, weight) + } + + return Result(weights, min, max, multiplier) + } + + /** + * A result returned by the weigher. + * + * @param weights The weights returned by the weigher. + * @param min The minimum weight returned. + * @param max The maximum weight returned. + * @param multiplier The weight multiplier to use. + */ + public class Result( + public val weights: DoubleArray, + public val min: Double, + public val max: Double, + public val multiplier: Double, + ) } diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/InstanceCountWeigher.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/InstanceCountWeigher.kt index 2ef733e5..732cbe03 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/InstanceCountWeigher.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/InstanceCountWeigher.kt @@ -28,7 +28,7 @@ import org.opendc.compute.service.internal.HostView /** * A [HostWeigher] that weighs the hosts based on the number of instances on the host. */ -public class InstanceCountWeigher : HostWeigher { +public class InstanceCountWeigher(override val multiplier: Double = 1.0) : HostWeigher { override fun getWeight(host: HostView, server: Server): Double { return host.instanceCount.toDouble() } diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/MemoryWeigher.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/MemoryWeigher.kt deleted file mode 100644 index 115d8e4d..00000000 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/MemoryWeigher.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.compute.service.scheduler.weights - -import org.opendc.compute.api.Server -import org.opendc.compute.service.internal.HostView - -/** - * A [HostWeigher] that weighs the hosts based on the available memory on the host. - */ -public class MemoryWeigher : HostWeigher { - override fun getWeight(host: HostView, server: Server): Double { - return host.availableMemory.toDouble() - } - - override fun toString(): String = "MemoryWeigher" -} diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/ProvisionedCoresWeigher.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/ProvisionedCoresWeigher.kt deleted file mode 100644 index df5bcd6e..00000000 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/ProvisionedCoresWeigher.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.compute.service.scheduler.weights - -import org.opendc.compute.api.Server -import org.opendc.compute.service.internal.HostView - -/** - * A [HostWeigher] that weighs the hosts based on the number of provisioned cores on the host. - */ -public class ProvisionedCoresWeigher : HostWeigher { - override fun getWeight(host: HostView, server: Server): Double { - return host.provisionedCores.toDouble() - } - - override fun toString(): String = "ProvisionedCoresWeigher" -} diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/RamWeigher.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/RamWeigher.kt new file mode 100644 index 00000000..d18d31f4 --- /dev/null +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/RamWeigher.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.service.scheduler.weights + +import org.opendc.compute.api.Server +import org.opendc.compute.service.internal.HostView + +/** + * A [HostWeigher] that weighs the hosts based on the available RAM (memory) on the host. + * + * @param multiplier Weight multiplier ratio. A positive value will result in the scheduler preferring hosts with more + * available memory, and a negative number will result in the scheduler preferring hosts with less memory. + */ +public class RamWeigher(override val multiplier: Double = 1.0) : HostWeigher { + override fun getWeight(host: HostView, server: Server): Double { + return host.availableMemory.toDouble() + } + + override fun toString(): String = "RamWeigher" +} diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/RandomWeigher.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/RandomWeigher.kt deleted file mode 100644 index 1615df3a..00000000 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/RandomWeigher.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.compute.service.scheduler.weights - -import org.opendc.compute.api.Server -import org.opendc.compute.service.internal.HostView -import java.util.* - -/** - * A [HostWeigher] that assigns random weights to each host every selection. - */ -public class RandomWeigher(private val random: Random) : HostWeigher { - override fun getWeight(host: HostView, server: Server): Double = random.nextDouble() - - override fun toString(): String = "RandomWeigher" -} diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/VCpuWeigher.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/VCpuWeigher.kt new file mode 100644 index 00000000..4a22269b --- /dev/null +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/VCpuWeigher.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.service.scheduler.weights + +import org.opendc.compute.api.Server +import org.opendc.compute.service.internal.HostView + +/** + * A [HostWeigher] that weighs the hosts based on the remaining number of vCPUs available. + * + * @param allocationRatio Virtual CPU to physical CPU allocation ratio. + */ +public class VCpuWeigher(private val allocationRatio: Double, override val multiplier: Double = 1.0) : HostWeigher { + + init { + require(allocationRatio > 0.0) { "Allocation ratio must be greater than zero" } + } + + override fun getWeight(host: HostView, server: Server): Double { + return host.host.model.cpuCount * allocationRatio - host.provisionedCores + } + + override fun toString(): String = "VCpuWeigher" +} diff --git a/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ComputeServiceTest.kt b/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ComputeServiceTest.kt index a6258845..564f9493 100644 --- a/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ComputeServiceTest.kt +++ b/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ComputeServiceTest.kt @@ -37,9 +37,10 @@ import org.opendc.compute.service.driver.HostListener import org.opendc.compute.service.driver.HostModel import org.opendc.compute.service.driver.HostState import org.opendc.compute.service.scheduler.FilterScheduler -import org.opendc.compute.service.scheduler.filters.ComputeCapabilitiesFilter import org.opendc.compute.service.scheduler.filters.ComputeFilter -import org.opendc.compute.service.scheduler.weights.MemoryWeigher +import org.opendc.compute.service.scheduler.filters.RamFilter +import org.opendc.compute.service.scheduler.filters.VCpuFilter +import org.opendc.compute.service.scheduler.weights.RamWeigher import org.opendc.simulator.core.SimulationCoroutineScope import org.opendc.simulator.core.runBlockingSimulation import java.util.* @@ -57,11 +58,10 @@ internal class ComputeServiceTest { scope = SimulationCoroutineScope() val clock = scope.clock val computeScheduler = FilterScheduler( - filters = listOf(ComputeFilter(), ComputeCapabilitiesFilter()), - weighers = listOf(MemoryWeigher() to -1.0) + filters = listOf(ComputeFilter(), VCpuFilter(allocationRatio = 1.0), RamFilter(allocationRatio = 1.0)), + weighers = listOf(RamWeigher()) ) - val meter = MeterProvider.noop().get("opendc-compute") - service = ComputeService(scope.coroutineContext, clock, meter, computeScheduler) + service = ComputeService(scope.coroutineContext, clock, MeterProvider.noop(), computeScheduler) } @Test @@ -167,9 +167,9 @@ internal class ComputeServiceTest { val server = client.newServer("test", image, flavor, start = false) server.start() - delay(5 * 60 * 1000) + delay(5L * 60 * 1000) server.refresh() - assertEquals(ServerState.ERROR, server.state) + assertEquals(ServerState.TERMINATED, server.state) } @Test @@ -180,9 +180,9 @@ internal class ComputeServiceTest { val server = client.newServer("test", image, flavor, start = false) server.start() - delay(5 * 60 * 1000) + delay(5L * 60 * 1000) server.refresh() - assertEquals(ServerState.ERROR, server.state) + assertEquals(ServerState.TERMINATED, server.state) } @Test @@ -193,9 +193,9 @@ internal class ComputeServiceTest { val server = client.newServer("test", image, flavor, start = false) server.start() - delay(5 * 60 * 1000) + delay(5L * 60 * 1000) server.refresh() - assertEquals(ServerState.ERROR, server.state) + assertEquals(ServerState.TERMINATED, server.state) } @Test @@ -207,7 +207,7 @@ internal class ComputeServiceTest { server.start() server.stop() - delay(5 * 60 * 1000) + delay(5L * 60 * 1000) server.refresh() assertEquals(ServerState.TERMINATED, server.state) } @@ -228,7 +228,7 @@ internal class ComputeServiceTest { val server = client.newServer("test", image, flavor, start = false) server.start() - delay(10 * 60 * 1000) + delay(10L * 60 * 1000) server.refresh() assertEquals(ServerState.PROVISIONING, server.state) @@ -254,12 +254,12 @@ internal class ComputeServiceTest { val server = client.newServer("test", image, flavor, start = false) server.start() - delay(5 * 60 * 1000) + delay(5L * 60 * 1000) every { host.state } returns HostState.UP listeners.forEach { it.onStateChanged(host, HostState.UP) } - delay(5 * 60 * 1000) + delay(5L * 60 * 1000) server.refresh() assertEquals(ServerState.PROVISIONING, server.state) @@ -284,13 +284,13 @@ internal class ComputeServiceTest { val image = client.newImage("test") val server = client.newServer("test", image, flavor, start = false) - delay(5 * 60 * 1000) + delay(5L * 60 * 1000) every { host.state } returns HostState.DOWN listeners.forEach { it.onStateChanged(host, HostState.DOWN) } server.start() - delay(5 * 60 * 1000) + delay(5L * 60 * 1000) server.refresh() assertEquals(ServerState.PROVISIONING, server.state) @@ -344,7 +344,7 @@ internal class ComputeServiceTest { // Start server server.start() - delay(5 * 60 * 1000) + delay(5L * 60 * 1000) coVerify { host.spawn(capture(slot), true) } listeners.forEach { it.onStateChanged(host, slot.captured, ServerState.RUNNING) } @@ -383,7 +383,7 @@ internal class ComputeServiceTest { val server = client.newServer("test", image, flavor, start = false) server.start() - delay(5 * 60 * 1000) + delay(5L * 60 * 1000) server.refresh() assertEquals(ServerState.PROVISIONING, server.state) diff --git a/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/InternalServerTest.kt b/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/InternalServerTest.kt index 20ea8d20..dfd3bc67 100644 --- a/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/InternalServerTest.kt +++ b/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/InternalServerTest.kt @@ -47,8 +47,9 @@ class InternalServerTest { fun testEquality() { val service = mockk<ComputeServiceImpl>() val uid = UUID.randomUUID() - val flavor = mockk<InternalFlavor>() - val image = mockk<InternalImage>() + val flavor = mockFlavor() + val image = mockImage() + val a = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) val b = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) @@ -59,8 +60,8 @@ class InternalServerTest { fun testEqualityWithDifferentType() { val service = mockk<ComputeServiceImpl>() val uid = UUID.randomUUID() - val flavor = mockk<InternalFlavor>() - val image = mockk<InternalImage>() + val flavor = mockFlavor() + val image = mockImage() val a = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) val b = mockk<Server>(relaxUnitFun = true) @@ -73,8 +74,8 @@ class InternalServerTest { fun testInequalityWithDifferentType() { val service = mockk<ComputeServiceImpl>() val uid = UUID.randomUUID() - val flavor = mockk<InternalFlavor>() - val image = mockk<InternalImage>() + val flavor = mockFlavor() + val image = mockImage() val a = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) val b = mockk<Server>(relaxUnitFun = true) @@ -87,8 +88,8 @@ class InternalServerTest { fun testInequalityWithIncorrectType() { val service = mockk<ComputeServiceImpl>() val uid = UUID.randomUUID() - val flavor = mockk<InternalFlavor>() - val image = mockk<InternalImage>() + val flavor = mockFlavor() + val image = mockImage() val a = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) assertNotEquals(a, Unit) @@ -98,11 +99,11 @@ class InternalServerTest { fun testStartTerminatedServer() = runBlockingSimulation { val service = mockk<ComputeServiceImpl>() val uid = UUID.randomUUID() - val flavor = mockk<InternalFlavor>() - val image = mockk<InternalImage>() + val flavor = mockFlavor() + val image = mockImage() val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) - every { service.schedule(any()) } answers { ComputeServiceImpl.SchedulingRequest(it.invocation.args[0] as InternalServer) } + every { service.schedule(any()) } answers { ComputeServiceImpl.SchedulingRequest(it.invocation.args[0] as InternalServer, 0) } server.start() @@ -114,8 +115,8 @@ class InternalServerTest { fun testStartDeletedServer() = runBlockingSimulation { val service = mockk<ComputeServiceImpl>() val uid = UUID.randomUUID() - val flavor = mockk<InternalFlavor>() - val image = mockk<InternalImage>() + val flavor = mockFlavor() + val image = mockImage() val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) server.state = ServerState.DELETED @@ -127,8 +128,8 @@ class InternalServerTest { fun testStartProvisioningServer() = runBlockingSimulation { val service = mockk<ComputeServiceImpl>() val uid = UUID.randomUUID() - val flavor = mockk<InternalFlavor>() - val image = mockk<InternalImage>() + val flavor = mockFlavor() + val image = mockImage() val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) server.state = ServerState.PROVISIONING @@ -142,8 +143,8 @@ class InternalServerTest { fun testStartRunningServer() = runBlockingSimulation { val service = mockk<ComputeServiceImpl>() val uid = UUID.randomUUID() - val flavor = mockk<InternalFlavor>() - val image = mockk<InternalImage>() + val flavor = mockFlavor() + val image = mockImage() val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) server.state = ServerState.RUNNING @@ -157,10 +158,10 @@ class InternalServerTest { fun testStopProvisioningServer() = runBlockingSimulation { val service = mockk<ComputeServiceImpl>() val uid = UUID.randomUUID() - val flavor = mockk<InternalFlavor>() - val image = mockk<InternalImage>() + val flavor = mockFlavor() + val image = mockImage() val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) - val request = ComputeServiceImpl.SchedulingRequest(server) + val request = ComputeServiceImpl.SchedulingRequest(server, 0) every { service.schedule(any()) } returns request @@ -175,8 +176,8 @@ class InternalServerTest { fun testStopTerminatedServer() = runBlockingSimulation { val service = mockk<ComputeServiceImpl>() val uid = UUID.randomUUID() - val flavor = mockk<InternalFlavor>() - val image = mockk<InternalImage>() + val flavor = mockFlavor() + val image = mockImage() val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) server.state = ServerState.TERMINATED @@ -189,8 +190,8 @@ class InternalServerTest { fun testStopDeletedServer() = runBlockingSimulation { val service = mockk<ComputeServiceImpl>() val uid = UUID.randomUUID() - val flavor = mockk<InternalFlavor>() - val image = mockk<InternalImage>() + val flavor = mockFlavor() + val image = mockImage() val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) server.state = ServerState.DELETED @@ -203,8 +204,8 @@ class InternalServerTest { fun testStopRunningServer() = runBlockingSimulation { val service = mockk<ComputeServiceImpl>() val uid = UUID.randomUUID() - val flavor = mockk<InternalFlavor>() - val image = mockk<InternalImage>() + val flavor = mockFlavor() + val image = mockImage() val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) val host = mockk<Host>(relaxUnitFun = true) @@ -220,10 +221,10 @@ class InternalServerTest { fun testDeleteProvisioningServer() = runBlockingSimulation { val service = mockk<ComputeServiceImpl>(relaxUnitFun = true) val uid = UUID.randomUUID() - val flavor = mockk<InternalFlavor>() - val image = mockk<InternalImage>() + val flavor = mockFlavor() + val image = mockImage() val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) - val request = ComputeServiceImpl.SchedulingRequest(server) + val request = ComputeServiceImpl.SchedulingRequest(server, 0) every { service.schedule(any()) } returns request @@ -239,8 +240,8 @@ class InternalServerTest { fun testDeleteTerminatedServer() = runBlockingSimulation { val service = mockk<ComputeServiceImpl>(relaxUnitFun = true) val uid = UUID.randomUUID() - val flavor = mockk<InternalFlavor>() - val image = mockk<InternalImage>() + val flavor = mockFlavor() + val image = mockImage() val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) server.state = ServerState.TERMINATED @@ -255,8 +256,8 @@ class InternalServerTest { fun testDeleteDeletedServer() = runBlockingSimulation { val service = mockk<ComputeServiceImpl>(relaxUnitFun = true) val uid = UUID.randomUUID() - val flavor = mockk<InternalFlavor>() - val image = mockk<InternalImage>() + val flavor = mockFlavor() + val image = mockImage() val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) server.state = ServerState.DELETED @@ -269,8 +270,8 @@ class InternalServerTest { fun testDeleteRunningServer() = runBlockingSimulation { val service = mockk<ComputeServiceImpl>(relaxUnitFun = true) val uid = UUID.randomUUID() - val flavor = mockk<InternalFlavor>() - val image = mockk<InternalImage>() + val flavor = mockFlavor() + val image = mockImage() val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) val host = mockk<Host>(relaxUnitFun = true) @@ -282,4 +283,20 @@ class InternalServerTest { coVerify { host.delete(server) } verify { service.delete(server) } } + + private fun mockFlavor(): InternalFlavor { + val flavor = mockk<InternalFlavor>() + every { flavor.name } returns "c5.large" + every { flavor.uid } returns UUID.randomUUID() + every { flavor.cpuCount } returns 2 + every { flavor.memorySize } returns 4096 + return flavor + } + + private fun mockImage(): InternalImage { + val image = mockk<InternalImage>() + every { image.name } returns "ubuntu-20.04" + every { image.uid } returns UUID.randomUUID() + return image + } } diff --git a/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/scheduler/FilterSchedulerTest.kt b/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/scheduler/FilterSchedulerTest.kt new file mode 100644 index 00000000..cafd4498 --- /dev/null +++ b/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/scheduler/FilterSchedulerTest.kt @@ -0,0 +1,407 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.service.scheduler + +import io.mockk.every +import io.mockk.mockk +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertAll +import org.junit.jupiter.api.assertThrows +import org.opendc.compute.api.Server +import org.opendc.compute.service.driver.HostModel +import org.opendc.compute.service.driver.HostState +import org.opendc.compute.service.internal.HostView +import org.opendc.compute.service.scheduler.filters.ComputeFilter +import org.opendc.compute.service.scheduler.filters.InstanceCountFilter +import org.opendc.compute.service.scheduler.filters.RamFilter +import org.opendc.compute.service.scheduler.filters.VCpuFilter +import org.opendc.compute.service.scheduler.weights.CoreRamWeigher +import org.opendc.compute.service.scheduler.weights.InstanceCountWeigher +import org.opendc.compute.service.scheduler.weights.RamWeigher +import org.opendc.compute.service.scheduler.weights.VCpuWeigher +import java.util.* + +/** + * Test suite for the [FilterScheduler]. + */ +internal class FilterSchedulerTest { + @Test + fun testInvalidSubsetSize() { + assertThrows<IllegalArgumentException> { + FilterScheduler( + filters = emptyList(), + weighers = emptyList(), + subsetSize = 0 + ) + } + + assertThrows<IllegalArgumentException> { + FilterScheduler( + filters = emptyList(), + weighers = emptyList(), + subsetSize = -2 + ) + } + } + + @Test + fun testNoHosts() { + val scheduler = FilterScheduler( + filters = emptyList(), + weighers = emptyList(), + ) + + val server = mockk<Server>() + every { server.flavor.cpuCount } returns 2 + every { server.flavor.memorySize } returns 1024 + + assertNull(scheduler.select(server)) + } + + @Test + fun testNoFiltersAndSchedulers() { + val scheduler = FilterScheduler( + filters = emptyList(), + weighers = emptyList(), + ) + + val hostA = mockk<HostView>() + every { hostA.host.state } returns HostState.DOWN + + val hostB = mockk<HostView>() + every { hostB.host.state } returns HostState.UP + + scheduler.addHost(hostA) + scheduler.addHost(hostB) + + val server = mockk<Server>() + every { server.flavor.cpuCount } returns 2 + every { server.flavor.memorySize } returns 1024 + + // Make sure we get the first host both times + assertAll( + { assertEquals(hostA, scheduler.select(server)) }, + { assertEquals(hostA, scheduler.select(server)) } + ) + } + + @Test + fun testNoFiltersAndSchedulersRandom() { + val scheduler = FilterScheduler( + filters = emptyList(), + weighers = emptyList(), + subsetSize = Int.MAX_VALUE, + random = Random(1) + ) + + val hostA = mockk<HostView>() + every { hostA.host.state } returns HostState.DOWN + + val hostB = mockk<HostView>() + every { hostB.host.state } returns HostState.UP + + scheduler.addHost(hostA) + scheduler.addHost(hostB) + + val server = mockk<Server>() + every { server.flavor.cpuCount } returns 2 + every { server.flavor.memorySize } returns 1024 + + // Make sure we get the first host both times + assertAll( + { assertEquals(hostB, scheduler.select(server)) }, + { assertEquals(hostA, scheduler.select(server)) } + ) + } + + @Test + fun testHostIsDown() { + val scheduler = FilterScheduler( + filters = listOf(ComputeFilter()), + weighers = emptyList(), + ) + + val host = mockk<HostView>() + every { host.host.state } returns HostState.DOWN + + scheduler.addHost(host) + + val server = mockk<Server>() + every { server.flavor.cpuCount } returns 2 + every { server.flavor.memorySize } returns 1024 + + assertNull(scheduler.select(server)) + } + + @Test + fun testHostIsUp() { + val scheduler = FilterScheduler( + filters = listOf(ComputeFilter()), + weighers = emptyList(), + ) + + val host = mockk<HostView>() + every { host.host.state } returns HostState.UP + + scheduler.addHost(host) + + val server = mockk<Server>() + every { server.flavor.cpuCount } returns 2 + every { server.flavor.memorySize } returns 1024 + + assertEquals(host, scheduler.select(server)) + } + + @Test + fun testRamFilter() { + val scheduler = FilterScheduler( + filters = listOf(RamFilter(1.0)), + weighers = emptyList(), + ) + + val hostA = mockk<HostView>() + every { hostA.host.state } returns HostState.UP + every { hostA.host.model } returns HostModel(4, 2048) + every { hostA.availableMemory } returns 512 + + val hostB = mockk<HostView>() + every { hostB.host.state } returns HostState.UP + every { hostB.host.model } returns HostModel(4, 2048) + every { hostB.availableMemory } returns 2048 + + scheduler.addHost(hostA) + scheduler.addHost(hostB) + + val server = mockk<Server>() + every { server.flavor.cpuCount } returns 2 + every { server.flavor.memorySize } returns 1024 + + assertEquals(hostB, scheduler.select(server)) + } + + @Test + fun testRamFilterOvercommit() { + val scheduler = FilterScheduler( + filters = listOf(RamFilter(1.5)), + weighers = emptyList(), + ) + + val host = mockk<HostView>() + every { host.host.state } returns HostState.UP + every { host.host.model } returns HostModel(4, 2048) + every { host.availableMemory } returns 2048 + + scheduler.addHost(host) + + val server = mockk<Server>() + every { server.flavor.cpuCount } returns 2 + every { server.flavor.memorySize } returns 2300 + + assertNull(scheduler.select(server)) + } + + @Test + fun testVCpuFilter() { + val scheduler = FilterScheduler( + filters = listOf(VCpuFilter(1.0)), + weighers = emptyList(), + ) + + val hostA = mockk<HostView>() + every { hostA.host.state } returns HostState.UP + every { hostA.host.model } returns HostModel(4, 2048) + every { hostA.provisionedCores } returns 3 + + val hostB = mockk<HostView>() + every { hostB.host.state } returns HostState.UP + every { hostB.host.model } returns HostModel(4, 2048) + every { hostB.provisionedCores } returns 0 + + scheduler.addHost(hostA) + scheduler.addHost(hostB) + + val server = mockk<Server>() + every { server.flavor.cpuCount } returns 2 + every { server.flavor.memorySize } returns 1024 + + assertEquals(hostB, scheduler.select(server)) + } + + @Test + fun testVCpuFilterOvercommit() { + val scheduler = FilterScheduler( + filters = listOf(VCpuFilter(16.0)), + weighers = emptyList(), + ) + + val host = mockk<HostView>() + every { host.host.state } returns HostState.UP + every { host.host.model } returns HostModel(4, 2048) + every { host.provisionedCores } returns 0 + + scheduler.addHost(host) + + val server = mockk<Server>() + every { server.flavor.cpuCount } returns 8 + every { server.flavor.memorySize } returns 1024 + + assertNull(scheduler.select(server)) + } + + @Test + fun testInstanceCountFilter() { + val scheduler = FilterScheduler( + filters = listOf(InstanceCountFilter(limit = 2)), + weighers = emptyList(), + ) + + val hostA = mockk<HostView>() + every { hostA.host.state } returns HostState.UP + every { hostA.host.model } returns HostModel(4, 2048) + every { hostA.instanceCount } returns 2 + + val hostB = mockk<HostView>() + every { hostB.host.state } returns HostState.UP + every { hostB.host.model } returns HostModel(4, 2048) + every { hostB.instanceCount } returns 0 + + scheduler.addHost(hostA) + scheduler.addHost(hostB) + + val server = mockk<Server>() + every { server.flavor.cpuCount } returns 2 + every { server.flavor.memorySize } returns 1024 + + assertEquals(hostB, scheduler.select(server)) + } + + @Test + fun testRamWeigher() { + val scheduler = FilterScheduler( + filters = emptyList(), + weighers = listOf(RamWeigher(1.5)), + ) + + val hostA = mockk<HostView>() + every { hostA.host.state } returns HostState.UP + every { hostA.host.model } returns HostModel(4, 2048) + every { hostA.availableMemory } returns 1024 + + val hostB = mockk<HostView>() + every { hostB.host.state } returns HostState.UP + every { hostB.host.model } returns HostModel(4, 2048) + every { hostB.availableMemory } returns 512 + + scheduler.addHost(hostA) + scheduler.addHost(hostB) + + val server = mockk<Server>() + every { server.flavor.cpuCount } returns 2 + every { server.flavor.memorySize } returns 1024 + + assertEquals(hostA, scheduler.select(server)) + } + + @Test + fun testCoreRamWeigher() { + val scheduler = FilterScheduler( + filters = emptyList(), + weighers = listOf(CoreRamWeigher(1.5)), + ) + + val hostA = mockk<HostView>() + every { hostA.host.state } returns HostState.UP + every { hostA.host.model } returns HostModel(12, 2048) + every { hostA.availableMemory } returns 1024 + + val hostB = mockk<HostView>() + every { hostB.host.state } returns HostState.UP + every { hostB.host.model } returns HostModel(4, 2048) + every { hostB.availableMemory } returns 512 + + scheduler.addHost(hostA) + scheduler.addHost(hostB) + + val server = mockk<Server>() + every { server.flavor.cpuCount } returns 2 + every { server.flavor.memorySize } returns 1024 + + assertEquals(hostB, scheduler.select(server)) + } + + @Test + fun testVCpuWeigher() { + val scheduler = FilterScheduler( + filters = emptyList(), + weighers = listOf(VCpuWeigher(16.0)), + ) + + val hostA = mockk<HostView>() + every { hostA.host.state } returns HostState.UP + every { hostA.host.model } returns HostModel(4, 2048) + every { hostA.provisionedCores } returns 2 + + val hostB = mockk<HostView>() + every { hostB.host.state } returns HostState.UP + every { hostB.host.model } returns HostModel(4, 2048) + every { hostB.provisionedCores } returns 0 + + scheduler.addHost(hostA) + scheduler.addHost(hostB) + + val server = mockk<Server>() + every { server.flavor.cpuCount } returns 2 + every { server.flavor.memorySize } returns 1024 + + assertEquals(hostB, scheduler.select(server)) + } + + @Test + fun testInstanceCountWeigher() { + val scheduler = FilterScheduler( + filters = emptyList(), + weighers = listOf(InstanceCountWeigher(multiplier = -1.0)), + ) + + val hostA = mockk<HostView>() + every { hostA.host.state } returns HostState.UP + every { hostA.host.model } returns HostModel(4, 2048) + every { hostA.instanceCount } returns 2 + + val hostB = mockk<HostView>() + every { hostB.host.state } returns HostState.UP + every { hostB.host.model } returns HostModel(4, 2048) + every { hostB.instanceCount } returns 0 + + scheduler.addHost(hostA) + scheduler.addHost(hostB) + + val server = mockk<Server>() + every { server.flavor.cpuCount } returns 2 + every { server.flavor.memorySize } returns 1024 + + assertEquals(hostB, scheduler.select(server)) + } +} diff --git a/opendc-compute/opendc-compute-simulator/build.gradle.kts b/opendc-compute/opendc-compute-simulator/build.gradle.kts index b31a2114..aaf69f78 100644 --- a/opendc-compute/opendc-compute-simulator/build.gradle.kts +++ b/opendc-compute/opendc-compute-simulator/build.gradle.kts @@ -33,11 +33,13 @@ dependencies { api(platform(projects.opendcPlatform)) api(projects.opendcCompute.opendcComputeService) api(projects.opendcSimulator.opendcSimulatorCompute) - api(projects.opendcSimulator.opendcSimulatorFailures) + api(libs.commons.math3) implementation(projects.opendcUtils) + implementation(libs.opentelemetry.semconv) implementation(libs.kotlin.logging) testImplementation(projects.opendcSimulator.opendcSimulatorCore) testImplementation(projects.opendcTelemetry.opendcTelemetrySdk) + testImplementation(projects.opendcTelemetry.opendcTelemetryCompute) testRuntimeOnly(libs.slf4j.simple) } diff --git a/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimHost.kt b/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimHost.kt index 68667a8c..b9d02185 100644 --- a/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimHost.kt +++ b/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimHost.kt @@ -22,29 +22,34 @@ package org.opendc.compute.simulator +import io.opentelemetry.api.common.AttributeKey +import io.opentelemetry.api.common.Attributes import io.opentelemetry.api.metrics.Meter -import io.opentelemetry.api.metrics.common.Labels +import io.opentelemetry.api.metrics.MeterProvider +import io.opentelemetry.api.metrics.ObservableDoubleMeasurement +import io.opentelemetry.api.metrics.ObservableLongMeasurement import kotlinx.coroutines.* import mu.KotlinLogging import org.opendc.compute.api.Flavor import org.opendc.compute.api.Server import org.opendc.compute.api.ServerState import org.opendc.compute.service.driver.* +import org.opendc.compute.simulator.internal.Guest +import org.opendc.compute.simulator.internal.GuestListener import org.opendc.simulator.compute.* -import org.opendc.simulator.compute.cpufreq.PerformanceScalingGovernor -import org.opendc.simulator.compute.cpufreq.ScalingDriver -import org.opendc.simulator.compute.cpufreq.ScalingGovernor -import org.opendc.simulator.compute.cpufreq.SimpleScalingDriver -import org.opendc.simulator.compute.interference.IMAGE_PERF_INTERFERENCE_MODEL -import org.opendc.simulator.compute.interference.PerformanceInterferenceModel +import org.opendc.simulator.compute.kernel.SimHypervisor +import org.opendc.simulator.compute.kernel.SimHypervisorProvider +import org.opendc.simulator.compute.kernel.cpufreq.PerformanceScalingGovernor +import org.opendc.simulator.compute.kernel.cpufreq.ScalingGovernor +import org.opendc.simulator.compute.kernel.interference.VmInterferenceDomain +import org.opendc.simulator.compute.model.MachineModel import org.opendc.simulator.compute.model.MemoryUnit import org.opendc.simulator.compute.power.ConstantPowerModel -import org.opendc.simulator.compute.power.PowerModel -import org.opendc.simulator.failures.FailureDomain -import java.time.Clock +import org.opendc.simulator.compute.power.PowerDriver +import org.opendc.simulator.compute.power.SimplePowerDriver +import org.opendc.simulator.flow.FlowEngine import java.util.* import kotlin.coroutines.CoroutineContext -import kotlin.coroutines.resume /** * A [Host] that is simulates virtual machines on a physical machine using [SimHypervisor]. @@ -52,34 +57,27 @@ import kotlin.coroutines.resume public class SimHost( override val uid: UUID, override val name: String, - model: SimMachineModel, + model: MachineModel, override val meta: Map<String, Any>, context: CoroutineContext, - clock: Clock, - meter: Meter, - hypervisor: SimHypervisorProvider, - scalingGovernor: ScalingGovernor, - scalingDriver: ScalingDriver, + engine: FlowEngine, + meterProvider: MeterProvider, + hypervisorProvider: SimHypervisorProvider, + scalingGovernor: ScalingGovernor = PerformanceScalingGovernor(), + powerDriver: PowerDriver = SimplePowerDriver(ConstantPowerModel(0.0)), private val mapper: SimWorkloadMapper = SimMetaWorkloadMapper(), -) : Host, FailureDomain, AutoCloseable { - - public constructor( - uid: UUID, - name: String, - model: SimMachineModel, - meta: Map<String, Any>, - context: CoroutineContext, - clock: Clock, - meter: Meter, - hypervisor: SimHypervisorProvider, - powerModel: PowerModel = ConstantPowerModel(0.0), - mapper: SimWorkloadMapper = SimMetaWorkloadMapper(), - ) : this(uid, name, model, meta, context, clock, meter, hypervisor, PerformanceScalingGovernor(), SimpleScalingDriver(powerModel), mapper) - + interferenceDomain: VmInterferenceDomain? = null, + private val optimize: Boolean = false +) : Host, AutoCloseable { /** * The [CoroutineScope] of the host bounded by the lifecycle of the host. */ - override val scope: CoroutineScope = CoroutineScope(context + Job()) + private val scope: CoroutineScope = CoroutineScope(context + Job()) + + /** + * The clock instance used by the host. + */ + private val clock = engine.clock /** * The logger instance of this server. @@ -87,52 +85,31 @@ public class SimHost( private val logger = KotlinLogging.logger {} /** - * The event listeners registered with this host. + * The [Meter] to track metrics of the simulated host. */ - private val listeners = mutableListOf<HostListener>() + private val meter = meterProvider.get("org.opendc.compute.simulator") /** - * Current total memory use of the images on this hypervisor. + * The event listeners registered with this host. */ - private var availableMemory: Long = model.memory.map { it.size }.sum() + private val listeners = mutableListOf<HostListener>() /** * The machine to run on. */ - public val machine: SimBareMetalMachine = SimBareMetalMachine(context, clock, model, scalingGovernor, scalingDriver) + public val machine: SimBareMetalMachine = SimBareMetalMachine(engine, model.optimize(), powerDriver) /** * The hypervisor to run multiple workloads. */ - public val hypervisor: SimHypervisor = hypervisor.create( - scope.coroutineContext, clock, - object : SimHypervisor.Listener { - override fun onSliceFinish( - hypervisor: SimHypervisor, - requestedWork: Long, - grantedWork: Long, - overcommittedWork: Long, - interferedWork: Long, - cpuUsage: Double, - cpuDemand: Double - ) { - - _batch.put(_cpuWork, requestedWork.toDouble()) - _batch.put(_cpuWorkGranted, grantedWork.toDouble()) - _batch.put(_cpuWorkOvercommit, overcommittedWork.toDouble()) - _batch.put(_cpuWorkInterference, interferedWork.toDouble()) - _batch.put(_cpuUsage, cpuUsage) - _batch.put(_cpuDemand, cpuDemand) - _batch.put(_cpuPower, machine.powerDraw) - _batch.record() - } - } - ) + private val hypervisor: SimHypervisor = hypervisorProvider + .create(engine, scalingGovernor = scalingGovernor, interferenceDomain = interferenceDomain) /** * The virtual machines running on the hypervisor. */ private val guests = HashMap<Server, Guest>() + private val _guests = mutableListOf<Guest>() override val state: HostState get() = _state @@ -144,123 +121,99 @@ public class SimHost( field = value } - override val model: HostModel = HostModel(model.cpus.size, model.memory.map { it.size }.sum()) - - /** - * The number of guests on the host. - */ - private val _guests = meter.longUpDownCounterBuilder("guests.total") - .setDescription("Number of guests") - .setUnit("1") - .build() - .bind(Labels.of("host", uid.toString())) - - /** - * The number of active guests on the host. - */ - private val _activeGuests = meter.longUpDownCounterBuilder("guests.active") - .setDescription("Number of active guests") - .setUnit("1") - .build() - .bind(Labels.of("host", uid.toString())) - - /** - * The CPU usage on the host. - */ - private val _cpuUsage = meter.doubleValueRecorderBuilder("cpu.usage") - .setDescription("The amount of CPU resources used by the host") - .setUnit("MHz") - .build() - - /** - * The CPU demand on the host. - */ - private val _cpuDemand = meter.doubleValueRecorderBuilder("cpu.demand") - .setDescription("The amount of CPU resources the guests would use if there were no CPU contention or CPU limits") - .setUnit("MHz") - .build() - - /** - * The requested work for the CPU. - */ - private val _cpuPower = meter.doubleValueRecorderBuilder("power.usage") - .setDescription("The amount of power used by the CPU") - .setUnit("W") - .build() - - /** - * The requested work for the CPU. - */ - private val _cpuWork = meter.doubleValueRecorderBuilder("cpu.work.total") - .setDescription("The amount of work supplied to the CPU") - .setUnit("1") - .build() - - /** - * The work actually performed by the CPU. - */ - private val _cpuWorkGranted = meter.doubleValueRecorderBuilder("cpu.work.granted") - .setDescription("The amount of work performed by the CPU") - .setUnit("1") - .build() + override val model: HostModel = HostModel(model.cpus.size, model.memory.sumOf { it.size }) /** - * The work that could not be performed by the CPU due to overcommitting resource. + * The [GuestListener] that listens for guest events. */ - private val _cpuWorkOvercommit = meter.doubleValueRecorderBuilder("cpu.work.overcommit") - .setDescription("The amount of work not performed by the CPU due to overcommitment") - .setUnit("1") - .build() + private val guestListener = object : GuestListener { + override fun onStart(guest: Guest) { + listeners.forEach { it.onStateChanged(this@SimHost, guest.server, guest.state) } + } - /** - * The work that could not be performed by the CPU due to interference. - */ - private val _cpuWorkInterference = meter.doubleValueRecorderBuilder("cpu.work.interference") - .setDescription("The amount of work not performed by the CPU due to interference") - .setUnit("1") - .build() + override fun onStop(guest: Guest) { + listeners.forEach { it.onStateChanged(this@SimHost, guest.server, guest.state) } + } + } /** - * The batch recorder used to record multiple metrics atomically. + * The [Job] that represents the machine running the hypervisor. */ - private val _batch = meter.newBatchRecorder("host", uid.toString()) + private var _job: Job? = null init { - // Launch hypervisor onto machine - scope.launch { - try { - _state = HostState.UP - machine.run(this@SimHost.hypervisor, emptyMap()) - } catch (_: CancellationException) { - // Ignored - } catch (cause: Throwable) { - logger.error(cause) { "Host failed" } - throw cause - } finally { - _state = HostState.DOWN - } - } + launch() + + meter.upDownCounterBuilder("system.guests") + .setDescription("Number of guests on this host") + .setUnit("1") + .buildWithCallback(::collectGuests) + meter.gaugeBuilder("system.cpu.limit") + .setDescription("Amount of CPU resources available to the host") + .buildWithCallback(::collectCpuLimit) + meter.gaugeBuilder("system.cpu.demand") + .setDescription("Amount of CPU resources the guests would use if there were no CPU contention or CPU limits") + .setUnit("MHz") + .buildWithCallback { result -> result.observe(hypervisor.cpuDemand) } + meter.gaugeBuilder("system.cpu.usage") + .setDescription("Amount of CPU resources used by the host") + .setUnit("MHz") + .buildWithCallback { result -> result.observe(hypervisor.cpuUsage) } + meter.gaugeBuilder("system.cpu.utilization") + .setDescription("Utilization of the CPU resources of the host") + .setUnit("%") + .buildWithCallback { result -> result.observe(hypervisor.cpuUsage / _cpuLimit) } + meter.counterBuilder("system.cpu.time") + .setDescription("Amount of CPU time spent by the host") + .setUnit("s") + .buildWithCallback(::collectCpuTime) + meter.gaugeBuilder("system.power.usage") + .setDescription("Power usage of the host ") + .setUnit("W") + .buildWithCallback { result -> result.observe(machine.powerUsage) } + meter.counterBuilder("system.power.total") + .setDescription("Amount of energy used by the CPU") + .setUnit("J") + .ofDoubles() + .buildWithCallback { result -> result.observe(machine.energyUsage) } + meter.counterBuilder("system.time") + .setDescription("The uptime of the host") + .setUnit("s") + .buildWithCallback(::collectUptime) + meter.gaugeBuilder("system.time.boot") + .setDescription("The boot time of the host") + .setUnit("1") + .ofLongs() + .buildWithCallback(::collectBootTime) } override fun canFit(server: Server): Boolean { - val sufficientMemory = availableMemory > server.flavor.memorySize - val enoughCpus = machine.model.cpus.size >= server.flavor.cpuCount + val sufficientMemory = model.memorySize >= server.flavor.memorySize + val enoughCpus = model.cpuCount >= server.flavor.cpuCount val canFit = hypervisor.canFit(server.flavor.toMachineModel()) return sufficientMemory && enoughCpus && canFit } override suspend fun spawn(server: Server, start: Boolean) { - // Return if the server already exists on this host - if (server in this) { - return + val guest = guests.computeIfAbsent(server) { key -> + require(canFit(key)) { "Server does not fit" } + + val machine = hypervisor.createMachine(key.flavor.toMachineModel(), key.name) + val newGuest = Guest( + scope.coroutineContext, + clock, + this, + mapper, + guestListener, + server, + machine + ) + + _guests.add(newGuest) + newGuest } - require(canFit(server)) { "Server does not fit" } - val guest = Guest(server, hypervisor.createMachine(server.flavor.toMachineModel())) - guests[server] = guest - _guests.add(1) - if (start) { guest.start() } @@ -281,9 +234,8 @@ public class SimHost( } override suspend fun delete(server: Server) { - val guest = guests.remove(server) ?: return - guest.terminate() - _guests.add(-1) + val guest = guests[server] ?: return + guest.delete() } override fun addListener(listener: HostListener) { @@ -295,130 +247,233 @@ public class SimHost( } override fun close() { + reset() scope.cancel() machine.close() } override fun toString(): String = "SimHost[uid=$uid,name=$name,model=$model]" + public suspend fun fail() { + reset() + + for (guest in _guests) { + guest.fail() + } + } + + public suspend fun recover() { + updateUptime() + + launch() + + // Wait for the hypervisor to launch before recovering the guests + yield() + + for (guest in _guests) { + guest.recover() + } + } + + /** + * Launch the hypervisor. + */ + private fun launch() { + check(_job == null) { "Concurrent hypervisor running" } + + // Launch hypervisor onto machine + _job = scope.launch { + try { + _bootTime = clock.millis() + _state = HostState.UP + machine.run(hypervisor, emptyMap()) + } catch (_: CancellationException) { + // Ignored + } catch (cause: Throwable) { + logger.error(cause) { "Host failed" } + throw cause + } finally { + _state = HostState.DOWN + } + } + } + + /** + * Reset the machine. + */ + private fun reset() { + updateUptime() + + // Stop the hypervisor + val job = _job + if (job != null) { + job.cancel() + _job = null + } + + _state = HostState.DOWN + } + /** * Convert flavor to machine model. */ - private fun Flavor.toMachineModel(): SimMachineModel { + private fun Flavor.toMachineModel(): MachineModel { val originalCpu = machine.model.cpus[0] val processingNode = originalCpu.node.copy(coreCount = cpuCount) val processingUnits = (0 until cpuCount).map { originalCpu.copy(id = it, node = processingNode) } val memoryUnits = listOf(MemoryUnit("Generic", "Generic", 3200.0, memorySize)) - return SimMachineModel(processingUnits, memoryUnits) + return MachineModel(processingUnits, memoryUnits).optimize() } - private fun onGuestStart(vm: Guest) { - guests.forEach { (_, guest) -> - if (guest.state == ServerState.RUNNING) { - vm.performanceInterferenceModel?.onStart(vm.server.image.name) - } + /** + * Optimize the [MachineModel] for simulation. + */ + private fun MachineModel.optimize(): MachineModel { + if (!optimize) { + return this } - _activeGuests.add(1) - listeners.forEach { it.onStateChanged(this, vm.server, vm.state) } + val originalCpu = cpus[0] + val freq = cpus.sumOf { it.frequency } + val processingNode = originalCpu.node.copy(coreCount = 1) + val processingUnits = listOf(originalCpu.copy(frequency = freq, node = processingNode)) + + val memorySize = memory.sumOf { it.size } + val memoryUnits = listOf(MemoryUnit("Generic", "Generic", 3200.0, memorySize)) + + return MachineModel(processingUnits, memoryUnits) } - private fun onGuestStop(vm: Guest) { - guests.forEach { (_, guest) -> - if (guest.state == ServerState.RUNNING) { - vm.performanceInterferenceModel?.onStop(vm.server.image.name) + private val STATE_KEY = AttributeKey.stringKey("state") + + private val terminatedState = Attributes.of(STATE_KEY, "terminated") + private val runningState = Attributes.of(STATE_KEY, "running") + private val errorState = Attributes.of(STATE_KEY, "error") + private val invalidState = Attributes.of(STATE_KEY, "invalid") + + /** + * Helper function to collect the guest counts on this host. + */ + private fun collectGuests(result: ObservableLongMeasurement) { + var terminated = 0L + var running = 0L + var error = 0L + var invalid = 0L + + val guests = _guests.listIterator() + for (guest in guests) { + when (guest.state) { + ServerState.TERMINATED -> terminated++ + ServerState.RUNNING -> running++ + ServerState.ERROR -> error++ + ServerState.DELETED -> { + // Remove guests that have been deleted + this.guests.remove(guest.server) + guests.remove() + } + else -> invalid++ } } - _activeGuests.add(-1) - listeners.forEach { it.onStateChanged(this, vm.server, vm.state) } + result.observe(terminated, terminatedState) + result.observe(running, runningState) + result.observe(error, errorState) + result.observe(invalid, invalidState) } - override suspend fun fail() { - _state = HostState.DOWN - } + private val _cpuLimit = machine.model.cpus.sumOf { it.frequency } + + /** + * Helper function to collect the CPU limits of a machine. + */ + private fun collectCpuLimit(result: ObservableDoubleMeasurement) { + result.observe(_cpuLimit) - override suspend fun recover() { - _state = HostState.UP + val guests = _guests + for (i in guests.indices) { + guests[i].collectCpuLimit(result) + } } + private val _activeState = Attributes.of(STATE_KEY, "active") + private val _stealState = Attributes.of(STATE_KEY, "steal") + private val _lostState = Attributes.of(STATE_KEY, "lost") + private val _idleState = Attributes.of(STATE_KEY, "idle") + /** - * A virtual machine instance that the driver manages. + * Helper function to track the CPU time of a machine. */ - private inner class Guest(val server: Server, val machine: SimMachine) { - val performanceInterferenceModel: PerformanceInterferenceModel? = server.meta[IMAGE_PERF_INTERFERENCE_MODEL] as? PerformanceInterferenceModel? + private fun collectCpuTime(result: ObservableLongMeasurement) { + val counters = hypervisor.counters - var state: ServerState = ServerState.TERMINATED + result.observe(counters.cpuActiveTime / 1000L, _activeState) + result.observe(counters.cpuIdleTime / 1000L, _idleState) + result.observe(counters.cpuStealTime / 1000L, _stealState) + result.observe(counters.cpuLostTime / 1000L, _lostState) - suspend fun start() { - when (state) { - ServerState.TERMINATED -> { - logger.info { "User requested to start server ${server.uid}" } - launch() - } - ServerState.RUNNING -> return - ServerState.DELETED -> { - logger.warn { "User tried to start terminated server" } - throw IllegalArgumentException("Server is terminated") - } - else -> assert(false) { "Invalid state transition" } - } + val guests = _guests + for (i in guests.indices) { + guests[i].collectCpuTime(result) } + } - suspend fun stop() { - when (state) { - ServerState.RUNNING, ServerState.ERROR -> { - val job = job ?: throw IllegalStateException("Server should be active") - job.cancel() - job.join() - } - ServerState.TERMINATED, ServerState.DELETED -> return - else -> assert(false) { "Invalid state transition" } - } - } + private var _lastReport = clock.millis() - suspend fun terminate() { - stop() - state = ServerState.DELETED + /** + * Helper function to track the uptime of a machine. + */ + private fun updateUptime() { + val now = clock.millis() + val duration = now - _lastReport + _lastReport = now + + if (_state == HostState.UP) { + _uptime += duration + } else if (_state == HostState.DOWN && scope.isActive) { + // Only increment downtime if the machine is in a failure state + _downtime += duration } - private var job: Job? = null - - private suspend fun launch() = suspendCancellableCoroutine<Unit> { cont -> - assert(job == null) { "Concurrent job running" } - val workload = mapper.createWorkload(server) - - job = scope.launch { - delay(1) // TODO Introduce boot time - init() - cont.resume(Unit) - try { - machine.run(workload, mapOf("driver" to this@SimHost, "server" to server)) - exit(null) - } catch (cause: Throwable) { - exit(cause) - } finally { - machine.close() - job = null - } - } + val guests = _guests + for (i in guests.indices) { + guests[i].updateUptime(duration) } + } + + private var _uptime = 0L + private var _downtime = 0L + private val _upState = Attributes.of(STATE_KEY, "up") + private val _downState = Attributes.of(STATE_KEY, "down") - private fun init() { - state = ServerState.RUNNING - onGuestStart(this) + /** + * Helper function to track the uptime of a machine. + */ + private fun collectUptime(result: ObservableLongMeasurement) { + updateUptime() + + result.observe(_uptime, _upState) + result.observe(_downtime, _downState) + + val guests = _guests + for (i in guests.indices) { + guests[i].collectUptime(result) } + } + + private var _bootTime = Long.MIN_VALUE - private fun exit(cause: Throwable?) { - state = - if (cause == null) - ServerState.TERMINATED - else - ServerState.ERROR + /** + * Helper function to track the boot time of a machine. + */ + private fun collectBootTime(result: ObservableLongMeasurement) { + if (_bootTime != Long.MIN_VALUE) { + result.observe(_bootTime) + } - availableMemory += server.flavor.memorySize - onGuestStop(this) + val guests = _guests + for (i in guests.indices) { + guests[i].collectBootTime(result) } } } diff --git a/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/failure/HostFault.kt b/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/failure/HostFault.kt new file mode 100644 index 00000000..258ccc89 --- /dev/null +++ b/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/failure/HostFault.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.simulator.failure + +import org.opendc.compute.simulator.SimHost +import java.time.Clock + +/** + * Interface responsible for applying the fault to a host. + */ +public interface HostFault { + /** + * Apply the fault to the specified [victims]. + */ + public suspend fun apply(clock: Clock, victims: List<SimHost>) +} diff --git a/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/failure/HostFaultInjector.kt b/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/failure/HostFaultInjector.kt new file mode 100644 index 00000000..5eff439f --- /dev/null +++ b/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/failure/HostFaultInjector.kt @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2020 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.simulator.failure + +import org.apache.commons.math3.distribution.RealDistribution +import org.opendc.compute.simulator.SimHost +import org.opendc.compute.simulator.internal.HostFaultInjectorImpl +import java.time.Clock +import kotlin.coroutines.CoroutineContext + +/** + * An interface for stochastically injecting faults into a set of hosts. + */ +public interface HostFaultInjector : AutoCloseable { + /** + * Start fault injection. + */ + public fun start() + + /** + * Stop fault injection into the system. + */ + public override fun close() + + public companion object { + /** + * Construct a new [HostFaultInjector]. + * + * @param context The scope to run the fault injector in. + * @param clock The [Clock] to keep track of simulation time. + * @param hosts The hosts to inject the faults into. + * @param iat The inter-arrival time distribution of the failures (in hours). + * @param selector The [VictimSelector] to select the host victims. + * @param fault The type of [HostFault] to inject. + */ + public operator fun invoke( + context: CoroutineContext, + clock: Clock, + hosts: Set<SimHost>, + iat: RealDistribution, + selector: VictimSelector, + fault: HostFault + ): HostFaultInjector = HostFaultInjectorImpl(context, clock, hosts, iat, selector, fault) + } +} diff --git a/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/failure/StartStopHostFault.kt b/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/failure/StartStopHostFault.kt new file mode 100644 index 00000000..fc7cebfc --- /dev/null +++ b/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/failure/StartStopHostFault.kt @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.simulator.failure + +import kotlinx.coroutines.delay +import org.apache.commons.math3.distribution.RealDistribution +import org.opendc.compute.simulator.SimHost +import java.time.Clock +import kotlin.math.roundToLong + +/** + * A type of [HostFault] where the hosts are stopped and recover after some random amount of time. + */ +public class StartStopHostFault(private val duration: RealDistribution) : HostFault { + override suspend fun apply(clock: Clock, victims: List<SimHost>) { + for (host in victims) { + host.fail() + } + + val df = (duration.sample() * 1000).roundToLong() // seconds to milliseconds + + // Handle long overflow + if (clock.millis() + df <= 0) { + return + } + + delay(df) + + for (host in victims) { + host.recover() + } + } + + override fun toString(): String = "StartStopHostFault[$duration]" +} diff --git a/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/failure/StochasticVictimSelector.kt b/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/failure/StochasticVictimSelector.kt new file mode 100644 index 00000000..fcd9dd7e --- /dev/null +++ b/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/failure/StochasticVictimSelector.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.simulator.failure + +import org.apache.commons.math3.distribution.RealDistribution +import org.opendc.compute.simulator.SimHost +import java.util.* +import kotlin.math.roundToInt + +/** + * A [VictimSelector] that stochastically selects a set of hosts to be failed. + */ +public class StochasticVictimSelector( + private val size: RealDistribution, + private val random: Random = Random(0) +) : VictimSelector { + + override fun select(hosts: Set<SimHost>): List<SimHost> { + val n = size.sample().roundToInt() + return hosts.shuffled(random).take(n) + } + + override fun toString(): String = "StochasticVictimSelector[$size]" +} diff --git a/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/failure/VictimSelector.kt b/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/failure/VictimSelector.kt new file mode 100644 index 00000000..b5610284 --- /dev/null +++ b/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/failure/VictimSelector.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.simulator.failure + +import org.opendc.compute.simulator.SimHost + +/** + * Interface responsible for selecting the victim(s) for fault injection. + */ +public interface VictimSelector { + /** + * Select the hosts from [hosts] where a fault will be injected. + */ + public fun select(hosts: Set<SimHost>): List<SimHost> +} diff --git a/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/internal/Guest.kt b/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/internal/Guest.kt new file mode 100644 index 00000000..5ea1860d --- /dev/null +++ b/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/internal/Guest.kt @@ -0,0 +1,350 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.simulator.internal + +import io.opentelemetry.api.common.AttributeKey +import io.opentelemetry.api.common.Attributes +import io.opentelemetry.api.common.AttributesBuilder +import io.opentelemetry.api.metrics.ObservableDoubleMeasurement +import io.opentelemetry.api.metrics.ObservableLongMeasurement +import io.opentelemetry.semconv.resource.attributes.ResourceAttributes +import kotlinx.coroutines.* +import mu.KotlinLogging +import org.opendc.compute.api.Server +import org.opendc.compute.api.ServerState +import org.opendc.compute.simulator.SimHost +import org.opendc.compute.simulator.SimWorkloadMapper +import org.opendc.simulator.compute.kernel.SimVirtualMachine +import org.opendc.simulator.compute.workload.SimWorkload +import java.time.Clock +import kotlin.coroutines.CoroutineContext + +/** + * A virtual machine instance that is managed by a [SimHost]. + */ +internal class Guest( + context: CoroutineContext, + private val clock: Clock, + val host: SimHost, + private val mapper: SimWorkloadMapper, + private val listener: GuestListener, + val server: Server, + val machine: SimVirtualMachine +) { + /** + * The [CoroutineScope] of the guest. + */ + private val scope: CoroutineScope = CoroutineScope(context + Job()) + + /** + * The logger instance of this guest. + */ + private val logger = KotlinLogging.logger {} + + /** + * The state of the [Guest]. + * + * [ServerState.PROVISIONING] is an invalid value for a guest, since it applies before the host is selected for + * a server. + */ + var state: ServerState = ServerState.TERMINATED + + /** + * The attributes of the guest. + */ + val attributes: Attributes = GuestAttributes(this) + + /** + * Start the guest. + */ + suspend fun start() { + when (state) { + ServerState.TERMINATED, ServerState.ERROR -> { + logger.info { "User requested to start server ${server.uid}" } + doStart() + } + ServerState.RUNNING -> return + ServerState.DELETED -> { + logger.warn { "User tried to start deleted server" } + throw IllegalArgumentException("Server is deleted") + } + else -> assert(false) { "Invalid state transition" } + } + } + + /** + * Stop the guest. + */ + suspend fun stop() { + when (state) { + ServerState.RUNNING -> doStop(ServerState.TERMINATED) + ServerState.ERROR -> doRecover() + ServerState.TERMINATED, ServerState.DELETED -> return + else -> assert(false) { "Invalid state transition" } + } + } + + /** + * Delete the guest. + * + * This operation will stop the guest if it is running on the host and remove all resources associated with the + * guest. + */ + suspend fun delete() { + stop() + + state = ServerState.DELETED + + machine.close() + scope.cancel() + } + + /** + * Fail the guest if it is active. + * + * This operation forcibly stops the guest and puts the server into an error state. + */ + suspend fun fail() { + if (state != ServerState.RUNNING) { + return + } + + doStop(ServerState.ERROR) + } + + /** + * Recover the guest if it is in an error state. + */ + suspend fun recover() { + if (state != ServerState.ERROR) { + return + } + + doStart() + } + + /** + * The [Job] representing the current active virtual machine instance or `null` if no virtual machine is active. + */ + private var job: Job? = null + + /** + * Launch the guest on the simulated + */ + private suspend fun doStart() { + assert(job == null) { "Concurrent job running" } + val workload = mapper.createWorkload(server) + + val job = scope.launch { runMachine(workload) } + this.job = job + + state = ServerState.RUNNING + onStart() + + job.invokeOnCompletion { cause -> + this.job = null + onStop(if (cause != null && cause !is CancellationException) ServerState.ERROR else ServerState.TERMINATED) + } + } + + /** + * Attempt to stop the server and put it into [target] state. + */ + private suspend fun doStop(target: ServerState) { + assert(job != null) { "Invalid job state" } + val job = job ?: return + job.cancel() + job.join() + + state = target + } + + /** + * Attempt to recover from an error state. + */ + private fun doRecover() { + state = ServerState.TERMINATED + } + + /** + * Converge the process that models the virtual machine lifecycle as a coroutine. + */ + private suspend fun runMachine(workload: SimWorkload) { + delay(1) // TODO Introduce model for boot time + machine.run(workload, mapOf("driver" to host, "server" to server)) + } + + /** + * This method is invoked when the guest was started on the host and has booted into a running state. + */ + private fun onStart() { + _bootTime = clock.millis() + state = ServerState.RUNNING + listener.onStart(this) + } + + /** + * This method is invoked when the guest stopped. + */ + private fun onStop(target: ServerState) { + state = target + listener.onStop(this) + } + + private val STATE_KEY = AttributeKey.stringKey("state") + + private var _uptime = 0L + private var _downtime = 0L + private val _upState = attributes.toBuilder() + .put(STATE_KEY, "up") + .build() + private val _downState = attributes.toBuilder() + .put(STATE_KEY, "down") + .build() + + /** + * Helper function to track the uptime and downtime of the guest. + */ + fun updateUptime(duration: Long) { + if (state == ServerState.RUNNING) { + _uptime += duration + } else if (state == ServerState.ERROR) { + _downtime += duration + } + } + + /** + * Helper function to track the uptime of the guest. + */ + fun collectUptime(result: ObservableLongMeasurement) { + result.observe(_uptime, _upState) + result.observe(_downtime, _downState) + } + + private var _bootTime = Long.MIN_VALUE + + /** + * Helper function to track the boot time of the guest. + */ + fun collectBootTime(result: ObservableLongMeasurement) { + if (_bootTime != Long.MIN_VALUE) { + result.observe(_bootTime) + } + } + + private val _activeState = attributes.toBuilder() + .put(STATE_KEY, "active") + .build() + private val _stealState = attributes.toBuilder() + .put(STATE_KEY, "steal") + .build() + private val _lostState = attributes.toBuilder() + .put(STATE_KEY, "lost") + .build() + private val _idleState = attributes.toBuilder() + .put(STATE_KEY, "idle") + .build() + + /** + * Helper function to track the CPU time of a machine. + */ + fun collectCpuTime(result: ObservableLongMeasurement) { + val counters = machine.counters + + result.observe(counters.cpuActiveTime / 1000, _activeState) + result.observe(counters.cpuIdleTime / 1000, _idleState) + result.observe(counters.cpuStealTime / 1000, _stealState) + result.observe(counters.cpuLostTime / 1000, _lostState) + } + + private val _cpuLimit = machine.model.cpus.sumOf { it.frequency } + + /** + * Helper function to collect the CPU limits of a machine. + */ + fun collectCpuLimit(result: ObservableDoubleMeasurement) { + result.observe(_cpuLimit, attributes) + } + + /** + * An optimized [Attributes] implementation. + */ + private class GuestAttributes(private val uid: String, private val attributes: Attributes) : Attributes by attributes { + /** + * Construct a [GuestAttributes] instance from a [Guest]. + */ + constructor(guest: Guest) : this( + guest.server.uid.toString(), + Attributes.builder() + .put(ResourceAttributes.HOST_NAME, guest.server.name) + .put(ResourceAttributes.HOST_ID, guest.server.uid.toString()) + .put(ResourceAttributes.HOST_TYPE, guest.server.flavor.name) + .put(AttributeKey.longKey("host.num_cpus"), guest.server.flavor.cpuCount.toLong()) + .put(AttributeKey.longKey("host.mem_capacity"), guest.server.flavor.memorySize) + .put(AttributeKey.stringArrayKey("host.labels"), guest.server.labels.map { (k, v) -> "$k:$v" }) + .put(ResourceAttributes.HOST_ARCH, ResourceAttributes.HostArchValues.AMD64) + .put(ResourceAttributes.HOST_IMAGE_NAME, guest.server.image.name) + .put(ResourceAttributes.HOST_IMAGE_ID, guest.server.image.uid.toString()) + .build() + ) + + override fun <T : Any?> get(key: AttributeKey<T>): T? { + // Optimize access to the HOST_ID key which is accessed quite often + if (key == ResourceAttributes.HOST_ID) { + @Suppress("UNCHECKED_CAST") + return uid as T? + } + return attributes.get(key) + } + + override fun toBuilder(): AttributesBuilder { + val delegate = attributes.toBuilder() + return object : AttributesBuilder { + + override fun putAll(attributes: Attributes): AttributesBuilder { + delegate.putAll(attributes) + return this + } + + override fun <T : Any?> put(key: AttributeKey<Long>, value: Int): AttributesBuilder { + delegate.put<T>(key, value) + return this + } + + override fun <T : Any?> put(key: AttributeKey<T>, value: T): AttributesBuilder { + delegate.put(key, value) + return this + } + + override fun build(): Attributes = GuestAttributes(uid, delegate.build()) + } + } + + override fun equals(other: Any?): Boolean = attributes == other + + // Cache hash code + private val _hash = attributes.hashCode() + + override fun hashCode(): Int = _hash + } +} diff --git a/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/internal/GuestListener.kt b/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/internal/GuestListener.kt new file mode 100644 index 00000000..e6d0fdad --- /dev/null +++ b/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/internal/GuestListener.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.simulator.internal + +/** + * Helper interface to listen for [Guest] events. + */ +internal interface GuestListener { + /** + * This method is invoked when the guest machine is running. + */ + fun onStart(guest: Guest) + + /** + * This method is invoked when the guest machine is stopped. + */ + fun onStop(guest: Guest) +} diff --git a/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/internal/HostFaultInjectorImpl.kt b/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/internal/HostFaultInjectorImpl.kt new file mode 100644 index 00000000..7d46e626 --- /dev/null +++ b/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/internal/HostFaultInjectorImpl.kt @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.simulator.internal + +import kotlinx.coroutines.* +import org.apache.commons.math3.distribution.RealDistribution +import org.opendc.compute.simulator.SimHost +import org.opendc.compute.simulator.failure.HostFault +import org.opendc.compute.simulator.failure.HostFaultInjector +import org.opendc.compute.simulator.failure.VictimSelector +import java.time.Clock +import kotlin.coroutines.CoroutineContext +import kotlin.math.roundToLong + +/** + * Internal implementation of the [HostFaultInjector] interface. + * + * @param context The scope to run the fault injector in. + * @param clock The [Clock] to keep track of simulation time. + * @param hosts The set of hosts to inject faults into. + * @param iat The inter-arrival time distribution of the failures (in hours). + * @param selector The [VictimSelector] to select the host victims. + * @param fault The type of [HostFault] to inject. + */ +internal class HostFaultInjectorImpl( + private val context: CoroutineContext, + private val clock: Clock, + private val hosts: Set<SimHost>, + private val iat: RealDistribution, + private val selector: VictimSelector, + private val fault: HostFault +) : HostFaultInjector { + /** + * The scope in which the injector runs. + */ + private val scope = CoroutineScope(context + Job()) + + /** + * The [Job] that awaits the nearest fault in the system. + */ + private var job: Job? = null + + /** + * Start the fault injection into the system. + */ + override fun start() { + if (job != null) { + return + } + + job = scope.launch { + runInjector() + job = null + } + } + + /** + * Converge the injection process. + */ + private suspend fun runInjector() { + while (true) { + // Make sure to convert delay from hours to milliseconds + val d = (iat.sample() * 3.6e6).roundToLong() + + // Handle long overflow + if (clock.millis() + d <= 0) { + return + } + + delay(d) + + val victims = selector.select(hosts) + fault.apply(clock, victims) + } + } + + /** + * Stop the fault injector. + */ + public override fun close() { + scope.cancel() + } +} diff --git a/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/SimHostTest.kt b/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/SimHostTest.kt index 5594fd59..a0ff9228 100644 --- a/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/SimHostTest.kt +++ b/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/SimHostTest.kt @@ -23,47 +23,48 @@ package org.opendc.compute.simulator import io.opentelemetry.api.metrics.MeterProvider -import io.opentelemetry.sdk.common.CompletableResultCode import io.opentelemetry.sdk.metrics.SdkMeterProvider -import io.opentelemetry.sdk.metrics.data.MetricData -import io.opentelemetry.sdk.metrics.export.MetricExporter import io.opentelemetry.sdk.metrics.export.MetricProducer +import io.opentelemetry.sdk.resources.Resource import kotlinx.coroutines.* import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertAll -import org.opendc.compute.api.Flavor -import org.opendc.compute.api.Image -import org.opendc.compute.api.Server -import org.opendc.compute.api.ServerState -import org.opendc.compute.api.ServerWatcher +import org.opendc.compute.api.* import org.opendc.compute.service.driver.Host import org.opendc.compute.service.driver.HostListener -import org.opendc.simulator.compute.SimFairShareHypervisorProvider -import org.opendc.simulator.compute.SimMachineModel +import org.opendc.simulator.compute.kernel.SimFairShareHypervisorProvider +import org.opendc.simulator.compute.model.MachineModel import org.opendc.simulator.compute.model.MemoryUnit import org.opendc.simulator.compute.model.ProcessingNode import org.opendc.simulator.compute.model.ProcessingUnit +import org.opendc.simulator.compute.workload.SimTrace +import org.opendc.simulator.compute.workload.SimTraceFragment import org.opendc.simulator.compute.workload.SimTraceWorkload import org.opendc.simulator.core.runBlockingSimulation +import org.opendc.simulator.flow.FlowEngine +import org.opendc.telemetry.compute.ComputeMetricExporter +import org.opendc.telemetry.compute.HOST_ID +import org.opendc.telemetry.compute.table.HostData +import org.opendc.telemetry.compute.table.ServerData import org.opendc.telemetry.sdk.metrics.export.CoroutineMetricReader import org.opendc.telemetry.sdk.toOtelClock -import java.util.UUID +import java.time.Duration +import java.util.* import kotlin.coroutines.resume /** * Basic test-suite for the hypervisor. */ -@OptIn(ExperimentalCoroutinesApi::class) internal class SimHostTest { - private lateinit var machineModel: SimMachineModel + private lateinit var machineModel: MachineModel @BeforeEach fun setUp() { val cpuNode = ProcessingNode("Intel", "Xeon", "amd64", 2) - machineModel = SimMachineModel( + machineModel = MachineModel( cpus = List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 3200.0) }, memory = List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) } ) @@ -74,16 +75,31 @@ internal class SimHostTest { */ @Test fun testOvercommitted() = runBlockingSimulation { - var requestedWork = 0L - var grantedWork = 0L - var overcommittedWork = 0L + var idleTime = 0L + var activeTime = 0L + var stealTime = 0L + val hostId = UUID.randomUUID() + val hostResource = Resource.builder() + .put(HOST_ID, hostId.toString()) + .build() val meterProvider: MeterProvider = SdkMeterProvider .builder() + .setResource(hostResource) .setClock(clock.toOtelClock()) .build() - val virtDriver = SimHost(UUID.randomUUID(), "test", machineModel, emptyMap(), coroutineContext, clock, meterProvider.get("opendc-compute-simulator"), SimFairShareHypervisorProvider()) + val engine = FlowEngine(coroutineContext, clock) + val virtDriver = SimHost( + uid = hostId, + name = "test", + model = machineModel, + meta = emptyMap(), + coroutineContext, + engine, + meterProvider, + SimFairShareHypervisorProvider() + ) val duration = 5 * 60L val vmImageA = MockImage( UUID.randomUUID(), @@ -91,12 +107,13 @@ internal class SimHostTest { emptyMap(), mapOf( "workload" to SimTraceWorkload( - sequenceOf( - SimTraceWorkload.Fragment(duration * 1000, 2 * 28.0, 2), - SimTraceWorkload.Fragment(duration * 1000, 2 * 3500.0, 2), - SimTraceWorkload.Fragment(duration * 1000, 0.0, 2), - SimTraceWorkload.Fragment(duration * 1000, 2 * 183.0, 2) + SimTrace.ofFragments( + SimTraceFragment(0, duration * 1000, 2 * 28.0, 2), + SimTraceFragment(duration * 1000, duration * 1000, 2 * 3500.0, 2), + SimTraceFragment(duration * 2000, duration * 1000, 0.0, 2), + SimTraceFragment(duration * 3000, duration * 1000, 2 * 183.0, 2) ), + offset = 1 ) ) ) @@ -106,12 +123,13 @@ internal class SimHostTest { emptyMap(), mapOf( "workload" to SimTraceWorkload( - sequenceOf( - SimTraceWorkload.Fragment(duration * 1000, 2 * 28.0, 2), - SimTraceWorkload.Fragment(duration * 1000, 2 * 3100.0, 2), - SimTraceWorkload.Fragment(duration * 1000, 0.0, 2), - SimTraceWorkload.Fragment(duration * 1000, 2 * 73.0, 2) - ) + SimTrace.ofFragments( + SimTraceFragment(0, duration * 1000, 2 * 28.0, 2), + SimTraceFragment(duration * 1000, duration * 1000, 2 * 3100.0, 2), + SimTraceFragment(duration * 2000, duration * 1000, 0.0, 2), + SimTraceFragment(duration * 3000, duration * 1000, 2 * 73.0, 2) + ), + offset = 1 ) ) ) @@ -121,20 +139,14 @@ internal class SimHostTest { // Setup metric reader val reader = CoroutineMetricReader( this, listOf(meterProvider as MetricProducer), - object : MetricExporter { - override fun export(metrics: Collection<MetricData>): CompletableResultCode { - val metricsByName = metrics.associateBy { it.name } - requestedWork += metricsByName.getValue("cpu.work.total").doubleSummaryData.points.first().sum.toLong() - grantedWork += metricsByName.getValue("cpu.work.granted").doubleSummaryData.points.first().sum.toLong() - overcommittedWork += metricsByName.getValue("cpu.work.overcommit").doubleSummaryData.points.first().sum.toLong() - return CompletableResultCode.ofSuccess() + object : ComputeMetricExporter() { + override fun record(data: HostData) { + activeTime += data.cpuActiveTime + idleTime += data.cpuIdleTime + stealTime += data.cpuStealTime } - - override fun flush(): CompletableResultCode = CompletableResultCode.ofSuccess() - - override fun shutdown(): CompletableResultCode = CompletableResultCode.ofSuccess() }, - exportInterval = duration * 1000 + exportInterval = Duration.ofSeconds(duration) ) coroutineScope { @@ -155,18 +167,124 @@ internal class SimHostTest { } // Ensure last cycle is collected - delay(1000 * duration) + delay(1000L * duration) virtDriver.close() reader.close() assertAll( - { assertEquals(4197600, requestedWork, "Requested work does not match") }, - { assertEquals(2157600, grantedWork, "Granted work does not match") }, - { assertEquals(2040000, overcommittedWork, "Overcommitted work does not match") }, + { assertEquals(658, activeTime, "Active time does not match") }, + { assertEquals(1741, idleTime, "Idle time does not match") }, + { assertEquals(637, stealTime, "Steal time does not match") }, { assertEquals(1500001, clock.millis()) } ) } + /** + * Test failure of the host. + */ + @Test + fun testFailure() = runBlockingSimulation { + var activeTime = 0L + var idleTime = 0L + var uptime = 0L + var downtime = 0L + var guestUptime = 0L + var guestDowntime = 0L + + val hostId = UUID.randomUUID() + val hostResource = Resource.builder() + .put(HOST_ID, hostId.toString()) + .build() + val meterProvider: MeterProvider = SdkMeterProvider + .builder() + .setResource(hostResource) + .setClock(clock.toOtelClock()) + .build() + + val engine = FlowEngine(coroutineContext, clock) + val host = SimHost( + uid = hostId, + name = "test", + model = machineModel, + meta = emptyMap(), + coroutineContext, + engine, + meterProvider, + SimFairShareHypervisorProvider() + ) + val duration = 5 * 60L + val image = MockImage( + UUID.randomUUID(), + "<unnamed>", + emptyMap(), + mapOf( + "workload" to SimTraceWorkload( + SimTrace.ofFragments( + SimTraceFragment(0, duration * 1000, 2 * 28.0, 2), + SimTraceFragment(duration * 1000L, duration * 1000, 2 * 3500.0, 2), + SimTraceFragment(duration * 2000L, duration * 1000, 0.0, 2), + SimTraceFragment(duration * 3000L, duration * 1000, 2 * 183.0, 2) + ), + offset = 1 + ) + ) + ) + val flavor = MockFlavor(2, 0) + val server = MockServer(UUID.randomUUID(), "a", flavor, image) + + // Setup metric reader + val reader = CoroutineMetricReader( + this, listOf(meterProvider as MetricProducer), + object : ComputeMetricExporter() { + override fun record(data: HostData) { + activeTime += data.cpuActiveTime + idleTime += data.cpuIdleTime + uptime += data.uptime + downtime += data.downtime + } + + override fun record(data: ServerData) { + guestUptime += data.uptime + guestDowntime += data.downtime + } + }, + exportInterval = Duration.ofSeconds(duration) + ) + + coroutineScope { + host.spawn(server) + delay(5000L) + host.fail() + delay(duration * 1000) + host.recover() + + suspendCancellableCoroutine<Unit> { cont -> + host.addListener(object : HostListener { + override fun onStateChanged(host: Host, server: Server, newState: ServerState) { + if (newState == ServerState.TERMINATED) { + cont.resume(Unit) + } + } + }) + } + } + + host.close() + // Ensure last cycle is collected + delay(1000L * duration) + + reader.close() + + assertAll( + { assertEquals(1175, idleTime, "Idle time does not match") }, + { assertEquals(624, activeTime, "Active time does not match") }, + { assertEquals(900001, uptime, "Uptime does not match") }, + { assertEquals(300000, downtime, "Downtime does not match") }, + { assertEquals(900000, guestUptime, "Guest uptime does not match") }, + { assertEquals(300000, guestDowntime, "Guest downtime does not match") }, + ) + } + private class MockFlavor( override val cpuCount: Int, override val memorySize: Long diff --git a/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/failure/HostFaultInjectorTest.kt b/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/failure/HostFaultInjectorTest.kt new file mode 100644 index 00000000..f240a25f --- /dev/null +++ b/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/failure/HostFaultInjectorTest.kt @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.simulator.failure + +import io.mockk.coVerify +import io.mockk.mockk +import kotlinx.coroutines.delay +import org.apache.commons.math3.distribution.LogNormalDistribution +import org.apache.commons.math3.random.Well19937c +import org.junit.jupiter.api.Test +import org.opendc.compute.simulator.SimHost +import org.opendc.simulator.core.runBlockingSimulation +import java.time.Clock +import java.time.Duration +import kotlin.coroutines.CoroutineContext +import kotlin.math.ln + +/** + * Test suite for [HostFaultInjector] class. + */ +internal class HostFaultInjectorTest { + /** + * Simple test case to test that nothing happens when the injector is not started. + */ + @Test + fun testInjectorNotStarted() = runBlockingSimulation { + val host = mockk<SimHost>(relaxUnitFun = true) + + val injector = createSimpleInjector(coroutineContext, clock, setOf(host)) + + coVerify(exactly = 0) { host.fail() } + coVerify(exactly = 0) { host.recover() } + + injector.close() + } + + /** + * Simple test case to test a start stop fault where the machine is stopped and started after some time. + */ + @Test + fun testInjectorStopsMachine() = runBlockingSimulation { + val host = mockk<SimHost>(relaxUnitFun = true) + + val injector = createSimpleInjector(coroutineContext, clock, setOf(host)) + + injector.start() + + delay(Duration.ofDays(55).toMillis()) + + injector.close() + + coVerify(exactly = 1) { host.fail() } + coVerify(exactly = 1) { host.recover() } + } + + /** + * Simple test case to test a start stop fault where multiple machines are stopped. + */ + @Test + fun testInjectorStopsMultipleMachines() = runBlockingSimulation { + val hosts = listOf<SimHost>( + mockk(relaxUnitFun = true), + mockk(relaxUnitFun = true) + ) + + val injector = createSimpleInjector(coroutineContext, clock, hosts.toSet()) + + injector.start() + + delay(Duration.ofDays(55).toMillis()) + + injector.close() + + coVerify(exactly = 1) { hosts[0].fail() } + coVerify(exactly = 1) { hosts[1].fail() } + coVerify(exactly = 1) { hosts[0].recover() } + coVerify(exactly = 1) { hosts[1].recover() } + } + + /** + * Create a simple start stop fault injector. + */ + private fun createSimpleInjector(context: CoroutineContext, clock: Clock, hosts: Set<SimHost>): HostFaultInjector { + val rng = Well19937c(0) + val iat = LogNormalDistribution(rng, ln(24 * 7.0), 1.03) + val selector = StochasticVictimSelector(LogNormalDistribution(rng, 1.88, 1.25)) + val fault = StartStopHostFault(LogNormalDistribution(rng, 8.89, 2.71)) + + return HostFaultInjector(context, clock, hosts, iat, selector, fault) + } +} diff --git a/opendc-compute/opendc-compute-workload/build.gradle.kts b/opendc-compute/opendc-compute-workload/build.gradle.kts new file mode 100644 index 00000000..28a5e1da --- /dev/null +++ b/opendc-compute/opendc-compute-workload/build.gradle.kts @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +description = "Support library for simulating VM-based workloads with OpenDC" + +/* Build configuration */ +plugins { + `kotlin-library-conventions` + `testing-conventions` +} + +dependencies { + api(platform(projects.opendcPlatform)) + api(projects.opendcCompute.opendcComputeSimulator) + + implementation(projects.opendcTrace.opendcTraceApi) + implementation(projects.opendcTrace.opendcTraceParquet) + implementation(projects.opendcSimulator.opendcSimulatorCore) + implementation(projects.opendcSimulator.opendcSimulatorCompute) + implementation(projects.opendcTelemetry.opendcTelemetrySdk) + implementation(projects.opendcTelemetry.opendcTelemetryCompute) + implementation(libs.opentelemetry.semconv) + + implementation(libs.kotlin.logging) + implementation(libs.jackson.databind) + implementation(libs.jackson.module.kotlin) + implementation(kotlin("reflect")) +} diff --git a/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/ComputeSchedulers.kt b/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/ComputeSchedulers.kt new file mode 100644 index 00000000..c94f30e4 --- /dev/null +++ b/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/ComputeSchedulers.kt @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +@file:JvmName("ComputeSchedulers") +package org.opendc.compute.workload + +import org.opendc.compute.service.scheduler.ComputeScheduler +import org.opendc.compute.service.scheduler.FilterScheduler +import org.opendc.compute.service.scheduler.ReplayScheduler +import org.opendc.compute.service.scheduler.filters.ComputeFilter +import org.opendc.compute.service.scheduler.filters.RamFilter +import org.opendc.compute.service.scheduler.filters.VCpuFilter +import org.opendc.compute.service.scheduler.weights.CoreRamWeigher +import org.opendc.compute.service.scheduler.weights.InstanceCountWeigher +import org.opendc.compute.service.scheduler.weights.RamWeigher +import org.opendc.compute.service.scheduler.weights.VCpuWeigher +import java.util.* + +/** + * Create a [ComputeScheduler] for the experiment. + */ +public fun createComputeScheduler(name: String, seeder: Random, placements: Map<String, String> = emptyMap()): ComputeScheduler { + val cpuAllocationRatio = 16.0 + val ramAllocationRatio = 1.5 + return when (name) { + "mem" -> FilterScheduler( + filters = listOf(ComputeFilter(), VCpuFilter(cpuAllocationRatio), RamFilter(ramAllocationRatio)), + weighers = listOf(RamWeigher(multiplier = 1.0)) + ) + "mem-inv" -> FilterScheduler( + filters = listOf(ComputeFilter(), VCpuFilter(cpuAllocationRatio), RamFilter(ramAllocationRatio)), + weighers = listOf(RamWeigher(multiplier = -1.0)) + ) + "core-mem" -> FilterScheduler( + filters = listOf(ComputeFilter(), VCpuFilter(cpuAllocationRatio), RamFilter(ramAllocationRatio)), + weighers = listOf(CoreRamWeigher(multiplier = 1.0)) + ) + "core-mem-inv" -> FilterScheduler( + filters = listOf(ComputeFilter(), VCpuFilter(cpuAllocationRatio), RamFilter(ramAllocationRatio)), + weighers = listOf(CoreRamWeigher(multiplier = -1.0)) + ) + "active-servers" -> FilterScheduler( + filters = listOf(ComputeFilter(), VCpuFilter(cpuAllocationRatio), RamFilter(ramAllocationRatio)), + weighers = listOf(InstanceCountWeigher(multiplier = -1.0)) + ) + "active-servers-inv" -> FilterScheduler( + filters = listOf(ComputeFilter(), VCpuFilter(cpuAllocationRatio), RamFilter(ramAllocationRatio)), + weighers = listOf(InstanceCountWeigher(multiplier = 1.0)) + ) + "provisioned-cores" -> FilterScheduler( + filters = listOf(ComputeFilter(), VCpuFilter(cpuAllocationRatio), RamFilter(ramAllocationRatio)), + weighers = listOf(VCpuWeigher(cpuAllocationRatio, multiplier = 1.0)) + ) + "provisioned-cores-inv" -> FilterScheduler( + filters = listOf(ComputeFilter(), VCpuFilter(cpuAllocationRatio), RamFilter(ramAllocationRatio)), + weighers = listOf(VCpuWeigher(cpuAllocationRatio, multiplier = -1.0)) + ) + "random" -> FilterScheduler( + filters = listOf(ComputeFilter(), VCpuFilter(cpuAllocationRatio), RamFilter(ramAllocationRatio)), + weighers = emptyList(), + subsetSize = Int.MAX_VALUE, + random = Random(seeder.nextLong()) + ) + "replay" -> ReplayScheduler(placements) + else -> throw IllegalArgumentException("Unknown policy $name") + } +} diff --git a/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/ComputeWorkload.kt b/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/ComputeWorkload.kt new file mode 100644 index 00000000..78002c2f --- /dev/null +++ b/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/ComputeWorkload.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.workload + +import java.util.* + +/** + * An interface that describes how a workload is resolved. + */ +public interface ComputeWorkload { + /** + * Resolve the workload into a list of [VirtualMachine]s to simulate. + */ + public fun resolve(loader: ComputeWorkloadLoader, random: Random): List<VirtualMachine> +} diff --git a/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/ComputeWorkloadLoader.kt b/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/ComputeWorkloadLoader.kt new file mode 100644 index 00000000..1a6624f7 --- /dev/null +++ b/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/ComputeWorkloadLoader.kt @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.workload + +import mu.KotlinLogging +import org.opendc.simulator.compute.workload.SimTrace +import org.opendc.trace.* +import java.io.File +import java.time.Duration +import java.time.Instant +import java.util.* +import java.util.concurrent.ConcurrentHashMap +import kotlin.math.max +import kotlin.math.roundToLong + +/** + * A helper class for loading compute workload traces into memory. + * + * @param baseDir The directory containing the traces. + */ +public class ComputeWorkloadLoader(private val baseDir: File) { + /** + * The logger for this instance. + */ + private val logger = KotlinLogging.logger {} + + /** + * The cache of workloads. + */ + private val cache = ConcurrentHashMap<String, List<VirtualMachine>>() + + /** + * Read the fragments into memory. + */ + private fun parseFragments(trace: Trace): Map<String, Builder> { + val reader = checkNotNull(trace.getTable(TABLE_RESOURCE_STATES)).newReader() + + val idCol = reader.resolve(RESOURCE_ID) + val timestampCol = reader.resolve(RESOURCE_STATE_TIMESTAMP) + val durationCol = reader.resolve(RESOURCE_STATE_DURATION) + val coresCol = reader.resolve(RESOURCE_CPU_COUNT) + val usageCol = reader.resolve(RESOURCE_STATE_CPU_USAGE) + + val fragments = mutableMapOf<String, Builder>() + + return try { + while (reader.nextRow()) { + val id = reader.get(idCol) as String + val time = reader.get(timestampCol) as Instant + val duration = reader.get(durationCol) as Duration + val cores = reader.getInt(coresCol) + val cpuUsage = reader.getDouble(usageCol) + + val timeMs = time.toEpochMilli() + val deadlineMs = timeMs + duration.toMillis() + val builder = fragments.computeIfAbsent(id) { Builder() } + builder.add(timeMs, deadlineMs, cpuUsage, cores) + } + + fragments + } finally { + reader.close() + } + } + + /** + * Read the metadata into a workload. + */ + private fun parseMeta(trace: Trace, fragments: Map<String, Builder>): List<VirtualMachine> { + val reader = checkNotNull(trace.getTable(TABLE_RESOURCES)).newReader() + + val idCol = reader.resolve(RESOURCE_ID) + val startTimeCol = reader.resolve(RESOURCE_START_TIME) + val stopTimeCol = reader.resolve(RESOURCE_STOP_TIME) + val coresCol = reader.resolve(RESOURCE_CPU_COUNT) + val memCol = reader.resolve(RESOURCE_MEM_CAPACITY) + + var counter = 0 + val entries = mutableListOf<VirtualMachine>() + + return try { + while (reader.nextRow()) { + + val id = reader.get(idCol) as String + if (!fragments.containsKey(id)) { + continue + } + + val submissionTime = reader.get(startTimeCol) as Instant + val endTime = reader.get(stopTimeCol) as Instant + val maxCores = reader.getInt(coresCol) + val requiredMemory = reader.getDouble(memCol) / 1000.0 // Convert from KB to MB + val uid = UUID.nameUUIDFromBytes("$id-${counter++}".toByteArray()) + + val builder = fragments.getValue(id) + val totalLoad = builder.totalLoad + + entries.add( + VirtualMachine( + uid, + id, + maxCores, + requiredMemory.roundToLong(), + totalLoad, + submissionTime, + endTime, + builder.build() + ) + ) + } + + // Make sure the virtual machines are ordered by start time + entries.sortBy { it.startTime } + + entries + } catch (e: Exception) { + e.printStackTrace() + throw e + } finally { + reader.close() + } + } + + /** + * Load the trace with the specified [name] and [format]. + */ + public fun get(name: String, format: String): List<VirtualMachine> { + return cache.computeIfAbsent(name) { + val path = baseDir.resolve(it) + + logger.info { "Loading trace $it at $path" } + + val trace = Trace.open(path, format) + val fragments = parseFragments(trace) + parseMeta(trace, fragments) + } + } + + /** + * Clear the workload cache. + */ + public fun reset() { + cache.clear() + } + + /** + * A builder for a VM trace. + */ + private class Builder { + /** + * The total load of the trace. + */ + @JvmField var totalLoad: Double = 0.0 + + /** + * The internal builder for the trace. + */ + private val builder = SimTrace.builder() + + /** + * Add a fragment to the trace. + * + * @param timestamp Timestamp at which the fragment starts (in epoch millis). + * @param deadline Timestamp at which the fragment ends (in epoch millis). + * @param usage CPU usage of this fragment. + * @param cores Number of cores used. + */ + fun add(timestamp: Long, deadline: Long, usage: Double, cores: Int) { + val duration = max(0, deadline - timestamp) + totalLoad += (usage * duration) / 1000.0 // avg MHz * duration = MFLOPs + builder.add(timestamp, deadline, usage, cores) + } + + /** + * Build the trace. + */ + fun build(): SimTrace = builder.build() + } +} diff --git a/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/ComputeWorkloadRunner.kt b/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/ComputeWorkloadRunner.kt new file mode 100644 index 00000000..283f82fe --- /dev/null +++ b/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/ComputeWorkloadRunner.kt @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.workload + +import io.opentelemetry.sdk.metrics.SdkMeterProvider +import io.opentelemetry.sdk.metrics.export.MetricProducer +import io.opentelemetry.sdk.resources.Resource +import io.opentelemetry.semconv.resource.attributes.ResourceAttributes +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.yield +import org.opendc.compute.service.ComputeService +import org.opendc.compute.service.scheduler.ComputeScheduler +import org.opendc.compute.simulator.SimHost +import org.opendc.compute.workload.topology.HostSpec +import org.opendc.simulator.compute.kernel.interference.VmInterferenceModel +import org.opendc.simulator.compute.workload.SimTraceWorkload +import org.opendc.simulator.flow.FlowEngine +import org.opendc.telemetry.compute.* +import org.opendc.telemetry.sdk.toOtelClock +import java.time.Clock +import java.util.* +import kotlin.coroutines.CoroutineContext +import kotlin.math.max + +/** + * Helper class to simulated VM-based workloads in OpenDC. + * + * @param context [CoroutineContext] to run the simulation in. + * @param clock [Clock] instance tracking simulation time. + * @param scheduler [ComputeScheduler] implementation to use for the service. + * @param failureModel A failure model to use for injecting failures. + * @param interferenceModel The model to use for performance interference. + */ +public class ComputeWorkloadRunner( + private val context: CoroutineContext, + private val clock: Clock, + scheduler: ComputeScheduler, + private val failureModel: FailureModel? = null, + private val interferenceModel: VmInterferenceModel? = null, +) : AutoCloseable { + /** + * The [ComputeService] that has been configured by the manager. + */ + public val service: ComputeService + + /** + * The [MetricProducer] that are used by the [ComputeService] and the simulated hosts. + */ + public val producers: List<MetricProducer> + get() = _metricProducers + private val _metricProducers = mutableListOf<MetricProducer>() + + /** + * The [FlowEngine] to simulate the hosts. + */ + private val engine = FlowEngine(context, clock) + + /** + * The hosts that belong to this class. + */ + private val hosts = mutableSetOf<SimHost>() + + init { + val (service, serviceMeterProvider) = createService(scheduler) + this._metricProducers.add(serviceMeterProvider) + this.service = service + } + + /** + * Converge a simulation of the [ComputeService] by replaying the workload trace given by [trace]. + */ + public suspend fun run(trace: List<VirtualMachine>, seed: Long) { + val random = Random(seed) + val injector = failureModel?.createInjector(context, clock, service, random) + val client = service.newClient() + + // Create new image for the virtual machine + val image = client.newImage("vm-image") + + try { + coroutineScope { + // Start the fault injector + injector?.start() + + var offset = Long.MIN_VALUE + + for (entry in trace.sortedBy { it.startTime }) { + val now = clock.millis() + val start = entry.startTime.toEpochMilli() + + if (offset < 0) { + offset = start - now + } + + // Make sure the trace entries are ordered by submission time + assert(start - offset >= 0) { "Invalid trace order" } + delay(max(0, (start - offset) - now)) + + launch { + val workloadOffset = -offset + 300001 + val workload = SimTraceWorkload(entry.trace, workloadOffset) + + val server = client.newServer( + entry.name, + image, + client.newFlavor( + entry.name, + entry.cpuCount, + entry.memCapacity + ), + meta = mapOf("workload" to workload) + ) + + // Wait for the server reach its end time + val endTime = entry.stopTime.toEpochMilli() + delay(endTime + workloadOffset - clock.millis() + 5 * 60 * 1000) + + // Delete the server after reaching the end-time of the virtual machine + server.delete() + } + } + } + + yield() + } finally { + injector?.close() + client.close() + } + } + + /** + * Register a host for this simulation. + * + * @param spec The definition of the host. + * @param optimize Merge the CPU resources of the host into a single CPU resource. + * @return The [SimHost] that has been constructed by the runner. + */ + public fun registerHost(spec: HostSpec, optimize: Boolean = false): SimHost { + val resource = Resource.builder() + .put(HOST_ID, spec.uid.toString()) + .put(HOST_NAME, spec.name) + .put(HOST_ARCH, ResourceAttributes.HostArchValues.AMD64) + .put(HOST_NCPUS, spec.model.cpus.size) + .put(HOST_MEM_CAPACITY, spec.model.memory.sumOf { it.size }) + .build() + + val meterProvider = SdkMeterProvider.builder() + .setClock(clock.toOtelClock()) + .setResource(resource) + .build() + _metricProducers.add(meterProvider) + + val host = SimHost( + spec.uid, + spec.name, + spec.model, + spec.meta, + context, + engine, + meterProvider, + spec.hypervisor, + powerDriver = spec.powerDriver, + interferenceDomain = interferenceModel?.newDomain(), + optimize = optimize + ) + + hosts.add(host) + service.addHost(host) + + return host + } + + override fun close() { + service.close() + + for (host in hosts) { + host.close() + } + + hosts.clear() + } + + /** + * Construct a [ComputeService] instance. + */ + private fun createService(scheduler: ComputeScheduler): Pair<ComputeService, SdkMeterProvider> { + val resource = Resource.builder() + .put(ResourceAttributes.SERVICE_NAME, "opendc-compute") + .build() + + val meterProvider = SdkMeterProvider.builder() + .setClock(clock.toOtelClock()) + .setResource(resource) + .build() + + val service = ComputeService(context, clock, meterProvider, scheduler) + return service to meterProvider + } +} diff --git a/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/ComputeWorkloads.kt b/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/ComputeWorkloads.kt new file mode 100644 index 00000000..2f4935ca --- /dev/null +++ b/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/ComputeWorkloads.kt @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +@file:JvmName("ComputeWorkloads") +package org.opendc.compute.workload + +import org.opendc.compute.workload.internal.CompositeComputeWorkload +import org.opendc.compute.workload.internal.HpcSampledComputeWorkload +import org.opendc.compute.workload.internal.LoadSampledComputeWorkload +import org.opendc.compute.workload.internal.TraceComputeWorkload + +/** + * Construct a workload from a trace. + */ +public fun trace(name: String, format: String = "opendc-vm"): ComputeWorkload = TraceComputeWorkload(name, format) + +/** + * Construct a composite workload with the specified fractions. + */ +public fun composite(vararg pairs: Pair<ComputeWorkload, Double>): ComputeWorkload { + return CompositeComputeWorkload(pairs.toMap()) +} + +/** + * Sample a workload by a [fraction] of the total load. + */ +public fun ComputeWorkload.sampleByLoad(fraction: Double): ComputeWorkload { + return LoadSampledComputeWorkload(this, fraction) +} + +/** + * Sample a workload by a [fraction] of the HPC VMs (count) + */ +public fun ComputeWorkload.sampleByHpc(fraction: Double): ComputeWorkload { + return HpcSampledComputeWorkload(this, fraction) +} + +/** + * Sample a workload by a [fraction] of the HPC load + */ +public fun ComputeWorkload.sampleByHpcLoad(fraction: Double): ComputeWorkload { + return HpcSampledComputeWorkload(this, fraction, sampleLoad = true) +} diff --git a/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/FailureModel.kt b/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/FailureModel.kt new file mode 100644 index 00000000..4d9ef15d --- /dev/null +++ b/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/FailureModel.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.workload + +import org.opendc.compute.service.ComputeService +import org.opendc.compute.simulator.failure.HostFaultInjector +import java.time.Clock +import java.util.* +import kotlin.coroutines.CoroutineContext + +/** + * Factory interface for constructing [HostFaultInjector] for modeling failures of compute service hosts. + */ +public interface FailureModel { + /** + * Construct a [HostFaultInjector] for the specified [service]. + */ + public fun createInjector(context: CoroutineContext, clock: Clock, service: ComputeService, random: Random): HostFaultInjector +} diff --git a/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/FailureModels.kt b/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/FailureModels.kt new file mode 100644 index 00000000..be7120b9 --- /dev/null +++ b/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/FailureModels.kt @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +@file:JvmName("FailureModels") +package org.opendc.compute.workload + +import org.apache.commons.math3.distribution.LogNormalDistribution +import org.apache.commons.math3.random.Well19937c +import org.opendc.compute.service.ComputeService +import org.opendc.compute.simulator.SimHost +import org.opendc.compute.simulator.failure.HostFaultInjector +import org.opendc.compute.simulator.failure.StartStopHostFault +import org.opendc.compute.simulator.failure.StochasticVictimSelector +import java.time.Clock +import java.time.Duration +import java.util.* +import kotlin.coroutines.CoroutineContext +import kotlin.math.ln + +/** + * Obtain a [FailureModel] based on the GRID'5000 failure trace. + * + * This fault injector uses parameters from the GRID'5000 failure trace as described in + * "A Framework for the Study of Grid Inter-Operation Mechanisms", A. Iosup, 2009. + */ +public fun grid5000(failureInterval: Duration): FailureModel { + return object : FailureModel { + override fun createInjector( + context: CoroutineContext, + clock: Clock, + service: ComputeService, + random: Random + ): HostFaultInjector { + val rng = Well19937c(random.nextLong()) + val hosts = service.hosts.map { it as SimHost }.toSet() + + // Parameters from A. Iosup, A Framework for the Study of Grid Inter-Operation Mechanisms, 2009 + // GRID'5000 + return HostFaultInjector( + context, + clock, + hosts, + iat = LogNormalDistribution(rng, ln(failureInterval.toHours().toDouble()), 1.03), + selector = StochasticVictimSelector(LogNormalDistribution(rng, 1.88, 1.25), random), + fault = StartStopHostFault(LogNormalDistribution(rng, 8.89, 2.71)) + ) + } + + override fun toString(): String = "Grid5000FailureModel" + } +} diff --git a/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/VirtualMachine.kt b/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/VirtualMachine.kt new file mode 100644 index 00000000..5dd239f6 --- /dev/null +++ b/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/VirtualMachine.kt @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.workload + +import org.opendc.simulator.compute.workload.SimTrace +import java.time.Instant +import java.util.* + +/** + * A virtual machine workload. + * + * @param uid The unique identifier of the virtual machine. + * @param name The name of the virtual machine. + * @param cpuCount The number of vCPUs in the VM. + * @param memCapacity The provisioned memory for the VM. + * @param startTime The start time of the VM. + * @param stopTime The stop time of the VM. + * @param trace The trace that belong to this VM. + */ +public data class VirtualMachine( + val uid: UUID, + val name: String, + val cpuCount: Int, + val memCapacity: Long, + val totalLoad: Double, + val startTime: Instant, + val stopTime: Instant, + val trace: SimTrace, +) diff --git a/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/export/parquet/ParquetComputeMetricExporter.kt b/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/export/parquet/ParquetComputeMetricExporter.kt new file mode 100644 index 00000000..ad182d67 --- /dev/null +++ b/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/export/parquet/ParquetComputeMetricExporter.kt @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.workload.export.parquet + +import io.opentelemetry.sdk.common.CompletableResultCode +import org.opendc.telemetry.compute.ComputeMetricExporter +import org.opendc.telemetry.compute.ComputeMonitor +import org.opendc.telemetry.compute.table.HostData +import org.opendc.telemetry.compute.table.ServerData +import org.opendc.telemetry.compute.table.ServiceData +import java.io.File + +/** + * A [ComputeMonitor] that logs the events to a Parquet file. + */ +public class ParquetComputeMetricExporter(base: File, partition: String, bufferSize: Int) : ComputeMetricExporter() { + private val serverWriter = ParquetServerDataWriter( + File(base, "server/$partition/data.parquet").also { it.parentFile.mkdirs() }, + bufferSize + ) + + private val hostWriter = ParquetHostDataWriter( + File(base, "host/$partition/data.parquet").also { it.parentFile.mkdirs() }, + bufferSize + ) + + private val serviceWriter = ParquetServiceDataWriter( + File(base, "service/$partition/data.parquet").also { it.parentFile.mkdirs() }, + bufferSize + ) + + override fun record(data: ServerData) { + serverWriter.write(data) + } + + override fun record(data: HostData) { + hostWriter.write(data) + } + + override fun record(data: ServiceData) { + serviceWriter.write(data) + } + + override fun shutdown(): CompletableResultCode { + hostWriter.close() + serviceWriter.close() + serverWriter.close() + + return CompletableResultCode.ofSuccess() + } +} diff --git a/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/export/parquet/ParquetDataWriter.kt b/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/export/parquet/ParquetDataWriter.kt new file mode 100644 index 00000000..4172d729 --- /dev/null +++ b/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/export/parquet/ParquetDataWriter.kt @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.workload.export.parquet + +import mu.KotlinLogging +import org.apache.avro.Schema +import org.apache.avro.generic.GenericData +import org.apache.avro.generic.GenericRecordBuilder +import org.apache.parquet.avro.AvroParquetWriter +import org.apache.parquet.hadoop.ParquetFileWriter +import org.apache.parquet.hadoop.ParquetWriter +import org.apache.parquet.hadoop.metadata.CompressionCodecName +import org.opendc.trace.util.parquet.LocalOutputFile +import java.io.File +import java.util.concurrent.ArrayBlockingQueue +import java.util.concurrent.BlockingQueue +import kotlin.concurrent.thread + +/** + * A writer that writes data in Parquet format. + */ +public abstract class ParquetDataWriter<in T>( + path: File, + private val schema: Schema, + bufferSize: Int = 4096 +) : AutoCloseable { + /** + * The logging instance to use. + */ + private val logger = KotlinLogging.logger {} + + /** + * The queue of commands to process. + */ + private val queue: BlockingQueue<T> = ArrayBlockingQueue(bufferSize) + + /** + * An exception to be propagated to the actual writer. + */ + private var exception: Throwable? = null + + /** + * The thread that is responsible for writing the Parquet records. + */ + private val writerThread = thread(start = false, name = this.toString()) { + val writer = let { + val builder = AvroParquetWriter.builder<GenericData.Record>(LocalOutputFile(path)) + .withSchema(schema) + .withCompressionCodec(CompressionCodecName.ZSTD) + .withWriteMode(ParquetFileWriter.Mode.OVERWRITE) + buildWriter(builder) + } + + val queue = queue + val buf = mutableListOf<T>() + var shouldStop = false + + try { + while (!shouldStop) { + try { + process(writer, queue.take()) + } catch (e: InterruptedException) { + shouldStop = true + } + + if (queue.drainTo(buf) > 0) { + for (data in buf) { + process(writer, data) + } + buf.clear() + } + } + } catch (e: Throwable) { + logger.error(e) { "Failure in Parquet data writer" } + exception = e + } finally { + writer.close() + } + } + + /** + * Build the [ParquetWriter] used to write the Parquet files. + */ + protected open fun buildWriter(builder: AvroParquetWriter.Builder<GenericData.Record>): ParquetWriter<GenericData.Record> { + return builder.build() + } + + /** + * Convert the specified [data] into a Parquet record. + */ + protected abstract fun convert(builder: GenericRecordBuilder, data: T) + + /** + * Write the specified metrics to the database. + */ + public fun write(data: T) { + val exception = exception + if (exception != null) { + throw IllegalStateException("Writer thread failed", exception) + } + + queue.put(data) + } + + /** + * Signal the writer to stop. + */ + override fun close() { + writerThread.interrupt() + writerThread.join() + } + + init { + writerThread.start() + } + + /** + * Process the specified [data] to be written to the Parquet file. + */ + private fun process(writer: ParquetWriter<GenericData.Record>, data: T) { + val builder = GenericRecordBuilder(schema) + convert(builder, data) + writer.write(builder.build()) + } +} diff --git a/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/export/parquet/ParquetHostDataWriter.kt b/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/export/parquet/ParquetHostDataWriter.kt new file mode 100644 index 00000000..98a0739e --- /dev/null +++ b/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/export/parquet/ParquetHostDataWriter.kt @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.workload.export.parquet + +import org.apache.avro.Schema +import org.apache.avro.SchemaBuilder +import org.apache.avro.generic.GenericData +import org.apache.avro.generic.GenericRecordBuilder +import org.apache.parquet.avro.AvroParquetWriter +import org.apache.parquet.hadoop.ParquetWriter +import org.opendc.telemetry.compute.table.HostData +import org.opendc.trace.util.parquet.TIMESTAMP_SCHEMA +import org.opendc.trace.util.parquet.UUID_SCHEMA +import org.opendc.trace.util.parquet.optional +import java.io.File + +/** + * A Parquet event writer for [HostData]s. + */ +public class ParquetHostDataWriter(path: File, bufferSize: Int) : + ParquetDataWriter<HostData>(path, SCHEMA, bufferSize) { + + override fun buildWriter(builder: AvroParquetWriter.Builder<GenericData.Record>): ParquetWriter<GenericData.Record> { + return builder + .withDictionaryEncoding("host_id", true) + .build() + } + + override fun convert(builder: GenericRecordBuilder, data: HostData) { + builder["timestamp"] = data.timestamp.toEpochMilli() + + builder["host_id"] = data.host.id + + builder["uptime"] = data.uptime + builder["downtime"] = data.downtime + val bootTime = data.bootTime + builder["boot_time"] = bootTime?.toEpochMilli() + + builder["cpu_count"] = data.host.cpuCount + builder["cpu_limit"] = data.cpuLimit + builder["cpu_time_active"] = data.cpuActiveTime + builder["cpu_time_idle"] = data.cpuIdleTime + builder["cpu_time_steal"] = data.cpuStealTime + builder["cpu_time_lost"] = data.cpuLostTime + + builder["mem_limit"] = data.host.memCapacity + + builder["power_total"] = data.powerTotal + + builder["guests_terminated"] = data.guestsTerminated + builder["guests_running"] = data.guestsRunning + builder["guests_error"] = data.guestsError + builder["guests_invalid"] = data.guestsInvalid + } + + override fun toString(): String = "host-writer" + + private companion object { + private val SCHEMA: Schema = SchemaBuilder + .record("host") + .namespace("org.opendc.telemetry.compute") + .fields() + .name("timestamp").type(TIMESTAMP_SCHEMA).noDefault() + .name("host_id").type(UUID_SCHEMA).noDefault() + .requiredLong("uptime") + .requiredLong("downtime") + .name("boot_time").type(TIMESTAMP_SCHEMA.optional()).noDefault() + .requiredInt("cpu_count") + .requiredDouble("cpu_limit") + .requiredLong("cpu_time_active") + .requiredLong("cpu_time_idle") + .requiredLong("cpu_time_steal") + .requiredLong("cpu_time_lost") + .requiredLong("mem_limit") + .requiredDouble("power_total") + .requiredInt("guests_terminated") + .requiredInt("guests_running") + .requiredInt("guests_error") + .requiredInt("guests_invalid") + .endRecord() + } +} diff --git a/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/export/parquet/ParquetServerDataWriter.kt b/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/export/parquet/ParquetServerDataWriter.kt new file mode 100644 index 00000000..0d11ec23 --- /dev/null +++ b/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/export/parquet/ParquetServerDataWriter.kt @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.workload.export.parquet + +import org.apache.avro.Schema +import org.apache.avro.SchemaBuilder +import org.apache.avro.generic.GenericData +import org.apache.avro.generic.GenericRecordBuilder +import org.apache.parquet.avro.AvroParquetWriter +import org.apache.parquet.hadoop.ParquetWriter +import org.opendc.telemetry.compute.table.ServerData +import org.opendc.trace.util.parquet.TIMESTAMP_SCHEMA +import org.opendc.trace.util.parquet.UUID_SCHEMA +import org.opendc.trace.util.parquet.optional +import java.io.File + +/** + * A Parquet event writer for [ServerData]s. + */ +public class ParquetServerDataWriter(path: File, bufferSize: Int) : + ParquetDataWriter<ServerData>(path, SCHEMA, bufferSize) { + + override fun buildWriter(builder: AvroParquetWriter.Builder<GenericData.Record>): ParquetWriter<GenericData.Record> { + return builder + .withDictionaryEncoding("server_id", true) + .withDictionaryEncoding("host_id", true) + .build() + } + + override fun convert(builder: GenericRecordBuilder, data: ServerData) { + builder["timestamp"] = data.timestamp.toEpochMilli() + + builder["server_id"] = data.server.id + builder["host_id"] = data.host?.id + + builder["uptime"] = data.uptime + builder["downtime"] = data.downtime + val bootTime = data.bootTime + builder["boot_time"] = bootTime?.toEpochMilli() + builder["scheduling_latency"] = data.schedulingLatency + + builder["cpu_count"] = data.server.cpuCount + builder["cpu_limit"] = data.cpuLimit + builder["cpu_time_active"] = data.cpuActiveTime + builder["cpu_time_idle"] = data.cpuIdleTime + builder["cpu_time_steal"] = data.cpuStealTime + builder["cpu_time_lost"] = data.cpuLostTime + + builder["mem_limit"] = data.server.memCapacity + } + + override fun toString(): String = "server-writer" + + private companion object { + private val SCHEMA: Schema = SchemaBuilder + .record("server") + .namespace("org.opendc.telemetry.compute") + .fields() + .name("timestamp").type(TIMESTAMP_SCHEMA).noDefault() + .name("server_id").type(UUID_SCHEMA).noDefault() + .name("host_id").type(UUID_SCHEMA.optional()).noDefault() + .requiredLong("uptime") + .requiredLong("downtime") + .name("boot_time").type(TIMESTAMP_SCHEMA.optional()).noDefault() + .requiredLong("scheduling_latency") + .requiredInt("cpu_count") + .requiredDouble("cpu_limit") + .requiredLong("cpu_time_active") + .requiredLong("cpu_time_idle") + .requiredLong("cpu_time_steal") + .requiredLong("cpu_time_lost") + .requiredLong("mem_limit") + .endRecord() + } +} diff --git a/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/export/parquet/ParquetServiceDataWriter.kt b/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/export/parquet/ParquetServiceDataWriter.kt new file mode 100644 index 00000000..47824b29 --- /dev/null +++ b/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/export/parquet/ParquetServiceDataWriter.kt @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.workload.export.parquet + +import org.apache.avro.Schema +import org.apache.avro.SchemaBuilder +import org.apache.avro.generic.GenericRecordBuilder +import org.opendc.telemetry.compute.table.ServiceData +import org.opendc.trace.util.parquet.TIMESTAMP_SCHEMA +import java.io.File + +/** + * A Parquet event writer for [ServiceData]s. + */ +public class ParquetServiceDataWriter(path: File, bufferSize: Int) : + ParquetDataWriter<ServiceData>(path, SCHEMA, bufferSize) { + + override fun convert(builder: GenericRecordBuilder, data: ServiceData) { + builder["timestamp"] = data.timestamp.toEpochMilli() + builder["hosts_up"] = data.hostsUp + builder["hosts_down"] = data.hostsDown + builder["servers_pending"] = data.serversPending + builder["servers_active"] = data.serversActive + builder["attempts_success"] = data.attemptsSuccess + builder["attempts_failure"] = data.attemptsFailure + builder["attempts_error"] = data.attemptsError + } + + override fun toString(): String = "service-writer" + + private companion object { + private val SCHEMA: Schema = SchemaBuilder + .record("service") + .namespace("org.opendc.telemetry.compute") + .fields() + .name("timestamp").type(TIMESTAMP_SCHEMA).noDefault() + .requiredInt("hosts_up") + .requiredInt("hosts_down") + .requiredInt("servers_pending") + .requiredInt("servers_active") + .requiredInt("attempts_success") + .requiredInt("attempts_failure") + .requiredInt("attempts_error") + .endRecord() + } +} diff --git a/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/internal/CompositeComputeWorkload.kt b/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/internal/CompositeComputeWorkload.kt new file mode 100644 index 00000000..9b2bec55 --- /dev/null +++ b/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/internal/CompositeComputeWorkload.kt @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.workload.internal + +import mu.KotlinLogging +import org.opendc.compute.workload.ComputeWorkload +import org.opendc.compute.workload.ComputeWorkloadLoader +import org.opendc.compute.workload.VirtualMachine +import java.util.* + +/** + * A [ComputeWorkload] that samples multiple workloads based on the total load of all workloads. + */ +internal class CompositeComputeWorkload(val sources: Map<ComputeWorkload, Double>) : ComputeWorkload { + /** + * The logging instance of this class. + */ + private val logger = KotlinLogging.logger {} + + override fun resolve(loader: ComputeWorkloadLoader, random: Random): List<VirtualMachine> { + val traces = sources.map { (source, fraction) -> fraction to source.resolve(loader, random) } + + val totalLoad = traces.sumOf { (_, vms) -> vms.sumOf { it.totalLoad } } + + val res = mutableListOf<VirtualMachine>() + + for ((fraction, vms) in traces) { + var currentLoad = 0.0 + + for (entry in vms) { + val entryLoad = entry.totalLoad + if ((currentLoad + entryLoad) / totalLoad > fraction) { + break + } + + currentLoad += entryLoad + res += entry + } + } + + val vmCount = traces.sumOf { (_, vms) -> vms.size } + logger.info { "Sampled $vmCount VMs into subset of ${res.size} VMs" } + + return res + } +} diff --git a/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/internal/HpcSampledComputeWorkload.kt b/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/internal/HpcSampledComputeWorkload.kt new file mode 100644 index 00000000..52f4c672 --- /dev/null +++ b/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/internal/HpcSampledComputeWorkload.kt @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.workload.internal + +import mu.KotlinLogging +import org.opendc.compute.workload.ComputeWorkload +import org.opendc.compute.workload.ComputeWorkloadLoader +import org.opendc.compute.workload.VirtualMachine +import java.util.* + +/** + * A [ComputeWorkload] that samples HPC VMs in the workload. + * + * @param fraction The fraction of load/virtual machines to sample + * @param sampleLoad A flag to indicate that the sampling should be based on the total load or on the number of VMs. + */ +internal class HpcSampledComputeWorkload(val source: ComputeWorkload, val fraction: Double, val sampleLoad: Boolean = false) : ComputeWorkload { + /** + * The logging instance of this class. + */ + private val logger = KotlinLogging.logger {} + + /** + * The pattern to match compute nodes in the workload. + */ + private val pattern = Regex("^(ComputeNode|cn).*") + + override fun resolve(loader: ComputeWorkloadLoader, random: Random): List<VirtualMachine> { + val vms = source.resolve(loader, random) + + val (hpc, nonHpc) = vms.partition { entry -> + val name = entry.name + name.matches(pattern) + } + + val hpcSequence = generateSequence(0) { it + 1 } + .map { index -> + val res = mutableListOf<VirtualMachine>() + hpc.mapTo(res) { sample(it, index) } + res.shuffle(random) + res + } + .flatten() + + val nonHpcSequence = generateSequence(0) { it + 1 } + .map { index -> + val res = mutableListOf<VirtualMachine>() + nonHpc.mapTo(res) { sample(it, index) } + res.shuffle(random) + res + } + .flatten() + + logger.debug { "Found ${hpc.size} HPC workloads and ${nonHpc.size} non-HPC workloads" } + + val totalLoad = vms.sumOf { it.totalLoad } + + logger.debug { "Total trace load: $totalLoad" } + var hpcCount = 0 + var hpcLoad = 0.0 + var nonHpcCount = 0 + var nonHpcLoad = 0.0 + + val res = mutableListOf<VirtualMachine>() + + if (sampleLoad) { + var currentLoad = 0.0 + for (entry in hpcSequence) { + val entryLoad = entry.totalLoad + if ((currentLoad + entryLoad) / totalLoad > fraction) { + break + } + + hpcLoad += entryLoad + hpcCount += 1 + currentLoad += entryLoad + res += entry + } + + for (entry in nonHpcSequence) { + val entryLoad = entry.totalLoad + if ((currentLoad + entryLoad) / totalLoad > 1) { + break + } + + nonHpcLoad += entryLoad + nonHpcCount += 1 + currentLoad += entryLoad + res += entry + } + } else { + hpcSequence + .take((fraction * vms.size).toInt()) + .forEach { entry -> + hpcLoad += entry.totalLoad + hpcCount += 1 + res.add(entry) + } + + nonHpcSequence + .take(((1 - fraction) * vms.size).toInt()) + .forEach { entry -> + nonHpcLoad += entry.totalLoad + nonHpcCount += 1 + res.add(entry) + } + } + + logger.debug { "HPC $hpcCount (load $hpcLoad) and non-HPC $nonHpcCount (load $nonHpcLoad)" } + logger.debug { "Total sampled load: ${hpcLoad + nonHpcLoad}" } + logger.info { "Sampled ${vms.size} VMs (fraction $fraction) into subset of ${res.size} VMs" } + + return res + } + + /** + * Sample a random trace entry. + */ + private fun sample(entry: VirtualMachine, i: Int): VirtualMachine { + val uid = UUID.nameUUIDFromBytes("${entry.uid}-$i".toByteArray()) + return entry.copy(uid = uid) + } +} diff --git a/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/internal/LoadSampledComputeWorkload.kt b/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/internal/LoadSampledComputeWorkload.kt new file mode 100644 index 00000000..ef6de729 --- /dev/null +++ b/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/internal/LoadSampledComputeWorkload.kt @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.workload.internal + +import mu.KotlinLogging +import org.opendc.compute.workload.ComputeWorkload +import org.opendc.compute.workload.ComputeWorkloadLoader +import org.opendc.compute.workload.VirtualMachine +import java.util.* + +/** + * A [ComputeWorkload] that is sampled based on total load. + */ +internal class LoadSampledComputeWorkload(val source: ComputeWorkload, val fraction: Double) : ComputeWorkload { + /** + * The logging instance of this class. + */ + private val logger = KotlinLogging.logger {} + + override fun resolve(loader: ComputeWorkloadLoader, random: Random): List<VirtualMachine> { + val vms = source.resolve(loader, random) + val res = mutableListOf<VirtualMachine>() + + val totalLoad = vms.sumOf { it.totalLoad } + var currentLoad = 0.0 + + for (entry in vms) { + val entryLoad = entry.totalLoad + if ((currentLoad + entryLoad) / totalLoad > fraction) { + break + } + + currentLoad += entryLoad + res += entry + } + + logger.info { "Sampled ${vms.size} VMs (fraction $fraction) into subset of ${res.size} VMs" } + + return res + } +} diff --git a/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/internal/TraceComputeWorkload.kt b/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/internal/TraceComputeWorkload.kt new file mode 100644 index 00000000..c20cb8f3 --- /dev/null +++ b/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/internal/TraceComputeWorkload.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.workload.internal + +import org.opendc.compute.workload.ComputeWorkload +import org.opendc.compute.workload.ComputeWorkloadLoader +import org.opendc.compute.workload.VirtualMachine +import java.util.* + +/** + * A [ComputeWorkload] from a trace. + */ +internal class TraceComputeWorkload(val name: String, val format: String) : ComputeWorkload { + override fun resolve(loader: ComputeWorkloadLoader, random: Random): List<VirtualMachine> { + return loader.get(name, format) + } +} diff --git a/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/topology/HostSpec.kt b/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/topology/HostSpec.kt new file mode 100644 index 00000000..f3dc1e9e --- /dev/null +++ b/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/topology/HostSpec.kt @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.workload.topology + +import org.opendc.simulator.compute.kernel.SimFairShareHypervisorProvider +import org.opendc.simulator.compute.kernel.SimHypervisorProvider +import org.opendc.simulator.compute.model.MachineModel +import org.opendc.simulator.compute.power.PowerDriver +import java.util.* + +/** + * Description of a physical host that will be simulated by OpenDC and host the virtual machines. + * + * @param uid Unique identifier of the host. + * @param name The name of the host. + * @param meta The metadata of the host. + * @param model The physical model of the machine. + * @param powerDriver The [PowerDriver] to model the power consumption of the machine. + * @param hypervisor The hypervisor implementation to use. + */ +public data class HostSpec( + val uid: UUID, + val name: String, + val meta: Map<String, Any>, + val model: MachineModel, + val powerDriver: PowerDriver, + val hypervisor: SimHypervisorProvider = SimFairShareHypervisorProvider() +) diff --git a/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/topology/Topology.kt b/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/topology/Topology.kt new file mode 100644 index 00000000..3b8dc918 --- /dev/null +++ b/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/topology/Topology.kt @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.workload.topology + +/** + * Representation of the environment of the compute service, describing the physical details of every host. + */ +public interface Topology { + /** + * Resolve the [Topology] into a list of [HostSpec]s. + */ + public fun resolve(): List<HostSpec> +} diff --git a/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/topology/TopologyHelpers.kt b/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/topology/TopologyHelpers.kt new file mode 100644 index 00000000..74f9a1f8 --- /dev/null +++ b/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/topology/TopologyHelpers.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +@file:JvmName("TopologyHelpers") +package org.opendc.compute.workload.topology + +import org.opendc.compute.workload.ComputeWorkloadRunner + +/** + * Apply the specified [topology] to the given [ComputeWorkloadRunner]. + */ +public fun ComputeWorkloadRunner.apply(topology: Topology, optimize: Boolean = false) { + val hosts = topology.resolve() + for (spec in hosts) { + registerHost(spec, optimize) + } +} diff --git a/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/util/PerformanceInterferenceReader.kt b/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/util/PerformanceInterferenceReader.kt new file mode 100644 index 00000000..67f9626c --- /dev/null +++ b/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/util/PerformanceInterferenceReader.kt @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.workload.util + +import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import com.fasterxml.jackson.module.kotlin.readValue +import org.opendc.simulator.compute.kernel.interference.VmInterferenceGroup +import java.io.File +import java.io.InputStream + +/** + * A parser for the JSON performance interference setup files used for the TPDS article on Capelin. + */ +public class PerformanceInterferenceReader { + /** + * The [ObjectMapper] to use. + */ + private val mapper = jacksonObjectMapper() + + init { + mapper.addMixIn(VmInterferenceGroup::class.java, GroupMixin::class.java) + } + + /** + * Read the performance interface model from [file]. + */ + public fun read(file: File): List<VmInterferenceGroup> { + return mapper.readValue(file) + } + + /** + * Read the performance interface model from the input. + */ + public fun read(input: InputStream): List<VmInterferenceGroup> { + return mapper.readValue(input) + } + + private data class GroupMixin( + @JsonProperty("minServerLoad") + val targetLoad: Double, + @JsonProperty("performanceScore") + val score: Double, + @JsonProperty("vms") + val members: Set<String>, + ) +} diff --git a/opendc-compute/opendc-compute-workload/src/test/kotlin/org/opendc/compute/workload/util/PerformanceInterferenceReaderTest.kt b/opendc-compute/opendc-compute-workload/src/test/kotlin/org/opendc/compute/workload/util/PerformanceInterferenceReaderTest.kt new file mode 100644 index 00000000..c79f0584 --- /dev/null +++ b/opendc-compute/opendc-compute-workload/src/test/kotlin/org/opendc/compute/workload/util/PerformanceInterferenceReaderTest.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.workload.util + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertAll + +/** + * Test suite for the [PerformanceInterferenceReader] class. + */ +class PerformanceInterferenceReaderTest { + @Test + fun testSmoke() { + val input = checkNotNull(PerformanceInterferenceReader::class.java.getResourceAsStream("/perf-interference.json")) + val result = PerformanceInterferenceReader().read(input) + + assertAll( + { assertEquals(2, result.size) }, + { assertEquals(setOf("vm_a", "vm_c", "vm_x", "vm_y"), result[0].members) }, + { assertEquals(0.0, result[0].targetLoad, 0.001) }, + { assertEquals(0.8830158730158756, result[0].score, 0.001) } + ) + } +} diff --git a/opendc-compute/opendc-compute-workload/src/test/resources/perf-interference.json b/opendc-compute/opendc-compute-workload/src/test/resources/perf-interference.json new file mode 100644 index 00000000..1be5852b --- /dev/null +++ b/opendc-compute/opendc-compute-workload/src/test/resources/perf-interference.json @@ -0,0 +1,22 @@ +[ + { + "vms": [ + "vm_a", + "vm_c", + "vm_x", + "vm_y" + ], + "minServerLoad": 0.0, + "performanceScore": 0.8830158730158756 + }, + { + "vms": [ + "vm_a", + "vm_b", + "vm_c", + "vm_d" + ], + "minServerLoad": 0.0, + "performanceScore": 0.7133055555552751 + } +] diff --git a/opendc-experiments/opendc-experiments-capelin/build.gradle.kts b/opendc-experiments/opendc-experiments-capelin/build.gradle.kts index 7c7f0dad..c20556b5 100644 --- a/opendc-experiments/opendc-experiments-capelin/build.gradle.kts +++ b/opendc-experiments/opendc-experiments-capelin/build.gradle.kts @@ -26,26 +26,29 @@ description = "Experiments for the Capelin work" plugins { `experiment-conventions` `testing-conventions` + `benchmark-conventions` } dependencies { api(platform(projects.opendcPlatform)) api(projects.opendcHarness.opendcHarnessApi) - implementation(projects.opendcFormat) + api(projects.opendcCompute.opendcComputeWorkload) + implementation(projects.opendcSimulator.opendcSimulatorCore) implementation(projects.opendcSimulator.opendcSimulatorCompute) - implementation(projects.opendcSimulator.opendcSimulatorFailures) implementation(projects.opendcCompute.opendcComputeSimulator) implementation(projects.opendcTelemetry.opendcTelemetrySdk) + implementation(projects.opendcTelemetry.opendcTelemetryCompute) - implementation(libs.kotlin.logging) implementation(libs.config) - implementation(libs.progressbar) - implementation(libs.clikt) + implementation(libs.kotlin.logging) + implementation(libs.jackson.databind) + implementation(libs.jackson.module.kotlin) + implementation(libs.jackson.dataformat.csv) + implementation(kotlin("reflect")) + implementation(libs.opentelemetry.semconv) + + runtimeOnly(projects.opendcTrace.opendcTraceOpendc) - implementation(libs.parquet) - implementation(libs.hadoop.client) { - exclude(group = "org.slf4j", module = "slf4j-log4j12") - exclude(group = "log4j") - } + testImplementation(libs.log4j.slf4j) } diff --git a/opendc-experiments/opendc-experiments-capelin/src/jmh/kotlin/org/opendc/experiments/capelin/CapelinBenchmarks.kt b/opendc-experiments/opendc-experiments-capelin/src/jmh/kotlin/org/opendc/experiments/capelin/CapelinBenchmarks.kt new file mode 100644 index 00000000..48a90985 --- /dev/null +++ b/opendc-experiments/opendc-experiments-capelin/src/jmh/kotlin/org/opendc/experiments/capelin/CapelinBenchmarks.kt @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.experiments.capelin + +import kotlinx.coroutines.ExperimentalCoroutinesApi +import org.opendc.compute.service.scheduler.FilterScheduler +import org.opendc.compute.service.scheduler.filters.ComputeFilter +import org.opendc.compute.service.scheduler.filters.RamFilter +import org.opendc.compute.service.scheduler.filters.VCpuFilter +import org.opendc.compute.service.scheduler.weights.CoreRamWeigher +import org.opendc.compute.workload.* +import org.opendc.compute.workload.topology.Topology +import org.opendc.compute.workload.topology.apply +import org.opendc.experiments.capelin.topology.clusterTopology +import org.opendc.simulator.core.runBlockingSimulation +import org.openjdk.jmh.annotations.* +import java.io.File +import java.util.* +import java.util.concurrent.TimeUnit + +/** + * Benchmark suite for the Capelin experiments. + */ +@State(Scope.Thread) +@Fork(1) +@Warmup(iterations = 2, time = 5, timeUnit = TimeUnit.SECONDS) +@Measurement(iterations = 5, time = 5, timeUnit = TimeUnit.SECONDS) +@OptIn(ExperimentalCoroutinesApi::class) +class CapelinBenchmarks { + private lateinit var vms: List<VirtualMachine> + private lateinit var topology: Topology + + @Param("true", "false") + private var isOptimized: Boolean = false + + @Setup + fun setUp() { + val loader = ComputeWorkloadLoader(File("src/test/resources/trace")) + val source = trace("bitbrains-small") + vms = source.resolve(loader, Random(1L)) + topology = checkNotNull(object {}.javaClass.getResourceAsStream("/env/topology.txt")).use { clusterTopology(it) } + } + + @Benchmark + fun benchmarkCapelin() = runBlockingSimulation { + val computeScheduler = FilterScheduler( + filters = listOf(ComputeFilter(), VCpuFilter(16.0), RamFilter(1.0)), + weighers = listOf(CoreRamWeigher(multiplier = 1.0)) + ) + val runner = ComputeWorkloadRunner( + coroutineContext, + clock, + computeScheduler + ) + + try { + runner.apply(topology, isOptimized) + runner.run(vms, 0) + } finally { + runner.close() + } + } +} diff --git a/opendc-experiments/opendc-experiments-capelin/src/jmh/resources/log4j2.xml b/opendc-experiments/opendc-experiments-capelin/src/jmh/resources/log4j2.xml new file mode 100644 index 00000000..c496dd75 --- /dev/null +++ b/opendc-experiments/opendc-experiments-capelin/src/jmh/resources/log4j2.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ MIT License + ~ + ~ Copyright (c) 2020 atlarge-research + ~ + ~ Permission is hereby granted, free of charge, to any person obtaining a copy + ~ of this software and associated documentation files (the "Software"), to deal + ~ in the Software without restriction, including without limitation the rights + ~ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + ~ copies of the Software, and to permit persons to whom the Software is + ~ furnished to do so, subject to the following conditions: + ~ + ~ The above copyright notice and this permission notice shall be included in all + ~ copies or substantial portions of the Software. + ~ + ~ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ~ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ~ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ~ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ~ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ~ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ~ SOFTWARE. + --> + +<Configuration status="WARN"> + <Appenders> + <Console name="Console" target="SYSTEM_OUT"> + <PatternLayout pattern="%d{HH:mm:ss.SSS} [%highlight{%-5level}] %logger{36} - %msg%n" disableAnsi="false"/> + </Console> + </Appenders> + <Loggers> + <Root level="warn"> + <AppenderRef ref="Console"/> + </Root> + </Loggers> +</Configuration> diff --git a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/CompositeWorkloadPortfolio.kt b/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/CompositeWorkloadPortfolio.kt index faabe5cb..31e8f961 100644 --- a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/CompositeWorkloadPortfolio.kt +++ b/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/CompositeWorkloadPortfolio.kt @@ -22,7 +22,8 @@ package org.opendc.experiments.capelin -import org.opendc.experiments.capelin.model.CompositeWorkload +import org.opendc.compute.workload.composite +import org.opendc.compute.workload.trace import org.opendc.experiments.capelin.model.OperationalPhenomena import org.opendc.experiments.capelin.model.Topology import org.opendc.experiments.capelin.model.Workload @@ -42,30 +43,25 @@ public class CompositeWorkloadPortfolio : Portfolio("composite-workload") { ) override val workload: Workload by anyOf( - CompositeWorkload( + Workload( "all-azure", - listOf(Workload("solvinity-short", 0.0), Workload("azure", 1.0)), - totalSampleLoad + composite(trace("solvinity-short") to 0.0, trace("azure") to 1.0) ), - CompositeWorkload( + Workload( "solvinity-25-azure-75", - listOf(Workload("solvinity-short", 0.25), Workload("azure", 0.75)), - totalSampleLoad + composite(trace("solvinity-short") to 0.25, trace("azure") to 0.75) ), - CompositeWorkload( + Workload( "solvinity-50-azure-50", - listOf(Workload("solvinity-short", 0.5), Workload("azure", 0.5)), - totalSampleLoad + composite(trace("solvinity-short") to 0.5, trace("azure") to 0.5) ), - CompositeWorkload( + Workload( "solvinity-75-azure-25", - listOf(Workload("solvinity-short", 0.75), Workload("azure", 0.25)), - totalSampleLoad + composite(trace("solvinity-short") to 0.75, trace("azure") to 0.25) ), - CompositeWorkload( + Workload( "all-solvinity", - listOf(Workload("solvinity-short", 1.0), Workload("azure", 0.0)), - totalSampleLoad + composite(trace("solvinity-short") to 1.0, trace("azure") to 0.0) ) ) diff --git a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/ExperimentHelpers.kt b/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/ExperimentHelpers.kt deleted file mode 100644 index 0fbb7280..00000000 --- a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/ExperimentHelpers.kt +++ /dev/null @@ -1,321 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.experiments.capelin - -import io.opentelemetry.api.metrics.MeterProvider -import io.opentelemetry.sdk.metrics.SdkMeterProvider -import io.opentelemetry.sdk.metrics.aggregator.AggregatorFactory -import io.opentelemetry.sdk.metrics.common.InstrumentType -import io.opentelemetry.sdk.metrics.export.MetricProducer -import io.opentelemetry.sdk.metrics.view.InstrumentSelector -import io.opentelemetry.sdk.metrics.view.View -import kotlinx.coroutines.* -import kotlinx.coroutines.channels.Channel -import mu.KotlinLogging -import org.opendc.compute.api.* -import org.opendc.compute.service.ComputeService -import org.opendc.compute.service.driver.Host -import org.opendc.compute.service.driver.HostListener -import org.opendc.compute.service.driver.HostState -import org.opendc.compute.service.scheduler.ComputeScheduler -import org.opendc.compute.simulator.SimHost -import org.opendc.experiments.capelin.monitor.ExperimentMetricExporter -import org.opendc.experiments.capelin.monitor.ExperimentMonitor -import org.opendc.experiments.capelin.trace.Sc20StreamingParquetTraceReader -import org.opendc.format.environment.EnvironmentReader -import org.opendc.format.trace.TraceReader -import org.opendc.simulator.compute.SimFairShareHypervisorProvider -import org.opendc.simulator.compute.interference.PerformanceInterferenceModel -import org.opendc.simulator.compute.workload.SimTraceWorkload -import org.opendc.simulator.compute.workload.SimWorkload -import org.opendc.simulator.failures.CorrelatedFaultInjector -import org.opendc.simulator.failures.FaultInjector -import org.opendc.telemetry.sdk.metrics.export.CoroutineMetricReader -import org.opendc.telemetry.sdk.toOtelClock -import java.io.File -import java.time.Clock -import kotlin.coroutines.resume -import kotlin.math.ln -import kotlin.math.max -import kotlin.random.Random - -/** - * The logger for this experiment. - */ -private val logger = KotlinLogging.logger {} - -/** - * Construct the failure domain for the experiments. - */ -public fun createFailureDomain( - coroutineScope: CoroutineScope, - clock: Clock, - seed: Int, - failureInterval: Double, - service: ComputeService, - chan: Channel<Unit> -): CoroutineScope { - val job = coroutineScope.launch { - chan.receive() - val random = Random(seed) - val injectors = mutableMapOf<String, FaultInjector>() - for (host in service.hosts) { - val cluster = host.meta["cluster"] as String - val injector = - injectors.getOrPut(cluster) { - createFaultInjector( - this, - clock, - random, - failureInterval - ) - } - injector.enqueue(host as SimHost) - } - } - return CoroutineScope(coroutineScope.coroutineContext + job) -} - -/** - * Obtain the [FaultInjector] to use for the experiments. - */ -public fun createFaultInjector( - coroutineScope: CoroutineScope, - clock: Clock, - random: Random, - failureInterval: Double -): FaultInjector { - // Parameters from A. Iosup, A Framework for the Study of Grid Inter-Operation Mechanisms, 2009 - // GRID'5000 - return CorrelatedFaultInjector( - coroutineScope, - clock, - iatScale = ln(failureInterval), iatShape = 1.03, // Hours - sizeScale = ln(2.0), sizeShape = ln(1.0), // Expect 2 machines, with variation of 1 - dScale = ln(60.0), dShape = ln(60.0 * 8), // Minutes - random = random - ) -} - -/** - * Create the trace reader from which the VM workloads are read. - */ -public fun createTraceReader( - path: File, - performanceInterferenceModel: PerformanceInterferenceModel, - vms: List<String>, - seed: Int -): Sc20StreamingParquetTraceReader { - return Sc20StreamingParquetTraceReader( - path, - performanceInterferenceModel, - vms, - Random(seed) - ) -} - -/** - * Construct the environment for a simulated compute service.. - */ -public suspend fun withComputeService( - clock: Clock, - meterProvider: MeterProvider, - environmentReader: EnvironmentReader, - scheduler: ComputeScheduler, - block: suspend CoroutineScope.(ComputeService) -> Unit -): Unit = coroutineScope { - val hosts = environmentReader - .use { it.read() } - .map { def -> - SimHost( - def.uid, - def.name, - def.model, - def.meta, - coroutineContext, - clock, - meterProvider.get("opendc-compute-simulator"), - SimFairShareHypervisorProvider(), - def.powerModel - ) - } - - val serviceMeter = meterProvider.get("opendc-compute") - val service = - ComputeService(coroutineContext, clock, serviceMeter, scheduler) - - for (host in hosts) { - service.addHost(host) - } - - try { - block(this, service) - } finally { - service.close() - hosts.forEach(SimHost::close) - } -} - -/** - * Attach the specified monitor to the VM provisioner. - */ -@OptIn(ExperimentalCoroutinesApi::class) -public suspend fun withMonitor( - monitor: ExperimentMonitor, - clock: Clock, - metricProducer: MetricProducer, - scheduler: ComputeService, - block: suspend CoroutineScope.() -> Unit -): Unit = coroutineScope { - val monitorJobs = mutableSetOf<Job>() - - // Monitor host events - for (host in scheduler.hosts) { - monitor.reportHostStateChange(clock.millis(), host, HostState.UP) - host.addListener(object : HostListener { - override fun onStateChanged(host: Host, newState: HostState) { - monitor.reportHostStateChange(clock.millis(), host, newState) - } - }) - } - - val reader = CoroutineMetricReader( - this, - listOf(metricProducer), - ExperimentMetricExporter(monitor, clock, scheduler.hosts.associateBy { it.uid.toString() }), - exportInterval = 5 * 60 * 1000 /* Every 5 min (which is the granularity of the workload trace) */ - ) - - try { - block(this) - } finally { - monitorJobs.forEach(Job::cancel) - reader.close() - monitor.close() - } -} - -public class ComputeMetrics { - public var submittedVms: Int = 0 - public var queuedVms: Int = 0 - public var runningVms: Int = 0 - public var unscheduledVms: Int = 0 - public var finishedVms: Int = 0 -} - -/** - * Collect the metrics of the compute service. - */ -public fun collectMetrics(metricProducer: MetricProducer): ComputeMetrics { - val metrics = metricProducer.collectAllMetrics().associateBy { it.name } - val res = ComputeMetrics() - try { - // Hack to extract metrics from OpenTelemetry SDK - res.submittedVms = metrics["servers.submitted"]?.longSumData?.points?.last()?.value?.toInt() ?: 0 - res.queuedVms = metrics["servers.waiting"]?.longSumData?.points?.last()?.value?.toInt() ?: 0 - res.unscheduledVms = metrics["servers.unscheduled"]?.longSumData?.points?.last()?.value?.toInt() ?: 0 - res.runningVms = metrics["servers.active"]?.longSumData?.points?.last()?.value?.toInt() ?: 0 - res.finishedVms = metrics["servers.finished"]?.longSumData?.points?.last()?.value?.toInt() ?: 0 - } catch (cause: Throwable) { - logger.warn(cause) { "Failed to collect metrics" } - } - return res -} - -/** - * Process the trace. - */ -public suspend fun processTrace( - clock: Clock, - reader: TraceReader<SimWorkload>, - scheduler: ComputeService, - chan: Channel<Unit>, - monitor: ExperimentMonitor -) { - val client = scheduler.newClient() - val image = client.newImage("vm-image") - var offset = Long.MIN_VALUE - try { - coroutineScope { - while (reader.hasNext()) { - val entry = reader.next() - - if (offset < 0) { - offset = entry.start - clock.millis() - } - - delay(max(0, (entry.start - offset) - clock.millis())) - launch { - chan.send(Unit) - val workload = SimTraceWorkload((entry.meta["workload"] as SimTraceWorkload).trace) - val server = client.newServer( - entry.name, - image, - client.newFlavor( - entry.name, - entry.meta["cores"] as Int, - entry.meta["required-memory"] as Long - ), - meta = entry.meta + mapOf("workload" to workload) - ) - - suspendCancellableCoroutine { cont -> - server.watch(object : ServerWatcher { - override fun onStateChanged(server: Server, newState: ServerState) { - monitor.reportVmStateChange(clock.millis(), server, newState) - - if (newState == ServerState.TERMINATED || newState == ServerState.ERROR) { - cont.resume(Unit) - } - } - }) - } - } - } - } - - yield() - } finally { - reader.close() - client.close() - } -} - -/** - * Create a [MeterProvider] instance for the experiment. - */ -public fun createMeterProvider(clock: Clock): MeterProvider { - val powerSelector = InstrumentSelector.builder() - .setInstrumentNameRegex("power\\.usage") - .setInstrumentType(InstrumentType.VALUE_RECORDER) - .build() - val powerView = View.builder() - .setAggregatorFactory(AggregatorFactory.lastValue()) - .build() - - return SdkMeterProvider - .builder() - .setClock(clock.toOtelClock()) - .registerView(powerSelector, powerView) - .build() -} diff --git a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/HorVerPortfolio.kt b/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/HorVerPortfolio.kt index e1cf8517..cd093e6c 100644 --- a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/HorVerPortfolio.kt +++ b/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/HorVerPortfolio.kt @@ -22,6 +22,8 @@ package org.opendc.experiments.capelin +import org.opendc.compute.workload.sampleByLoad +import org.opendc.compute.workload.trace import org.opendc.experiments.capelin.model.OperationalPhenomena import org.opendc.experiments.capelin.model.Topology import org.opendc.experiments.capelin.model.Workload @@ -44,10 +46,10 @@ public class HorVerPortfolio : Portfolio("horizontal_vs_vertical") { ) override val workload: Workload by anyOf( - Workload("solvinity", 0.1), - Workload("solvinity", 0.25), - Workload("solvinity", 0.5), - Workload("solvinity", 1.0) + Workload("solvinity", trace("solvinity").sampleByLoad(0.1)), + Workload("solvinity", trace("solvinity").sampleByLoad(0.25)), + Workload("solvinity", trace("solvinity").sampleByLoad(0.5)), + Workload("solvinity", trace("solvinity").sampleByLoad(1.0)) ) override val operationalPhenomena: OperationalPhenomena by anyOf( diff --git a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/MoreHpcPortfolio.kt b/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/MoreHpcPortfolio.kt index a995e467..73e59a58 100644 --- a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/MoreHpcPortfolio.kt +++ b/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/MoreHpcPortfolio.kt @@ -22,8 +22,10 @@ package org.opendc.experiments.capelin +import org.opendc.compute.workload.sampleByHpc +import org.opendc.compute.workload.sampleByHpcLoad +import org.opendc.compute.workload.trace import org.opendc.experiments.capelin.model.OperationalPhenomena -import org.opendc.experiments.capelin.model.SamplingStrategy import org.opendc.experiments.capelin.model.Topology import org.opendc.experiments.capelin.model.Workload import org.opendc.harness.dsl.anyOf @@ -40,13 +42,13 @@ public class MoreHpcPortfolio : Portfolio("more_hpc") { ) override val workload: Workload by anyOf( - Workload("solvinity", 0.0, samplingStrategy = SamplingStrategy.HPC), - Workload("solvinity", 0.25, samplingStrategy = SamplingStrategy.HPC), - Workload("solvinity", 0.5, samplingStrategy = SamplingStrategy.HPC), - Workload("solvinity", 1.0, samplingStrategy = SamplingStrategy.HPC), - Workload("solvinity", 0.25, samplingStrategy = SamplingStrategy.HPC_LOAD), - Workload("solvinity", 0.5, samplingStrategy = SamplingStrategy.HPC_LOAD), - Workload("solvinity", 1.0, samplingStrategy = SamplingStrategy.HPC_LOAD) + Workload("solvinity", trace("solvinity").sampleByHpc(0.0)), + Workload("solvinity", trace("solvinity").sampleByHpc(0.25)), + Workload("solvinity", trace("solvinity").sampleByHpc(0.5)), + Workload("solvinity", trace("solvinity").sampleByHpc(1.0)), + Workload("solvinity", trace("solvinity").sampleByHpcLoad(0.25)), + Workload("solvinity", trace("solvinity").sampleByHpcLoad(0.5)), + Workload("solvinity", trace("solvinity").sampleByHpcLoad(1.0)) ) override val operationalPhenomena: OperationalPhenomena by anyOf( diff --git a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/MoreVelocityPortfolio.kt b/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/MoreVelocityPortfolio.kt index 49559e0e..9d5717bb 100644 --- a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/MoreVelocityPortfolio.kt +++ b/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/MoreVelocityPortfolio.kt @@ -22,6 +22,8 @@ package org.opendc.experiments.capelin +import org.opendc.compute.workload.sampleByLoad +import org.opendc.compute.workload.trace import org.opendc.experiments.capelin.model.OperationalPhenomena import org.opendc.experiments.capelin.model.Topology import org.opendc.experiments.capelin.model.Workload @@ -40,10 +42,10 @@ public class MoreVelocityPortfolio : Portfolio("more_velocity") { ) override val workload: Workload by anyOf( - Workload("solvinity", 0.1), - Workload("solvinity", 0.25), - Workload("solvinity", 0.5), - Workload("solvinity", 1.0) + Workload("solvinity", trace("solvinity").sampleByLoad(0.1)), + Workload("solvinity", trace("solvinity").sampleByLoad(0.25)), + Workload("solvinity", trace("solvinity").sampleByLoad(0.5)), + Workload("solvinity", trace("solvinity").sampleByLoad(1.0)) ) override val operationalPhenomena: OperationalPhenomena by anyOf( diff --git a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/OperationalPhenomenaPortfolio.kt b/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/OperationalPhenomenaPortfolio.kt index 1aac4f9e..7ab586b3 100644 --- a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/OperationalPhenomenaPortfolio.kt +++ b/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/OperationalPhenomenaPortfolio.kt @@ -22,6 +22,8 @@ package org.opendc.experiments.capelin +import org.opendc.compute.workload.sampleByLoad +import org.opendc.compute.workload.trace import org.opendc.experiments.capelin.model.OperationalPhenomena import org.opendc.experiments.capelin.model.Topology import org.opendc.experiments.capelin.model.Workload @@ -36,10 +38,10 @@ public class OperationalPhenomenaPortfolio : Portfolio("operational_phenomena") ) override val workload: Workload by anyOf( - Workload("solvinity", 0.1), - Workload("solvinity", 0.25), - Workload("solvinity", 0.5), - Workload("solvinity", 1.0) + Workload("solvinity", trace("solvinity").sampleByLoad(0.1)), + Workload("solvinity", trace("solvinity").sampleByLoad(0.25)), + Workload("solvinity", trace("solvinity").sampleByLoad(0.5)), + Workload("solvinity", trace("solvinity").sampleByLoad(1.0)) ) override val operationalPhenomena: OperationalPhenomena by anyOf( diff --git a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/Portfolio.kt b/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/Portfolio.kt index b70eefb2..4e855f82 100644 --- a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/Portfolio.kt +++ b/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/Portfolio.kt @@ -23,38 +23,35 @@ package org.opendc.experiments.capelin import com.typesafe.config.ConfigFactory -import io.opentelemetry.sdk.metrics.export.MetricProducer -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.cancel -import kotlinx.coroutines.channels.Channel import mu.KotlinLogging -import org.opendc.compute.service.scheduler.* -import org.opendc.compute.service.scheduler.filters.ComputeCapabilitiesFilter -import org.opendc.compute.service.scheduler.filters.ComputeFilter -import org.opendc.compute.service.scheduler.weights.* -import org.opendc.experiments.capelin.model.CompositeWorkload +import org.opendc.compute.workload.ComputeWorkloadLoader +import org.opendc.compute.workload.ComputeWorkloadRunner +import org.opendc.compute.workload.createComputeScheduler +import org.opendc.compute.workload.export.parquet.ParquetComputeMetricExporter +import org.opendc.compute.workload.grid5000 +import org.opendc.compute.workload.topology.apply +import org.opendc.compute.workload.util.PerformanceInterferenceReader import org.opendc.experiments.capelin.model.OperationalPhenomena import org.opendc.experiments.capelin.model.Topology import org.opendc.experiments.capelin.model.Workload -import org.opendc.experiments.capelin.monitor.ParquetExperimentMonitor -import org.opendc.experiments.capelin.trace.Sc20ParquetTraceReader -import org.opendc.experiments.capelin.trace.Sc20RawParquetTraceReader -import org.opendc.format.environment.sc20.Sc20ClusterEnvironmentReader -import org.opendc.format.trace.PerformanceInterferenceModelReader +import org.opendc.experiments.capelin.topology.clusterTopology import org.opendc.harness.dsl.Experiment import org.opendc.harness.dsl.anyOf +import org.opendc.simulator.compute.kernel.interference.VmInterferenceModel import org.opendc.simulator.core.runBlockingSimulation +import org.opendc.telemetry.compute.collectServiceMetrics +import org.opendc.telemetry.sdk.metrics.export.CoroutineMetricReader import java.io.File +import java.time.Duration import java.util.* -import java.util.concurrent.ConcurrentHashMap -import kotlin.random.asKotlinRandom +import kotlin.math.roundToLong /** * A portfolio represents a collection of scenarios are tested for the work. * * @param name The name of the portfolio. */ -public abstract class Portfolio(name: String) : Experiment(name) { +abstract class Portfolio(name: String) : Experiment(name) { /** * The logger for this portfolio instance. */ @@ -71,147 +68,84 @@ public abstract class Portfolio(name: String) : Experiment(name) { private val vmPlacements by anyOf(emptyMap<String, String>()) /** - * The path to the performance interference model. - */ - private val performanceInterferenceModel by anyOf<PerformanceInterferenceModelReader?>(null) - - /** * The topology to test. */ - public abstract val topology: Topology + abstract val topology: Topology /** * The workload to test. */ - public abstract val workload: Workload + abstract val workload: Workload /** * The operational phenomenas to consider. */ - public abstract val operationalPhenomena: OperationalPhenomena + abstract val operationalPhenomena: OperationalPhenomena /** * The allocation policies to consider. */ - public abstract val allocationPolicy: String + abstract val allocationPolicy: String /** - * A map of trace readers. + * A helper class to load workload traces. */ - private val traceReaders = ConcurrentHashMap<String, Sc20RawParquetTraceReader>() + private val workloadLoader = ComputeWorkloadLoader(File(config.getString("trace-path"))) /** * Perform a single trial for this portfolio. */ - @OptIn(ExperimentalCoroutinesApi::class) override fun doRun(repeat: Int): Unit = runBlockingSimulation { val seeder = Random(repeat.toLong()) - val environment = Sc20ClusterEnvironmentReader(File(config.getString("env-path"), "${topology.name}.txt")) - - val chan = Channel<Unit>(Channel.CONFLATED) - val allocationPolicy = createComputeScheduler(seeder) - - val meterProvider = createMeterProvider(clock) - val workload = workload - val workloadNames = if (workload is CompositeWorkload) { - workload.workloads.map { it.name } - } else { - listOf(workload.name) - } - - val rawReaders = workloadNames.map { workloadName -> - traceReaders.computeIfAbsent(workloadName) { - logger.info { "Loading trace $workloadName" } - Sc20RawParquetTraceReader(File(config.getString("trace-path"), workloadName)) - } - } - val performanceInterferenceModel = performanceInterferenceModel - ?.takeIf { operationalPhenomena.hasInterference } - ?.construct(seeder.asKotlinRandom()) ?: emptyMap() - val trace = Sc20ParquetTraceReader(rawReaders, performanceInterferenceModel, workload, seeder.nextInt()) + val performanceInterferenceModel = if (operationalPhenomena.hasInterference) + PerformanceInterferenceReader() + .read(File(config.getString("interference-model"))) + .let { VmInterferenceModel(it, Random(seeder.nextLong())) } + else + null + + val computeScheduler = createComputeScheduler(allocationPolicy, seeder, vmPlacements) + val failureModel = + if (operationalPhenomena.failureFrequency > 0) + grid5000(Duration.ofSeconds((operationalPhenomena.failureFrequency * 60).roundToLong())) + else + null + val runner = ComputeWorkloadRunner( + coroutineContext, + clock, + computeScheduler, + failureModel, + performanceInterferenceModel + ) - val monitor = ParquetExperimentMonitor( + val exporter = ParquetComputeMetricExporter( File(config.getString("output-path")), "portfolio_id=$name/scenario_id=$id/run_id=$repeat", 4096 ) - - withComputeService(clock, meterProvider, environment, allocationPolicy) { scheduler -> - val failureDomain = if (operationalPhenomena.failureFrequency > 0) { - logger.debug("ENABLING failures") - createFailureDomain( - this, - clock, - seeder.nextInt(), - operationalPhenomena.failureFrequency, - scheduler, - chan - ) - } else { - null - } - - withMonitor(monitor, clock, meterProvider as MetricProducer, scheduler) { - processTrace( - clock, - trace, - scheduler, - chan, - monitor - ) - } - - failureDomain?.cancel() + val metricReader = CoroutineMetricReader(this, runner.producers, exporter) + val topology = clusterTopology(File(config.getString("env-path"), "${topology.name}.txt")) + + try { + // Instantiate the desired topology + runner.apply(topology) + + // Converge the workload trace + runner.run(workload.source.resolve(workloadLoader, seeder), seeder.nextLong()) + } finally { + runner.close() + metricReader.close() } - val monitorResults = collectMetrics(meterProvider as MetricProducer) - logger.debug { "Finish SUBMIT=${monitorResults.submittedVms} FAIL=${monitorResults.unscheduledVms} QUEUE=${monitorResults.queuedVms} RUNNING=${monitorResults.runningVms}" } - } - - /** - * Create the [ComputeScheduler] instance to use for the trial. - */ - private fun createComputeScheduler(seeder: Random): ComputeScheduler { - return when (allocationPolicy) { - "mem" -> FilterScheduler( - filters = listOf(ComputeFilter(), ComputeCapabilitiesFilter()), - weighers = listOf(MemoryWeigher() to -1.0) - ) - "mem-inv" -> FilterScheduler( - filters = listOf(ComputeFilter(), ComputeCapabilitiesFilter()), - weighers = listOf(MemoryWeigher() to -1.0) - ) - "core-mem" -> FilterScheduler( - filters = listOf(ComputeFilter(), ComputeCapabilitiesFilter()), - weighers = listOf(CoreMemoryWeigher() to -1.0) - ) - "core-mem-inv" -> FilterScheduler( - filters = listOf(ComputeFilter(), ComputeCapabilitiesFilter()), - weighers = listOf(CoreMemoryWeigher() to -1.0) - ) - "active-servers" -> FilterScheduler( - filters = listOf(ComputeFilter(), ComputeCapabilitiesFilter()), - weighers = listOf(ProvisionedCoresWeigher() to -1.0) - ) - "active-servers-inv" -> FilterScheduler( - filters = listOf(ComputeFilter(), ComputeCapabilitiesFilter()), - weighers = listOf(InstanceCountWeigher() to 1.0) - ) - "provisioned-cores" -> FilterScheduler( - filters = listOf(ComputeFilter(), ComputeCapabilitiesFilter()), - weighers = listOf(ProvisionedCoresWeigher() to -1.0) - ) - "provisioned-cores-inv" -> FilterScheduler( - filters = listOf(ComputeFilter(), ComputeCapabilitiesFilter()), - weighers = listOf(ProvisionedCoresWeigher() to 1.0) - ) - "random" -> FilterScheduler( - filters = listOf(ComputeFilter(), ComputeCapabilitiesFilter()), - weighers = listOf(RandomWeigher(Random(seeder.nextLong())) to 1.0) - ) - "replay" -> ReplayScheduler(vmPlacements) - else -> throw IllegalArgumentException("Unknown policy $allocationPolicy") + val monitorResults = collectServiceMetrics(runner.producers[0]) + logger.debug { + "Scheduler " + + "Success=${monitorResults.attemptsSuccess} " + + "Failure=${monitorResults.attemptsFailure} " + + "Error=${monitorResults.attemptsError} " + + "Pending=${monitorResults.serversPending} " + + "Active=${monitorResults.serversActive}" } } } diff --git a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/ReplayPortfolio.kt b/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/ReplayPortfolio.kt index b6d3b30c..17ec48d4 100644 --- a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/ReplayPortfolio.kt +++ b/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/ReplayPortfolio.kt @@ -22,6 +22,7 @@ package org.opendc.experiments.capelin +import org.opendc.compute.workload.trace import org.opendc.experiments.capelin.model.OperationalPhenomena import org.opendc.experiments.capelin.model.Topology import org.opendc.experiments.capelin.model.Workload @@ -36,7 +37,7 @@ public class ReplayPortfolio : Portfolio("replay") { ) override val workload: Workload by anyOf( - Workload("solvinity", 1.0) + Workload("solvinity", trace("solvinity")) ) override val operationalPhenomena: OperationalPhenomena by anyOf( diff --git a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/TestPortfolio.kt b/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/TestPortfolio.kt index 90840db8..98eb989d 100644 --- a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/TestPortfolio.kt +++ b/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/TestPortfolio.kt @@ -22,6 +22,7 @@ package org.opendc.experiments.capelin +import org.opendc.compute.workload.trace import org.opendc.experiments.capelin.model.OperationalPhenomena import org.opendc.experiments.capelin.model.Topology import org.opendc.experiments.capelin.model.Workload @@ -36,7 +37,7 @@ public class TestPortfolio : Portfolio("test") { ) override val workload: Workload by anyOf( - Workload("solvinity", 1.0) + Workload("solvinity", trace("solvinity")) ) override val operationalPhenomena: OperationalPhenomena by anyOf( diff --git a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/model/Workload.kt b/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/model/Workload.kt index c4ddd158..a2e71243 100644 --- a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/model/Workload.kt +++ b/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/model/Workload.kt @@ -22,23 +22,12 @@ package org.opendc.experiments.capelin.model -public enum class SamplingStrategy { - REGULAR, - HPC, - HPC_LOAD -} +import org.opendc.compute.workload.ComputeWorkload /** - * A workload that is considered for a scenario. - */ -public open class Workload( - public open val name: String, - public val fraction: Double, - public val samplingStrategy: SamplingStrategy = SamplingStrategy.REGULAR -) - -/** - * A workload that is composed of multiple workloads. + * A single workload originating from a trace. + * + * @param name the name of the workload. + * @param source The source of the workload data. */ -public class CompositeWorkload(override val name: String, public val workloads: List<Workload>, public val totalLoad: Double) : - Workload(name, -1.0) +data class Workload(val name: String, val source: ComputeWorkload) diff --git a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/monitor/ExperimentMetricExporter.kt b/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/monitor/ExperimentMetricExporter.kt deleted file mode 100644 index 54ab3b5b..00000000 --- a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/monitor/ExperimentMetricExporter.kt +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.experiments.capelin.monitor - -import io.opentelemetry.sdk.common.CompletableResultCode -import io.opentelemetry.sdk.metrics.data.MetricData -import io.opentelemetry.sdk.metrics.export.MetricExporter -import org.opendc.compute.service.driver.Host -import java.time.Clock - -/** - * A [MetricExporter] that exports the metrics to the [ExperimentMonitor]. - */ -public class ExperimentMetricExporter( - private val monitor: ExperimentMonitor, - private val clock: Clock, - private val hosts: Map<String, Host> -) : MetricExporter { - override fun export(metrics: Collection<MetricData>): CompletableResultCode { - val metricsByName = metrics.associateBy { it.name } - reportHostMetrics(metricsByName) - reportProvisionerMetrics(metricsByName) - return CompletableResultCode.ofSuccess() - } - - private fun reportHostMetrics(metrics: Map<String, MetricData>) { - val hostMetrics = mutableMapOf<String, HostMetrics>() - hosts.mapValuesTo(hostMetrics) { HostMetrics() } - - mapDoubleSummary(metrics["cpu.demand"], hostMetrics) { m, v -> - m.cpuDemand = v - } - - mapDoubleSummary(metrics["cpu.usage"], hostMetrics) { m, v -> - m.cpuUsage = v - } - - mapDoubleGauge(metrics["power.usage"], hostMetrics) { m, v -> - m.powerDraw = v - } - - mapDoubleSummary(metrics["cpu.work.total"], hostMetrics) { m, v -> - m.requestedBurst = v.toLong() - } - - mapDoubleSummary(metrics["cpu.work.granted"], hostMetrics) { m, v -> - m.grantedBurst = v.toLong() - } - - mapDoubleSummary(metrics["cpu.work.overcommit"], hostMetrics) { m, v -> - m.overcommissionedBurst = v.toLong() - } - - mapDoubleSummary(metrics["cpu.work.interfered"], hostMetrics) { m, v -> - m.interferedBurst = v.toLong() - } - - mapLongSum(metrics["guests.active"], hostMetrics) { m, v -> - m.numberOfDeployedImages = v.toInt() - } - - for ((id, hostMetric) in hostMetrics) { - val host = hosts.getValue(id) - monitor.reportHostSlice( - clock.millis(), - hostMetric.requestedBurst, - hostMetric.grantedBurst, - hostMetric.overcommissionedBurst, - hostMetric.interferedBurst, - hostMetric.cpuUsage, - hostMetric.cpuDemand, - hostMetric.powerDraw, - hostMetric.numberOfDeployedImages, - host - ) - } - } - - private fun mapDoubleSummary(data: MetricData?, hostMetrics: MutableMap<String, HostMetrics>, block: (HostMetrics, Double) -> Unit) { - val points = data?.doubleSummaryData?.points ?: emptyList() - for (point in points) { - val uid = point.labels["host"] - val hostMetric = hostMetrics[uid] - - if (hostMetric != null) { - // Take the average of the summary - val avg = (point.percentileValues[0].value + point.percentileValues[1].value) / 2 - block(hostMetric, avg) - } - } - } - - private fun mapDoubleGauge(data: MetricData?, hostMetrics: MutableMap<String, HostMetrics>, block: (HostMetrics, Double) -> Unit) { - val points = data?.doubleGaugeData?.points ?: emptyList() - for (point in points) { - val uid = point.labels["host"] - val hostMetric = hostMetrics[uid] - - if (hostMetric != null) { - block(hostMetric, point.value) - } - } - } - - private fun mapLongSum(data: MetricData?, hostMetrics: MutableMap<String, HostMetrics>, block: (HostMetrics, Long) -> Unit) { - val points = data?.longSumData?.points ?: emptyList() - for (point in points) { - val uid = point.labels["host"] - val hostMetric = hostMetrics[uid] - - if (hostMetric != null) { - block(hostMetric, point.value) - } - } - } - - private fun reportProvisionerMetrics(metrics: Map<String, MetricData>) { - val submittedVms = metrics["servers.submitted"]?.longSumData?.points?.last()?.value?.toInt() ?: 0 - val queuedVms = metrics["servers.waiting"]?.longSumData?.points?.last()?.value?.toInt() ?: 0 - val unscheduledVms = metrics["servers.unscheduled"]?.longSumData?.points?.last()?.value?.toInt() ?: 0 - val runningVms = metrics["servers.active"]?.longSumData?.points?.last()?.value?.toInt() ?: 0 - val finishedVms = metrics["servers.finished"]?.longSumData?.points?.last()?.value?.toInt() ?: 0 - val hosts = metrics["hosts.total"]?.longSumData?.points?.last()?.value?.toInt() ?: 0 - val availableHosts = metrics["hosts.available"]?.longSumData?.points?.last()?.value?.toInt() ?: 0 - - monitor.reportProvisionerMetrics( - clock.millis(), - hosts, - availableHosts, - submittedVms, - runningVms, - finishedVms, - queuedVms, - unscheduledVms - ) - } - - private class HostMetrics { - var requestedBurst: Long = 0 - var grantedBurst: Long = 0 - var overcommissionedBurst: Long = 0 - var interferedBurst: Long = 0 - var cpuUsage: Double = 0.0 - var cpuDemand: Double = 0.0 - var numberOfDeployedImages: Int = 0 - var powerDraw: Double = 0.0 - } - - override fun flush(): CompletableResultCode = CompletableResultCode.ofSuccess() - - override fun shutdown(): CompletableResultCode = CompletableResultCode.ofSuccess() -} diff --git a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/monitor/ExperimentMonitor.kt b/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/monitor/ExperimentMonitor.kt deleted file mode 100644 index 68631dee..00000000 --- a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/monitor/ExperimentMonitor.kt +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.experiments.capelin.monitor - -import org.opendc.compute.api.Server -import org.opendc.compute.api.ServerState -import org.opendc.compute.service.driver.Host -import org.opendc.compute.service.driver.HostState - -/** - * A monitor watches the events of an experiment. - */ -public interface ExperimentMonitor : AutoCloseable { - /** - * This method is invoked when the state of a VM changes. - */ - public fun reportVmStateChange(time: Long, server: Server, newState: ServerState) {} - - /** - * This method is invoked when the state of a host changes. - */ - public fun reportHostStateChange(time: Long, host: Host, newState: HostState) {} - - /** - * This method is invoked for a host for each slice that is finishes. - */ - public fun reportHostSlice( - time: Long, - requestedBurst: Long, - grantedBurst: Long, - overcommissionedBurst: Long, - interferedBurst: Long, - cpuUsage: Double, - cpuDemand: Double, - powerDraw: Double, - numberOfDeployedImages: Int, - host: Host - ) { - } - - /** - * This method is invoked for a provisioner event. - */ - public fun reportProvisionerMetrics( - time: Long, - totalHostCount: Int, - availableHostCount: Int, - totalVmCount: Int, - activeVmCount: Int, - inactiveVmCount: Int, - waitingVmCount: Int, - failedVmCount: Int - ) {} -} diff --git a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/monitor/ParquetExperimentMonitor.kt b/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/monitor/ParquetExperimentMonitor.kt deleted file mode 100644 index 983b4cff..00000000 --- a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/monitor/ParquetExperimentMonitor.kt +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.experiments.capelin.monitor - -import mu.KotlinLogging -import org.opendc.compute.api.Server -import org.opendc.compute.api.ServerState -import org.opendc.compute.service.driver.Host -import org.opendc.compute.service.driver.HostState -import org.opendc.experiments.capelin.telemetry.HostEvent -import org.opendc.experiments.capelin.telemetry.ProvisionerEvent -import org.opendc.experiments.capelin.telemetry.parquet.ParquetHostEventWriter -import org.opendc.experiments.capelin.telemetry.parquet.ParquetProvisionerEventWriter -import java.io.File - -/** - * The logger instance to use. - */ -private val logger = KotlinLogging.logger {} - -/** - * An [ExperimentMonitor] that logs the events to a Parquet file. - */ -public class ParquetExperimentMonitor(base: File, partition: String, bufferSize: Int) : ExperimentMonitor { - private val hostWriter = ParquetHostEventWriter( - File(base, "host-metrics/$partition/data.parquet"), - bufferSize - ) - private val provisionerWriter = ParquetProvisionerEventWriter( - File(base, "provisioner-metrics/$partition/data.parquet"), - bufferSize - ) - - override fun reportVmStateChange(time: Long, server: Server, newState: ServerState) {} - - override fun reportHostStateChange(time: Long, host: Host, newState: HostState) { - logger.debug { "Host ${host.uid} changed state $newState [$time]" } - } - - override fun reportHostSlice( - time: Long, - requestedBurst: Long, - grantedBurst: Long, - overcommissionedBurst: Long, - interferedBurst: Long, - cpuUsage: Double, - cpuDemand: Double, - powerDraw: Double, - numberOfDeployedImages: Int, - host: Host - ) { - hostWriter.write( - HostEvent( - time, - 5 * 60 * 1000L, - host, - numberOfDeployedImages, - requestedBurst, - grantedBurst, - overcommissionedBurst, - interferedBurst, - cpuUsage, - cpuDemand, - powerDraw, - host.model.cpuCount - ) - ) - } - - override fun reportProvisionerMetrics( - time: Long, - totalHostCount: Int, - availableHostCount: Int, - totalVmCount: Int, - activeVmCount: Int, - inactiveVmCount: Int, - waitingVmCount: Int, - failedVmCount: Int - ) { - provisionerWriter.write( - ProvisionerEvent( - time, - totalHostCount, - availableHostCount, - totalVmCount, - activeVmCount, - inactiveVmCount, - waitingVmCount, - failedVmCount - ) - ) - } - - override fun close() { - hostWriter.close() - provisionerWriter.close() - } -} diff --git a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/telemetry/Event.kt b/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/telemetry/Event.kt deleted file mode 100644 index c29e116e..00000000 --- a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/telemetry/Event.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2020 atlarge-research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.experiments.capelin.telemetry - -/** - * An event that occurs within the system. - */ -public abstract class Event(public val name: String) { - /** - * The time of occurrence of this event. - */ - public abstract val timestamp: Long -} diff --git a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/telemetry/HostEvent.kt b/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/telemetry/HostEvent.kt deleted file mode 100644 index 899fc9b1..00000000 --- a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/telemetry/HostEvent.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2020 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.experiments.capelin.telemetry - -import org.opendc.compute.service.driver.Host - -/** - * A periodic report of the host machine metrics. - */ -public data class HostEvent( - override val timestamp: Long, - public val duration: Long, - public val host: Host, - public val vmCount: Int, - public val requestedBurst: Long, - public val grantedBurst: Long, - public val overcommissionedBurst: Long, - public val interferedBurst: Long, - public val cpuUsage: Double, - public val cpuDemand: Double, - public val powerDraw: Double, - public val cores: Int -) : Event("host-metrics") diff --git a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/telemetry/ProvisionerEvent.kt b/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/telemetry/ProvisionerEvent.kt deleted file mode 100644 index 539c9bc9..00000000 --- a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/telemetry/ProvisionerEvent.kt +++ /dev/null @@ -1,39 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2020 atlarge-research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.experiments.capelin.telemetry - -/** - * A periodic report of the provisioner's metrics. - */ -public data class ProvisionerEvent( - override val timestamp: Long, - public val totalHostCount: Int, - public val availableHostCount: Int, - public val totalVmCount: Int, - public val activeVmCount: Int, - public val inactiveVmCount: Int, - public val waitingVmCount: Int, - public val failedVmCount: Int -) : Event("provisioner-metrics") diff --git a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/telemetry/RunEvent.kt b/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/telemetry/RunEvent.kt deleted file mode 100644 index 6c8fc941..00000000 --- a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/telemetry/RunEvent.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2020 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.experiments.capelin.telemetry - -import org.opendc.experiments.capelin.Portfolio - -/** - * A periodic report of the host machine metrics. - */ -public data class RunEvent( - val portfolio: Portfolio, - val repeat: Int, - override val timestamp: Long -) : Event("run") diff --git a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/telemetry/VmEvent.kt b/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/telemetry/VmEvent.kt deleted file mode 100644 index 7631f55f..00000000 --- a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/telemetry/VmEvent.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2020 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.experiments.capelin.telemetry - -import org.opendc.compute.api.Server - -/** - * A periodic report of a virtual machine's metrics. - */ -public data class VmEvent( - override val timestamp: Long, - public val duration: Long, - public val vm: Server, - public val host: Server, - public val requestedBurst: Long, - public val grantedBurst: Long, - public val overcommissionedBurst: Long, - public val interferedBurst: Long, - public val cpuUsage: Double, - public val cpuDemand: Double -) : Event("vm-metrics") diff --git a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/telemetry/parquet/ParquetEventWriter.kt b/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/telemetry/parquet/ParquetEventWriter.kt deleted file mode 100644 index 38930ee5..00000000 --- a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/telemetry/parquet/ParquetEventWriter.kt +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (c) 2020 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.experiments.capelin.telemetry.parquet - -import mu.KotlinLogging -import org.apache.avro.Schema -import org.apache.avro.generic.GenericData -import org.apache.hadoop.fs.Path -import org.apache.parquet.avro.AvroParquetWriter -import org.apache.parquet.hadoop.metadata.CompressionCodecName -import org.opendc.experiments.capelin.telemetry.Event -import java.io.Closeable -import java.io.File -import java.util.concurrent.ArrayBlockingQueue -import java.util.concurrent.BlockingQueue -import kotlin.concurrent.thread - -/** - * The logging instance to use. - */ -private val logger = KotlinLogging.logger {} - -/** - * A writer that writes events in Parquet format. - */ -public open class ParquetEventWriter<in T : Event>( - private val path: File, - private val schema: Schema, - private val converter: (T, GenericData.Record) -> Unit, - private val bufferSize: Int = 4096 -) : Runnable, Closeable { - /** - * The writer to write the Parquet file. - */ - private val writer = AvroParquetWriter.builder<GenericData.Record>(Path(path.absolutePath)) - .withSchema(schema) - .withCompressionCodec(CompressionCodecName.SNAPPY) - .withPageSize(4 * 1024 * 1024) // For compression - .withRowGroupSize(16 * 1024 * 1024) // For write buffering (Page size) - .build() - - /** - * The queue of commands to process. - */ - private val queue: BlockingQueue<Action> = ArrayBlockingQueue(bufferSize) - - /** - * The thread that is responsible for writing the Parquet records. - */ - private val writerThread = thread(start = false, name = "parquet-writer") { run() } - - /** - * Write the specified metrics to the database. - */ - public fun write(event: T) { - queue.put(Action.Write(event)) - } - - /** - * Signal the writer to stop. - */ - public override fun close() { - queue.put(Action.Stop) - writerThread.join() - } - - init { - writerThread.start() - } - - /** - * Start the writer thread. - */ - override fun run() { - try { - loop@ while (true) { - val action = queue.take() - when (action) { - is Action.Stop -> break@loop - is Action.Write<*> -> { - val record = GenericData.Record(schema) - @Suppress("UNCHECKED_CAST") - converter(action.event as T, record) - writer.write(record) - } - } - } - } catch (e: Throwable) { - logger.error("Writer failed", e) - } finally { - writer.close() - } - } - - public sealed class Action { - /** - * A poison pill that will stop the writer thread. - */ - public object Stop : Action() - - /** - * Write the specified metrics to the database. - */ - public data class Write<out T : Event>(val event: T) : Action() - } -} diff --git a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/telemetry/parquet/ParquetHostEventWriter.kt b/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/telemetry/parquet/ParquetHostEventWriter.kt deleted file mode 100644 index c8fe1cb2..00000000 --- a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/telemetry/parquet/ParquetHostEventWriter.kt +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (c) 2020 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.experiments.capelin.telemetry.parquet - -import org.apache.avro.Schema -import org.apache.avro.SchemaBuilder -import org.apache.avro.generic.GenericData -import org.opendc.experiments.capelin.telemetry.HostEvent -import java.io.File - -/** - * A Parquet event writer for [HostEvent]s. - */ -public class ParquetHostEventWriter(path: File, bufferSize: Int) : - ParquetEventWriter<HostEvent>(path, schema, convert, bufferSize) { - - override fun toString(): String = "host-writer" - - public companion object { - private val convert: (HostEvent, GenericData.Record) -> Unit = { event, record -> - // record.put("portfolio_id", event.run.parent.parent.id) - // record.put("scenario_id", event.run.parent.id) - // record.put("run_id", event.run.id) - record.put("host_id", event.host.name) - record.put("state", event.host.state.name) - record.put("timestamp", event.timestamp) - record.put("duration", event.duration) - record.put("vm_count", event.vmCount) - record.put("requested_burst", event.requestedBurst) - record.put("granted_burst", event.grantedBurst) - record.put("overcommissioned_burst", event.overcommissionedBurst) - record.put("interfered_burst", event.interferedBurst) - record.put("cpu_usage", event.cpuUsage) - record.put("cpu_demand", event.cpuDemand) - record.put("power_draw", event.powerDraw) - record.put("cores", event.cores) - } - - private val schema: Schema = SchemaBuilder - .record("host_metrics") - .namespace("org.opendc.experiments.sc20") - .fields() - // .name("portfolio_id").type().intType().noDefault() - // .name("scenario_id").type().intType().noDefault() - // .name("run_id").type().intType().noDefault() - .name("timestamp").type().longType().noDefault() - .name("duration").type().longType().noDefault() - .name("host_id").type().stringType().noDefault() - .name("state").type().stringType().noDefault() - .name("vm_count").type().intType().noDefault() - .name("requested_burst").type().longType().noDefault() - .name("granted_burst").type().longType().noDefault() - .name("overcommissioned_burst").type().longType().noDefault() - .name("interfered_burst").type().longType().noDefault() - .name("cpu_usage").type().doubleType().noDefault() - .name("cpu_demand").type().doubleType().noDefault() - .name("power_draw").type().doubleType().noDefault() - .name("cores").type().intType().noDefault() - .endRecord() - } -} diff --git a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/telemetry/parquet/ParquetProvisionerEventWriter.kt b/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/telemetry/parquet/ParquetProvisionerEventWriter.kt deleted file mode 100644 index 8feff8d9..00000000 --- a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/telemetry/parquet/ParquetProvisionerEventWriter.kt +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2020 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.experiments.capelin.telemetry.parquet - -import org.apache.avro.Schema -import org.apache.avro.SchemaBuilder -import org.apache.avro.generic.GenericData -import org.opendc.experiments.capelin.telemetry.ProvisionerEvent -import java.io.File - -/** - * A Parquet event writer for [ProvisionerEvent]s. - */ -public class ParquetProvisionerEventWriter(path: File, bufferSize: Int) : - ParquetEventWriter<ProvisionerEvent>(path, schema, convert, bufferSize) { - - override fun toString(): String = "provisioner-writer" - - public companion object { - private val convert: (ProvisionerEvent, GenericData.Record) -> Unit = { event, record -> - record.put("timestamp", event.timestamp) - record.put("host_total_count", event.totalHostCount) - record.put("host_available_count", event.availableHostCount) - record.put("vm_total_count", event.totalVmCount) - record.put("vm_active_count", event.activeVmCount) - record.put("vm_inactive_count", event.inactiveVmCount) - record.put("vm_waiting_count", event.waitingVmCount) - record.put("vm_failed_count", event.failedVmCount) - } - - private val schema: Schema = SchemaBuilder - .record("provisioner_metrics") - .namespace("org.opendc.experiments.sc20") - .fields() - .name("timestamp").type().longType().noDefault() - .name("host_total_count").type().intType().noDefault() - .name("host_available_count").type().intType().noDefault() - .name("vm_total_count").type().intType().noDefault() - .name("vm_active_count").type().intType().noDefault() - .name("vm_inactive_count").type().intType().noDefault() - .name("vm_waiting_count").type().intType().noDefault() - .name("vm_failed_count").type().intType().noDefault() - .endRecord() - } -} diff --git a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/telemetry/parquet/ParquetRunEventWriter.kt b/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/telemetry/parquet/ParquetRunEventWriter.kt deleted file mode 100644 index 946410eb..00000000 --- a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/telemetry/parquet/ParquetRunEventWriter.kt +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2020 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.experiments.capelin.telemetry.parquet - -import org.apache.avro.Schema -import org.apache.avro.SchemaBuilder -import org.apache.avro.generic.GenericData -import org.opendc.experiments.capelin.telemetry.RunEvent -import java.io.File - -/** - * A Parquet event writer for [RunEvent]s. - */ -public class ParquetRunEventWriter(path: File, bufferSize: Int) : - ParquetEventWriter<RunEvent>(path, schema, convert, bufferSize) { - - override fun toString(): String = "run-writer" - - public companion object { - private val convert: (RunEvent, GenericData.Record) -> Unit = { event, record -> - val portfolio = event.portfolio - record.put("portfolio_name", portfolio.name) - record.put("scenario_id", portfolio.id) - record.put("run_id", event.repeat) - record.put("topology", portfolio.topology.name) - record.put("workload_name", portfolio.workload.name) - record.put("workload_fraction", portfolio.workload.fraction) - record.put("workload_sampler", portfolio.workload.samplingStrategy) - record.put("allocation_policy", portfolio.allocationPolicy) - record.put("failure_frequency", portfolio.operationalPhenomena.failureFrequency) - record.put("interference", portfolio.operationalPhenomena.hasInterference) - record.put("seed", event.repeat) - } - - private val schema: Schema = SchemaBuilder - .record("runs") - .namespace("org.opendc.experiments.sc20") - .fields() - .name("portfolio_name").type().stringType().noDefault() - .name("scenario_id").type().intType().noDefault() - .name("run_id").type().intType().noDefault() - .name("topology").type().stringType().noDefault() - .name("workload_name").type().stringType().noDefault() - .name("workload_fraction").type().doubleType().noDefault() - .name("workload_sampler").type().stringType().noDefault() - .name("allocation_policy").type().stringType().noDefault() - .name("failure_frequency").type().doubleType().noDefault() - .name("interference").type().booleanType().noDefault() - .name("seed").type().intType().noDefault() - .endRecord() - } -} diff --git a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/topology/ClusterSpec.kt b/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/topology/ClusterSpec.kt new file mode 100644 index 00000000..b8b65d28 --- /dev/null +++ b/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/topology/ClusterSpec.kt @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.experiments.capelin.topology + +/** + * Definition of a compute cluster modeled in the simulation. + * + * @param id A unique identifier representing the compute cluster. + * @param name The name of the cluster. + * @param cpuCount The total number of CPUs in the cluster. + * @param cpuSpeed The speed of a CPU in the cluster in MHz. + * @param memCapacity The total memory capacity of the cluster (in MiB). + * @param hostCount The number of hosts in the cluster. + * @param memCapacityPerHost The memory capacity per host in the cluster (MiB). + * @param cpuCountPerHost The number of CPUs per host in the cluster. + */ +public data class ClusterSpec( + val id: String, + val name: String, + val cpuCount: Int, + val cpuSpeed: Double, + val memCapacity: Double, + val hostCount: Int, + val memCapacityPerHost: Double, + val cpuCountPerHost: Int +) diff --git a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/topology/ClusterSpecReader.kt b/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/topology/ClusterSpecReader.kt new file mode 100644 index 00000000..5a175f2c --- /dev/null +++ b/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/topology/ClusterSpecReader.kt @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.experiments.capelin.topology + +import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.databind.MappingIterator +import com.fasterxml.jackson.databind.ObjectReader +import com.fasterxml.jackson.dataformat.csv.CsvMapper +import com.fasterxml.jackson.dataformat.csv.CsvSchema +import java.io.File +import java.io.InputStream + +/** + * A helper class for reading a cluster specification file. + */ +class ClusterSpecReader { + /** + * The [CsvMapper] to map the environment file to an object. + */ + private val mapper = CsvMapper() + + /** + * The [ObjectReader] to convert the lines into objects. + */ + private val reader: ObjectReader = mapper.readerFor(Entry::class.java).with(schema) + + /** + * Read the specified [file]. + */ + fun read(file: File): List<ClusterSpec> { + return reader.readValues<Entry>(file).use { read(it) } + } + + /** + * Read the specified [input]. + */ + fun read(input: InputStream): List<ClusterSpec> { + return reader.readValues<Entry>(input).use { read(it) } + } + + /** + * Convert the specified [MappingIterator] into a list of [ClusterSpec]s. + */ + private fun read(it: MappingIterator<Entry>): List<ClusterSpec> { + val result = mutableListOf<ClusterSpec>() + + for (entry in it) { + val def = ClusterSpec( + entry.id, + entry.name, + entry.cpuCount, + entry.cpuSpeed * 1000, // Convert to MHz + entry.memCapacity * 1000, // Convert to MiB + entry.hostCount, + entry.memCapacityPerHost * 1000, + entry.cpuCountPerHost + ) + result.add(def) + } + + return result + } + + private open class Entry( + @JsonProperty("ClusterID") + val id: String, + @JsonProperty("ClusterName") + val name: String, + @JsonProperty("Cores") + val cpuCount: Int, + @JsonProperty("Speed") + val cpuSpeed: Double, + @JsonProperty("Memory") + val memCapacity: Double, + @JsonProperty("numberOfHosts") + val hostCount: Int, + @JsonProperty("memoryCapacityPerHost") + val memCapacityPerHost: Double, + @JsonProperty("coreCountPerHost") + val cpuCountPerHost: Int + ) + + companion object { + /** + * The [CsvSchema] that is used to parse the trace. + */ + private val schema = CsvSchema.builder() + .addColumn("ClusterID", CsvSchema.ColumnType.STRING) + .addColumn("ClusterName", CsvSchema.ColumnType.STRING) + .addColumn("Cores", CsvSchema.ColumnType.NUMBER) + .addColumn("Speed", CsvSchema.ColumnType.NUMBER) + .addColumn("Memory", CsvSchema.ColumnType.NUMBER) + .addColumn("numberOfHosts", CsvSchema.ColumnType.NUMBER) + .addColumn("memoryCapacityPerHost", CsvSchema.ColumnType.NUMBER) + .addColumn("coreCountPerHost", CsvSchema.ColumnType.NUMBER) + .setAllowComments(true) + .setColumnSeparator(';') + .setUseHeader(true) + .build() + } +} diff --git a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/topology/TopologyFactories.kt b/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/topology/TopologyFactories.kt new file mode 100644 index 00000000..5ab4261a --- /dev/null +++ b/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/topology/TopologyFactories.kt @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +@file:JvmName("TopologyFactories") +package org.opendc.experiments.capelin.topology + +import org.opendc.compute.workload.topology.HostSpec +import org.opendc.compute.workload.topology.Topology +import org.opendc.simulator.compute.model.MachineModel +import org.opendc.simulator.compute.model.MemoryUnit +import org.opendc.simulator.compute.model.ProcessingNode +import org.opendc.simulator.compute.model.ProcessingUnit +import org.opendc.simulator.compute.power.LinearPowerModel +import org.opendc.simulator.compute.power.PowerModel +import org.opendc.simulator.compute.power.SimplePowerDriver +import java.io.File +import java.io.InputStream +import java.util.* +import kotlin.math.roundToLong + +/** + * A [ClusterSpecReader] that is used to read the cluster definition file. + */ +private val reader = ClusterSpecReader() + +/** + * Construct a [Topology] from the specified [file]. + */ +fun clusterTopology( + file: File, + powerModel: PowerModel = LinearPowerModel(350.0, idlePower = 200.0), + random: Random = Random(0) +): Topology = clusterTopology(reader.read(file), powerModel, random) + +/** + * Construct a [Topology] from the specified [input]. + */ +fun clusterTopology( + input: InputStream, + powerModel: PowerModel = LinearPowerModel(350.0, idlePower = 200.0), + random: Random = Random(0) +): Topology = clusterTopology(reader.read(input), powerModel, random) + +/** + * Construct a [Topology] from the given list of [clusters]. + */ +fun clusterTopology( + clusters: List<ClusterSpec>, + powerModel: PowerModel, + random: Random = Random(0) +): Topology { + return object : Topology { + override fun resolve(): List<HostSpec> { + val hosts = mutableListOf<HostSpec>() + for (cluster in clusters) { + val cpuSpeed = cluster.cpuSpeed + val memoryPerHost = cluster.memCapacityPerHost.roundToLong() + + val unknownProcessingNode = ProcessingNode("unknown", "unknown", "unknown", cluster.cpuCountPerHost) + val unknownMemoryUnit = MemoryUnit("unknown", "unknown", -1.0, memoryPerHost) + val machineModel = MachineModel( + List(cluster.cpuCountPerHost) { coreId -> ProcessingUnit(unknownProcessingNode, coreId, cpuSpeed) }, + listOf(unknownMemoryUnit) + ) + + repeat(cluster.hostCount) { + val spec = HostSpec( + UUID(random.nextLong(), it.toLong()), + "node-${cluster.name}-$it", + mapOf("cluster" to cluster.id), + machineModel, + SimplePowerDriver(powerModel) + ) + + hosts += spec + } + } + + return hosts + } + + override fun toString(): String = "ClusterSpecTopology" + } +} diff --git a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/trace/Sc20ParquetTraceReader.kt b/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/trace/Sc20ParquetTraceReader.kt deleted file mode 100644 index a8462a51..00000000 --- a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/trace/Sc20ParquetTraceReader.kt +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (c) 2020 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.experiments.capelin.trace - -import org.opendc.experiments.capelin.model.CompositeWorkload -import org.opendc.experiments.capelin.model.Workload -import org.opendc.format.trace.TraceEntry -import org.opendc.format.trace.TraceReader -import org.opendc.simulator.compute.interference.IMAGE_PERF_INTERFERENCE_MODEL -import org.opendc.simulator.compute.interference.PerformanceInterferenceModel -import org.opendc.simulator.compute.workload.SimWorkload -import java.util.TreeSet - -/** - * A [TraceReader] for the internal VM workload trace format. - * - * @param reader The internal trace reader to use. - * @param performanceInterferenceModel The performance model covering the workload in the VM trace. - * @param run The run to which this reader belongs. - */ -@OptIn(ExperimentalStdlibApi::class) -public class Sc20ParquetTraceReader( - rawReaders: List<Sc20RawParquetTraceReader>, - performanceInterferenceModel: Map<String, PerformanceInterferenceModel>, - workload: Workload, - seed: Int -) : TraceReader<SimWorkload> { - /** - * The iterator over the actual trace. - */ - private val iterator: Iterator<TraceEntry<SimWorkload>> = - rawReaders - .map { it.read() } - .run { - if (workload is CompositeWorkload) { - this.zip(workload.workloads) - } else { - this.zip(listOf(workload)) - } - } - .map { sampleWorkload(it.first, workload, it.second, seed) } - .flatten() - .run { - // Apply performance interference model - if (performanceInterferenceModel.isEmpty()) - this - else { - map { entry -> - val id = entry.name - val relevantPerformanceInterferenceModelItems = - performanceInterferenceModel[id] ?: PerformanceInterferenceModel(TreeSet()) - - entry.copy(meta = entry.meta + mapOf(IMAGE_PERF_INTERFERENCE_MODEL to relevantPerformanceInterferenceModelItems)) - } - } - } - .iterator() - - override fun hasNext(): Boolean = iterator.hasNext() - - override fun next(): TraceEntry<SimWorkload> = iterator.next() - - override fun close() {} -} diff --git a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/trace/Sc20RawParquetTraceReader.kt b/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/trace/Sc20RawParquetTraceReader.kt deleted file mode 100644 index ffbf46d4..00000000 --- a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/trace/Sc20RawParquetTraceReader.kt +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright (c) 2020 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.experiments.capelin.trace - -import mu.KotlinLogging -import org.apache.avro.generic.GenericData -import org.apache.hadoop.fs.Path -import org.apache.parquet.avro.AvroParquetReader -import org.opendc.format.trace.TraceEntry -import org.opendc.format.trace.TraceReader -import org.opendc.simulator.compute.workload.SimTraceWorkload -import org.opendc.simulator.compute.workload.SimWorkload -import java.io.File -import java.util.UUID - -private val logger = KotlinLogging.logger {} - -/** - * A [TraceReader] for the internal VM workload trace format. - * - * @param path The directory of the traces. - */ -@OptIn(ExperimentalStdlibApi::class) -public class Sc20RawParquetTraceReader(private val path: File) { - /** - * Read the fragments into memory. - */ - private fun parseFragments(path: File): Map<String, List<SimTraceWorkload.Fragment>> { - @Suppress("DEPRECATION") - val reader = AvroParquetReader.builder<GenericData.Record>(Path(path.absolutePath, "trace.parquet")) - .disableCompatibility() - .build() - - val fragments = mutableMapOf<String, MutableList<SimTraceWorkload.Fragment>>() - - return try { - while (true) { - val record = reader.read() ?: break - - val id = record["id"].toString() - val duration = record["duration"] as Long - val cores = record["cores"] as Int - val cpuUsage = record["cpuUsage"] as Double - - val fragment = SimTraceWorkload.Fragment( - duration, - cpuUsage, - cores - ) - - fragments.getOrPut(id) { mutableListOf() }.add(fragment) - } - - fragments - } finally { - reader.close() - } - } - - /** - * Read the metadata into a workload. - */ - private fun parseMeta(path: File, fragments: Map<String, List<SimTraceWorkload.Fragment>>): List<TraceEntry<SimWorkload>> { - @Suppress("DEPRECATION") - val metaReader = AvroParquetReader.builder<GenericData.Record>(Path(path.absolutePath, "meta.parquet")) - .disableCompatibility() - .build() - - var counter = 0 - val entries = mutableListOf<TraceEntry<SimWorkload>>() - - return try { - while (true) { - val record = metaReader.read() ?: break - - val id = record["id"].toString() - if (!fragments.containsKey(id)) { - continue - } - - val submissionTime = record["submissionTime"] as Long - val endTime = record["endTime"] as Long - val maxCores = record["maxCores"] as Int - val requiredMemory = record["requiredMemory"] as Long - val uid = UUID.nameUUIDFromBytes("$id-${counter++}".toByteArray()) - - val vmFragments = fragments.getValue(id).asSequence() - val totalLoad = vmFragments.sumByDouble { it.usage } * 5 * 60 // avg MHz * duration = MFLOPs - val workload = SimTraceWorkload(vmFragments) - entries.add( - TraceEntry( - uid, id, submissionTime, workload, - mapOf( - "submit-time" to submissionTime, - "end-time" to endTime, - "total-load" to totalLoad, - "cores" to maxCores, - "required-memory" to requiredMemory, - "workload" to workload - ) - ) - ) - } - - entries - } catch (e: Exception) { - e.printStackTrace() - throw e - } finally { - metaReader.close() - } - } - - /** - * The entries in the trace. - */ - private val entries: List<TraceEntry<SimWorkload>> - - init { - val fragments = parseFragments(path) - entries = parseMeta(path, fragments) - } - - /** - * Read the entries in the trace. - */ - public fun read(): List<TraceEntry<SimWorkload>> = entries - - /** - * Create a [TraceReader] instance. - */ - public fun createReader(): TraceReader<SimWorkload> { - return object : TraceReader<SimWorkload>, Iterator<TraceEntry<SimWorkload>> by entries.iterator() { - override fun close() {} - } - } -} diff --git a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/trace/Sc20StreamingParquetTraceReader.kt b/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/trace/Sc20StreamingParquetTraceReader.kt deleted file mode 100644 index c5294b55..00000000 --- a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/trace/Sc20StreamingParquetTraceReader.kt +++ /dev/null @@ -1,284 +0,0 @@ -/* - * Copyright (c) 2020 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.experiments.capelin.trace - -import mu.KotlinLogging -import org.apache.avro.generic.GenericData -import org.apache.hadoop.fs.Path -import org.apache.parquet.avro.AvroParquetReader -import org.apache.parquet.filter2.compat.FilterCompat -import org.apache.parquet.filter2.predicate.FilterApi -import org.apache.parquet.filter2.predicate.Statistics -import org.apache.parquet.filter2.predicate.UserDefinedPredicate -import org.apache.parquet.io.api.Binary -import org.opendc.format.trace.TraceEntry -import org.opendc.format.trace.TraceReader -import org.opendc.simulator.compute.interference.IMAGE_PERF_INTERFERENCE_MODEL -import org.opendc.simulator.compute.interference.PerformanceInterferenceModel -import org.opendc.simulator.compute.workload.SimTraceWorkload -import org.opendc.simulator.compute.workload.SimWorkload -import java.io.File -import java.io.Serializable -import java.util.SortedSet -import java.util.TreeSet -import java.util.UUID -import java.util.concurrent.ArrayBlockingQueue -import kotlin.concurrent.thread -import kotlin.random.Random - -private val logger = KotlinLogging.logger {} - -/** - * A [TraceReader] for the internal VM workload trace format that streams workloads on the fly. - * - * @param traceFile The directory of the traces. - * @param performanceInterferenceModel The performance model covering the workload in the VM trace. - */ -@OptIn(ExperimentalStdlibApi::class) -public class Sc20StreamingParquetTraceReader( - traceFile: File, - performanceInterferenceModel: PerformanceInterferenceModel? = null, - selectedVms: List<String> = emptyList(), - random: Random -) : TraceReader<SimWorkload> { - /** - * The internal iterator to use for this reader. - */ - private val iterator: Iterator<TraceEntry<SimWorkload>> - - /** - * The intermediate buffer to store the read records in. - */ - private val queue = ArrayBlockingQueue<Pair<String, SimTraceWorkload.Fragment>>(1024) - - /** - * An optional filter for filtering the selected VMs - */ - private val filter = - if (selectedVms.isEmpty()) - null - else - FilterCompat.get( - FilterApi.userDefined( - FilterApi.binaryColumn("id"), - SelectedVmFilter( - TreeSet(selectedVms) - ) - ) - ) - - /** - * A poisonous fragment. - */ - private val poison = Pair("\u0000", SimTraceWorkload.Fragment(0, 0.0, 0)) - - /** - * The thread to read the records in. - */ - private val readerThread = thread(start = true, name = "sc20-reader") { - @Suppress("DEPRECATION") - val reader = AvroParquetReader.builder<GenericData.Record>(Path(traceFile.absolutePath, "trace.parquet")) - .disableCompatibility() - .run { if (filter != null) withFilter(filter) else this } - .build() - - try { - while (true) { - val record = reader.read() - - if (record == null) { - queue.put(poison) - break - } - - val id = record["id"].toString() - val duration = record["duration"] as Long - val cores = record["cores"] as Int - val cpuUsage = record["cpuUsage"] as Double - - val fragment = SimTraceWorkload.Fragment( - duration, - cpuUsage, - cores - ) - - queue.put(id to fragment) - } - } catch (e: InterruptedException) { - // Do not rethrow this - } finally { - reader.close() - } - } - - /** - * Fill the buffers with the VMs - */ - private fun pull(buffers: Map<String, List<MutableList<SimTraceWorkload.Fragment>>>) { - if (!hasNext) { - return - } - - val fragments = mutableListOf<Pair<String, SimTraceWorkload.Fragment>>() - queue.drainTo(fragments) - - for ((id, fragment) in fragments) { - if (id == poison.first) { - hasNext = false - return - } - buffers[id]?.forEach { it.add(fragment) } - } - } - - /** - * A flag to indicate whether the reader has more entries. - */ - private var hasNext: Boolean = true - - /** - * Initialize the reader. - */ - init { - val takenIds = mutableSetOf<UUID>() - val entries = mutableMapOf<String, GenericData.Record>() - val buffers = mutableMapOf<String, MutableList<MutableList<SimTraceWorkload.Fragment>>>() - - @Suppress("DEPRECATION") - val metaReader = AvroParquetReader.builder<GenericData.Record>(Path(traceFile.absolutePath, "meta.parquet")) - .disableCompatibility() - .run { if (filter != null) withFilter(filter) else this } - .build() - - while (true) { - val record = metaReader.read() ?: break - val id = record["id"].toString() - entries[id] = record - } - - metaReader.close() - - val selection = if (selectedVms.isEmpty()) entries.keys else selectedVms - - // Create the entry iterator - iterator = selection.asSequence() - .mapNotNull { entries[it] } - .mapIndexed { index, record -> - val id = record["id"].toString() - val submissionTime = record["submissionTime"] as Long - val endTime = record["endTime"] as Long - val maxCores = record["maxCores"] as Int - val requiredMemory = record["requiredMemory"] as Long - val uid = UUID.nameUUIDFromBytes("$id-$index".toByteArray()) - - assert(uid !in takenIds) - takenIds += uid - - logger.info("Processing VM $id") - - val internalBuffer = mutableListOf<SimTraceWorkload.Fragment>() - val externalBuffer = mutableListOf<SimTraceWorkload.Fragment>() - buffers.getOrPut(id) { mutableListOf() }.add(externalBuffer) - val fragments = sequence { - var time = submissionTime - repeat@ while (true) { - if (externalBuffer.isEmpty()) { - if (hasNext) { - pull(buffers) - continue - } else { - break - } - } - - internalBuffer.addAll(externalBuffer) - externalBuffer.clear() - - for (fragment in internalBuffer) { - yield(fragment) - - time += fragment.duration - if (time >= endTime) { - break@repeat - } - } - - internalBuffer.clear() - } - - buffers.remove(id) - } - val relevantPerformanceInterferenceModelItems = - if (performanceInterferenceModel != null) - PerformanceInterferenceModel( - performanceInterferenceModel.items.filter { it.workloadNames.contains(id) }.toSortedSet(), - Random(random.nextInt()) - ) - else - null - val workload = SimTraceWorkload(fragments) - val meta = mapOf( - "cores" to maxCores, - "required-memory" to requiredMemory, - "workload" to workload - ) - - TraceEntry( - uid, id, submissionTime, workload, - if (performanceInterferenceModel != null) - meta + mapOf(IMAGE_PERF_INTERFERENCE_MODEL to relevantPerformanceInterferenceModelItems as Any) - else - meta - ) - } - .sortedBy { it.start } - .toList() - .iterator() - } - - override fun hasNext(): Boolean = iterator.hasNext() - - override fun next(): TraceEntry<SimWorkload> = iterator.next() - - override fun close() { - readerThread.interrupt() - } - - private class SelectedVmFilter(val selectedVms: SortedSet<String>) : UserDefinedPredicate<Binary>(), Serializable { - override fun keep(value: Binary?): Boolean = value != null && selectedVms.contains(value.toStringUsingUTF8()) - - override fun canDrop(statistics: Statistics<Binary>): Boolean { - val min = statistics.min - val max = statistics.max - - return selectedVms.subSet(min.toStringUsingUTF8(), max.toStringUsingUTF8() + "\u0000").isEmpty() - } - - override fun inverseCanDrop(statistics: Statistics<Binary>): Boolean { - val min = statistics.min - val max = statistics.max - - return selectedVms.subSet(min.toStringUsingUTF8(), max.toStringUsingUTF8() + "\u0000").isNotEmpty() - } - } -} diff --git a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/trace/Sc20TraceConverter.kt b/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/trace/Sc20TraceConverter.kt deleted file mode 100644 index 7713c06f..00000000 --- a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/trace/Sc20TraceConverter.kt +++ /dev/null @@ -1,621 +0,0 @@ -/* - * Copyright (c) 2020 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.experiments.capelin.trace - -import com.github.ajalt.clikt.core.CliktCommand -import com.github.ajalt.clikt.parameters.arguments.argument -import com.github.ajalt.clikt.parameters.groups.OptionGroup -import com.github.ajalt.clikt.parameters.groups.groupChoice -import com.github.ajalt.clikt.parameters.options.convert -import com.github.ajalt.clikt.parameters.options.default -import com.github.ajalt.clikt.parameters.options.defaultLazy -import com.github.ajalt.clikt.parameters.options.option -import com.github.ajalt.clikt.parameters.options.required -import com.github.ajalt.clikt.parameters.options.split -import com.github.ajalt.clikt.parameters.types.file -import com.github.ajalt.clikt.parameters.types.long -import me.tongfei.progressbar.ProgressBar -import org.apache.avro.Schema -import org.apache.avro.SchemaBuilder -import org.apache.avro.generic.GenericData -import org.apache.hadoop.fs.Path -import org.apache.parquet.avro.AvroParquetWriter -import org.apache.parquet.hadoop.ParquetWriter -import org.apache.parquet.hadoop.metadata.CompressionCodecName -import org.opendc.format.trace.sc20.Sc20VmPlacementReader -import java.io.BufferedReader -import java.io.File -import java.io.FileReader -import java.util.Random -import kotlin.math.max -import kotlin.math.min - -/** - * Represents the command for converting traces - */ -public class TraceConverterCli : CliktCommand(name = "trace-converter") { - /** - * The directory where the trace should be stored. - */ - private val outputPath by option("-O", "--output", help = "path to store the trace") - .file(canBeFile = false, mustExist = false) - .defaultLazy { File("output") } - - /** - * The directory where the input trace is located. - */ - private val inputPath by argument("input", help = "path to the input trace") - .file(canBeFile = false) - - /** - * The input type of the trace. - */ - private val type by option("-t", "--type", help = "input type of trace").groupChoice( - "solvinity" to SolvinityConversion(), - "bitbrains" to BitbrainsConversion(), - "azure" to AzureConversion() - ) - - override fun run() { - val metaSchema = SchemaBuilder - .record("meta") - .namespace("org.opendc.format.sc20") - .fields() - .name("id").type().stringType().noDefault() - .name("submissionTime").type().longType().noDefault() - .name("endTime").type().longType().noDefault() - .name("maxCores").type().intType().noDefault() - .name("requiredMemory").type().longType().noDefault() - .endRecord() - val schema = SchemaBuilder - .record("trace") - .namespace("org.opendc.format.sc20") - .fields() - .name("id").type().stringType().noDefault() - .name("time").type().longType().noDefault() - .name("duration").type().longType().noDefault() - .name("cores").type().intType().noDefault() - .name("cpuUsage").type().doubleType().noDefault() - .name("flops").type().longType().noDefault() - .endRecord() - - val metaParquet = File(outputPath, "meta.parquet") - val traceParquet = File(outputPath, "trace.parquet") - - if (metaParquet.exists()) { - metaParquet.delete() - } - if (traceParquet.exists()) { - traceParquet.delete() - } - - val metaWriter = AvroParquetWriter.builder<GenericData.Record>(Path(metaParquet.toURI())) - .withSchema(metaSchema) - .withCompressionCodec(CompressionCodecName.SNAPPY) - .withPageSize(4 * 1024 * 1024) // For compression - .withRowGroupSize(16 * 1024 * 1024) // For write buffering (Page size) - .build() - - val writer = AvroParquetWriter.builder<GenericData.Record>(Path(traceParquet.toURI())) - .withSchema(schema) - .withCompressionCodec(CompressionCodecName.SNAPPY) - .withPageSize(4 * 1024 * 1024) // For compression - .withRowGroupSize(16 * 1024 * 1024) // For write buffering (Page size) - .build() - - try { - val type = type ?: throw IllegalArgumentException("Invalid trace conversion") - val allFragments = type.read(inputPath, metaSchema, metaWriter) - allFragments.sortWith(compareBy<Fragment> { it.tick }.thenBy { it.id }) - - for (fragment in allFragments) { - val record = GenericData.Record(schema) - record.put("id", fragment.id) - record.put("time", fragment.tick) - record.put("duration", fragment.duration) - record.put("cores", fragment.cores) - record.put("cpuUsage", fragment.usage) - record.put("flops", fragment.flops) - - writer.write(record) - } - } finally { - writer.close() - metaWriter.close() - } - } -} - -/** - * The supported trace conversions. - */ -public sealed class TraceConversion(name: String) : OptionGroup(name) { - /** - * Read the fragments of the trace. - */ - public abstract fun read( - traceDirectory: File, - metaSchema: Schema, - metaWriter: ParquetWriter<GenericData.Record> - ): MutableList<Fragment> -} - -public class SolvinityConversion : TraceConversion("Solvinity") { - private val clusters by option() - .split(",") - - private val vmPlacements by option("--vm-placements", help = "file containing the VM placements") - .file(canBeDir = false) - .convert { it.inputStream().buffered().use { Sc20VmPlacementReader(it).construct() } } - .required() - - override fun read( - traceDirectory: File, - metaSchema: Schema, - metaWriter: ParquetWriter<GenericData.Record> - ): MutableList<Fragment> { - val clusters = clusters?.toSet() ?: emptySet() - val timestampCol = 0 - val cpuUsageCol = 1 - val coreCol = 12 - val provisionedMemoryCol = 20 - val traceInterval = 5 * 60 * 1000L - - // Identify start time of the entire trace - var minTimestamp = Long.MAX_VALUE - traceDirectory.walk() - .filterNot { it.isDirectory } - .filter { it.extension == "csv" || it.extension == "txt" } - .toList() - .forEach file@{ vmFile -> - BufferedReader(FileReader(vmFile)).use { reader -> - reader.lineSequence() - .chunked(128) - .forEach { lines -> - for (line in lines) { - // Ignore comments in the trace - if (line.startsWith("#") || line.isBlank()) { - continue - } - - val vmId = vmFile.name - - // Check if VM in topology - val clusterName = vmPlacements[vmId] - if (clusterName == null || !clusters.contains(clusterName)) { - continue - } - - val values = line.split("\t") - val timestamp = (values[timestampCol].trim().toLong() - 5 * 60) * 1000L - - if (timestamp < minTimestamp) { - minTimestamp = timestamp - } - return@file - } - } - } - } - - println("Start of trace at $minTimestamp") - - val allFragments = mutableListOf<Fragment>() - - val begin = 15 * 24 * 60 * 60 * 1000L - val end = 45 * 24 * 60 * 60 * 1000L - - traceDirectory.walk() - .filterNot { it.isDirectory } - .filter { it.extension == "csv" || it.extension == "txt" } - .toList() - .forEach { vmFile -> - println(vmFile) - - var vmId = "" - var maxCores = -1 - var requiredMemory = -1L - var cores: Int - var minTime = Long.MAX_VALUE - - val flopsFragments = sequence { - var last: Fragment? = null - - BufferedReader(FileReader(vmFile)).use { reader -> - reader.lineSequence() - .chunked(128) - .forEach { lines -> - for (line in lines) { - // Ignore comments in the trace - if (line.startsWith("#") || line.isBlank()) { - continue - } - - val values = line.split("\t") - - vmId = vmFile.name - - // Check if VM in topology - val clusterName = vmPlacements[vmId] - if (clusterName == null || !clusters.contains(clusterName)) { - continue - } - - val timestamp = - (values[timestampCol].trim().toLong() - 5 * 60) * 1000L - minTimestamp - if (begin > timestamp || timestamp > end) { - continue - } - - cores = values[coreCol].trim().toInt() - requiredMemory = max(requiredMemory, values[provisionedMemoryCol].trim().toLong()) - maxCores = max(maxCores, cores) - minTime = min(minTime, timestamp) - val cpuUsage = values[cpuUsageCol].trim().toDouble() // MHz - requiredMemory = max(requiredMemory, values[provisionedMemoryCol].trim().toLong()) - maxCores = max(maxCores, cores) - - val flops: Long = (cpuUsage * 5 * 60).toLong() - - last = if (last != null && last!!.flops == 0L && flops == 0L) { - val oldFragment = last!! - Fragment( - vmId, - oldFragment.tick, - oldFragment.flops + flops, - oldFragment.duration + traceInterval, - cpuUsage, - cores - ) - } else { - val fragment = - Fragment( - vmId, - timestamp, - flops, - traceInterval, - cpuUsage, - cores - ) - if (last != null) { - yield(last!!) - } - fragment - } - } - } - } - - if (last != null) { - yield(last!!) - } - } - - var maxTime = Long.MIN_VALUE - flopsFragments.filter { it.tick in begin until end }.forEach { fragment -> - allFragments.add(fragment) - maxTime = max(maxTime, fragment.tick) - } - - if (minTime in begin until end) { - val metaRecord = GenericData.Record(metaSchema) - metaRecord.put("id", vmId) - metaRecord.put("submissionTime", minTime) - metaRecord.put("endTime", maxTime) - metaRecord.put("maxCores", maxCores) - metaRecord.put("requiredMemory", requiredMemory) - metaWriter.write(metaRecord) - } - } - - return allFragments - } -} - -/** - * Conversion of the Bitbrains public trace. - */ -public class BitbrainsConversion : TraceConversion("Bitbrains") { - override fun read( - traceDirectory: File, - metaSchema: Schema, - metaWriter: ParquetWriter<GenericData.Record> - ): MutableList<Fragment> { - val timestampCol = 0 - val cpuUsageCol = 3 - val coreCol = 1 - val provisionedMemoryCol = 5 - val traceInterval = 5 * 60 * 1000L - - val allFragments = mutableListOf<Fragment>() - - traceDirectory.walk() - .filterNot { it.isDirectory } - .filter { it.extension == "csv" || it.extension == "txt" } - .toList() - .forEach { vmFile -> - println(vmFile) - - var vmId = "" - var maxCores = -1 - var requiredMemory = -1L - var cores: Int - var minTime = Long.MAX_VALUE - - val flopsFragments = sequence { - var last: Fragment? = null - - BufferedReader(FileReader(vmFile)).use { reader -> - reader.lineSequence() - .drop(1) - .chunked(128) - .forEach { lines -> - for (line in lines) { - // Ignore comments in the trace - if (line.startsWith("#") || line.isBlank()) { - continue - } - - val values = line.split(";\t") - - vmId = vmFile.name - - val timestamp = (values[timestampCol].trim().toLong() - 5 * 60) * 1000L - - cores = values[coreCol].trim().toInt() - val provisionedMemory = values[provisionedMemoryCol].trim().toDouble() // KB - requiredMemory = max(requiredMemory, (provisionedMemory / 1000).toLong()) - maxCores = max(maxCores, cores) - minTime = min(minTime, timestamp) - val cpuUsage = values[cpuUsageCol].trim().toDouble() // MHz - - val flops: Long = (cpuUsage * 5 * 60).toLong() - - last = if (last != null && last!!.flops == 0L && flops == 0L) { - val oldFragment = last!! - Fragment( - vmId, - oldFragment.tick, - oldFragment.flops + flops, - oldFragment.duration + traceInterval, - cpuUsage, - cores - ) - } else { - val fragment = - Fragment( - vmId, - timestamp, - flops, - traceInterval, - cpuUsage, - cores - ) - if (last != null) { - yield(last!!) - } - fragment - } - } - } - } - - if (last != null) { - yield(last!!) - } - } - - var maxTime = Long.MIN_VALUE - flopsFragments.forEach { fragment -> - allFragments.add(fragment) - maxTime = max(maxTime, fragment.tick) - } - - val metaRecord = GenericData.Record(metaSchema) - metaRecord.put("id", vmId) - metaRecord.put("submissionTime", minTime) - metaRecord.put("endTime", maxTime) - metaRecord.put("maxCores", maxCores) - metaRecord.put("requiredMemory", requiredMemory) - metaWriter.write(metaRecord) - } - - return allFragments - } -} - -/** - * Conversion of the Azure public VM trace. - */ -public class AzureConversion : TraceConversion("Azure") { - private val seed by option(help = "seed for trace sampling") - .long() - .default(0) - - override fun read( - traceDirectory: File, - metaSchema: Schema, - metaWriter: ParquetWriter<GenericData.Record> - ): MutableList<Fragment> { - val random = Random(seed) - val fraction = 0.01 - - // Read VM table - val vmIdTableCol = 0 - val coreTableCol = 9 - val provisionedMemoryTableCol = 10 - - var vmId: String - var cores: Int - var requiredMemory: Long - - val vmIds = mutableSetOf<String>() - val vmIdToMetadata = mutableMapOf<String, VmInfo>() - - BufferedReader(FileReader(File(traceDirectory, "vmtable.csv"))).use { reader -> - reader.lineSequence() - .chunked(1024) - .forEach { lines -> - for (line in lines) { - // Ignore comments in the trace - if (line.startsWith("#") || line.isBlank()) { - continue - } - // Sample only a fraction of the VMs - if (random.nextDouble() > fraction) { - continue - } - - val values = line.split(",") - - // Exclude VMs with a large number of cores (not specified exactly) - if (values[coreTableCol].contains(">")) { - continue - } - - vmId = values[vmIdTableCol].trim() - cores = values[coreTableCol].trim().toInt() - requiredMemory = values[provisionedMemoryTableCol].trim().toInt() * 1_000L // GB -> MB - - vmIds.add(vmId) - vmIdToMetadata[vmId] = VmInfo(cores, requiredMemory, Long.MAX_VALUE, -1L) - } - } - } - - // Read VM metric reading files - val timestampCol = 0 - val vmIdCol = 1 - val cpuUsageCol = 4 - val traceInterval = 5 * 60 * 1000L - - val vmIdToFragments = mutableMapOf<String, MutableList<Fragment>>() - val vmIdToLastFragment = mutableMapOf<String, Fragment?>() - val allFragments = mutableListOf<Fragment>() - - for (i in ProgressBar.wrap((1..195).toList(), "Reading Trace")) { - val readingsFile = File(File(traceDirectory, "readings"), "readings-$i.csv") - var timestamp: Long - var cpuUsage: Double - - BufferedReader(FileReader(readingsFile)).use { reader -> - reader.lineSequence() - .chunked(128) - .forEach { lines -> - for (line in lines) { - // Ignore comments in the trace - if (line.startsWith("#") || line.isBlank()) { - continue - } - - val values = line.split(",") - vmId = values[vmIdCol].trim() - - // Ignore readings for VMs not in the sample - if (!vmIds.contains(vmId)) { - continue - } - - timestamp = values[timestampCol].trim().toLong() * 1000L - vmIdToMetadata[vmId]!!.minTime = min(vmIdToMetadata[vmId]!!.minTime, timestamp) - cpuUsage = values[cpuUsageCol].trim().toDouble() * 3_000 // MHz - vmIdToMetadata[vmId]!!.maxTime = max(vmIdToMetadata[vmId]!!.maxTime, timestamp) - - val flops: Long = (cpuUsage * 5 * 60).toLong() - val lastFragment = vmIdToLastFragment[vmId] - - vmIdToLastFragment[vmId] = - if (lastFragment != null && lastFragment.flops == 0L && flops == 0L) { - Fragment( - vmId, - lastFragment.tick, - lastFragment.flops + flops, - lastFragment.duration + traceInterval, - cpuUsage, - vmIdToMetadata[vmId]!!.cores - ) - } else { - val fragment = - Fragment( - vmId, - timestamp, - flops, - traceInterval, - cpuUsage, - vmIdToMetadata[vmId]!!.cores - ) - if (lastFragment != null) { - if (vmIdToFragments[vmId] == null) { - vmIdToFragments[vmId] = mutableListOf() - } - vmIdToFragments[vmId]!!.add(lastFragment) - allFragments.add(lastFragment) - } - fragment - } - } - } - } - } - - for (entry in vmIdToLastFragment) { - if (entry.value != null) { - if (vmIdToFragments[entry.key] == null) { - vmIdToFragments[entry.key] = mutableListOf() - } - vmIdToFragments[entry.key]!!.add(entry.value!!) - } - } - - println("Read ${vmIdToLastFragment.size} VMs") - - for (entry in vmIdToMetadata) { - val metaRecord = GenericData.Record(metaSchema) - metaRecord.put("id", entry.key) - metaRecord.put("submissionTime", entry.value.minTime) - metaRecord.put("endTime", entry.value.maxTime) - println("${entry.value.minTime} - ${entry.value.maxTime}") - metaRecord.put("maxCores", entry.value.cores) - metaRecord.put("requiredMemory", entry.value.requiredMemory) - metaWriter.write(metaRecord) - } - - return allFragments - } -} - -public data class Fragment( - public val id: String, - public val tick: Long, - public val flops: Long, - public val duration: Long, - public val usage: Double, - public val cores: Int -) - -public class VmInfo(public val cores: Int, public val requiredMemory: Long, public var minTime: Long, public var maxTime: Long) - -/** - * A script to convert a trace in text format into a Parquet trace. - */ -public fun main(args: Array<String>): Unit = TraceConverterCli().main(args) diff --git a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/trace/WorkloadSampler.kt b/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/trace/WorkloadSampler.kt deleted file mode 100644 index 5c8727ea..00000000 --- a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/trace/WorkloadSampler.kt +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright (c) 2020 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.experiments.capelin.trace - -import mu.KotlinLogging -import org.opendc.experiments.capelin.model.CompositeWorkload -import org.opendc.experiments.capelin.model.SamplingStrategy -import org.opendc.experiments.capelin.model.Workload -import org.opendc.format.trace.TraceEntry -import org.opendc.simulator.compute.workload.SimWorkload -import java.util.* -import kotlin.random.Random - -private val logger = KotlinLogging.logger {} - -/** - * Sample the workload for the specified [run]. - */ -public fun sampleWorkload( - trace: List<TraceEntry<SimWorkload>>, - workload: Workload, - subWorkload: Workload, - seed: Int -): List<TraceEntry<SimWorkload>> { - return when { - workload is CompositeWorkload -> sampleRegularWorkload(trace, workload, subWorkload, seed) - workload.samplingStrategy == SamplingStrategy.HPC -> - sampleHpcWorkload(trace, workload, seed, sampleOnLoad = false) - workload.samplingStrategy == SamplingStrategy.HPC_LOAD -> - sampleHpcWorkload(trace, workload, seed, sampleOnLoad = true) - else -> - sampleRegularWorkload(trace, workload, workload, seed) - } -} - -/** - * Sample a regular (non-HPC) workload. - */ -public fun sampleRegularWorkload( - trace: List<TraceEntry<SimWorkload>>, - workload: Workload, - subWorkload: Workload, - seed: Int -): List<TraceEntry<SimWorkload>> { - val fraction = subWorkload.fraction - - val shuffled = trace.shuffled(Random(seed)) - val res = mutableListOf<TraceEntry<SimWorkload>>() - val totalLoad = if (workload is CompositeWorkload) { - workload.totalLoad - } else { - shuffled.sumByDouble { it.meta.getValue("total-load") as Double } - } - var currentLoad = 0.0 - - for (entry in shuffled) { - val entryLoad = entry.meta.getValue("total-load") as Double - if ((currentLoad + entryLoad) / totalLoad > fraction) { - break - } - - currentLoad += entryLoad - res += entry - } - - logger.info { "Sampled ${trace.size} VMs (fraction $fraction) into subset of ${res.size} VMs" } - - return res -} - -/** - * Sample a HPC workload. - */ -public fun sampleHpcWorkload( - trace: List<TraceEntry<SimWorkload>>, - workload: Workload, - seed: Int, - sampleOnLoad: Boolean -): List<TraceEntry<SimWorkload>> { - val pattern = Regex("^vm__workload__(ComputeNode|cn).*") - val random = Random(seed) - - val fraction = workload.fraction - val (hpc, nonHpc) = trace.partition { entry -> - val name = entry.name - name.matches(pattern) - } - - val hpcSequence = generateSequence(0) { it + 1 } - .map { index -> - val res = mutableListOf<TraceEntry<SimWorkload>>() - hpc.mapTo(res) { sample(it, index) } - res.shuffle(random) - res - } - .flatten() - - val nonHpcSequence = generateSequence(0) { it + 1 } - .map { index -> - val res = mutableListOf<TraceEntry<SimWorkload>>() - nonHpc.mapTo(res) { sample(it, index) } - res.shuffle(random) - res - } - .flatten() - - logger.debug { "Found ${hpc.size} HPC workloads and ${nonHpc.size} non-HPC workloads" } - - val totalLoad = if (workload is CompositeWorkload) { - workload.totalLoad - } else { - trace.sumByDouble { it.meta.getValue("total-load") as Double } - } - - logger.debug { "Total trace load: $totalLoad" } - var hpcCount = 0 - var hpcLoad = 0.0 - var nonHpcCount = 0 - var nonHpcLoad = 0.0 - - val res = mutableListOf<TraceEntry<SimWorkload>>() - - if (sampleOnLoad) { - var currentLoad = 0.0 - for (entry in hpcSequence) { - val entryLoad = entry.meta.getValue("total-load") as Double - if ((currentLoad + entryLoad) / totalLoad > fraction) { - break - } - - hpcLoad += entryLoad - hpcCount += 1 - currentLoad += entryLoad - res += entry - } - - for (entry in nonHpcSequence) { - val entryLoad = entry.meta.getValue("total-load") as Double - if ((currentLoad + entryLoad) / totalLoad > 1) { - break - } - - nonHpcLoad += entryLoad - nonHpcCount += 1 - currentLoad += entryLoad - res += entry - } - } else { - hpcSequence - .take((fraction * trace.size).toInt()) - .forEach { entry -> - hpcLoad += entry.meta.getValue("total-load") as Double - hpcCount += 1 - res.add(entry) - } - - nonHpcSequence - .take(((1 - fraction) * trace.size).toInt()) - .forEach { entry -> - nonHpcLoad += entry.meta.getValue("total-load") as Double - nonHpcCount += 1 - res.add(entry) - } - } - - logger.debug { "HPC $hpcCount (load $hpcLoad) and non-HPC $nonHpcCount (load $nonHpcLoad)" } - logger.debug { "Total sampled load: ${hpcLoad + nonHpcLoad}" } - logger.info { "Sampled ${trace.size} VMs (fraction $fraction) into subset of ${res.size} VMs" } - - return res -} - -/** - * Sample a random trace entry. - */ -private fun sample(entry: TraceEntry<SimWorkload>, i: Int): TraceEntry<SimWorkload> { - val uid = UUID.nameUUIDFromBytes("${entry.uid}-$i".toByteArray()) - return entry.copy(uid = uid) -} diff --git a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/util/VmPlacementReader.kt b/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/util/VmPlacementReader.kt new file mode 100644 index 00000000..67de2777 --- /dev/null +++ b/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/util/VmPlacementReader.kt @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.experiments.capelin.util + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import com.fasterxml.jackson.module.kotlin.readValue +import java.io.InputStream + +/** + * A parser for the JSON VM placement data files used for the TPDS article on Capelin. + */ +class VmPlacementReader { + /** + * The [ObjectMapper] to parse the placement. + */ + private val mapper = jacksonObjectMapper() + + /** + * Read the VM placements from the input. + */ + fun read(input: InputStream): Map<String, String> { + return mapper.readValue<Map<String, String>>(input) + .mapKeys { "vm__workload__${it.key}.txt" } + .mapValues { it.value.split("/")[1] } // Clusters have format XX0 / X00 + } +} diff --git a/opendc-experiments/opendc-experiments-capelin/src/main/resources/log4j2.xml b/opendc-experiments/opendc-experiments-capelin/src/main/resources/log4j2.xml index d1c01b8e..d46b50c3 100644 --- a/opendc-experiments/opendc-experiments-capelin/src/main/resources/log4j2.xml +++ b/opendc-experiments/opendc-experiments-capelin/src/main/resources/log4j2.xml @@ -36,7 +36,7 @@ <Logger name="org.opendc.experiments.capelin" level="info" additivity="false"> <AppenderRef ref="Console"/> </Logger> - <Logger name="org.opendc.experiments.capelin.trace" level="debug" additivity="false"> + <Logger name="org.opendc.experiments.vm.trace" level="debug" additivity="false"> <AppenderRef ref="Console"/> </Logger> <Logger name="org.apache.hadoop" level="warn" additivity="false"> diff --git a/opendc-experiments/opendc-experiments-capelin/src/test/kotlin/org/opendc/experiments/capelin/CapelinIntegrationTest.kt b/opendc-experiments/opendc-experiments-capelin/src/test/kotlin/org/opendc/experiments/capelin/CapelinIntegrationTest.kt index 2d5cc68c..e34c5bdc 100644 --- a/opendc-experiments/opendc-experiments-capelin/src/test/kotlin/org/opendc/experiments/capelin/CapelinIntegrationTest.kt +++ b/opendc-experiments/opendc-experiments-capelin/src/test/kotlin/org/opendc/experiments/capelin/CapelinIntegrationTest.kt @@ -22,185 +22,276 @@ package org.opendc.experiments.capelin -import io.opentelemetry.sdk.metrics.export.MetricProducer -import kotlinx.coroutines.cancel -import kotlinx.coroutines.channels.Channel import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertAll -import org.opendc.compute.service.driver.Host import org.opendc.compute.service.scheduler.FilterScheduler -import org.opendc.compute.service.scheduler.filters.ComputeCapabilitiesFilter import org.opendc.compute.service.scheduler.filters.ComputeFilter -import org.opendc.compute.service.scheduler.weights.CoreMemoryWeigher -import org.opendc.experiments.capelin.model.Workload -import org.opendc.experiments.capelin.monitor.ExperimentMonitor -import org.opendc.experiments.capelin.trace.Sc20ParquetTraceReader -import org.opendc.experiments.capelin.trace.Sc20RawParquetTraceReader -import org.opendc.format.environment.EnvironmentReader -import org.opendc.format.environment.sc20.Sc20ClusterEnvironmentReader -import org.opendc.format.trace.TraceReader -import org.opendc.simulator.compute.workload.SimWorkload +import org.opendc.compute.service.scheduler.filters.RamFilter +import org.opendc.compute.service.scheduler.filters.VCpuFilter +import org.opendc.compute.service.scheduler.weights.CoreRamWeigher +import org.opendc.compute.workload.* +import org.opendc.compute.workload.topology.Topology +import org.opendc.compute.workload.topology.apply +import org.opendc.compute.workload.util.PerformanceInterferenceReader +import org.opendc.experiments.capelin.topology.clusterTopology +import org.opendc.simulator.compute.kernel.interference.VmInterferenceModel import org.opendc.simulator.core.runBlockingSimulation +import org.opendc.telemetry.compute.ComputeMetricExporter +import org.opendc.telemetry.compute.collectServiceMetrics +import org.opendc.telemetry.compute.table.HostData +import org.opendc.telemetry.sdk.metrics.export.CoroutineMetricReader import java.io.File +import java.time.Duration +import java.util.* /** - * An integration test suite for the SC20 experiments. + * An integration test suite for the Capelin experiments. */ class CapelinIntegrationTest { /** * The monitor used to keep track of the metrics. */ - private lateinit var monitor: TestExperimentReporter + private lateinit var exporter: TestComputeMetricExporter + + /** + * The [FilterScheduler] to use for all experiments. + */ + private lateinit var computeScheduler: FilterScheduler + + /** + * The [ComputeWorkloadLoader] responsible for loading the traces. + */ + private lateinit var workloadLoader: ComputeWorkloadLoader /** * Setup the experimental environment. */ @BeforeEach fun setUp() { - monitor = TestExperimentReporter() + exporter = TestComputeMetricExporter() + computeScheduler = FilterScheduler( + filters = listOf(ComputeFilter(), VCpuFilter(16.0), RamFilter(1.0)), + weighers = listOf(CoreRamWeigher(multiplier = 1.0)) + ) + workloadLoader = ComputeWorkloadLoader(File("src/test/resources/trace")) } + /** + * Test a large simulation setup. + */ @Test fun testLarge() = runBlockingSimulation { - val failures = false - val seed = 0 - val chan = Channel<Unit>(Channel.CONFLATED) - val allocationPolicy = FilterScheduler( - filters = listOf(ComputeFilter(), ComputeCapabilitiesFilter()), - weighers = listOf(CoreMemoryWeigher() to -1.0) + val workload = createTestWorkload(1.0) + val runner = ComputeWorkloadRunner( + coroutineContext, + clock, + computeScheduler ) - val traceReader = createTestTraceReader() - val environmentReader = createTestEnvironmentReader() - lateinit var monitorResults: ComputeMetrics - - val meterProvider = createMeterProvider(clock) - withComputeService(clock, meterProvider, environmentReader, allocationPolicy) { scheduler -> - val failureDomain = if (failures) { - println("ENABLING failures") - createFailureDomain( - this, - clock, - seed, - 24.0 * 7, - scheduler, - chan - ) - } else { - null - } - - withMonitor(monitor, clock, meterProvider as MetricProducer, scheduler) { - processTrace( - clock, - traceReader, - scheduler, - chan, - monitor - ) - } - - failureDomain?.cancel() + val topology = createTopology() + val metricReader = CoroutineMetricReader(this, runner.producers, exporter) + + try { + runner.apply(topology) + runner.run(workload, 0) + } finally { + runner.close() + metricReader.close() } - monitorResults = collectMetrics(meterProvider as MetricProducer) - println("Finish SUBMIT=${monitorResults.submittedVms} FAIL=${monitorResults.unscheduledVms} QUEUE=${monitorResults.queuedVms} RUNNING=${monitorResults.runningVms}") + val serviceMetrics = collectServiceMetrics(runner.producers[0]) + println( + "Scheduler " + + "Success=${serviceMetrics.attemptsSuccess} " + + "Failure=${serviceMetrics.attemptsFailure} " + + "Error=${serviceMetrics.attemptsError} " + + "Pending=${serviceMetrics.serversPending} " + + "Active=${serviceMetrics.serversActive}" + ) // Note that these values have been verified beforehand assertAll( - { assertEquals(50, monitorResults.submittedVms, "The trace contains 50 VMs") }, - { assertEquals(0, monitorResults.runningVms, "All VMs should finish after a run") }, - { assertEquals(0, monitorResults.unscheduledVms, "No VM should not be unscheduled") }, - { assertEquals(0, monitorResults.queuedVms, "No VM should not be in the queue") }, - { assertEquals(207389912923, monitor.totalRequestedBurst) { "Incorrect requested burst" } }, - { assertEquals(207122087280, monitor.totalGrantedBurst) { "Incorrect granted burst" } }, - { assertEquals(267825640, monitor.totalOvercommissionedBurst) { "Incorrect overcommitted burst" } }, - { assertEquals(0, monitor.totalInterferedBurst) { "Incorrect interfered burst" } } + { assertEquals(50, serviceMetrics.attemptsSuccess, "The scheduler should schedule 50 VMs") }, + { assertEquals(0, serviceMetrics.serversActive, "All VMs should finish after a run") }, + { assertEquals(0, serviceMetrics.attemptsFailure, "No VM should be unscheduled") }, + { assertEquals(0, serviceMetrics.serversPending, "No VM should not be in the queue") }, + { assertEquals(223325655, this@CapelinIntegrationTest.exporter.idleTime) { "Incorrect idle time" } }, + { assertEquals(67006560, this@CapelinIntegrationTest.exporter.activeTime) { "Incorrect active time" } }, + { assertEquals(3159377, this@CapelinIntegrationTest.exporter.stealTime) { "Incorrect steal time" } }, + { assertEquals(0, this@CapelinIntegrationTest.exporter.lostTime) { "Incorrect lost time" } }, + { assertEquals(5.840212485920686E9, this@CapelinIntegrationTest.exporter.energyUsage, 0.01) { "Incorrect power draw" } }, ) } + /** + * Test a small simulation setup. + */ @Test fun testSmall() = runBlockingSimulation { val seed = 1 - val chan = Channel<Unit>(Channel.CONFLATED) - val allocationPolicy = FilterScheduler( - filters = listOf(ComputeFilter(), ComputeCapabilitiesFilter()), - weighers = listOf(CoreMemoryWeigher() to -1.0) + val workload = createTestWorkload(0.25, seed) + + val simulator = ComputeWorkloadRunner( + coroutineContext, + clock, + computeScheduler ) - val traceReader = createTestTraceReader(0.5, seed) - val environmentReader = createTestEnvironmentReader("single") - - val meterProvider = createMeterProvider(clock) - - withComputeService(clock, meterProvider, environmentReader, allocationPolicy) { scheduler -> - withMonitor(monitor, clock, meterProvider as MetricProducer, scheduler) { - processTrace( - clock, - traceReader, - scheduler, - chan, - monitor - ) - } + val topology = createTopology("single") + val metricReader = CoroutineMetricReader(this, simulator.producers, exporter) + + try { + simulator.apply(topology) + simulator.run(workload, seed.toLong()) + } finally { + simulator.close() + metricReader.close() } - val metrics = collectMetrics(meterProvider as MetricProducer) - println("Finish SUBMIT=${metrics.submittedVms} FAIL=${metrics.unscheduledVms} QUEUE=${metrics.queuedVms} RUNNING=${metrics.runningVms}") + val serviceMetrics = collectServiceMetrics(simulator.producers[0]) + println( + "Scheduler " + + "Success=${serviceMetrics.attemptsSuccess} " + + "Failure=${serviceMetrics.attemptsFailure} " + + "Error=${serviceMetrics.attemptsError} " + + "Pending=${serviceMetrics.serversPending} " + + "Active=${serviceMetrics.serversActive}" + ) // Note that these values have been verified beforehand assertAll( - { assertEquals(96350072517, monitor.totalRequestedBurst) { "Total requested work incorrect" } }, - { assertEquals(96330335057, monitor.totalGrantedBurst) { "Total granted work incorrect" } }, - { assertEquals(19737460, monitor.totalOvercommissionedBurst) { "Total overcommitted work incorrect" } }, - { assertEquals(0, monitor.totalInterferedBurst) { "Total interfered work incorrect" } } + { assertEquals(10997726, this@CapelinIntegrationTest.exporter.idleTime) { "Idle time incorrect" } }, + { assertEquals(9740289, this@CapelinIntegrationTest.exporter.activeTime) { "Active time incorrect" } }, + { assertEquals(0, this@CapelinIntegrationTest.exporter.stealTime) { "Steal time incorrect" } }, + { assertEquals(0, this@CapelinIntegrationTest.exporter.lostTime) { "Lost time incorrect" } }, + { assertEquals(7.0099453912813E8, this@CapelinIntegrationTest.exporter.energyUsage, 0.01) { "Incorrect power draw" } } ) } /** - * Obtain the trace reader for the test. + * Test a small simulation setup with interference. */ - private fun createTestTraceReader(fraction: Double = 1.0, seed: Int = 0): TraceReader<SimWorkload> { - return Sc20ParquetTraceReader( - listOf(Sc20RawParquetTraceReader(File("src/test/resources/trace"))), - emptyMap(), - Workload("test", fraction), - seed + @Test + fun testInterference() = runBlockingSimulation { + val seed = 0 + val workload = createTestWorkload(1.0, seed) + val perfInterferenceInput = checkNotNull(CapelinIntegrationTest::class.java.getResourceAsStream("/bitbrains-perf-interference.json")) + val performanceInterferenceModel = + PerformanceInterferenceReader() + .read(perfInterferenceInput) + .let { VmInterferenceModel(it, Random(seed.toLong())) } + + val simulator = ComputeWorkloadRunner( + coroutineContext, + clock, + computeScheduler, + interferenceModel = performanceInterferenceModel + ) + val topology = createTopology("single") + val metricReader = CoroutineMetricReader(this, simulator.producers, exporter) + + try { + simulator.apply(topology) + simulator.run(workload, seed.toLong()) + } finally { + simulator.close() + metricReader.close() + } + + val serviceMetrics = collectServiceMetrics(simulator.producers[0]) + println( + "Scheduler " + + "Success=${serviceMetrics.attemptsSuccess} " + + "Failure=${serviceMetrics.attemptsFailure} " + + "Error=${serviceMetrics.attemptsError} " + + "Pending=${serviceMetrics.serversPending} " + + "Active=${serviceMetrics.serversActive}" + ) + + // Note that these values have been verified beforehand + assertAll( + { assertEquals(6013515, this@CapelinIntegrationTest.exporter.idleTime) { "Idle time incorrect" } }, + { assertEquals(14724500, this@CapelinIntegrationTest.exporter.activeTime) { "Active time incorrect" } }, + { assertEquals(12530742, this@CapelinIntegrationTest.exporter.stealTime) { "Steal time incorrect" } }, + { assertEquals(481251, this@CapelinIntegrationTest.exporter.lostTime) { "Lost time incorrect" } } ) } /** - * Obtain the environment reader for the test. + * Test a small simulation setup with failures. */ - private fun createTestEnvironmentReader(name: String = "topology"): EnvironmentReader { - val stream = object {}.javaClass.getResourceAsStream("/env/$name.txt") - return Sc20ClusterEnvironmentReader(stream) - } + @Test + fun testFailures() = runBlockingSimulation { + val seed = 1 + val simulator = ComputeWorkloadRunner( + coroutineContext, + clock, + computeScheduler, + grid5000(Duration.ofDays(7)) + ) + val topology = createTopology("single") + val workload = createTestWorkload(0.25, seed) + val metricReader = CoroutineMetricReader(this, simulator.producers, exporter) - class TestExperimentReporter : ExperimentMonitor { - var totalRequestedBurst = 0L - var totalGrantedBurst = 0L - var totalOvercommissionedBurst = 0L - var totalInterferedBurst = 0L - - override fun reportHostSlice( - time: Long, - requestedBurst: Long, - grantedBurst: Long, - overcommissionedBurst: Long, - interferedBurst: Long, - cpuUsage: Double, - cpuDemand: Double, - powerDraw: Double, - numberOfDeployedImages: Int, - host: Host, - ) { - totalRequestedBurst += requestedBurst - totalGrantedBurst += grantedBurst - totalOvercommissionedBurst += overcommissionedBurst - totalInterferedBurst += interferedBurst + try { + simulator.apply(topology) + simulator.run(workload, seed.toLong()) + } finally { + simulator.close() + metricReader.close() } - override fun close() {} + val serviceMetrics = collectServiceMetrics(simulator.producers[0]) + println( + "Scheduler " + + "Success=${serviceMetrics.attemptsSuccess} " + + "Failure=${serviceMetrics.attemptsFailure} " + + "Error=${serviceMetrics.attemptsError} " + + "Pending=${serviceMetrics.serversPending} " + + "Active=${serviceMetrics.serversActive}" + ) + + // Note that these values have been verified beforehand + assertAll( + { assertEquals(10865478, exporter.idleTime) { "Idle time incorrect" } }, + { assertEquals(9606177, exporter.activeTime) { "Active time incorrect" } }, + { assertEquals(0, exporter.stealTime) { "Steal time incorrect" } }, + { assertEquals(0, exporter.lostTime) { "Lost time incorrect" } }, + { assertEquals(2559005056, exporter.uptime) { "Uptime incorrect" } } + ) + } + + /** + * Obtain the trace reader for the test. + */ + private fun createTestWorkload(fraction: Double, seed: Int = 0): List<VirtualMachine> { + val source = trace("bitbrains-small").sampleByLoad(fraction) + return source.resolve(workloadLoader, Random(seed.toLong())) + } + + /** + * Obtain the topology factory for the test. + */ + private fun createTopology(name: String = "topology"): Topology { + val stream = checkNotNull(object {}.javaClass.getResourceAsStream("/env/$name.txt")) + return stream.use { clusterTopology(stream) } + } + + class TestComputeMetricExporter : ComputeMetricExporter() { + var idleTime = 0L + var activeTime = 0L + var stealTime = 0L + var lostTime = 0L + var energyUsage = 0.0 + var uptime = 0L + + override fun record(data: HostData) { + idleTime += data.cpuIdleTime + activeTime += data.cpuActiveTime + stealTime += data.cpuStealTime + lostTime += data.cpuLostTime + energyUsage += data.powerTotal + uptime += data.uptime + } } } diff --git a/opendc-experiments/opendc-experiments-capelin/src/test/resources/bitbrains-perf-interference.json b/opendc-experiments/opendc-experiments-capelin/src/test/resources/bitbrains-perf-interference.json new file mode 100644 index 00000000..51fc6366 --- /dev/null +++ b/opendc-experiments/opendc-experiments-capelin/src/test/resources/bitbrains-perf-interference.json @@ -0,0 +1,21 @@ +[ + { + "vms": [ + "141", + "379", + "851", + "116" + ], + "minServerLoad": 0.0, + "performanceScore": 0.8830158730158756 + }, + { + "vms": [ + "205", + "116", + "463" + ], + "minServerLoad": 0.0, + "performanceScore": 0.7133055555552751 + } +] diff --git a/opendc-experiments/opendc-experiments-capelin/src/test/resources/env/single.txt b/opendc-experiments/opendc-experiments-capelin/src/test/resources/env/single.txt index 53b3c2d7..5642003d 100644 --- a/opendc-experiments/opendc-experiments-capelin/src/test/resources/env/single.txt +++ b/opendc-experiments/opendc-experiments-capelin/src/test/resources/env/single.txt @@ -1,3 +1,3 @@ ClusterID;ClusterName;Cores;Speed;Memory;numberOfHosts;memoryCapacityPerHost;coreCountPerHost -A01;A01;8;3.2;64;1;64;8 +A01;A01;8;3.2;128;1;128;8 diff --git a/opendc-experiments/opendc-experiments-capelin/src/test/resources/trace/bitbrains-small/meta.parquet b/opendc-experiments/opendc-experiments-capelin/src/test/resources/trace/bitbrains-small/meta.parquet Binary files differnew file mode 100644 index 00000000..da6e5330 --- /dev/null +++ b/opendc-experiments/opendc-experiments-capelin/src/test/resources/trace/bitbrains-small/meta.parquet diff --git a/opendc-experiments/opendc-experiments-capelin/src/test/resources/trace/bitbrains-small/trace.parquet b/opendc-experiments/opendc-experiments-capelin/src/test/resources/trace/bitbrains-small/trace.parquet Binary files differnew file mode 100644 index 00000000..fe0a254c --- /dev/null +++ b/opendc-experiments/opendc-experiments-capelin/src/test/resources/trace/bitbrains-small/trace.parquet diff --git a/opendc-experiments/opendc-experiments-capelin/src/test/resources/trace/meta.parquet b/opendc-experiments/opendc-experiments-capelin/src/test/resources/trace/meta.parquet Binary files differdeleted file mode 100644 index ce7a812c..00000000 --- a/opendc-experiments/opendc-experiments-capelin/src/test/resources/trace/meta.parquet +++ /dev/null diff --git a/opendc-experiments/opendc-experiments-capelin/src/test/resources/trace/trace.parquet b/opendc-experiments/opendc-experiments-capelin/src/test/resources/trace/trace.parquet Binary files differdeleted file mode 100644 index 1d7ce882..00000000 --- a/opendc-experiments/opendc-experiments-capelin/src/test/resources/trace/trace.parquet +++ /dev/null diff --git a/opendc-experiments/opendc-experiments-energy21/.gitignore b/opendc-experiments/opendc-experiments-energy21/.gitignore deleted file mode 100644 index 55da79f8..00000000 --- a/opendc-experiments/opendc-experiments-energy21/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -input/ -output/ -.ipynb_checkpoints diff --git a/opendc-experiments/opendc-experiments-energy21/build.gradle.kts b/opendc-experiments/opendc-experiments-energy21/build.gradle.kts deleted file mode 100644 index bc05f09b..00000000 --- a/opendc-experiments/opendc-experiments-energy21/build.gradle.kts +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -description = "Experiments for the OpenDC Energy work" - -/* Build configuration */ -plugins { - `experiment-conventions` - `testing-conventions` -} - -dependencies { - api(platform(projects.opendcPlatform)) - api(projects.opendcHarness.opendcHarnessApi) - implementation(projects.opendcFormat) - implementation(projects.opendcSimulator.opendcSimulatorCore) - implementation(projects.opendcSimulator.opendcSimulatorCompute) - implementation(projects.opendcSimulator.opendcSimulatorFailures) - implementation(projects.opendcCompute.opendcComputeSimulator) - implementation(projects.opendcExperiments.opendcExperimentsCapelin) - implementation(projects.opendcTelemetry.opendcTelemetrySdk) - implementation(libs.kotlin.logging) - implementation(libs.config) - - implementation(libs.parquet) { - exclude(group = "org.slf4j", module = "slf4j-log4j12") - exclude(group = "log4j") - } -} diff --git a/opendc-experiments/opendc-experiments-energy21/plots.ipynb b/opendc-experiments/opendc-experiments-energy21/plots.ipynb deleted file mode 100644 index 7b18bd2b..00000000 --- a/opendc-experiments/opendc-experiments-energy21/plots.ipynb +++ /dev/null @@ -1,270 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "import pandas as pd\n", - "import matplotlib.pyplot as pyplot\n", - "import seaborn as sns\n", - "\n", - "sns.set()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "df = pd.read_parquet(\"output/host-metrics\")\n", - "df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')\n", - "df" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "x = df.groupby(['power_model', 'host_id', pd.Grouper(freq='1D', key='timestamp')]).mean()\n", - "x" - ] - }, - { - "cell_type": "code", - "execution_count": 125, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "<AxesSubplot:xlabel='timestamp', ylabel='power_draw'>" - ] - }, - "execution_count": 125, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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Lu5+9FYfD74T8PHx8ZkEhIIhABSSgrTJyQIAYS1ImomTaPGdwASQyaT5n//5b1555UV03SAej3PTTTeTSCT42Mc+xJ133jvi+CVLzuDpp1dy//338sMf3kpdXT1KKVzX4dpr/4E3vvHNANx00428//03cvrpZ6CU4g9/+C0PPHA/4OUCX3/9DbzhDZce8vv18fE5cKKBIOBtDAeLOkKHAt8JHGSklHz605/g9NPP4Je/vB3DMHjppZV8+tMf59vf/v5eX79kyVI+//mvANDT0811172VCy64uCTnPMhPf/pjNmxYz49+9FNisRidnR3cdNONlJWVl6QqfHx8jhxCpoFQCpdDW4jpO4GDzEsvraS7u5sPfOBDaJqXgXv66Wfwuc99aZ/74WazWcLhMIFAYNTjd9xxO7/5zR9LzqG2to5///dvECwuKX18fI4sTEPHUCCF7wQOiGdea+PpV9tGPb6rOub+sOTkBs49ac/lGhs2rGfevPklBzDI4sVLaGtr3es1nn76Sd773utxXYcdO7bzD//w3lFOYPv2bUQiURoapox4fN68BeO8Ex8fn8mGEAJdehvDh5IJLRb7/ve/zxVXXMGVV17JL3/5SwCWL1/OVVddxbJly7j11lsn8vKHBU0T7E6OaaxOQbtKUC9ZspT/+7/bue22O/jLXx7gscce5qGHRspECKHt9ho+Pj5HLoYCRxza3/aErQSef/55nnvuOe655x4cx+GKK65g8eLFfO5zn+O2226joaGBD33oQzzxxBOcf/75B+2655409mz9UImezZ07nz//+c5Rg/tPfvLfLFhwEul0esTxvb29pZ4Bu1JeXs5ZZy3mtddWcckll5UenzFjBoVCnvb2durr60uPP/zwg/T29vKOd1x3kO/Kx8fnUKArkIewoQxM4Epg0aJF/PrXv8YwDHp6enBdl2QySVNTE9OmTcMwDK666ioeeGBsMbQjlVNOOY2Kikp+8YufljTBV6x4lvvvv4f58xcwbdo0Hn/8kdLxd999F2ecsWjMc1mWxWuvreKEE+aOeDwYDHHNNe/gu9+9hUzGcyptba385Cc/ZsaMmRN0Zz4+PhONrgTu0bISADBNkx/84Af84he/4LLLLqOzs5OamprS87W1tXR0dEykCYccIQTf/OZ/8cMffpcbbngnhmFQVlbOt7/9fSorq/jiF7/Gd7/7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\n", - "text/plain": [ - "<Figure size 432x288 with 1 Axes>" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "sns.lineplot(x=\"timestamp\", y=\"power_draw\", hue='power_model', data=x)" - ] - }, - { - "cell_type": "code", - "execution_count": 127, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "<AxesSubplot:xlabel='timestamp', ylabel='cpu_usage'>" - ] - }, - "execution_count": 127, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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L75YfOxjHyu551NOOUWcffbZkybX+eefP+j7u2vXLnHDDTeIE044QXziE58I/u5PfvIT8f3vf39Cn9UVV1whfve73wkhhLj33nvFF7/4xWnxrAaTazCm0+eu+Dc/+clPilNPPVX8+te/HvT9vf7668UPfvADsWnTJvGFL3whuI7ruuLHP/6xOPHEE8Xq1asnRaZ9+/YNev5Q3/Nt27aJm266adBzJpojxs21efNmNm3aFAzIeuuttzj11FODn88991x++9vflpzzi1/8gmg0GnQefuKJJ4J/r1u3jieffBLbtkvOsW2b559/nlWrVgGwYcOGYPb8888/j+d5wS5lMJlOOeUUXn31VVatWsW5555LKpUqOf/NN99k8eLFzJs3b1JkGorHHnuMhQsXsmjRIlauXBnI/OKLLxIKhbjqqqv43Oc+xymnnDLpz/HSSy8NZtMcf/zxHDx4cMCzfOONN3Bdl0svvRSAjRs38qc//ankns8//3w0TZtUuQb7zL3yyissXLiQVatWsXv37uBv//nPf2bLli28/vrrPPzwwxw8eHDcMv3sZz9jxYoVeJ5HY2MjZWVl0+JZDSbXYEynzx3Aj3/8Y84++2wqKyt5+OGHB31/n332WdavX8+5555LU1NTcJ2dO3fy2GOPcdxxx1FTUzMpMg3FUN/zP//5z+zYsYMNGzZw5ZVX8tZbbw35N8bLEaNMbr31VpYvXx78vHTpUl555RUOHjyI67ps27atZDKj67p8//vf50tf+lJwrO9ceV3XSSQSdHR0lFyns7OTRCKBrvsewtraWpqbmwH4y7/8S/7+7/+eSCQypEwvv/wy0WgURVHYtm0b3d3dwflnnXUWu3fv5sILL5w0mYbiIx/5CFdddRXnn38+c+bMCY7Pnj0bz/P4/ve/zznnnMPtt98+6c9xw4YNaJoGwPe+9z3OP//8Ac9y1qxZCCFobW3FdV2effZZLMsK7vlLX/oSTz31FCeddNKkybV+/fpBP3Nnnnkmn/rUp9i9ezeZTCb4fTweJ5VK8fOf/5yVK1fyhS98Ydwy6bpOKpVixYoV/PznP+d//I//MS2e1WByDcZ0+ty99tprPPvss3ziE58A4Mtf/vKg729zczNVVVVs27aN9vb24DqLFi2iqamJjRs3TppMQzHU9zwcDvORj3yE+++/n0996lP8z//5P4P3fqI5YpRJf4455hi+9KUvcfXVV7Nx40aOP/54DMMIfv/73/+eY445huOPP/6Qf0dVSx+RGMfs+WOOOYarrrqKrq6uEpmK5xdlmjVr1pTJNBzf/va3ufHGG7n66qv51a9+RSaTKbn+ZD1HIQTf/OY3eeWVV/jqV7864LXz5s0jkUgE7+9xxx1Xcv7vf/97ampqKC8vnzK5+n/mqqurA+UDcP7553PCCSdw/PHH89GPfpR33nln0OuNVqaysjKeeuopvvWtb3H11Vfjum7Jaw/XsxpOrkMx1Z+7XC7HP/7jP/K///f/HnBOkeL76zgOV155ZcmaoqpqIFPRqzAVMg3H5z//eS677DIAVq5cSSwWY9euXWP6W8NxRAXg+2KaJieffDJbtmwB4Ne//nXJm/zb3/62xAIAqKuro62tjVmzZuE4Dul0moqKCj784Q8Hr7nvvvtIp9O4roumabS2tg47e765uZnPfOYz6LrOt7/9bcLhMP/+7//OY489Rn19Pfl8fspl6nv+Aw88MOhrhBDceeedXHjhhcFzPOWUU4Jg3mTJ7DgOX/nKV2hubub//b//RzKZDGQuPsv/+q//wrZtfvGLX6BpGv/5n/9JOBwukevkk0/G87xJl6vv+3vnnXcGz+raa68NFmjP87jzzjuDL/ZEybR161bWrFmDoiisWLGCfD5Pd3c3n/jEJw7rszqUXEWm0+fuhRdeoK2tjauvvhrwLYqrrrqKf/mXf6GtrS14fzdt2kRDQwPf/e53efXVV5k7dy7PPfccFRUVUyrTbbfdRktLCwA//OEPqa+vH/RZ3nPPPaxbty5wkQkhAgtoojlilUk2m+Vv/uZvePjhhwmFQtxzzz0lpvbLL7/MZz7zmZJzVq5cyZYtW/jc5z7H1q1bWb58OYZhDPjQL1++nK1bt7J+/Xq2bNky7Oz5+vp6fvSjHxGPx/nIRz7CqaeeyoMPPsj9999PVVVVcH5RphdffHHSZRrqi9wXRVH4zW9+w//3//1/PPbYY2zbto1QKMS6deuC10zGc/zmN79JOp3mpz/9KaFQqETm8847jx/96EfYto3nedx///18+MMf5gc/+AGnnXZaiVwXXXRRyS5ssuTq//4WP3OvvPJK4DpSVZW3336bbDYLwJYtW4IFcjwy/fSnP0XXdS644AKeeeYZKisrqaqqOuzP6lByDcfh+Nydc845PP7448FrzjvvPH74wx8yd+5campqSt7fc845h1/84hc888wzHHvsscF1ijI1NTVNukw/+tGPhn2O4MdS8vk8n/nMZ3juuefwPI9FixaN6NxRMyVh/ink3HPPDTIeNm/eLC688EJxwQUXiO9973slrzv55JNFPp8vOdbZ2Sk++9nPigsvvFD89V//9ZCZE/v37xdXXHGFWLNmjfjkJz8purq6Sn7/i1/8oiSjor9MH/rQh8R73vMeceaZZ5acX5Sp7/mTJdNQfO973xPf+973Apl37Nghzj//fLFs2TLxnve8R9x6660lr5/o59je3i5OOOEE8aEPfUhcdNFFwX+DPcsf/vCH4pRTThEnnXSS+OAHP1hyzyeffLL4j//4j5J7niy5hvrMbdy4seRz9573vEdceuml4sILLxRXXHGFaGxsHPf7+/bbb4vLLrtMXHTRRWLjxo1ix44dh/1ZDSfXYBzuz11/+j67/u/vqlWrxCmnnCLOPPPMkusUZXrmmWfEFVdcMakyDUX/73lTU5P4+Mc/LtauXSs2bNggtm/ffsjzx4OctCiRSCSScXPEBuAlEolEMnVIZSKRSCSScSOViUQikUjGjVQmEolEIhk3UplIJBKJZNxIZSKRjIBPfvKTdHR08JnPfIZ33nlnUq+1b98+Pv/5z0/qNSSSieaILVqUSCaSP/zhDwAjLhYbD42NjSUNIiWSmYCsM5FIhuGGG27g/vvv57jjjuOdd95h8+bNZLNZvvWtb1FXV8fbb79NNBrl85//PPfccw+7d+/mggsuCHp3Pf7443z/+9/Htm0ikQhf+cpXeO9738vOnTu58cYbsSwLIQSXXHIJl112GatXr6a5uZnTTz+dn/zkJ9x999389re/xTRNcrkcX/nKV/jQhz7EnXfeyd69e9m3bx8tLS2cfPLJvP/972fLli3s37+f6667jnXr1nHnnXfy9ttv09bWRnt7O0uXLuXWW28lkUgc5icrOaKYtHJIieQI4rjjjhPt7e3i3HPPFa+++qp45plnxAknnCBef/11IYQQn/rUp8Rf//VfC9M0RXt7uzjppJNEU1OT2L17t1i3bp3o6OgQQgixY8cO8f73v19kMhlxww03BLMuWlpaxLXXXitc1xXPPPOMWLt2rRDCr4z+2Mc+JnK5nBBCiIceekisW7dOCCGCivFUKiVyuZw4/fTTxW233SaEEOI3v/mNuOCCC4LXrVixQrS2tgrXdcUXv/hF8Y1vfGPqHp7kqEC6uSSSMTJ37lxOPPFEAObPn08ymSQUClFVVUU8Hqe7u5vnn3+elpYWPv7xjwfnKYrC3r17+dCHPsRXvvIVXn31Vc466yy+9rWvDegO29DQwDe/+U1+9atf8e677/LKK6+UtLU/++yzg4aTdXV1nHPOOYE8XV1dwetWr14dzNi45JJL+D//5//wla98ZTIei+QoRQbgJZIx0rfZIzBoN1bP8zjrrLN44IEHgv82b97MkiVLOPfcc3n00UdZs2YN27dvZ/369ezdu7fk/Ndff53LLruMdDrN+9//fj796U+PWgagpBW+53ljbmkukQyF/ERJJCOgOLd9tLzvfe/jD3/4Azt37gTgd7/7HRdddBGmafKlL32JrVu3snbtWjZt2kQikeDgwYNomhZM43v++edZtmwZn/jEJzjjjDN47LHHRjUXpMhjjz1GT08PnuexefNmzj333FH/DYnkUEg3l0QyAj70oQ9x+eWXl7iYRsKSJUv4x3/8R774xS8GsyS+//3vE4vF+Nu//VtuvPFG/vM//xNN0zj//PM544wzSKVSaJrGJZdcwt13382vf/1rLrzwQgzD4KyzzqK7u5t0Oj0qOWpqavjMZz5DZ2cnp59+Op/73OdGdb5EMhwym0siOcK588476ezs5Oabbz7cokiOYKSbSyKRSCTjRlomEolEIhk30jKRSCQSybiRykQikUgk40YqE4lEIpGMG6lMJBKJRDJupDKRSCQSybiRykQikUgk4+b/B8dfKnCRHoIcAAAAAElFTkSuQmCC\n", - "text/plain": [ - "<Figure size 432x288 with 1 Axes>" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "sns.lineplot(x=\"timestamp\", y=\"cpu_usage\", data=x)" - ] - }, - { - "cell_type": "code", - "execution_count": 128, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "<AxesSubplot:xlabel='timestamp', ylabel='cpu_demand'>" - ] - }, - "execution_count": 128, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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RENGbsOrp0Y+0Wob0zYyef755znrrLMoLy8nEomwbNkyNm3aNNVmSSQSSVGQ1+0LBSZHtmnaRibNzc3U1NT4f6+treXVV18d8flVVWObAq2pKRnT+RNBMdo0HMVqczHaVYw2QfHaNRTFaHMx2jQapq0zEaJ/i9toJjvb2hKH3CZXU1PiK3wWC8Vo03AUq83FaFcx2gTFa9dQFKPNxWhTX1RVGfImfNqmuerq6mhtbfX/3tzcTG1t7RRaJJFIJEcu09aZnHPOOfzlL3+hvb2ddDrNU089xZIlS6baLIlEIjkimbZprrq6Om644QauvvpqLMviIx/5CKeccspUmyWRSCRHJNPWmQCsXr2a1atXT7UZEolEcsQzrZ3JWBipYNpEnT8RFKNNw1GsNhejXcVoExSvXUNRjDYXo029Gc4+RQzUFiWRSCQSySiYtgV4iUQikRQP0plIJBKJZMxIZyKRSCSSMSOdiUQikUjGjHQmEolEIhkz0plIJBKJZMxIZyKRSCSSMSOdiUQikUjGjHQmEolEIhkzh5WcSiKR4LLLLuP+++9n1qxZPPzww/z4xz9G0zTOPPNMbrrpJrq6uvjEJz7hn9Pd3U1HRwd///vficfjfOlLX2Lv3r1UVlbyne98p2ABV57GxkZuvPFG2tramDdvHnfddRfRaNT/94ceeogtW7Zw5513DmjT/fffT3NzM4ZhcNppp/G1r32N/+//+//885uamojH47zxxhsTYtNQfPe738W2bX7/+99z//33c+DAAT796U/jOA6KojBr1iweffTRCX0ft2/fzi233EIymSQUCvFv//ZvnHDCCf3ey5/+9Kd897vfxXEc6urqePjhh7Ft27ers7OTeDwOMGF2zZ49e9DvXDAY5Nxzz+XKK6/kE5/4BKlUin379qFpGrZt8+EPf5ibb755TDa98847fO1rXyOVSlFWVsadd95JQ0PDlL9Xg9lVzN+7PAcPHuRDH/oQDz/8MOXl5f0+3x/+8Ic0NzejaRrHHnss//Zv/8aNN97on9/a2kp7eztbt26dEJtmzZo16PvY9/e8sbGRlStXMmfOHACqq6v5yU9+Muj5Y0IcJrz88sti1apV4qSTThJ79+4V27dvF+eee65oamoSQgixbt068dOf/rTgHMdxxJVXXikeffRRIYQQt912m/jBD34ghBDi17/+tbj++usHfK5rr71WPP7440IIIb7//e+Lb33rW0IIITKZjPiP//gPsWjRIvHlL395UJv+6Z/+STz++ONi3bp14hOf+ETB+d/61rfEggULxBVXXDEhNg1GPB4XN998s1i4cKE4++yzfZu/9a1vidNOO21S38fLLrtMPPPMM0IIIZ5//nmxevXqAd/LhQsXigcffFAIIcQll1wirrrqqoLXfOqpp4pzzjlnwuw6//zzB/x8d+zYIW6++WZxwgkniI9//OP+z/3JT34i7rvvvnF9r6688krxhz/8QQghxIMPPij+5V/+pSjeq4HsGohi+t7lf+YnPvEJsWjRIvHUU08N+PnedNNN4gc/+IFYt26duOGGG/zncRxH/PjHPxYnnniiWL58+YTYtHfv3gHPH+z3fNOmTeKWW24Z8Jzx5rBJc61fv55169b5C7LeeustFi1a5P/9vPPO43e/+13BOb/61a8Ih8O+8vCzzz7r/3nVqlU899xzWJZVcI5lWWzevJlly5YBsGbNGn/3/ObNm3Fd179LGcimU089lVdffZVly5Zx3nnnEY/HC85/8803Ofroo5k9e/aE2DQYTz/9NEcddRTz589n6dKlvs0vvfQSgUCAa6+9ls9+9rOceuqpE/4+Xnrppf5umuOPP54DBw70ey/feOMNHMfh0ksvBWDt2rX8/e9/L3jN559/PpqmTahdA33nXnnlFY466iiWLVvGzp07/Z/9j3/8gw0bNvD666/zm9/8hgMHDozZpv/6r/9iyZIluK5LY2MjpaWlRfFeDWTXQBTT9w7gxz/+Meeccw4VFRX85je/GfDzffHFF1m9ejXnnXceBw8e9J9n+/btPP300xx33HFUV1dPiE2DMdjv+T/+8Q+2bdvGmjVruPrqq3nrrbcG/Rlj5bBxJrfffjuLFy/2/75gwQJeeeUVDhw4gOM4bNq0qWAzo+M43HfffXzxi1/0j/XeK6/rOrFYjPb29oLn6ejoIBaLoetehrCmpoampiYA3vve9/Kv//qvhEKhQW16+eWXCYfDKIrCpk2b6Orq8s8/++yz2blzJytWrJgwmwbj4osv5tprr+X8889n5syZ/vH6+npc1+W+++7j3HPP5Vvf+taEv49r1qxB0zQAvve973H++ef3ey9nzJiBEIKWlhYcx+HFF1/ENE3/NX/xi1/kT3/6EyeddNKE2bV69eoBv3Nnnnkmn/zkJ9m5cyfJZNL/92g0Sjwe53/+539YunQpN9xww5ht0nWdeDzOkiVL+J//+R8++tGPFsV7NZBdA1FM37vXXnuNF198kY9//OMAfOlLXxrw821qaqKyspJNmzbR1tbmP8/8+fM5ePAga9eunTCbBmOw3/NgMMjFF1/Mww8/zCc/+Uk+97nP+Z/9eHPYOJO+zJs3jy9+8Ytcd911rF27luOPPx7DMPx//+Mf/8i8efM4/vjjh/w5qlr4Fokx7J6fN28e1157LZ2dnQU25c/P2zRjxoxJs2k47r77br761a9y3XXX8dhjj5FMJguef6LeRyEE3/zmN3nllVf4yle+0u+xs2fPJhaL+Z/vcccdV3D+H//4R6qrqykrK5s0u/p+56qqqnznA3D++edzwgkncPzxx3P55ZfzzjvvDPh8o7WptLSUP/3pT3z729/muuuuw3GcgsdO1Xs1nF1DMdnfu3Q6zde//nX+9//+3/3OyZP/fG3b5uqrry64pqiq6tuUzypMhk3D8fnPf57LLrsMgKVLlxKJRNixY8ch/azhOKwK8L3JZrOccsopbNiwAYCnnnqq4EP+3e9+VxABANTW1tLa2sqMGTOwbZtEIkF5eTkXXXSR/5iHHnqIRCKB4zhomkZLS8uwu+ebmpr49Kc/ja7r3H333QSDQX72s5/x9NNPU1dXRyaTmXSbep//yCOPDPgYIQT33HMPK1as8N/HU0891S/mTZTNtm3z5S9/maamJv77v/+bkpIS3+b8e/nLX/4Sy7L41a9+haZp/OIXvyAYDBbYdcopp+C67oTb1fvzveeee/z36gtf+IJ/gXZdl3vuucf/xR4vmzZu3MiFF16IoigsWbKETCZDV1cXH//4x6f0vRrKrjzF9L3bsmULra2tXHfddYAXUVx77bV8//vfp7W11f98161bR0NDA9/97nd59dVXmTVrFn/9618pLy+fVJvuuOMOmpubAfjhD39IXV3dgO/lAw88wKpVq/wUmRDCj4DGm8PWmaRSKf7pn/6J3/zmNwQCAR544IGCUPvll1/m05/+dME5S5cuZcOGDXz2s59l48aNLF68GMMw+n3pFy9ezMaNG1m9ejUbNmwYdvd8XV0dP/rRj4hGo1x88cUsWrSIRx99lIcffpjKykr//LxNL7300oTbNNgvcm8UReG3v/0t/+///T+efvpp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\n", - "text/plain": [ - "<Figure size 432x288 with 1 Axes>" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "sns.lineplot(x=\"timestamp\", y=\"cpu_demand\", data=x)" - ] - }, - { - "cell_type": "code", - "execution_count": 129, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "<AxesSubplot:xlabel='timestamp', ylabel='requested_burst'>" - ] - }, - "execution_count": 129, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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L70U+/fvh6IoIISAEDIldr311lu44YYbkMvlEI/H8a1vfQvxePyQvlalbGpvb6/pz5xHV1cXLrjgAmzatAnz588f9f7+7Gc/w7e//W1YloW6ujps3LgRqqr6dmWzWfT09ECSpKraVY6R3/OOjg6cd955OOywwwAAjY2NuPvuu8vef9LQaeDll1+mGzZsoKtXr6b79u2jO3bsoKeddhrt7u6mlFJ644030nvuuafoPo7j0Msvv5w+8MADlFJK//Vf/5XeddddlFJKf/vb39LPfvazJY919dVX04ceeohSSuntt99Ob7nlFkoppfl8nv7Hf/wHPfLII+l1111X1q41a9bQn//855RSSt/3vvfRD33oQ0X3X7duHT355JOralcpUqkU/cpXvkLXrFlDTzrpJN/mW265hR599NHT+lpeeuml9Mknn6SUUvrXv/6VnnXWWSXf3yuuuII+9NBD9MYbb6RXXnll0XO+5ZZb6MqVK+kHP/jBqth0/vnnl3x/Tz31VPq5z32Orl27ll500UX+a3X33XfTO++8c8pfq8svv5z+8Y9/pJRS+vOf/5x+9KMfPeSv1UibvvCFL5S8fy195rzHvPLKK+mRRx5J9+3bV/L9XbduHf3Od75DKaX0wx/+ML3gggv8+9599930uOOOo8ccc0xV7SpFue/5I488Qr/2ta+VvM9UMi2pm40bN+LGG2/0F49s3boVRx55pP/zmWeeiccff7zoPvfddx+CwaCvfvnUU0/5/96wYQP+9Kc/wbKsovtYloUXXngB73rXuwAAF110kb+n9oUXXgAhBF/84hfL2rVlyxY4joN/+Id/AABcdtll+Pvf/150/7POOguSJFXVrlI88cQTWLRoEZYsWYIzzjjDt/mll16Cqqq4+uqrcc0112DdunVVfy3/4R/+wd8tsGLFCnR2do56f9etW4dXXnkF73rXu3DmmWcilUoVPec333wTS5cuxYIFC6pmU6nPXUtLC1atWoWPfvSjWLRokf9avfrqq/jLX/6Cd7zjHXjrrbdw7LHHToldP/rRj3D66aeDEIKOjg50dXUd8tdqpE2xWAylqKXPHAD88Ic/xMknn4y6ujoApf0KpRQf+MAHAABXXHEFtm3bBsuysGPHDuzYsQPnnXceRFGsql2lKPc9f/XVV7Ft2zZcdNFF+PCHP4ytW7eWfYyDYVoc/U033eR/cQBg5cqV2Lx5Mzo7O+E4Dh555JGibVSO4+COO+7Atdde6/+ucAetLMuIRCIYGBgoOs7g4CAikQhkmWWkmpqa0N3dDQA49dRT8aUvfQmBQKCsXa2traCUore3F47j4LnnnoNpmv79r732Wjz99NNYvXp1Ve0qxXve8x5cffXVOOusszBv3jz/921tbSCE4I477sBpp52GW265peqv5UUXXQRJkgAAt956K84///xR7+/LL7+MYDAIQRDwyCOPIJlM+vc/6aSTsGvXLqxfv75qNp111lklP3e9vb04//zzIQgCduzY4b9W0WgUl19+OURRxCWXXILPf/7zU2KXLMtIpVI4/fTT8Ytf/ALf+c53DvlrNdKm97///ShFLX3mXnvtNTz33HP46Ec/6t++1Pubz+dh2zYcx8Fjjz0GQRAwMDCAww8/HN/4xjfw6KOP+ifMatlVinLfc03T8J73vAebNm3Cxz72MfzTP/2T73OmkkNSjF28eDGuvfZafOITn8Bll12GFStWQFEU/+9//vOfsXjxYqxYsWLMxxFHbE+nB7mndsGCBYhEIr5dy5cvL7r/n//8ZzQ2NiIej0+rXWPx3e9+F1/96lfxiU98Ag8++CCy2WzR8av1WlJKcfPNN2Pz5s24/vrri263ePFiXH311RgaGip6f737eza1trZOm02eXd7nbtOmTWhoaPA/d9/4xjegqioWL16Mz33uc3jrrbeQTpfeETpRu2KxGJ5++mn853/+Jz7xiU/AcZwimw7FazWWTeMx3Z85XdfxjW98A//2b/826j6FLF68GJIk4VOf+pT/WhYe589//jNaW1sRCoWm1a6x+PSnP41LL70UAHDGGWcgFAph586dk3qssTgkevSGYWDt2rW4//77AWDUWfbxxx8vimAAoLm5GX19fWhtbYVt28hkMkgkErjwwgv92/zmN79BJpOB4ziQJAm9vb3j7qm98MIL0d3djauuugq//vWvYVkW7rvvPkiShF/96lfQNK3IrrVr14IQMi12efzud78reRtKKW677TasX7/efy3XrVvnF3Y8m6f6tbRtG9dddx26u7tx7733IhqNAoD/OsqyjO9+97vQNA0/+9nP8MQTT6ClpQX5fH7abSp8f++//37/c3fbbbdhx44dUFUVhBDcdddd2L9/f5FdsiwftF0PP/ww3v3ud0MQBJx++unI5/N+xH6oXqtyNhVGpbX0mXvxxRfR19eHT3ziEwBYFH711Vfj9ttvxze/+U3/tbzrrrvQ2NiIu+66C62trfj9738PAEgkEr5dJ554Il555ZVpsaunpwcA8P3vfx8tLS0lX8+f/OQn2LBhg5/2oZT6Vw5TySGJ6HO5HK644gpkMhmYpomf/OQnRR+Ml19+ueiSDGBnO++D9fDDD+PYY4+Foij43e9+5/+nKAqOPfZYPPzwwwCA+++/f9w9tb/73e/Q0tKCH/zgB7AsC4QQbNq0CaZp4q677sLRRx9dZNeiRYumzS7vv3IIgoDHHnsMH/zgB5HJZPCb3/wGqqpiw4YNVX0tb775ZmQyGdxzzz2+QwXgv4733nsvPvaxj+HII4/EAw88gJ/85CeIRqP+/afTpsL3t/Bz5zgONm/ejPXr10MURTz22GN4+umnceyxx+L+++/HunXrEAwGD9que+65B4899hgA4Nlnn0VdXR3q6+sP6WtVzqZa/cyddtppePLJJ/3bNTc34/vf/z6WLFmCH/zgB/5rGY1GkU6nsXHjRpimiVtvvRWHH364f9X28ssvj7rKqKZd3u/LOXmA5e5/85vfAACef/55EEKwZMmSsrefNFUv9xZw5pln+lXpjRs30vXr19NzzjmH3nrrrUW3W7t2Lc3n80W/GxwcpB//+Mfp+vXr6SWXXFK2ur1//356+eWX03e/+930yiuvpENDQ0V/v++++0Z1txTa9f3vf5+uW7eOrl69mr7zne8suv/atWvpL3/5y6L7V9OuUtx666301ltv9W3etm0bPeuss+iaNWvoEUccQW+66aai20/1a9nf309XrVpFzz77bHrBBRf4/418HTdu3EjPPvtsesQRR9ATTjih6Dl7NhU+52rZVMqu9evX0+OOO45edtll/m22bdtGV6xYQc8991x6+eWX046OjoO2i1JKt2/fTi+99FJ6wQUX0Msuu4xu27btkL5WY9lUjkP9mRtJ4Ws38ucf//jH9Mgjj6SrV6+mp59+etHt1q5dS//0pz/Ryy+/fFrsKsXI73lXVxf9yEc+Qs877zx60UUX0TfeeGPM+08WvmGKw+FwZjl8MpbD4XBmOdzRczgcziyHO3oOh8OZ5XBHz+FwOLMc7ug5HA5nlsMdPWdWcOWVV2JgYABXXXUV3nrrraoea9++ffj0pz9d1WNwOFPJIZmM5XCmmr/85S8AgB/84AdVP1ZHRwd27dpV9eNwOFMF76PnzHi+8pWvYNOmTVi+fDneeustbNy4EblcDv/5n/+J5uZmbN++HcFgEJ/+9Kfxk5/8BLt27cI555zj6+E8+eSTuOOOO2BZFgKBAK677jocddRR2LFjB7761a/CNE1QSnHxxRfj0ksvxbnnnovu7m4cd9xxuPvuu3HnnXfi8ccfh2EY0HUd1113Hc4++2zcdttt2Lt3L/bt24eenh6sXbsWp5xyCu6//37s378fX/ziF7Fhwwbcdttt2L59O/r6+tDf34+VK1fipptuQiQSOcSvLGfWUJUxLA5nmlm+fDnt7++nZ555Jn3llVfos88+S1etWkVff/11SimlH/vYx+gll1xCDcOg/f39dPXq1bSrq4vu2rWLbtiwgQ4MDFBK2YTsKaecQrPZLP3KV77ia5X39PTQz33uc9RxHPrss8/S8847j1LKJiY/9KEPUV3XKaWUPvTQQ3TDhg2UUupPk6ZSKarrOj3uuOPoN7/5TUoppY899hg955xz/NudfvrptLe3lzqOQ7/whS/Qb33rW9P34nFmPTx1w5m1zJ8/H29729sAAIcddhii0ShUVUV9fT3C4TCSySReeOEF9PT04CMf+Yh/P0EQsHfvXpx99tm47rrr8Morr+Ckk07CDTfcMEqlsL29HTfffDMefPBB7NmzB5s3b0Y2m/X/fvLJJ/vaO83NzTjttNN8e4aGhvzbnXvuuWhsbAQAXHzxxfj3f/93XHfdddV4WThzEF6M5cxaVFUt+rmUKiAhBCeddFKRiNXGjRtx+OGH48wzz8Qf/vAHvPvd78Ybb7yB888/H3v37i26/+uvv45LL70UmUwGp5xyCv7xH/9xwjYA8LX0PZsmK3vL4ZSCf5o4swJJkmDb9oTvd+KJJ+Ivf/kLduzYAQD44x//iAsuuACGYeDaa6/Fww8/jPPOOw833ngjIpEIOjs7IUmSv4XohRdewJo1a/DRj34Uxx9/PJ544okJabt7PPHEE0in0yCEYOPGjTjzzDMn/BgcTjl46oYzKzj77LPxwQ9+sChtUgne5qEvfOELvhb4HXfcgVAohE9+8pP46le/il/96leQJAlnnXUWjj/+eKRSKUiShIsvvhh33nknHn30Uaxfvx6KouCkk05CMplEJpOZkB2NjY246qqrMDg4iOOOOw7XXHPNhO7P4YwF77rhcA4xt912GwYHB/Ev//Ivh9oUziyFp244HA5nlsMjeg6Hw5nl8Iiew+FwZjnc0XM4HM4shzt6DofDmeVwR8/hcDizHO7oORwOZ5bDHT2Hw+HMcv4/3vtnH3kfWvEAAAAASUVORK5CYII=\n", - "text/plain": [ - "<Figure size 432x288 with 1 Axes>" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "sns.lineplot(x=\"timestamp\", y=\"requested_burst\", data=x)" - ] - }, - { - "cell_type": "code", - "execution_count": 130, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "<AxesSubplot:xlabel='timestamp', ylabel='granted_burst'>" - ] - }, - "execution_count": 130, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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k7N6rMrZtHDhwpo+53y6urpw0UUXYevWrWhvbx/1/f7oRz/CV7/6Vdi2jbq6OmzZsgWqqgZ25fN59PT0QJKkqto1FiN/5x0dHdi0aRMWL14MAGhsbMTdd9895vZThs4AL7zwAt28eTNdv349PXDgAN21axc988wzaXd3N6WU0ltvvZV+73vfK9nGdV161VVX0QcffJBSSun//b//l951112UUkp/+ctf0n/5l38pu6/rr7+e/upXv6KUUvqtb32L3nbbbZRSSg3DoP/5n/9Jjz32WHrjjTeOadeGDRvoj3/8Y0oppe9617vo+9///pLtjznmGHr66adX1a5yZDIZ+tnPfpZu2LCBnnbaaYHNt912Gz3++ONn9FheccUV9IknnqCUUvr000/Tc845p+z3+8EPfpD+6le/orfeeiu95pprSj7zbbfdRteuXUvf9773VcWmCy+8sOz3+6Y3vYl+/OMfpxs3bqSXXnppcKzuvvtueuedd077sbrqqqvo73//e0oppT/+8Y/p1VdfPevHaqRNn/zkJ8tuX0vnnP+e11xzDT322GPpgQMHyn6/xxxzDP3a175GKaX0Ax/4AL3ooouCbe+++2560kkn0RNOOKGqdpVjrN/5I488Qj/3uc+V3WY6mZHUzZYtW3DrrbcGg0feeOMNHHvsscHfZ599Nn7729+WbHPfffchHA4H6pdPPvlk8N+bN2/GH/7wB9i2XbKNbdt49tln8fa3vx0AcOmllwZzap999lkQQvDpT396TLteffVVuK6Ld7/73QCAK6+8Es8//3zJ9ueccw4kSaqqXeV4/PHHsXTpUixfvhxvfvObA5v/9re/QVVVXH/99bjhhhtwzDHHVP1Yvvvd7w5mC6xZswadnZ2jvt9jjjkGL774It7+9rfj7LPPRiaTKfnMr7/+OlasWIFFixZVzaZy511LSwvWrVuHq6++GkuXLg2O1UsvvYQ//elPeOtb34qdO3fixBNPnBa7vv/97+Oss84CIQQdHR3o6uqa9WM10qZEIoFy1NI5BwDf/e53cfrpp6Ourg5Aeb9CKcV73/teAMAHP/hBbN++HbZtY9euXdi1axc2bdoEURSralc5xvqdv/TSS9i+fTsuvfRSfOADH8Abb7wx5nscDjPi6L/4xS8GPxwAWLt2LbZt24bOzk64rotHHnmkZBqV67q444478KlP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jp4z5/nsZz+LrVu3YvXq1di5cye2bNmCQqGA//qv/0JzczN27NiBcDiMj370o7j33nuxZ88enHfeeYEezhNPPIE77rgDtm0jFArhxhtvxHHHHYddu3bhX//1X2FZFiiluOyyy3DFFVfg/PPPR3d3N0466STcfffduPPOO/Hb3/4WpmlC13XceOONOPfcc/HNb34T+/fvx4EDB9DT04ONGzfijDPOwP3334+DBw/i05/+NDZv3oxvfvOb2LFjB/r6+tDf34+1a9fii1/8ImKx2CwfWc68oSptWBzODLN69Wra399Pzz77bPriiy/SZ555hq5bt46+8sorlFJKr732Wvqe97yHmqZJ+/v76fr162lXVxfds2cP3bx5Mx0YGKCUsg7ZM844g+bzefrZz3420Crv6emhH//4x6nruvSZZ56hmzZtopSyjsn3v//9VNd1Simlv/rVr+jmzZsppTToJs1kMlTXdXrSSSfRL33pS5RSSh977DF63nnnBa8766yzaG9vL3Vdl37yk5+kX/7yl2fu4HHmPTx1w5m3tLe346ijjgIALF68GPF4HKqqor6+HtFoFOl0Gs8++yx6enrwoQ99KNhOEATs378f5557Lm688Ua8+OKLOO2003DLLbeMUilcuHAhvvKVr+Chhx7Cvn37sG3bNuTz+eD5008/PdDeaW5uxplnnhnYMzQ0FLzu/PPPR2NjIwDgsssuw3/8x3/gxhtvrMZh4RyB8MVYzrxFVdWSv8upAhJCcNppp5WIWG3ZsgWrVq3C2Wefjd/85jd4xzvegddeew0XXngh9u/fX7L9K6+8giuuuAK5XA5nnHEG/uEf/mHSNgAItPR9m6Yqe8vhlIOfTZx5gSRJcBxn0tudeuqp+NOf/oRdu3YBAH7/+9/joosugmma+NSnPoWHH34YmzZtwq233opYLIbOzk5IkhRMIXr22WexYcMGXH311Tj55JPx+OOPT0rb3efxxx9HNpsFIQRbtmzB2WefPen34HDGgqduOPOCc889F+973/tK0iaV4E8e+uQnPxlogd9xxx2IRCL4p3/6J/zrv/4rfvazn0GSJJxzzjk4+eSTkclkIEkSLrvsMtx555149NFHccEFF0BRFJx22mlIp9PI5XKTsqOxsRHXXXcdBgcHcdJJJ+GGG26Y1PYcznjwqhsOZ5b55je/icHBQXz+85+fbVM48xSeuuFwOJx5Do/oORwOZ57DI3oOh8OZ53BHz+FwOPMc7ug5HA5nnsMdPYfD4cxzuKPncDiceQ539BwOhzPP+f8BGzg98J4V3VcAAAAASUVORK5CYII=\n", - "text/plain": [ - "<Figure size 432x288 with 1 Axes>" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "sns.lineplot(x=\"timestamp\", y=\"granted_burst\", data=x)" - ] - }, - { - "cell_type": "code", - "execution_count": 131, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "<AxesSubplot:xlabel='timestamp', ylabel='overcommissioned_burst'>" - ] - }, - "execution_count": 131, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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eisdNWoUnTVrlnmvnnvuObp8+XLu9+rKK6+k77//PqWU0j//+c/02muv7fZ7FS/T7bffnvR64z03atQoevfdd5ty//a3v6W//vWvj/ln2Fjzuuuuo2PGjKFr165Nei/vuusu+vTTT9PFixfT2267zXwcVVXp73//e3rqqafSqVOnZkSmAwcOJL2+o895dXU1vffee5New5usdCutWLECixcvNmdIf/311xgzZoz5/YQJE7Bu3bqYa15//XXk5uaaw4L+8Y9/mF/PmDED//znPyHLsYPsZVnG5s2bMWXKFADArFmzUF1dDQDYvHkzCCExJ4h4ub766iuoqorvf//7AID58+fj3//+d8z1EydOhNPpzKhcyVi/fj0GDx6ME088ERdeeKEp85YtW+DxeHD99dfjhhtuwOjRozN+L7///e+bI1+HDx+OI0eOJLy+o0ePxrZt2zBlyhRMmDABra2tMc95x44dGDp0KAYOHJgxmZK97yorK3HKKafg2muvxeDBg8179fnnn+ODDz7ARRddhN27d2Ps2LFc5HrhhRcwfvx4EEJw+PBhHD16tNvvVbxMhYWFSIbxnrv22mvx1VdfmXLv2LEDGzduRCAQwH333YcjR44ck88wAPz+97/Heeedh379+uFvf/tb0n3l448/xsyZMzFhwgQcPXrUfJw9e/Zg/fr1OPnkk1FWVpYRmTqio8/5559/jp07d2LWrFm4+uqr8fXXX3e4BitZqRwefPBB88MGACNGjMDWrVtx5MgRqKqK6upq1NfXm79XVRVPPfUU7rjjDvNntbW1prnncrng8/nQ2NgY8zhNTU3w+XxwuTTvWnl5OWpqagAA3/3ud/E///M/yMnJ6VCu/v37g1KKuro6qKqKjz/+GJFIxLz+jjvuwMaNG3HaaadlVK5kXHbZZbj++usxceJEHH/88ebPjzvuOBBC8NRTT+GCCy7AI488kvF7OWvWLDid2pD6xx57DDNnzkx4fT/77DPk5uZCkiRUV1ejpaXFvH7cuHHYu3cvpk2bljGZJk6cmPR9V1dXh5kzZ0KSJOzZs8e8VwUFBbjyyivhcDgwd+5c3HbbbVzkcrlcaG1txfjx4/GXv/wF//u//9vt9ypepssvvxzJMN5zTqcTF110kSm3z+fDFVdcgfz8fIwZMwa33nrrMfkMf/HFF/j4449x7bXXAgB+9rOf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\n", - "text/plain": [ - "<Figure size 432x288 with 1 Axes>" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "sns.lineplot(x=\"timestamp\", y=\"overcommissioned_burst\", data=x)" - ] - }, - { - "cell_type": "code", - "execution_count": 105, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "<AxesSubplot:xlabel='timestamp', ylabel='vm_count'>" - ] - }, - "execution_count": 105, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "<Figure size 432x288 with 1 Axes>" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "sns.lineplot(x=\"timestamp\", y=\"vm_count\", data=df.resample('1D').mean())" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "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.9" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/opendc-experiments/opendc-experiments-energy21/src/main/kotlin/org/opendc/experiments/energy21/EnergyExperiment.kt b/opendc-experiments/opendc-experiments-energy21/src/main/kotlin/org/opendc/experiments/energy21/EnergyExperiment.kt deleted file mode 100644 index 7460a1e7..00000000 --- a/opendc-experiments/opendc-experiments-energy21/src/main/kotlin/org/opendc/experiments/energy21/EnergyExperiment.kt +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.experiments.energy21 - -import com.typesafe.config.ConfigFactory -import io.opentelemetry.api.metrics.MeterProvider -import io.opentelemetry.sdk.metrics.SdkMeterProvider -import io.opentelemetry.sdk.metrics.export.MetricProducer -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.coroutineScope -import mu.KotlinLogging -import org.opendc.compute.service.ComputeService -import org.opendc.compute.service.scheduler.ComputeScheduler -import org.opendc.compute.service.scheduler.FilterScheduler -import org.opendc.compute.service.scheduler.filters.ComputeCapabilitiesFilter -import org.opendc.compute.service.scheduler.filters.ComputeFilter -import org.opendc.compute.service.scheduler.weights.RandomWeigher -import org.opendc.compute.simulator.SimHost -import org.opendc.experiments.capelin.* -import org.opendc.experiments.capelin.monitor.ParquetExperimentMonitor -import org.opendc.experiments.capelin.trace.Sc20StreamingParquetTraceReader -import org.opendc.harness.dsl.Experiment -import org.opendc.harness.dsl.anyOf -import org.opendc.simulator.compute.SimFairShareHypervisorProvider -import org.opendc.simulator.compute.SimMachineModel -import org.opendc.simulator.compute.cpufreq.* -import org.opendc.simulator.compute.model.MemoryUnit -import org.opendc.simulator.compute.model.ProcessingNode -import org.opendc.simulator.compute.model.ProcessingUnit -import org.opendc.simulator.compute.power.* -import org.opendc.simulator.core.runBlockingSimulation -import org.opendc.telemetry.sdk.toOtelClock -import java.io.File -import java.time.Clock -import java.util.* -import kotlin.random.asKotlinRandom - -/** - * Experiments for the OpenDC project on Energy modeling. - */ -public class EnergyExperiment : Experiment("Energy Modeling 2021") { - /** - * The logger for this portfolio instance. - */ - private val logger = KotlinLogging.logger {} - - /** - * The configuration to use. - */ - private val config = ConfigFactory.load().getConfig("opendc.experiments.energy21") - - /** - * The traces to test. - */ - private val trace by anyOf("solvinity") - - /** - * The power models to test. - */ - private val powerModel by anyOf(PowerModelType.LINEAR, PowerModelType.CUBIC, PowerModelType.INTERPOLATION) - - override fun doRun(repeat: Int): Unit = runBlockingSimulation { - val chan = Channel<Unit>(Channel.CONFLATED) - val allocationPolicy = FilterScheduler( - filters = listOf(ComputeFilter(), ComputeCapabilitiesFilter()), - weighers = listOf(RandomWeigher(Random(0)) to 1.0) - ) - - val meterProvider: MeterProvider = SdkMeterProvider - .builder() - .setClock(clock.toOtelClock()) - .build() - - val monitor = ParquetExperimentMonitor(File(config.getString("output-path")), "power_model=$powerModel/run_id=$repeat", 4096) - val trace = Sc20StreamingParquetTraceReader(File(config.getString("trace-path"), trace), random = Random(1).asKotlinRandom()) - - withComputeService(clock, meterProvider, allocationPolicy) { scheduler -> - withMonitor(monitor, clock, meterProvider as MetricProducer, scheduler) { - processTrace( - clock, - trace, - scheduler, - chan, - monitor - ) - } - } - - val monitorResults = collectMetrics(meterProvider as MetricProducer) - logger.debug { - "Finish SUBMIT=${monitorResults.submittedVms} " + - "FAIL=${monitorResults.unscheduledVms} " + - "QUEUE=${monitorResults.queuedVms} " + - "RUNNING=${monitorResults.runningVms}" - } - } - - /** - * Construct the environment for a simulated compute service.. - */ - public suspend fun withComputeService( - clock: Clock, - meterProvider: MeterProvider, - scheduler: ComputeScheduler, - block: suspend CoroutineScope.(ComputeService) -> Unit - ): Unit = coroutineScope { - val model = createMachineModel() - val hosts = List(64) { id -> - SimHost( - UUID(0, id.toLong()), - "node-$id", - model, - emptyMap(), - coroutineContext, - clock, - meterProvider.get("opendc-compute-simulator"), - SimFairShareHypervisorProvider(), - PerformanceScalingGovernor(), - powerModel.driver - ) - } - - val serviceMeter = meterProvider.get("opendc-compute") - val service = - ComputeService(coroutineContext, clock, serviceMeter, scheduler) - - for (host in hosts) { - service.addHost(host) - } - - try { - block(this, service) - } finally { - service.close() - hosts.forEach(SimHost::close) - } - } - - /** - * The machine model based on: https://www.spec.org/power_ssj2008/results/res2020q1/power_ssj2008-20191125-01012.html - */ - private fun createMachineModel(): SimMachineModel { - val node = ProcessingNode("AMD", "am64", "EPYC 7742", 64) - val cpus = List(node.coreCount) { id -> ProcessingUnit(node, id, 3400.0) } - val memory = List(8) { MemoryUnit("Samsung", "Unknown", 2933.0, 16_000) } - - return SimMachineModel(cpus, memory) - } - - /** - * The power models to test. - */ - public enum class PowerModelType { - CUBIC { - override val driver: ScalingDriver = SimpleScalingDriver(CubicPowerModel(206.0, 56.4)) - }, - - LINEAR { - override val driver: ScalingDriver = SimpleScalingDriver(LinearPowerModel(206.0, 56.4)) - }, - - SQRT { - override val driver: ScalingDriver = SimpleScalingDriver(SqrtPowerModel(206.0, 56.4)) - }, - - SQUARE { - override val driver: ScalingDriver = SimpleScalingDriver(SquarePowerModel(206.0, 56.4)) - }, - - INTERPOLATION { - override val driver: ScalingDriver = SimpleScalingDriver( - InterpolationPowerModel( - listOf(56.4, 100.0, 107.0, 117.0, 127.0, 138.0, 149.0, 162.0, 177.0, 191.0, 206.0) - ) - ) - }, - - MSE { - override val driver: ScalingDriver = SimpleScalingDriver(MsePowerModel(206.0, 56.4, 1.4)) - }, - - ASYMPTOTIC { - override val driver: ScalingDriver = SimpleScalingDriver(AsymptoticPowerModel(206.0, 56.4, 0.3, false)) - }, - - ASYMPTOTIC_DVFS { - override val driver: ScalingDriver = SimpleScalingDriver(AsymptoticPowerModel(206.0, 56.4, 0.3, true)) - }; - - public abstract val driver: ScalingDriver - } -} diff --git a/opendc-experiments/opendc-experiments-energy21/src/main/resources/application.conf b/opendc-experiments/opendc-experiments-energy21/src/main/resources/application.conf deleted file mode 100644 index 3e011862..00000000 --- a/opendc-experiments/opendc-experiments-energy21/src/main/resources/application.conf +++ /dev/null @@ -1,8 +0,0 @@ -# Default configuration for the energy experiments -opendc.experiments.energy21 { - # Path to the directory containing the input traces - trace-path = input/traces - - # Path to the output directory to write the results to - output-path = output -} diff --git a/opendc-experiments/opendc-experiments-serverless20/build.gradle.kts b/opendc-experiments/opendc-experiments-serverless20/build.gradle.kts index bdb0d098..65c31c4f 100644 --- a/opendc-experiments/opendc-experiments-serverless20/build.gradle.kts +++ b/opendc-experiments/opendc-experiments-serverless20/build.gradle.kts @@ -32,14 +32,9 @@ dependencies { api(platform(projects.opendcPlatform)) api(projects.opendcHarness.opendcHarnessApi) implementation(projects.opendcSimulator.opendcSimulatorCore) - implementation(projects.opendcServerless.opendcServerlessService) - implementation(projects.opendcServerless.opendcServerlessSimulator) + implementation(projects.opendcFaas.opendcFaasService) + implementation(projects.opendcFaas.opendcFaasSimulator) implementation(projects.opendcTelemetry.opendcTelemetrySdk) implementation(libs.kotlin.logging) implementation(libs.config) - - implementation(libs.parquet) { - exclude(group = "org.slf4j", module = "slf4j-log4j12") - exclude(group = "log4j") - } } diff --git a/opendc-experiments/opendc-experiments-serverless20/src/main/kotlin/org/opendc/experiments/serverless/ServerlessExperiment.kt b/opendc-experiments/opendc-experiments-serverless20/src/main/kotlin/org/opendc/experiments/serverless/ServerlessExperiment.kt index 516bcc3e..3312d6c0 100644 --- a/opendc-experiments/opendc-experiments-serverless20/src/main/kotlin/org/opendc/experiments/serverless/ServerlessExperiment.kt +++ b/opendc-experiments/opendc-experiments-serverless20/src/main/kotlin/org/opendc/experiments/serverless/ServerlessExperiment.kt @@ -31,21 +31,22 @@ import kotlinx.coroutines.launch import mu.KotlinLogging import org.opendc.experiments.serverless.trace.FunctionTraceWorkload import org.opendc.experiments.serverless.trace.ServerlessTraceReader +import org.opendc.faas.service.FaaSService +import org.opendc.faas.service.autoscaler.FunctionTerminationPolicyFixed +import org.opendc.faas.service.router.RandomRoutingPolicy +import org.opendc.faas.simulator.SimFunctionDeployer +import org.opendc.faas.simulator.delay.ColdStartModel +import org.opendc.faas.simulator.delay.StochasticDelayInjector import org.opendc.harness.dsl.Experiment import org.opendc.harness.dsl.anyOf -import org.opendc.serverless.service.ServerlessService -import org.opendc.serverless.service.autoscaler.FunctionTerminationPolicyFixed -import org.opendc.serverless.service.router.RandomRoutingPolicy -import org.opendc.serverless.simulator.SimFunctionDeployer -import org.opendc.serverless.simulator.delay.ColdStartModel -import org.opendc.serverless.simulator.delay.StochasticDelayInjector -import org.opendc.simulator.compute.SimMachineModel +import org.opendc.simulator.compute.model.MachineModel import org.opendc.simulator.compute.model.MemoryUnit import org.opendc.simulator.compute.model.ProcessingNode import org.opendc.simulator.compute.model.ProcessingUnit import org.opendc.simulator.core.runBlockingSimulation import org.opendc.telemetry.sdk.toOtelClock import java.io.File +import java.time.Duration import java.util.* import kotlin.math.max @@ -85,7 +86,7 @@ public class ServerlessExperiment : Experiment("Serverless") { val delayInjector = StochasticDelayInjector(coldStartModel, Random()) val deployer = SimFunctionDeployer(clock, this, createMachineModel(), delayInjector) { FunctionTraceWorkload(traceById.getValue(it.name)) } val service = - ServerlessService(coroutineContext, clock, meterProvider.get("opendc-serverless"), deployer, routingPolicy, FunctionTerminationPolicyFixed(coroutineContext, clock, timeout = 10 * 60 * 1000)) + FaaSService(coroutineContext, clock, meterProvider, deployer, routingPolicy, FunctionTerminationPolicyFixed(coroutineContext, clock, timeout = Duration.ofMinutes(10))) val client = service.newClient() coroutineScope { @@ -122,10 +123,10 @@ public class ServerlessExperiment : Experiment("Serverless") { /** * Construct the machine model to test with. */ - private fun createMachineModel(): SimMachineModel { + private fun createMachineModel(): MachineModel { val cpuNode = ProcessingNode("Intel", "Xeon", "amd64", 2) - return SimMachineModel( + return MachineModel( cpus = List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 1000.0) }, memory = List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) } ) diff --git a/opendc-experiments/opendc-experiments-serverless20/src/main/kotlin/org/opendc/experiments/serverless/trace/FunctionTraceWorkload.kt b/opendc-experiments/opendc-experiments-serverless20/src/main/kotlin/org/opendc/experiments/serverless/trace/FunctionTraceWorkload.kt index 7d824857..bbe130e3 100644 --- a/opendc-experiments/opendc-experiments-serverless20/src/main/kotlin/org/opendc/experiments/serverless/trace/FunctionTraceWorkload.kt +++ b/opendc-experiments/opendc-experiments-serverless20/src/main/kotlin/org/opendc/experiments/serverless/trace/FunctionTraceWorkload.kt @@ -22,13 +22,16 @@ package org.opendc.experiments.serverless.trace -import org.opendc.serverless.simulator.workload.SimServerlessWorkload +import org.opendc.faas.simulator.workload.SimFaaSWorkload +import org.opendc.simulator.compute.workload.SimTrace +import org.opendc.simulator.compute.workload.SimTraceFragment import org.opendc.simulator.compute.workload.SimTraceWorkload import org.opendc.simulator.compute.workload.SimWorkload /** - * A [SimServerlessWorkload] for a [FunctionTrace]. + * A [SimFaaSWorkload] for a [FunctionTrace]. */ -public class FunctionTraceWorkload(trace: FunctionTrace) : SimServerlessWorkload, SimWorkload by SimTraceWorkload(trace.samples.asSequence().map { SimTraceWorkload.Fragment(it.duration, it.cpuUsage, 1) }) { +class FunctionTraceWorkload(trace: FunctionTrace) : + SimFaaSWorkload, SimWorkload by SimTraceWorkload(SimTrace.ofFragments(trace.samples.map { SimTraceFragment(it.timestamp, it.duration, it.cpuUsage, 1) })) { override suspend fun invoke() {} } diff --git a/opendc-experiments/opendc-experiments-tf20/build.gradle.kts b/opendc-experiments/opendc-experiments-tf20/build.gradle.kts index 64483bd4..882c4894 100644 --- a/opendc-experiments/opendc-experiments-tf20/build.gradle.kts +++ b/opendc-experiments/opendc-experiments-tf20/build.gradle.kts @@ -34,13 +34,11 @@ dependencies { implementation(projects.opendcSimulator.opendcSimulatorCore) implementation(projects.opendcSimulator.opendcSimulatorCompute) implementation(projects.opendcTelemetry.opendcTelemetrySdk) - implementation(projects.opendcFormat) implementation(projects.opendcUtils) implementation(libs.kotlin.logging) - implementation(libs.parquet) - implementation(libs.hadoop.client) { - exclude(group = "org.slf4j", module = "slf4j-log4j12") - exclude(group = "log4j") + implementation(libs.jackson.module.kotlin) { + exclude(group = "org.jetbrains.kotlin", module = "kotlin-reflect") } + implementation("org.jetbrains.kotlin:kotlin-reflect:1.5.30") } diff --git a/opendc-experiments/opendc-experiments-tf20/src/main/kotlin/org/opendc/experiments/tf20/TensorFlowExperiment.kt b/opendc-experiments/opendc-experiments-tf20/src/main/kotlin/org/opendc/experiments/tf20/TensorFlowExperiment.kt index 9a48aced..2153a862 100644 --- a/opendc-experiments/opendc-experiments-tf20/src/main/kotlin/org/opendc/experiments/tf20/TensorFlowExperiment.kt +++ b/opendc-experiments/opendc-experiments-tf20/src/main/kotlin/org/opendc/experiments/tf20/TensorFlowExperiment.kt @@ -55,7 +55,8 @@ public class TensorFlowExperiment : Experiment(name = "tf20") { .build() val meter = meterProvider.get("opendc-tf20") - val def = MLEnvironmentReader(TensorFlowExperiment::class.java.getResourceAsStream(environmentFile)).read().first() + val envInput = checkNotNull(TensorFlowExperiment::class.java.getResourceAsStream(environmentFile)) + val def = MLEnvironmentReader().readEnvironment(envInput).first() val device = SimTFDevice( def.uid, def.meta["gpu"] as Boolean, coroutineContext, clock, meter, def.model.cpus[0], def.model.memory[0], LinearPowerModel(250.0, 60.0) diff --git a/opendc-experiments/opendc-experiments-tf20/src/main/kotlin/org/opendc/experiments/tf20/core/SimTFDevice.kt b/opendc-experiments/opendc-experiments-tf20/src/main/kotlin/org/opendc/experiments/tf20/core/SimTFDevice.kt index f4c18ff1..fb36d2c7 100644 --- a/opendc-experiments/opendc-experiments-tf20/src/main/kotlin/org/opendc/experiments/tf20/core/SimTFDevice.kt +++ b/opendc-experiments/opendc-experiments-tf20/src/main/kotlin/org/opendc/experiments/tf20/core/SimTFDevice.kt @@ -22,28 +22,26 @@ package org.opendc.experiments.tf20.core +import io.opentelemetry.api.common.AttributeKey +import io.opentelemetry.api.common.Attributes import io.opentelemetry.api.metrics.Meter -import io.opentelemetry.api.metrics.common.Labels import kotlinx.coroutines.* import org.opendc.simulator.compute.SimBareMetalMachine import org.opendc.simulator.compute.SimMachine import org.opendc.simulator.compute.SimMachineContext -import org.opendc.simulator.compute.SimMachineModel -import org.opendc.simulator.compute.cpufreq.PerformanceScalingGovernor -import org.opendc.simulator.compute.cpufreq.SimpleScalingDriver +import org.opendc.simulator.compute.model.MachineModel import org.opendc.simulator.compute.model.MemoryUnit import org.opendc.simulator.compute.model.ProcessingUnit import org.opendc.simulator.compute.power.PowerModel +import org.opendc.simulator.compute.power.SimplePowerDriver import org.opendc.simulator.compute.workload.SimWorkload -import org.opendc.simulator.resources.SimResourceCommand -import org.opendc.simulator.resources.SimResourceConsumer -import org.opendc.simulator.resources.SimResourceContext -import org.opendc.simulator.resources.SimResourceEvent +import org.opendc.simulator.flow.* import java.time.Clock import java.util.* import kotlin.coroutines.Continuation import kotlin.coroutines.CoroutineContext import kotlin.coroutines.resume +import kotlin.math.roundToLong /** * A [TFDevice] implementation using simulated components. @@ -67,36 +65,41 @@ public class SimTFDevice( * The [SimMachine] representing the device. */ private val machine = SimBareMetalMachine( - scope.coroutineContext, clock, SimMachineModel(listOf(pu), listOf(memory)), - PerformanceScalingGovernor(), SimpleScalingDriver(powerModel) + FlowEngine(scope.coroutineContext, clock), MachineModel(listOf(pu), listOf(memory)), + SimplePowerDriver(powerModel) ) /** + * The identifier of a device. + */ + private val deviceId = AttributeKey.stringKey("device.id") + + /** * The usage of the device. */ - private val _usage = meter.doubleValueRecorderBuilder("device.usage") + private val _usage = meter.histogramBuilder("device.usage") .setDescription("The amount of device resources used") .setUnit("MHz") .build() - .bind(Labels.of("device", uid.toString())) + .bind(Attributes.of(deviceId, uid.toString())) /** * The power draw of the device. */ - private val _power = meter.doubleValueRecorderBuilder("device.power") + private val _power = meter.histogramBuilder("device.power") .setDescription("The power draw of the device") .setUnit("W") .build() - .bind(Labels.of("device", uid.toString())) + .bind(Attributes.of(deviceId, uid.toString())) /** * The workload that will be run by the device. */ - private val workload = object : SimWorkload, SimResourceConsumer { + private val workload = object : SimWorkload, FlowSource { /** * The resource context to interrupt the workload with. */ - var ctx: SimResourceContext? = null + var ctx: FlowConnection? = null /** * The capacity of the device. @@ -119,17 +122,32 @@ public class SimTFDevice( */ private var activeWork: Work? = null - override fun onStart(ctx: SimMachineContext) {} + override fun onStart(ctx: SimMachineContext) { + for (cpu in ctx.cpus) { + cpu.startConsumer(this) + } + } + + override fun onStart(conn: FlowConnection, now: Long) { + ctx = conn + capacity = conn.capacity - override fun getConsumer(ctx: SimMachineContext, cpu: ProcessingUnit): SimResourceConsumer = this + conn.shouldSourceConverge = true + } + + override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long { + val consumedWork = conn.rate * delta / 1000.0 + + capacity = conn.capacity - override fun onNext(ctx: SimResourceContext): SimResourceCommand { val activeWork = activeWork if (activeWork != null) { - if (activeWork.consume(activeWork.flops - ctx.remainingWork)) { + if (activeWork.consume(consumedWork)) { this.activeWork = null } else { - return SimResourceCommand.Consume(activeWork.flops, ctx.capacity) + val duration = (activeWork.flops / conn.capacity * 1000).roundToLong() + conn.push(conn.capacity) + return duration } } @@ -137,28 +155,18 @@ public class SimTFDevice( val head = queue.poll() return if (head != null) { this.activeWork = head - SimResourceCommand.Consume(head.flops, ctx.capacity) + val duration = (head.flops / conn.capacity * 1000).roundToLong() + conn.push(conn.capacity) + duration } else { - SimResourceCommand.Idle() + conn.push(0.0) + Long.MAX_VALUE } } - override fun onEvent(ctx: SimResourceContext, event: SimResourceEvent) { - when (event) { - SimResourceEvent.Start -> { - this.ctx = ctx - this.capacity = ctx.capacity - } - SimResourceEvent.Capacity -> { - this.capacity = ctx.capacity - ctx.interrupt() - } - SimResourceEvent.Run -> { - _usage.record(ctx.speed) - _power.record(machine.powerDraw) - } - else -> {} - } + override fun onConverge(conn: FlowConnection, now: Long, delta: Long) { + _usage.record(conn.rate) + _power.record(machine.psu.powerDraw) } } @@ -176,7 +184,7 @@ public class SimTFDevice( override suspend fun compute(flops: Double) = suspendCancellableCoroutine<Unit> { cont -> workload.queue.add(Work(flops, cont)) if (workload.isIdle) { - workload.ctx?.interrupt() + workload.ctx?.pull() } } diff --git a/opendc-experiments/opendc-experiments-tf20/src/main/kotlin/org/opendc/experiments/tf20/distribute/Strategy.kt b/opendc-experiments/opendc-experiments-tf20/src/main/kotlin/org/opendc/experiments/tf20/distribute/Strategy.kt index 5839c0df..3e755b56 100644 --- a/opendc-experiments/opendc-experiments-tf20/src/main/kotlin/org/opendc/experiments/tf20/distribute/Strategy.kt +++ b/opendc-experiments/opendc-experiments-tf20/src/main/kotlin/org/opendc/experiments/tf20/distribute/Strategy.kt @@ -27,7 +27,7 @@ package org.opendc.experiments.tf20.distribute */ public interface Strategy { /** - * Run the specified batch using the given strategy. + * Converge the specified batch using the given strategy. */ public suspend fun run(forward: Double, backward: Double, batchSize: Int) } diff --git a/opendc-experiments/opendc-experiments-tf20/src/main/kotlin/org/opendc/experiments/tf20/keras/Sequential.kt b/opendc-experiments/opendc-experiments-tf20/src/main/kotlin/org/opendc/experiments/tf20/keras/Sequential.kt index 411ddb59..83995fa1 100644 --- a/opendc-experiments/opendc-experiments-tf20/src/main/kotlin/org/opendc/experiments/tf20/keras/Sequential.kt +++ b/opendc-experiments/opendc-experiments-tf20/src/main/kotlin/org/opendc/experiments/tf20/keras/Sequential.kt @@ -49,10 +49,10 @@ public class Sequential(vararg layers: Layer) : TrainableModel(*layers) { } override fun forward(): Double { - return layers.sumByDouble { it.forward() } + return layers.sumOf { it.forward() } } override fun backward(): Double { - return layers.sumByDouble { it.backward() } + return layers.sumOf { it.backward() } } } diff --git a/opendc-experiments/opendc-experiments-tf20/src/main/kotlin/org/opendc/experiments/tf20/network/NetworkController.kt b/opendc-experiments/opendc-experiments-tf20/src/main/kotlin/org/opendc/experiments/tf20/network/NetworkController.kt index 75b11423..9771cc20 100644 --- a/opendc-experiments/opendc-experiments-tf20/src/main/kotlin/org/opendc/experiments/tf20/network/NetworkController.kt +++ b/opendc-experiments/opendc-experiments-tf20/src/main/kotlin/org/opendc/experiments/tf20/network/NetworkController.kt @@ -82,7 +82,7 @@ public class NetworkController(context: CoroutineContext, clock: Clock) : AutoCl val target = channels[to] ?: return // Drop if destination not found - scheduler.startSingleTimer(message, delayTime) { target.offer(message) } + scheduler.startSingleTimer(message, delayTime) { target.trySend(message) } } /** diff --git a/opendc-experiments/opendc-experiments-tf20/src/main/kotlin/org/opendc/experiments/tf20/util/MLEnvironmentReader.kt b/opendc-experiments/opendc-experiments-tf20/src/main/kotlin/org/opendc/experiments/tf20/util/MLEnvironmentReader.kt index eea079fb..3cdf28fd 100644 --- a/opendc-experiments/opendc-experiments-tf20/src/main/kotlin/org/opendc/experiments/tf20/util/MLEnvironmentReader.kt +++ b/opendc-experiments/opendc-experiments-tf20/src/main/kotlin/org/opendc/experiments/tf20/util/MLEnvironmentReader.kt @@ -25,9 +25,7 @@ package org.opendc.experiments.tf20.util import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.readValue -import org.opendc.format.environment.EnvironmentReader -import org.opendc.format.environment.MachineDef -import org.opendc.simulator.compute.SimMachineModel +import org.opendc.simulator.compute.model.MachineModel import org.opendc.simulator.compute.model.MemoryUnit import org.opendc.simulator.compute.model.ProcessingNode import org.opendc.simulator.compute.model.ProcessingUnit @@ -36,13 +34,16 @@ import java.io.InputStream import java.util.* /** - * An [EnvironmentReader] for the TensorFlow experiments. + * An environment reader for the TensorFlow experiments. */ -public class MLEnvironmentReader(input: InputStream, mapper: ObjectMapper = jacksonObjectMapper()) : EnvironmentReader { +public class MLEnvironmentReader { + /** + * The [ObjectMapper] to convert the format. + */ + private val mapper = jacksonObjectMapper() - private val setup: Setup = mapper.readValue(input) - - override fun read(): List<MachineDef> { + public fun readEnvironment(input: InputStream): List<MachineDef> { + val setup: Setup = mapper.readValue(input) var counter = 0 return setup.rooms.flatMap { room -> room.objects.flatMap { roomObject -> @@ -100,7 +101,7 @@ public class MLEnvironmentReader(input: InputStream, mapper: ObjectMapper = jack UUID(0, counter.toLong()), "node-${counter++}", mapOf("gpu" to isGpuFlag), - SimMachineModel(cores, memories), + MachineModel(cores, memories), LinearPowerModel(maxPower, minPower) ) } @@ -109,6 +110,4 @@ public class MLEnvironmentReader(input: InputStream, mapper: ObjectMapper = jack } } } - - override fun close() {} } diff --git a/opendc-experiments/opendc-experiments-tf20/src/main/kotlin/org/opendc/experiments/tf20/util/MachineDef.kt b/opendc-experiments/opendc-experiments-tf20/src/main/kotlin/org/opendc/experiments/tf20/util/MachineDef.kt new file mode 100644 index 00000000..271f5923 --- /dev/null +++ b/opendc-experiments/opendc-experiments-tf20/src/main/kotlin/org/opendc/experiments/tf20/util/MachineDef.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.experiments.tf20.util + +import org.opendc.simulator.compute.model.MachineModel +import org.opendc.simulator.compute.power.PowerModel +import java.util.* + +/** + * A definition of a machine in a cluster. + */ +public data class MachineDef( + val uid: UUID, + val name: String, + val meta: Map<String, Any>, + val model: MachineModel, + val powerModel: PowerModel +) diff --git a/opendc-faas/build.gradle.kts b/opendc-faas/build.gradle.kts new file mode 100644 index 00000000..2493639f --- /dev/null +++ b/opendc-faas/build.gradle.kts @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +description = "Function-as-a-Service (FaaS) platform for OpenDC" diff --git a/opendc-faas/opendc-faas-api/build.gradle.kts b/opendc-faas/opendc-faas-api/build.gradle.kts new file mode 100644 index 00000000..7362d949 --- /dev/null +++ b/opendc-faas/opendc-faas-api/build.gradle.kts @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +description = "API for the OpenDC FaaS platform" + +/* Build configuration */ +plugins { + `kotlin-library-conventions` +} + +dependencies { + api(platform(projects.opendcPlatform)) +} diff --git a/opendc-faas/opendc-faas-api/src/main/kotlin/org/opendc/faas/api/FaaSClient.kt b/opendc-faas/opendc-faas-api/src/main/kotlin/org/opendc/faas/api/FaaSClient.kt new file mode 100644 index 00000000..ebda4f90 --- /dev/null +++ b/opendc-faas/opendc-faas-api/src/main/kotlin/org/opendc/faas/api/FaaSClient.kt @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.faas.api + +import java.util.* + +/** + * Client interface to the OpenDC FaaS platform. + */ +public interface FaaSClient : AutoCloseable { + /** + * Obtain the list of [FaaSFunction]s accessible by the requesting user. + */ + public suspend fun queryFunctions(): List<FaaSFunction> + + /** + * Obtain a [FaaSFunction] by its unique identifier. + * + * @param id The identifier of the flavor. + */ + public suspend fun findFunction(id: UUID): FaaSFunction? + + /** + * Obtain a [FaaSFunction] by its name. + * + * @param name The name of the function. + */ + public suspend fun findFunction(name: String): FaaSFunction? + + /** + * Create a new serverless function. + * + * @param name The name of the function. + * @param memorySize The memory allocated for the function in MB. + * @param labels The labels associated with the function. + * @param meta The metadata associated with the function. + */ + public suspend fun newFunction( + name: String, + memorySize: Long, + labels: Map<String, String> = emptyMap(), + meta: Map<String, Any> = emptyMap() + ): FaaSFunction + + /** + * Invoke the function with the specified [name]. + */ + public suspend fun invoke(name: String) + + /** + * Release the resources associated with this client, preventing any further API calls. + */ + public override fun close() +} diff --git a/opendc-faas/opendc-faas-api/src/main/kotlin/org/opendc/faas/api/FaaSFunction.kt b/opendc-faas/opendc-faas-api/src/main/kotlin/org/opendc/faas/api/FaaSFunction.kt new file mode 100644 index 00000000..40f0092f --- /dev/null +++ b/opendc-faas/opendc-faas-api/src/main/kotlin/org/opendc/faas/api/FaaSFunction.kt @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.faas.api + +import java.util.UUID + +/** + * A serverless function instance. + */ +public interface FaaSFunction { + /** + * The unique identifier of the function. + */ + public val uid: UUID + + /** + * The name of the function. + */ + public val name: String + + /** + * The amount of memory allocated for this function in MB. + */ + public val memorySize: Long + + /** + * The identifying labels attached to the resource. + */ + public val labels: Map<String, String> + + /** + * The non-identifying metadata attached to the resource. + */ + public val meta: Map<String, Any> + + /** + * Invoke the serverless function. + */ + public suspend operator fun invoke() + + /** + * Request the function to be deleted. + */ + public suspend fun delete() + + /** + * Refresh the local state of this object. + */ + public suspend fun refresh() +} diff --git a/opendc-faas/opendc-faas-service/build.gradle.kts b/opendc-faas/opendc-faas-service/build.gradle.kts new file mode 100644 index 00000000..6f4fcc9b --- /dev/null +++ b/opendc-faas/opendc-faas-service/build.gradle.kts @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +description = "FaaS service for OpenDC" + +/* Build configuration */ +plugins { + `kotlin-library-conventions` + `testing-conventions` + `jacoco-conventions` +} + +dependencies { + api(platform(projects.opendcPlatform)) + api(projects.opendcFaas.opendcFaasApi) + api(projects.opendcTelemetry.opendcTelemetryApi) + implementation(projects.opendcUtils) + implementation(libs.kotlin.logging) + implementation(libs.opentelemetry.semconv) + + testImplementation(projects.opendcSimulator.opendcSimulatorCore) + testRuntimeOnly(libs.log4j.slf4j) +} diff --git a/opendc-faas/opendc-faas-service/src/main/kotlin/org/opendc/faas/service/FaaSService.kt b/opendc-faas/opendc-faas-service/src/main/kotlin/org/opendc/faas/service/FaaSService.kt new file mode 100644 index 00000000..1d5331cb --- /dev/null +++ b/opendc-faas/opendc-faas-service/src/main/kotlin/org/opendc/faas/service/FaaSService.kt @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.faas.service + +import io.opentelemetry.api.metrics.Meter +import io.opentelemetry.api.metrics.MeterProvider +import org.opendc.faas.api.FaaSClient +import org.opendc.faas.service.autoscaler.FunctionTerminationPolicy +import org.opendc.faas.service.deployer.FunctionDeployer +import org.opendc.faas.service.internal.FaaSServiceImpl +import org.opendc.faas.service.router.RoutingPolicy +import java.time.Clock +import kotlin.coroutines.CoroutineContext + +/** + * The [FaaSService] hosts the service implementation of the OpenDC FaaS platform. + */ +public interface FaaSService : AutoCloseable { + /** + * Create a new [FaaSClient] to control the compute service. + */ + public fun newClient(): FaaSClient + + /** + * Terminate the lifecycle of the FaaS service, stopping all running function instances. + */ + public override fun close() + + public companion object { + /** + * Construct a new [FaaSService] implementation. + * + * @param context The [CoroutineContext] to use in the service. + * @param clock The clock instance to use. + * @param meterProvider The [MeterProvider] to create a [Meter] with. + * @param deployer the [FunctionDeployer] to use for deploying function instances. + * @param routingPolicy The policy to route function invocations. + * @param terminationPolicy The policy for terminating function instances. + */ + public operator fun invoke( + context: CoroutineContext, + clock: Clock, + meterProvider: MeterProvider, + deployer: FunctionDeployer, + routingPolicy: RoutingPolicy, + terminationPolicy: FunctionTerminationPolicy, + ): FaaSService { + return FaaSServiceImpl(context, clock, meterProvider, deployer, routingPolicy, terminationPolicy) + } + } +} diff --git a/opendc-faas/opendc-faas-service/src/main/kotlin/org/opendc/faas/service/FunctionObject.kt b/opendc-faas/opendc-faas-service/src/main/kotlin/org/opendc/faas/service/FunctionObject.kt new file mode 100644 index 00000000..54df2b59 --- /dev/null +++ b/opendc-faas/opendc-faas-service/src/main/kotlin/org/opendc/faas/service/FunctionObject.kt @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.faas.service + +import io.opentelemetry.api.common.AttributeKey +import io.opentelemetry.api.common.Attributes +import io.opentelemetry.api.metrics.BoundLongCounter +import io.opentelemetry.api.metrics.BoundLongHistogram +import io.opentelemetry.api.metrics.BoundLongUpDownCounter +import io.opentelemetry.api.metrics.Meter +import io.opentelemetry.semconv.resource.attributes.ResourceAttributes +import org.opendc.faas.service.deployer.FunctionInstance +import java.util.* + +/** + * An [FunctionObject] represents the service's view of a serverless function. + */ +public class FunctionObject( + meter: Meter, + public val uid: UUID, + name: String, + allocatedMemory: Long, + labels: Map<String, String>, + meta: Map<String, Any> +) : AutoCloseable { + /** + * The attributes of this function. + */ + public val attributes: Attributes = Attributes.builder() + .put(ResourceAttributes.FAAS_ID, uid.toString()) + .put(ResourceAttributes.FAAS_NAME, name) + .put(ResourceAttributes.FAAS_MAX_MEMORY, allocatedMemory) + .put(AttributeKey.stringArrayKey("faas.labels"), labels.map { (k, v) -> "$k:$v" }) + .build() + + /** + * The total amount of function invocations received by the function. + */ + public val invocations: BoundLongCounter = meter.counterBuilder("function.invocations.total") + .setDescription("Number of function invocations") + .setUnit("1") + .build() + .bind(attributes) + + /** + * The amount of function invocations that could be handled directly. + */ + public val timelyInvocations: BoundLongCounter = meter.counterBuilder("function.invocations.warm") + .setDescription("Number of function invocations handled directly") + .setUnit("1") + .build() + .bind(attributes) + + /** + * The amount of function invocations that were delayed due to function deployment. + */ + public val delayedInvocations: BoundLongCounter = meter.counterBuilder("function.invocations.cold") + .setDescription("Number of function invocations that are delayed") + .setUnit("1") + .build() + .bind(attributes) + + /** + * The amount of function invocations that failed. + */ + public val failedInvocations: BoundLongCounter = meter.counterBuilder("function.invocations.failed") + .setDescription("Number of function invocations that failed") + .setUnit("1") + .build() + .bind(attributes) + + /** + * The amount of instances for this function. + */ + public val activeInstances: BoundLongUpDownCounter = meter.upDownCounterBuilder("function.instances.active") + .setDescription("Number of active function instances") + .setUnit("1") + .build() + .bind(attributes) + + /** + * The amount of idle instances for this function. + */ + public val idleInstances: BoundLongUpDownCounter = meter.upDownCounterBuilder("function.instances.idle") + .setDescription("Number of idle function instances") + .setUnit("1") + .build() + .bind(attributes) + + /** + * The time that the function waited. + */ + public val waitTime: BoundLongHistogram = meter.histogramBuilder("function.time.wait") + .ofLongs() + .setDescription("Time the function has to wait before being started") + .setUnit("ms") + .build() + .bind(attributes) + + /** + * The time that the function was running. + */ + public val activeTime: BoundLongHistogram = meter.histogramBuilder("function.time.active") + .ofLongs() + .setDescription("Time the function was running") + .setUnit("ms") + .build() + .bind(attributes) + + /** + * The instances associated with this function. + */ + public val instances: MutableList<FunctionInstance> = mutableListOf() + + public var name: String = name + private set + + public var memorySize: Long = allocatedMemory + private set + + public val labels: MutableMap<String, String> = labels.toMutableMap() + + public val meta: MutableMap<String, Any> = meta.toMutableMap() + + override fun close() { + instances.forEach(FunctionInstance::close) + instances.clear() + } + + override fun equals(other: Any?): Boolean = other is FunctionObject && uid == other.uid + + override fun hashCode(): Int = uid.hashCode() +} diff --git a/opendc-faas/opendc-faas-service/src/main/kotlin/org/opendc/faas/service/autoscaler/FunctionTerminationPolicy.kt b/opendc-faas/opendc-faas-service/src/main/kotlin/org/opendc/faas/service/autoscaler/FunctionTerminationPolicy.kt new file mode 100644 index 00000000..2ab3638b --- /dev/null +++ b/opendc-faas/opendc-faas-service/src/main/kotlin/org/opendc/faas/service/autoscaler/FunctionTerminationPolicy.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.faas.service.autoscaler + +import org.opendc.faas.service.deployer.FunctionInstance +import org.opendc.faas.service.deployer.FunctionInstanceListener + +/** + * A management policy that is responsible for downscaling the active function instances for a function. + */ +public interface FunctionTerminationPolicy : FunctionInstanceListener { + /** + * Enqueue the specified [instance] to be scheduled for termination a + */ + public fun enqueue(instance: FunctionInstance) +} diff --git a/opendc-faas/opendc-faas-service/src/main/kotlin/org/opendc/faas/service/autoscaler/FunctionTerminationPolicyFixed.kt b/opendc-faas/opendc-faas-service/src/main/kotlin/org/opendc/faas/service/autoscaler/FunctionTerminationPolicyFixed.kt new file mode 100644 index 00000000..63dbadc7 --- /dev/null +++ b/opendc-faas/opendc-faas-service/src/main/kotlin/org/opendc/faas/service/autoscaler/FunctionTerminationPolicyFixed.kt @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.faas.service.autoscaler + +import org.opendc.faas.service.deployer.FunctionInstance +import org.opendc.faas.service.deployer.FunctionInstanceState +import org.opendc.utils.TimerScheduler +import java.time.Clock +import java.time.Duration +import kotlin.coroutines.CoroutineContext + +/** + * A [FunctionTerminationPolicy] that terminates idle function instances after a fixed keep-alive time. + * + * @param timeout The idle timeout after which the function instance is terminated. + */ +public class FunctionTerminationPolicyFixed( + context: CoroutineContext, + clock: Clock, + public val timeout: Duration +) : FunctionTerminationPolicy { + /** + * The [TimerScheduler] used to schedule the function terminations. + */ + private val scheduler = TimerScheduler<FunctionInstance>(context, clock) + + override fun enqueue(instance: FunctionInstance) { + // Cancel the existing timeout timer + scheduler.cancel(instance) + } + + override fun onStateChanged(instance: FunctionInstance, newState: FunctionInstanceState) { + when (newState) { + FunctionInstanceState.Active -> scheduler.cancel(instance) + FunctionInstanceState.Idle -> schedule(instance) + else -> {} + } + } + + /** + * Schedule termination for the specified [instance]. + */ + private fun schedule(instance: FunctionInstance) { + scheduler.startSingleTimer(instance, delay = timeout.toMillis()) { instance.close() } + } +} diff --git a/opendc-faas/opendc-faas-service/src/main/kotlin/org/opendc/faas/service/autoscaler/FunctionTerminationPolicyNull.kt b/opendc-faas/opendc-faas-service/src/main/kotlin/org/opendc/faas/service/autoscaler/FunctionTerminationPolicyNull.kt new file mode 100644 index 00000000..957e569b --- /dev/null +++ b/opendc-faas/opendc-faas-service/src/main/kotlin/org/opendc/faas/service/autoscaler/FunctionTerminationPolicyNull.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.faas.service.autoscaler + +import org.opendc.faas.service.deployer.FunctionInstance + +/** + * A [FunctionTerminationPolicy] that never terminates function instances. + */ +public class FunctionTerminationPolicyNull : FunctionTerminationPolicy { + override fun enqueue(instance: FunctionInstance) { + // No-op + } +} diff --git a/opendc-faas/opendc-faas-service/src/main/kotlin/org/opendc/faas/service/deployer/FunctionDeployer.kt b/opendc-faas/opendc-faas-service/src/main/kotlin/org/opendc/faas/service/deployer/FunctionDeployer.kt new file mode 100644 index 00000000..049f1cc7 --- /dev/null +++ b/opendc-faas/opendc-faas-service/src/main/kotlin/org/opendc/faas/service/deployer/FunctionDeployer.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.faas.service.deployer + +import org.opendc.faas.service.FunctionObject + +/** + * A [FunctionDeployer] is responsible for ensuring that an instance of an arbitrary function, a [FunctionInstance], + * is deployed. + * + * The function deployer should combines the configuration stored in the function registry, the parameters supplied by + * the requester, and other factors into a decision of how the function should be deployed, including how many and + * what kind of resources it should receive. + * + * Though it decides how the function instance should be deployed, the deployment of the function instance itself is + * delegated to the Resource Orchestration Layer. + */ +public interface FunctionDeployer { + /** + * Deploy the specified [function]. + */ + public fun deploy(function: FunctionObject, listener: FunctionInstanceListener): FunctionInstance +} diff --git a/opendc-faas/opendc-faas-service/src/main/kotlin/org/opendc/faas/service/deployer/FunctionInstance.kt b/opendc-faas/opendc-faas-service/src/main/kotlin/org/opendc/faas/service/deployer/FunctionInstance.kt new file mode 100644 index 00000000..a8b04df4 --- /dev/null +++ b/opendc-faas/opendc-faas-service/src/main/kotlin/org/opendc/faas/service/deployer/FunctionInstance.kt @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.faas.service.deployer + +import org.opendc.faas.service.FunctionObject + +/** + * A [FunctionInstance] is a a self-contained worker—typically a container—capable of handling function executions. + * + * Multiple, concurrent function instances can exists for a single function, for scalability purposes. + */ +public interface FunctionInstance : AutoCloseable { + /** + * The state of the instance. + */ + public val state: FunctionInstanceState + + /** + * The [FunctionObject] that is represented by this instance. + */ + public val function: FunctionObject + + /** + * Invoke the function instance. + * + * This method will suspend execution util the function instance has returned. + */ + public suspend fun invoke() + + /** + * Indicate to the resource manager that the instance is not needed anymore and may be cleaned up by the resource + * manager. + */ + public override fun close() +} diff --git a/opendc-faas/opendc-faas-service/src/main/kotlin/org/opendc/faas/service/deployer/FunctionInstanceListener.kt b/opendc-faas/opendc-faas-service/src/main/kotlin/org/opendc/faas/service/deployer/FunctionInstanceListener.kt new file mode 100644 index 00000000..20e280a2 --- /dev/null +++ b/opendc-faas/opendc-faas-service/src/main/kotlin/org/opendc/faas/service/deployer/FunctionInstanceListener.kt @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.faas.service.deployer + +/** + * Listener interface for events originating from a [FunctionInstance]. + */ +public interface FunctionInstanceListener { + /** + * This method is invoked when the state of a [FunctionInstance] has changed. + */ + public fun onStateChanged(instance: FunctionInstance, newState: FunctionInstanceState) {} +} diff --git a/opendc-faas/opendc-faas-service/src/main/kotlin/org/opendc/faas/service/deployer/FunctionInstanceState.kt b/opendc-faas/opendc-faas-service/src/main/kotlin/org/opendc/faas/service/deployer/FunctionInstanceState.kt new file mode 100644 index 00000000..2b6b6eba --- /dev/null +++ b/opendc-faas/opendc-faas-service/src/main/kotlin/org/opendc/faas/service/deployer/FunctionInstanceState.kt @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.faas.service.deployer + +/** + * This enumeration describes the states of a [FunctionInstance]. + */ +public enum class FunctionInstanceState { + /** + * The function instance is currently being provisioned. + */ + Provisioning, + + /** + * The function instance is idle and ready to execute. + */ + Idle, + + /** + * The function instance is executing. + */ + Active, + + /** + * The function instance is released and cannot be used anymore. + */ + Deleted +} diff --git a/opendc-faas/opendc-faas-service/src/main/kotlin/org/opendc/faas/service/internal/FaaSFunctionImpl.kt b/opendc-faas/opendc-faas-service/src/main/kotlin/org/opendc/faas/service/internal/FaaSFunctionImpl.kt new file mode 100644 index 00000000..bd7f13f6 --- /dev/null +++ b/opendc-faas/opendc-faas-service/src/main/kotlin/org/opendc/faas/service/internal/FaaSFunctionImpl.kt @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.faas.service.internal + +import org.opendc.faas.api.FaaSFunction +import org.opendc.faas.service.FunctionObject +import java.util.* + +/** + * A [FaaSFunction] implementation that is passed to clients. + */ +internal class FaaSFunctionImpl( + private val service: FaaSServiceImpl, + private val state: FunctionObject +) : FaaSFunction { + override val uid: UUID = state.uid + + override var name: String = state.name + private set + + override var memorySize: Long = state.memorySize + private set + + override var labels: Map<String, String> = state.labels.toMap() + private set + + override var meta: Map<String, Any> = state.meta.toMap() + private set + + override suspend fun delete() { + service.delete(state) + } + + override suspend fun invoke() { + service.invoke(state) + } + + override suspend fun refresh() { + name = state.name + memorySize = state.memorySize + labels = state.labels + meta = state.meta + } + + override fun equals(other: Any?): Boolean = other is FaaSFunctionImpl && uid == other.uid + + override fun hashCode(): Int = uid.hashCode() + + override fun toString(): String = "FaaSFunction[uid=$uid,name=$name]" +} diff --git a/opendc-faas/opendc-faas-service/src/main/kotlin/org/opendc/faas/service/internal/FaaSServiceImpl.kt b/opendc-faas/opendc-faas-service/src/main/kotlin/org/opendc/faas/service/internal/FaaSServiceImpl.kt new file mode 100644 index 00000000..3b560cd3 --- /dev/null +++ b/opendc-faas/opendc-faas-service/src/main/kotlin/org/opendc/faas/service/internal/FaaSServiceImpl.kt @@ -0,0 +1,310 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.faas.service.internal + +import io.opentelemetry.api.metrics.Meter +import io.opentelemetry.api.metrics.MeterProvider +import kotlinx.coroutines.* +import kotlinx.coroutines.intrinsics.startCoroutineCancellable +import mu.KotlinLogging +import org.opendc.faas.api.FaaSClient +import org.opendc.faas.api.FaaSFunction +import org.opendc.faas.service.FaaSService +import org.opendc.faas.service.FunctionObject +import org.opendc.faas.service.autoscaler.FunctionTerminationPolicy +import org.opendc.faas.service.deployer.FunctionDeployer +import org.opendc.faas.service.deployer.FunctionInstance +import org.opendc.faas.service.deployer.FunctionInstanceListener +import org.opendc.faas.service.deployer.FunctionInstanceState +import org.opendc.faas.service.router.RoutingPolicy +import org.opendc.utils.TimerScheduler +import java.lang.IllegalStateException +import java.time.Clock +import java.util.* +import kotlin.coroutines.Continuation +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.resumeWithException + +/** + * Implementation of the [FaaSService] interface. + * + * This component acts as the function router from the SPEC RG Reference Architecture for FaaS and is responsible + * for routing incoming requests or events to the correct [FunctionInstance]. If no [FunctionInstance] is available, + * this component queues the events to await the deployment of new instances. + */ +internal class FaaSServiceImpl( + context: CoroutineContext, + private val clock: Clock, + private val meterProvider: MeterProvider, + private val deployer: FunctionDeployer, + private val routingPolicy: RoutingPolicy, + private val terminationPolicy: FunctionTerminationPolicy +) : FaaSService, FunctionInstanceListener { + /** + * The [CoroutineScope] of the service bounded by the lifecycle of the service. + */ + private val scope = CoroutineScope(context + Job()) + + /** + * The logger instance of this server. + */ + private val logger = KotlinLogging.logger {} + + /** + * The [Meter] that collects the metrics of this service. + */ + private val meter = meterProvider.get("org.opendc.faas.service") + + /** + * The [TimerScheduler] to use for scheduling the scheduler cycles. + */ + private val scheduler: TimerScheduler<Unit> = TimerScheduler(scope.coroutineContext, clock) + + /** + * The [Random] instance used to generate unique identifiers for the objects. + */ + private val random = Random(0) + + /** + * The registered functions for this service. + */ + private val functions = mutableMapOf<UUID, FunctionObject>() + private val functionsByName = mutableMapOf<String, FunctionObject>() + + /** + * The queue of invocation requests. + */ + private val queue = ArrayDeque<InvocationRequest>() + + /** + * The total amount of function invocations received by the service. + */ + private val _invocations = meter.counterBuilder("service.invocations.total") + .setDescription("Number of function invocations") + .setUnit("1") + .build() + + /** + * The amount of function invocations that could be handled directly. + */ + private val _timelyInvocations = meter.counterBuilder("service.invocations.warm") + .setDescription("Number of function invocations handled directly") + .setUnit("1") + .build() + + /** + * The amount of function invocations that were delayed due to function deployment. + */ + private val _delayedInvocations = meter.counterBuilder("service.invocations.cold") + .setDescription("Number of function invocations that are delayed") + .setUnit("1") + .build() + + override fun newClient(): FaaSClient { + return object : FaaSClient { + private var isClosed: Boolean = false + + /** + * Exposes a [FunctionObject] to a client-exposed [FaaSFunction] instance. + */ + private fun FunctionObject.asClientFunction(): FaaSFunction { + return FaaSFunctionImpl(this@FaaSServiceImpl, this) + } + + override suspend fun queryFunctions(): List<FaaSFunction> { + check(!isClosed) { "Client is already closed" } + + return functions.values.map { it.asClientFunction() } + } + + override suspend fun findFunction(id: UUID): FaaSFunction? { + check(!isClosed) { "Client is already closed" } + + return functions[id]?.asClientFunction() + } + + override suspend fun findFunction(name: String): FaaSFunction? { + check(!isClosed) { "Client is already closed" } + + return functionsByName[name]?.asClientFunction() + } + + override suspend fun newFunction( + name: String, + memorySize: Long, + labels: Map<String, String>, + meta: Map<String, Any> + ): FaaSFunction { + check(!isClosed) { "Client is already closed" } + require(name !in functionsByName) { "Function with same name exists" } + + val uid = UUID(clock.millis(), random.nextLong()) + val function = FunctionObject( + meter, + uid, + name, + memorySize, + labels, + meta + ) + + functionsByName[name] = function + functions[uid] = function + + return function.asClientFunction() + } + + override suspend fun invoke(name: String) { + check(!isClosed) { "Client is already closed" } + + val func = requireNotNull(functionsByName[name]) { "Unknown function" } + this@FaaSServiceImpl.invoke(func) + } + + override fun close() { + isClosed = true + } + } + } + + /** + * Indicate that a new scheduling cycle is needed due to a change to the service's state. + */ + private fun schedule() { + // Bail out in case we have already requested a new cycle or the queue is empty. + if (scheduler.isTimerActive(Unit) || queue.isEmpty()) { + return + } + + val quantum = 100 + + // We assume that the provisioner runs at a fixed slot every time quantum (e.g t=0, t=60, t=120). + // This is important because the slices of the VMs need to be aligned. + // We calculate here the delay until the next scheduling slot. + val delay = quantum - (clock.millis() % quantum) + + scheduler.startSingleTimer(Unit, delay, ::doSchedule) + } + + /** + * Run a single scheduling iteration. + */ + @OptIn(InternalCoroutinesApi::class) + private fun doSchedule() { + try { + while (queue.isNotEmpty()) { + val (submitTime, function, cont) = queue.poll() + + val instances = function.instances + + // Check if there exists an instance of the function + val activeInstance = if (instances.isNotEmpty()) { + routingPolicy.select(instances, function) + } else { + null + } + + val instance = if (activeInstance != null) { + _timelyInvocations.add(1) + function.timelyInvocations.add(1) + + activeInstance + } else { + val instance = deployer.deploy(function, this) + instances.add(instance) + terminationPolicy.enqueue(instance) + + function.idleInstances.add(1) + + _delayedInvocations.add(1) + function.delayedInvocations.add(1) + + instance + } + + suspend { + val start = clock.millis() + function.waitTime.record(start - submitTime) + function.idleInstances.add(-1) + function.activeInstances.add(1) + try { + instance.invoke() + } catch (e: Throwable) { + logger.debug(e) { "Function invocation failed" } + function.failedInvocations.add(1) + } finally { + val end = clock.millis() + function.activeTime.record(end - start) + function.idleInstances.add(1) + function.activeInstances.add(-1) + } + }.startCoroutineCancellable(cont) + } + } catch (cause: Throwable) { + logger.error(cause) { "Exception occurred during scheduling cycle" } + } + } + + suspend fun invoke(function: FunctionObject) { + check(function.uid in functions) { "Function does not exist (anymore)" } + + _invocations.add(1) + function.invocations.add(1) + + return suspendCancellableCoroutine { cont -> + if (!queue.add(InvocationRequest(clock.millis(), function, cont))) { + cont.resumeWithException(IllegalStateException("Failed to enqueue request")) + } else { + schedule() + } + } + } + + fun delete(function: FunctionObject) { + functions.remove(function.uid) + functionsByName.remove(function.name) + } + + override fun close() { + scope.cancel() + + // Stop all function instances + for ((_, function) in functions) { + function.close() + } + } + + override fun onStateChanged(instance: FunctionInstance, newState: FunctionInstanceState) { + terminationPolicy.onStateChanged(instance, newState) + + if (newState == FunctionInstanceState.Deleted) { + val function = instance.function + function.instances.remove(instance) + } + } + + /** + * A request to invoke a function. + */ + private data class InvocationRequest(val timestamp: Long, val function: FunctionObject, val cont: Continuation<Unit>) +} diff --git a/opendc-faas/opendc-faas-service/src/main/kotlin/org/opendc/faas/service/router/RandomRoutingPolicy.kt b/opendc-faas/opendc-faas-service/src/main/kotlin/org/opendc/faas/service/router/RandomRoutingPolicy.kt new file mode 100644 index 00000000..5bd9d4d3 --- /dev/null +++ b/opendc-faas/opendc-faas-service/src/main/kotlin/org/opendc/faas/service/router/RandomRoutingPolicy.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.faas.service.router + +import org.opendc.faas.service.FunctionObject +import org.opendc.faas.service.deployer.FunctionInstance +import kotlin.random.Random + +/** + * A [RoutingPolicy] that selects a random function instance. + */ +public class RandomRoutingPolicy(private val random: Random = Random(0)) : RoutingPolicy { + override fun select(instances: List<FunctionInstance>, function: FunctionObject): FunctionInstance { + return instances.random(random) + } +} diff --git a/opendc-faas/opendc-faas-service/src/main/kotlin/org/opendc/faas/service/router/RoutingPolicy.kt b/opendc-faas/opendc-faas-service/src/main/kotlin/org/opendc/faas/service/router/RoutingPolicy.kt new file mode 100644 index 00000000..e99e329a --- /dev/null +++ b/opendc-faas/opendc-faas-service/src/main/kotlin/org/opendc/faas/service/router/RoutingPolicy.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.faas.service.router + +import org.opendc.faas.service.FunctionObject +import org.opendc.faas.service.deployer.FunctionInstance + +/** + * A [RoutingPolicy] decides to which [FunctionInstance] a function invocation should be routed. + */ +public interface RoutingPolicy { + /** + * Select the instance to which the request should be routed to. + */ + public fun select(instances: List<FunctionInstance>, function: FunctionObject): FunctionInstance? +} diff --git a/opendc-faas/opendc-faas-service/src/test/kotlin/org/opendc/faas/service/FaaSServiceTest.kt b/opendc-faas/opendc-faas-service/src/test/kotlin/org/opendc/faas/service/FaaSServiceTest.kt new file mode 100644 index 00000000..1612e10b --- /dev/null +++ b/opendc-faas/opendc-faas-service/src/test/kotlin/org/opendc/faas/service/FaaSServiceTest.kt @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.faas.service + +import io.mockk.* +import io.opentelemetry.api.metrics.MeterProvider +import kotlinx.coroutines.ExperimentalCoroutinesApi +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertDoesNotThrow +import org.junit.jupiter.api.assertThrows +import org.opendc.faas.api.FaaSFunction +import org.opendc.faas.service.deployer.FunctionDeployer +import org.opendc.faas.service.deployer.FunctionInstance +import org.opendc.faas.service.deployer.FunctionInstanceState +import org.opendc.simulator.core.runBlockingSimulation +import java.util.* + +/** + * Test suite for the [FaaSService] implementation. + */ +@OptIn(ExperimentalCoroutinesApi::class) +internal class FaaSServiceTest { + + @Test + fun testClientState() = runBlockingSimulation { + val service = FaaSService(coroutineContext, clock, MeterProvider.noop(), mockk(), mockk(), mockk()) + + val client = assertDoesNotThrow { service.newClient() } + assertDoesNotThrow { client.close() } + + assertThrows<IllegalStateException> { client.queryFunctions() } + assertThrows<IllegalStateException> { client.newFunction("test", 128) } + assertThrows<IllegalStateException> { client.invoke("test") } + assertThrows<IllegalStateException> { client.findFunction(UUID.randomUUID()) } + assertThrows<IllegalStateException> { client.findFunction("name") } + } + + @Test + fun testClientInvokeUnknown() = runBlockingSimulation { + val service = FaaSService(coroutineContext, clock, MeterProvider.noop(), mockk(), mockk(), mockk()) + + val client = service.newClient() + + assertThrows<IllegalArgumentException> { client.invoke("test") } + } + + @Test + fun testClientFunctionCreation() = runBlockingSimulation { + val service = FaaSService(coroutineContext, clock, MeterProvider.noop(), mockk(), mockk(), mockk()) + + val client = service.newClient() + + val function = client.newFunction("test", 128) + + assertEquals("test", function.name) + } + + @Test + fun testClientFunctionQuery() = runBlockingSimulation { + val service = FaaSService(coroutineContext, clock, MeterProvider.noop(), mockk(), mockk(), mockk()) + + val client = service.newClient() + + assertEquals(emptyList<FaaSFunction>(), client.queryFunctions()) + + val function = client.newFunction("test", 128) + + assertEquals(listOf(function), client.queryFunctions()) + } + + @Test + fun testClientFunctionFindById() = runBlockingSimulation { + val service = FaaSService(coroutineContext, clock, MeterProvider.noop(), mockk(), mockk(), mockk()) + + val client = service.newClient() + + assertEquals(emptyList<FaaSFunction>(), client.queryFunctions()) + + val function = client.newFunction("test", 128) + + assertNotNull(client.findFunction(function.uid)) + } + + @Test + fun testClientFunctionFindByName() = runBlockingSimulation { + val service = FaaSService(coroutineContext, clock, MeterProvider.noop(), mockk(), mockk(), mockk()) + + val client = service.newClient() + + assertEquals(emptyList<FaaSFunction>(), client.queryFunctions()) + + val function = client.newFunction("test", 128) + + assertNotNull(client.findFunction(function.name)) + } + + @Test + fun testClientFunctionDuplicateName() = runBlockingSimulation { + val service = FaaSService(coroutineContext, clock, MeterProvider.noop(), mockk(), mockk(), mockk()) + + val client = service.newClient() + + client.newFunction("test", 128) + + assertThrows<IllegalArgumentException> { client.newFunction("test", 128) } + } + + @Test + fun testClientFunctionDelete() = runBlockingSimulation { + val service = FaaSService(coroutineContext, clock, MeterProvider.noop(), mockk(), mockk(), mockk()) + + val client = service.newClient() + val function = client.newFunction("test", 128) + assertNotNull(client.findFunction(function.uid)) + function.delete() + assertNull(client.findFunction(function.uid)) + + // Delete should be idempotent + function.delete() + } + + @Test + fun testClientFunctionCannotInvokeDeleted() = runBlockingSimulation { + val service = FaaSService(coroutineContext, clock, MeterProvider.noop(), mockk(), mockk(), mockk()) + + val client = service.newClient() + val function = client.newFunction("test", 128) + assertNotNull(client.findFunction(function.uid)) + function.delete() + + assertThrows<IllegalStateException> { function.invoke() } + } + + @Test + fun testClientFunctionInvoke() = runBlockingSimulation { + val deployer = mockk<FunctionDeployer>() + val service = FaaSService(coroutineContext, clock, MeterProvider.noop(), deployer, mockk(), mockk(relaxUnitFun = true)) + + every { deployer.deploy(any(), any()) } answers { + object : FunctionInstance { + override val state: FunctionInstanceState = FunctionInstanceState.Idle + override val function: FunctionObject = it.invocation.args[0] as FunctionObject + + override suspend fun invoke() {} + + override fun close() {} + } + } + + val client = service.newClient() + val function = client.newFunction("test", 128) + + function.invoke() + } +} diff --git a/opendc-faas/opendc-faas-simulator/build.gradle.kts b/opendc-faas/opendc-faas-simulator/build.gradle.kts new file mode 100644 index 00000000..fed1862d --- /dev/null +++ b/opendc-faas/opendc-faas-simulator/build.gradle.kts @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +description = "Simulator for the OpenDC FaaS platform" + +/* Build configuration */ +plugins { + `kotlin-library-conventions` + `testing-conventions` + `jacoco-conventions` +} + +dependencies { + api(platform(projects.opendcPlatform)) + api(projects.opendcFaas.opendcFaasService) + api(projects.opendcSimulator.opendcSimulatorCompute) + + testImplementation(projects.opendcSimulator.opendcSimulatorCore) + testRuntimeOnly(libs.slf4j.simple) +} diff --git a/opendc-faas/opendc-faas-simulator/src/main/kotlin/org/opendc/faas/simulator/SimFunctionDeployer.kt b/opendc-faas/opendc-faas-simulator/src/main/kotlin/org/opendc/faas/simulator/SimFunctionDeployer.kt new file mode 100644 index 00000000..020d75b5 --- /dev/null +++ b/opendc-faas/opendc-faas-simulator/src/main/kotlin/org/opendc/faas/simulator/SimFunctionDeployer.kt @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.faas.simulator + +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.Channel +import org.opendc.faas.service.FunctionObject +import org.opendc.faas.service.deployer.FunctionDeployer +import org.opendc.faas.service.deployer.FunctionInstance +import org.opendc.faas.service.deployer.FunctionInstanceListener +import org.opendc.faas.service.deployer.FunctionInstanceState +import org.opendc.faas.simulator.delay.DelayInjector +import org.opendc.faas.simulator.workload.SimFaaSWorkloadMapper +import org.opendc.simulator.compute.SimBareMetalMachine +import org.opendc.simulator.compute.SimMachine +import org.opendc.simulator.compute.model.MachineModel +import org.opendc.simulator.compute.power.ConstantPowerModel +import org.opendc.simulator.compute.power.SimplePowerDriver +import org.opendc.simulator.flow.FlowEngine +import java.time.Clock +import java.util.ArrayDeque +import kotlin.coroutines.Continuation +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException + +/** + * A [FunctionDeployer] that uses that simulates the [FunctionInstance]s. + */ +public class SimFunctionDeployer( + private val clock: Clock, + private val scope: CoroutineScope, + private val model: MachineModel, + private val delayInjector: DelayInjector, + private val mapper: SimFaaSWorkloadMapper +) : FunctionDeployer { + + override fun deploy(function: FunctionObject, listener: FunctionInstanceListener): Instance { + val instance = Instance(function, listener) + instance.start() + return instance + } + + /** + * A simulated [FunctionInstance]. + */ + public inner class Instance(override val function: FunctionObject, private val listener: FunctionInstanceListener) : + FunctionInstance { + /** + * The workload associated with this instance. + */ + private val workload = mapper.createWorkload(function) + + /** + * The machine that will execute the workloads. + */ + public val machine: SimMachine = SimBareMetalMachine( + FlowEngine(scope.coroutineContext, clock), + model, + SimplePowerDriver(ConstantPowerModel(0.0)) + ) + + /** + * The job associated with the lifecycle of the instance. + */ + private var job: Job? = null + + /** + * The invocation request queue. + */ + private val queue = ArrayDeque<InvocationRequest>() + + /** + * A channel used to signal that new invocations have been enqueued. + */ + private val chan = Channel<Unit>(Channel.RENDEZVOUS) + + override var state: FunctionInstanceState = FunctionInstanceState.Provisioning + set(value) { + if (field != value) { + listener.onStateChanged(this, value) + } + + field = value + } + + override suspend fun invoke() { + check(state != FunctionInstanceState.Deleted) { "Function instance has been released" } + return suspendCancellableCoroutine { cont -> + queue.add(InvocationRequest(cont)) + chan.trySend(Unit) + } + } + + override fun close() { + state = FunctionInstanceState.Deleted + stop() + machine.close() + } + + override fun toString(): String = "FunctionInstance[state=$state]" + + /** + * Start the function instance. + */ + @OptIn(InternalCoroutinesApi::class) + internal fun start() { + check(state == FunctionInstanceState.Provisioning) { "Invalid state of function instance" } + job = scope.launch { + delay(delayInjector.getColdStartDelay(this@Instance)) + + launch { + try { + machine.run(workload) + } finally { + state = FunctionInstanceState.Deleted + } + } + + while (isActive) { + if (queue.isEmpty()) { + chan.receive() + } + + state = FunctionInstanceState.Active + while (queue.isNotEmpty()) { + val request = queue.poll() + try { + workload.invoke() + request.cont.resume(Unit) + } catch (cause: CancellationException) { + request.cont.resumeWithException(cause) + throw cause + } catch (cause: Throwable) { + request.cont.resumeWithException(cause) + } + } + state = FunctionInstanceState.Idle + } + } + } + + /** + * Stop the function instance. + */ + private fun stop() { + val job = job + + if (job != null) { + this.job = null + job.cancel() + } + } + } + + /** + * A function invocation request. + */ + private data class InvocationRequest(val cont: Continuation<Unit>) +} diff --git a/opendc-faas/opendc-faas-simulator/src/main/kotlin/org/opendc/faas/simulator/delay/ColdStartModel.kt b/opendc-faas/opendc-faas-simulator/src/main/kotlin/org/opendc/faas/simulator/delay/ColdStartModel.kt new file mode 100644 index 00000000..624067be --- /dev/null +++ b/opendc-faas/opendc-faas-simulator/src/main/kotlin/org/opendc/faas/simulator/delay/ColdStartModel.kt @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.faas.simulator.delay + +/** + * Model parameters for the cold start times of serverless services. + */ +public enum class ColdStartModel { + // Min and max memory values from [Peeking Behind The Curtains of Serverless Platforms][2018], + // other values deduced from linear curve. + LAMBDA { + override fun coldStartParam(provisionedMemory: Int): Pair<Double, Double> { + return when (provisionedMemory) { + 128 -> Pair(265.21, 354.43) + 256 -> Pair(261.46, 334.23) + 512 -> Pair(257.71, 314.03) + 1024 -> Pair(253.96, 293.83) + 1536 -> Pair(250.07, 273.63) + 2048 -> Pair(246.11, 253.43) + else -> Pair(0.0, 1.0) + } + } + }, + AZURE { + // Azure by default uses 1.5gb memory to instantiate functions + override fun coldStartParam(provisionedMemory: Int): Pair<Double, Double> { + return Pair(242.66, 340.67) + } + }, + + GOOGLE { + override fun coldStartParam(provisionedMemory: Int): Pair<Double, Double> { + return when (provisionedMemory) { + 128 -> Pair(493.04, 345.8) + 256 -> Pair(416.59, 301.5) + 512 -> Pair(340.14, 257.2) + 1024 -> Pair(263.69, 212.9) + 1536 -> Pair(187.24, 168.6) + 2048 -> Pair(110.77, 124.3) + else -> Pair(0.0, 1.0) + } + } + }; + + /** + * Obtain the stochastic parameters for the cold start models. + */ + public abstract fun coldStartParam(provisionedMemory: Int): Pair<Double, Double> +} diff --git a/opendc-faas/opendc-faas-simulator/src/main/kotlin/org/opendc/faas/simulator/delay/DelayInjector.kt b/opendc-faas/opendc-faas-simulator/src/main/kotlin/org/opendc/faas/simulator/delay/DelayInjector.kt new file mode 100644 index 00000000..c1df682c --- /dev/null +++ b/opendc-faas/opendc-faas-simulator/src/main/kotlin/org/opendc/faas/simulator/delay/DelayInjector.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.faas.simulator.delay + +import org.opendc.faas.service.deployer.FunctionInstance + +/** + * An interface for modeling the delay caused by function cold starts. + */ +public interface DelayInjector { + /** + * Returns the cold start delay duration sampled from a normal distribution, the distribution is + * initialized using custom mean and standard deviation based on provisioned memory, language and + * failure model + */ + public fun getColdStartDelay(instance: FunctionInstance): Long +} diff --git a/opendc-faas/opendc-faas-simulator/src/main/kotlin/org/opendc/faas/simulator/delay/StochasticDelayInjector.kt b/opendc-faas/opendc-faas-simulator/src/main/kotlin/org/opendc/faas/simulator/delay/StochasticDelayInjector.kt new file mode 100644 index 00000000..9442e2d3 --- /dev/null +++ b/opendc-faas/opendc-faas-simulator/src/main/kotlin/org/opendc/faas/simulator/delay/StochasticDelayInjector.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.faas.simulator.delay + +import org.opendc.faas.service.deployer.FunctionInstance +import java.util.* +import kotlin.math.abs + +/* + * Interface for instance deployment delay estimation. + */ +public class StochasticDelayInjector(private val model: ColdStartModel, private val random: Random) : DelayInjector { + override fun getColdStartDelay(instance: FunctionInstance): Long { + val (mean, sd) = model.coldStartParam(instance.function.memorySize.toInt()) + return abs(random.nextGaussian() * sd + mean).toLong() + } +} diff --git a/opendc-faas/opendc-faas-simulator/src/main/kotlin/org/opendc/faas/simulator/delay/ZeroDelayInjector.kt b/opendc-faas/opendc-faas-simulator/src/main/kotlin/org/opendc/faas/simulator/delay/ZeroDelayInjector.kt new file mode 100644 index 00000000..0e318764 --- /dev/null +++ b/opendc-faas/opendc-faas-simulator/src/main/kotlin/org/opendc/faas/simulator/delay/ZeroDelayInjector.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.faas.simulator.delay + +import org.opendc.faas.service.deployer.FunctionInstance + +public object ZeroDelayInjector : DelayInjector { + override fun getColdStartDelay(instance: FunctionInstance): Long = 0 +} diff --git a/opendc-faas/opendc-faas-simulator/src/main/kotlin/org/opendc/faas/simulator/workload/SimFaaSWorkload.kt b/opendc-faas/opendc-faas-simulator/src/main/kotlin/org/opendc/faas/simulator/workload/SimFaaSWorkload.kt new file mode 100644 index 00000000..aaee26c0 --- /dev/null +++ b/opendc-faas/opendc-faas-simulator/src/main/kotlin/org/opendc/faas/simulator/workload/SimFaaSWorkload.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.faas.simulator.workload + +import org.opendc.simulator.compute.workload.SimWorkload + +/** + * A model for a serverless function workload, which may be invoked multiple times. + */ +public interface SimFaaSWorkload : SimWorkload { + /** + * This method is invoked when an active function instance is invoked. + */ + public suspend fun invoke() +} diff --git a/opendc-faas/opendc-faas-simulator/src/main/kotlin/org/opendc/faas/simulator/workload/SimFaaSWorkloadMapper.kt b/opendc-faas/opendc-faas-simulator/src/main/kotlin/org/opendc/faas/simulator/workload/SimFaaSWorkloadMapper.kt new file mode 100644 index 00000000..b22dd659 --- /dev/null +++ b/opendc-faas/opendc-faas-simulator/src/main/kotlin/org/opendc/faas/simulator/workload/SimFaaSWorkloadMapper.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.faas.simulator.workload + +import org.opendc.faas.api.FaaSFunction +import org.opendc.faas.service.FunctionObject + +/** + * A [SimFaaSWorkloadMapper] is responsible for mapping a [FaaSFunction] to a [SimFaaSWorkload] that + * can be simulated. + */ +public fun interface SimFaaSWorkloadMapper { + /** + * Map the specified [function] to a [SimFaaSWorkload] that can be simulated. + */ + public fun createWorkload(function: FunctionObject): SimFaaSWorkload +} diff --git a/opendc-faas/opendc-faas-simulator/src/test/kotlin/org/opendc/faas/simulator/SimFaaSServiceTest.kt b/opendc-faas/opendc-faas-simulator/src/test/kotlin/org/opendc/faas/simulator/SimFaaSServiceTest.kt new file mode 100644 index 00000000..0dc9ba87 --- /dev/null +++ b/opendc-faas/opendc-faas-simulator/src/test/kotlin/org/opendc/faas/simulator/SimFaaSServiceTest.kt @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.faas.simulator + +import io.mockk.coVerify +import io.mockk.spyk +import io.opentelemetry.api.metrics.MeterProvider +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.delay +import kotlinx.coroutines.yield +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertAll +import org.opendc.faas.service.FaaSService +import org.opendc.faas.service.autoscaler.FunctionTerminationPolicyFixed +import org.opendc.faas.service.router.RandomRoutingPolicy +import org.opendc.faas.simulator.delay.ZeroDelayInjector +import org.opendc.faas.simulator.workload.SimFaaSWorkload +import org.opendc.simulator.compute.model.MachineModel +import org.opendc.simulator.compute.model.MemoryUnit +import org.opendc.simulator.compute.model.ProcessingNode +import org.opendc.simulator.compute.model.ProcessingUnit +import org.opendc.simulator.compute.workload.SimFlopsWorkload +import org.opendc.simulator.compute.workload.SimWorkload +import org.opendc.simulator.core.runBlockingSimulation +import java.time.Duration + +/** + * A test suite for the [FaaSService] implementation under simulated conditions. + */ +@OptIn(ExperimentalCoroutinesApi::class) +internal class SimFaaSServiceTest { + + private lateinit var machineModel: MachineModel + + @BeforeEach + fun setUp() { + val cpuNode = ProcessingNode("Intel", "Xeon", "amd64", 2) + + machineModel = MachineModel( + cpus = List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 1000.0) }, + memory = List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) } + ) + } + + @Test + fun testSmoke() = runBlockingSimulation { + val workload = spyk(object : SimFaaSWorkload, SimWorkload by SimFlopsWorkload(1000) { + override suspend fun invoke() {} + }) + val deployer = SimFunctionDeployer(clock, this, machineModel, ZeroDelayInjector) { workload } + val service = FaaSService( + coroutineContext, clock, MeterProvider.noop(), deployer, RandomRoutingPolicy(), + FunctionTerminationPolicyFixed(coroutineContext, clock, timeout = Duration.ofMillis(10000)) + ) + + val client = service.newClient() + + val function = client.newFunction("test", 128) + function.invoke() + delay(2000) + + service.close() + + yield() + + assertAll( + { coVerify { workload.invoke() } }, + ) + } +} diff --git a/opendc-format/build.gradle.kts b/opendc-format/build.gradle.kts deleted file mode 100644 index 2647c834..00000000 --- a/opendc-format/build.gradle.kts +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2019 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -description = "Library for reading common data formats for topology simulation" - -/* Build configuration */ -plugins { - `kotlin-library-conventions` - `testing-conventions` - `jacoco-conventions` -} - -dependencies { - api(platform(projects.opendcPlatform)) - api(projects.opendcCompute.opendcComputeApi) - api(projects.opendcWorkflow.opendcWorkflowApi) - implementation(projects.opendcSimulator.opendcSimulatorCompute) - implementation(projects.opendcCompute.opendcComputeSimulator) - api(libs.jackson.module.kotlin) - - implementation(libs.parquet) - implementation(libs.hadoop.client) { - exclude(group = "org.slf4j", module = "slf4j-log4j12") - exclude(group = "log4j") - } -} diff --git a/opendc-format/src/main/kotlin/org/opendc/format/environment/EnvironmentReader.kt b/opendc-format/src/main/kotlin/org/opendc/format/environment/EnvironmentReader.kt deleted file mode 100644 index 97d6f239..00000000 --- a/opendc-format/src/main/kotlin/org/opendc/format/environment/EnvironmentReader.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2020 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.format.environment - -import java.io.Closeable - -/** - * An interface for reading descriptions of topology environments into memory. - */ -public interface EnvironmentReader : Closeable { - /** - * Read the environment into a list. - */ - public fun read(): List<MachineDef> -} diff --git a/opendc-format/src/main/kotlin/org/opendc/format/environment/MachineDef.kt b/opendc-format/src/main/kotlin/org/opendc/format/environment/MachineDef.kt deleted file mode 100644 index 9b799cc2..00000000 --- a/opendc-format/src/main/kotlin/org/opendc/format/environment/MachineDef.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.format.environment - -import org.opendc.simulator.compute.SimMachineModel -import org.opendc.simulator.compute.power.PowerModel -import java.util.* - -public data class MachineDef( - val uid: UUID, - val name: String, - val meta: Map<String, Any>, - val model: SimMachineModel, - val powerModel: PowerModel -) diff --git a/opendc-format/src/main/kotlin/org/opendc/format/environment/sc18/Model.kt b/opendc-format/src/main/kotlin/org/opendc/format/environment/sc18/Model.kt deleted file mode 100644 index c313467f..00000000 --- a/opendc-format/src/main/kotlin/org/opendc/format/environment/sc18/Model.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2020 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.format.environment.sc18 - -import com.fasterxml.jackson.annotation.JsonSubTypes -import com.fasterxml.jackson.annotation.JsonTypeInfo - -/** - * A topology setup. - * - * @property name The name of the setup. - * @property rooms The rooms in the topology. - */ -internal data class Setup(val name: String, val rooms: List<Room>) - -/** - * A room in a topology. - * - * @property type The type of room in the topology. - * @property objects The objects in the room. - */ -internal data class Room(val type: String, val objects: List<RoomObject>) - -/** - * An object in a [Room]. - * - * @property type The type of the room object. - */ -@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") -@JsonSubTypes(value = [JsonSubTypes.Type(name = "RACK", value = RoomObject.Rack::class)]) -internal sealed class RoomObject(val type: String) { - /** - * A rack in a server room. - * - * @property machines The machines in the rack. - */ - internal data class Rack(val machines: List<Machine>) : RoomObject("RACK") -} - -/** - * A machine in the setup that consists of the specified CPU's represented as - * integer identifiers and ethernet speed. - * - * @property cpus The CPUs in the machine represented as integer identifiers. - */ -internal data class Machine(val cpus: List<Int>) diff --git a/opendc-format/src/main/kotlin/org/opendc/format/environment/sc18/Sc18EnvironmentReader.kt b/opendc-format/src/main/kotlin/org/opendc/format/environment/sc18/Sc18EnvironmentReader.kt deleted file mode 100644 index 1f080c2d..00000000 --- a/opendc-format/src/main/kotlin/org/opendc/format/environment/sc18/Sc18EnvironmentReader.kt +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (c) 2020 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.format.environment.sc18 - -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import com.fasterxml.jackson.module.kotlin.readValue -import org.opendc.format.environment.EnvironmentReader -import org.opendc.format.environment.MachineDef -import org.opendc.simulator.compute.SimMachineModel -import org.opendc.simulator.compute.model.MemoryUnit -import org.opendc.simulator.compute.model.ProcessingNode -import org.opendc.simulator.compute.model.ProcessingUnit -import org.opendc.simulator.compute.power.ConstantPowerModel -import java.io.InputStream -import java.util.* - -/** - * A parser for the JSON experiment setup files used for the SC18 paper: "A Reference Architecture for Topology - * Schedulers". - * - * @param input The input stream to read from. - * @param mapper The Jackson object mapper to use. - */ -public class Sc18EnvironmentReader(input: InputStream, mapper: ObjectMapper = jacksonObjectMapper()) : EnvironmentReader { - /** - * The environment that was read from the file. - */ - private val setup: Setup = mapper.readValue(input) - - /** - * Read the environment. - */ - public override fun read(): List<MachineDef> { - var counter = 0 - return setup.rooms.flatMap { room -> - room.objects.flatMap { roomObject -> - when (roomObject) { - is RoomObject.Rack -> { - roomObject.machines.map { machine -> - val cores = machine.cpus.flatMap { id -> - when (id) { - 1 -> { - val node = ProcessingNode("Intel", "Core(TM) i7-6920HQ", "amd64", 4) - List(node.coreCount) { ProcessingUnit(node, it, 4100.0) } - } - 2 -> { - val node = ProcessingNode("Intel", "Core(TM) i7-6920HQ", "amd64", 2) - List(node.coreCount) { ProcessingUnit(node, it, 3500.0) } - } - else -> throw IllegalArgumentException("The cpu id $id is not recognized") - } - } - MachineDef( - UUID(0L, counter++.toLong()), - "node-$counter", - emptyMap(), - SimMachineModel(cores, listOf(MemoryUnit("", "", 2300.0, 16000))), - ConstantPowerModel(0.0) - ) - } - } - } - } - } - } - - override fun close() {} -} diff --git a/opendc-format/src/main/kotlin/org/opendc/format/environment/sc20/Model.kt b/opendc-format/src/main/kotlin/org/opendc/format/environment/sc20/Model.kt deleted file mode 100644 index 58af8453..00000000 --- a/opendc-format/src/main/kotlin/org/opendc/format/environment/sc20/Model.kt +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (c) 2020 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.format.environment.sc20 - -import com.fasterxml.jackson.annotation.JsonSubTypes -import com.fasterxml.jackson.annotation.JsonTypeInfo - -/** - * A topology setup. - * - * @property name The name of the setup. - * @property rooms The rooms in the topology. - */ -internal data class Setup(val name: String, val rooms: List<Room>) - -/** - * A room in a topology. - * - * @property type The type of room in the topology. - * @property objects The objects in the room. - */ -internal data class Room(val type: String, val objects: List<RoomObject>) - -/** - * An object in a [Room]. - * - * @property type The type of the room object. - */ -@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") -@JsonSubTypes(value = [JsonSubTypes.Type(name = "RACK", value = RoomObject.Rack::class)]) -internal sealed class RoomObject(val type: String) { - /** - * A rack in a server room. - * - * @property machines The machines in the rack. - */ - internal data class Rack(val machines: List<Machine>) : RoomObject("RACK") -} - -/** - * A machine in the setup that consists of the specified CPU's represented as - * integer identifiers and ethernet speed. - * - * @property cpus The CPUs in the machine represented as integer identifiers. - * @property memories The memories in the machine represented as integer identifiers. - */ -internal data class Machine(val cpus: List<Int>, val memories: List<Int>) diff --git a/opendc-format/src/main/kotlin/org/opendc/format/environment/sc20/Sc20ClusterEnvironmentReader.kt b/opendc-format/src/main/kotlin/org/opendc/format/environment/sc20/Sc20ClusterEnvironmentReader.kt deleted file mode 100644 index cf90da68..00000000 --- a/opendc-format/src/main/kotlin/org/opendc/format/environment/sc20/Sc20ClusterEnvironmentReader.kt +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (c) 2020 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.format.environment.sc20 - -import org.opendc.format.environment.EnvironmentReader -import org.opendc.format.environment.MachineDef -import org.opendc.simulator.compute.SimMachineModel -import org.opendc.simulator.compute.model.MemoryUnit -import org.opendc.simulator.compute.model.ProcessingNode -import org.opendc.simulator.compute.model.ProcessingUnit -import org.opendc.simulator.compute.power.LinearPowerModel -import java.io.File -import java.io.FileInputStream -import java.io.InputStream -import java.util.* - -/** - * A [EnvironmentReader] for the internal environment format. - * - * @param environmentFile The file describing the physical cluster. - */ -public class Sc20ClusterEnvironmentReader( - private val input: InputStream -) : EnvironmentReader { - - public constructor(file: File) : this(FileInputStream(file)) - - public override fun read(): List<MachineDef> { - var clusterIdCol = 0 - var speedCol = 0 - var numberOfHostsCol = 0 - var memoryPerHostCol = 0 - var coresPerHostCol = 0 - - var clusterIdx: Int = 0 - var clusterId: String - var speed: Double - var numberOfHosts: Int - var memoryPerHost: Long - var coresPerHost: Int - - val nodes = mutableListOf<MachineDef>() - val random = Random(0) - - input.bufferedReader().use { reader -> - reader.lineSequence() - .filter { line -> - // Ignore comments in the file - !line.startsWith("#") && line.isNotBlank() - } - .forEachIndexed { idx, line -> - val values = line.split(";") - - if (idx == 0) { - val header = values.mapIndexed { col, name -> Pair(name.trim(), col) }.toMap() - clusterIdCol = header["ClusterID"]!! - speedCol = header["Speed"]!! - numberOfHostsCol = header["numberOfHosts"]!! - memoryPerHostCol = header["memoryCapacityPerHost"]!! - coresPerHostCol = header["coreCountPerHost"]!! - return@forEachIndexed - } - - clusterIdx++ - clusterId = values[clusterIdCol].trim() - speed = values[speedCol].trim().toDouble() * 1000.0 - numberOfHosts = values[numberOfHostsCol].trim().toInt() - memoryPerHost = values[memoryPerHostCol].trim().toLong() * 1000L - coresPerHost = values[coresPerHostCol].trim().toInt() - - val unknownProcessingNode = ProcessingNode("unknown", "unknown", "unknown", coresPerHost) - val unknownMemoryUnit = MemoryUnit("unknown", "unknown", -1.0, memoryPerHost) - - repeat(numberOfHosts) { - nodes.add( - MachineDef( - UUID(random.nextLong(), random.nextLong()), - "node-$clusterId-$it", - mapOf("cluster" to clusterId), - SimMachineModel( - List(coresPerHost) { coreId -> - ProcessingUnit(unknownProcessingNode, coreId, speed) - }, - listOf(unknownMemoryUnit) - ), - // For now we assume a simple linear load model with an idle draw of ~200W and a maximum - // power draw of 350W. - // Source: https://stackoverflow.com/questions/6128960 - LinearPowerModel(350.0, 200 / 350.0) - ) - ) - } - } - } - - return nodes - } - - override fun close() {} -} diff --git a/opendc-format/src/main/kotlin/org/opendc/format/environment/sc20/Sc20EnvironmentReader.kt b/opendc-format/src/main/kotlin/org/opendc/format/environment/sc20/Sc20EnvironmentReader.kt deleted file mode 100644 index c6a19430..00000000 --- a/opendc-format/src/main/kotlin/org/opendc/format/environment/sc20/Sc20EnvironmentReader.kt +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (c) 2020 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.format.environment.sc20 - -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import com.fasterxml.jackson.module.kotlin.readValue -import org.opendc.format.environment.EnvironmentReader -import org.opendc.format.environment.MachineDef -import org.opendc.simulator.compute.SimMachineModel -import org.opendc.simulator.compute.model.MemoryUnit -import org.opendc.simulator.compute.model.ProcessingNode -import org.opendc.simulator.compute.model.ProcessingUnit -import org.opendc.simulator.compute.power.LinearPowerModel -import java.io.InputStream -import java.util.* - -/** - * A parser for the JSON experiment setup files used for the SC20 paper. - * - * @param input The input stream to read from. - * @param mapper The Jackson object mapper to use. - */ -public class Sc20EnvironmentReader(input: InputStream, mapper: ObjectMapper = jacksonObjectMapper()) : EnvironmentReader { - /** - * The environment that was read from the file. - */ - private val setup: Setup = mapper.readValue(input) - - /** - * Read the environment. - */ - public override fun read(): List<MachineDef> { - var counter = 0 - return setup.rooms.flatMap { room -> - room.objects.flatMap { roomObject -> - when (roomObject) { - is RoomObject.Rack -> { - roomObject.machines.map { machine -> - val cores = machine.cpus.flatMap { id -> - when (id) { - 1 -> { - val node = ProcessingNode("Intel", "Core(TM) i7-6920HQ", "amd64", 4) - List(node.coreCount) { ProcessingUnit(node, it, 4100.0) } - } - 2 -> { - val node = ProcessingNode("Intel", "Core(TM) i7-6920HQ", "amd64", 2) - List(node.coreCount) { ProcessingUnit(node, it, 3500.0) } - } - else -> throw IllegalArgumentException("The cpu id $id is not recognized") - } - } - val memories = machine.memories.map { id -> - when (id) { - 1 -> MemoryUnit("Samsung", "PC DRAM K4A4G045WD", 1600.0, 4_000L) - else -> throw IllegalArgumentException("The cpu id $id is not recognized") - } - } - MachineDef( - UUID(0L, counter++.toLong()), - "node-$counter", - emptyMap(), - SimMachineModel(cores, memories), - // For now we assume a simple linear load model with an idle draw of ~200W and a maximum - // power draw of 350W. - // Source: https://stackoverflow.com/questions/6128960 - LinearPowerModel(350.0, 200 / 350.0) - ) - } - } - } - } - } - } - - override fun close() {} -} diff --git a/opendc-format/src/main/kotlin/org/opendc/format/trace/PerformanceInterferenceModelReader.kt b/opendc-format/src/main/kotlin/org/opendc/format/trace/PerformanceInterferenceModelReader.kt deleted file mode 100644 index f30e64cf..00000000 --- a/opendc-format/src/main/kotlin/org/opendc/format/trace/PerformanceInterferenceModelReader.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2020 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.format.trace - -import org.opendc.simulator.compute.interference.PerformanceInterferenceModel -import java.io.Closeable -import kotlin.random.Random - -/** - * An interface for reading descriptions of performance interference models into memory. - */ -public interface PerformanceInterferenceModelReader : Closeable { - /** - * Construct a [PerformanceInterferenceModel]. - */ - public fun construct(random: Random): Map<String, PerformanceInterferenceModel> -} diff --git a/opendc-format/src/main/kotlin/org/opendc/format/trace/TraceEntry.kt b/opendc-format/src/main/kotlin/org/opendc/format/trace/TraceEntry.kt deleted file mode 100644 index 3ce79d69..00000000 --- a/opendc-format/src/main/kotlin/org/opendc/format/trace/TraceEntry.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2019 atlarge-research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.format.trace - -import java.util.UUID - -/** - * An entry in a workload trace. - * - * @param uid The unique identifier of the entry. - * @param name The name of the entry. - * @param start The start time of the workload. - * @param workload The workload of the entry. - * @param meta The meta-data associated with the workload. - */ -public data class TraceEntry<out T>( - val uid: UUID, - val name: String, - val start: Long, - val workload: T, - val meta: Map<String, Any> -) diff --git a/opendc-format/src/main/kotlin/org/opendc/format/trace/TraceReader.kt b/opendc-format/src/main/kotlin/org/opendc/format/trace/TraceReader.kt deleted file mode 100644 index 7df1acd3..00000000 --- a/opendc-format/src/main/kotlin/org/opendc/format/trace/TraceReader.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2020 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.format.trace - -import java.io.Closeable - -/** - * An interface for reading workloads into memory. - * - * This interface must guarantee that the entries are delivered in order of submission time. - * - * @param T The shape of the workloads supported by this reader. - */ -public interface TraceReader<T> : Iterator<TraceEntry<T>>, Closeable diff --git a/opendc-format/src/main/kotlin/org/opendc/format/trace/VmPlacementReader.kt b/opendc-format/src/main/kotlin/org/opendc/format/trace/VmPlacementReader.kt deleted file mode 100644 index 6861affe..00000000 --- a/opendc-format/src/main/kotlin/org/opendc/format/trace/VmPlacementReader.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2019 atlarge-research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.format.trace - -import java.io.Closeable - -/** - * An interface for reading VM placement data into memory. - */ -public interface VmPlacementReader : Closeable { - /** - * Construct a map of VMs to clusters. - */ - public fun construct(): Map<String, String> -} diff --git a/opendc-format/src/main/kotlin/org/opendc/format/trace/bitbrains/BitbrainsTraceReader.kt b/opendc-format/src/main/kotlin/org/opendc/format/trace/bitbrains/BitbrainsTraceReader.kt deleted file mode 100644 index 769b2b13..00000000 --- a/opendc-format/src/main/kotlin/org/opendc/format/trace/bitbrains/BitbrainsTraceReader.kt +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (c) 2020 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.format.trace.bitbrains - -import org.opendc.format.trace.TraceEntry -import org.opendc.format.trace.TraceReader -import org.opendc.simulator.compute.interference.IMAGE_PERF_INTERFERENCE_MODEL -import org.opendc.simulator.compute.interference.PerformanceInterferenceModel -import org.opendc.simulator.compute.workload.SimTraceWorkload -import org.opendc.simulator.compute.workload.SimWorkload -import java.io.BufferedReader -import java.io.File -import java.io.FileReader -import java.util.* -import kotlin.math.min - -/** - * A [TraceReader] for the public VM workload trace format. - * - * @param traceDirectory The directory of the traces. - * @param performanceInterferenceModel The performance model covering the workload in the VM trace. - */ -public class BitbrainsTraceReader( - traceDirectory: File, - performanceInterferenceModel: PerformanceInterferenceModel -) : TraceReader<SimWorkload> { - /** - * The internal iterator to use for this reader. - */ - private val iterator: Iterator<TraceEntry<SimWorkload>> - - /** - * Initialize the reader. - */ - init { - val entries = mutableMapOf<Long, TraceEntry<SimWorkload>>() - - var timestampCol = 0 - var coreCol = 0 - var cpuUsageCol = 0 - var provisionedMemoryCol = 0 - val traceInterval = 5 * 60 * 1000L - - traceDirectory.walk() - .filterNot { it.isDirectory } - .forEach { vmFile -> - println(vmFile) - val flopsHistory = mutableListOf<SimTraceWorkload.Fragment>() - var vmId = -1L - var cores = -1 - var requiredMemory = -1L - var startTime = -1L - - BufferedReader(FileReader(vmFile)).use { reader -> - reader.lineSequence() - .filter { line -> - // Ignore comments in the trace - !line.startsWith("#") && line.isNotBlank() - } - .forEachIndexed { idx, line -> - val values = line.split(";\t") - - // Parse GWF header - if (idx == 0) { - val header = values.mapIndexed { col, name -> Pair(name.trim(), col) }.toMap() - timestampCol = header["Timestamp [ms]"]!! - coreCol = header["CPU cores"]!! - cpuUsageCol = header["CPU usage [MHZ]"]!! - provisionedMemoryCol = header["Memory capacity provisioned [KB]"]!! - return@forEachIndexed - } - - vmId = vmFile.nameWithoutExtension.trim().toLong() - startTime = min(startTime, values[timestampCol].trim().toLong() - 5 * 60) - cores = values[coreCol].trim().toInt() - val cpuUsage = values[cpuUsageCol].trim().toDouble() // MHz - requiredMemory = (values[provisionedMemoryCol].trim().toDouble() / 1000).toLong() - - if (flopsHistory.isEmpty()) { - flopsHistory.add(SimTraceWorkload.Fragment(traceInterval, cpuUsage, cores)) - } else { - if (flopsHistory.last().usage != cpuUsage) { - flopsHistory.add( - SimTraceWorkload.Fragment( - traceInterval, - cpuUsage, - cores - ) - ) - } else { - val oldFragment = flopsHistory.removeAt(flopsHistory.size - 1) - flopsHistory.add( - SimTraceWorkload.Fragment( - oldFragment.duration + traceInterval, - cpuUsage, - cores - ) - ) - } - } - } - } - - val uuid = UUID(0L, vmId) - - val relevantPerformanceInterferenceModelItems = - PerformanceInterferenceModel( - performanceInterferenceModel.items.filter { it.workloadNames.contains(vmId.toString()) } - .toSortedSet() - ) - - val workload = SimTraceWorkload(flopsHistory.asSequence()) - entries[vmId] = TraceEntry( - uuid, - vmId.toString(), - startTime, - workload, - mapOf( - IMAGE_PERF_INTERFERENCE_MODEL to relevantPerformanceInterferenceModelItems, - "cores" to cores, - "required-memory" to requiredMemory, - "workload" to workload - ) - ) - } - - // Create the entry iterator - iterator = entries.values.sortedBy { it.start }.iterator() - } - - override fun hasNext(): Boolean = iterator.hasNext() - - override fun next(): TraceEntry<SimWorkload> = iterator.next() - - override fun close() {} -} diff --git a/opendc-format/src/main/kotlin/org/opendc/format/trace/gwf/GwfTraceReader.kt b/opendc-format/src/main/kotlin/org/opendc/format/trace/gwf/GwfTraceReader.kt deleted file mode 100644 index e68afeb7..00000000 --- a/opendc-format/src/main/kotlin/org/opendc/format/trace/gwf/GwfTraceReader.kt +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright (c) 2020 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.format.trace.gwf - -import org.opendc.format.trace.TraceEntry -import org.opendc.format.trace.TraceReader -import org.opendc.simulator.compute.workload.SimFlopsWorkload -import org.opendc.workflow.api.Job -import org.opendc.workflow.api.Task -import org.opendc.workflow.api.WORKFLOW_TASK_CORES -import org.opendc.workflow.api.WORKFLOW_TASK_DEADLINE -import java.io.BufferedReader -import java.io.File -import java.io.InputStream -import java.util.* -import kotlin.collections.HashSet -import kotlin.collections.Iterator -import kotlin.collections.List -import kotlin.collections.MutableSet -import kotlin.collections.component1 -import kotlin.collections.component2 -import kotlin.collections.filter -import kotlin.collections.forEach -import kotlin.collections.getOrPut -import kotlin.collections.map -import kotlin.collections.mapIndexed -import kotlin.collections.mapOf -import kotlin.collections.mutableMapOf -import kotlin.collections.set -import kotlin.collections.sortedBy -import kotlin.collections.toMap -import kotlin.math.max -import kotlin.math.min - -/** - * A [TraceReader] for the Grid Workload Format. See the Grid Workloads Archive (http://gwa.ewi.tudelft.nl/) for more - * information about the format. - * - * Be aware that in the Grid Workload Format, workflows are not required to be ordered by submission time and therefore - * this reader needs to read the whole trace into memory before an entry can be read. Consider converting the trace to a - * different format for better performance. - * - * @param reader The buffered reader to read the trace with. - */ -public class GwfTraceReader(reader: BufferedReader) : TraceReader<Job> { - /** - * The internal iterator to use for this reader. - */ - private val iterator: Iterator<TraceEntry<Job>> - - /** - * Create a [GwfTraceReader] instance from the specified [File]. - * - * @param file The file to read from. - */ - public constructor(file: File) : this(file.bufferedReader()) - - /** - * Create a [GwfTraceReader] instance from the specified [InputStream]. - * - * @param input The input stream to read from. - */ - public constructor(input: InputStream) : this(input.bufferedReader()) - - /** - * Initialize the reader. - */ - init { - val workflows = mutableMapOf<Long, Job>() - val starts = mutableMapOf<Long, Long>() - val tasks = mutableMapOf<Long, Task>() - val taskDependencies = mutableMapOf<Task, List<Long>>() - - var workflowIdCol = 0 - var taskIdCol = 0 - var submitTimeCol = 0 - var runtimeCol = 0 - var coreCol = 0 - var dependencyCol = 0 - - try { - reader.lineSequence() - .filter { line -> - // Ignore comments in the trace - !line.startsWith("#") && line.isNotBlank() - } - .forEachIndexed { idx, line -> - val values = line.split(",") - - // Parse GWF header - if (idx == 0) { - val header = values.mapIndexed { col, name -> Pair(name.trim(), col) }.toMap() - workflowIdCol = header["WorkflowID"]!! - taskIdCol = header["JobID"]!! - submitTimeCol = header["SubmitTime"]!! - runtimeCol = header["RunTime"]!! - coreCol = header["NProcs"]!! - dependencyCol = header["Dependencies"]!! - return@forEachIndexed - } - - val workflowId = values[workflowIdCol].trim().toLong() - val taskId = values[taskIdCol].trim().toLong() - val submitTime = values[submitTimeCol].trim().toLong() * 1000 // ms - val runtime = max(0, values[runtimeCol].trim().toLong()) // s - val cores = values[coreCol].trim().toInt() - val dependencies = values[dependencyCol].split(" ") - .filter { it.isNotEmpty() } - .map { it.trim().toLong() } - - val flops: Long = 4000 * runtime * cores - - val workflow = workflows.getOrPut(workflowId) { - Job(UUID(0L, workflowId), "<unnamed>", HashSet()) - } - val workload = SimFlopsWorkload(flops) - val task = Task( - UUID(0L, taskId), - "<unnamed>", - HashSet(), - mapOf( - "workload" to workload, - WORKFLOW_TASK_CORES to cores, - WORKFLOW_TASK_DEADLINE to (runtime * 1000) - ), - ) - starts.merge(workflowId, submitTime, ::min) - (workflow.tasks as MutableSet<Task>).add(task) - tasks[taskId] = task - taskDependencies[task] = dependencies - } - } finally { - reader.close() - } - - // Fix dependencies and dependents for all tasks - taskDependencies.forEach { (task, dependencies) -> - (task.dependencies as MutableSet<Task>).addAll( - dependencies.map { taskId -> - tasks[taskId] ?: throw IllegalArgumentException("Dependency task with id $taskId not found") - } - ) - } - - // Create the entry iterator - iterator = workflows.map { (id, job) -> TraceEntry(job.uid, job.name, starts.getValue(id), job, job.metadata) } - .sortedBy { it.start } - .iterator() - } - - override fun hasNext(): Boolean = iterator.hasNext() - - override fun next(): TraceEntry<Job> = iterator.next() - - override fun close() {} -} diff --git a/opendc-format/src/main/kotlin/org/opendc/format/trace/sc20/PerformanceInterferenceEntry.kt b/opendc-format/src/main/kotlin/org/opendc/format/trace/sc20/PerformanceInterferenceEntry.kt deleted file mode 100644 index 0da1f7c2..00000000 --- a/opendc-format/src/main/kotlin/org/opendc/format/trace/sc20/PerformanceInterferenceEntry.kt +++ /dev/null @@ -1,7 +0,0 @@ -package org.opendc.format.trace.sc20 - -internal data class PerformanceInterferenceEntry( - val vms: List<String>, - val minServerLoad: Double, - val performanceScore: Double -) diff --git a/opendc-format/src/main/kotlin/org/opendc/format/trace/sc20/Sc20PerformanceInterferenceReader.kt b/opendc-format/src/main/kotlin/org/opendc/format/trace/sc20/Sc20PerformanceInterferenceReader.kt deleted file mode 100644 index 4267737d..00000000 --- a/opendc-format/src/main/kotlin/org/opendc/format/trace/sc20/Sc20PerformanceInterferenceReader.kt +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2020 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.format.trace.sc20 - -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import com.fasterxml.jackson.module.kotlin.readValue -import org.opendc.format.trace.PerformanceInterferenceModelReader -import org.opendc.simulator.compute.interference.PerformanceInterferenceModel -import java.io.InputStream -import java.util.* -import kotlin.random.Random - -/** - * A parser for the JSON performance interference setup files used for the SC20 paper. - * - * @param input The input stream to read from. - * @param mapper The Jackson object mapper to use. - */ -public class Sc20PerformanceInterferenceReader(input: InputStream, mapper: ObjectMapper = jacksonObjectMapper()) : - PerformanceInterferenceModelReader { - /** - * The computed value from the file. - */ - private val items: Map<String, TreeSet<PerformanceInterferenceModel.Item>> - - init { - val entries: List<PerformanceInterferenceEntry> = mapper.readValue(input) - val res = mutableMapOf<String, TreeSet<PerformanceInterferenceModel.Item>>() - for (entry in entries) { - val item = PerformanceInterferenceModel.Item(TreeSet(entry.vms), entry.minServerLoad, entry.performanceScore) - for (workload in entry.vms) { - res.computeIfAbsent(workload) { TreeSet() }.add(item) - } - } - - items = res - } - - override fun construct(random: Random): Map<String, PerformanceInterferenceModel> { - return items.mapValues { PerformanceInterferenceModel(it.value, Random(random.nextInt())) } - } - - override fun close() {} -} diff --git a/opendc-format/src/main/kotlin/org/opendc/format/trace/sc20/Sc20TraceReader.kt b/opendc-format/src/main/kotlin/org/opendc/format/trace/sc20/Sc20TraceReader.kt deleted file mode 100644 index 1eb4bac2..00000000 --- a/opendc-format/src/main/kotlin/org/opendc/format/trace/sc20/Sc20TraceReader.kt +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (c) 2020 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.format.trace.sc20 - -import org.opendc.format.trace.TraceEntry -import org.opendc.format.trace.TraceReader -import org.opendc.simulator.compute.interference.IMAGE_PERF_INTERFERENCE_MODEL -import org.opendc.simulator.compute.interference.PerformanceInterferenceModel -import org.opendc.simulator.compute.workload.SimTraceWorkload -import org.opendc.simulator.compute.workload.SimWorkload -import java.io.BufferedReader -import java.io.File -import java.io.FileReader -import java.util.* -import kotlin.math.max -import kotlin.math.min -import kotlin.random.Random - -/** - * A [TraceReader] for the internal VM workload trace format. - * - * @param traceDirectory The directory of the traces. - * @param performanceInterferenceModel The performance model covering the workload in the VM trace. - */ -public class Sc20TraceReader( - traceDirectory: File, - performanceInterferenceModel: PerformanceInterferenceModel, - selectedVms: List<String>, - random: Random -) : TraceReader<SimWorkload> { - /** - * The internal iterator to use for this reader. - */ - private val iterator: Iterator<TraceEntry<SimWorkload>> - - /** - * Initialize the reader. - */ - init { - val entries = mutableMapOf<UUID, TraceEntry<SimWorkload>>() - - val timestampCol = 0 - val cpuUsageCol = 1 - val coreCol = 12 - val provisionedMemoryCol = 20 - val traceInterval = 5 * 60 * 1000L - - val vms = if (selectedVms.isEmpty()) { - traceDirectory.walk() - .filterNot { it.isDirectory } - .filter { it.extension == "csv" || it.extension == "txt" } - .toList() - } else { - selectedVms.map { - File(traceDirectory, it) - } - } - - vms - .forEachIndexed { idx, vmFile -> - println(vmFile) - - var vmId = "" - var maxCores = -1 - var requiredMemory = -1L - var timestamp: Long - var cores = -1 - var minTime = Long.MAX_VALUE - - BufferedReader(FileReader(vmFile)).use { reader -> - reader.lineSequence() - .filter { line -> - // Ignore comments in the trace - !line.startsWith("#") && line.isNotBlank() - } - .forEach { line -> - val values = line.split(" ") - - vmId = vmFile.name - timestamp = (values[timestampCol].trim().toLong() - 5 * 60) * 1000L - cores = values[coreCol].trim().toInt() - requiredMemory = max(requiredMemory, values[provisionedMemoryCol].trim().toLong()) - maxCores = max(maxCores, cores) - minTime = min(minTime, timestamp) - } - } - - val flopsFragments = sequence { - var last: SimTraceWorkload.Fragment? = null - - BufferedReader(FileReader(vmFile)).use { reader -> - reader.lineSequence() - .chunked(128) - .forEach { lines -> - for (line in lines) { - // Ignore comments in the trace - if (line.startsWith("#") || line.isBlank()) { - continue - } - - val values = line.split(" ") - val cpuUsage = values[cpuUsageCol].trim().toDouble() // MHz - requiredMemory = max(requiredMemory, values[provisionedMemoryCol].trim().toLong()) - maxCores = max(maxCores, cores) - - last = if (last != null && last!!.usage == 0.0 && cpuUsage == 0.0) { - val oldFragment = last!! - SimTraceWorkload.Fragment( - oldFragment.duration + traceInterval, - cpuUsage, - cores - ) - } else { - val fragment = - SimTraceWorkload.Fragment(traceInterval, cpuUsage, cores) - if (last != null) { - yield(last!!) - } - fragment - } - } - } - - if (last != null) { - yield(last!!) - } - } - } - - val uuid = UUID(0, idx.toLong()) - - val relevantPerformanceInterferenceModelItems = - PerformanceInterferenceModel( - performanceInterferenceModel.items.filter { it.workloadNames.contains(vmId) }.toSortedSet(), - Random(random.nextInt()) - ) - val workload = SimTraceWorkload(flopsFragments.asSequence()) - entries[uuid] = TraceEntry( - uuid, - vmId, - minTime, - workload, - mapOf( - IMAGE_PERF_INTERFERENCE_MODEL to relevantPerformanceInterferenceModelItems, - "cores" to cores, - "required-memory" to requiredMemory, - "workload" to workload - ) - ) - } - - // Create the entry iterator - iterator = entries.values.sortedBy { it.start }.iterator() - } - - override fun hasNext(): Boolean = iterator.hasNext() - - override fun next(): TraceEntry<SimWorkload> = iterator.next() - - override fun close() {} -} diff --git a/opendc-format/src/main/kotlin/org/opendc/format/trace/sc20/Sc20VmPlacementReader.kt b/opendc-format/src/main/kotlin/org/opendc/format/trace/sc20/Sc20VmPlacementReader.kt deleted file mode 100644 index 61bdea60..00000000 --- a/opendc-format/src/main/kotlin/org/opendc/format/trace/sc20/Sc20VmPlacementReader.kt +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2020 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.format.trace.sc20 - -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import com.fasterxml.jackson.module.kotlin.readValue -import org.opendc.format.trace.VmPlacementReader -import java.io.InputStream - -/** - * A parser for the JSON VM placement data files used for the SC20 paper. - * - * @param input The input stream to read from. - * @param mapper The Jackson object mapper to use. - */ -public class Sc20VmPlacementReader(input: InputStream, mapper: ObjectMapper = jacksonObjectMapper()) : - VmPlacementReader { - /** - * The environment that was read from the file. - */ - private val placements = mapper.readValue<Map<String, String>>(input) - - override fun construct(): Map<String, String> { - return placements - .mapKeys { "vm__workload__${it.key}.txt" } - .mapValues { it.value.split("/")[1] } // Clusters have format XX0 / X00 - } - - override fun close() {} -} diff --git a/opendc-format/src/main/kotlin/org/opendc/format/trace/swf/SwfTraceReader.kt b/opendc-format/src/main/kotlin/org/opendc/format/trace/swf/SwfTraceReader.kt deleted file mode 100644 index 0d1f3cea..00000000 --- a/opendc-format/src/main/kotlin/org/opendc/format/trace/swf/SwfTraceReader.kt +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright (c) 2020 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.format.trace.swf - -import org.opendc.format.trace.TraceEntry -import org.opendc.format.trace.TraceReader -import org.opendc.simulator.compute.workload.SimTraceWorkload -import org.opendc.simulator.compute.workload.SimWorkload -import java.io.BufferedReader -import java.io.File -import java.io.FileReader -import java.util.* - -/** - * A [TraceReader] for reading SWF traces into VM-modeled workloads. - * - * The standard is defined by the PWA, see here: https://www.cse.huji.ac.il/labs/parallel/workload/swf.html - * - * @param file The trace file. - */ -public class SwfTraceReader( - file: File, - maxNumCores: Int = -1 -) : TraceReader<SimWorkload> { - /** - * The internal iterator to use for this reader. - */ - private val iterator: Iterator<TraceEntry<SimWorkload>> - - /** - * Initialize the reader. - */ - init { - val entries = mutableMapOf<Long, TraceEntry<SimWorkload>>() - - val jobNumberCol = 0 - val submitTimeCol = 1 // seconds (begin of trace is 0) - val waitTimeCol = 2 // seconds - val runTimeCol = 3 // seconds - val numAllocatedCoresCol = 4 // We assume that single-core processors were used at the time - val requestedMemoryCol = 9 // KB per processor/core (-1 if not specified) - - val sliceDuration = 5 * 60L - - var jobNumber: Long - var submitTime: Long - var waitTime: Long - var runTime: Long - var cores: Int - var memory: Long - var slicedWaitTime: Long - var flopsPerSecond: Long - var flopsPartialSlice: Long - var runtimePartialSliceRemainder: Long - - BufferedReader(FileReader(file)).use { reader -> - reader.lineSequence() - .filter { line -> - // Ignore comments in the trace - !line.startsWith(";") && line.isNotBlank() - } - .forEach { line -> - val values = line.trim().split("\\s+".toRegex()) - - jobNumber = values[jobNumberCol].trim().toLong() - submitTime = values[submitTimeCol].trim().toLong() - waitTime = values[waitTimeCol].trim().toLong() - runTime = values[runTimeCol].trim().toLong() - cores = values[numAllocatedCoresCol].trim().toInt() - memory = values[requestedMemoryCol].trim().toLong() - - if (maxNumCores != -1 && cores > maxNumCores) { - println("Skipped a task due to processor count ($cores > $maxNumCores).") - return@forEach - } - - if (memory == -1L) { - memory = 1000L * cores // assume 1GB of memory per processor if not specified - } else { - memory /= 1000 // convert KB to MB - } - - val flopsHistory = mutableListOf<SimTraceWorkload.Fragment>() - - // Insert waiting time slices - - // We ignore wait time remainders under one - slicedWaitTime = 0L - if (waitTime >= sliceDuration) { - for (tick in submitTime until (submitTime + waitTime - sliceDuration) step sliceDuration) { - flopsHistory.add( - SimTraceWorkload.Fragment( - sliceDuration * 1000L, - 0.0, - cores - ) - ) - slicedWaitTime += sliceDuration - } - } - - // Insert run time slices - - flopsPerSecond = 4_000L * cores - runtimePartialSliceRemainder = runTime % sliceDuration - flopsPartialSlice = flopsPerSecond * runtimePartialSliceRemainder - - for ( - tick in (submitTime + slicedWaitTime) - until (submitTime + slicedWaitTime + runTime - sliceDuration) - step sliceDuration - ) { - flopsHistory.add( - SimTraceWorkload.Fragment( - sliceDuration * 1000L, - 1.0, - cores - ) - ) - } - - if (runtimePartialSliceRemainder > 0) { - flopsHistory.add( - SimTraceWorkload.Fragment( - sliceDuration, - runtimePartialSliceRemainder / sliceDuration.toDouble(), - cores - ) - ) - } - - val uuid = UUID(0L, jobNumber) - val workload = SimTraceWorkload(flopsHistory.asSequence()) - entries[jobNumber] = TraceEntry( - uuid, - jobNumber.toString(), - submitTime, - workload, - mapOf( - "cores" to cores, - "required-memory" to memory, - "workload" to workload - ) - ) - } - } - - // Create the entry iterator - iterator = entries.values.sortedBy { it.start }.iterator() - } - - override fun hasNext(): Boolean = iterator.hasNext() - - override fun next(): TraceEntry<SimWorkload> = iterator.next() - - override fun close() {} -} diff --git a/opendc-format/src/main/kotlin/org/opendc/format/trace/wtf/WtfTraceReader.kt b/opendc-format/src/main/kotlin/org/opendc/format/trace/wtf/WtfTraceReader.kt deleted file mode 100644 index feadf61f..00000000 --- a/opendc-format/src/main/kotlin/org/opendc/format/trace/wtf/WtfTraceReader.kt +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (c) 2020 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.format.trace.wtf - -import org.apache.avro.generic.GenericRecord -import org.apache.hadoop.fs.Path -import org.apache.parquet.avro.AvroParquetReader -import org.opendc.format.trace.TraceEntry -import org.opendc.format.trace.TraceReader -import org.opendc.simulator.compute.workload.SimFlopsWorkload -import org.opendc.workflow.api.Job -import org.opendc.workflow.api.Task -import org.opendc.workflow.api.WORKFLOW_TASK_CORES -import org.opendc.workflow.api.WORKFLOW_TASK_DEADLINE -import java.util.UUID -import kotlin.math.min - -/** - * A [TraceReader] for the Workflow Trace Format (WTF). See the Workflow Trace Archive - * (https://wta.atlarge-research.com/) for more information about the format. - * - * @param path The path to the trace. - */ -public class WtfTraceReader(path: String) : TraceReader<Job> { - /** - * The internal iterator to use for this reader. - */ - private val iterator: Iterator<TraceEntry<Job>> - - /** - * Initialize the reader. - */ - init { - val workflows = mutableMapOf<Long, Job>() - val starts = mutableMapOf<Long, Long>() - val tasks = mutableMapOf<Long, Task>() - val taskDependencies = mutableMapOf<Task, List<Long>>() - - @Suppress("DEPRECATION") - val reader = AvroParquetReader.builder<GenericRecord>(Path(path, "tasks/schema-1.0")).build() - - while (true) { - val nextRecord = reader.read() ?: break - - val workflowId = nextRecord.get("workflow_id") as Long - val taskId = nextRecord.get("id") as Long - val submitTime = nextRecord.get("ts_submit") as Long - val runtime = nextRecord.get("runtime") as Long - val cores = (nextRecord.get("resource_amount_requested") as Double).toInt() - @Suppress("UNCHECKED_CAST") - val dependencies = (nextRecord.get("parents") as ArrayList<GenericRecord>).map { - it.get("item") as Long - } - - val flops: Long = 4100 * (runtime / 1000) * cores - - val workflow = workflows.getOrPut(workflowId) { - Job(UUID(0L, workflowId), "<unnamed>", HashSet()) - } - val workload = SimFlopsWorkload(flops) - val task = Task( - UUID(0L, taskId), - "<unnamed>", - HashSet(), - mapOf( - "workload" to workload, - WORKFLOW_TASK_CORES to cores, - WORKFLOW_TASK_DEADLINE to runtime - ) - ) - - starts.merge(workflowId, submitTime, ::min) - (workflow.tasks as MutableSet<Task>).add(task) - tasks[taskId] = task - taskDependencies[task] = dependencies - } - - // Fix dependencies and dependents for all tasks - taskDependencies.forEach { (task, dependencies) -> - (task.dependencies as MutableSet<Task>).addAll( - dependencies.map { taskId -> - tasks[taskId] ?: throw IllegalArgumentException("Dependency task with id $taskId not found") - } - ) - } - - // Create the entry iterator - iterator = workflows.map { (id, job) -> TraceEntry(job.uid, job.name, starts.getValue(id), job, job.metadata) } - .sortedBy { it.start } - .iterator() - } - - override fun hasNext(): Boolean = iterator.hasNext() - - override fun next(): TraceEntry<Job> = iterator.next() - - override fun close() {} -} diff --git a/opendc-format/src/test/kotlin/org/opendc/format/trace/swf/SwfTraceReaderTest.kt b/opendc-format/src/test/kotlin/org/opendc/format/trace/swf/SwfTraceReaderTest.kt deleted file mode 100644 index e0e049cf..00000000 --- a/opendc-format/src/test/kotlin/org/opendc/format/trace/swf/SwfTraceReaderTest.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2020 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.format.trace.swf - -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test -import org.opendc.simulator.compute.workload.SimTraceWorkload -import java.io.File - -class SwfTraceReaderTest { - @Test - internal fun testParseSwf() { - val reader = SwfTraceReader(File(SwfTraceReaderTest::class.java.getResource("/swf_trace.txt").toURI())) - var entry = reader.next() - assertEquals(0, entry.start) - // 1961 slices for waiting, 3 full and 1 partial running slices - assertEquals(1965, (entry.workload as SimTraceWorkload).trace.toList().size) - - entry = reader.next() - assertEquals(164472, entry.start) - // 1188 slices for waiting, 0 full and 1 partial running slices - assertEquals(1189, (entry.workload as SimTraceWorkload).trace.toList().size) - assertEquals(0.25, (entry.workload as SimTraceWorkload).trace.toList().last().usage) - } -} diff --git a/opendc-format/src/test/kotlin/org/opendc/format/trace/wtf/WtfTraceReaderTest.kt b/opendc-format/src/test/kotlin/org/opendc/format/trace/wtf/WtfTraceReaderTest.kt deleted file mode 100644 index bcfa7553..00000000 --- a/opendc-format/src/test/kotlin/org/opendc/format/trace/wtf/WtfTraceReaderTest.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2020 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.format.trace.wtf - -import org.junit.jupiter.api.Assertions.* -import org.junit.jupiter.api.Test - -/** - * Test suite for the [WtfTraceReader] class. - */ -class WtfTraceReaderTest { - /** - * Smoke test for parsing WTF traces. - */ - @Test - fun testParseWtf() { - val reader = WtfTraceReader("src/test/resources/wtf-trace") - var entry = reader.next() - assertEquals(0, entry.start) - assertEquals(23, entry.workload.tasks.size) - - entry = reader.next() - assertEquals(333387, entry.start) - assertEquals(23, entry.workload.tasks.size) - } -} diff --git a/opendc-serverless/build.gradle.kts b/opendc-serverless/build.gradle.kts deleted file mode 100644 index a458c809..00000000 --- a/opendc-serverless/build.gradle.kts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -description = "Serverless platform for OpenDC" diff --git a/opendc-serverless/opendc-serverless-api/build.gradle.kts b/opendc-serverless/opendc-serverless-api/build.gradle.kts deleted file mode 100644 index 0d9a8036..00000000 --- a/opendc-serverless/opendc-serverless-api/build.gradle.kts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -description = "Serverless API for OpenDC" - -/* Build configuration */ -plugins { - `kotlin-library-conventions` -} - -dependencies { - api(platform(projects.opendcPlatform)) -} diff --git a/opendc-serverless/opendc-serverless-api/src/main/kotlin/org/opendc/serverless/api/ServerlessClient.kt b/opendc-serverless/opendc-serverless-api/src/main/kotlin/org/opendc/serverless/api/ServerlessClient.kt deleted file mode 100644 index b66369ec..00000000 --- a/opendc-serverless/opendc-serverless-api/src/main/kotlin/org/opendc/serverless/api/ServerlessClient.kt +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.serverless.api - -import java.util.* - -/** - * Client interface to the OpenDC Serverless service. - */ -public interface ServerlessClient : AutoCloseable { - /** - * Obtain the list of [ServerlessFunction]s accessible by the requesting user. - */ - public suspend fun queryFunctions(): List<ServerlessFunction> - - /** - * Obtain a [ServerlessFunction] by its unique identifier. - * - * @param id The identifier of the flavor. - */ - public suspend fun findFunction(id: UUID): ServerlessFunction? - - /** - * Obtain a [ServerlessFunction] by its name. - * - * @param name The name of the function. - */ - public suspend fun findFunction(name: String): ServerlessFunction? - - /** - * Create a new serverless function. - * - * @param name The name of the function. - * @param memorySize The memory allocated for the function in MB. - * @param labels The labels associated with the function. - * @param meta The metadata associated with the function. - */ - public suspend fun newFunction( - name: String, - memorySize: Long, - labels: Map<String, String> = emptyMap(), - meta: Map<String, Any> = emptyMap() - ): ServerlessFunction - - /** - * Invoke the function with the specified [name]. - */ - public suspend fun invoke(name: String) - - /** - * Release the resources associated with this client, preventing any further API calls. - */ - public override fun close() -} diff --git a/opendc-serverless/opendc-serverless-api/src/main/kotlin/org/opendc/serverless/api/ServerlessFunction.kt b/opendc-serverless/opendc-serverless-api/src/main/kotlin/org/opendc/serverless/api/ServerlessFunction.kt deleted file mode 100644 index f1360966..00000000 --- a/opendc-serverless/opendc-serverless-api/src/main/kotlin/org/opendc/serverless/api/ServerlessFunction.kt +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.serverless.api - -import java.util.UUID - -/** - * A serverless function instance. - */ -public interface ServerlessFunction { - /** - * The unique identifier of the function. - */ - public val uid: UUID - - /** - * The name of the function. - */ - public val name: String - - /** - * The amount of memory allocated for this function in MB. - */ - public val memorySize: Long - - /** - * The identifying labels attached to the resource. - */ - public val labels: Map<String, String> - - /** - * The non-identifying metadata attached to the resource. - */ - public val meta: Map<String, Any> - - /** - * Invoke the serverless function. - */ - public suspend operator fun invoke() - - /** - * Request the function to be deleted. - */ - public suspend fun delete() - - /** - * Refresh the local state of this object. - */ - public suspend fun refresh() -} diff --git a/opendc-serverless/opendc-serverless-service/build.gradle.kts b/opendc-serverless/opendc-serverless-service/build.gradle.kts deleted file mode 100644 index bce72c72..00000000 --- a/opendc-serverless/opendc-serverless-service/build.gradle.kts +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -description = "Serverless service for OpenDC" - -/* Build configuration */ -plugins { - `kotlin-library-conventions` - `testing-conventions` - `jacoco-conventions` -} - -dependencies { - api(platform(projects.opendcPlatform)) - api(projects.opendcServerless.opendcServerlessApi) - api(projects.opendcTelemetry.opendcTelemetryApi) - implementation(projects.opendcUtils) - implementation(libs.kotlin.logging) - - testImplementation(projects.opendcSimulator.opendcSimulatorCore) - testRuntimeOnly(libs.log4j.slf4j) -} diff --git a/opendc-serverless/opendc-serverless-service/src/main/kotlin/org/opendc/serverless/service/FunctionObject.kt b/opendc-serverless/opendc-serverless-service/src/main/kotlin/org/opendc/serverless/service/FunctionObject.kt deleted file mode 100644 index c12bbfe2..00000000 --- a/opendc-serverless/opendc-serverless-service/src/main/kotlin/org/opendc/serverless/service/FunctionObject.kt +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.serverless.service - -import io.opentelemetry.api.metrics.BoundLongCounter -import io.opentelemetry.api.metrics.BoundLongUpDownCounter -import io.opentelemetry.api.metrics.BoundLongValueRecorder -import io.opentelemetry.api.metrics.Meter -import io.opentelemetry.api.metrics.common.Labels -import org.opendc.serverless.service.deployer.FunctionInstance -import java.util.* - -/** - * An [FunctionObject] represents the service's view of a serverless function. - */ -public class FunctionObject( - meter: Meter, - public val uid: UUID, - name: String, - allocatedMemory: Long, - labels: Map<String, String>, - meta: Map<String, Any> -) : AutoCloseable { - /** - * The total amount of function invocations received by the function. - */ - public val invocations: BoundLongCounter = meter.longCounterBuilder("function.invocations.total") - .setDescription("Number of function invocations") - .setUnit("1") - .build() - .bind(Labels.of("function", uid.toString())) - - /** - * The amount of function invocations that could be handled directly. - */ - public val timelyInvocations: BoundLongCounter = meter.longCounterBuilder("function.invocations.warm") - .setDescription("Number of function invocations handled directly") - .setUnit("1") - .build() - .bind(Labels.of("function", uid.toString())) - - /** - * The amount of function invocations that were delayed due to function deployment. - */ - public val delayedInvocations: BoundLongCounter = meter.longCounterBuilder("function.invocations.cold") - .setDescription("Number of function invocations that are delayed") - .setUnit("1") - .build() - .bind(Labels.of("function", uid.toString())) - - /** - * The amount of function invocations that failed. - */ - public val failedInvocations: BoundLongCounter = meter.longCounterBuilder("function.invocations.failed") - .setDescription("Number of function invocations that failed") - .setUnit("1") - .build() - .bind(Labels.of("function", uid.toString())) - - /** - * The amount of instances for this function. - */ - public val activeInstances: BoundLongUpDownCounter = meter.longUpDownCounterBuilder("function.instances.active") - .setDescription("Number of active function instances") - .setUnit("1") - .build() - .bind(Labels.of("function", uid.toString())) - - /** - * The amount of idle instances for this function. - */ - public val idleInstances: BoundLongUpDownCounter = meter.longUpDownCounterBuilder("function.instances.idle") - .setDescription("Number of idle function instances") - .setUnit("1") - .build() - .bind(Labels.of("function", uid.toString())) - - /** - * The time that the function waited. - */ - public val waitTime: BoundLongValueRecorder = meter.longValueRecorderBuilder("function.time.wait") - .setDescription("Time the function has to wait before being started") - .setUnit("ms") - .build() - .bind(Labels.of("function", uid.toString())) - - /** - * The time that the function was running. - */ - public val activeTime: BoundLongValueRecorder = meter.longValueRecorderBuilder("function.time.active") - .setDescription("Time the function was running") - .setUnit("ms") - .build() - .bind(Labels.of("function", uid.toString())) - - /** - * The instances associated with this function. - */ - public val instances: MutableList<FunctionInstance> = mutableListOf() - - public var name: String = name - private set - - public var memorySize: Long = allocatedMemory - private set - - public val labels: MutableMap<String, String> = labels.toMutableMap() - - public val meta: MutableMap<String, Any> = meta.toMutableMap() - - override fun close() { - instances.forEach(FunctionInstance::close) - instances.clear() - } - - override fun equals(other: Any?): Boolean = other is FunctionObject && uid == other.uid - - override fun hashCode(): Int = uid.hashCode() -} diff --git a/opendc-serverless/opendc-serverless-service/src/main/kotlin/org/opendc/serverless/service/ServerlessService.kt b/opendc-serverless/opendc-serverless-service/src/main/kotlin/org/opendc/serverless/service/ServerlessService.kt deleted file mode 100644 index e2f135ae..00000000 --- a/opendc-serverless/opendc-serverless-service/src/main/kotlin/org/opendc/serverless/service/ServerlessService.kt +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.serverless.service - -import io.opentelemetry.api.metrics.Meter -import org.opendc.serverless.api.ServerlessClient -import org.opendc.serverless.service.autoscaler.FunctionTerminationPolicy -import org.opendc.serverless.service.deployer.FunctionDeployer -import org.opendc.serverless.service.internal.ServerlessServiceImpl -import org.opendc.serverless.service.router.RoutingPolicy -import java.time.Clock -import kotlin.coroutines.CoroutineContext - -/** - * The [ServerlessService] hosts the API implementation of the OpenDC Serverless service. - */ -public interface ServerlessService : AutoCloseable { - /** - * Create a new [ServerlessClient] to control the compute service. - */ - public fun newClient(): ServerlessClient - - /** - * Terminate the lifecycle of the serverless service, stopping all running function instances. - */ - public override fun close() - - public companion object { - /** - * Construct a new [ServerlessService] implementation. - * - * @param context The [CoroutineContext] to use in the service. - * @param clock The clock instance to use. - * @param meter The meter to report metrics to. - * @param deployer the [FunctionDeployer] to use for deploying function instances. - * @param routingPolicy The policy to route function invocations. - * @param terminationPolicy The policy for terminating function instances. - */ - public operator fun invoke( - context: CoroutineContext, - clock: Clock, - meter: Meter, - deployer: FunctionDeployer, - routingPolicy: RoutingPolicy, - terminationPolicy: FunctionTerminationPolicy, - ): ServerlessService { - return ServerlessServiceImpl(context, clock, meter, deployer, routingPolicy, terminationPolicy) - } - } -} diff --git a/opendc-serverless/opendc-serverless-service/src/main/kotlin/org/opendc/serverless/service/autoscaler/FunctionTerminationPolicy.kt b/opendc-serverless/opendc-serverless-service/src/main/kotlin/org/opendc/serverless/service/autoscaler/FunctionTerminationPolicy.kt deleted file mode 100644 index 25df10a6..00000000 --- a/opendc-serverless/opendc-serverless-service/src/main/kotlin/org/opendc/serverless/service/autoscaler/FunctionTerminationPolicy.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.serverless.service.autoscaler - -import org.opendc.serverless.service.deployer.FunctionInstance -import org.opendc.serverless.service.deployer.FunctionInstanceListener - -/** - * A management policy that is responsible for downscaling the active function instances for a function. - */ -public interface FunctionTerminationPolicy : FunctionInstanceListener { - /** - * Enqueue the specified [instance] to be scheduled for termination a - */ - public fun enqueue(instance: FunctionInstance) -} diff --git a/opendc-serverless/opendc-serverless-service/src/main/kotlin/org/opendc/serverless/service/autoscaler/FunctionTerminationPolicyFixed.kt b/opendc-serverless/opendc-serverless-service/src/main/kotlin/org/opendc/serverless/service/autoscaler/FunctionTerminationPolicyFixed.kt deleted file mode 100644 index 26b99f52..00000000 --- a/opendc-serverless/opendc-serverless-service/src/main/kotlin/org/opendc/serverless/service/autoscaler/FunctionTerminationPolicyFixed.kt +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.serverless.service.autoscaler - -import org.opendc.serverless.service.deployer.FunctionInstance -import org.opendc.serverless.service.deployer.FunctionInstanceState -import org.opendc.utils.TimerScheduler -import java.time.Clock -import kotlin.coroutines.CoroutineContext - -/** - * A [FunctionTerminationPolicy] that terminates idle function instances after a fixed keep-alive time. - * - * @param timeout The idle timeout after which the function instance is terminated. - */ -public class FunctionTerminationPolicyFixed( - context: CoroutineContext, - clock: Clock, - public val timeout: Long -) : FunctionTerminationPolicy { - /** - * The [TimerScheduler] used to schedule the function terminations. - */ - private val scheduler = TimerScheduler<FunctionInstance>(context, clock) - - override fun enqueue(instance: FunctionInstance) { - // Cancel the existing timeout timer - scheduler.cancel(instance) - } - - override fun onStateChanged(instance: FunctionInstance, newState: FunctionInstanceState) { - when (newState) { - FunctionInstanceState.Active -> scheduler.cancel(instance) - FunctionInstanceState.Idle -> schedule(instance) - else -> {} - } - } - - /** - * Schedule termination for the specified [instance]. - */ - private fun schedule(instance: FunctionInstance) { - scheduler.startSingleTimer(instance, delay = timeout) { instance.close() } - } -} diff --git a/opendc-serverless/opendc-serverless-service/src/main/kotlin/org/opendc/serverless/service/autoscaler/FunctionTerminationPolicyNull.kt b/opendc-serverless/opendc-serverless-service/src/main/kotlin/org/opendc/serverless/service/autoscaler/FunctionTerminationPolicyNull.kt deleted file mode 100644 index f2d8da59..00000000 --- a/opendc-serverless/opendc-serverless-service/src/main/kotlin/org/opendc/serverless/service/autoscaler/FunctionTerminationPolicyNull.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.serverless.service.autoscaler - -import org.opendc.serverless.service.deployer.FunctionInstance - -/** - * A [FunctionTerminationPolicy] that never terminates function instances. - */ -public class FunctionTerminationPolicyNull : FunctionTerminationPolicy { - override fun enqueue(instance: FunctionInstance) { - // No-op - } -} diff --git a/opendc-serverless/opendc-serverless-service/src/main/kotlin/org/opendc/serverless/service/deployer/FunctionDeployer.kt b/opendc-serverless/opendc-serverless-service/src/main/kotlin/org/opendc/serverless/service/deployer/FunctionDeployer.kt deleted file mode 100644 index 5355b659..00000000 --- a/opendc-serverless/opendc-serverless-service/src/main/kotlin/org/opendc/serverless/service/deployer/FunctionDeployer.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.serverless.service.deployer - -import org.opendc.serverless.service.FunctionObject - -/** - * A [FunctionDeployer] is responsible for ensuring that an instance of an arbitrary function, a [FunctionInstance], - * is deployed. - * - * The function deployer should combines the configuration stored in the function registry, the parameters supplied by - * the requester, and other factors into a decision of how the function should be deployed, including how many and - * what kind of resources it should receive. - * - * Though it decides how the function instance should be deployed, the deployment of the function instance itself is - * delegated to the Resource Orchestration Layer. - */ -public interface FunctionDeployer { - /** - * Deploy the specified [function]. - */ - public fun deploy(function: FunctionObject, listener: FunctionInstanceListener): FunctionInstance -} diff --git a/opendc-serverless/opendc-serverless-service/src/main/kotlin/org/opendc/serverless/service/deployer/FunctionInstance.kt b/opendc-serverless/opendc-serverless-service/src/main/kotlin/org/opendc/serverless/service/deployer/FunctionInstance.kt deleted file mode 100644 index d60648ea..00000000 --- a/opendc-serverless/opendc-serverless-service/src/main/kotlin/org/opendc/serverless/service/deployer/FunctionInstance.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.serverless.service.deployer - -import org.opendc.serverless.service.FunctionObject - -/** - * A [FunctionInstance] is a a self-contained worker—typically a container—capable of handling function executions. - * - * Multiple, concurrent function instances can exists for a single function, for scalability purposes. - */ -public interface FunctionInstance : AutoCloseable { - /** - * The state of the instance. - */ - public val state: FunctionInstanceState - - /** - * The [FunctionObject] that is represented by this instance. - */ - public val function: FunctionObject - - /** - * Invoke the function instance. - * - * This method will suspend execution util the function instance has returned. - */ - public suspend fun invoke() - - /** - * Indicate to the resource manager that the instance is not needed anymore and may be cleaned up by the resource - * manager. - */ - public override fun close() -} diff --git a/opendc-serverless/opendc-serverless-service/src/main/kotlin/org/opendc/serverless/service/deployer/FunctionInstanceListener.kt b/opendc-serverless/opendc-serverless-service/src/main/kotlin/org/opendc/serverless/service/deployer/FunctionInstanceListener.kt deleted file mode 100644 index 27803a63..00000000 --- a/opendc-serverless/opendc-serverless-service/src/main/kotlin/org/opendc/serverless/service/deployer/FunctionInstanceListener.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.serverless.service.deployer - -/** - * Listener interface for events originating from a [FunctionInstance]. - */ -public interface FunctionInstanceListener { - /** - * This method is invoked when the state of a [FunctionInstance] has changed. - */ - public fun onStateChanged(instance: FunctionInstance, newState: FunctionInstanceState) {} -} diff --git a/opendc-serverless/opendc-serverless-service/src/main/kotlin/org/opendc/serverless/service/deployer/FunctionInstanceState.kt b/opendc-serverless/opendc-serverless-service/src/main/kotlin/org/opendc/serverless/service/deployer/FunctionInstanceState.kt deleted file mode 100644 index 4fc4a83f..00000000 --- a/opendc-serverless/opendc-serverless-service/src/main/kotlin/org/opendc/serverless/service/deployer/FunctionInstanceState.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.serverless.service.deployer - -/** - * This enumeration describes the states of a [FunctionInstance]. - */ -public enum class FunctionInstanceState { - /** - * The function instance is currently being provisioned. - */ - Provisioning, - - /** - * The function instance is idle and ready to execute. - */ - Idle, - - /** - * The function instance is executing. - */ - Active, - - /** - * The function instance is released and cannot be used anymore. - */ - Deleted -} diff --git a/opendc-serverless/opendc-serverless-service/src/main/kotlin/org/opendc/serverless/service/internal/ServerlessFunctionImpl.kt b/opendc-serverless/opendc-serverless-service/src/main/kotlin/org/opendc/serverless/service/internal/ServerlessFunctionImpl.kt deleted file mode 100644 index 80b50e77..00000000 --- a/opendc-serverless/opendc-serverless-service/src/main/kotlin/org/opendc/serverless/service/internal/ServerlessFunctionImpl.kt +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.serverless.service.internal - -import org.opendc.serverless.api.ServerlessFunction -import org.opendc.serverless.service.FunctionObject -import java.util.* - -/** - * A [ServerlessFunction] implementation that is passed to clients. - */ -internal class ServerlessFunctionImpl( - private val service: ServerlessServiceImpl, - private val state: FunctionObject -) : ServerlessFunction { - override val uid: UUID = state.uid - - override var name: String = state.name - private set - - override var memorySize: Long = state.memorySize - private set - - override var labels: Map<String, String> = state.labels.toMap() - private set - - override var meta: Map<String, Any> = state.meta.toMap() - private set - - override suspend fun delete() { - service.delete(state) - } - - override suspend fun invoke() { - service.invoke(state) - } - - override suspend fun refresh() { - name = state.name - memorySize = state.memorySize - labels = state.labels - meta = state.meta - } - - override fun equals(other: Any?): Boolean = other is ServerlessFunctionImpl && uid == other.uid - - override fun hashCode(): Int = uid.hashCode() - - override fun toString(): String = "ServerlessFunction[uid=$uid,name=$name]" -} diff --git a/opendc-serverless/opendc-serverless-service/src/main/kotlin/org/opendc/serverless/service/internal/ServerlessServiceImpl.kt b/opendc-serverless/opendc-serverless-service/src/main/kotlin/org/opendc/serverless/service/internal/ServerlessServiceImpl.kt deleted file mode 100644 index 91a59279..00000000 --- a/opendc-serverless/opendc-serverless-service/src/main/kotlin/org/opendc/serverless/service/internal/ServerlessServiceImpl.kt +++ /dev/null @@ -1,304 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.serverless.service.internal - -import io.opentelemetry.api.metrics.Meter -import kotlinx.coroutines.* -import kotlinx.coroutines.intrinsics.startCoroutineCancellable -import mu.KotlinLogging -import org.opendc.serverless.api.ServerlessClient -import org.opendc.serverless.api.ServerlessFunction -import org.opendc.serverless.service.FunctionObject -import org.opendc.serverless.service.ServerlessService -import org.opendc.serverless.service.autoscaler.FunctionTerminationPolicy -import org.opendc.serverless.service.deployer.FunctionDeployer -import org.opendc.serverless.service.deployer.FunctionInstance -import org.opendc.serverless.service.deployer.FunctionInstanceListener -import org.opendc.serverless.service.deployer.FunctionInstanceState -import org.opendc.serverless.service.router.RoutingPolicy -import org.opendc.utils.TimerScheduler -import java.lang.IllegalStateException -import java.time.Clock -import java.util.* -import kotlin.coroutines.Continuation -import kotlin.coroutines.CoroutineContext -import kotlin.coroutines.resumeWithException - -/** - * Implementation of the [ServerlessService] interface. - * - * This component acts as the function router from the SPEC RG Reference Architecture for FaaS and is responsible - * for routing incoming requests or events to the correct [FunctionInstance]. If no [FunctionInstance] is available, - * this component queues the events to await the deployment of new instances. - */ -internal class ServerlessServiceImpl( - context: CoroutineContext, - private val clock: Clock, - private val meter: Meter, - private val deployer: FunctionDeployer, - private val routingPolicy: RoutingPolicy, - private val terminationPolicy: FunctionTerminationPolicy -) : ServerlessService, FunctionInstanceListener { - /** - * The [CoroutineScope] of the service bounded by the lifecycle of the service. - */ - private val scope = CoroutineScope(context + Job()) - - /** - * The logger instance of this server. - */ - private val logger = KotlinLogging.logger {} - - /** - * The [TimerScheduler] to use for scheduling the scheduler cycles. - */ - private val scheduler: TimerScheduler<Unit> = TimerScheduler(scope.coroutineContext, clock) - - /** - * The [Random] instance used to generate unique identifiers for the objects. - */ - private val random = Random(0) - - /** - * The registered functions for this service. - */ - private val functions = mutableMapOf<UUID, FunctionObject>() - private val functionsByName = mutableMapOf<String, FunctionObject>() - - /** - * The queue of invocation requests. - */ - private val queue = ArrayDeque<InvocationRequest>() - - /** - * The total amount of function invocations received by the service. - */ - private val _invocations = meter.longCounterBuilder("service.invocations.total") - .setDescription("Number of function invocations") - .setUnit("1") - .build() - - /** - * The amount of function invocations that could be handled directly. - */ - private val _timelyInvocations = meter.longCounterBuilder("service.invocations.warm") - .setDescription("Number of function invocations handled directly") - .setUnit("1") - .build() - - /** - * The amount of function invocations that were delayed due to function deployment. - */ - private val _delayedInvocations = meter.longCounterBuilder("service.invocations.cold") - .setDescription("Number of function invocations that are delayed") - .setUnit("1") - .build() - - override fun newClient(): ServerlessClient { - return object : ServerlessClient { - private var isClosed: Boolean = false - - /** - * Exposes a [FunctionObject] to a client-exposed [ServerlessFunction] instance. - */ - private fun FunctionObject.asClientFunction(): ServerlessFunction { - return ServerlessFunctionImpl(this@ServerlessServiceImpl, this) - } - - override suspend fun queryFunctions(): List<ServerlessFunction> { - check(!isClosed) { "Client is already closed" } - - return functions.values.map { it.asClientFunction() } - } - - override suspend fun findFunction(id: UUID): ServerlessFunction? { - check(!isClosed) { "Client is already closed" } - - return functions[id]?.asClientFunction() - } - - override suspend fun findFunction(name: String): ServerlessFunction? { - check(!isClosed) { "Client is already closed" } - - return functionsByName[name]?.asClientFunction() - } - - override suspend fun newFunction( - name: String, - memorySize: Long, - labels: Map<String, String>, - meta: Map<String, Any> - ): ServerlessFunction { - check(!isClosed) { "Client is already closed" } - require(name !in functionsByName) { "Function with same name exists" } - - val uid = UUID(clock.millis(), random.nextLong()) - val function = FunctionObject( - meter, - uid, - name, - memorySize, - labels, - meta - ) - - functionsByName[name] = function - functions[uid] = function - - return function.asClientFunction() - } - - override suspend fun invoke(name: String) { - check(!isClosed) { "Client is already closed" } - - val func = requireNotNull(functionsByName[name]) { "Unknown function" } - this@ServerlessServiceImpl.invoke(func) - } - - override fun close() { - isClosed = true - } - } - } - - /** - * Indicate that a new scheduling cycle is needed due to a change to the service's state. - */ - private fun schedule() { - // Bail out in case we have already requested a new cycle or the queue is empty. - if (scheduler.isTimerActive(Unit) || queue.isEmpty()) { - return - } - - val quantum = 100 - - // We assume that the provisioner runs at a fixed slot every time quantum (e.g t=0, t=60, t=120). - // This is important because the slices of the VMs need to be aligned. - // We calculate here the delay until the next scheduling slot. - val delay = quantum - (clock.millis() % quantum) - - scheduler.startSingleTimer(Unit, delay, ::doSchedule) - } - - /** - * Run a single scheduling iteration. - */ - @OptIn(InternalCoroutinesApi::class) - private fun doSchedule() { - try { - while (queue.isNotEmpty()) { - val (submitTime, function, cont) = queue.poll() - - val instances = function.instances - - // Check if there exists an instance of the function - val activeInstance = if (instances.isNotEmpty()) { - routingPolicy.select(instances, function) - } else { - null - } - - val instance = if (activeInstance != null) { - _timelyInvocations.add(1) - function.timelyInvocations.add(1) - - activeInstance - } else { - val instance = deployer.deploy(function, this) - instances.add(instance) - terminationPolicy.enqueue(instance) - - function.idleInstances.add(1) - - _delayedInvocations.add(1) - function.delayedInvocations.add(1) - - instance - } - - suspend { - val start = clock.millis() - function.waitTime.record(start - submitTime) - function.idleInstances.add(-1) - function.activeInstances.add(1) - try { - instance.invoke() - } catch (e: Throwable) { - logger.debug(e) { "Function invocation failed" } - function.failedInvocations.add(1) - } finally { - val end = clock.millis() - function.activeTime.record(end - start) - function.idleInstances.add(1) - function.activeInstances.add(-1) - } - }.startCoroutineCancellable(cont) - } - } catch (cause: Throwable) { - logger.error(cause) { "Exception occurred during scheduling cycle" } - } - } - - suspend fun invoke(function: FunctionObject) { - check(function.uid in functions) { "Function does not exist (anymore)" } - - _invocations.add(1) - function.invocations.add(1) - - return suspendCancellableCoroutine { cont -> - if (!queue.add(InvocationRequest(clock.millis(), function, cont))) { - cont.resumeWithException(IllegalStateException("Failed to enqueue request")) - } else { - schedule() - } - } - } - - fun delete(function: FunctionObject) { - functions.remove(function.uid) - functionsByName.remove(function.name) - } - - override fun close() { - scope.cancel() - - // Stop all function instances - for ((_, function) in functions) { - function.close() - } - } - - override fun onStateChanged(instance: FunctionInstance, newState: FunctionInstanceState) { - terminationPolicy.onStateChanged(instance, newState) - - if (newState == FunctionInstanceState.Deleted) { - val function = instance.function - function.instances.remove(instance) - } - } - - /** - * A request to invoke a function. - */ - private data class InvocationRequest(val timestamp: Long, val function: FunctionObject, val cont: Continuation<Unit>) -} diff --git a/opendc-serverless/opendc-serverless-service/src/main/kotlin/org/opendc/serverless/service/router/RandomRoutingPolicy.kt b/opendc-serverless/opendc-serverless-service/src/main/kotlin/org/opendc/serverless/service/router/RandomRoutingPolicy.kt deleted file mode 100644 index 063fb80a..00000000 --- a/opendc-serverless/opendc-serverless-service/src/main/kotlin/org/opendc/serverless/service/router/RandomRoutingPolicy.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.serverless.service.router - -import org.opendc.serverless.service.FunctionObject -import org.opendc.serverless.service.deployer.FunctionInstance -import kotlin.random.Random - -/** - * A [RoutingPolicy] that selects a random function instance. - */ -public class RandomRoutingPolicy(private val random: Random = Random(0)) : RoutingPolicy { - override fun select(instances: List<FunctionInstance>, function: FunctionObject): FunctionInstance { - return instances.random(random) - } -} diff --git a/opendc-serverless/opendc-serverless-service/src/main/kotlin/org/opendc/serverless/service/router/RoutingPolicy.kt b/opendc-serverless/opendc-serverless-service/src/main/kotlin/org/opendc/serverless/service/router/RoutingPolicy.kt deleted file mode 100644 index d5d1166f..00000000 --- a/opendc-serverless/opendc-serverless-service/src/main/kotlin/org/opendc/serverless/service/router/RoutingPolicy.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.serverless.service.router - -import org.opendc.serverless.service.FunctionObject -import org.opendc.serverless.service.deployer.FunctionInstance - -/** - * A [RoutingPolicy] decides to which [FunctionInstance] a function invocation should be routed. - */ -public interface RoutingPolicy { - /** - * Select the instance to which the request should be routed to. - */ - public fun select(instances: List<FunctionInstance>, function: FunctionObject): FunctionInstance? -} diff --git a/opendc-serverless/opendc-serverless-service/src/test/kotlin/org/opendc/serverless/service/ServerlessServiceTest.kt b/opendc-serverless/opendc-serverless-service/src/test/kotlin/org/opendc/serverless/service/ServerlessServiceTest.kt deleted file mode 100644 index 6b2e8223..00000000 --- a/opendc-serverless/opendc-serverless-service/src/test/kotlin/org/opendc/serverless/service/ServerlessServiceTest.kt +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.serverless.service - -import io.mockk.* -import io.opentelemetry.api.metrics.MeterProvider -import kotlinx.coroutines.ExperimentalCoroutinesApi -import org.junit.jupiter.api.Assertions.* -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertDoesNotThrow -import org.junit.jupiter.api.assertThrows -import org.opendc.serverless.api.ServerlessFunction -import org.opendc.serverless.service.deployer.FunctionDeployer -import org.opendc.serverless.service.deployer.FunctionInstance -import org.opendc.serverless.service.deployer.FunctionInstanceState -import org.opendc.simulator.core.runBlockingSimulation -import java.util.* - -/** - * Test suite for the [ServerlessService] implementation. - */ -@OptIn(ExperimentalCoroutinesApi::class) -internal class ServerlessServiceTest { - - @Test - fun testClientState() = runBlockingSimulation { - val meter = MeterProvider.noop().get("opendc-serverless") - val service = ServerlessService(coroutineContext, clock, meter, mockk(), mockk(), mockk()) - - val client = assertDoesNotThrow { service.newClient() } - assertDoesNotThrow { client.close() } - - assertThrows<IllegalStateException> { client.queryFunctions() } - assertThrows<IllegalStateException> { client.newFunction("test", 128) } - assertThrows<IllegalStateException> { client.invoke("test") } - assertThrows<IllegalStateException> { client.findFunction(UUID.randomUUID()) } - assertThrows<IllegalStateException> { client.findFunction("name") } - } - - @Test - fun testClientInvokeUnknown() = runBlockingSimulation { - val meter = MeterProvider.noop().get("opendc-serverless") - val service = ServerlessService(coroutineContext, clock, meter, mockk(), mockk(), mockk()) - - val client = service.newClient() - - assertThrows<IllegalArgumentException> { client.invoke("test") } - } - - @Test - fun testClientFunctionCreation() = runBlockingSimulation { - val meter = MeterProvider.noop().get("opendc-serverless") - val service = ServerlessService(coroutineContext, clock, meter, mockk(), mockk(), mockk()) - - val client = service.newClient() - - val function = client.newFunction("test", 128) - - assertEquals("test", function.name) - } - - @Test - fun testClientFunctionQuery() = runBlockingSimulation { - val meter = MeterProvider.noop().get("opendc-serverless") - val service = ServerlessService(coroutineContext, clock, meter, mockk(), mockk(), mockk()) - - val client = service.newClient() - - assertEquals(emptyList<ServerlessFunction>(), client.queryFunctions()) - - val function = client.newFunction("test", 128) - - assertEquals(listOf(function), client.queryFunctions()) - } - - @Test - fun testClientFunctionFindById() = runBlockingSimulation { - val meter = MeterProvider.noop().get("opendc-serverless") - val service = ServerlessService(coroutineContext, clock, meter, mockk(), mockk(), mockk()) - - val client = service.newClient() - - assertEquals(emptyList<ServerlessFunction>(), client.queryFunctions()) - - val function = client.newFunction("test", 128) - - assertNotNull(client.findFunction(function.uid)) - } - - @Test - fun testClientFunctionFindByName() = runBlockingSimulation { - val meter = MeterProvider.noop().get("opendc-serverless") - val service = ServerlessService(coroutineContext, clock, meter, mockk(), mockk(), mockk()) - - val client = service.newClient() - - assertEquals(emptyList<ServerlessFunction>(), client.queryFunctions()) - - val function = client.newFunction("test", 128) - - assertNotNull(client.findFunction(function.name)) - } - - @Test - fun testClientFunctionDuplicateName() = runBlockingSimulation { - val meter = MeterProvider.noop().get("opendc-serverless") - val service = ServerlessService(coroutineContext, clock, meter, mockk(), mockk(), mockk()) - - val client = service.newClient() - - client.newFunction("test", 128) - - assertThrows<IllegalArgumentException> { client.newFunction("test", 128) } - } - - @Test - fun testClientFunctionDelete() = runBlockingSimulation { - val meter = MeterProvider.noop().get("opendc-serverless") - val service = ServerlessService(coroutineContext, clock, meter, mockk(), mockk(), mockk()) - - val client = service.newClient() - val function = client.newFunction("test", 128) - assertNotNull(client.findFunction(function.uid)) - function.delete() - assertNull(client.findFunction(function.uid)) - - // Delete should be idempotent - function.delete() - } - - @Test - fun testClientFunctionCannotInvokeDeleted() = runBlockingSimulation { - val meter = MeterProvider.noop().get("opendc-serverless") - val service = ServerlessService(coroutineContext, clock, meter, mockk(), mockk(), mockk()) - - val client = service.newClient() - val function = client.newFunction("test", 128) - assertNotNull(client.findFunction(function.uid)) - function.delete() - - assertThrows<IllegalStateException> { function.invoke() } - } - - @Test - fun testClientFunctionInvoke() = runBlockingSimulation { - val meter = MeterProvider.noop().get("opendc-serverless") - val deployer = mockk<FunctionDeployer>() - val service = ServerlessService(coroutineContext, clock, meter, deployer, mockk(), mockk(relaxUnitFun = true)) - - every { deployer.deploy(any(), any()) } answers { - object : FunctionInstance { - override val state: FunctionInstanceState = FunctionInstanceState.Idle - override val function: FunctionObject = it.invocation.args[0] as FunctionObject - - override suspend fun invoke() {} - - override fun close() {} - } - } - - val client = service.newClient() - val function = client.newFunction("test", 128) - - function.invoke() - } -} diff --git a/opendc-serverless/opendc-serverless-simulator/build.gradle.kts b/opendc-serverless/opendc-serverless-simulator/build.gradle.kts deleted file mode 100644 index 5e2c522d..00000000 --- a/opendc-serverless/opendc-serverless-simulator/build.gradle.kts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -description = "Simulator for OpenDC Serverles" - -/* Build configuration */ -plugins { - `kotlin-library-conventions` - `testing-conventions` - `jacoco-conventions` -} - -dependencies { - api(platform(projects.opendcPlatform)) - api(projects.opendcServerless.opendcServerlessService) - api(projects.opendcSimulator.opendcSimulatorCompute) - - testImplementation(projects.opendcSimulator.opendcSimulatorCore) - testRuntimeOnly(libs.slf4j.simple) -} diff --git a/opendc-serverless/opendc-serverless-simulator/src/main/kotlin/org/opendc/serverless/simulator/SimFunctionDeployer.kt b/opendc-serverless/opendc-serverless-simulator/src/main/kotlin/org/opendc/serverless/simulator/SimFunctionDeployer.kt deleted file mode 100644 index 0605eaac..00000000 --- a/opendc-serverless/opendc-serverless-simulator/src/main/kotlin/org/opendc/serverless/simulator/SimFunctionDeployer.kt +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.serverless.simulator - -import kotlinx.coroutines.* -import kotlinx.coroutines.channels.Channel -import org.opendc.serverless.service.FunctionObject -import org.opendc.serverless.service.deployer.FunctionDeployer -import org.opendc.serverless.service.deployer.FunctionInstance -import org.opendc.serverless.service.deployer.FunctionInstanceListener -import org.opendc.serverless.service.deployer.FunctionInstanceState -import org.opendc.serverless.simulator.delay.DelayInjector -import org.opendc.serverless.simulator.workload.SimServerlessWorkloadMapper -import org.opendc.simulator.compute.SimBareMetalMachine -import org.opendc.simulator.compute.SimMachine -import org.opendc.simulator.compute.SimMachineModel -import org.opendc.simulator.compute.cpufreq.PerformanceScalingGovernor -import org.opendc.simulator.compute.cpufreq.SimpleScalingDriver -import org.opendc.simulator.compute.power.ConstantPowerModel -import java.time.Clock -import java.util.ArrayDeque -import kotlin.coroutines.Continuation -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException - -/** - * A [FunctionDeployer] that uses that simulates the [FunctionInstance]s. - */ -public class SimFunctionDeployer( - private val clock: Clock, - private val scope: CoroutineScope, - private val model: SimMachineModel, - private val delayInjector: DelayInjector, - private val mapper: SimServerlessWorkloadMapper -) : FunctionDeployer { - - override fun deploy(function: FunctionObject, listener: FunctionInstanceListener): Instance { - val instance = Instance(function, listener) - instance.start() - return instance - } - - /** - * A simulated [FunctionInstance]. - */ - public inner class Instance(override val function: FunctionObject, private val listener: FunctionInstanceListener) : FunctionInstance { - /** - * The workload associated with this instance. - */ - private val workload = mapper.createWorkload(function) - - /** - * The machine that will execute the workloads. - */ - public val machine: SimMachine = SimBareMetalMachine(scope.coroutineContext, clock, model, PerformanceScalingGovernor(), SimpleScalingDriver(ConstantPowerModel(0.0))) - - /** - * The job associated with the lifecycle of the instance. - */ - private var job: Job? = null - - /** - * The invocation request queue. - */ - private val queue = ArrayDeque<InvocationRequest>() - - /** - * A channel used to signal that new invocations have been enqueued. - */ - private val chan = Channel<Unit>(Channel.RENDEZVOUS) - - override var state: FunctionInstanceState = FunctionInstanceState.Provisioning - set(value) { - if (field != value) { - listener.onStateChanged(this, value) - } - - field = value - } - - override suspend fun invoke() { - check(state != FunctionInstanceState.Deleted) { "Function instance has been released" } - return suspendCancellableCoroutine { cont -> - queue.add(InvocationRequest(cont)) - chan.offer(Unit) - } - } - - override fun close() { - state = FunctionInstanceState.Deleted - stop() - machine.close() - } - - override fun toString(): String = "FunctionInstance[state=$state]" - - /** - * Start the function instance. - */ - @OptIn(InternalCoroutinesApi::class) - internal fun start() { - check(state == FunctionInstanceState.Provisioning) { "Invalid state of function instance" } - job = scope.launch { - delay(delayInjector.getColdStartDelay(this@Instance)) - - launch { - try { - machine.run(workload) - } finally { - state = FunctionInstanceState.Deleted - } - } - - while (isActive) { - if (queue.isEmpty()) { - chan.receive() - } - - state = FunctionInstanceState.Active - while (queue.isNotEmpty()) { - val request = queue.poll() - try { - workload.invoke() - request.cont.resume(Unit) - } catch (cause: CancellationException) { - request.cont.resumeWithException(cause) - throw cause - } catch (cause: Throwable) { - request.cont.resumeWithException(cause) - } - } - state = FunctionInstanceState.Idle - } - } - } - - /** - * Stop the function instance. - */ - private fun stop() { - val job = job - - if (job != null) { - this.job = null - job.cancel() - } - } - } - - /** - * A function invocation request. - */ - private data class InvocationRequest(val cont: Continuation<Unit>) -} diff --git a/opendc-serverless/opendc-serverless-simulator/src/main/kotlin/org/opendc/serverless/simulator/delay/ColdStartModel.kt b/opendc-serverless/opendc-serverless-simulator/src/main/kotlin/org/opendc/serverless/simulator/delay/ColdStartModel.kt deleted file mode 100644 index f9f3718e..00000000 --- a/opendc-serverless/opendc-serverless-simulator/src/main/kotlin/org/opendc/serverless/simulator/delay/ColdStartModel.kt +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.serverless.simulator.delay - -/** - * Model parameters for the cold start times of serverless services. - */ -public enum class ColdStartModel { - // Min and max memory values from [Peeking Behind The Curtains of Serverless Platforms][2018], - // other values deduced from linear curve. - LAMBDA { - override fun coldStartParam(provisionedMemory: Int): Pair<Double, Double> { - return when (provisionedMemory) { - 128 -> Pair(265.21, 354.43) - 256 -> Pair(261.46, 334.23) - 512 -> Pair(257.71, 314.03) - 1024 -> Pair(253.96, 293.83) - 1536 -> Pair(250.07, 273.63) - 2048 -> Pair(246.11, 253.43) - else -> Pair(0.0, 1.0) - } - } - }, - AZURE { - // Azure by default uses 1.5gb memory to instantiate functions - override fun coldStartParam(provisionedMemory: Int): Pair<Double, Double> { - return Pair(242.66, 340.67) - } - }, - - GOOGLE { - override fun coldStartParam(provisionedMemory: Int): Pair<Double, Double> { - return when (provisionedMemory) { - 128 -> Pair(493.04, 345.8) - 256 -> Pair(416.59, 301.5) - 512 -> Pair(340.14, 257.2) - 1024 -> Pair(263.69, 212.9) - 1536 -> Pair(187.24, 168.6) - 2048 -> Pair(110.77, 124.3) - else -> Pair(0.0, 1.0) - } - } - }; - - /** - * Obtain the stochastic parameters for the cold start models. - */ - public abstract fun coldStartParam(provisionedMemory: Int): Pair<Double, Double> -} diff --git a/opendc-serverless/opendc-serverless-simulator/src/main/kotlin/org/opendc/serverless/simulator/delay/DelayInjector.kt b/opendc-serverless/opendc-serverless-simulator/src/main/kotlin/org/opendc/serverless/simulator/delay/DelayInjector.kt deleted file mode 100644 index f882031b..00000000 --- a/opendc-serverless/opendc-serverless-simulator/src/main/kotlin/org/opendc/serverless/simulator/delay/DelayInjector.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.serverless.simulator.delay - -import org.opendc.serverless.service.deployer.FunctionInstance - -/** - * An interface for modeling the delay caused by function cold starts. - */ -public interface DelayInjector { - /** - * Returns the cold start delay duration sampled from a normal distribution, the distribution is - * initialized using custom mean and standard deviation based on provisioned memory, language and - * failure model - */ - public fun getColdStartDelay(instance: FunctionInstance): Long -} diff --git a/opendc-serverless/opendc-serverless-simulator/src/main/kotlin/org/opendc/serverless/simulator/delay/StochasticDelayInjector.kt b/opendc-serverless/opendc-serverless-simulator/src/main/kotlin/org/opendc/serverless/simulator/delay/StochasticDelayInjector.kt deleted file mode 100644 index 154378e1..00000000 --- a/opendc-serverless/opendc-serverless-simulator/src/main/kotlin/org/opendc/serverless/simulator/delay/StochasticDelayInjector.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.serverless.simulator.delay - -import org.opendc.serverless.service.deployer.FunctionInstance -import java.util.* -import kotlin.math.abs - -/* - * Interface for instance deployment delay estimation. - */ -public class StochasticDelayInjector(private val model: ColdStartModel, private val random: Random) : DelayInjector { - override fun getColdStartDelay(instance: FunctionInstance): Long { - val (mean, sd) = model.coldStartParam(instance.function.memorySize.toInt()) - return abs(random.nextGaussian() * sd + mean).toLong() - } -} diff --git a/opendc-serverless/opendc-serverless-simulator/src/main/kotlin/org/opendc/serverless/simulator/delay/ZeroDelayInjector.kt b/opendc-serverless/opendc-serverless-simulator/src/main/kotlin/org/opendc/serverless/simulator/delay/ZeroDelayInjector.kt deleted file mode 100644 index 0895ee18..00000000 --- a/opendc-serverless/opendc-serverless-simulator/src/main/kotlin/org/opendc/serverless/simulator/delay/ZeroDelayInjector.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.serverless.simulator.delay - -import org.opendc.serverless.service.deployer.FunctionInstance - -public object ZeroDelayInjector : DelayInjector { - override fun getColdStartDelay(instance: FunctionInstance): Long = 0 -} diff --git a/opendc-serverless/opendc-serverless-simulator/src/main/kotlin/org/opendc/serverless/simulator/workload/SimServerlessWorkload.kt b/opendc-serverless/opendc-serverless-simulator/src/main/kotlin/org/opendc/serverless/simulator/workload/SimServerlessWorkload.kt deleted file mode 100644 index 121bf915..00000000 --- a/opendc-serverless/opendc-serverless-simulator/src/main/kotlin/org/opendc/serverless/simulator/workload/SimServerlessWorkload.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.serverless.simulator.workload - -import org.opendc.simulator.compute.workload.SimWorkload - -/** - * A model for a serverless workload, which may be invoked multiple times. - */ -public interface SimServerlessWorkload : SimWorkload { - /** - * This method is invoked when an active function instance is invoked. - */ - public suspend fun invoke() -} diff --git a/opendc-serverless/opendc-serverless-simulator/src/main/kotlin/org/opendc/serverless/simulator/workload/SimServerlessWorkloadMapper.kt b/opendc-serverless/opendc-serverless-simulator/src/main/kotlin/org/opendc/serverless/simulator/workload/SimServerlessWorkloadMapper.kt deleted file mode 100644 index 3a47eb53..00000000 --- a/opendc-serverless/opendc-serverless-simulator/src/main/kotlin/org/opendc/serverless/simulator/workload/SimServerlessWorkloadMapper.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.serverless.simulator.workload - -import org.opendc.serverless.api.ServerlessFunction -import org.opendc.serverless.service.FunctionObject - -/** - * A [SimServerlessWorkloadMapper] is responsible for mapping a [ServerlessFunction] to a [SimServerlessWorkload] that - * can be simulated. - */ -public fun interface SimServerlessWorkloadMapper { - /** - * Map the specified [function] to a [SimServerlessWorkload] that can be simulated. - */ - public fun createWorkload(function: FunctionObject): SimServerlessWorkload -} diff --git a/opendc-serverless/opendc-serverless-simulator/src/test/kotlin/org/opendc/serverless/simulator/SimServerlessServiceTest.kt b/opendc-serverless/opendc-serverless-simulator/src/test/kotlin/org/opendc/serverless/simulator/SimServerlessServiceTest.kt deleted file mode 100644 index 6afa1b65..00000000 --- a/opendc-serverless/opendc-serverless-simulator/src/test/kotlin/org/opendc/serverless/simulator/SimServerlessServiceTest.kt +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.serverless.simulator - -import io.mockk.coVerify -import io.mockk.spyk -import io.opentelemetry.api.metrics.MeterProvider -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.delay -import kotlinx.coroutines.yield -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertAll -import org.opendc.serverless.service.ServerlessService -import org.opendc.serverless.service.autoscaler.FunctionTerminationPolicyFixed -import org.opendc.serverless.service.router.RandomRoutingPolicy -import org.opendc.serverless.simulator.delay.ZeroDelayInjector -import org.opendc.serverless.simulator.workload.SimServerlessWorkload -import org.opendc.simulator.compute.SimMachineModel -import org.opendc.simulator.compute.model.MemoryUnit -import org.opendc.simulator.compute.model.ProcessingNode -import org.opendc.simulator.compute.model.ProcessingUnit -import org.opendc.simulator.compute.workload.SimFlopsWorkload -import org.opendc.simulator.compute.workload.SimWorkload -import org.opendc.simulator.core.runBlockingSimulation - -/** - * A test suite for the [ServerlessService] implementation under simulated conditions. - */ -@OptIn(ExperimentalCoroutinesApi::class) -internal class SimServerlessServiceTest { - - private lateinit var machineModel: SimMachineModel - - @BeforeEach - fun setUp() { - val cpuNode = ProcessingNode("Intel", "Xeon", "amd64", 2) - - machineModel = SimMachineModel( - cpus = List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 1000.0) }, - memory = List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) } - ) - } - - @Test - fun testSmoke() = runBlockingSimulation { - val meter = MeterProvider.noop().get("opendc-serverless") - val workload = spyk(object : SimServerlessWorkload, SimWorkload by SimFlopsWorkload(1000) { - override suspend fun invoke() {} - }) - val deployer = SimFunctionDeployer(clock, this, machineModel, ZeroDelayInjector) { workload } - val service = ServerlessService( - coroutineContext, clock, meter, deployer, RandomRoutingPolicy(), - FunctionTerminationPolicyFixed(coroutineContext, clock, timeout = 10000) - ) - - val client = service.newClient() - - val function = client.newFunction("test", 128) - function.invoke() - delay(2000) - - service.close() - - yield() - - assertAll( - { coVerify { workload.invoke() } }, - ) - } -} diff --git a/opendc-simulator/opendc-simulator-compute/build.gradle.kts b/opendc-simulator/opendc-simulator-compute/build.gradle.kts index 4eb0be33..a2bb89c2 100644 --- a/opendc-simulator/opendc-simulator-compute/build.gradle.kts +++ b/opendc-simulator/opendc-simulator-compute/build.gradle.kts @@ -31,8 +31,11 @@ plugins { dependencies { api(platform(projects.opendcPlatform)) - api(projects.opendcSimulator.opendcSimulatorResources) + api(projects.opendcSimulator.opendcSimulatorFlow) + api(projects.opendcSimulator.opendcSimulatorPower) + api(projects.opendcSimulator.opendcSimulatorNetwork) implementation(projects.opendcSimulator.opendcSimulatorCore) implementation(projects.opendcUtils) - implementation(libs.yaml) + + testImplementation(libs.slf4j.simple) } diff --git a/opendc-simulator/opendc-simulator-compute/src/jmh/kotlin/org/opendc/simulator/compute/SimMachineBenchmarks.kt b/opendc-simulator/opendc-simulator-compute/src/jmh/kotlin/org/opendc/simulator/compute/SimMachineBenchmarks.kt index 15714aca..cb52d24f 100644 --- a/opendc-simulator/opendc-simulator-compute/src/jmh/kotlin/org/opendc/simulator/compute/SimMachineBenchmarks.kt +++ b/opendc-simulator/opendc-simulator-compute/src/jmh/kotlin/org/opendc/simulator/compute/SimMachineBenchmarks.kt @@ -25,18 +25,20 @@ package org.opendc.simulator.compute import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch -import org.opendc.simulator.compute.cpufreq.PerformanceScalingGovernor -import org.opendc.simulator.compute.cpufreq.SimpleScalingDriver +import org.opendc.simulator.compute.kernel.SimFairShareHypervisor +import org.opendc.simulator.compute.kernel.SimSpaceSharedHypervisor +import org.opendc.simulator.compute.model.MachineModel import org.opendc.simulator.compute.model.MemoryUnit import org.opendc.simulator.compute.model.ProcessingNode import org.opendc.simulator.compute.model.ProcessingUnit import org.opendc.simulator.compute.power.ConstantPowerModel +import org.opendc.simulator.compute.power.SimplePowerDriver +import org.opendc.simulator.compute.workload.SimTrace import org.opendc.simulator.compute.workload.SimTraceWorkload -import org.opendc.simulator.core.SimulationCoroutineScope import org.opendc.simulator.core.runBlockingSimulation -import org.opendc.simulator.resources.SimResourceScheduler -import org.opendc.simulator.resources.SimResourceSchedulerTrampoline +import org.opendc.simulator.flow.FlowEngine import org.openjdk.jmh.annotations.* +import java.util.concurrent.ThreadLocalRandom import java.util.concurrent.TimeUnit @State(Scope.Thread) @@ -45,68 +47,54 @@ import java.util.concurrent.TimeUnit @Measurement(iterations = 5, time = 3, timeUnit = TimeUnit.SECONDS) @OptIn(ExperimentalCoroutinesApi::class) class SimMachineBenchmarks { - private lateinit var scope: SimulationCoroutineScope - private lateinit var scheduler: SimResourceScheduler - private lateinit var machineModel: SimMachineModel + private lateinit var machineModel: MachineModel + private lateinit var trace: SimTrace @Setup fun setUp() { - scope = SimulationCoroutineScope() - scheduler = SimResourceSchedulerTrampoline(scope.coroutineContext, scope.clock) - val cpuNode = ProcessingNode("Intel", "Xeon", "amd64", 2) - machineModel = SimMachineModel( + machineModel = MachineModel( cpus = List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 1000.0) }, memory = List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) } ) - } - @State(Scope.Benchmark) - class Workload { - lateinit var trace: Sequence<SimTraceWorkload.Fragment> - - @Setup - fun setUp() { - trace = sequenceOf( - SimTraceWorkload.Fragment(1000, 28.0, 1), - SimTraceWorkload.Fragment(1000, 3500.0, 1), - SimTraceWorkload.Fragment(1000, 0.0, 1), - SimTraceWorkload.Fragment(1000, 183.0, 1), - SimTraceWorkload.Fragment(1000, 400.0, 1), - SimTraceWorkload.Fragment(1000, 100.0, 1), - SimTraceWorkload.Fragment(1000, 3000.0, 1), - SimTraceWorkload.Fragment(1000, 4500.0, 1), - ) + val random = ThreadLocalRandom.current() + val builder = SimTrace.builder() + repeat(10000) { + val timestamp = it.toLong() + val deadline = timestamp + 1000 + builder.add(timestamp, deadline, random.nextDouble(0.0, 4500.0), 1) } + trace = builder.build() } @Benchmark - fun benchmarkBareMetal(state: Workload) { - return scope.runBlockingSimulation { + fun benchmarkBareMetal() { + return runBlockingSimulation { + val engine = FlowEngine(coroutineContext, clock) val machine = SimBareMetalMachine( - coroutineContext, clock, machineModel, PerformanceScalingGovernor(), - SimpleScalingDriver(ConstantPowerModel(0.0)) + engine, machineModel, SimplePowerDriver(ConstantPowerModel(0.0)) ) - return@runBlockingSimulation machine.run(SimTraceWorkload(state.trace)) + return@runBlockingSimulation machine.run(SimTraceWorkload(trace)) } } @Benchmark - fun benchmarkSpaceSharedHypervisor(state: Workload) { - return scope.runBlockingSimulation { + fun benchmarkSpaceSharedHypervisor() { + return runBlockingSimulation { + val engine = FlowEngine(coroutineContext, clock) val machine = SimBareMetalMachine( - coroutineContext, clock, machineModel, PerformanceScalingGovernor(), - SimpleScalingDriver(ConstantPowerModel(0.0)) + engine, machineModel, SimplePowerDriver(ConstantPowerModel(0.0)) ) - val hypervisor = SimSpaceSharedHypervisor() + val hypervisor = SimSpaceSharedHypervisor(engine, null, null) launch { machine.run(hypervisor) } val vm = hypervisor.createMachine(machineModel) try { - return@runBlockingSimulation vm.run(SimTraceWorkload(state.trace)) + return@runBlockingSimulation vm.run(SimTraceWorkload(trace)) } finally { vm.close() machine.close() @@ -115,20 +103,20 @@ class SimMachineBenchmarks { } @Benchmark - fun benchmarkFairShareHypervisorSingle(state: Workload) { - return scope.runBlockingSimulation { + fun benchmarkFairShareHypervisorSingle() { + return runBlockingSimulation { + val engine = FlowEngine(coroutineContext, clock) val machine = SimBareMetalMachine( - coroutineContext, clock, machineModel, PerformanceScalingGovernor(), - SimpleScalingDriver(ConstantPowerModel(0.0)) + engine, machineModel, SimplePowerDriver(ConstantPowerModel(0.0)) ) - val hypervisor = SimFairShareHypervisor(scheduler) + val hypervisor = SimFairShareHypervisor(engine, null, null, null) launch { machine.run(hypervisor) } val vm = hypervisor.createMachine(machineModel) try { - return@runBlockingSimulation vm.run(SimTraceWorkload(state.trace)) + return@runBlockingSimulation vm.run(SimTraceWorkload(trace)) } finally { vm.close() machine.close() @@ -137,13 +125,13 @@ class SimMachineBenchmarks { } @Benchmark - fun benchmarkFairShareHypervisorDouble(state: Workload) { - return scope.runBlockingSimulation { + fun benchmarkFairShareHypervisorDouble() { + return runBlockingSimulation { + val engine = FlowEngine(coroutineContext, clock) val machine = SimBareMetalMachine( - coroutineContext, clock, machineModel, PerformanceScalingGovernor(), - SimpleScalingDriver(ConstantPowerModel(0.0)) + engine, machineModel, SimplePowerDriver(ConstantPowerModel(0.0)) ) - val hypervisor = SimFairShareHypervisor(scheduler) + val hypervisor = SimFairShareHypervisor(engine, null, null, null) launch { machine.run(hypervisor) } @@ -153,7 +141,7 @@ class SimMachineBenchmarks { launch { try { - vm.run(SimTraceWorkload(state.trace)) + vm.run(SimTraceWorkload(trace)) } finally { machine.close() } diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimAbstractHypervisor.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimAbstractHypervisor.kt deleted file mode 100644 index 713376e7..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimAbstractHypervisor.kt +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute - -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.launch -import org.opendc.simulator.compute.interference.PerformanceInterferenceModel -import org.opendc.simulator.compute.model.MemoryUnit -import org.opendc.simulator.compute.model.ProcessingUnit -import org.opendc.simulator.compute.workload.SimWorkload -import org.opendc.simulator.resources.* -import java.time.Clock - -/** - * Abstract implementation of the [SimHypervisor] interface. - */ -public abstract class SimAbstractHypervisor : SimHypervisor { - /** - * The machine on which the hypervisor runs. - */ - private lateinit var context: SimMachineContext - - /** - * The resource switch to use. - */ - private lateinit var switch: SimResourceSwitch - - /** - * The virtual machines running on this hypervisor. - */ - private val _vms = mutableSetOf<VirtualMachine>() - override val vms: Set<SimMachine> - get() = _vms - - /** - * Construct the [SimResourceSwitch] implementation that performs the actual scheduling of the CPUs. - */ - public abstract fun createSwitch(ctx: SimMachineContext): SimResourceSwitch - - /** - * Check whether the specified machine model fits on this hypervisor. - */ - public abstract fun canFit(model: SimMachineModel, switch: SimResourceSwitch): Boolean - - override fun canFit(model: SimMachineModel): Boolean { - return canFit(model, switch) - } - - override fun createMachine( - model: SimMachineModel, - performanceInterferenceModel: PerformanceInterferenceModel? - ): SimMachine { - require(canFit(model)) { "Machine does not fit" } - val vm = VirtualMachine(model, performanceInterferenceModel) - _vms.add(vm) - return vm - } - - /** - * A virtual machine running on the hypervisor. - * - * @property model The machine model of the virtual machine. - * @property performanceInterferenceModel The performance interference model to utilize. - */ - private inner class VirtualMachine( - override val model: SimMachineModel, - val performanceInterferenceModel: PerformanceInterferenceModel? = null, - ) : SimMachine { - /** - * A [StateFlow] representing the CPU usage of the simulated machine. - */ - override val usage: MutableStateFlow<Double> = MutableStateFlow(0.0) - - /** - * A flag to indicate that the machine is terminated. - */ - private var isTerminated = false - - /** - * The vCPUs of the machine. - */ - private val cpus = model.cpus.map { ProcessingUnitImpl(it, switch) } - - /** - * Run the specified [SimWorkload] on this machine and suspend execution util the workload has finished. - */ - override suspend fun run(workload: SimWorkload, meta: Map<String, Any>) { - coroutineScope { - require(!isTerminated) { "Machine is terminated" } - - val ctx = object : SimMachineContext { - override val cpus: List<SimProcessingUnit> = this@VirtualMachine.cpus - - override val memory: List<MemoryUnit> - get() = model.memory - - override val clock: Clock - get() = this@SimAbstractHypervisor.context.clock - - override val meta: Map<String, Any> = meta - } - - workload.onStart(ctx) - - for (cpu in cpus) { - launch { - cpu.consume(workload.getConsumer(ctx, cpu.model)) - } - } - } - } - - /** - * Terminate this VM instance. - */ - override fun close() { - if (!isTerminated) { - isTerminated = true - - cpus.forEach(SimProcessingUnit::close) - _vms.remove(this) - } - } - } - - override fun onStart(ctx: SimMachineContext) { - context = ctx - switch = createSwitch(ctx) - } - - override fun getConsumer(ctx: SimMachineContext, cpu: ProcessingUnit): SimResourceConsumer { - val forwarder = SimResourceForwarder() - switch.addInput(forwarder) - return forwarder - } - - /** - * The [SimProcessingUnit] of this machine. - */ - public inner class ProcessingUnitImpl(override val model: ProcessingUnit, switch: SimResourceSwitch) : SimProcessingUnit { - /** - * The actual resource supporting the processing unit. - */ - private val source = switch.addOutput(model.frequency) - - override val speed: Double = 0.0 /* TODO Implement */ - - override val state: SimResourceState - get() = source.state - - override fun startConsumer(consumer: SimResourceConsumer) { - source.startConsumer(consumer) - } - - override fun interrupt() { - source.interrupt() - } - - override fun cancel() { - source.cancel() - } - - override fun close() { - source.close() - } - } -} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimAbstractMachine.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimAbstractMachine.kt index f6324e13..60a10f20 100644 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimAbstractMachine.kt +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimAbstractMachine.kt @@ -23,29 +23,53 @@ package org.opendc.simulator.compute import kotlinx.coroutines.* -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow +import org.opendc.simulator.compute.device.SimNetworkAdapter +import org.opendc.simulator.compute.device.SimPeripheral +import org.opendc.simulator.compute.model.MachineModel import org.opendc.simulator.compute.model.MemoryUnit +import org.opendc.simulator.compute.model.NetworkAdapter +import org.opendc.simulator.compute.model.StorageDevice import org.opendc.simulator.compute.workload.SimWorkload -import org.opendc.simulator.resources.consume -import org.opendc.simulator.resources.consumer.SimSpeedConsumerAdapter -import java.time.Clock -import kotlin.coroutines.CoroutineContext +import org.opendc.simulator.flow.* +import kotlin.coroutines.Continuation +import kotlin.coroutines.resume /** * Abstract implementation of the [SimMachine] interface. + * + * @param engine The engine to manage the machine's resources. + * @param parent The parent simulation system. + * @param model The model of the machine. */ -public abstract class SimAbstractMachine(private val clock: Clock) : SimMachine { - private val _usage = MutableStateFlow(0.0) - override val usage: StateFlow<Double> - get() = _usage +public abstract class SimAbstractMachine( + protected val engine: FlowEngine, + private val parent: FlowConvergenceListener?, + final override val model: MachineModel +) : SimMachine, FlowConvergenceListener { + /** + * The resources allocated for this machine. + */ + public abstract val cpus: List<SimProcessingUnit> + + /** + * The memory interface of the machine. + */ + public val memory: SimMemory = Memory(FlowSink(engine, model.memory.sumOf { it.size }.toDouble()), model.memory) /** - * The speed of the CPU cores. + * The network interfaces available to the machine. */ - public val speed: DoubleArray - get() = _speed - private var _speed = doubleArrayOf() + public val net: List<SimNetworkInterface> = model.net.mapIndexed { i, adapter -> NetworkAdapterImpl(engine, adapter, i) } + + /** + * The network interfaces available to the machine. + */ + public val storage: List<SimStorageInterface> = model.storage.mapIndexed { i, device -> StorageDeviceImpl(engine, device, i) } + + /** + * The peripherals of the machine. + */ + public override val peripherals: List<SimPeripheral> = net.map { it as SimNetworkAdapter } /** * A flag to indicate that the machine is terminated. @@ -53,75 +77,135 @@ public abstract class SimAbstractMachine(private val clock: Clock) : SimMachine private var isTerminated = false /** - * The [CoroutineContext] to run in. + * The continuation to resume when the virtual machine workload has finished. */ - protected abstract val context: CoroutineContext + private var cont: Continuation<Unit>? = null /** - * The resources allocated for this machine. + * Converge the specified [SimWorkload] on this machine and suspend execution util the workload has finished. + */ + override suspend fun run(workload: SimWorkload, meta: Map<String, Any>) { + check(!isTerminated) { "Machine is terminated" } + check(cont == null) { "A machine cannot run concurrently" } + + val ctx = Context(meta) + + return suspendCancellableCoroutine { cont -> + this.cont = cont + + // Cancel all cpus on cancellation + cont.invokeOnCancellation { + this.cont = null + engine.batch { + for (cpu in cpus) { + cpu.cancel() + } + } + } + + engine.batch { workload.onStart(ctx) } + } + } + + override fun close() { + if (isTerminated) { + return + } + + isTerminated = true + cancel() + } + + override fun onConverge(now: Long, delta: Long) { + parent?.onConverge(now, delta) + } + + /** + * Cancel the workload that is currently running on the machine. */ - protected abstract val cpus: List<SimProcessingUnit> + private fun cancel() { + engine.batch { + for (cpu in cpus) { + cpu.cancel() + } + } + + val cont = cont + if (cont != null) { + this.cont = null + cont.resume(Unit) + } + } /** * The execution context in which the workload runs. */ private inner class Context(override val meta: Map<String, Any>) : SimMachineContext { - override val clock: Clock - get() = this@SimAbstractMachine.clock + override val engine: FlowEngine + get() = this@SimAbstractMachine.engine override val cpus: List<SimProcessingUnit> = this@SimAbstractMachine.cpus - override val memory: List<MemoryUnit> = model.memory + override val memory: SimMemory = this@SimAbstractMachine.memory + + override val net: List<SimNetworkInterface> = this@SimAbstractMachine.net + + override val storage: List<SimStorageInterface> = this@SimAbstractMachine.storage + + override fun close() = cancel() } /** - * Run the specified [SimWorkload] on this machine and suspend execution util the workload has finished. + * The [SimMemory] implementation for a machine. */ - override suspend fun run(workload: SimWorkload, meta: Map<String, Any>): Unit = withContext(context) { - require(!isTerminated) { "Machine is terminated" } - val ctx = Context(meta) - val totalCapacity = model.cpus.sumByDouble { it.frequency } + private class Memory(source: FlowSink, override val models: List<MemoryUnit>) : SimMemory, FlowConsumer by source { + override fun toString(): String = "SimAbstractMachine.Memory" + } - _speed = DoubleArray(model.cpus.size) { 0.0 } - var totalSpeed = 0.0 + /** + * The [SimNetworkAdapter] implementation for a machine. + */ + private class NetworkAdapterImpl( + private val engine: FlowEngine, + model: NetworkAdapter, + index: Int + ) : SimNetworkAdapter(), SimNetworkInterface { + override val name: String = "eth$index" - // Before the workload starts, initialize the initial power draw - updateUsage(0.0) + override val bandwidth: Double = model.bandwidth - workload.onStart(ctx) + override val provider: FlowConsumer + get() = _rx - for (cpu in cpus) { - val model = cpu.model - val consumer = workload.getConsumer(ctx, model) - val adapter = SimSpeedConsumerAdapter(consumer) { newSpeed -> - val _speed = _speed - val _usage = _usage + override fun createConsumer(): FlowSource = _tx - val oldSpeed = _speed[model.id] - _speed[model.id] = newSpeed - totalSpeed = totalSpeed - oldSpeed + newSpeed + override val tx: FlowConsumer + get() = _tx + private val _tx = FlowForwarder(engine) - val newUsage = totalSpeed / totalCapacity - if (_usage.value != newUsage) { - updateUsage(totalSpeed / totalCapacity) - } - } + override val rx: FlowSource + get() = _rx + private val _rx = FlowForwarder(engine) - launch { cpu.consume(adapter) } - } + override fun toString(): String = "SimAbstractMachine.NetworkAdapterImpl[name=$name,bandwidth=$bandwidth]" } /** - * This method is invoked when the usage of the machine is updated. + * The [SimStorageInterface] implementation for a machine. */ - protected open fun updateUsage(usage: Double) { - _usage.value = usage - } + private class StorageDeviceImpl( + engine: FlowEngine, + model: StorageDevice, + index: Int + ) : SimStorageInterface { + override val name: String = "disk$index" - override fun close() { - if (!isTerminated) { - isTerminated = true - cpus.forEach(SimProcessingUnit::close) - } + override val capacity: Double = model.capacity + + override val read: FlowConsumer = FlowSink(engine, model.readBandwidth) + + override val write: FlowConsumer = FlowSink(engine, model.writeBandwidth) + + override fun toString(): String = "SimAbstractMachine.StorageDeviceImpl[name=$name,capacity=$capacity]" } } diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimBareMetalMachine.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimBareMetalMachine.kt index 27ebba21..9140d31b 100644 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimBareMetalMachine.kt +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimBareMetalMachine.kt @@ -22,111 +22,93 @@ package org.opendc.simulator.compute -import kotlinx.coroutines.* -import org.opendc.simulator.compute.cpufreq.ScalingDriver -import org.opendc.simulator.compute.cpufreq.ScalingGovernor +import org.opendc.simulator.compute.device.SimPsu +import org.opendc.simulator.compute.model.MachineModel import org.opendc.simulator.compute.model.ProcessingUnit -import org.opendc.simulator.resources.* -import org.opendc.utils.TimerScheduler -import java.time.Clock -import kotlin.coroutines.* +import org.opendc.simulator.compute.power.PowerDriver +import org.opendc.simulator.flow.* +import org.opendc.simulator.flow.FlowEngine +import kotlin.math.max /** * A simulated bare-metal machine that is able to run a single workload. * - * A [SimBareMetalMachine] is a stateful object and you should be careful when operating this object concurrently. For - * example. the class expects only a single concurrent call to [run]. + * A [SimBareMetalMachine] is a stateful object, and you should be careful when operating this object concurrently. For + * example, the class expects only a single concurrent call to [run]. * - * @param context The [CoroutineContext] to run the simulated workload in. - * @param clock The virtual clock to track the simulation time. + * @param engine The [FlowEngine] to drive the simulation. * @param model The machine model to simulate. + * @param powerDriver The power driver to use. + * @param psu The power supply of the machine. + * @param parent The parent simulation system. */ -@OptIn(ExperimentalCoroutinesApi::class, InternalCoroutinesApi::class) public class SimBareMetalMachine( - context: CoroutineContext, - private val clock: Clock, - override val model: SimMachineModel, - scalingGovernor: ScalingGovernor, - scalingDriver: ScalingDriver -) : SimAbstractMachine(clock) { + engine: FlowEngine, + model: MachineModel, + powerDriver: PowerDriver, + public val psu: SimPsu = SimPsu(500.0, mapOf(1.0 to 1.0)), + parent: FlowConvergenceListener? = null, +) : SimAbstractMachine(engine, parent, model) { /** - * The [Job] associated with this machine. + * The current power usage of the machine (without PSU loss) in W. */ - private val scope = CoroutineScope(context + Job()) - - override val context: CoroutineContext = scope.coroutineContext - - /** - * The [TimerScheduler] to use for scheduling the interrupts. - */ - private val scheduler = SimResourceSchedulerTrampoline(this.context, clock) - - override val cpus: List<SimProcessingUnit> = model.cpus.map { ProcessingUnitImpl(it) } + public val powerUsage: Double + get() = _powerUsage + private var _powerUsage = 0.0 /** - * Construct the [ScalingDriver.Logic] for this machine. + * The total energy usage of the machine (without PSU loss) in Joules. */ - private val scalingDriver = scalingDriver.createLogic(this) + public val energyUsage: Double + get() = _energyUsage + private var _energyUsage = 0.0 /** - * The scaling contexts associated with each CPU. + * The processing units of the machine. */ - private val scalingGovernors = cpus.map { cpu -> - scalingGovernor.createLogic(this.scalingDriver.createContext(cpu)) - } - - init { - scalingGovernors.forEach { it.onStart() } + override val cpus: List<SimProcessingUnit> = model.cpus.map { cpu -> + Cpu(FlowSink(engine, cpu.frequency, this@SimBareMetalMachine), cpu) } /** - * The power draw of the machine. + * The logic of the power driver. */ - public var powerDraw: Double = 0.0 - private set + private val powerDriverLogic = powerDriver.createLogic(this, cpus) - override fun updateUsage(usage: Double) { - super.updateUsage(usage) + private var _lastConverge = Long.MAX_VALUE - scalingGovernors.forEach { it.onLimit() } - powerDraw = scalingDriver.computePower() - } + override fun onConverge(now: Long, delta: Long) { + // Update the PSU stage + psu.update() - override fun close() { - super.close() + val lastConverge = _lastConverge + _lastConverge = now + val duration = max(0, now - lastConverge) + if (duration > 0) { + // Compute the power and energy usage of the machine + _energyUsage += _powerUsage * (duration / 1000.0) + _powerUsage = powerDriverLogic.computePower() + } + } - scope.cancel() + init { + psu.connect(powerDriverLogic) } /** - * The [SimProcessingUnit] of this machine. + * A [SimProcessingUnit] of a bare-metal machine. */ - public inner class ProcessingUnitImpl(override val model: ProcessingUnit) : SimProcessingUnit { - /** - * The actual resource supporting the processing unit. - */ - private val source = SimResourceSource(model.frequency, scheduler) - - override val speed: Double - get() = source.speed - - override val state: SimResourceState - get() = source.state - - override fun startConsumer(consumer: SimResourceConsumer) { - source.startConsumer(consumer) - } - - override fun interrupt() { - source.interrupt() - } - - override fun cancel() { - source.cancel() - } - - override fun close() { - source.interrupt() - } + private class Cpu( + private val source: FlowSink, + override val model: ProcessingUnit + ) : SimProcessingUnit, FlowConsumer by source { + override var capacity: Double + get() = source.capacity + set(value) { + // Clamp the capacity of the CPU between [0.0, maxFreq] + source.capacity = value.coerceIn(0.0, model.frequency) + } + + override fun toString(): String = "SimBareMetalMachine.Cpu[model=$model]" } } diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimFairShareHypervisor.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimFairShareHypervisor.kt deleted file mode 100644 index 11aec2de..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimFairShareHypervisor.kt +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute - -import org.opendc.simulator.compute.workload.SimWorkload -import org.opendc.simulator.resources.* - -/** - * A [SimHypervisor] that distributes the computing requirements of multiple [SimWorkload] on a single - * [SimBareMetalMachine] concurrently using weighted fair sharing. - * - * @param listener The hypervisor listener to use. - */ -public class SimFairShareHypervisor(private val scheduler: SimResourceScheduler, private val listener: SimHypervisor.Listener? = null) : SimAbstractHypervisor() { - - override fun canFit(model: SimMachineModel, switch: SimResourceSwitch): Boolean = true - - override fun createSwitch(ctx: SimMachineContext): SimResourceSwitch { - return SimResourceSwitchMaxMin( - scheduler, - object : SimResourceSwitchMaxMin.Listener { - override fun onSliceFinish( - switch: SimResourceSwitchMaxMin, - requestedWork: Long, - grantedWork: Long, - overcommittedWork: Long, - interferedWork: Long, - cpuUsage: Double, - cpuDemand: Double - ) { - listener?.onSliceFinish(this@SimFairShareHypervisor, requestedWork, grantedWork, overcommittedWork, interferedWork, cpuUsage, cpuDemand) - } - } - ) - } -} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimFairShareHypervisorProvider.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimFairShareHypervisorProvider.kt deleted file mode 100644 index 2ab3ea09..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimFairShareHypervisorProvider.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute - -import org.opendc.simulator.resources.SimResourceSchedulerTrampoline -import java.time.Clock -import kotlin.coroutines.CoroutineContext - -/** - * A [SimHypervisorProvider] for the [SimFairShareHypervisor] implementation. - */ -public class SimFairShareHypervisorProvider : SimHypervisorProvider { - override val id: String = "fair-share" - - override fun create(context: CoroutineContext, clock: Clock, listener: SimHypervisor.Listener?): SimHypervisor { - return SimFairShareHypervisor(SimResourceSchedulerTrampoline(context, clock), listener) - } -} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimHypervisor.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimHypervisor.kt deleted file mode 100644 index 4a233fec..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimHypervisor.kt +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (c) 2020 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute - -import org.opendc.simulator.compute.interference.PerformanceInterferenceModel -import org.opendc.simulator.compute.workload.SimWorkload - -/** - * A SimHypervisor facilitates the execution of multiple concurrent [SimWorkload]s, while acting as a single workload - * to a [SimBareMetalMachine]. - */ -public interface SimHypervisor : SimWorkload { - /** - * The machines running on the hypervisor. - */ - public val vms: Set<SimMachine> - - /** - * Determine whether the specified machine characterized by [model] can fit on this hypervisor at this moment. - */ - public fun canFit(model: SimMachineModel): Boolean - - /** - * Create a [SimMachine] instance on which users may run a [SimWorkload]. - * - * @param model The machine to create. - * @param performanceInterferenceModel The performance interference model to use. - */ - public fun createMachine( - model: SimMachineModel, - performanceInterferenceModel: PerformanceInterferenceModel? = null - ): SimMachine - - /** - * Event listener for hypervisor events. - */ - public interface Listener { - /** - * This method is invoked when a slice is finished. - */ - public fun onSliceFinish( - hypervisor: SimHypervisor, - requestedWork: Long, - grantedWork: Long, - overcommittedWork: Long, - interferedWork: Long, - cpuUsage: Double, - cpuDemand: Double - ) - } -} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimHypervisorProvider.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimHypervisorProvider.kt deleted file mode 100644 index b66020f4..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimHypervisorProvider.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute - -import java.time.Clock -import kotlin.coroutines.CoroutineContext - -/** - * A service provider interface for constructing a [SimHypervisor]. - */ -public interface SimHypervisorProvider { - /** - * A unique identifier for this hypervisor implementation. - * - * Each hypervisor must provide a unique ID, so that they can be selected by the user. - * When in doubt, you may use the fully qualified name of your custom [SimHypervisor] implementation class. - */ - public val id: String - - /** - * Create a [SimHypervisor] instance with the specified [listener]. - */ - public fun create(context: CoroutineContext, clock: Clock, listener: SimHypervisor.Listener? = null): SimHypervisor -} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachine.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachine.kt index bfaa60bc..ab0b56ae 100644 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachine.kt +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachine.kt @@ -22,7 +22,8 @@ package org.opendc.simulator.compute -import kotlinx.coroutines.flow.StateFlow +import org.opendc.simulator.compute.device.SimPeripheral +import org.opendc.simulator.compute.model.MachineModel import org.opendc.simulator.compute.workload.SimWorkload /** @@ -32,15 +33,15 @@ public interface SimMachine : AutoCloseable { /** * The model of the machine containing its specifications. */ - public val model: SimMachineModel + public val model: MachineModel /** - * A [StateFlow] representing the CPU usage of the simulated machine. + * The peripherals attached to the machine. */ - public val usage: StateFlow<Double> + public val peripherals: List<SimPeripheral> /** - * Run the specified [SimWorkload] on this machine and suspend execution util the workload has finished. + * Converge the specified [SimWorkload] on this machine and suspend execution util the workload has finished. */ public suspend fun run(workload: SimWorkload, meta: Map<String, Any> = emptyMap()) diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachineContext.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachineContext.kt index c2523a2a..1317f728 100644 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachineContext.kt +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachineContext.kt @@ -22,19 +22,18 @@ package org.opendc.simulator.compute -import org.opendc.simulator.compute.model.MemoryUnit -import java.time.Clock +import org.opendc.simulator.flow.FlowEngine /** * A simulated execution context in which a bootable image runs. This interface represents the * firmware interface between the running image (e.g. operating system) and the physical or virtual firmware on * which the image runs. */ -public interface SimMachineContext { +public interface SimMachineContext : AutoCloseable { /** - * The virtual clock tracking simulation time. + * The [FlowEngine] that simulates the machine. */ - public val clock: Clock + public val engine: FlowEngine /** * The metadata associated with the context. @@ -47,7 +46,22 @@ public interface SimMachineContext { public val cpus: List<SimProcessingUnit> /** - * The memory available on the machine + * The memory interface of the machine. */ - public val memory: List<MemoryUnit> + public val memory: SimMemory + + /** + * The network interfaces available to the workload. + */ + public val net: List<SimNetworkInterface> + + /** + * The storage devices available to the workload. + */ + public val storage: List<SimStorageInterface> + + /** + * Stop the workload. + */ + public override fun close() } diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachineModel.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachineModel.kt deleted file mode 100644 index 2b414540..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachineModel.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute - -import org.opendc.simulator.compute.model.MemoryUnit -import org.opendc.simulator.compute.model.ProcessingUnit - -/** - * A description of the physical or virtual machine on which a bootable image runs. - * - * @property cpus The list of processing units available to the image. - * @property memory The list of memory units available to the image. - */ -public data class SimMachineModel(public val cpus: List<ProcessingUnit>, public val memory: List<MemoryUnit>) diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMemory.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMemory.kt new file mode 100644 index 00000000..b1aef495 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMemory.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute + +import org.opendc.simulator.compute.model.MemoryUnit +import org.opendc.simulator.flow.FlowConsumer + +/** + * An interface to control the memory usage of simulated workloads. + */ +public interface SimMemory : FlowConsumer { + /** + * The models representing the static information of the memory units supporting this interface. + */ + public val models: List<MemoryUnit> +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimNetworkInterface.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimNetworkInterface.kt new file mode 100644 index 00000000..660b2871 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimNetworkInterface.kt @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute + +import org.opendc.simulator.flow.FlowConsumer +import org.opendc.simulator.flow.FlowSource + +/** + * A firmware interface to a network adapter. + */ +public interface SimNetworkInterface { + /** + * The name of the network interface. + */ + public val name: String + + /** + * The unidirectional bandwidth of the network interface in Mbps. + */ + public val bandwidth: Double + + /** + * The resource provider for the transmit channel of the network interface. + */ + public val tx: FlowConsumer + + /** + * The resource consumer for the receive channel of the network interface. + */ + public val rx: FlowSource +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimProcessingUnit.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimProcessingUnit.kt index 13c7d9b2..c9f36ece 100644 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimProcessingUnit.kt +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimProcessingUnit.kt @@ -23,19 +23,19 @@ package org.opendc.simulator.compute import org.opendc.simulator.compute.model.ProcessingUnit -import org.opendc.simulator.resources.SimResourceProvider +import org.opendc.simulator.flow.FlowConsumer /** * A simulated processing unit. */ -public interface SimProcessingUnit : SimResourceProvider { +public interface SimProcessingUnit : FlowConsumer { /** - * The model representing the static properties of the processing unit. + * The capacity of the processing unit, which can be adjusted by the workload if supported by the machine. */ - public val model: ProcessingUnit + public override var capacity: Double /** - * The current speed of the processing unit. + * The model representing the static properties of the processing unit. */ - public val speed: Double + public val model: ProcessingUnit } diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimSpaceSharedHypervisor.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimSpaceSharedHypervisor.kt deleted file mode 100644 index fd8e546f..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimSpaceSharedHypervisor.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute - -import org.opendc.simulator.resources.* - -/** - * A [SimHypervisor] that allocates its sub-resources exclusively for the virtual machine that it hosts. - */ -public class SimSpaceSharedHypervisor : SimAbstractHypervisor() { - override fun canFit(model: SimMachineModel, switch: SimResourceSwitch): Boolean { - return switch.inputs.size - switch.outputs.size >= model.cpus.size - } - - override fun createSwitch(ctx: SimMachineContext): SimResourceSwitch { - return SimResourceSwitchExclusive() - } -} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimSpaceSharedHypervisorProvider.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimSpaceSharedHypervisorProvider.kt deleted file mode 100644 index 83b924d7..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimSpaceSharedHypervisorProvider.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute - -import java.time.Clock -import kotlin.coroutines.CoroutineContext - -/** - * A [SimHypervisorProvider] for the [SimSpaceSharedHypervisor] implementation. - */ -public class SimSpaceSharedHypervisorProvider : SimHypervisorProvider { - override val id: String = "space-shared" - - override fun create(context: CoroutineContext, clock: Clock, listener: SimHypervisor.Listener?): SimHypervisor { - return SimSpaceSharedHypervisor() - } -} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimStorageInterface.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimStorageInterface.kt new file mode 100644 index 00000000..3d648671 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimStorageInterface.kt @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute + +import org.opendc.simulator.flow.FlowConsumer + +/** + * A firmware interface to a storage device. + */ +public interface SimStorageInterface { + /** + * The name of the storage device. + */ + public val name: String + + /** + * The capacity of the storage device in MBs. + */ + public val capacity: Double + + /** + * The resource provider for the read operations of the storage device. + */ + public val read: FlowConsumer + + /** + * The resource consumer for the write operation of the storage device. + */ + public val write: FlowConsumer +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/DemandScalingGovernor.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/DemandScalingGovernor.kt deleted file mode 100644 index ddbe1ca0..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/DemandScalingGovernor.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute.cpufreq - -/** - * A CPUFreq [ScalingGovernor] that requests the frequency based on the utilization of the machine. - */ -public class DemandScalingGovernor : ScalingGovernor { - override fun createLogic(ctx: ScalingContext): ScalingGovernor.Logic = object : ScalingGovernor.Logic { - override fun onLimit() { - ctx.setTarget(ctx.cpu.speed) - } - - override fun toString(): String = "DemandScalingGovernor.Logic" - } -} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/PStateScalingDriver.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/PStateScalingDriver.kt deleted file mode 100644 index 6f44d778..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/PStateScalingDriver.kt +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute.cpufreq - -import org.opendc.simulator.compute.SimMachine -import org.opendc.simulator.compute.SimProcessingUnit -import org.opendc.simulator.compute.power.PowerModel -import java.util.* -import kotlin.math.max -import kotlin.math.min - -/** - * A [ScalingDriver] that scales the frequency of the processor based on a discrete set of frequencies. - * - * @param states A map describing the states of the driver. - */ -public class PStateScalingDriver(states: Map<Double, PowerModel>) : ScalingDriver { - /** - * The P-States defined by the user and ordered by key. - */ - private val states = TreeMap(states) - - override fun createLogic(machine: SimMachine): ScalingDriver.Logic = object : ScalingDriver.Logic { - /** - * The scaling contexts. - */ - private val contexts = mutableListOf<ScalingContextImpl>() - - override fun createContext(cpu: SimProcessingUnit): ScalingContext { - val ctx = ScalingContextImpl(machine, cpu) - contexts.add(ctx) - return ctx - } - - override fun computePower(): Double { - var targetFreq = 0.0 - var totalSpeed = 0.0 - - for (ctx in contexts) { - targetFreq = max(ctx.target, targetFreq) - totalSpeed += ctx.cpu.speed - } - - val maxFreq = states.lastKey() - val (actualFreq, model) = states.ceilingEntry(min(maxFreq, targetFreq)) - val utilization = totalSpeed / (actualFreq * contexts.size) - return model.computePower(utilization) - } - - override fun toString(): String = "PStateScalingDriver.Logic" - } - - private class ScalingContextImpl( - override val machine: SimMachine, - override val cpu: SimProcessingUnit - ) : ScalingContext { - var target = cpu.model.frequency - private set - - override fun setTarget(freq: Double) { - target = freq - } - - override fun toString(): String = "PStateScalingDriver.Context" - } -} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/PerformanceScalingGovernor.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/PerformanceScalingGovernor.kt deleted file mode 100644 index 96f8775a..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/PerformanceScalingGovernor.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute.cpufreq - -/** - * A CPUFreq [ScalingGovernor] that causes the highest possible frequency to be requested from the resource. - */ -public class PerformanceScalingGovernor : ScalingGovernor { - override fun createLogic(ctx: ScalingContext): ScalingGovernor.Logic = object : ScalingGovernor.Logic { - override fun onLimit() { - ctx.setTarget(ctx.cpu.model.frequency) - } - - override fun toString(): String = "PerformanceScalingGovernor.Logic" - } -} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/ScalingContext.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/ScalingContext.kt deleted file mode 100644 index 18338079..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/ScalingContext.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute.cpufreq - -import org.opendc.simulator.compute.SimMachine -import org.opendc.simulator.compute.SimProcessingUnit - -/** - * A [ScalingContext] is used to communicate frequency scaling changes between the [ScalingGovernor] and driver. - */ -public interface ScalingContext { - /** - * The machine the processing unit belongs to. - */ - public val machine: SimMachine - - /** - * The processing unit associated with this context. - */ - public val cpu: SimProcessingUnit - - /** - * Target the processor to run at the specified target [frequency][freq]. - */ - public fun setTarget(freq: Double) -} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/ScalingDriver.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/ScalingDriver.kt deleted file mode 100644 index b4fd7550..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/ScalingDriver.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute.cpufreq - -import org.opendc.simulator.compute.SimMachine -import org.opendc.simulator.compute.SimProcessingUnit - -/** - * A [ScalingDriver] is responsible for switching the processor to the correct frequency. - */ -public interface ScalingDriver { - /** - * Create the scaling logic for the specified [machine] - */ - public fun createLogic(machine: SimMachine): Logic - - /** - * The logic of the scaling driver. - */ - public interface Logic { - /** - * Create the [ScalingContext] for the specified [cpu] instance. - */ - public fun createContext(cpu: SimProcessingUnit): ScalingContext - - /** - * Compute the power consumption of the processor. - * - * @return The power consumption of the processor in W. - */ - public fun computePower(): Double - } -} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/ScalingGovernor.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/ScalingGovernor.kt deleted file mode 100644 index c9aea580..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/ScalingGovernor.kt +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute.cpufreq - -/** - * A [ScalingGovernor] in the CPUFreq subsystem of OpenDC is responsible for scaling the frequency of simulated CPUs - * independent of the particular implementation of the CPU. - * - * Each of the scaling governors implements a single, possibly parametrized, performance scaling algorithm. - * - * For more information, see the documentation of the Linux CPUFreq subsystem: - * https://www.kernel.org/doc/html/latest/admin-guide/pm/cpufreq.html - */ -public interface ScalingGovernor { - /** - * Create the scaling logic for the specified [context] - */ - public fun createLogic(ctx: ScalingContext): Logic - - /** - * The logic of the scaling governor. - */ - public interface Logic { - /** - * This method is invoked when the governor is started. - */ - public fun onStart() {} - - /** - * This method is invoked when the governor should re-decide the frequency limits. - */ - public fun onLimit() {} - } -} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/SimpleScalingDriver.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/SimpleScalingDriver.kt deleted file mode 100644 index cf0bbb28..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/SimpleScalingDriver.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute.cpufreq - -import org.opendc.simulator.compute.SimMachine -import org.opendc.simulator.compute.SimProcessingUnit -import org.opendc.simulator.compute.power.PowerModel - -/** - * A [ScalingDriver] that ignores the instructions of the [ScalingGovernor] and directly computes the power consumption - * based on the specified [power model][model]. - */ -public class SimpleScalingDriver(private val model: PowerModel) : ScalingDriver { - override fun createLogic(machine: SimMachine): ScalingDriver.Logic = object : ScalingDriver.Logic { - override fun createContext(cpu: SimProcessingUnit): ScalingContext { - return object : ScalingContext { - override val machine: SimMachine = machine - - override val cpu: SimProcessingUnit = cpu - - override fun setTarget(freq: Double) {} - } - } - - override fun computePower(): Double = model.computePower(machine.usage.value) - - override fun toString(): String = "SimpleScalingDriver.Logic" - } -} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/device/SimNetworkAdapter.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/device/SimNetworkAdapter.kt new file mode 100644 index 00000000..dfb4ecf3 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/device/SimNetworkAdapter.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.device + +import org.opendc.simulator.compute.SimMachine +import org.opendc.simulator.network.SimNetworkPort + +/** + * A simulated network interface card (NIC or network adapter) that can be attached to a [SimMachine]. + */ +public abstract class SimNetworkAdapter : SimNetworkPort(), SimPeripheral { + /** + * The unidirectional bandwidth of the network adapter in Mbps. + */ + public abstract val bandwidth: Double +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/device/SimPeripheral.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/device/SimPeripheral.kt new file mode 100644 index 00000000..268271be --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/device/SimPeripheral.kt @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.device + +import org.opendc.simulator.compute.SimMachine + +/** + * A component that can be attached to a [SimMachine]. + * + * This interface represents the physical view of the peripheral and should be used to configure the physical properties + * of the peripheral. + */ +public interface SimPeripheral diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/device/SimPsu.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/device/SimPsu.kt new file mode 100644 index 00000000..09defbb5 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/device/SimPsu.kt @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.device + +import org.opendc.simulator.compute.power.PowerDriver +import org.opendc.simulator.flow.FlowConnection +import org.opendc.simulator.flow.FlowSource +import org.opendc.simulator.power.SimPowerInlet +import java.util.* + +/** + * A power supply of a [SimBareMetalMachine]. + * + * @param ratedOutputPower The rated output power of the PSU. + * @param energyEfficiency The energy efficiency of the PSU for various power draws. + */ +public class SimPsu( + private val ratedOutputPower: Double, + energyEfficiency: Map<Double, Double>, +) : SimPowerInlet() { + /** + * The power draw of the machine at this instant. + */ + public val powerDraw: Double + get() = _powerDraw + private var _powerDraw = 0.0 + + /** + * The energy efficiency of the PSU at various power draws. + */ + private val energyEfficiency = TreeMap(energyEfficiency) + + /** + * The consumer context. + */ + private var _ctx: FlowConnection? = null + + /** + * The driver that is connected to the PSU. + */ + private var _driver: PowerDriver.Logic? = null + + init { + require(energyEfficiency.isNotEmpty()) { "Must specify at least one entry for energy efficiency of PSU" } + } + + /** + * Update the power draw of the PSU. + */ + public fun update() { + _ctx?.pull() + } + + /** + * Connect the specified [PowerDriver.Logic] to this PSU. + */ + public fun connect(driver: PowerDriver.Logic) { + check(_driver == null) { "PSU already connected" } + _driver = driver + update() + } + + override fun createSource(): FlowSource = object : FlowSource { + override fun onStart(conn: FlowConnection, now: Long) { + _ctx = conn + conn.shouldSourceConverge = true + } + + override fun onStop(conn: FlowConnection, now: Long, delta: Long) { + _ctx = null + } + + override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long { + val powerDraw = computePowerDraw(_driver?.computePower() ?: 0.0) + conn.push(powerDraw) + return Long.MAX_VALUE + } + + override fun onConverge(conn: FlowConnection, now: Long, delta: Long) { + _powerDraw = conn.rate + } + } + + /** + * Compute the power draw of the PSU including the power loss. + */ + private fun computePowerDraw(load: Double): Double { + val loadPercentage = (load / ratedOutputPower).coerceIn(0.0, 1.0) + val efficiency = energyEfficiency.ceilingEntry(loadPercentage)?.value ?: 1.0 + return load / efficiency + } + + override fun toString(): String = "SimPsu[draw=$_powerDraw]" +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/interference/PerformanceInterferenceModel.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/interference/PerformanceInterferenceModel.kt deleted file mode 100644 index 4c409887..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/interference/PerformanceInterferenceModel.kt +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (c) 2020 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute.interference - -import java.util.* -import kotlin.random.Random - -/** - * Meta-data key for the [PerformanceInterferenceModel] of an image. - */ -public const val IMAGE_PERF_INTERFERENCE_MODEL: String = "image:performance-interference" - -/** - * Performance Interference Model describing the variability incurred by different sets of workloads if colocated. - * - * @param items The [PerformanceInterferenceModel.Item]s that make up this model. - */ -public class PerformanceInterferenceModel( - public val items: SortedSet<Item>, - private val random: Random = Random(0) -) { - private var intersectingItems: List<Item> = emptyList() - private val colocatedWorkloads = TreeMap<String, Int>() - - /** - * Indicate that a VM has started. - */ - public fun onStart(name: String) { - colocatedWorkloads.merge(name, 1, Int::plus) - intersectingItems = items.filter { item -> doesMatch(item) } - } - - /** - * Indicate that a VM has stopped. - */ - public fun onStop(name: String) { - colocatedWorkloads.computeIfPresent(name) { _, v -> (v - 1).takeUnless { it == 0 } } - intersectingItems = items.filter { item -> doesMatch(item) } - } - - /** - * Compute the performance interference based on the current server load. - */ - public fun apply(currentServerLoad: Double): Double { - if (intersectingItems.isEmpty()) { - return 1.0 - } - val score = intersectingItems - .firstOrNull { it.minServerLoad <= currentServerLoad } - - // Apply performance penalty to (on average) only one of the VMs - return if (score != null && random.nextInt(score.workloadNames.size) == 0) { - score.performanceScore - } else { - 1.0 - } - } - - private fun doesMatch(item: Item): Boolean { - var count = 0 - for ( - name in item.workloadNames.subSet( - colocatedWorkloads.firstKey(), - colocatedWorkloads.lastKey() + "\u0000" - ) - ) { - count += colocatedWorkloads.getOrDefault(name, 0) - if (count > 1) - return true - } - return false - } - - /** - * Model describing how a specific set of workloads causes performance variability for each workload. - * - * @param workloadNames The names of the workloads that together cause performance variability for each workload in the set. - * @param minServerLoad The minimum total server load at which this interference is activated and noticeable. - * @param performanceScore The performance score that should be applied to each workload's performance. 1 means no - * influence, <1 means that performance degrades, and >1 means that performance improves. - */ - public data class Item( - public val workloadNames: SortedSet<String>, - public val minServerLoad: Double, - public val performanceScore: Double - ) : Comparable<Item> { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as Item - - if (workloadNames != other.workloadNames) return false - - return true - } - - override fun hashCode(): Int = workloadNames.hashCode() - - override fun compareTo(other: Item): Int { - var cmp = performanceScore.compareTo(other.performanceScore) - if (cmp != 0) { - return cmp - } - - cmp = minServerLoad.compareTo(other.minServerLoad) - if (cmp != 0) { - return cmp - } - - return hashCode().compareTo(other.hashCode()) - } - } -} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimAbstractHypervisor.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimAbstractHypervisor.kt new file mode 100644 index 00000000..f6d8f628 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimAbstractHypervisor.kt @@ -0,0 +1,290 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.kernel + +import org.opendc.simulator.compute.* +import org.opendc.simulator.compute.kernel.cpufreq.ScalingGovernor +import org.opendc.simulator.compute.kernel.cpufreq.ScalingPolicy +import org.opendc.simulator.compute.kernel.interference.VmInterferenceDomain +import org.opendc.simulator.compute.model.MachineModel +import org.opendc.simulator.compute.model.ProcessingUnit +import org.opendc.simulator.flow.* +import org.opendc.simulator.flow.mux.FlowMultiplexer +import kotlin.math.roundToLong + +/** + * Abstract implementation of the [SimHypervisor] interface. + * + * @param engine The [FlowEngine] to drive the simulation. + * @param scalingGovernor The scaling governor to use for scaling the CPU frequency of the underlying hardware. + */ +public abstract class SimAbstractHypervisor( + protected val engine: FlowEngine, + private val listener: FlowConvergenceListener?, + private val scalingGovernor: ScalingGovernor?, + protected val interferenceDomain: VmInterferenceDomain? = null +) : SimHypervisor, FlowConvergenceListener { + /** + * The machine on which the hypervisor runs. + */ + protected lateinit var context: SimMachineContext + + /** + * The resource switch to use. + */ + protected abstract val mux: FlowMultiplexer + + /** + * The virtual machines running on this hypervisor. + */ + private val _vms = mutableSetOf<VirtualMachine>() + override val vms: Set<SimMachine> + get() = _vms + + /** + * The resource counters associated with the hypervisor. + */ + public override val counters: SimHypervisorCounters + get() = _counters + private val _counters = CountersImpl(this) + + /** + * The CPU capacity of the hypervisor in MHz. + */ + override val cpuCapacity: Double + get() = mux.capacity + + /** + * The CPU demand of the hypervisor in MHz. + */ + override val cpuDemand: Double + get() = mux.demand + + /** + * The CPU usage of the hypervisor in MHz. + */ + override val cpuUsage: Double + get() = mux.rate + + /** + * The scaling governors attached to the physical CPUs backing this hypervisor. + */ + private val governors = mutableListOf<ScalingGovernor.Logic>() + + /* SimHypervisor */ + override fun createMachine(model: MachineModel, interferenceId: String?): SimVirtualMachine { + require(canFit(model)) { "Machine does not fit" } + val vm = VirtualMachine(model, interferenceId) + _vms.add(vm) + return vm + } + + /* SimWorkload */ + override fun onStart(ctx: SimMachineContext) { + context = ctx + + _cpuCount = ctx.cpus.size + _cpuCapacity = ctx.cpus.sumOf { it.model.frequency } + _counters.d = _cpuCount / _cpuCapacity * 1000L + + // Clear the existing outputs of the multiplexer + mux.clearOutputs() + + for (cpu in ctx.cpus) { + val governor = scalingGovernor?.createLogic(ScalingPolicyImpl(cpu)) + if (governor != null) { + governors.add(governor) + governor.onStart() + } + + cpu.startConsumer(mux.newOutput()) + } + } + + private var _cpuCount = 0 + private var _cpuCapacity = 0.0 + + /* FlowConvergenceListener */ + override fun onConverge(now: Long, delta: Long) { + _counters.record() + + val load = cpuDemand / cpuCapacity + for (governor in governors) { + governor.onLimit(load) + } + + listener?.onConverge(now, delta) + } + + /** + * A virtual machine running on the hypervisor. + * + * @param model The machine model of the virtual machine. + */ + private inner class VirtualMachine(model: MachineModel, interferenceId: String? = null) : SimAbstractMachine(engine, parent = null, model), SimVirtualMachine { + /** + * The interference key of this virtual machine. + */ + private val interferenceKey = interferenceId?.let { interferenceDomain?.join(interferenceId) } + + /** + * The vCPUs of the machine. + */ + override val cpus = model.cpus.map { VCpu(mux, mux.newInput(interferenceKey), it) } + + /** + * The resource counters associated with the hypervisor. + */ + override val counters: SimHypervisorCounters + get() = _counters + private val _counters = object : SimHypervisorCounters { + private val d = cpus.size / cpus.sumOf { it.model.frequency } * 1000 + + override val cpuActiveTime: Long + get() = (cpus.sumOf { it.counters.actual } * d).roundToLong() + override val cpuIdleTime: Long + get() = (cpus.sumOf { it.counters.actual + it.counters.remaining } * d).roundToLong() + override val cpuStealTime: Long + get() = (cpus.sumOf { it.counters.demand - it.counters.actual } * d).roundToLong() + override val cpuLostTime: Long = (cpus.sumOf { it.counters.interference } * d).roundToLong() + } + + /** + * The CPU capacity of the hypervisor in MHz. + */ + override val cpuCapacity: Double + get() = cpus.sumOf(FlowConsumer::capacity) + + /** + * The CPU demand of the hypervisor in MHz. + */ + override val cpuDemand: Double + get() = cpus.sumOf(FlowConsumer::demand) + + /** + * The CPU usage of the hypervisor in MHz. + */ + override val cpuUsage: Double + get() = cpus.sumOf(FlowConsumer::rate) + + override fun close() { + super.close() + + for (cpu in cpus) { + cpu.close() + } + + _vms.remove(this) + if (interferenceKey != null) { + interferenceDomain?.leave(interferenceKey) + } + } + } + + /** + * A [SimProcessingUnit] of a virtual machine. + */ + private class VCpu( + private val switch: FlowMultiplexer, + private val source: FlowConsumer, + override val model: ProcessingUnit + ) : SimProcessingUnit, FlowConsumer by source { + override var capacity: Double + get() = source.capacity + set(_) { + // Ignore capacity changes + } + + override fun toString(): String = "SimAbstractHypervisor.VCpu[model=$model]" + + /** + * Close the CPU + */ + fun close() { + switch.removeInput(source) + } + } + + /** + * A [ScalingPolicy] for a physical CPU of the hypervisor. + */ + private class ScalingPolicyImpl(override val cpu: SimProcessingUnit) : ScalingPolicy { + override var target: Double + get() = cpu.capacity + set(value) { + cpu.capacity = value + } + + override val max: Double = cpu.model.frequency + + override val min: Double = 0.0 + } + + /** + * Implementation of [SimHypervisorCounters]. + */ + private class CountersImpl(private val hv: SimAbstractHypervisor) : SimHypervisorCounters { + @JvmField var d = 1.0 // Number of CPUs divided by total CPU capacity + + override val cpuActiveTime: Long + get() = _cpuTime[0] + override val cpuIdleTime: Long + get() = _cpuTime[1] + override val cpuStealTime: Long + get() = _cpuTime[2] + override val cpuLostTime: Long + get() = _cpuTime[3] + + private val _cpuTime = LongArray(4) + private val _previous = DoubleArray(4) + + /** + * Record the CPU time of the hypervisor. + */ + fun record() { + val cpuTime = _cpuTime + val previous = _previous + val counters = hv.mux.counters + + val demand = counters.demand + val actual = counters.actual + val remaining = counters.remaining + val interference = counters.interference + + val demandDelta = demand - previous[0] + val actualDelta = actual - previous[1] + val remainingDelta = remaining - previous[2] + val interferenceDelta = interference - previous[3] + + previous[0] = demand + previous[1] = actual + previous[2] = remaining + previous[3] = interference + + cpuTime[0] += (actualDelta * d).roundToLong() + cpuTime[1] += (remainingDelta * d).roundToLong() + cpuTime[2] += ((demandDelta - actualDelta) * d).roundToLong() + cpuTime[3] += (interferenceDelta * d).roundToLong() + } + } +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimFairShareHypervisor.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimFairShareHypervisor.kt new file mode 100644 index 00000000..36f76650 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimFairShareHypervisor.kt @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.kernel + +import org.opendc.simulator.compute.SimMachine +import org.opendc.simulator.compute.kernel.cpufreq.ScalingGovernor +import org.opendc.simulator.compute.kernel.interference.VmInterferenceDomain +import org.opendc.simulator.compute.model.MachineModel +import org.opendc.simulator.compute.workload.SimWorkload +import org.opendc.simulator.flow.FlowConvergenceListener +import org.opendc.simulator.flow.FlowEngine +import org.opendc.simulator.flow.mux.FlowMultiplexer +import org.opendc.simulator.flow.mux.MaxMinFlowMultiplexer + +/** + * A [SimHypervisor] that distributes the computing requirements of multiple [SimWorkload]s on a single [SimMachine] + * concurrently using weighted fair sharing. + * + * @param engine The [FlowEngine] to manage the machine's resources. + * @param listener The listener for the convergence of the system. + * @param scalingGovernor The CPU frequency scaling governor to use for the hypervisor. + * @param interferenceDomain The resource interference domain to which the hypervisor belongs. + */ +public class SimFairShareHypervisor( + engine: FlowEngine, + listener: FlowConvergenceListener?, + scalingGovernor: ScalingGovernor?, + interferenceDomain: VmInterferenceDomain?, +) : SimAbstractHypervisor(engine, listener, scalingGovernor, interferenceDomain) { + /** + * The multiplexer that distributes the computing capacity. + */ + override val mux: FlowMultiplexer = MaxMinFlowMultiplexer(engine, this, interferenceDomain) + + override fun canFit(model: MachineModel): Boolean = true +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimFairShareHypervisorProvider.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimFairShareHypervisorProvider.kt new file mode 100644 index 00000000..3136f4c8 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimFairShareHypervisorProvider.kt @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.kernel + +import org.opendc.simulator.compute.kernel.cpufreq.ScalingGovernor +import org.opendc.simulator.compute.kernel.interference.VmInterferenceDomain +import org.opendc.simulator.flow.FlowConvergenceListener +import org.opendc.simulator.flow.FlowEngine + +/** + * A [SimHypervisorProvider] for the [SimFairShareHypervisor] implementation. + */ +public class SimFairShareHypervisorProvider : SimHypervisorProvider { + override val id: String = "fair-share" + + override fun create( + engine: FlowEngine, + listener: FlowConvergenceListener?, + scalingGovernor: ScalingGovernor?, + interferenceDomain: VmInterferenceDomain?, + ): SimHypervisor = SimFairShareHypervisor(engine, listener, scalingGovernor, interferenceDomain) +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimHypervisor.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimHypervisor.kt new file mode 100644 index 00000000..57d4cf20 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimHypervisor.kt @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.kernel + +import org.opendc.simulator.compute.SimMachine +import org.opendc.simulator.compute.model.MachineModel +import org.opendc.simulator.compute.workload.SimWorkload + +/** + * A SimHypervisor facilitates the execution of multiple concurrent [SimWorkload]s, while acting as a single workload + * to another [SimMachine]. + */ +public interface SimHypervisor : SimWorkload { + /** + * The machines running on the hypervisor. + */ + public val vms: Set<SimMachine> + + /** + * The resource counters associated with the hypervisor. + */ + public val counters: SimHypervisorCounters + + /** + * The CPU usage of the hypervisor in MHz. + */ + public val cpuUsage: Double + + /** + * The CPU usage of the hypervisor in MHz. + */ + public val cpuDemand: Double + + /** + * The CPU capacity of the hypervisor in MHz. + */ + public val cpuCapacity: Double + + /** + * Determine whether the specified machine characterized by [model] can fit on this hypervisor at this moment. + */ + public fun canFit(model: MachineModel): Boolean + + /** + * Create a [SimMachine] instance on which users may run a [SimWorkload]. + * + * @param model The machine to create. + * @param interferenceId An identifier for the interference model. + */ + public fun createMachine(model: MachineModel, interferenceId: String? = null): SimVirtualMachine +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimHypervisorCounters.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimHypervisorCounters.kt new file mode 100644 index 00000000..030d9c5f --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimHypervisorCounters.kt @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.kernel + +/** + * Performance counters of a [SimHypervisor]. + */ +public interface SimHypervisorCounters { + /** + * The amount of time (in milliseconds) the CPUs of the hypervisor were actively running. + */ + public val cpuActiveTime: Long + + /** + * The amount of time (in milliseconds) the CPUs of the hypervisor were idle. + */ + public val cpuIdleTime: Long + + /** + * The amount of CPU time (in milliseconds) that virtual machines were ready to run, but were not able to. + */ + public val cpuStealTime: Long + + /** + * The amount of CPU time (in milliseconds) that was lost due to interference between virtual machines. + */ + public val cpuLostTime: Long +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimHypervisorProvider.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimHypervisorProvider.kt new file mode 100644 index 00000000..483217af --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimHypervisorProvider.kt @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.kernel + +import org.opendc.simulator.compute.kernel.cpufreq.ScalingGovernor +import org.opendc.simulator.compute.kernel.interference.VmInterferenceDomain +import org.opendc.simulator.flow.FlowConvergenceListener +import org.opendc.simulator.flow.FlowEngine + +/** + * A service provider interface for constructing a [SimHypervisor]. + */ +public interface SimHypervisorProvider { + /** + * A unique identifier for this hypervisor implementation. + * + * Each hypervisor must provide a unique ID, so that they can be selected by the user. + * When in doubt, you may use the fully qualified name of your custom [SimHypervisor] implementation class. + */ + public val id: String + + /** + * Create a new [SimHypervisor] instance. + */ + public fun create( + engine: FlowEngine, + listener: FlowConvergenceListener? = null, + scalingGovernor: ScalingGovernor? = null, + interferenceDomain: VmInterferenceDomain? = null, + ): SimHypervisor +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimSpaceSharedHypervisor.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimSpaceSharedHypervisor.kt new file mode 100644 index 00000000..82f8df38 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimSpaceSharedHypervisor.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.kernel + +import org.opendc.simulator.compute.kernel.cpufreq.ScalingGovernor +import org.opendc.simulator.compute.model.MachineModel +import org.opendc.simulator.flow.FlowConvergenceListener +import org.opendc.simulator.flow.FlowEngine +import org.opendc.simulator.flow.mux.FlowMultiplexer +import org.opendc.simulator.flow.mux.ForwardingFlowMultiplexer + +/** + * A [SimHypervisor] that allocates its sub-resources exclusively for the virtual machine that it hosts. + */ +public class SimSpaceSharedHypervisor( + engine: FlowEngine, + listener: FlowConvergenceListener?, + scalingGovernor: ScalingGovernor?, +) : SimAbstractHypervisor(engine, listener, scalingGovernor) { + override val mux: FlowMultiplexer = ForwardingFlowMultiplexer(engine) + + override fun canFit(model: MachineModel): Boolean { + return mux.outputs.size - mux.inputs.size >= model.cpus.size + } +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimSpaceSharedHypervisorProvider.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimSpaceSharedHypervisorProvider.kt new file mode 100644 index 00000000..dd6fb0b1 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimSpaceSharedHypervisorProvider.kt @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.kernel + +import org.opendc.simulator.compute.kernel.cpufreq.ScalingGovernor +import org.opendc.simulator.compute.kernel.interference.VmInterferenceDomain +import org.opendc.simulator.flow.FlowConvergenceListener +import org.opendc.simulator.flow.FlowEngine + +/** + * A [SimHypervisorProvider] for the [SimSpaceSharedHypervisor] implementation. + */ +public class SimSpaceSharedHypervisorProvider : SimHypervisorProvider { + override val id: String = "space-shared" + + override fun create( + engine: FlowEngine, + listener: FlowConvergenceListener?, + scalingGovernor: ScalingGovernor?, + interferenceDomain: VmInterferenceDomain?, + ): SimHypervisor = SimSpaceSharedHypervisor(engine, listener, scalingGovernor) +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimVirtualMachine.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimVirtualMachine.kt new file mode 100644 index 00000000..36219ef2 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimVirtualMachine.kt @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.kernel + +import org.opendc.simulator.compute.SimMachine + +/** + * A virtual [SimMachine] running on top of another [SimMachine]. + */ +public interface SimVirtualMachine : SimMachine { + /** + * The resource counters associated with the virtual machine. + */ + public val counters: SimHypervisorCounters + + /** + * The CPU usage of the VM in MHz. + */ + public val cpuUsage: Double + + /** + * The CPU usage of the VM in MHz. + */ + public val cpuDemand: Double + + /** + * The CPU capacity of the VM in MHz. + */ + public val cpuCapacity: Double +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/ConservativeScalingGovernor.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/ConservativeScalingGovernor.kt new file mode 100644 index 00000000..1a03221d --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/ConservativeScalingGovernor.kt @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.kernel.cpufreq + +/** + * A CPUFreq [ScalingGovernor] that models the conservative scaling governor in the Linux kernel. + */ +public class ConservativeScalingGovernor(public val threshold: Double = 0.8, private val stepSize: Double = -1.0) : + ScalingGovernor { + override fun createLogic(policy: ScalingPolicy): ScalingGovernor.Logic = object : ScalingGovernor.Logic { + /** + * The step size to use. + */ + private val stepSize = if (this@ConservativeScalingGovernor.stepSize < 0) { + // https://github.com/torvalds/linux/blob/master/drivers/cpufreq/cpufreq_conservative.c#L33 + policy.max * 0.05 + } else { + this@ConservativeScalingGovernor.stepSize.coerceAtMost(policy.max) + } + + /** + * The previous load of the CPU. + */ + private var previousLoad = threshold + + override fun onStart() { + policy.target = policy.min + } + + override fun onLimit(load: Double) { + val currentTarget = policy.target + if (load > threshold) { + // Check for load increase (see: https://github.com/torvalds/linux/blob/master/drivers/cpufreq/cpufreq_conservative.c#L102) + val step = when { + load > previousLoad -> stepSize + load < previousLoad -> -stepSize + else -> 0.0 + } + policy.target = (currentTarget + step).coerceIn(policy.min, policy.max) + } + previousLoad = load + } + } + + override fun toString(): String = "ConservativeScalingGovernor[threshold=$threshold,stepSize=$stepSize]" +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/OnDemandScalingGovernor.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/OnDemandScalingGovernor.kt new file mode 100644 index 00000000..aef15ce9 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/OnDemandScalingGovernor.kt @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.kernel.cpufreq + +/** + * A CPUFreq [ScalingGovernor] that models the on-demand scaling governor in the Linux kernel. + */ +public class OnDemandScalingGovernor(public val threshold: Double = 0.8) : ScalingGovernor { + override fun createLogic(policy: ScalingPolicy): ScalingGovernor.Logic = object : ScalingGovernor.Logic { + /** + * The multiplier used for the linear frequency scaling. + */ + private val multiplier = (policy.max - policy.min) / 100 + + override fun onStart() { + policy.target = policy.min + } + + override fun onLimit(load: Double) { + policy.target = if (load < threshold) { + /* Proportional scaling (see: https://github.com/torvalds/linux/blob/master/drivers/cpufreq/cpufreq_ondemand.c#L151). */ + policy.min + load * multiplier + } else { + policy.max + } + } + } + + override fun toString(): String = "OnDemandScalingGovernor[threshold=$threshold]" +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/PerformanceScalingGovernor.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/PerformanceScalingGovernor.kt new file mode 100644 index 00000000..13109a9a --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/PerformanceScalingGovernor.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.kernel.cpufreq + +/** + * A CPUFreq [ScalingGovernor] that causes the highest possible frequency to be requested from the resource. + */ +public class PerformanceScalingGovernor : ScalingGovernor { + override fun createLogic(policy: ScalingPolicy): ScalingGovernor.Logic = object : ScalingGovernor.Logic { + override fun onStart() { + policy.target = policy.max + } + } + + override fun toString(): String = "PerformanceScalingGovernor" +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/PowerSaveScalingGovernor.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/PowerSaveScalingGovernor.kt new file mode 100644 index 00000000..32c0703a --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/PowerSaveScalingGovernor.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.kernel.cpufreq + +/** + * A CPUFreq [ScalingGovernor] that causes the lowest possible frequency to be requested from the resource. + */ +public class PowerSaveScalingGovernor : ScalingGovernor { + override fun createLogic(policy: ScalingPolicy): ScalingGovernor.Logic = object : ScalingGovernor.Logic { + override fun onStart() { + policy.target = policy.min + } + } + + override fun toString(): String = "PowerSaveScalingGovernor" +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/ScalingGovernor.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/ScalingGovernor.kt new file mode 100644 index 00000000..d33827db --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/ScalingGovernor.kt @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.kernel.cpufreq + +/** + * A [ScalingGovernor] in the CPUFreq subsystem of OpenDC is responsible for scaling the frequency of simulated CPUs + * independent of the particular implementation of the CPU. + * + * Each of the scaling governors implements a single, possibly parametrized, performance scaling algorithm. + * + * For more information, see the documentation of the Linux CPUFreq subsystem: + * https://www.kernel.org/doc/html/latest/admin-guide/pm/cpufreq.html + */ +public interface ScalingGovernor { + /** + * Create the scaling logic for the specified [policy] + */ + public fun createLogic(policy: ScalingPolicy): Logic + + /** + * The logic of the scaling governor. + */ + public interface Logic { + /** + * This method is invoked when the governor is started. + */ + public fun onStart() {} + + /** + * This method is invoked when the governor should re-decide the frequency limits. + * + * @param load The load of the system. + */ + public fun onLimit(load: Double) {} + } +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/ScalingPolicy.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/ScalingPolicy.kt new file mode 100644 index 00000000..f9351896 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/ScalingPolicy.kt @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.kernel.cpufreq + +import org.opendc.simulator.compute.SimProcessingUnit + +/** + * An interface that holds the state managed by a [ScalingGovernor] and used by the underlying machine to control the + * CPU frequencies. + */ +public interface ScalingPolicy { + /** + * The processing unit that is associated with this policy. + */ + public val cpu: SimProcessingUnit + + /** + * The target frequency which the CPU should attempt to attain. + */ + public var target: Double + + /** + * The minimum frequency to which the CPU may scale. + */ + public val min: Double + + /** + * The maximum frequency to which the CPU may scale. + */ + public val max: Double +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/interference/VmInterferenceDomain.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/interference/VmInterferenceDomain.kt new file mode 100644 index 00000000..b737d61a --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/interference/VmInterferenceDomain.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.kernel.interference + +import org.opendc.simulator.flow.interference.InterferenceDomain +import org.opendc.simulator.flow.interference.InterferenceKey + +/** + * The interference domain of a hypervisor. + */ +public interface VmInterferenceDomain : InterferenceDomain { + /** + * Join this interference domain. + * + * @param id The identifier of the virtual machine. + */ + public fun join(id: String): InterferenceKey + + /** + * Leave this interference domain. + */ + public fun leave(key: InterferenceKey) +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/interference/VmInterferenceGroup.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/interference/VmInterferenceGroup.kt new file mode 100644 index 00000000..708ddede --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/interference/VmInterferenceGroup.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.kernel.interference + +/** + * A group of virtual machines that together can interfere when operating on the same resources, causing performance + * variability. + */ +public data class VmInterferenceGroup( + /** + * The minimum load of the host before the interference occurs. + */ + public val targetLoad: Double, + + /** + * A score in [0, 1] representing the performance variability as a result of resource interference. + */ + public val score: Double, + + /** + * The members of this interference group. + */ + public val members: Set<String> +) diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/interference/VmInterferenceModel.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/interference/VmInterferenceModel.kt new file mode 100644 index 00000000..b3d72507 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/interference/VmInterferenceModel.kt @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.kernel.interference + +import org.opendc.simulator.flow.interference.InterferenceKey +import java.util.* + +/** + * An interference model that models the resource interference between virtual machines on a host. + * + * @param groups The groups of virtual machines that interfere with each other. + * @param random The [Random] instance to select the affected virtual machines. + */ +public class VmInterferenceModel( + private val groups: List<VmInterferenceGroup>, + private val random: Random = Random(0) +) { + /** + * Construct a new [VmInterferenceDomain]. + */ + public fun newDomain(): VmInterferenceDomain = object : VmInterferenceDomain { + /** + * The stateful groups of this domain. + */ + private val groups = this@VmInterferenceModel.groups.map { GroupContext(it) } + + /** + * The set of keys active in this domain. + */ + private val keys = mutableSetOf<InterferenceKeyImpl>() + + override fun join(id: String): InterferenceKey { + val key = InterferenceKeyImpl(id, groups.filter { id in it }.sortedBy { it.group.targetLoad }) + keys += key + return key + } + + override fun leave(key: InterferenceKey) { + if (key is InterferenceKeyImpl) { + keys -= key + key.leave() + } + } + + override fun apply(key: InterferenceKey?, load: Double): Double { + if (key == null || key !is InterferenceKeyImpl) { + return 1.0 + } + + val ctx = key.findGroup(load) + val group = ctx?.group + + // Apply performance penalty to (on average) only one of the VMs + return if (group != null && random.nextInt(group.members.size) == 0) { + group.score + } else { + 1.0 + } + } + + override fun toString(): String = "VmInterferenceDomain" + } + + /** + * An interference key. + * + * @param id The identifier of the member. + * @param groups The groups to which the key belongs. + */ + private inner class InterferenceKeyImpl(val id: String, private val groups: List<GroupContext>) : InterferenceKey { + init { + for (group in groups) { + group.join(this) + } + } + + /** + * Find the active group that applies for the interference member. + */ + fun findGroup(load: Double): GroupContext? { + // Find the first active group whose target load is lower than the current load + val index = groups.binarySearchBy(load) { it.group.targetLoad } + val target = if (index >= 0) index else -(index + 1) + + // Check whether there are active groups ahead of the index + for (i in target until groups.size) { + val group = groups[i] + if (group.group.targetLoad > load) { + break + } else if (group.isActive) { + return group + } + } + + // Check whether there are active groups before the index + for (i in (target - 1) downTo 0) { + val group = groups[i] + if (group.isActive) { + return group + } + } + + return null + } + + /** + * Leave all the groups. + */ + fun leave() { + for (group in groups) { + group.leave(this) + } + } + } + + /** + * A group context is used to track the active keys per interference group. + */ + private inner class GroupContext(val group: VmInterferenceGroup) { + /** + * The active keys that are part of this group. + */ + private val keys = mutableSetOf<InterferenceKeyImpl>() + + /** + * A flag to indicate that the group is active. + */ + val isActive + get() = keys.size > 1 + + /** + * Determine whether the specified [id] is part of this group. + */ + operator fun contains(id: String): Boolean = id in group.members + + /** + * Join this group with the specified [key]. + */ + fun join(key: InterferenceKeyImpl) { + keys += key + } + + /** + * Leave this group with the specified [key]. + */ + fun leave(key: InterferenceKeyImpl) { + keys -= key + } + } +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/MachineModel.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/MachineModel.kt new file mode 100644 index 00000000..7e4d7191 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/MachineModel.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.model + +/** + * A description of the physical or virtual machine on which a bootable image runs. + * + * @property cpus The list of processing units available to the image. + * @property memory The list of memory units available to the image. + * @property net A list of network adapters available to the machine. + * @property storage A list of storage devices available to the machine. + */ +public data class MachineModel( + public val cpus: List<ProcessingUnit>, + public val memory: List<MemoryUnit>, + public val net: List<NetworkAdapter> = emptyList(), + public val storage: List<StorageDevice> = emptyList() +) diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/NetworkAdapter.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/NetworkAdapter.kt new file mode 100644 index 00000000..46472144 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/NetworkAdapter.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.model + +/** + * A description of a network adapter that is + * + * @property vendor The vendor of the network adapter. + * @property modelName The model name of the network adapter. + * @property bandwidth The bandwidth of the network adapter in Mbps. + */ +public data class NetworkAdapter( + public val vendor: String, + public val modelName: String, + public val bandwidth: Double +) diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/StorageDevice.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/StorageDevice.kt new file mode 100644 index 00000000..2621ad6d --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/StorageDevice.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.model + +/** + * Model for a physical storage device attached to a machine. + * + * @property vendor The vendor of the storage device. + * @property modelName The model name of the device. + * @property capacity The capacity of the device. + * @property readBandwidth The read bandwidth of the device in MBps. + * @property writeBandwidth The write bandwidth of the device in MBps. + */ +public data class StorageDevice( + public val vendor: String, + public val modelName: String, + public val capacity: Double, + public val readBandwidth: Double, + public val writeBandwidth: Double +) diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/AsymptoticPowerModel.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/AsymptoticPowerModel.kt index ddc6d5b1..62b85e12 100644 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/AsymptoticPowerModel.kt +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/AsymptoticPowerModel.kt @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + package org.opendc.simulator.compute.power import kotlin.math.E diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/ConstantPowerModel.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/ConstantPowerModel.kt index b8cb8412..0fe32b0d 100644 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/ConstantPowerModel.kt +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/ConstantPowerModel.kt @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + package org.opendc.simulator.compute.power /** diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/CubicPowerModel.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/CubicPowerModel.kt index 9c44438a..0d3bf6cc 100644 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/CubicPowerModel.kt +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/CubicPowerModel.kt @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + package org.opendc.simulator.compute.power import kotlin.math.pow diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/InterpolationPowerModel.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/InterpolationPowerModel.kt index cbfcd810..2694700c 100644 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/InterpolationPowerModel.kt +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/InterpolationPowerModel.kt @@ -1,6 +1,27 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + package org.opendc.simulator.compute.power -import org.yaml.snakeyaml.Yaml import kotlin.math.ceil import kotlin.math.floor import kotlin.math.max @@ -15,8 +36,6 @@ import kotlin.math.min * @see <a href="http://www.spec.org/power_ssj2008/results/res2011q1/">Machines used in the SPEC benchmark</a> */ public class InterpolationPowerModel(private val powerValues: List<Double>) : PowerModel { - public constructor(hardwareName: String) : this(loadAveragePowerValue(hardwareName)) - public override fun computePower(utilization: Double): Double { val clampedUtilization = min(1.0, max(0.0, utilization)) val utilizationFlr = floor(clampedUtilization * 10).toInt() @@ -41,14 +60,4 @@ public class InterpolationPowerModel(private val powerValues: List<Double>) : Po * @return the power consumption for the given utilization percentage */ private fun getAveragePowerValue(index: Int): Double = powerValues[index] - - private companion object { - private fun loadAveragePowerValue(hardwareName: String, path: String = "spec_machines.yml"): List<Double> { - val content = this::class - .java.classLoader - .getResourceAsStream(path) - val hardwareToAveragePowerValues: Map<String, List<Double>> = Yaml().load(content) - return hardwareToAveragePowerValues.getOrDefault(hardwareName, listOf()) - } - } } diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/LinearPowerModel.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/LinearPowerModel.kt index 0f45afae..dadc56ec 100644 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/LinearPowerModel.kt +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/LinearPowerModel.kt @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + package org.opendc.simulator.compute.power /** diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/MsePowerModel.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/MsePowerModel.kt index 8486d680..612ce2fc 100644 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/MsePowerModel.kt +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/MsePowerModel.kt @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + package org.opendc.simulator.compute.power import kotlin.math.pow diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/PStatePowerDriver.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/PStatePowerDriver.kt new file mode 100644 index 00000000..f71446f8 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/PStatePowerDriver.kt @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.power + +import org.opendc.simulator.compute.SimMachine +import org.opendc.simulator.compute.SimProcessingUnit +import java.util.* +import kotlin.math.max +import kotlin.math.min + +/** + * A [PowerDriver] that computes the power draw using multiple [PowerModel]s based on multiple frequency states. + * + * @param states A map describing the states of the driver. + */ +public class PStatePowerDriver(states: Map<Double, PowerModel>) : PowerDriver { + /** + * The P-States defined by the user and ordered by key. + */ + private val states: TreeMap<Double, PowerModel> = TreeMap(states) + + override fun createLogic(machine: SimMachine, cpus: List<SimProcessingUnit>): PowerDriver.Logic = object : PowerDriver.Logic { + override fun computePower(): Double { + var targetFreq = 0.0 + var totalSpeed = 0.0 + + for (cpu in cpus) { + targetFreq = max(cpu.capacity, targetFreq) + totalSpeed += cpu.rate + } + + val maxFreq = states.lastKey() + val (actualFreq, model) = states.ceilingEntry(min(maxFreq, targetFreq)) + val utilization = totalSpeed / (actualFreq * cpus.size) + return model.computePower(utilization) + } + } + + override fun toString(): String = "PStatePowerDriver[states=$states]" +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/PowerDriver.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/PowerDriver.kt new file mode 100644 index 00000000..1a46dd4a --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/PowerDriver.kt @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.power + +import org.opendc.simulator.compute.SimMachine +import org.opendc.simulator.compute.SimProcessingUnit + +/** + * A [PowerDriver] is responsible for tracking the power usage for a component of the machine. + */ +public interface PowerDriver { + /** + * Create the driver logic for the specified [machine]. + */ + public fun createLogic(machine: SimMachine, cpus: List<SimProcessingUnit>): Logic + + /** + * The logic of the power driver. + */ + public interface Logic { + /** + * Compute the power consumption of the component. + * + * @return The power consumption of the component in W. + */ + public fun computePower(): Double + } +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/PowerModel.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/PowerModel.kt index 1387e65a..decb2420 100644 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/PowerModel.kt +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/PowerModel.kt @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + package org.opendc.simulator.compute.power import org.opendc.simulator.compute.SimMachine diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SimplePowerDriver.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SimplePowerDriver.kt new file mode 100644 index 00000000..34e91c35 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SimplePowerDriver.kt @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.power + +import org.opendc.simulator.compute.SimMachine +import org.opendc.simulator.compute.SimProcessingUnit + +/** + * A [PowerDriver] that computes the power consumption based on a single specified [power model][model]. + */ +public class SimplePowerDriver(private val model: PowerModel) : PowerDriver { + override fun createLogic(machine: SimMachine, cpus: List<SimProcessingUnit>): PowerDriver.Logic = object : PowerDriver.Logic { + + override fun computePower(): Double { + var targetFreq = 0.0 + var totalSpeed = 0.0 + + for (cpu in cpus) { + targetFreq += cpu.capacity + totalSpeed += cpu.rate + } + + return model.computePower(totalSpeed / targetFreq) + } + } + + override fun toString(): String = "SimplePowerDriver[model=$model]" +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SqrtPowerModel.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SqrtPowerModel.kt index afa1d82f..0665dbd9 100644 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SqrtPowerModel.kt +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SqrtPowerModel.kt @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + package org.opendc.simulator.compute.power import kotlin.math.sqrt diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SquarePowerModel.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SquarePowerModel.kt index 82a9d37d..e4ae88a9 100644 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SquarePowerModel.kt +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SquarePowerModel.kt @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + package org.opendc.simulator.compute.power import kotlin.math.pow diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/ZeroIdlePowerDecorator.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/ZeroIdlePowerDecorator.kt index 19dfcadd..886227e1 100644 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/ZeroIdlePowerDecorator.kt +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/ZeroIdlePowerDecorator.kt @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + package org.opendc.simulator.compute.power /** diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimFlopsWorkload.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimFlopsWorkload.kt index 63c9d28c..99f4a1e1 100644 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimFlopsWorkload.kt +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimFlopsWorkload.kt @@ -23,9 +23,7 @@ package org.opendc.simulator.compute.workload import org.opendc.simulator.compute.SimMachineContext -import org.opendc.simulator.compute.model.ProcessingUnit -import org.opendc.simulator.resources.SimResourceConsumer -import org.opendc.simulator.resources.consumer.SimWorkConsumer +import org.opendc.simulator.flow.source.FixedFlowSource /** * A [SimWorkload] that models applications as a static number of floating point operations ([flops]) executed on @@ -43,10 +41,11 @@ public class SimFlopsWorkload( require(utilization > 0.0 && utilization <= 1.0) { "Utilization must be in (0, 1]" } } - override fun onStart(ctx: SimMachineContext) {} - - override fun getConsumer(ctx: SimMachineContext, cpu: ProcessingUnit): SimResourceConsumer { - return SimWorkConsumer(flops.toDouble() / ctx.cpus.size, utilization) + override fun onStart(ctx: SimMachineContext) { + val lifecycle = SimWorkloadLifecycle(ctx) + for (cpu in ctx.cpus) { + cpu.startConsumer(lifecycle.waitFor(FixedFlowSource(flops.toDouble() / ctx.cpus.size, utilization))) + } } override fun toString(): String = "SimFlopsWorkload(FLOPs=$flops,utilization=$utilization)" diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimRuntimeWorkload.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimRuntimeWorkload.kt index a3420e32..2ef3bc43 100644 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimRuntimeWorkload.kt +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimRuntimeWorkload.kt @@ -23,9 +23,7 @@ package org.opendc.simulator.compute.workload import org.opendc.simulator.compute.SimMachineContext -import org.opendc.simulator.compute.model.ProcessingUnit -import org.opendc.simulator.resources.SimResourceConsumer -import org.opendc.simulator.resources.consumer.SimWorkConsumer +import org.opendc.simulator.flow.source.FixedFlowSource /** * A [SimWorkload] that models application execution as a single duration. @@ -42,11 +40,12 @@ public class SimRuntimeWorkload( require(utilization > 0.0 && utilization <= 1.0) { "Utilization must be in (0, 1]" } } - override fun onStart(ctx: SimMachineContext) {} - - override fun getConsumer(ctx: SimMachineContext, cpu: ProcessingUnit): SimResourceConsumer { - val limit = cpu.frequency * utilization - return SimWorkConsumer((limit / 1000) * duration, utilization) + override fun onStart(ctx: SimMachineContext) { + val lifecycle = SimWorkloadLifecycle(ctx) + for (cpu in ctx.cpus) { + val limit = cpu.capacity * utilization + cpu.startConsumer(lifecycle.waitFor(FixedFlowSource((limit / 1000) * duration, utilization))) + } } override fun toString(): String = "SimRuntimeWorkload(duration=$duration,utilization=$utilization)" diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimTrace.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimTrace.kt new file mode 100644 index 00000000..4f567b55 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimTrace.kt @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.workload + +import org.opendc.simulator.compute.model.ProcessingUnit +import org.opendc.simulator.flow.FlowConnection +import org.opendc.simulator.flow.FlowSource +import kotlin.math.min + +/** + * A workload trace that describes the resource utilization over time in a collection of [SimTraceFragment]s. + * + * @param usageCol The column containing the CPU usage of each fragment (in MHz). + * @param timestampCol The column containing the starting timestamp for each fragment (in epoch millis). + * @param deadlineCol The column containing the ending timestamp for each fragment (in epoch millis). + * @param coresCol The column containing the utilized cores. + * @param size The number of fragments in the trace. + */ +public class SimTrace( + private val usageCol: DoubleArray, + private val timestampCol: LongArray, + private val deadlineCol: LongArray, + private val coresCol: IntArray, + private val size: Int, +) { + init { + require(size >= 0) { "Invalid trace size" } + require(usageCol.size >= size) { "Invalid number of usage entries" } + require(timestampCol.size >= size) { "Invalid number of timestamp entries" } + require(deadlineCol.size >= size) { "Invalid number of deadline entries" } + require(coresCol.size >= size) { "Invalid number of core entries" } + } + + public companion object { + /** + * Construct a [SimTrace] with the specified fragments. + */ + public fun ofFragments(fragments: List<SimTraceFragment>): SimTrace { + val size = fragments.size + val usageCol = DoubleArray(size) + val timestampCol = LongArray(size) + val deadlineCol = LongArray(size) + val coresCol = IntArray(size) + + for (i in fragments.indices) { + val fragment = fragments[i] + usageCol[i] = fragment.usage + timestampCol[i] = fragment.timestamp + deadlineCol[i] = fragment.timestamp + fragment.duration + coresCol[i] = fragment.cores + } + + return SimTrace(usageCol, timestampCol, deadlineCol, coresCol, size) + } + + /** + * Construct a [SimTrace] with the specified fragments. + */ + @JvmStatic + public fun ofFragments(vararg fragments: SimTraceFragment): SimTrace { + val size = fragments.size + val usageCol = DoubleArray(size) + val timestampCol = LongArray(size) + val deadlineCol = LongArray(size) + val coresCol = IntArray(size) + + for (i in fragments.indices) { + val fragment = fragments[i] + usageCol[i] = fragment.usage + timestampCol[i] = fragment.timestamp + deadlineCol[i] = fragment.timestamp + fragment.duration + coresCol[i] = fragment.cores + } + + return SimTrace(usageCol, timestampCol, deadlineCol, coresCol, size) + } + + /** + * Create a [SimTrace.Builder] instance. + */ + @JvmStatic + public fun builder(): Builder = Builder() + } + + /** + * Construct a new [FlowSource] for the specified [cpu]. + */ + public fun newSource(cpu: ProcessingUnit, offset: Long): FlowSource { + return CpuConsumer(cpu, offset, usageCol, timestampCol, deadlineCol, coresCol, size) + } + + /** + * A builder class for a [SimTrace]. + */ + public class Builder internal constructor() { + /** + * The columns of the trace. + */ + private var usageCol: DoubleArray = DoubleArray(16) + private var timestampCol: LongArray = LongArray(16) + private var deadlineCol: LongArray = LongArray(16) + private var coresCol: IntArray = IntArray(16) + + /** + * The number of entries in the trace. + */ + private var size = 0 + + /** + * Add the specified [SimTraceFragment] to the trace. + */ + public fun add(fragment: SimTraceFragment) { + add(fragment.timestamp, fragment.timestamp + fragment.duration, fragment.usage, fragment.cores) + } + + /** + * Add a fragment to the trace. + * + * @param timestamp Timestamp at which the fragment starts (in epoch millis). + * @param deadline Timestamp at which the fragment ends (in epoch millis). + * @param usage CPU usage of this fragment. + * @param cores Number of cores used. + */ + public fun add(timestamp: Long, deadline: Long, usage: Double, cores: Int) { + val size = size + + if (size == usageCol.size) { + grow() + } + + timestampCol[size] = timestamp + deadlineCol[size] = deadline + usageCol[size] = usage + coresCol[size] = cores + + this.size++ + } + + /** + * Helper function to grow the capacity of the column arrays. + */ + private fun grow() { + val arraySize = usageCol.size + val newSize = arraySize * 2 + + usageCol = usageCol.copyOf(newSize) + timestampCol = timestampCol.copyOf(newSize) + deadlineCol = deadlineCol.copyOf(newSize) + coresCol = coresCol.copyOf(newSize) + } + + /** + * Construct the immutable [SimTrace]. + */ + public fun build(): SimTrace { + return SimTrace(usageCol, timestampCol, deadlineCol, coresCol, size) + } + } + + /** + * A CPU consumer for the trace workload. + */ + private class CpuConsumer( + cpu: ProcessingUnit, + private val offset: Long, + private val usageCol: DoubleArray, + private val timestampCol: LongArray, + private val deadlineCol: LongArray, + private val coresCol: IntArray, + private val size: Int + ) : FlowSource { + private val id = cpu.id + private val coreCount = cpu.node.coreCount + + /** + * The index in the trace. + */ + private var _idx = 0 + + override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long { + val size = size + val nowOffset = now - offset + + var idx = _idx + val deadlines = deadlineCol + var deadline = deadlines[idx] + + while (deadline <= nowOffset && ++idx < size) { + deadline = deadlines[idx] + } + + if (idx >= size) { + conn.close() + return Long.MAX_VALUE + } + + _idx = idx + val timestamp = timestampCol[idx] + + // Fragment is in the future + if (timestamp > nowOffset) { + conn.push(0.0) + return timestamp - nowOffset + } + + val cores = min(coreCount, coresCol[idx]) + val usage = usageCol[idx] + + conn.push(if (id < cores) usage / cores else 0.0) + return deadline - nowOffset + } + } +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimTraceFragment.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimTraceFragment.kt new file mode 100644 index 00000000..5285847f --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimTraceFragment.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.workload + +/** + * A fragment of the workload trace. + * + * @param timestamp The timestamp at which the fragment starts (in epoch millis). + * @param duration The duration of the fragment (in milliseconds). + * @param usage The CPU usage during the fragment (in MHz). + * @param cores The amount of cores utilized during the fragment. + */ +public data class SimTraceFragment( + @JvmField val timestamp: Long, + @JvmField val duration: Long, + @JvmField val usage: Double, + @JvmField val cores: Int +) diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimTraceWorkload.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimTraceWorkload.kt index ffb332d1..53c98409 100644 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimTraceWorkload.kt +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimTraceWorkload.kt @@ -23,72 +23,22 @@ package org.opendc.simulator.compute.workload import org.opendc.simulator.compute.SimMachineContext -import org.opendc.simulator.compute.model.ProcessingUnit -import org.opendc.simulator.resources.SimResourceCommand -import org.opendc.simulator.resources.SimResourceConsumer -import org.opendc.simulator.resources.SimResourceContext -import org.opendc.simulator.resources.consumer.SimConsumerBarrier /** * A [SimWorkload] that replays a workload trace consisting of multiple fragments, each indicating the resource * consumption for some period of time. + * + * @param trace The trace of fragments to use. + * @param offset The offset for the timestamps. */ -public class SimTraceWorkload(public val trace: Sequence<Fragment>) : SimWorkload { - private var offset = Long.MIN_VALUE - private val iterator = trace.iterator() - private var fragment: Fragment? = null - private lateinit var barrier: SimConsumerBarrier - +public class SimTraceWorkload(private val trace: SimTrace, private val offset: Long = 0L) : SimWorkload { override fun onStart(ctx: SimMachineContext) { - check(offset == Long.MIN_VALUE) { "Workload does not support re-use" } - - barrier = SimConsumerBarrier(ctx.cpus.size) - fragment = nextFragment() - offset = ctx.clock.millis() - } - - override fun getConsumer(ctx: SimMachineContext, cpu: ProcessingUnit): SimResourceConsumer { - return object : SimResourceConsumer { - override fun onNext(ctx: SimResourceContext): SimResourceCommand { - val now = ctx.clock.millis() - val fragment = fragment ?: return SimResourceCommand.Exit - val usage = fragment.usage / fragment.cores - val work = (fragment.duration / 1000) * usage - val deadline = offset + fragment.duration - - assert(deadline >= now) { "Deadline already passed" } + val lifecycle = SimWorkloadLifecycle(ctx) - val cmd = - if (cpu.id < fragment.cores && work > 0.0) - SimResourceCommand.Consume(work, usage, deadline) - else - SimResourceCommand.Idle(deadline) - - if (barrier.enter()) { - this@SimTraceWorkload.fragment = nextFragment() - this@SimTraceWorkload.offset += fragment.duration - } - - return cmd - } + for (cpu in ctx.cpus) { + cpu.startConsumer(lifecycle.waitFor(trace.newSource(cpu.model, offset))) } } override fun toString(): String = "SimTraceWorkload" - - /** - * Obtain the next fragment. - */ - private fun nextFragment(): Fragment? { - return if (iterator.hasNext()) { - iterator.next() - } else { - null - } - } - - /** - * A fragment of the workload. - */ - public data class Fragment(val duration: Long, val usage: Double, val cores: Int) } diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimWorkload.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimWorkload.kt index bdc12bb5..b80665fa 100644 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimWorkload.kt +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimWorkload.kt @@ -23,8 +23,6 @@ package org.opendc.simulator.compute.workload import org.opendc.simulator.compute.SimMachineContext -import org.opendc.simulator.compute.model.ProcessingUnit -import org.opendc.simulator.resources.SimResourceConsumer /** * A model that characterizes the runtime behavior of some particular workload. @@ -35,11 +33,8 @@ import org.opendc.simulator.resources.SimResourceConsumer public interface SimWorkload { /** * This method is invoked when the workload is started. + * + * @param ctx The execution context in which the machine runs. */ public fun onStart(ctx: SimMachineContext) - - /** - * Obtain the resource consumer for the specified processing unit. - */ - public fun getConsumer(ctx: SimMachineContext, cpu: ProcessingUnit): SimResourceConsumer } diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimWorkloadLifecycle.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimWorkloadLifecycle.kt new file mode 100644 index 00000000..cc4f1f6a --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimWorkloadLifecycle.kt @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.workload + +import org.opendc.simulator.compute.SimMachineContext +import org.opendc.simulator.flow.FlowConnection +import org.opendc.simulator.flow.FlowSource + +/** + * A helper class to manage the lifecycle of a [SimWorkload] + */ +public class SimWorkloadLifecycle(private val ctx: SimMachineContext) { + /** + * The resource consumers which represent the lifecycle of the workload. + */ + private val waiting = mutableSetOf<FlowSource>() + + /** + * Wait for the specified [consumer] to complete before ending the lifecycle of the workload. + */ + public fun waitFor(consumer: FlowSource): FlowSource { + waiting.add(consumer) + return object : FlowSource by consumer { + override fun onStop(conn: FlowConnection, now: Long, delta: Long) { + try { + consumer.onStop(conn, now, delta) + } finally { + complete(consumer) + } + } + override fun toString(): String = "SimWorkloadLifecycle.Consumer[delegate=$consumer]" + } + } + + /** + * Complete the specified [FlowSource]. + */ + private fun complete(consumer: FlowSource) { + if (waiting.remove(consumer) && waiting.isEmpty()) { + ctx.close() + } + } +} diff --git a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimHypervisorTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimHypervisorTest.kt deleted file mode 100644 index 8886caa7..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimHypervisorTest.kt +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright (c) 2020 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute - -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.flow.toList -import kotlinx.coroutines.launch -import kotlinx.coroutines.yield -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertAll -import org.opendc.simulator.compute.cpufreq.PerformanceScalingGovernor -import org.opendc.simulator.compute.cpufreq.SimpleScalingDriver -import org.opendc.simulator.compute.model.MemoryUnit -import org.opendc.simulator.compute.model.ProcessingNode -import org.opendc.simulator.compute.model.ProcessingUnit -import org.opendc.simulator.compute.power.ConstantPowerModel -import org.opendc.simulator.compute.workload.SimTraceWorkload -import org.opendc.simulator.core.runBlockingSimulation -import org.opendc.simulator.resources.SimResourceSchedulerTrampoline - -/** - * Test suite for the [SimHypervisor] class. - */ -@OptIn(ExperimentalCoroutinesApi::class) -internal class SimHypervisorTest { - private lateinit var model: SimMachineModel - - @BeforeEach - fun setUp() { - val cpuNode = ProcessingNode("Intel", "Xeon", "amd64", 1) - model = SimMachineModel( - cpus = List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 3200.0) }, - memory = List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) } - ) - } - - /** - * Test overcommitting of resources via the hypervisor with a single VM. - */ - @Test - fun testOvercommittedSingle() = runBlockingSimulation { - val listener = object : SimHypervisor.Listener { - var totalRequestedWork = 0L - var totalGrantedWork = 0L - var totalOvercommittedWork = 0L - - override fun onSliceFinish( - hypervisor: SimHypervisor, - requestedWork: Long, - grantedWork: Long, - overcommittedWork: Long, - interferedWork: Long, - cpuUsage: Double, - cpuDemand: Double - ) { - totalRequestedWork += requestedWork - totalGrantedWork += grantedWork - totalOvercommittedWork += overcommittedWork - } - } - - val duration = 5 * 60L - val workloadA = - SimTraceWorkload( - sequenceOf( - SimTraceWorkload.Fragment(duration * 1000, 28.0, 1), - SimTraceWorkload.Fragment(duration * 1000, 3500.0, 1), - SimTraceWorkload.Fragment(duration * 1000, 0.0, 1), - SimTraceWorkload.Fragment(duration * 1000, 183.0, 1) - ), - ) - - val machine = SimBareMetalMachine(coroutineContext, clock, model, PerformanceScalingGovernor(), SimpleScalingDriver(ConstantPowerModel(0.0))) - val hypervisor = SimFairShareHypervisor(SimResourceSchedulerTrampoline(coroutineContext, clock), listener) - - launch { - machine.run(hypervisor) - println("Hypervisor finished") - } - yield() - val vm = hypervisor.createMachine(model) - val res = mutableListOf<Double>() - val job = launch { machine.usage.toList(res) } - - vm.run(workloadA) - yield() - job.cancel() - machine.close() - - assertAll( - { assertEquals(1113300, listener.totalRequestedWork, "Requested Burst does not match") }, - { assertEquals(1023300, listener.totalGrantedWork, "Granted Burst does not match") }, - { assertEquals(90000, listener.totalOvercommittedWork, "Overcommissioned Burst does not match") }, - { assertEquals(listOf(0.0, 0.00875, 1.0, 0.0, 0.0571875, 0.0), res) { "VM usage is correct" } }, - { assertEquals(1200000, clock.millis()) { "Current time is correct" } } - ) - } - - /** - * Test overcommitting of resources via the hypervisor with two VMs. - */ - @Test - fun testOvercommittedDual() = runBlockingSimulation { - val listener = object : SimHypervisor.Listener { - var totalRequestedWork = 0L - var totalGrantedWork = 0L - var totalOvercommittedWork = 0L - - override fun onSliceFinish( - hypervisor: SimHypervisor, - requestedWork: Long, - grantedWork: Long, - overcommittedWork: Long, - interferedWork: Long, - cpuUsage: Double, - cpuDemand: Double - ) { - totalRequestedWork += requestedWork - totalGrantedWork += grantedWork - totalOvercommittedWork += overcommittedWork - } - } - - val duration = 5 * 60L - val workloadA = - SimTraceWorkload( - sequenceOf( - SimTraceWorkload.Fragment(duration * 1000, 28.0, 1), - SimTraceWorkload.Fragment(duration * 1000, 3500.0, 1), - SimTraceWorkload.Fragment(duration * 1000, 0.0, 1), - SimTraceWorkload.Fragment(duration * 1000, 183.0, 1) - ), - ) - val workloadB = - SimTraceWorkload( - sequenceOf( - SimTraceWorkload.Fragment(duration * 1000, 28.0, 1), - SimTraceWorkload.Fragment(duration * 1000, 3100.0, 1), - SimTraceWorkload.Fragment(duration * 1000, 0.0, 1), - SimTraceWorkload.Fragment(duration * 1000, 73.0, 1) - ) - ) - - val machine = SimBareMetalMachine( - coroutineContext, clock, model, PerformanceScalingGovernor(), - SimpleScalingDriver(ConstantPowerModel(0.0)) - ) - val hypervisor = SimFairShareHypervisor(SimResourceSchedulerTrampoline(coroutineContext, clock), listener) - - launch { - machine.run(hypervisor) - } - - yield() - coroutineScope { - launch { - val vm = hypervisor.createMachine(model) - vm.run(workloadA) - vm.close() - } - val vm = hypervisor.createMachine(model) - vm.run(workloadB) - vm.close() - } - yield() - machine.close() - yield() - - assertAll( - { assertEquals(2082000, listener.totalRequestedWork, "Requested Burst does not match") }, - { assertEquals(1062000, listener.totalGrantedWork, "Granted Burst does not match") }, - { assertEquals(1020000, listener.totalOvercommittedWork, "Overcommissioned Burst does not match") }, - { assertEquals(1200000, clock.millis()) } - ) - } -} diff --git a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimMachineTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimMachineTest.kt index 205f2eca..0bb24ed8 100644 --- a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimMachineTest.kt +++ b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimMachineTest.kt @@ -23,41 +23,47 @@ package org.opendc.simulator.compute import kotlinx.coroutines.* -import kotlinx.coroutines.flow.toList +import org.junit.jupiter.api.* import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertDoesNotThrow -import org.junit.jupiter.api.assertThrows -import org.opendc.simulator.compute.cpufreq.PerformanceScalingGovernor -import org.opendc.simulator.compute.cpufreq.SimpleScalingDriver -import org.opendc.simulator.compute.model.MemoryUnit -import org.opendc.simulator.compute.model.ProcessingNode -import org.opendc.simulator.compute.model.ProcessingUnit +import org.opendc.simulator.compute.device.SimNetworkAdapter +import org.opendc.simulator.compute.model.* import org.opendc.simulator.compute.power.ConstantPowerModel +import org.opendc.simulator.compute.power.LinearPowerModel +import org.opendc.simulator.compute.power.SimplePowerDriver import org.opendc.simulator.compute.workload.SimFlopsWorkload +import org.opendc.simulator.compute.workload.SimWorkload +import org.opendc.simulator.compute.workload.SimWorkloadLifecycle import org.opendc.simulator.core.runBlockingSimulation +import org.opendc.simulator.flow.FlowEngine +import org.opendc.simulator.flow.source.FixedFlowSource +import org.opendc.simulator.network.SimNetworkSink +import org.opendc.simulator.power.SimPowerSource /** * Test suite for the [SimBareMetalMachine] class. */ -@OptIn(ExperimentalCoroutinesApi::class) class SimMachineTest { - private lateinit var machineModel: SimMachineModel + private lateinit var machineModel: MachineModel @BeforeEach fun setUp() { val cpuNode = ProcessingNode("Intel", "Xeon", "amd64", 2) - machineModel = SimMachineModel( + machineModel = MachineModel( cpus = List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 1000.0) }, - memory = List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) } + memory = List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) }, + net = listOf(NetworkAdapter("Mellanox", "ConnectX-5", 25000.0)), + storage = listOf(StorageDevice("Samsung", "EVO", 1000.0, 250.0, 250.0)) ) } @Test fun testFlopsWorkload() = runBlockingSimulation { - val machine = SimBareMetalMachine(coroutineContext, clock, machineModel, PerformanceScalingGovernor(), SimpleScalingDriver(ConstantPowerModel(0.0))) + val machine = SimBareMetalMachine( + FlowEngine(coroutineContext, clock), + machineModel, + SimplePowerDriver(ConstantPowerModel(0.0)) + ) try { machine.run(SimFlopsWorkload(2_000, utilization = 1.0)) @@ -72,11 +78,15 @@ class SimMachineTest { @Test fun testDualSocketMachine() = runBlockingSimulation { val cpuNode = machineModel.cpus[0].node - val machineModel = SimMachineModel( + val machineModel = MachineModel( cpus = List(cpuNode.coreCount * 2) { ProcessingUnit(cpuNode, it % 2, 1000.0) }, memory = List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) } ) - val machine = SimBareMetalMachine(coroutineContext, clock, machineModel, PerformanceScalingGovernor(), SimpleScalingDriver(ConstantPowerModel(0.0))) + val machine = SimBareMetalMachine( + FlowEngine(coroutineContext, clock), + machineModel, + SimplePowerDriver(ConstantPowerModel(0.0)) + ) try { machine.run(SimFlopsWorkload(2_000, utilization = 1.0)) @@ -89,17 +99,212 @@ class SimMachineTest { } @Test - fun testUsage() = runBlockingSimulation { - val machine = SimBareMetalMachine(coroutineContext, clock, machineModel, PerformanceScalingGovernor(), SimpleScalingDriver(ConstantPowerModel(0.0))) + fun testPower() = runBlockingSimulation { + val engine = FlowEngine(coroutineContext, clock) + val machine = SimBareMetalMachine( + engine, + machineModel, + SimplePowerDriver(LinearPowerModel(100.0, 50.0)) + ) + val source = SimPowerSource(engine, capacity = 1000.0) + source.connect(machine.psu) - val res = mutableListOf<Double>() - val job = launch { machine.usage.toList(res) } + try { + coroutineScope { + launch { machine.run(SimFlopsWorkload(2_000, utilization = 1.0)) } + assertAll( + { assertEquals(100.0, machine.psu.powerDraw) }, + { assertEquals(100.0, source.powerDraw) } + ) + } + } finally { + machine.close() + } + } + + @Test + fun testCapacityClamp() = runBlockingSimulation { + val machine = SimBareMetalMachine( + FlowEngine(coroutineContext, clock), + machineModel, + SimplePowerDriver(ConstantPowerModel(0.0)) + ) try { - machine.run(SimFlopsWorkload(2_000, utilization = 1.0)) - yield() - job.cancel() - assertEquals(listOf(0.0, 0.5, 1.0, 0.5, 0.0), res) { "Machine is fully utilized" } + machine.run(object : SimWorkload { + override fun onStart(ctx: SimMachineContext) { + val cpu = ctx.cpus[0] + + cpu.capacity = cpu.model.frequency + 1000.0 + assertEquals(cpu.model.frequency, cpu.capacity) + cpu.capacity = -1.0 + assertEquals(0.0, cpu.capacity) + + ctx.close() + } + }) + } finally { + machine.close() + } + } + + @Test + fun testMemory() = runBlockingSimulation { + val machine = SimBareMetalMachine( + FlowEngine(coroutineContext, clock), + machineModel, + SimplePowerDriver(ConstantPowerModel(0.0)) + ) + + try { + machine.run(object : SimWorkload { + override fun onStart(ctx: SimMachineContext) { + assertEquals(32_000 * 4.0, ctx.memory.capacity) + ctx.close() + } + }) + } finally { + machine.close() + } + } + + @Test + fun testMemoryUsage() = runBlockingSimulation { + val machine = SimBareMetalMachine( + FlowEngine(coroutineContext, clock), + machineModel, + SimplePowerDriver(ConstantPowerModel(0.0)) + ) + + try { + machine.run(object : SimWorkload { + override fun onStart(ctx: SimMachineContext) { + val lifecycle = SimWorkloadLifecycle(ctx) + ctx.memory.startConsumer(lifecycle.waitFor(FixedFlowSource(ctx.memory.capacity, utilization = 0.8))) + } + }) + + assertEquals(1250, clock.millis()) + } finally { + machine.close() + } + } + + @Test + fun testNetUsage() = runBlockingSimulation { + val engine = FlowEngine(coroutineContext, clock) + val machine = SimBareMetalMachine( + engine, + machineModel, + SimplePowerDriver(ConstantPowerModel(0.0)) + ) + + val adapter = (machine.peripherals[0] as SimNetworkAdapter) + adapter.connect(SimNetworkSink(engine, adapter.bandwidth)) + + try { + machine.run(object : SimWorkload { + override fun onStart(ctx: SimMachineContext) { + val lifecycle = SimWorkloadLifecycle(ctx) + val iface = ctx.net[0] + iface.tx.startConsumer(lifecycle.waitFor(FixedFlowSource(iface.bandwidth, utilization = 0.8))) + } + }) + + assertEquals(1250, clock.millis()) + } finally { + machine.close() + } + } + + @Test + fun testDiskReadUsage() = runBlockingSimulation { + val engine = FlowEngine(coroutineContext, clock) + val machine = SimBareMetalMachine( + engine, + machineModel, + SimplePowerDriver(ConstantPowerModel(0.0)) + ) + + try { + machine.run(object : SimWorkload { + override fun onStart(ctx: SimMachineContext) { + val lifecycle = SimWorkloadLifecycle(ctx) + val disk = ctx.storage[0] + disk.read.startConsumer(lifecycle.waitFor(FixedFlowSource(disk.read.capacity, utilization = 0.8))) + } + }) + + assertEquals(1250, clock.millis()) + } finally { + machine.close() + } + } + + @Test + fun testDiskWriteUsage() = runBlockingSimulation { + val engine = FlowEngine(coroutineContext, clock) + val machine = SimBareMetalMachine( + engine, + machineModel, + SimplePowerDriver(ConstantPowerModel(0.0)) + ) + + try { + machine.run(object : SimWorkload { + override fun onStart(ctx: SimMachineContext) { + val lifecycle = SimWorkloadLifecycle(ctx) + val disk = ctx.storage[0] + disk.write.startConsumer(lifecycle.waitFor(FixedFlowSource(disk.write.capacity, utilization = 0.8))) + } + }) + + assertEquals(1250, clock.millis()) + } finally { + machine.close() + } + } + + @Test + fun testCancellation() = runBlockingSimulation { + val machine = SimBareMetalMachine( + FlowEngine(coroutineContext, clock), + machineModel, + SimplePowerDriver(ConstantPowerModel(0.0)) + ) + + try { + coroutineScope { + launch { machine.run(SimFlopsWorkload(2_000, utilization = 1.0)) } + cancel() + } + } catch (_: CancellationException) { + // Ignore + } finally { + machine.close() + } + + assertEquals(0, clock.millis()) + } + + @Test + fun testConcurrentRuns() = runBlockingSimulation { + val machine = SimBareMetalMachine( + FlowEngine(coroutineContext, clock), + machineModel, + SimplePowerDriver(ConstantPowerModel(0.0)) + ) + + try { + coroutineScope { + launch { + machine.run(SimFlopsWorkload(2_000, utilization = 1.0)) + } + + assertThrows<IllegalStateException> { + machine.run(SimFlopsWorkload(2_000, utilization = 1.0)) + } + } } finally { machine.close() } @@ -107,7 +312,11 @@ class SimMachineTest { @Test fun testClose() = runBlockingSimulation { - val machine = SimBareMetalMachine(coroutineContext, clock, machineModel, PerformanceScalingGovernor(), SimpleScalingDriver(ConstantPowerModel(0.0))) + val machine = SimBareMetalMachine( + FlowEngine(coroutineContext, clock), + machineModel, + SimplePowerDriver(ConstantPowerModel(0.0)) + ) machine.close() assertDoesNotThrow { machine.close() } diff --git a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimSpaceSharedHypervisorTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimSpaceSharedHypervisorTest.kt deleted file mode 100644 index ef6f536d..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimSpaceSharedHypervisorTest.kt +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute - -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.toList -import kotlinx.coroutines.launch -import kotlinx.coroutines.yield -import org.junit.jupiter.api.Assertions.* -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows -import org.opendc.simulator.compute.cpufreq.PerformanceScalingGovernor -import org.opendc.simulator.compute.cpufreq.SimpleScalingDriver -import org.opendc.simulator.compute.model.MemoryUnit -import org.opendc.simulator.compute.model.ProcessingNode -import org.opendc.simulator.compute.model.ProcessingUnit -import org.opendc.simulator.compute.power.ConstantPowerModel -import org.opendc.simulator.compute.workload.SimFlopsWorkload -import org.opendc.simulator.compute.workload.SimRuntimeWorkload -import org.opendc.simulator.compute.workload.SimTraceWorkload -import org.opendc.simulator.core.runBlockingSimulation - -/** - * A test suite for the [SimSpaceSharedHypervisor]. - */ -@OptIn(ExperimentalCoroutinesApi::class) -internal class SimSpaceSharedHypervisorTest { - private lateinit var machineModel: SimMachineModel - - @BeforeEach - fun setUp() { - val cpuNode = ProcessingNode("Intel", "Xeon", "amd64", 1) - machineModel = SimMachineModel( - cpus = List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 3200.0) }, - memory = List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) } - ) - } - - /** - * Test a trace workload. - */ - @Test - fun testTrace() = runBlockingSimulation { - val usagePm = mutableListOf<Double>() - val usageVm = mutableListOf<Double>() - - val duration = 5 * 60L - val workloadA = - SimTraceWorkload( - sequenceOf( - SimTraceWorkload.Fragment(duration * 1000, 28.0, 1), - SimTraceWorkload.Fragment(duration * 1000, 3500.0, 1), - SimTraceWorkload.Fragment(duration * 1000, 0.0, 1), - SimTraceWorkload.Fragment(duration * 1000, 183.0, 1) - ), - ) - - val machine = SimBareMetalMachine( - coroutineContext, clock, machineModel, PerformanceScalingGovernor(), - SimpleScalingDriver(ConstantPowerModel(0.0)) - ) - val hypervisor = SimSpaceSharedHypervisor() - - val colA = launch { machine.usage.toList(usagePm) } - launch { machine.run(hypervisor) } - - yield() - - val vm = hypervisor.createMachine(machineModel) - val colB = launch { vm.usage.toList(usageVm) } - vm.run(workloadA) - yield() - - vm.close() - machine.close() - colA.cancel() - colB.cancel() - - assertAll( - { assertEquals(listOf(0.0, 0.00875, 1.0, 0.0, 0.0571875, 0.0), usagePm) { "Correct PM usage" } }, - // Temporary limitation is that VMs do not emit usage information - // { assertEquals(listOf(0.0, 0.00875, 1.0, 0.0, 0.0571875, 0.0), usageVm) { "Correct VM usage" } }, - { assertEquals(5 * 60L * 4000, clock.millis()) { "Took enough time" } } - ) - } - - /** - * Test runtime workload on hypervisor. - */ - @Test - fun testRuntimeWorkload() = runBlockingSimulation { - val duration = 5 * 60L * 1000 - val workload = SimRuntimeWorkload(duration) - val machine = SimBareMetalMachine( - coroutineContext, clock, machineModel, PerformanceScalingGovernor(), - SimpleScalingDriver(ConstantPowerModel(0.0)) - ) - val hypervisor = SimSpaceSharedHypervisor() - - launch { machine.run(hypervisor) } - yield() - val vm = hypervisor.createMachine(machineModel) - vm.run(workload) - vm.close() - machine.close() - - assertEquals(duration, clock.millis()) { "Took enough time" } - } - - /** - * Test FLOPs workload on hypervisor. - */ - @Test - fun testFlopsWorkload() = runBlockingSimulation { - val duration = 5 * 60L * 1000 - val workload = SimFlopsWorkload((duration * 3.2).toLong(), 1.0) - val machine = SimBareMetalMachine( - coroutineContext, clock, machineModel, PerformanceScalingGovernor(), - SimpleScalingDriver(ConstantPowerModel(0.0)) - ) - val hypervisor = SimSpaceSharedHypervisor() - - launch { machine.run(hypervisor) } - yield() - val vm = hypervisor.createMachine(machineModel) - vm.run(workload) - machine.close() - - assertEquals(duration, clock.millis()) { "Took enough time" } - } - - /** - * Test two workloads running sequentially. - */ - @Test - fun testTwoWorkloads() = runBlockingSimulation { - val duration = 5 * 60L * 1000 - val machine = SimBareMetalMachine( - coroutineContext, clock, machineModel, PerformanceScalingGovernor(), - SimpleScalingDriver(ConstantPowerModel(0.0)) - ) - val hypervisor = SimSpaceSharedHypervisor() - - launch { machine.run(hypervisor) } - yield() - - val vm = hypervisor.createMachine(machineModel) - vm.run(SimRuntimeWorkload(duration)) - vm.close() - - val vm2 = hypervisor.createMachine(machineModel) - vm2.run(SimRuntimeWorkload(duration)) - vm2.close() - machine.close() - - assertEquals(duration * 2, clock.millis()) { "Took enough time" } - } - - /** - * Test concurrent workloads on the machine. - */ - @Test - fun testConcurrentWorkloadFails() = runBlockingSimulation { - val machine = SimBareMetalMachine( - coroutineContext, clock, machineModel, PerformanceScalingGovernor(), - SimpleScalingDriver(ConstantPowerModel(0.0)) - ) - val hypervisor = SimSpaceSharedHypervisor() - - launch { machine.run(hypervisor) } - yield() - - hypervisor.createMachine(machineModel) - - assertAll( - { assertFalse(hypervisor.canFit(machineModel)) }, - { assertThrows<IllegalArgumentException> { hypervisor.createMachine(machineModel) } } - ) - - machine.close() - } - - /** - * Test concurrent workloads on the machine. - */ - @Test - fun testConcurrentWorkloadSucceeds() = runBlockingSimulation { - val machine = SimBareMetalMachine( - coroutineContext, clock, machineModel, PerformanceScalingGovernor(), - SimpleScalingDriver(ConstantPowerModel(0.0)) - ) - val hypervisor = SimSpaceSharedHypervisor() - - launch { machine.run(hypervisor) } - yield() - - hypervisor.createMachine(machineModel).close() - - assertAll( - { assertTrue(hypervisor.canFit(machineModel)) }, - { assertDoesNotThrow { hypervisor.createMachine(machineModel) } } - ) - - machine.close() - } -} diff --git a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/cpufreq/DemandScalingGovernorTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/cpufreq/DemandScalingGovernorTest.kt deleted file mode 100644 index c482d348..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/cpufreq/DemandScalingGovernorTest.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute.cpufreq - -import io.mockk.every -import io.mockk.mockk -import io.mockk.verify -import org.junit.jupiter.api.Test - -/** - * Test suite for the [DemandScalingGovernor] - */ -internal class DemandScalingGovernorTest { - @Test - fun testSetDemandLimit() { - val ctx = mockk<ScalingContext>(relaxUnitFun = true) - - every { ctx.cpu.speed } returns 2100.0 - - val logic = DemandScalingGovernor().createLogic(ctx) - - logic.onStart() - verify(exactly = 0) { ctx.setTarget(any()) } - - logic.onLimit() - verify(exactly = 1) { ctx.setTarget(2100.0) } - } -} diff --git a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/cpufreq/PStateScalingDriverTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/cpufreq/PStateScalingDriverTest.kt deleted file mode 100644 index bbea3ee2..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/cpufreq/PStateScalingDriverTest.kt +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute.cpufreq - -import io.mockk.every -import io.mockk.mockk -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test -import org.opendc.simulator.compute.SimBareMetalMachine -import org.opendc.simulator.compute.SimProcessingUnit -import org.opendc.simulator.compute.power.ConstantPowerModel -import org.opendc.simulator.compute.power.LinearPowerModel - -/** - * Test suite for [PStateScalingDriver]. - */ -internal class PStateScalingDriverTest { - @Test - fun testPowerWithoutGovernor() { - val machine = mockk<SimBareMetalMachine>() - - val driver = PStateScalingDriver( - sortedMapOf( - 2800.0 to ConstantPowerModel(200.0), - 3300.0 to ConstantPowerModel(300.0), - 3600.0 to ConstantPowerModel(350.0), - ) - ) - - val logic = driver.createLogic(machine) - assertEquals(200.0, logic.computePower()) - } - - @Test - fun testPowerWithSingleGovernor() { - val machine = mockk<SimBareMetalMachine>() - val cpu = mockk<SimProcessingUnit>() - - every { cpu.model.frequency } returns 4100.0 - every { cpu.speed } returns 1200.0 - - val driver = PStateScalingDriver( - sortedMapOf( - 2800.0 to ConstantPowerModel(200.0), - 3300.0 to ConstantPowerModel(300.0), - 3600.0 to ConstantPowerModel(350.0), - ) - ) - - val logic = driver.createLogic(machine) - - val scalingContext = logic.createContext(cpu) - scalingContext.setTarget(3200.0) - - assertEquals(300.0, logic.computePower()) - } - - @Test - fun testPowerWithMultipleGovernors() { - val machine = mockk<SimBareMetalMachine>() - val cpu = mockk<SimProcessingUnit>() - - every { cpu.model.frequency } returns 4100.0 - every { cpu.speed } returns 1200.0 - - val driver = PStateScalingDriver( - sortedMapOf( - 2800.0 to ConstantPowerModel(200.0), - 3300.0 to ConstantPowerModel(300.0), - 3600.0 to ConstantPowerModel(350.0), - ) - ) - - val logic = driver.createLogic(machine) - - val scalingContextA = logic.createContext(cpu) - scalingContextA.setTarget(1000.0) - - val scalingContextB = logic.createContext(cpu) - scalingContextB.setTarget(3400.0) - - assertEquals(350.0, logic.computePower()) - } - - @Test - fun testPowerBasedOnUtilization() { - val machine = mockk<SimBareMetalMachine>() - val cpu = mockk<SimProcessingUnit>() - - every { cpu.model.frequency } returns 4200.0 - - val driver = PStateScalingDriver( - sortedMapOf( - 2800.0 to LinearPowerModel(200.0, 100.0), - 3300.0 to LinearPowerModel(250.0, 150.0), - 4000.0 to LinearPowerModel(300.0, 200.0), - ) - ) - - val logic = driver.createLogic(machine) - - val scalingContext = logic.createContext(cpu) - - every { cpu.speed } returns 1400.0 - scalingContext.setTarget(1400.0) - assertEquals(150.0, logic.computePower()) - - every { cpu.speed } returns 1400.0 - scalingContext.setTarget(4000.0) - assertEquals(235.0, logic.computePower()) - } -} diff --git a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/device/SimPsuTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/device/SimPsuTest.kt new file mode 100644 index 00000000..e5b509f0 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/device/SimPsuTest.kt @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.device + +import io.mockk.every +import io.mockk.mockk +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.opendc.simulator.compute.power.PowerDriver +import org.opendc.simulator.core.runBlockingSimulation +import org.opendc.simulator.flow.FlowEngine +import org.opendc.simulator.power.SimPowerSource + +/** + * Test suite for [SimPsu] + */ +internal class SimPsuTest { + + @Test + fun testInvalidInput() { + assertThrows<IllegalArgumentException> { SimPsu(1.0, emptyMap()) } + } + + @Test + fun testDoubleConnect() { + val psu = SimPsu(1.0, mapOf(0.0 to 1.0)) + val cpuLogic = mockk<PowerDriver.Logic>() + psu.connect(cpuLogic) + assertThrows<IllegalStateException> { psu.connect(mockk()) } + } + + @Test + fun testPsuIdle() = runBlockingSimulation { + val ratedOutputPower = 240.0 + val energyEfficiency = mapOf(0.0 to 1.0) + + val engine = FlowEngine(coroutineContext, clock) + val source = SimPowerSource(engine, capacity = ratedOutputPower) + + val cpuLogic = mockk<PowerDriver.Logic>() + every { cpuLogic.computePower() } returns 0.0 + + val psu = SimPsu(ratedOutputPower, energyEfficiency) + psu.connect(cpuLogic) + source.connect(psu) + + assertEquals(0.0, source.powerDraw, 0.01) + } + + @Test + fun testPsuPowerLoss() = runBlockingSimulation { + val ratedOutputPower = 240.0 + // Efficiency of 80 Plus Titanium PSU + val energyEfficiency = sortedMapOf( + 0.3 to 0.9, + 0.7 to 0.92, + 1.0 to 0.94, + ) + + val engine = FlowEngine(coroutineContext, clock) + val source = SimPowerSource(engine, capacity = ratedOutputPower) + + val cpuLogic = mockk<PowerDriver.Logic>() + every { cpuLogic.computePower() } returnsMany listOf(50.0, 100.0, 150.0, 200.0) + + val psu = SimPsu(ratedOutputPower, energyEfficiency) + psu.connect(cpuLogic) + source.connect(psu) + + assertEquals(55.55, source.powerDraw, 0.01) + + psu.update() + assertEquals(108.695, source.powerDraw, 0.01) + + psu.update() + assertEquals(163.043, source.powerDraw, 0.01) + + psu.update() + assertEquals(212.765, source.powerDraw, 0.01) + } +} diff --git a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/SimFairShareHypervisorTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/SimFairShareHypervisorTest.kt new file mode 100644 index 00000000..6f32cf46 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/SimFairShareHypervisorTest.kt @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.kernel + +import kotlinx.coroutines.* +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertAll +import org.junit.jupiter.api.assertDoesNotThrow +import org.opendc.simulator.compute.SimBareMetalMachine +import org.opendc.simulator.compute.kernel.cpufreq.PerformanceScalingGovernor +import org.opendc.simulator.compute.kernel.interference.VmInterferenceGroup +import org.opendc.simulator.compute.kernel.interference.VmInterferenceModel +import org.opendc.simulator.compute.model.MachineModel +import org.opendc.simulator.compute.model.MemoryUnit +import org.opendc.simulator.compute.model.ProcessingNode +import org.opendc.simulator.compute.model.ProcessingUnit +import org.opendc.simulator.compute.power.ConstantPowerModel +import org.opendc.simulator.compute.power.SimplePowerDriver +import org.opendc.simulator.compute.workload.SimTrace +import org.opendc.simulator.compute.workload.SimTraceFragment +import org.opendc.simulator.compute.workload.SimTraceWorkload +import org.opendc.simulator.core.runBlockingSimulation +import org.opendc.simulator.flow.FlowEngine + +/** + * Test suite for the [SimHypervisor] class. + */ +@OptIn(ExperimentalCoroutinesApi::class) +internal class SimFairShareHypervisorTest { + private lateinit var model: MachineModel + + @BeforeEach + fun setUp() { + val cpuNode = ProcessingNode("Intel", "Xeon", "amd64", 1) + model = MachineModel( + cpus = List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 3200.0) }, + memory = List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) } + ) + } + + /** + * Test overcommitting of resources via the hypervisor with a single VM. + */ + @Test + fun testOvercommittedSingle() = runBlockingSimulation { + val duration = 5 * 60L + val workloadA = + SimTraceWorkload( + SimTrace.ofFragments( + SimTraceFragment(0, duration * 1000, 28.0, 1), + SimTraceFragment(duration * 1000, duration * 1000, 3500.0, 1), + SimTraceFragment(duration * 2000, duration * 1000, 0.0, 1), + SimTraceFragment(duration * 3000, duration * 1000, 183.0, 1) + ), + ) + + val platform = FlowEngine(coroutineContext, clock) + val machine = SimBareMetalMachine(platform, model, SimplePowerDriver(ConstantPowerModel(0.0))) + val hypervisor = SimFairShareHypervisor(platform, null, PerformanceScalingGovernor(), null) + + launch { + machine.run(hypervisor) + println("Hypervisor finished") + } + yield() + + val vm = hypervisor.createMachine(model) + vm.run(workloadA) + + yield() + machine.close() + + assertAll( + { assertEquals(319781, hypervisor.counters.cpuActiveTime, "Active time does not match") }, + { assertEquals(880219, hypervisor.counters.cpuIdleTime, "Idle time does not match") }, + { assertEquals(28125, hypervisor.counters.cpuStealTime, "Steal time does not match") }, + { assertEquals(1200000, clock.millis()) { "Current time is correct" } } + ) + } + + /** + * Test overcommitting of resources via the hypervisor with two VMs. + */ + @Test + fun testOvercommittedDual() = runBlockingSimulation { + val duration = 5 * 60L + val workloadA = + SimTraceWorkload( + SimTrace.ofFragments( + SimTraceFragment(0, duration * 1000, 28.0, 1), + SimTraceFragment(duration * 1000, duration * 1000, 3500.0, 1), + SimTraceFragment(duration * 2000, duration * 1000, 0.0, 1), + SimTraceFragment(duration * 3000, duration * 1000, 183.0, 1) + ), + ) + val workloadB = + SimTraceWorkload( + SimTrace.ofFragments( + SimTraceFragment(0, duration * 1000, 28.0, 1), + SimTraceFragment(duration * 1000, duration * 1000, 3100.0, 1), + SimTraceFragment(duration * 2000, duration * 1000, 0.0, 1), + SimTraceFragment(duration * 3000, duration * 1000, 73.0, 1) + ) + ) + + val platform = FlowEngine(coroutineContext, clock) + val machine = SimBareMetalMachine( + platform, model, SimplePowerDriver(ConstantPowerModel(0.0)) + ) + val hypervisor = SimFairShareHypervisor(platform, null, null, null) + + launch { + machine.run(hypervisor) + } + + yield() + coroutineScope { + launch { + val vm = hypervisor.createMachine(model) + vm.run(workloadA) + vm.close() + } + val vm = hypervisor.createMachine(model) + vm.run(workloadB) + vm.close() + } + yield() + machine.close() + yield() + + assertAll( + { assertEquals(329250, hypervisor.counters.cpuActiveTime, "Active time does not match") }, + { assertEquals(870750, hypervisor.counters.cpuIdleTime, "Idle time does not match") }, + { assertEquals(318750, hypervisor.counters.cpuStealTime, "Steal time does not match") }, + { assertEquals(1200000, clock.millis()) } + ) + } + + @Test + fun testMultipleCPUs() = runBlockingSimulation { + val cpuNode = ProcessingNode("Intel", "Xeon", "amd64", 2) + val model = MachineModel( + cpus = List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 3200.0) }, + memory = List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) } + ) + + val platform = FlowEngine(coroutineContext, clock) + val machine = SimBareMetalMachine(platform, model, SimplePowerDriver(ConstantPowerModel(0.0))) + val hypervisor = SimFairShareHypervisor(platform, null, null, null) + + assertDoesNotThrow { + launch { + machine.run(hypervisor) + } + } + + machine.close() + } + + @Test + fun testInterference() = runBlockingSimulation { + val cpuNode = ProcessingNode("Intel", "Xeon", "amd64", 2) + val model = MachineModel( + cpus = List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 3200.0) }, + memory = List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) } + ) + + val groups = listOf( + VmInterferenceGroup(targetLoad = 0.0, score = 0.9, members = setOf("a", "b")), + VmInterferenceGroup(targetLoad = 0.0, score = 0.6, members = setOf("a", "c")), + VmInterferenceGroup(targetLoad = 0.1, score = 0.8, members = setOf("a", "n")) + ) + val interferenceModel = VmInterferenceModel(groups) + + val platform = FlowEngine(coroutineContext, clock) + val machine = SimBareMetalMachine( + platform, model, SimplePowerDriver(ConstantPowerModel(0.0)) + ) + val hypervisor = SimFairShareHypervisor(platform, null, null, interferenceModel.newDomain()) + + val duration = 5 * 60L + val workloadA = + SimTraceWorkload( + SimTrace.ofFragments( + SimTraceFragment(0, duration * 1000, 0.0, 1), + SimTraceFragment(duration * 1000, duration * 1000, 28.0, 1), + SimTraceFragment(duration * 2000, duration * 1000, 3500.0, 1), + SimTraceFragment(duration * 3000, duration * 1000, 183.0, 1) + ), + ) + val workloadB = + SimTraceWorkload( + SimTrace.ofFragments( + SimTraceFragment(0, duration * 1000, 0.0, 1), + SimTraceFragment(duration * 1000, duration * 1000, 28.0, 1), + SimTraceFragment(duration * 2000, duration * 1000, 3100.0, 1), + SimTraceFragment(duration * 3000, duration * 1000, 73.0, 1) + ) + ) + + launch { + machine.run(hypervisor) + } + + coroutineScope { + launch { + val vm = hypervisor.createMachine(model, "a") + vm.run(workloadA) + vm.close() + } + val vm = hypervisor.createMachine(model, "b") + vm.run(workloadB) + vm.close() + } + + machine.close() + } +} diff --git a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/SimSpaceSharedHypervisorTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/SimSpaceSharedHypervisorTest.kt new file mode 100644 index 00000000..02d308ff --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/SimSpaceSharedHypervisorTest.kt @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.kernel + +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.launch +import kotlinx.coroutines.yield +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.opendc.simulator.compute.SimBareMetalMachine +import org.opendc.simulator.compute.model.MachineModel +import org.opendc.simulator.compute.model.MemoryUnit +import org.opendc.simulator.compute.model.ProcessingNode +import org.opendc.simulator.compute.model.ProcessingUnit +import org.opendc.simulator.compute.power.ConstantPowerModel +import org.opendc.simulator.compute.power.SimplePowerDriver +import org.opendc.simulator.compute.workload.* +import org.opendc.simulator.core.runBlockingSimulation +import org.opendc.simulator.flow.FlowEngine + +/** + * A test suite for the [SimSpaceSharedHypervisor]. + */ +@OptIn(ExperimentalCoroutinesApi::class) +internal class SimSpaceSharedHypervisorTest { + private lateinit var machineModel: MachineModel + + @BeforeEach + fun setUp() { + val cpuNode = ProcessingNode("Intel", "Xeon", "amd64", 1) + machineModel = MachineModel( + cpus = List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 3200.0) }, + memory = List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) } + ) + } + + /** + * Test a trace workload. + */ + @Test + fun testTrace() = runBlockingSimulation { + val duration = 5 * 60L + val workloadA = + SimTraceWorkload( + SimTrace.ofFragments( + SimTraceFragment(0, duration * 1000, 28.0, 1), + SimTraceFragment(duration * 1000, duration * 1000, 3500.0, 1), + SimTraceFragment(duration * 2000, duration * 1000, 0.0, 1), + SimTraceFragment(duration * 3000, duration * 1000, 183.0, 1) + ), + ) + + val engine = FlowEngine(coroutineContext, clock) + val machine = SimBareMetalMachine(engine, machineModel, SimplePowerDriver(ConstantPowerModel(0.0))) + val hypervisor = SimSpaceSharedHypervisor(engine, null, null) + + launch { machine.run(hypervisor) } + val vm = hypervisor.createMachine(machineModel) + vm.run(workloadA) + yield() + + vm.close() + machine.close() + + assertEquals(5 * 60L * 4000, clock.millis()) { "Took enough time" } + } + + /** + * Test runtime workload on hypervisor. + */ + @Test + fun testRuntimeWorkload() = runBlockingSimulation { + val duration = 5 * 60L * 1000 + val workload = SimRuntimeWorkload(duration) + val engine = FlowEngine(coroutineContext, clock) + val machine = SimBareMetalMachine(engine, machineModel, SimplePowerDriver(ConstantPowerModel(0.0))) + val hypervisor = SimSpaceSharedHypervisor(engine, null, null) + + launch { machine.run(hypervisor) } + yield() + val vm = hypervisor.createMachine(machineModel) + vm.run(workload) + vm.close() + machine.close() + + assertEquals(duration, clock.millis()) { "Took enough time" } + } + + /** + * Test FLOPs workload on hypervisor. + */ + @Test + fun testFlopsWorkload() = runBlockingSimulation { + val duration = 5 * 60L * 1000 + val workload = SimFlopsWorkload((duration * 3.2).toLong(), 1.0) + val engine = FlowEngine(coroutineContext, clock) + val machine = SimBareMetalMachine( + engine, machineModel, SimplePowerDriver(ConstantPowerModel(0.0)) + ) + val hypervisor = SimSpaceSharedHypervisor(engine, null, null) + + launch { machine.run(hypervisor) } + yield() + val vm = hypervisor.createMachine(machineModel) + vm.run(workload) + machine.close() + + assertEquals(duration, clock.millis()) { "Took enough time" } + } + + /** + * Test two workloads running sequentially. + */ + @Test + fun testTwoWorkloads() = runBlockingSimulation { + val duration = 5 * 60L * 1000 + val engine = FlowEngine(coroutineContext, clock) + val machine = SimBareMetalMachine( + engine, machineModel, SimplePowerDriver(ConstantPowerModel(0.0)) + ) + val hypervisor = SimSpaceSharedHypervisor(engine, null, null) + + launch { machine.run(hypervisor) } + yield() + + val vm = hypervisor.createMachine(machineModel) + vm.run(SimRuntimeWorkload(duration)) + vm.close() + + yield() + + val vm2 = hypervisor.createMachine(machineModel) + vm2.run(SimRuntimeWorkload(duration)) + vm2.close() + machine.close() + + assertEquals(duration * 2, clock.millis()) { "Took enough time" } + } + + /** + * Test concurrent workloads on the machine. + */ + @Test + fun testConcurrentWorkloadFails() = runBlockingSimulation { + val engine = FlowEngine(coroutineContext, clock) + val machine = SimBareMetalMachine(engine, machineModel, SimplePowerDriver(ConstantPowerModel(0.0))) + val hypervisor = SimSpaceSharedHypervisor(engine, null, null) + + launch { machine.run(hypervisor) } + yield() + + hypervisor.createMachine(machineModel) + + assertAll( + { assertFalse(hypervisor.canFit(machineModel)) }, + { assertThrows<IllegalArgumentException> { hypervisor.createMachine(machineModel) } } + ) + + machine.close() + } + + /** + * Test concurrent workloads on the machine. + */ + @Test + fun testConcurrentWorkloadSucceeds() = runBlockingSimulation { + val interpreter = FlowEngine(coroutineContext, clock) + val machine = SimBareMetalMachine( + interpreter, machineModel, SimplePowerDriver(ConstantPowerModel(0.0)) + ) + val hypervisor = SimSpaceSharedHypervisor(interpreter, null, null) + + launch { machine.run(hypervisor) } + yield() + + hypervisor.createMachine(machineModel).close() + + assertAll( + { assertTrue(hypervisor.canFit(machineModel)) }, + { assertDoesNotThrow { hypervisor.createMachine(machineModel) } } + ) + + machine.close() + } +} diff --git a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/ConservativeScalingGovernorTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/ConservativeScalingGovernorTest.kt new file mode 100644 index 00000000..ef354569 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/ConservativeScalingGovernorTest.kt @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.kernel.cpufreq + +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +/** + * Test suite for the [ConservativeScalingGovernor] + */ +internal class ConservativeScalingGovernorTest { + @Test + fun testSetStartLimitWithoutPStates() { + val cpuCapacity = 4100.0 + val minSpeed = cpuCapacity / 2 + val defaultThreshold = 0.8 + val defaultStepSize = 0.05 * cpuCapacity + val governor = ConservativeScalingGovernor() + + val policy = mockk<ScalingPolicy>(relaxUnitFun = true) + every { policy.max } returns cpuCapacity + every { policy.min } returns minSpeed + + var target = 0.0 + every { policy.target } answers { target } + every { policy.target = any() } propertyType Double::class answers { target = value } + + val logic = governor.createLogic(policy) + logic.onStart() + assertEquals(defaultThreshold, governor.threshold) + + logic.onLimit(0.5) + + /* Upwards scaling */ + logic.onLimit(defaultThreshold + 0.2) + + /* Downwards scaling */ + logic.onLimit(defaultThreshold + 0.1) + + verify(exactly = 2) { policy.target = minSpeed } + verify(exactly = 1) { policy.target = minSpeed + defaultStepSize } + } + + @Test + fun testSetStartLimitWithPStatesAndParams() { + val firstPState = 1000.0 + val cpuCapacity = 4100.0 + val minSpeed = firstPState + val threshold = 0.5 + val stepSize = 0.02 * cpuCapacity + val governor = ConservativeScalingGovernor(threshold, stepSize) + + val policy = mockk<ScalingPolicy>(relaxUnitFun = true) + every { policy.max } returns cpuCapacity + every { policy.min } returns firstPState + + var target = 0.0 + every { policy.target } answers { target } + every { policy.target = any() } propertyType Double::class answers { target = value } + + val logic = governor.createLogic(policy) + logic.onStart() + assertEquals(threshold, governor.threshold) + logic.onLimit(0.5) + + /* Upwards scaling */ + logic.onLimit(threshold + 0.2) + + /* Downwards scaling */ + logic.onLimit(threshold + 0.1) + + verify(exactly = 2) { policy.target = minSpeed } + verify(exactly = 1) { policy.target = minSpeed + stepSize } + } +} diff --git a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/OnDemandScalingGovernorTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/OnDemandScalingGovernorTest.kt new file mode 100644 index 00000000..ca759e39 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/OnDemandScalingGovernorTest.kt @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.kernel.cpufreq + +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +/** + * Test suite for the [OnDemandScalingGovernor] + */ +internal class OnDemandScalingGovernorTest { + @Test + fun testSetStartLimitWithoutPStates() { + val cpuCapacity = 4100.0 + val minSpeed = cpuCapacity / 2 + val defaultThreshold = 0.8 + val governor = OnDemandScalingGovernor() + + val policy = mockk<ScalingPolicy>(relaxUnitFun = true) + every { policy.min } returns minSpeed + every { policy.max } returns cpuCapacity + + val logic = governor.createLogic(policy) + logic.onStart() + assertEquals(defaultThreshold, governor.threshold) + verify(exactly = 1) { policy.target = minSpeed } + + logic.onLimit(0.5) + verify(exactly = 1) { policy.target = minSpeed + 0.5 * (cpuCapacity - minSpeed) / 100 } + + logic.onLimit(defaultThreshold) + verify(exactly = 1) { policy.target = cpuCapacity } + } + + @Test + fun testSetStartLimitWithPStatesAndParams() { + val firstPState = 1000.0 + val cpuCapacity = 4100.0 + val threshold = 0.5 + val governor = OnDemandScalingGovernor(threshold) + + val policy = mockk<ScalingPolicy>(relaxUnitFun = true) + every { policy.max } returns cpuCapacity + every { policy.min } returns firstPState + + val logic = governor.createLogic(policy) + + logic.onStart() + assertEquals(threshold, governor.threshold) + verify(exactly = 1) { policy.target = firstPState } + + logic.onLimit(0.1) + verify(exactly = 1) { policy.target = firstPState + 0.1 * (cpuCapacity - firstPState) / 100 } + + logic.onLimit(threshold) + verify(exactly = 1) { policy.target = cpuCapacity } + } +} diff --git a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/PerformanceScalingGovernorTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/PerformanceScalingGovernorTest.kt new file mode 100644 index 00000000..a4bb24f2 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/PerformanceScalingGovernorTest.kt @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.kernel.cpufreq + +import io.mockk.every +import io.mockk.spyk +import io.mockk.verify +import org.junit.jupiter.api.Test + +/** + * Test suite for the [PerformanceScalingGovernor] + */ +internal class PerformanceScalingGovernorTest { + @Test + fun testSetStartLimit() { + val policy = spyk<ScalingPolicy>() + val logic = PerformanceScalingGovernor().createLogic(policy) + + every { policy.max } returns 4100.0 + + logic.onStart() + verify(exactly = 1) { policy.target = 4100.0 } + + logic.onLimit(0.0) + verify(exactly = 1) { policy.target = 4100.0 } + + logic.onLimit(1.0) + verify(exactly = 1) { policy.target = 4100.0 } + } +} diff --git a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/PowerSaveScalingGovernorTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/PowerSaveScalingGovernorTest.kt new file mode 100644 index 00000000..662d55fb --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/PowerSaveScalingGovernorTest.kt @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.kernel.cpufreq + +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import org.junit.jupiter.api.Test + +/** + * Test suite for the [PowerSaveScalingGovernor] + */ +internal class PowerSaveScalingGovernorTest { + @Test + fun testSetStartLimitWithoutPStates() { + val cpuCapacity = 4100.0 + val minSpeed = cpuCapacity / 2 + val policy = mockk<ScalingPolicy>(relaxUnitFun = true) + val logic = PowerSaveScalingGovernor().createLogic(policy) + + every { policy.max } returns cpuCapacity + every { policy.min } returns minSpeed + + logic.onStart() + + logic.onLimit(0.0) + verify(exactly = 1) { policy.target = minSpeed } + + logic.onLimit(1.0) + verify(exactly = 1) { policy.target = minSpeed } + } + + @Test + fun testSetStartLimitWithPStates() { + val cpuCapacity = 4100.0 + val firstPState = 1000.0 + val policy = mockk<ScalingPolicy>(relaxUnitFun = true) + val logic = PowerSaveScalingGovernor().createLogic(policy) + + every { policy.max } returns cpuCapacity + every { policy.min } returns firstPState + + logic.onStart() + verify(exactly = 1) { policy.target = firstPState } + + logic.onLimit(0.0) + verify(exactly = 1) { policy.target = firstPState } + + logic.onLimit(1.0) + verify(exactly = 1) { policy.target = firstPState } + } +} diff --git a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/power/PStatePowerDriverTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/power/PStatePowerDriverTest.kt new file mode 100644 index 00000000..f557c8d3 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/power/PStatePowerDriverTest.kt @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.power + +import io.mockk.every +import io.mockk.mockk +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.opendc.simulator.compute.SimBareMetalMachine +import org.opendc.simulator.compute.SimProcessingUnit + +/** + * Test suite for [PStatePowerDriver]. + */ +internal class PStatePowerDriverTest { + @Test + fun testPowerBaseline() { + val machine = mockk<SimBareMetalMachine>() + + val driver = PStatePowerDriver( + sortedMapOf( + 2800.0 to ConstantPowerModel(200.0), + 3300.0 to ConstantPowerModel(300.0), + 3600.0 to ConstantPowerModel(350.0), + ) + ) + + val logic = driver.createLogic(machine, emptyList()) + assertEquals(200.0, logic.computePower()) + } + + @Test + fun testPowerWithSingleCpu() { + val machine = mockk<SimBareMetalMachine>() + val cpu = mockk<SimProcessingUnit>(relaxUnitFun = true) + + every { cpu.capacity } returns 3200.0 + every { cpu.rate } returns 1200.0 + + val driver = PStatePowerDriver( + sortedMapOf( + 2800.0 to ConstantPowerModel(200.0), + 3300.0 to ConstantPowerModel(300.0), + 3600.0 to ConstantPowerModel(350.0), + ) + ) + + val logic = driver.createLogic(machine, listOf(cpu)) + + assertEquals(300.0, logic.computePower()) + } + + @Test + fun testPowerWithMultipleCpus() { + val machine = mockk<SimBareMetalMachine>() + val cpu = mockk<SimProcessingUnit>(relaxUnitFun = true) + val cpus = listOf(cpu, cpu) + + every { cpus[0].capacity } returns 1000.0 + every { cpus[0].rate } returns 1200.0 + + every { cpus[1].capacity } returns 3500.0 + every { cpus[1].rate } returns 1200.0 + + val driver = PStatePowerDriver( + sortedMapOf( + 2800.0 to ConstantPowerModel(200.0), + 3300.0 to ConstantPowerModel(300.0), + 3600.0 to ConstantPowerModel(350.0), + ) + ) + + val logic = driver.createLogic(machine, cpus) + + assertEquals(350.0, logic.computePower()) + } + + @Test + fun testPowerBasedOnUtilization() { + val machine = mockk<SimBareMetalMachine>() + val cpu = mockk<SimProcessingUnit>(relaxUnitFun = true) + + every { cpu.model.frequency } returns 4200.0 + + val driver = PStatePowerDriver( + sortedMapOf( + 2800.0 to LinearPowerModel(200.0, 100.0), + 3300.0 to LinearPowerModel(250.0, 150.0), + 4000.0 to LinearPowerModel(300.0, 200.0), + ) + ) + + val logic = driver.createLogic(machine, listOf(cpu)) + + every { cpu.rate } returns 1400.0 + every { cpu.capacity } returns 1400.0 + assertEquals(150.0, logic.computePower()) + + every { cpu.rate } returns 1400.0 + every { cpu.capacity } returns 4000.0 + assertEquals(235.0, logic.computePower()) + } +} diff --git a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/power/PowerModelTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/power/PowerModelTest.kt index dd93302b..7852534a 100644 --- a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/power/PowerModelTest.kt +++ b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/power/PowerModelTest.kt @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + package org.opendc.simulator.compute.power import org.junit.jupiter.api.Assertions.assertAll @@ -39,7 +61,8 @@ internal class PowerModelTest { @Test fun `compute power draw by the SPEC benchmark model`() { - val powerModel = InterpolationPowerModel("IBMx3550M3_XeonX5675") + val ibm = listOf(58.4, 98.0, 109.0, 118.0, 128.0, 140.0, 153.0, 170.0, 189.0, 205.0, 222.0) + val powerModel = InterpolationPowerModel(ibm) assertAll( { assertEquals(58.4, powerModel.computePower(0.0)) }, diff --git a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/workload/SimTraceWorkloadTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/workload/SimTraceWorkloadTest.kt new file mode 100644 index 00000000..574860e8 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/workload/SimTraceWorkloadTest.kt @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.workload + +import kotlinx.coroutines.delay +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.opendc.simulator.compute.SimBareMetalMachine +import org.opendc.simulator.compute.model.* +import org.opendc.simulator.compute.power.ConstantPowerModel +import org.opendc.simulator.compute.power.SimplePowerDriver +import org.opendc.simulator.core.runBlockingSimulation +import org.opendc.simulator.flow.FlowEngine + +/** + * Test suite for the [SimTraceWorkloadTest] class. + */ +class SimTraceWorkloadTest { + private lateinit var machineModel: MachineModel + + @BeforeEach + fun setUp() { + val cpuNode = ProcessingNode("Intel", "Xeon", "amd64", 2) + + machineModel = MachineModel( + cpus = List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 1000.0) }, + memory = List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) } + ) + } + + @Test + fun testSmoke() = runBlockingSimulation { + val machine = SimBareMetalMachine( + FlowEngine(coroutineContext, clock), + machineModel, + SimplePowerDriver(ConstantPowerModel(0.0)) + ) + + val workload = SimTraceWorkload( + SimTrace.ofFragments( + SimTraceFragment(0, 1000, 2 * 28.0, 2), + SimTraceFragment(1000, 1000, 2 * 3100.0, 2), + SimTraceFragment(2000, 1000, 0.0, 2), + SimTraceFragment(3000, 1000, 2 * 73.0, 2) + ), + offset = 0 + ) + + try { + machine.run(workload) + + assertEquals(4000, clock.millis()) + } finally { + machine.close() + } + } + + @Test + fun testOffset() = runBlockingSimulation { + val machine = SimBareMetalMachine( + FlowEngine(coroutineContext, clock), + machineModel, + SimplePowerDriver(ConstantPowerModel(0.0)) + ) + + val workload = SimTraceWorkload( + SimTrace.ofFragments( + SimTraceFragment(0, 1000, 2 * 28.0, 2), + SimTraceFragment(1000, 1000, 2 * 3100.0, 2), + SimTraceFragment(2000, 1000, 0.0, 2), + SimTraceFragment(3000, 1000, 2 * 73.0, 2) + ), + offset = 1000 + ) + + try { + machine.run(workload) + + assertEquals(5000, clock.millis()) + } finally { + machine.close() + } + } + + @Test + fun testSkipFragment() = runBlockingSimulation { + val machine = SimBareMetalMachine( + FlowEngine(coroutineContext, clock), + machineModel, + SimplePowerDriver(ConstantPowerModel(0.0)) + ) + + val workload = SimTraceWorkload( + SimTrace.ofFragments( + SimTraceFragment(0, 1000, 2 * 28.0, 2), + SimTraceFragment(1000, 1000, 2 * 3100.0, 2), + SimTraceFragment(2000, 1000, 0.0, 2), + SimTraceFragment(3000, 1000, 2 * 73.0, 2) + ), + offset = 0 + ) + + try { + delay(1000L) + machine.run(workload) + + assertEquals(4000, clock.millis()) + } finally { + machine.close() + } + } + + @Test + fun testZeroCores() = runBlockingSimulation { + val machine = SimBareMetalMachine( + FlowEngine(coroutineContext, clock), + machineModel, + SimplePowerDriver(ConstantPowerModel(0.0)) + ) + + val workload = SimTraceWorkload( + SimTrace.ofFragments( + SimTraceFragment(0, 1000, 2 * 28.0, 2), + SimTraceFragment(1000, 1000, 2 * 3100.0, 2), + SimTraceFragment(2000, 1000, 0.0, 0), + SimTraceFragment(3000, 1000, 2 * 73.0, 2) + ), + offset = 0 + ) + + try { + machine.run(workload) + + assertEquals(4000, clock.millis()) + } finally { + machine.close() + } + } +} diff --git a/opendc-simulator/opendc-simulator-core/src/main/kotlin/org/opendc/simulator/core/SimulationCoroutineDispatcher.kt b/opendc-simulator/opendc-simulator-core/src/main/kotlin/org/opendc/simulator/core/SimulationCoroutineDispatcher.kt index e2f7874c..908e902a 100644 --- a/opendc-simulator/opendc-simulator-core/src/main/kotlin/org/opendc/simulator/core/SimulationCoroutineDispatcher.kt +++ b/opendc-simulator/opendc-simulator-core/src/main/kotlin/org/opendc/simulator/core/SimulationCoroutineDispatcher.kt @@ -37,11 +37,6 @@ import kotlin.coroutines.CoroutineContext @OptIn(InternalCoroutinesApi::class) public class SimulationCoroutineDispatcher : CoroutineDispatcher(), SimulationController, Delay { /** - * The virtual clock of this dispatcher. - */ - override val clock: Clock = VirtualClock() - - /** * Queue of ordered tasks to run. */ private val queue = PriorityQueue<TimedRunnable>() @@ -54,7 +49,12 @@ public class SimulationCoroutineDispatcher : CoroutineDispatcher(), SimulationCo /** * The current virtual time of simulation */ - private var _time = 0L + private var _clock = SimClock() + + /** + * The virtual clock of this dispatcher. + */ + override val clock: Clock = ClockAdapter(_clock) override fun dispatch(context: CoroutineContext, block: Runnable) { block.run() @@ -79,14 +79,14 @@ public class SimulationCoroutineDispatcher : CoroutineDispatcher(), SimulationCo } override fun toString(): String { - return "SimulationCoroutineDispatcher[time=${_time}ms, queued=${queue.size}]" + return "SimulationCoroutineDispatcher[time=${_clock.time}ms, queued=${queue.size}]" } private fun post(block: Runnable) = queue.add(TimedRunnable(block, _counter++)) private fun postDelayed(block: Runnable, delayTime: Long) = - TimedRunnable(block, _counter++, safePlus(_time, delayTime)) + TimedRunnable(block, _counter++, safePlus(_clock.time, delayTime)) .also { queue.add(it) } @@ -100,31 +100,41 @@ public class SimulationCoroutineDispatcher : CoroutineDispatcher(), SimulationCo override fun advanceUntilIdle(): Long { val queue = queue - val oldTime = _time - while (queue.isNotEmpty()) { - val current = queue.poll() + val clock = _clock + val oldTime = clock.time + + while (true) { + val current = queue.poll() ?: break // If the scheduled time is 0 (immediate) use current virtual time if (current.time != 0L) { - _time = current.time + clock.time = current.time } current.run() } - return _time - oldTime + return clock.time - oldTime } - private inner class VirtualClock(private val zone: ZoneId = ZoneId.systemDefault()) : Clock() { + /** + * A helper class that holds the time of the simulation. + */ + private class SimClock(@JvmField var time: Long = 0) + + /** + * A helper class to expose a [Clock] instance for this dispatcher. + */ + private class ClockAdapter(private val clock: SimClock, private val zone: ZoneId = ZoneId.systemDefault()) : Clock() { override fun getZone(): ZoneId = zone - override fun withZone(zone: ZoneId): Clock = VirtualClock(zone) + override fun withZone(zone: ZoneId): Clock = ClockAdapter(clock, zone) override fun instant(): Instant = Instant.ofEpochMilli(millis()) - override fun millis(): Long = _time + override fun millis(): Long = clock.time - override fun toString(): String = "SimulationCoroutineDispatcher.VirtualClock[time=$_time]" + override fun toString(): String = "SimulationCoroutineDispatcher.ClockAdapter[time=${clock.time}]" } /** diff --git a/opendc-simulator/opendc-simulator-failures/build.gradle.kts b/opendc-simulator/opendc-simulator-failures/build.gradle.kts deleted file mode 100644 index 57cd0a35..00000000 --- a/opendc-simulator/opendc-simulator-failures/build.gradle.kts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2020 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -description = "Failure models for OpenDC" - -plugins { - `kotlin-library-conventions` -} - -dependencies { - api(platform(projects.opendcPlatform)) - api(libs.kotlinx.coroutines) -} diff --git a/opendc-simulator/opendc-simulator-failures/src/main/kotlin/org/opendc/simulator/failures/CorrelatedFaultInjector.kt b/opendc-simulator/opendc-simulator-failures/src/main/kotlin/org/opendc/simulator/failures/CorrelatedFaultInjector.kt deleted file mode 100644 index 0e15f338..00000000 --- a/opendc-simulator/opendc-simulator-failures/src/main/kotlin/org/opendc/simulator/failures/CorrelatedFaultInjector.kt +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (c) 2020 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.failures - -import kotlinx.coroutines.* -import java.time.Clock -import kotlin.math.exp -import kotlin.math.max -import kotlin.random.Random -import kotlin.random.asJavaRandom - -/** - * A [FaultInjector] that injects fault in the system which are correlated to each other. Failures do not occur in - * isolation, but will trigger other faults. - */ -public class CorrelatedFaultInjector( - private val coroutineScope: CoroutineScope, - private val clock: Clock, - private val iatScale: Double, - private val iatShape: Double, - private val sizeScale: Double, - private val sizeShape: Double, - private val dScale: Double, - private val dShape: Double, - random: Random = Random(0) -) : FaultInjector { - /** - * The active failure domains that have been registered. - */ - private val active = mutableSetOf<FailureDomain>() - - /** - * The [Job] that awaits the nearest fault in the system. - */ - private var job: Job? = null - - /** - * The [Random] instance to use. - */ - private val random: java.util.Random = random.asJavaRandom() - - /** - * Enqueue the specified [FailureDomain] to fail some time in the future. - */ - override fun enqueue(domain: FailureDomain) { - active += domain - - // Clean up the domain if it finishes - domain.scope.coroutineContext[Job]!!.invokeOnCompletion { - this@CorrelatedFaultInjector.coroutineScope.launch { - active -= domain - - if (active.isEmpty()) { - job?.cancel() - job = null - } - } - } - - if (job != null) { - return - } - - job = this.coroutineScope.launch { - while (active.isNotEmpty()) { - ensureActive() - - // Make sure to convert delay from hours to milliseconds - val d = lognvariate(iatScale, iatShape) * 3.6e6 - - // Handle long overflow - if (clock.millis() + d <= 0) { - return@launch - } - - delay(d.toLong()) - - val n = lognvariate(sizeScale, sizeShape).toInt() - val targets = active.shuffled(random).take(n) - - for (failureDomain in targets) { - active -= failureDomain - failureDomain.fail() - } - - val df = max(lognvariate(dScale, dShape) * 6e4, 15 * 6e4) - - // Handle long overflow - if (clock.millis() + df <= 0) { - return@launch - } - - delay(df.toLong()) - - for (failureDomain in targets) { - failureDomain.recover() - - // Re-enqueue machine to be failed - enqueue(failureDomain) - } - } - - job = null - } - } - - // XXX We should extract this in some common package later on. - private fun lognvariate(scale: Double, shape: Double) = exp(scale + shape * random.nextGaussian()) -} diff --git a/opendc-simulator/opendc-simulator-failures/src/main/kotlin/org/opendc/simulator/failures/FailureDomain.kt b/opendc-simulator/opendc-simulator-failures/src/main/kotlin/org/opendc/simulator/failures/FailureDomain.kt deleted file mode 100644 index dc3006e8..00000000 --- a/opendc-simulator/opendc-simulator-failures/src/main/kotlin/org/opendc/simulator/failures/FailureDomain.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2020 atlarge-research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.failures - -import kotlinx.coroutines.CoroutineScope - -/** - * A logical or physical component in a computing environment which may fail. - */ -public interface FailureDomain { - /** - * The lifecycle of the failure domain to which a [FaultInjector] will attach. - */ - public val scope: CoroutineScope - - /** - * Fail the domain externally. - */ - public suspend fun fail() - - /** - * Resume the failure domain. - */ - public suspend fun recover() -} diff --git a/opendc-simulator/opendc-simulator-failures/src/main/kotlin/org/opendc/simulator/failures/FaultInjector.kt b/opendc-simulator/opendc-simulator-failures/src/main/kotlin/org/opendc/simulator/failures/FaultInjector.kt deleted file mode 100644 index a866260c..00000000 --- a/opendc-simulator/opendc-simulator-failures/src/main/kotlin/org/opendc/simulator/failures/FaultInjector.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2020 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.failures - -/** - * An interface for stochastically injecting faults into a running system. - */ -public interface FaultInjector { - /** - * Enqueue the specified [FailureDomain] into the queue as candidate for failure injection in the future. - */ - public fun enqueue(domain: FailureDomain) -} diff --git a/opendc-simulator/opendc-simulator-failures/src/main/kotlin/org/opendc/simulator/failures/UncorrelatedFaultInjector.kt b/opendc-simulator/opendc-simulator-failures/src/main/kotlin/org/opendc/simulator/failures/UncorrelatedFaultInjector.kt deleted file mode 100644 index b3bd737e..00000000 --- a/opendc-simulator/opendc-simulator-failures/src/main/kotlin/org/opendc/simulator/failures/UncorrelatedFaultInjector.kt +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2020 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.failures - -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import java.time.Clock -import kotlin.math.ln1p -import kotlin.math.pow -import kotlin.random.Random - -/** - * A [FaultInjector] that injects uncorrelated faults into the system, meaning that failures of the subsystems are - * independent. - */ -public class UncorrelatedFaultInjector( - private val clock: Clock, - private val alpha: Double, - private val beta: Double, - private val random: Random = Random(0) -) : FaultInjector { - /** - * Enqueue the specified [FailureDomain] to fail some time in the future. - */ - override fun enqueue(domain: FailureDomain) { - domain.scope.launch { - val d = random.weibull(alpha, beta) * 1e3 // Make sure to convert delay to milliseconds - - // Handle long overflow - if (clock.millis() + d <= 0) { - return@launch - } - - delay(d.toLong()) - domain.fail() - } - } - - // XXX We should extract this in some common package later on. - private fun Random.weibull(alpha: Double, beta: Double) = (beta * (-ln1p(-nextDouble())).pow(1.0 / alpha)) -} diff --git a/opendc-simulator/opendc-simulator-flow/build.gradle.kts b/opendc-simulator/opendc-simulator-flow/build.gradle.kts new file mode 100644 index 00000000..05e21c3c --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/build.gradle.kts @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +description = "High-performance flow simulator" + +plugins { + `kotlin-library-conventions` + `testing-conventions` + `jacoco-conventions` + `benchmark-conventions` +} + +dependencies { + api(platform(projects.opendcPlatform)) + api(libs.kotlinx.coroutines) + implementation(libs.kotlin.logging) + + testImplementation(projects.opendcSimulator.opendcSimulatorCore) + testImplementation(libs.slf4j.simple) +} diff --git a/opendc-simulator/opendc-simulator-flow/src/jmh/kotlin/org/opendc/simulator/flow/FlowBenchmarks.kt b/opendc-simulator/opendc-simulator-flow/src/jmh/kotlin/org/opendc/simulator/flow/FlowBenchmarks.kt new file mode 100644 index 00000000..aabd2220 --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/jmh/kotlin/org/opendc/simulator/flow/FlowBenchmarks.kt @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.flow + +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.launch +import org.opendc.simulator.core.runBlockingSimulation +import org.opendc.simulator.flow.mux.ForwardingFlowMultiplexer +import org.opendc.simulator.flow.mux.MaxMinFlowMultiplexer +import org.opendc.simulator.flow.source.TraceFlowSource +import org.openjdk.jmh.annotations.* +import java.util.concurrent.ThreadLocalRandom +import java.util.concurrent.TimeUnit + +@State(Scope.Thread) +@Fork(1) +@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS) +@Measurement(iterations = 5, time = 3, timeUnit = TimeUnit.SECONDS) +@OptIn(ExperimentalCoroutinesApi::class) +class FlowBenchmarks { + private lateinit var trace: Sequence<TraceFlowSource.Fragment> + + @Setup + fun setUp() { + val random = ThreadLocalRandom.current() + val entries = List(10000) { TraceFlowSource.Fragment(1000, random.nextDouble(0.0, 4500.0)) } + trace = entries.asSequence() + } + + @Benchmark + fun benchmarkSink() { + return runBlockingSimulation { + val engine = FlowEngine(coroutineContext, clock) + val provider = FlowSink(engine, 4200.0) + return@runBlockingSimulation provider.consume(TraceFlowSource(trace)) + } + } + + @Benchmark + fun benchmarkForward() { + return runBlockingSimulation { + val engine = FlowEngine(coroutineContext, clock) + val provider = FlowSink(engine, 4200.0) + val forwarder = FlowForwarder(engine) + provider.startConsumer(forwarder) + return@runBlockingSimulation forwarder.consume(TraceFlowSource(trace)) + } + } + + @Benchmark + fun benchmarkMuxMaxMinSingleSource() { + return runBlockingSimulation { + val engine = FlowEngine(coroutineContext, clock) + val switch = MaxMinFlowMultiplexer(engine) + + FlowSink(engine, 3000.0).startConsumer(switch.newOutput()) + FlowSink(engine, 3000.0).startConsumer(switch.newOutput()) + + val provider = switch.newInput() + return@runBlockingSimulation provider.consume(TraceFlowSource(trace)) + } + } + + @Benchmark + fun benchmarkMuxMaxMinTripleSource() { + return runBlockingSimulation { + val engine = FlowEngine(coroutineContext, clock) + val switch = MaxMinFlowMultiplexer(engine) + + FlowSink(engine, 3000.0).startConsumer(switch.newOutput()) + FlowSink(engine, 3000.0).startConsumer(switch.newOutput()) + + repeat(3) { + launch { + val provider = switch.newInput() + provider.consume(TraceFlowSource(trace)) + } + } + } + } + + @Benchmark + fun benchmarkMuxExclusiveSingleSource() { + return runBlockingSimulation { + val engine = FlowEngine(coroutineContext, clock) + val switch = ForwardingFlowMultiplexer(engine) + + FlowSink(engine, 3000.0).startConsumer(switch.newOutput()) + FlowSink(engine, 3000.0).startConsumer(switch.newOutput()) + + val provider = switch.newInput() + return@runBlockingSimulation provider.consume(TraceFlowSource(trace)) + } + } + + @Benchmark + fun benchmarkMuxExclusiveTripleSource() { + return runBlockingSimulation { + val engine = FlowEngine(coroutineContext, clock) + val switch = ForwardingFlowMultiplexer(engine) + + FlowSink(engine, 3000.0).startConsumer(switch.newOutput()) + FlowSink(engine, 3000.0).startConsumer(switch.newOutput()) + + repeat(2) { + launch { + val provider = switch.newInput() + provider.consume(TraceFlowSource(trace)) + } + } + } + } +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConnection.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConnection.kt new file mode 100644 index 00000000..8ff0bc76 --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConnection.kt @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.flow + +/** + * An active connection between a [FlowSource] and [FlowConsumer]. + */ +public interface FlowConnection : AutoCloseable { + /** + * The capacity of the connection. + */ + public val capacity: Double + + /** + * The flow rate over the connection. + */ + public val rate: Double + + /** + * The flow demand of the source. + */ + public val demand: Double + + /** + * A flag to control whether [FlowSource.onConverge] should be invoked for this source. + */ + public var shouldSourceConverge: Boolean + + /** + * Pull the source. + */ + public fun pull() + + /** + * Pull the source. + * + * @param now The timestamp at which the connection is pulled. + */ + public fun pull(now: Long) + + /** + * Push the given flow [rate] over this connection. + * + * @param rate The rate of the flow to push. + */ + public fun push(rate: Double) + + /** + * Disconnect the consumer from its source. + */ + public override fun close() +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConsumer.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConsumer.kt new file mode 100644 index 00000000..4685a755 --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConsumer.kt @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.flow + +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException + +/** + * A consumer of a [FlowSource]. + */ +public interface FlowConsumer { + /** + * A flag to indicate that the consumer is currently consuming a [FlowSource]. + */ + public val isActive: Boolean + + /** + * The flow capacity of this consumer. + */ + public val capacity: Double + + /** + * The current flow rate of the consumer. + */ + public val rate: Double + + /** + * The current flow demand. + */ + public val demand: Double + + /** + * The flow counters to track the flow metrics of the consumer. + */ + public val counters: FlowCounters + + /** + * Start consuming the specified [source]. + * + * @throws IllegalStateException if the consumer is already active. + */ + public fun startConsumer(source: FlowSource) + + /** + * Ask the consumer to pull its source. + * + * If the consumer is not active, this operation will be a no-op. + */ + public fun pull() + + /** + * Disconnect the consumer from its source. + * + * If the consumer is not active, this operation will be a no-op. + */ + public fun cancel() +} + +/** + * Consume the specified [source] and suspend execution until the source is fully consumed or failed. + */ +public suspend fun FlowConsumer.consume(source: FlowSource) { + return suspendCancellableCoroutine { cont -> + startConsumer(object : FlowSource { + override fun onStart(conn: FlowConnection, now: Long) { + try { + source.onStart(conn, now) + } catch (cause: Throwable) { + cont.resumeWithException(cause) + throw cause + } + } + + override fun onStop(conn: FlowConnection, now: Long, delta: Long) { + try { + source.onStop(conn, now, delta) + + if (!cont.isCompleted) { + cont.resume(Unit) + } + } catch (cause: Throwable) { + cont.resumeWithException(cause) + throw cause + } + } + + override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long { + return try { + source.onPull(conn, now, delta) + } catch (cause: Throwable) { + cont.resumeWithException(cause) + throw cause + } + } + + override fun onConverge(conn: FlowConnection, now: Long, delta: Long) { + try { + source.onConverge(conn, now, delta) + } catch (cause: Throwable) { + cont.resumeWithException(cause) + throw cause + } + } + + override fun toString(): String = "SuspendingFlowSource" + }) + + cont.invokeOnCancellation { cancel() } + } +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConsumerContext.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConsumerContext.kt new file mode 100644 index 00000000..98922ab3 --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConsumerContext.kt @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.flow + +/** + * A controllable [FlowConnection]. + * + * This interface is used by [FlowConsumer]s to control the connection between it and the source. + */ +public interface FlowConsumerContext : FlowConnection { + /** + * The deadline of the source. + */ + public val deadline: Long + + /** + * The capacity of the connection. + */ + public override var capacity: Double + + /** + * A flag to control whether [FlowConsumerLogic.onConverge] should be invoked for the consumer. + */ + public var shouldConsumerConverge: Boolean + + /** + * A flag to control whether the timers for the [FlowSource] should be enabled. + */ + public var enableTimers: Boolean + + /** + * Start the flow over the connection. + */ + public fun start() + + /** + * Synchronously pull the source of the connection. + * + * @param now The timestamp at which the connection is pulled. + */ + public fun pullSync(now: Long) +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConsumerLogic.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConsumerLogic.kt new file mode 100644 index 00000000..50fbc9c7 --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConsumerLogic.kt @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.flow + +/** + * A collection of callbacks associated with a [FlowConsumer]. + */ +public interface FlowConsumerLogic { + /** + * This method is invoked when a [FlowSource] changes the rate of flow to this consumer. + * + * @param ctx The context in which the provider runs. + * @param now The virtual timestamp in milliseconds at which the update is occurring. + * @param delta The virtual duration between this call and the last call to [onPush] in milliseconds. + * @param rate The requested processing rate of the source. + */ + public fun onPush(ctx: FlowConsumerContext, now: Long, delta: Long, rate: Double) {} + + /** + * This method is invoked when the flow graph has converged into a steady-state system. + * + * Make sure to enable [FlowConsumerContext.shouldSourceConverge] if you need this callback. By default, this method + * will not be invoked. + * + * @param ctx The context in which the provider runs. + * @param now The virtual timestamp in milliseconds at which the system converged. + * @param delta The virtual duration between this call and the last call to [onConverge] in milliseconds. + */ + public fun onConverge(ctx: FlowConsumerContext, now: Long, delta: Long) {} + + /** + * This method is invoked when the [FlowSource] completed or failed. + * + * @param ctx The context in which the provider runs. + * @param now The virtual timestamp in milliseconds at which the provider finished. + * @param delta The virtual duration between this call and the last call to [onPush] in milliseconds. + * @param cause The cause of the failure or `null` if the source completed. + */ + public fun onFinish(ctx: FlowConsumerContext, now: Long, delta: Long, cause: Throwable?) {} +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConvergenceListener.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConvergenceListener.kt new file mode 100644 index 00000000..d1afda6f --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConvergenceListener.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.flow + +/** + * A listener interface for when a flow stage has converged into a steady-state. + */ +public interface FlowConvergenceListener { + /** + * This method is invoked when the system has converged to a steady-state. + * + * @param now The timestamp at which the system converged. + * @param delta The virtual duration between this call and the last call to [onConverge] in milliseconds. + */ + public fun onConverge(now: Long, delta: Long) {} +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowCounters.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowCounters.kt new file mode 100644 index 00000000..a717ae6e --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowCounters.kt @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.flow + +/** + * An interface that tracks cumulative counts of the flow accumulation over a stage. + */ +public interface FlowCounters { + /** + * The accumulated flow that a source wanted to push over the connection. + */ + public val demand: Double + + /** + * The accumulated flow that was actually transferred over the connection. + */ + public val actual: Double + + /** + * The amount of capacity that was not utilized. + */ + public val remaining: Double + + /** + * The accumulated flow lost due to interference between sources. + */ + public val interference: Double + + /** + * Reset the flow counters. + */ + public fun reset() +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowEngine.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowEngine.kt new file mode 100644 index 00000000..65224827 --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowEngine.kt @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.flow + +import org.opendc.simulator.flow.internal.FlowEngineImpl +import java.time.Clock +import kotlin.coroutines.CoroutineContext + +/** + * A [FlowEngine] is responsible for managing the interaction between [FlowSource]s and [FlowConsumer]s. + * + * The engine centralizes the scheduling logic of state updates of flow connections, allowing update propagation + * to happen more efficiently. and overall, reducing the work necessary to transition into a steady state. + */ +public interface FlowEngine { + /** + * The virtual [Clock] associated with this engine. + */ + public val clock: Clock + + /** + * Create a new [FlowConsumerContext] with the given [provider]. + * + * @param consumer The consumer logic. + * @param provider The logic of the resource provider. + */ + public fun newContext(consumer: FlowSource, provider: FlowConsumerLogic): FlowConsumerContext + + /** + * Start batching the execution of resource updates until [popBatch] is called. + * + * This method is useful if you want to propagate multiple resources updates (e.g., starting multiple CPUs + * simultaneously) in a single state update. + * + * Multiple calls to this method requires the same number of [popBatch] calls in order to properly flush the + * resource updates. This allows nested calls to [pushBatch], but might cause issues if [popBatch] is not called + * the same amount of times. To simplify batching, see [batch]. + */ + public fun pushBatch() + + /** + * Stop the batching of resource updates and run the interpreter on the batch. + * + * Note that method will only flush the event once the first call to [pushBatch] has received a [popBatch] call. + */ + public fun popBatch() + + public companion object { + /** + * Construct a new [FlowEngine] implementation. + * + * @param context The coroutine context to use. + * @param clock The virtual simulation clock. + */ + @JvmStatic + @JvmName("create") + public operator fun invoke(context: CoroutineContext, clock: Clock): FlowEngine { + return FlowEngineImpl(context, clock) + } + } +} + +/** + * Batch the execution of several interrupts into a single call. + * + * This method is useful if you want to propagate the start of multiple resources (e.g., CPUs) in a single update. + */ +public inline fun FlowEngine.batch(block: () -> Unit) { + try { + pushBatch() + block() + } finally { + popBatch() + } +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowForwarder.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowForwarder.kt new file mode 100644 index 00000000..e3bdd7ba --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowForwarder.kt @@ -0,0 +1,256 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.flow + +import mu.KotlinLogging +import org.opendc.simulator.flow.internal.D_MS_TO_S +import org.opendc.simulator.flow.internal.MutableFlowCounters +import kotlin.math.max + +/** + * A class that acts as a [FlowSource] and [FlowConsumer] at the same time. + * + * @param engine The [FlowEngine] the forwarder runs in. + * @param isCoupled A flag to indicate that the transformer will exit when the resource consumer exits. + */ +public class FlowForwarder(private val engine: FlowEngine, private val isCoupled: Boolean = false) : FlowSource, FlowConsumer, AutoCloseable { + /** + * The logging instance of this connection. + */ + private val logger = KotlinLogging.logger {} + + /** + * The delegate [FlowSource]. + */ + private var delegate: FlowSource? = null + + /** + * A flag to indicate that the delegate was started. + */ + private var hasDelegateStarted: Boolean = false + + /** + * The exposed [FlowConnection]. + */ + private val _ctx = object : FlowConnection { + override var shouldSourceConverge: Boolean = false + set(value) { + field = value + _innerCtx?.shouldSourceConverge = value + } + + override val capacity: Double + get() = _innerCtx?.capacity ?: 0.0 + + override val demand: Double + get() = _innerCtx?.demand ?: 0.0 + + override val rate: Double + get() = _innerCtx?.rate ?: 0.0 + + override fun pull() { + _innerCtx?.pull() + } + + override fun pull(now: Long) { + _innerCtx?.pull(now) + } + + @JvmField var lastPull = Long.MAX_VALUE + + override fun push(rate: Double) { + if (delegate == null) { + return + } + + _innerCtx?.push(rate) + _demand = rate + } + + override fun close() { + val delegate = delegate ?: return + val hasDelegateStarted = hasDelegateStarted + + // Warning: resumption of the continuation might change the entire state of the forwarder. Make sure we + // reset beforehand the existing state and check whether it has been updated afterwards + reset() + + if (hasDelegateStarted) { + val now = engine.clock.millis() + val delta = max(0, now - lastPull) + delegate.onStop(this, now, delta) + } + } + } + + /** + * The [FlowConnection] in which the forwarder runs. + */ + private var _innerCtx: FlowConnection? = null + + override val isActive: Boolean + get() = delegate != null + + override val capacity: Double + get() = _ctx.capacity + + override val rate: Double + get() = _ctx.rate + + override val demand: Double + get() = _ctx.demand + + override val counters: FlowCounters + get() = _counters + private val _counters = MutableFlowCounters() + + override fun startConsumer(source: FlowSource) { + check(delegate == null) { "Forwarder already active" } + + delegate = source + + // Pull to replace the source + pull() + } + + override fun pull() { + _ctx.pull() + } + + override fun cancel() { + _ctx.close() + } + + override fun close() { + val ctx = _innerCtx + + if (ctx != null) { + this._innerCtx = null + ctx.pull() + } + } + + override fun onStart(conn: FlowConnection, now: Long) { + _innerCtx = conn + + if (_ctx.shouldSourceConverge) { + conn.shouldSourceConverge = true + } + } + + override fun onStop(conn: FlowConnection, now: Long, delta: Long) { + _innerCtx = null + + val delegate = delegate + if (delegate != null) { + reset() + + try { + delegate.onStop(this._ctx, now, delta) + } catch (cause: Throwable) { + logger.error(cause) { "Uncaught exception" } + } + } + } + + override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long { + val delegate = delegate + + if (!hasDelegateStarted) { + start() + } + + _ctx.lastPull = now + updateCounters(conn, delta) + + return try { + delegate?.onPull(_ctx, now, delta) ?: Long.MAX_VALUE + } catch (cause: Throwable) { + logger.error(cause) { "Uncaught exception" } + + reset() + Long.MAX_VALUE + } + } + + override fun onConverge(conn: FlowConnection, now: Long, delta: Long) { + try { + delegate?.onConverge(this._ctx, now, delta) + } catch (cause: Throwable) { + logger.error(cause) { "Uncaught exception" } + + _innerCtx = null + reset() + } + } + + /** + * Start the delegate. + */ + private fun start() { + val delegate = delegate ?: return + + try { + delegate.onStart(_ctx, engine.clock.millis()) + hasDelegateStarted = true + } catch (cause: Throwable) { + logger.error(cause) { "Uncaught exception" } + reset() + } + } + + /** + * Reset the delegate. + */ + private fun reset() { + if (isCoupled) + _innerCtx?.close() + else + _innerCtx?.push(0.0) + + delegate = null + hasDelegateStarted = false + } + + /** + * The requested flow rate. + */ + private var _demand: Double = 0.0 + + /** + * Update the flow counters for the transformer. + */ + private fun updateCounters(ctx: FlowConnection, delta: Long) { + if (delta <= 0) { + return + } + + val counters = _counters + val deltaS = delta * D_MS_TO_S + val total = ctx.capacity * deltaS + val work = _demand * deltaS + val actualWork = ctx.rate * deltaS + + counters.increment(work, actualWork, (total - actualWork), 0.0) + } +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowMapper.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowMapper.kt new file mode 100644 index 00000000..6867bcef --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowMapper.kt @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.flow + +/** + * A [FlowConsumer] that maps the pushed flow through [transform]. + * + * @param source The source of the flow. + * @param transform The method to transform the flow. + */ +public class FlowMapper( + private val source: FlowSource, + private val transform: (FlowConnection, Double) -> Double +) : FlowSource { + + /** + * The current active connection. + */ + private var _conn: Connection? = null + + override fun onStart(conn: FlowConnection, now: Long) { + check(_conn == null) { "Concurrent access" } + val delegate = Connection(conn, transform) + _conn = delegate + source.onStart(delegate, now) + } + + override fun onStop(conn: FlowConnection, now: Long, delta: Long) { + val delegate = checkNotNull(_conn) { "Invariant violation" } + _conn = null + source.onStop(delegate, now, delta) + } + + override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long { + val delegate = checkNotNull(_conn) { "Invariant violation" } + return source.onPull(delegate, now, delta) + } + + override fun onConverge(conn: FlowConnection, now: Long, delta: Long) { + val delegate = _conn ?: return + source.onConverge(delegate, now, delta) + } + + /** + * The wrapper [FlowConnection] that is used to transform the flow. + */ + private class Connection( + private val delegate: FlowConnection, + private val transform: (FlowConnection, Double) -> Double + ) : FlowConnection by delegate { + override fun push(rate: Double) { + delegate.push(transform(this, rate)) + } + } +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowSink.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowSink.kt new file mode 100644 index 00000000..e9094443 --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowSink.kt @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.flow + +import org.opendc.simulator.flow.internal.D_MS_TO_S +import org.opendc.simulator.flow.internal.MutableFlowCounters + +/** + * A [FlowSink] represents a sink with a fixed capacity. + * + * @param initialCapacity The initial capacity of the resource. + * @param engine The engine that is used for driving the flow simulation. + * @param parent The parent flow system. + */ +public class FlowSink( + private val engine: FlowEngine, + initialCapacity: Double, + private val parent: FlowConvergenceListener? = null +) : FlowConsumer { + /** + * A flag to indicate that the flow consumer is active. + */ + public override val isActive: Boolean + get() = _ctx != null + + /** + * The capacity of the consumer. + */ + public override var capacity: Double = initialCapacity + set(value) { + field = value + _ctx?.capacity = value + } + + /** + * The current processing rate of the consumer. + */ + public override val rate: Double + get() = _ctx?.rate ?: 0.0 + + /** + * The flow processing rate demand at this instant. + */ + public override val demand: Double + get() = _ctx?.demand ?: 0.0 + + /** + * The flow counters to track the flow metrics of the consumer. + */ + public override val counters: FlowCounters + get() = _counters + private val _counters = MutableFlowCounters() + + /** + * The current active [FlowConsumerLogic] of this sink. + */ + private var _ctx: FlowConsumerContext? = null + + override fun startConsumer(source: FlowSource) { + check(_ctx == null) { "Consumer is in invalid state" } + + val ctx = engine.newContext(source, Logic(parent, _counters)) + _ctx = ctx + + ctx.capacity = capacity + if (parent != null) { + ctx.shouldConsumerConverge = true + } + + ctx.start() + } + + override fun pull() { + _ctx?.pull() + } + + override fun cancel() { + _ctx?.close() + } + + override fun toString(): String = "FlowSink[capacity=$capacity]" + + /** + * [FlowConsumerLogic] of a sink. + */ + private inner class Logic(private val parent: FlowConvergenceListener?, private val counters: MutableFlowCounters) : FlowConsumerLogic { + override fun onPush( + ctx: FlowConsumerContext, + now: Long, + delta: Long, + rate: Double + ) { + updateCounters(ctx, delta, rate, ctx.capacity) + } + + override fun onFinish(ctx: FlowConsumerContext, now: Long, delta: Long, cause: Throwable?) { + updateCounters(ctx, delta, 0.0, 0.0) + + _ctx = null + } + + override fun onConverge(ctx: FlowConsumerContext, now: Long, delta: Long) { + parent?.onConverge(now, delta) + } + + /** + * The previous demand and capacity for the consumer. + */ + private val _previous = DoubleArray(2) + + /** + * Update the counters of the flow consumer. + */ + private fun updateCounters(ctx: FlowConnection, delta: Long, nextDemand: Double, nextCapacity: Double) { + val counters = counters + val previous = _previous + val demand = previous[0] + val capacity = previous[1] + + previous[0] = nextDemand + previous[1] = nextCapacity + + if (delta <= 0) { + return + } + + val deltaS = delta * D_MS_TO_S + val total = demand * deltaS + val work = capacity * deltaS + val actualWork = ctx.rate * deltaS + + counters.increment(work, actualWork, (total - actualWork), 0.0) + } + } +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowSource.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowSource.kt new file mode 100644 index 00000000..3a7e52aa --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowSource.kt @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.flow + +/** + * A source of flow that is consumed by a [FlowConsumer]. + * + * Implementations of this interface should be considered stateful and must be assumed not to be re-usable + * (concurrently) for multiple [FlowConsumer]s, unless explicitly said otherwise. + */ +public interface FlowSource { + /** + * This method is invoked when the source is started. + * + * @param conn The connection between the source and consumer. + * @param now The virtual timestamp in milliseconds at which the provider finished. + */ + public fun onStart(conn: FlowConnection, now: Long) {} + + /** + * This method is invoked when the source is finished. + * + * @param conn The connection between the source and consumer. + * @param now The virtual timestamp in milliseconds at which the source finished. + * @param delta The virtual duration between this call and the last call to [onPull] in milliseconds. + */ + public fun onStop(conn: FlowConnection, now: Long, delta: Long) {} + + /** + * This method is invoked when the source is pulled. + * + * @param conn The connection between the source and consumer. + * @param now The virtual timestamp in milliseconds at which the pull is occurring. + * @param delta The virtual duration between this call and the last call to [onPull] in milliseconds. + * @return The duration after which the resource consumer should be pulled again. + */ + public fun onPull(conn: FlowConnection, now: Long, delta: Long): Long + + /** + * This method is invoked when the flow graph has converged into a steady-state system. + * + * Make sure to enable [FlowConnection.shouldSourceConverge] if you need this callback. By default, this method + * will not be invoked. + * + * @param conn The connection between the source and consumer. + * @param now The virtual timestamp in milliseconds at which the system converged. + * @param delta The virtual duration between this call and the last call to [onConverge] in milliseconds. + */ + public fun onConverge(conn: FlowConnection, now: Long, delta: Long) {} +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/interference/InterferenceDomain.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/interference/InterferenceDomain.kt new file mode 100644 index 00000000..aa2713b6 --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/interference/InterferenceDomain.kt @@ -0,0 +1,19 @@ +package org.opendc.simulator.flow.interference + +import org.opendc.simulator.flow.FlowSource + +/** + * An interference domain represents a system of flow stages where [flow sources][FlowSource] may incur + * performance variability due to operating on the same resource and therefore causing interference. + */ +public interface InterferenceDomain { + /** + * Compute the performance score of a participant in this interference domain. + * + * @param key The participant to obtain the score of or `null` if the participant has no key. + * @param load The overall load on the interference domain. + * @return A score representing the performance score to be applied to the resource consumer, with 1 + * meaning no influence, <1 means that performance degrades, and >1 means that performance improves. + */ + public fun apply(key: InterferenceKey?, load: Double): Double +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/interference/InterferenceKey.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/interference/InterferenceKey.kt new file mode 100644 index 00000000..d28ebde5 --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/interference/InterferenceKey.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.flow.interference + +/** + * A key that uniquely identifies a participant of an interference domain. + */ +public interface InterferenceKey diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/Constants.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/Constants.kt new file mode 100644 index 00000000..450195ec --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/Constants.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.flow.internal + +/** + * Constant for converting milliseconds into seconds. + */ +internal const val D_MS_TO_S = 1 / 1000.0 diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/Flags.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/Flags.kt new file mode 100644 index 00000000..97d56fff --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/Flags.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.flow.internal + +/** + * States of the flow connection. + */ +internal const val ConnPending = 0 // Connection is pending and the consumer is waiting to consume the source +internal const val ConnActive = 1 // Connection is active and the source is currently being consumed +internal const val ConnClosed = 2 // Connection is closed and source cannot be consumed through this connection anymore +internal const val ConnState = 0b11 // Mask for accessing the state of the flow connection + +/** + * Flags of the flow connection + */ +internal const val ConnPulled = 1 shl 2 // The source should be pulled +internal const val ConnPushed = 1 shl 3 // The source has pushed a value +internal const val ConnClose = 1 shl 4 // The connection should be closed +internal const val ConnUpdateActive = 1 shl 5 // An update for the connection is active +internal const val ConnUpdatePending = 1 shl 6 // An (immediate) update of the connection is pending +internal const val ConnConvergePending = 1 shl 7 // Indication that a convergence is already pending +internal const val ConnConvergeSource = 1 shl 8 // Enable convergence of the source +internal const val ConnConvergeConsumer = 1 shl 9 // Enable convergence of the consumer +internal const val ConnDisableTimers = 1 shl 10 // Disable timers for the source diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowConsumerContextImpl.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowConsumerContextImpl.kt new file mode 100644 index 00000000..58ca918b --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowConsumerContextImpl.kt @@ -0,0 +1,452 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.flow.internal + +import mu.KotlinLogging +import org.opendc.simulator.flow.* +import java.util.* +import kotlin.math.max +import kotlin.math.min + +/** + * Implementation of a [FlowConnection] managing the communication between flow sources and consumers. + */ +internal class FlowConsumerContextImpl( + private val engine: FlowEngineImpl, + private val source: FlowSource, + private val logic: FlowConsumerLogic +) : FlowConsumerContext { + /** + * The logging instance of this connection. + */ + private val logger = KotlinLogging.logger {} + + /** + * The capacity of the connection. + */ + override var capacity: Double + get() = _capacity + set(value) { + val oldValue = _capacity + + // Only changes will be propagated + if (value != oldValue) { + _capacity = value + pull() + } + } + private var _capacity: Double = 0.0 + + /** + * The current processing rate of the connection. + */ + override val rate: Double + get() = _rate + private var _rate = 0.0 + + /** + * The current flow processing demand. + */ + override val demand: Double + get() = _demand + private var _demand: Double = 0.0 // The current (pending) demand of the source + + /** + * The deadline of the source. + */ + override val deadline: Long + get() = _deadline + private var _deadline: Long = Long.MAX_VALUE // The deadline of the source's timer + + /** + * Flags to control the convergence of the consumer and source. + */ + override var shouldSourceConverge: Boolean + get() = _flags and ConnConvergeSource == ConnConvergeSource + set(value) { + _flags = + if (value) + _flags or ConnConvergeSource + else + _flags and ConnConvergeSource.inv() + } + override var shouldConsumerConverge: Boolean + get() = _flags and ConnConvergeConsumer == ConnConvergeConsumer + set(value) { + _flags = + if (value) + _flags or ConnConvergeConsumer + else + _flags and ConnConvergeConsumer.inv() + } + + /** + * Flag to control the timers on the [FlowSource] + */ + override var enableTimers: Boolean + get() = _flags and ConnDisableTimers != ConnDisableTimers + set(value) { + _flags = + if (!value) + _flags or ConnDisableTimers + else + _flags and ConnDisableTimers.inv() + } + + /** + * The clock to track simulation time. + */ + private val _clock = engine.clock + + /** + * The flags of the flow connection, indicating certain actions. + */ + private var _flags: Int = 0 + + /** + * The timestamp of calls to the callbacks. + */ + private var _lastPull: Long = Long.MIN_VALUE // Last call to `onPull` + private var _lastPush: Long = Long.MIN_VALUE // Last call to `onPush` + private var _lastSourceConvergence: Long = Long.MAX_VALUE // Last call to source `onConvergence` + private var _lastConsumerConvergence: Long = Long.MAX_VALUE // Last call to consumer `onConvergence` + + /** + * The timers at which the context is scheduled to be interrupted. + */ + private var _timer: Long = Long.MAX_VALUE + private val _pendingTimers: ArrayDeque<Long> = ArrayDeque(5) + + override fun start() { + check(_flags and ConnState == ConnPending) { "Consumer is already started" } + engine.batch { + val now = _clock.millis() + source.onStart(this, now) + + // Mark the connection as active and pulled + val newFlags = (_flags and ConnState.inv()) or ConnActive or ConnPulled + scheduleImmediate(now, newFlags) + } + } + + override fun close() { + val flags = _flags + if (flags and ConnState == ConnClosed) { + return + } + + // Toggle the close bit. In case no update is active, schedule a new update. + if (flags and ConnUpdateActive == 0) { + val now = _clock.millis() + scheduleImmediate(now, flags or ConnClose) + } else { + _flags = flags or ConnClose + } + } + + override fun pull(now: Long) { + val flags = _flags + if (flags and ConnState != ConnActive) { + return + } + + // Mark connection as pulled + scheduleImmediate(now, flags or ConnPulled) + } + + override fun pull() { + pull(_clock.millis()) + } + + override fun pullSync(now: Long) { + val flags = _flags + + // Do not attempt to flush the connection if the connection is closed or an update is already active + if (flags and ConnState != ConnActive || flags and ConnUpdateActive != 0) { + return + } + + if (flags and (ConnPulled or ConnPushed) != 0 || _deadline == now) { + engine.scheduleSync(now, this) + } + } + + override fun push(rate: Double) { + if (_demand == rate) { + return + } + + _demand = rate + + val flags = _flags + + if (flags and ConnUpdateActive != 0) { + // If an update is active, it will already get picked up at the end of the update + _flags = flags or ConnPushed + } else { + // Invalidate only if no update is active + scheduleImmediate(_clock.millis(), flags or ConnPushed) + } + } + + /** + * Update the state of the flow connection. + * + * @param now The current virtual timestamp. + * @param visited The queue of connections that have been visited during the cycle. + * @param timerQueue The queue of all pending timers. + * @param isImmediate A flag to indicate that this invocation is an immediate update or a delayed update. + */ + fun doUpdate( + now: Long, + visited: FlowDeque, + timerQueue: FlowTimerQueue, + isImmediate: Boolean + ) { + var flags = _flags + + // Precondition: The flow connection must be active + if (flags and ConnState != ConnActive) { + return + } + + val deadline = _deadline + val reachedDeadline = deadline == now + var newDeadline: Long + var hasUpdated = false + + try { + // Pull the source if (1) `pull` is called or (2) the timer of the source has expired + newDeadline = if (flags and ConnPulled != 0 || reachedDeadline) { + val lastPull = _lastPull + val delta = max(0, now - lastPull) + + // Update state before calling into the outside world, so it observes a consistent state + _lastPull = now + _flags = (flags and ConnPulled.inv()) or ConnUpdateActive + hasUpdated = true + + val duration = source.onPull(this, now, delta) + + // IMPORTANT: Re-fetch the flags after the callback might have changed those + flags = _flags + + if (duration != Long.MAX_VALUE) + now + duration + else + duration + } else { + deadline + } + + // Make the new deadline available for the consumer if it has changed + if (newDeadline != deadline) { + _deadline = newDeadline + } + + // Push to the consumer if the rate of the source has changed (after a call to `push`) + if (flags and ConnPushed != 0) { + val lastPush = _lastPush + val delta = max(0, now - lastPush) + + // Update state before calling into the outside world, so it observes a consistent state + _lastPush = now + _flags = (flags and ConnPushed.inv()) or ConnUpdateActive + hasUpdated = true + + logic.onPush(this, now, delta, _demand) + + // IMPORTANT: Re-fetch the flags after the callback might have changed those + flags = _flags + } + + // Check whether the source or consumer have tried to close the connection + if (flags and ConnClose != 0) { + hasUpdated = true + + // The source has called [FlowConnection.close], so clean up the connection + doStopSource(now) + + // IMPORTANT: Re-fetch the flags after the callback might have changed those + // We now also mark the connection as closed + flags = (_flags and ConnState.inv()) or ConnClosed + + _demand = 0.0 + newDeadline = Long.MAX_VALUE + } + } catch (cause: Throwable) { + hasUpdated = true + + // Clean up the connection + doFailSource(now, cause) + + // Mark the connection as closed + flags = (flags and ConnState.inv()) or ConnClosed + + _demand = 0.0 + newDeadline = Long.MAX_VALUE + } + + // Check whether the connection needs to be added to the visited queue. This is the case when: + // (1) An update was performed (either a push or a pull) + // (2) Either the source or consumer want to converge, and + // (3) Convergence is not already pending (ConnConvergePending) + if (hasUpdated && flags and (ConnConvergeSource or ConnConvergeConsumer) != 0 && flags and ConnConvergePending == 0) { + visited.add(this) + flags = flags or ConnConvergePending + } + + // Compute the new flow rate of the connection + // Note: _demand might be changed by [logic.onConsume], so we must re-fetch the value + _rate = min(_capacity, _demand) + + // Indicate that no update is active anymore and flush the flags + _flags = flags and ConnUpdateActive.inv() and ConnUpdatePending.inv() + + val pendingTimers = _pendingTimers + + // Prune the head timer if this is a delayed update + val timer = if (!isImmediate) { + // Invariant: Any pending timer should only point to a future timestamp + val timer = pendingTimers.poll() ?: Long.MAX_VALUE + _timer = timer + timer + } else { + _timer + } + + // Check whether we need to schedule a new timer for this connection. That is the case when: + // (1) The deadline is valid (not the maximum value) + // (2) The connection is active + // (3) Timers are not disabled for the source + // (4) The current active timer for the connection points to a later deadline + if (newDeadline == Long.MAX_VALUE || + flags and ConnState != ConnActive || + flags and ConnDisableTimers != 0 || + (timer != Long.MAX_VALUE && newDeadline >= timer) + ) { + // Ignore any deadline scheduled at the maximum value + // This indicates that the source does not want to register a timer + return + } + + // Construct a timer for the new deadline and add it to the global queue of timers + _timer = newDeadline + timerQueue.add(this, newDeadline) + + // Slow-path: a timer already exists for this connection, so add it to the queue of pending timers + if (timer != Long.MAX_VALUE) { + pendingTimers.addFirst(timer) + } + } + + /** + * This method is invoked when the system converges into a steady state. + */ + fun onConverge(now: Long) { + try { + val flags = _flags + + // The connection is converging now, so unset the convergence pending flag + _flags = flags and ConnConvergePending.inv() + + // Call the source converge callback if it has enabled convergence + if (flags and ConnConvergeSource != 0) { + val delta = max(0, now - _lastSourceConvergence) + _lastSourceConvergence = now + + source.onConverge(this, now, delta) + } + + // Call the consumer callback if it has enabled convergence + if (flags and ConnConvergeConsumer != 0) { + val delta = max(0, now - _lastConsumerConvergence) + _lastConsumerConvergence = now + + logic.onConverge(this, now, delta) + } + } catch (cause: Throwable) { + // Invoke the finish callbacks + doFailSource(now, cause) + + // Mark the connection as closed + _flags = (_flags and ConnState.inv()) or ConnClosed + _demand = 0.0 + _deadline = Long.MAX_VALUE + } + } + + override fun toString(): String = "FlowConsumerContextImpl[capacity=$capacity,rate=$_rate]" + + /** + * Stop the [FlowSource]. + */ + private fun doStopSource(now: Long) { + try { + source.onStop(this, now, max(0, now - _lastPull)) + doFinishConsumer(now, null) + } catch (cause: Throwable) { + doFinishConsumer(now, cause) + } + } + + /** + * Fail the [FlowSource]. + */ + private fun doFailSource(now: Long, cause: Throwable) { + try { + source.onStop(this, now, max(0, now - _lastPull)) + } catch (e: Throwable) { + e.addSuppressed(cause) + doFinishConsumer(now, e) + } + } + + /** + * Finish the consumer. + */ + private fun doFinishConsumer(now: Long, cause: Throwable?) { + try { + logic.onFinish(this, now, max(0, now - _lastPush), cause) + } catch (e: Throwable) { + e.addSuppressed(cause) + logger.error(e) { "Uncaught exception" } + } + } + + /** + * Schedule an immediate update for this connection. + */ + private fun scheduleImmediate(now: Long, flags: Int) { + // In case an immediate update is already scheduled, no need to do anything + if (flags and ConnUpdatePending != 0) { + _flags = flags + return + } + + // Mark the connection that there is an update pending + _flags = flags or ConnUpdatePending + + engine.scheduleImmediate(now, this) + } +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowDeque.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowDeque.kt new file mode 100644 index 00000000..c6cba4b7 --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowDeque.kt @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.flow.internal + +import java.util.* + +/** + * A specialized [ArrayDeque] for [FlowConsumerContextImpl] implementations. + */ +internal class FlowDeque(initialCapacity: Int = 256) { + /** + * The array of elements in the queue. + */ + private var _elements: Array<FlowConsumerContextImpl?> = arrayOfNulls(initialCapacity) + private var _head = 0 + private var _tail = 0 + + /** + * Determine whether this queue is not empty. + */ + fun isNotEmpty(): Boolean { + return _head != _tail + } + + /** + * Add the specified [ctx] to the queue. + */ + fun add(ctx: FlowConsumerContextImpl) { + val es = _elements + var tail = _tail + + es[tail] = ctx + + tail = inc(tail, es.size) + _tail = tail + + if (_head == tail) { + doubleCapacity() + } + } + + /** + * Remove a [FlowConsumerContextImpl] from the queue or `null` if the queue is empty. + */ + fun poll(): FlowConsumerContextImpl? { + val es = _elements + val head = _head + val ctx = es[head] + + if (ctx != null) { + es[head] = null + _head = inc(head, es.size) + } + + return ctx + } + + /** + * Clear the queue. + */ + fun clear() { + _elements.fill(null) + _head = 0 + _tail = 0 + } + + private fun inc(i: Int, modulus: Int): Int { + var x = i + if (++x >= modulus) { + x = 0 + } + return x + } + + /** + * Doubles the capacity of this deque + */ + private fun doubleCapacity() { + assert(_head == _tail) + val p = _head + val n = _elements.size + val r = n - p // number of elements to the right of p + + val newCapacity = n shl 1 + check(newCapacity >= 0) { "Sorry, deque too big" } + + val a = arrayOfNulls<FlowConsumerContextImpl>(newCapacity) + + _elements.copyInto(a, 0, p, r) + _elements.copyInto(a, r, 0, p) + + _elements = a + _head = 0 + _tail = n + } +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowEngineImpl.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowEngineImpl.kt new file mode 100644 index 00000000..3c79d54e --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowEngineImpl.kt @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.flow.internal + +import kotlinx.coroutines.Delay +import kotlinx.coroutines.DisposableHandle +import kotlinx.coroutines.InternalCoroutinesApi +import kotlinx.coroutines.Runnable +import org.opendc.simulator.flow.* +import java.time.Clock +import java.util.* +import kotlin.coroutines.ContinuationInterceptor +import kotlin.coroutines.CoroutineContext + +/** + * Internal implementation of the [FlowEngine] interface. + * + * @param context The coroutine context to use. + * @param clock The virtual simulation clock. + */ +internal class FlowEngineImpl(private val context: CoroutineContext, clock: Clock) : FlowEngine, Runnable { + /** + * The [Delay] instance that provides scheduled execution of [Runnable]s. + */ + @OptIn(InternalCoroutinesApi::class) + private val delay = requireNotNull(context[ContinuationInterceptor] as? Delay) { "Invalid CoroutineDispatcher: no delay implementation" } + + /** + * The queue of connection updates that are scheduled for immediate execution. + */ + private val queue = FlowDeque() + + /** + * A priority queue containing the connection updates to be scheduled in the future. + */ + private val futureQueue = FlowTimerQueue() + + /** + * The stack of engine invocations to occur in the future. + */ + private val futureInvocations = ArrayDeque<Invocation>() + + /** + * The systems that have been visited during the engine cycle. + */ + private val visited = FlowDeque() + + /** + * The index in the batch stack. + */ + private var batchIndex = 0 + + /** + * The virtual [Clock] of this engine. + */ + override val clock: Clock + get() = _clock + private val _clock: Clock = clock + + /** + * Update the specified [ctx] synchronously. + */ + fun scheduleSync(now: Long, ctx: FlowConsumerContextImpl) { + ctx.doUpdate(now, visited, futureQueue, isImmediate = true) + + // In-case the engine is already running in the call-stack, return immediately. The changes will be picked + // up by the active engine. + if (batchIndex > 0) { + return + } + + doRunEngine(now) + } + + /** + * Enqueue the specified [ctx] to be updated immediately during the active engine cycle. + * + * This method should be used when the state of a flow context is invalidated/interrupted and needs to be + * re-computed. In case no engine is currently active, the engine will be started. + */ + fun scheduleImmediate(now: Long, ctx: FlowConsumerContextImpl) { + queue.add(ctx) + + // In-case the engine is already running in the call-stack, return immediately. The changes will be picked + // up by the active engine. + if (batchIndex > 0) { + return + } + + doRunEngine(now) + } + + override fun newContext(consumer: FlowSource, provider: FlowConsumerLogic): FlowConsumerContext = FlowConsumerContextImpl(this, consumer, provider) + + override fun pushBatch() { + batchIndex++ + } + + override fun popBatch() { + try { + // Flush the work if the engine is not already running + if (batchIndex == 1 && queue.isNotEmpty()) { + doRunEngine(_clock.millis()) + } + } finally { + batchIndex-- + } + } + + /* Runnable */ + override fun run() { + val invocation = futureInvocations.poll() // Clear invocation from future invocation queue + doRunEngine(invocation.timestamp) + } + + /** + * Run all the enqueued actions for the specified [timestamp][now]. + */ + private fun doRunEngine(now: Long) { + val queue = queue + val futureQueue = futureQueue + val futureInvocations = futureInvocations + val visited = visited + + try { + // Increment batch index so synchronous calls will not launch concurrent engine invocations + batchIndex++ + + // Execute all scheduled updates at current timestamp + while (true) { + val ctx = futureQueue.poll(now) ?: break + ctx.doUpdate(now, visited, futureQueue, isImmediate = false) + } + + // Repeat execution of all immediate updates until the system has converged to a steady-state + // We have to take into account that the onConverge callback can also trigger new actions. + do { + // Execute all immediate updates + while (true) { + val ctx = queue.poll() ?: break + ctx.doUpdate(now, visited, futureQueue, isImmediate = true) + } + + while (true) { + val ctx = visited.poll() ?: break + ctx.onConverge(now) + } + } while (queue.isNotEmpty()) + } finally { + // Decrement batch index to indicate no engine is active at the moment + batchIndex-- + } + + // Schedule an engine invocation for the next update to occur. + val headDeadline = futureQueue.peekDeadline() + if (headDeadline != Long.MAX_VALUE) { + trySchedule(now, futureInvocations, headDeadline) + } + } + + /** + * Try to schedule an engine invocation at the specified [target]. + * + * @param now The current virtual timestamp. + * @param target The virtual timestamp at which the engine invocation should happen. + * @param scheduled The queue of scheduled invocations. + */ + private fun trySchedule(now: Long, scheduled: ArrayDeque<Invocation>, target: Long) { + val head = scheduled.peek() + + // Only schedule a new scheduler invocation in case the target is earlier than all other pending + // scheduler invocations + if (head == null || target < head.timestamp) { + @OptIn(InternalCoroutinesApi::class) + val handle = delay.invokeOnTimeout(target - now, this, context) + scheduled.addFirst(Invocation(target, handle)) + } + } + + /** + * A future engine invocation. + * + * This class is used to keep track of the future engine invocations created using the [Delay] instance. In case + * the invocation is not needed anymore, it can be cancelled via [cancel]. + */ + private class Invocation( + @JvmField val timestamp: Long, + @JvmField val handle: DisposableHandle + ) { + /** + * Cancel the engine invocation. + */ + fun cancel() = handle.dispose() + } +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowTimerQueue.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowTimerQueue.kt new file mode 100644 index 00000000..22a390e6 --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowTimerQueue.kt @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.flow.internal + +/** + * Specialized priority queue for flow timers. + */ +internal class FlowTimerQueue(initialCapacity: Int = 256) { + /** + * The binary heap of deadlines. + */ + private var _deadlines = LongArray(initialCapacity) { Long.MIN_VALUE } + + /** + * The binary heap of [FlowConsumerContextImpl]s. + */ + private var _pending = arrayOfNulls<FlowConsumerContextImpl>(initialCapacity) + + /** + * The number of elements in the priority queue. + */ + private var size = 0 + + /** + * Register a timer for [ctx] with [deadline]. + */ + fun add(ctx: FlowConsumerContextImpl, deadline: Long) { + val i = size + val deadlines = _deadlines + if (i >= deadlines.size) { + grow() + } + + siftUp(deadlines, _pending, i, ctx, deadline) + + size = i + 1 + } + + /** + * Update all pending [FlowConsumerContextImpl]s at the timestamp [now]. + */ + fun poll(now: Long): FlowConsumerContextImpl? { + if (size == 0) { + return null + } + + val deadlines = _deadlines + val deadline = deadlines[0] + + if (now < deadline) { + return null + } + + val pending = _pending + val res = pending[0] + val s = --size + + val nextDeadline = deadlines[s] + val next = pending[s]!! + + // Clear the last element of the queue + pending[s] = null + deadlines[s] = Long.MIN_VALUE + + if (s != 0) { + siftDown(deadlines, pending, next, nextDeadline) + } + + return res + } + + /** + * Find the earliest deadline in the queue. + */ + fun peekDeadline(): Long { + return if (size == 0) Long.MAX_VALUE else _deadlines[0] + } + + /** + * Increases the capacity of the array. + */ + private fun grow() { + val oldCapacity = _deadlines.size + // Double size if small; else grow by 50% + val newCapacity = oldCapacity + if (oldCapacity < 64) oldCapacity + 2 else oldCapacity shr 1 + + _deadlines = _deadlines.copyOf(newCapacity) + _pending = _pending.copyOf(newCapacity) + } + + /** + * Insert item [ctx] at position [pos], maintaining heap invariant by promoting [ctx] up the tree until it is + * greater than or equal to its parent, or is the root. + * + * @param deadlines The heap of deadlines. + * @param pending The heap of contexts. + * @param pos The position to fill. + * @param ctx The [FlowConsumerContextImpl] to insert. + * @param deadline The deadline of the context. + */ + private fun siftUp( + deadlines: LongArray, + pending: Array<FlowConsumerContextImpl?>, + pos: Int, + ctx: FlowConsumerContextImpl, + deadline: Long + ) { + var k = pos + + while (k > 0) { + val parent = (k - 1) ushr 1 + val parentDeadline = deadlines[parent] + + if (deadline >= parentDeadline) { + break + } + + deadlines[k] = parentDeadline + pending[k] = pending[parent] + + k = parent + } + + deadlines[k] = deadline + pending[k] = ctx + } + + /** + * Inserts [ctx] at the top, maintaining heap invariant by demoting [ctx] down the tree repeatedly until it + * is less than or equal to its children or is a leaf. + * + * @param deadlines The heap of deadlines. + * @param pending The heap of contexts. + * @param ctx The [FlowConsumerContextImpl] to insert. + * @param deadline The deadline of the context. + */ + private fun siftDown( + deadlines: LongArray, + pending: Array<FlowConsumerContextImpl?>, + ctx: FlowConsumerContextImpl, + deadline: Long + ) { + var k = 0 + val size = size + val half = size ushr 1 + + while (k < half) { + var child = (k shl 1) + 1 + + var childDeadline = deadlines[child] + val right = child + 1 + + if (right < size) { + val rightDeadline = deadlines[right] + + if (childDeadline > rightDeadline) { + child = right + childDeadline = rightDeadline + } + } + + if (deadline <= childDeadline) { + break + } + + deadlines[k] = childDeadline + pending[k] = pending[child] + + k = child + } + + deadlines[k] = deadline + pending[k] = ctx + } +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/MutableFlowCounters.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/MutableFlowCounters.kt new file mode 100644 index 00000000..d990dc61 --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/MutableFlowCounters.kt @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.flow.internal + +import org.opendc.simulator.flow.FlowCounters + +/** + * Mutable implementation of the [FlowCounters] interface. + */ +public class MutableFlowCounters : FlowCounters { + override val demand: Double + get() = _counters[0] + override val actual: Double + get() = _counters[1] + override val remaining: Double + get() = _counters[2] + override val interference: Double + get() = _counters[3] + private val _counters = DoubleArray(4) + + override fun reset() { + _counters.fill(0.0) + } + + public fun increment(demand: Double, actual: Double, remaining: Double, interference: Double) { + val counters = _counters + counters[0] += demand + counters[1] += actual + counters[2] += remaining + counters[3] += interference + } + + override fun toString(): String { + return "FlowCounters[demand=$demand,actual=$actual,remaining=$remaining,interference=$interference]" + } +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/FlowMultiplexer.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/FlowMultiplexer.kt new file mode 100644 index 00000000..04ba7f21 --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/FlowMultiplexer.kt @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.flow.mux + +import org.opendc.simulator.flow.FlowConsumer +import org.opendc.simulator.flow.FlowCounters +import org.opendc.simulator.flow.FlowSource +import org.opendc.simulator.flow.interference.InterferenceKey + +/** + * A [FlowMultiplexer] enables multiplexing multiple [FlowSource]s over possibly multiple [FlowConsumer]s. + */ +public interface FlowMultiplexer { + /** + * The inputs of the multiplexer that can be used to consume sources. + */ + public val inputs: Set<FlowConsumer> + + /** + * The outputs of the multiplexer over which the flows will be distributed. + */ + public val outputs: Set<FlowSource> + + /** + * The actual processing rate of the multiplexer. + */ + public val rate: Double + + /** + * The demanded processing rate of the input. + */ + public val demand: Double + + /** + * The capacity of the outputs. + */ + public val capacity: Double + + /** + * The flow counters to track the flow metrics of all multiplexer inputs. + */ + public val counters: FlowCounters + + /** + * Create a new input on this multiplexer. + * + * @param key The key of the interference member to which the input belongs. + */ + public fun newInput(key: InterferenceKey? = null): FlowConsumer + + /** + * Remove [input] from this multiplexer. + */ + public fun removeInput(input: FlowConsumer) + + /** + * Create a new output on this multiplexer. + */ + public fun newOutput(): FlowSource + + /** + * Remove [output] from this multiplexer. + */ + public fun removeOutput(output: FlowSource) + + /** + * Clear all inputs and outputs from the multiplexer. + */ + public fun clear() + + /** + * Clear the inputs of the multiplexer. + */ + public fun clearInputs() + + /** + * Clear the outputs of the multiplexer. + */ + public fun clearOutputs() +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/ForwardingFlowMultiplexer.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/ForwardingFlowMultiplexer.kt new file mode 100644 index 00000000..125d10fe --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/ForwardingFlowMultiplexer.kt @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.flow.mux + +import org.opendc.simulator.flow.* +import org.opendc.simulator.flow.interference.InterferenceKey +import java.util.ArrayDeque + +/** + * A [FlowMultiplexer] implementation that allocates inputs to the outputs of the multiplexer exclusively. This means + * that a single input is directly connected to an output and that the multiplexer can only support as many + * inputs as outputs. + * + * @param engine The [FlowEngine] driving the simulation. + */ +public class ForwardingFlowMultiplexer(private val engine: FlowEngine) : FlowMultiplexer { + override val inputs: Set<FlowConsumer> + get() = _inputs + private val _inputs = mutableSetOf<Input>() + + override val outputs: Set<FlowSource> + get() = _outputs + private val _outputs = mutableSetOf<Output>() + private val _availableOutputs = ArrayDeque<Output>() + + override val counters: FlowCounters = object : FlowCounters { + override val demand: Double + get() = _outputs.sumOf { it.forwarder.counters.demand } + override val actual: Double + get() = _outputs.sumOf { it.forwarder.counters.actual } + override val remaining: Double + get() = _outputs.sumOf { it.forwarder.counters.remaining } + override val interference: Double + get() = _outputs.sumOf { it.forwarder.counters.interference } + + override fun reset() { + for (output in _outputs) { + output.forwarder.counters.reset() + } + } + + override fun toString(): String = "FlowCounters[demand=$demand,actual=$actual,remaining=$remaining]" + } + + override val rate: Double + get() = _outputs.sumOf { it.forwarder.rate } + + override val demand: Double + get() = _outputs.sumOf { it.forwarder.demand } + + override val capacity: Double + get() = _outputs.sumOf { it.forwarder.capacity } + + override fun newInput(key: InterferenceKey?): FlowConsumer { + val output = checkNotNull(_availableOutputs.poll()) { "No capacity to serve request" } + val input = Input(output) + _inputs += input + return input + } + + override fun removeInput(input: FlowConsumer) { + if (!_inputs.remove(input)) { + return + } + + val output = (input as Input).output + output.forwarder.cancel() + _availableOutputs += output + } + + override fun newOutput(): FlowSource { + val forwarder = FlowForwarder(engine) + val output = Output(forwarder) + + _outputs += output + return output + } + + override fun removeOutput(output: FlowSource) { + if (!_outputs.remove(output)) { + return + } + + val forwarder = (output as Output).forwarder + forwarder.close() + } + + override fun clearInputs() { + for (input in _inputs) { + val output = input.output + output.forwarder.cancel() + _availableOutputs += output + } + + _inputs.clear() + } + + override fun clearOutputs() { + for (output in _outputs) { + output.forwarder.cancel() + } + _outputs.clear() + _availableOutputs.clear() + } + + override fun clear() { + clearOutputs() + clearInputs() + } + + /** + * An input on the multiplexer. + */ + private inner class Input(@JvmField val output: Output) : FlowConsumer by output.forwarder { + override fun toString(): String = "ForwardingFlowMultiplexer.Input" + } + + /** + * An output on the multiplexer. + */ + private inner class Output(@JvmField val forwarder: FlowForwarder) : FlowSource by forwarder { + override fun onStart(conn: FlowConnection, now: Long) { + _availableOutputs += this + forwarder.onStart(conn, now) + } + + override fun onStop(conn: FlowConnection, now: Long, delta: Long) { + forwarder.cancel() + forwarder.onStop(conn, now, delta) + } + + override fun toString(): String = "ForwardingFlowMultiplexer.Output" + } +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/MaxMinFlowMultiplexer.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/MaxMinFlowMultiplexer.kt new file mode 100644 index 00000000..a0fb8a4e --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/MaxMinFlowMultiplexer.kt @@ -0,0 +1,789 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.flow.mux + +import org.opendc.simulator.flow.* +import org.opendc.simulator.flow.interference.InterferenceDomain +import org.opendc.simulator.flow.interference.InterferenceKey +import org.opendc.simulator.flow.internal.D_MS_TO_S +import org.opendc.simulator.flow.internal.MutableFlowCounters +import kotlin.math.max +import kotlin.math.min + +/** + * A [FlowMultiplexer] implementation that multiplexes flows over the available outputs using max-min fair sharing. + * + * @param engine The [FlowEngine] to drive the flow simulation. + * @param parent The parent flow system of the multiplexer. + * @param interferenceDomain The interference domain of the multiplexer. + */ +public class MaxMinFlowMultiplexer( + private val engine: FlowEngine, + parent: FlowConvergenceListener? = null, + private val interferenceDomain: InterferenceDomain? = null +) : FlowMultiplexer { + /** + * The inputs of the multiplexer. + */ + override val inputs: Set<FlowConsumer> + get() = _inputs + private val _inputs = mutableSetOf<Input>() + + /** + * The outputs of the multiplexer. + */ + override val outputs: Set<FlowSource> + get() = _outputs + private val _outputs = mutableSetOf<Output>() + + /** + * The flow counters of this multiplexer. + */ + public override val counters: FlowCounters + get() = scheduler.counters + + /** + * The actual processing rate of the multiplexer. + */ + public override val rate: Double + get() = scheduler.rate + + /** + * The demanded processing rate of the input. + */ + public override val demand: Double + get() = scheduler.demand + + /** + * The capacity of the outputs. + */ + public override val capacity: Double + get() = scheduler.capacity + + /** + * The [Scheduler] instance of this multiplexer. + */ + private val scheduler = Scheduler(engine, parent) + + override fun newInput(key: InterferenceKey?): FlowConsumer { + val provider = Input(engine, scheduler, interferenceDomain, key, scheduler.capacity) + _inputs.add(provider) + return provider + } + + override fun removeInput(input: FlowConsumer) { + if (!_inputs.remove(input)) { + return + } + // This cast should always succeed since only `Input` instances should be added to `_inputs` + (input as Input).close() + } + + override fun newOutput(): FlowSource { + val output = Output(scheduler) + _outputs.add(output) + return output + } + + override fun removeOutput(output: FlowSource) { + if (!_outputs.remove(output)) { + return + } + + // This cast should always succeed since only `Output` instances should be added to `_outputs` + (output as Output).cancel() + } + + override fun clearInputs() { + for (input in _inputs) { + input.cancel() + } + _inputs.clear() + } + + override fun clearOutputs() { + for (output in _outputs) { + output.cancel() + } + _outputs.clear() + } + + override fun clear() { + clearOutputs() + clearInputs() + } + + /** + * Helper class containing the scheduler state. + */ + private class Scheduler(engine: FlowEngine, private val parent: FlowConvergenceListener?) { + /** + * The flow counters of this scheduler. + */ + @JvmField val counters = MutableFlowCounters() + + /** + * The flow rate of the multiplexer. + */ + @JvmField var rate = 0.0 + + /** + * The demand for the multiplexer. + */ + @JvmField var demand = 0.0 + + /** + * The capacity of the multiplexer. + */ + @JvmField var capacity = 0.0 + + /** + * An [Output] that is used to activate the scheduler. + */ + @JvmField var activationOutput: Output? = null + + /** + * The active inputs registered with the scheduler. + */ + private val _activeInputs = mutableListOf<Input>() + + /** + * An array containing the active inputs, which is used to reduce the overhead of an [ArrayList]. + */ + private var _inputArray = emptyArray<Input>() + + /** + * The active outputs registered with the scheduler. + */ + private val _activeOutputs = mutableListOf<Output>() + + /** + * Flag to indicate that the scheduler is active. + */ + private var _schedulerActive = false + private var _lastSchedulerCycle = Long.MAX_VALUE + + /** + * The last convergence timestamp and the input. + */ + private var _lastConverge: Long = Long.MIN_VALUE + private var _lastConvergeInput: Input? = null + + /** + * The simulation clock. + */ + private val _clock = engine.clock + + /** + * Register the specified [input] to this scheduler. + */ + fun registerInput(input: Input) { + _activeInputs.add(input) + _inputArray = _activeInputs.toTypedArray() + + val hasActivationOutput = activationOutput != null + + // Disable timers and convergence of the source if one of the output manages it + input.shouldConsumerConverge = !hasActivationOutput + input.enableTimers = !hasActivationOutput + input.capacity = capacity + + trigger(_clock.millis()) + } + + /** + * De-register the specified [input] from this scheduler. + */ + fun deregisterInput(input: Input, now: Long) { + // Assign a new input responsible for handling the convergence events + if (_lastConvergeInput == input) { + _lastConvergeInput = null + } + + _activeInputs.remove(input) + + // Re-run scheduler to distribute new load + trigger(now) + } + + /** + * This method is invoked when one of the inputs converges. + */ + fun convergeInput(input: Input, now: Long) { + + val lastConverge = _lastConverge + val lastConvergeInput = _lastConvergeInput + val parent = parent + + if (parent != null && (now > lastConverge || lastConvergeInput == null || lastConvergeInput == input)) { + _lastConverge = now + _lastConvergeInput = input + + parent.onConverge(now, max(0, now - lastConverge)) + } + } + + /** + * Register the specified [output] to this scheduler. + */ + fun registerOutput(output: Output) { + _activeOutputs.add(output) + + updateCapacity() + updateActivationOutput() + } + + /** + * De-register the specified [output] from this scheduler. + */ + fun deregisterOutput(output: Output, now: Long) { + _activeOutputs.remove(output) + updateCapacity() + + trigger(now) + } + + /** + * This method is invoked when one of the outputs converges. + */ + fun convergeOutput(output: Output, now: Long) { + val lastConverge = _lastConverge + val parent = parent + + if (parent != null) { + _lastConverge = now + + parent.onConverge(now, max(0, now - lastConverge)) + } + + if (!output.isActive) { + output.isActivationOutput = false + updateActivationOutput() + } + } + + /** + * Trigger the scheduler of the multiplexer. + * + * @param now The current virtual timestamp of the simulation. + */ + fun trigger(now: Long) { + if (_schedulerActive) { + // No need to trigger the scheduler in case it is already active + return + } + + val activationOutput = activationOutput + + // We can run the scheduler in two ways: + // (1) We can pull one of the multiplexer's outputs. This allows us to cascade multiple pushes by the input + // into a single scheduling cycle, but is slower in case of a few changes at the same timestamp. + // (2) We run the scheduler directly from this method call. This is the fastest approach when there are only + // a few inputs and little changes at the same timestamp. + // We always pick for option (1) unless there are no outputs available. + if (activationOutput != null) { + activationOutput.pull(now) + return + } else { + runScheduler(now) + } + } + + /** + * Synchronously run the scheduler of the multiplexer. + */ + fun runScheduler(now: Long): Long { + val lastSchedulerCycle = _lastSchedulerCycle + _lastSchedulerCycle = now + + val delta = max(0, now - lastSchedulerCycle) + + return try { + _schedulerActive = true + doRunScheduler(now, delta) + } finally { + _schedulerActive = false + } + } + + /** + * Recompute the capacity of the multiplexer. + */ + fun updateCapacity() { + val newCapacity = _activeOutputs.sumOf(Output::capacity) + + // No-op if the capacity is unchanged + if (capacity == newCapacity) { + return + } + + capacity = newCapacity + + for (input in _activeInputs) { + input.capacity = newCapacity + } + + // Sort outputs by their capacity + _activeOutputs.sort() + } + + /** + * Updates the output that is used for scheduler activation. + */ + private fun updateActivationOutput() { + val output = _activeOutputs.firstOrNull() + activationOutput = output + + if (output != null) { + output.isActivationOutput = true + } + + val hasActivationOutput = output != null + + for (input in _activeInputs) { + input.shouldConsumerConverge = !hasActivationOutput + input.enableTimers = !hasActivationOutput + } + } + + /** + * Schedule the inputs over the outputs. + * + * @return The deadline after which a new scheduling cycle should start. + */ + private fun doRunScheduler(now: Long, delta: Long): Long { + val activeInputs = _activeInputs + val activeOutputs = _activeOutputs + var inputArray = _inputArray + var inputSize = _inputArray.size + + // Update the counters of the scheduler + updateCounters(delta) + + // If there is no work yet, mark the inputs as idle. + if (inputSize == 0) { + demand = 0.0 + rate = 0.0 + return Long.MAX_VALUE + } + + val capacity = capacity + var availableCapacity = capacity + var deadline = Long.MAX_VALUE + var demand = 0.0 + var shouldRebuild = false + + // Pull in the work of the inputs + for (i in 0 until inputSize) { + val input = inputArray[i] + + input.pullSync(now) + + // Remove inputs that have finished + if (!input.isActive) { + input.actualRate = 0.0 + shouldRebuild = true + } else { + demand += input.limit + deadline = min(deadline, input.deadline) + } + } + + // Slow-path: Rebuild the input array based on the (apparently) updated `activeInputs` + if (shouldRebuild) { + inputArray = activeInputs.toTypedArray() + inputSize = inputArray.size + _inputArray = inputArray + } + + val rate = if (demand > capacity) { + // If the demand is higher than the capacity, we need use max-min fair sharing to distribute the + // constrained capacity across the inputs. + + // Sort in-place the inputs based on their pushed flow. + // Profiling shows that it is faster than maintaining some kind of sorted set. + inputArray.sort() + + // Divide the available output capacity fairly over the inputs using max-min fair sharing + for (i in 0 until inputSize) { + val input = inputArray[i] + val availableShare = availableCapacity / (inputSize - i) + val grantedRate = min(input.allowedRate, availableShare) + + availableCapacity -= grantedRate + input.actualRate = grantedRate + } + + capacity - availableCapacity + } else { + demand + } + + this.demand = demand + if (this.rate != rate) { + // Only update the outputs if the output rate has changed + this.rate = rate + + // Divide the requests over the available capacity of the input resources fairly + for (i in activeOutputs.indices) { + val output = activeOutputs[i] + val inputCapacity = output.capacity + val fraction = inputCapacity / capacity + val grantedSpeed = rate * fraction + + output.push(grantedSpeed) + } + } + + return deadline + } + + /** + * The previous capacity of the multiplexer. + */ + private var _previousCapacity = 0.0 + + /** + * Update the counters of the scheduler. + */ + private fun updateCounters(delta: Long) { + val previousCapacity = _previousCapacity + _previousCapacity = capacity + + if (delta <= 0) { + return + } + + val deltaS = delta * D_MS_TO_S + val demand = demand + val rate = rate + + counters.increment( + demand = demand * deltaS, + actual = rate * deltaS, + remaining = (previousCapacity - rate) * deltaS, + interference = 0.0 + ) + } + } + + /** + * An internal [FlowConsumer] implementation for multiplexer inputs. + */ + private class Input( + private val engine: FlowEngine, + private val scheduler: Scheduler, + private val interferenceDomain: InterferenceDomain?, + @JvmField val key: InterferenceKey?, + initialCapacity: Double, + ) : FlowConsumer, FlowConsumerLogic, Comparable<Input> { + /** + * A flag to indicate that the consumer is active. + */ + override val isActive: Boolean + get() = _ctx != null + + /** + * The demand of the consumer. + */ + override val demand: Double + get() = limit + + /** + * The processing rate of the consumer. + */ + override val rate: Double + get() = actualRate + + /** + * The capacity of the input. + */ + override var capacity: Double + get() = _capacity + set(value) { + allowedRate = min(limit, value) + _capacity = value + _ctx?.capacity = value + } + private var _capacity = initialCapacity + + /** + * The flow counters to track the flow metrics of the consumer. + */ + override val counters: FlowCounters + get() = _counters + private val _counters = MutableFlowCounters() + + /** + * A flag to enable timers for the input. + */ + var enableTimers: Boolean = true + set(value) { + field = value + _ctx?.enableTimers = value + } + + /** + * A flag to control whether the input should converge. + */ + var shouldConsumerConverge: Boolean = true + set(value) { + field = value + _ctx?.shouldConsumerConverge = value + } + + /** + * The requested limit. + */ + @JvmField var limit: Double = 0.0 + + /** + * The actual processing speed. + */ + @JvmField var actualRate: Double = 0.0 + + /** + * The processing rate that is allowed by the model constraints. + */ + @JvmField var allowedRate: Double = 0.0 + + /** + * The deadline of the input. + */ + val deadline: Long + get() = _ctx?.deadline ?: Long.MAX_VALUE + + /** + * The [FlowConsumerContext] that is currently running. + */ + private var _ctx: FlowConsumerContext? = null + + /** + * A flag to indicate that the input is closed. + */ + private var _isClosed: Boolean = false + + /** + * Close the input. + * + * This method is invoked when the user removes an input from the switch. + */ + fun close() { + _isClosed = true + cancel() + } + + /** + * Pull the source if necessary. + */ + fun pullSync(now: Long) { + _ctx?.pullSync(now) + } + + /* FlowConsumer */ + override fun startConsumer(source: FlowSource) { + check(!_isClosed) { "Cannot re-use closed input" } + check(_ctx == null) { "Consumer is in invalid state" } + + val ctx = engine.newContext(source, this) + _ctx = ctx + + ctx.capacity = capacity + scheduler.registerInput(this) + + ctx.start() + } + + override fun pull() { + _ctx?.pull() + } + + override fun cancel() { + _ctx?.close() + } + + /* FlowConsumerLogic */ + override fun onPush( + ctx: FlowConsumerContext, + now: Long, + delta: Long, + rate: Double + ) { + doUpdateCounters(delta) + + val allowed = min(rate, capacity) + limit = rate + actualRate = allowed + allowedRate = allowed + + scheduler.trigger(now) + } + + override fun onFinish(ctx: FlowConsumerContext, now: Long, delta: Long, cause: Throwable?) { + doUpdateCounters(delta) + + limit = 0.0 + actualRate = 0.0 + allowedRate = 0.0 + + scheduler.deregisterInput(this, now) + + _ctx = null + } + + override fun onConverge(ctx: FlowConsumerContext, now: Long, delta: Long) { + scheduler.convergeInput(this, now) + } + + /* Comparable */ + override fun compareTo(other: Input): Int = allowedRate.compareTo(other.allowedRate) + + /** + * Helper method to update the flow counters of the multiplexer. + */ + private fun doUpdateCounters(delta: Long) { + if (delta <= 0L) { + return + } + + // Compute the performance penalty due to flow interference + val perfScore = if (interferenceDomain != null) { + val load = scheduler.rate / scheduler.capacity + interferenceDomain.apply(key, load) + } else { + 1.0 + } + + val actualRate = actualRate + + val deltaS = delta * D_MS_TO_S + val demand = limit * deltaS + val actual = actualRate * deltaS + val remaining = (_capacity - actualRate) * deltaS + val interference = actual * max(0.0, 1 - perfScore) + + _counters.increment(demand, actual, remaining, interference) + scheduler.counters.increment(0.0, 0.0, 0.0, interference) + } + } + + /** + * An internal [FlowSource] implementation for multiplexer outputs. + */ + private class Output(private val scheduler: Scheduler) : FlowSource, Comparable<Output> { + /** + * The active [FlowConnection] of this source. + */ + private var _conn: FlowConnection? = null + + /** + * The capacity of this output. + */ + @JvmField var capacity: Double = 0.0 + + /** + * A flag to indicate that this output is the activation output. + */ + var isActivationOutput: Boolean + get() = _isActivationOutput + set(value) { + _isActivationOutput = value + _conn?.shouldSourceConverge = value + } + private var _isActivationOutput: Boolean = false + + /** + * A flag to indicate that the output is active. + */ + @JvmField var isActive = false + + /** + * Push the specified rate to the consumer. + */ + fun push(rate: Double) { + _conn?.push(rate) + } + + /** + * Cancel this output. + */ + fun cancel() { + _conn?.close() + } + + /** + * Pull this output. + */ + fun pull(now: Long) { + _conn?.pull(now) + } + + override fun onStart(conn: FlowConnection, now: Long) { + assert(_conn == null) { "Source running concurrently" } + _conn = conn + capacity = conn.capacity + isActive = true + + scheduler.registerOutput(this) + } + + override fun onStop(conn: FlowConnection, now: Long, delta: Long) { + _conn = null + capacity = 0.0 + isActive = false + + scheduler.deregisterOutput(this, now) + } + + override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long { + val capacity = capacity + if (capacity != conn.capacity) { + this.capacity = capacity + scheduler.updateCapacity() + } + + return if (_isActivationOutput) { + // If this output is the activation output, synchronously run the scheduler and return the new deadline + val deadline = scheduler.runScheduler(now) + if (deadline == Long.MAX_VALUE) + deadline + else + deadline - now + } else { + // Output is not the activation output, so trigger activation output and do not install timer for this + // output (by returning `Long.MAX_VALUE`) + scheduler.trigger(now) + + Long.MAX_VALUE + } + } + + override fun onConverge(conn: FlowConnection, now: Long, delta: Long) { + if (_isActivationOutput) { + scheduler.convergeOutput(this, now) + } + } + + override fun compareTo(other: Output): Int = capacity.compareTo(other.capacity) + } +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/FixedFlowSource.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/FixedFlowSource.kt new file mode 100644 index 00000000..d9779c6a --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/FixedFlowSource.kt @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.flow.source + +import org.opendc.simulator.flow.FlowConnection +import org.opendc.simulator.flow.FlowSource +import kotlin.math.roundToLong + +/** + * A [FlowSource] that contains a fixed [amount] and is pushed with a given [utilization]. + */ +public class FixedFlowSource(private val amount: Double, private val utilization: Double) : FlowSource { + + init { + require(amount >= 0.0) { "Amount must be positive" } + require(utilization > 0.0) { "Utilization must be positive" } + } + + private var remainingAmount = amount + + override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long { + val consumed = conn.rate * delta / 1000.0 + val limit = conn.capacity * utilization + + remainingAmount -= consumed + + val duration = (remainingAmount / limit * 1000).roundToLong() + + return if (duration > 0) { + conn.push(limit) + duration + } else { + conn.close() + Long.MAX_VALUE + } + } +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/FlowSourceBarrier.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/FlowSourceBarrier.kt new file mode 100644 index 00000000..b3191ad3 --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/FlowSourceBarrier.kt @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.flow.source + +/** + * The [FlowSourceBarrier] is a barrier that allows multiple sources to wait for a select number of other sources to + * finish a pull, before proceeding its operation. + */ +public class FlowSourceBarrier(public val parties: Int) { + private var counter = 0 + + /** + * Enter the barrier and determine whether the caller is the last to reach the barrier. + * + * @return `true` if the caller is the last to reach the barrier, `false` otherwise. + */ + public fun enter(): Boolean { + val last = ++counter == parties + if (last) { + counter = 0 + return true + } + return false + } + + /** + * Reset the barrier. + */ + public fun reset() { + counter = 0 + } +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/FlowSourceRateAdapter.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/FlowSourceRateAdapter.kt new file mode 100644 index 00000000..6dd60d95 --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/FlowSourceRateAdapter.kt @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.flow.source + +import org.opendc.simulator.flow.FlowConnection +import org.opendc.simulator.flow.FlowSource + +/** + * Helper class to expose an observable [rate] field describing the flow rate of the source. + */ +public class FlowSourceRateAdapter( + private val delegate: FlowSource, + private val callback: (Double) -> Unit = {} +) : FlowSource by delegate { + /** + * The resource processing speed at this instant. + */ + public var rate: Double = 0.0 + private set(value) { + if (field != value) { + callback(value) + field = value + } + } + + init { + callback(0.0) + } + + override fun onStart(conn: FlowConnection, now: Long) { + conn.shouldSourceConverge = true + + delegate.onStart(conn, now) + } + + override fun onStop(conn: FlowConnection, now: Long, delta: Long) { + try { + delegate.onStop(conn, now, delta) + } finally { + rate = 0.0 + } + } + + override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long { + return delegate.onPull(conn, now, delta) + } + + override fun onConverge(conn: FlowConnection, now: Long, delta: Long) { + try { + delegate.onConverge(conn, now, delta) + } finally { + rate = conn.rate + } + } + + override fun toString(): String = "FlowSourceRateAdapter[delegate=$delegate]" +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/TraceFlowSource.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/TraceFlowSource.kt new file mode 100644 index 00000000..ae537845 --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/TraceFlowSource.kt @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.flow.source + +import org.opendc.simulator.flow.FlowConnection +import org.opendc.simulator.flow.FlowSource + +/** + * A [FlowSource] that replays a sequence of [Fragment], each indicating the flow rate for some period of time. + */ +public class TraceFlowSource(private val trace: Sequence<Fragment>) : FlowSource { + private var _iterator: Iterator<Fragment>? = null + private var _nextTarget = Long.MIN_VALUE + + override fun onStart(conn: FlowConnection, now: Long) { + check(_iterator == null) { "Source already running" } + _iterator = trace.iterator() + } + + override fun onStop(conn: FlowConnection, now: Long, delta: Long) { + _iterator = null + } + + override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long { + // Check whether the trace fragment was fully consumed, otherwise wait until we have done so + val nextTarget = _nextTarget + if (nextTarget > now) { + return now - nextTarget + } + + val iterator = checkNotNull(_iterator) + return if (iterator.hasNext()) { + val fragment = iterator.next() + _nextTarget = now + fragment.duration + conn.push(fragment.usage) + fragment.duration + } else { + conn.close() + Long.MAX_VALUE + } + } + + /** + * A fragment of the trace. + */ + public data class Fragment(val duration: Long, val usage: Double) +} diff --git a/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/FlowConsumerContextTest.kt b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/FlowConsumerContextTest.kt new file mode 100644 index 00000000..fe39eb2c --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/FlowConsumerContextTest.kt @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.flow + +import io.mockk.* +import org.junit.jupiter.api.* +import org.opendc.simulator.core.runBlockingSimulation +import org.opendc.simulator.flow.internal.FlowConsumerContextImpl +import org.opendc.simulator.flow.internal.FlowEngineImpl + +/** + * A test suite for the [FlowConsumerContextImpl] class. + */ +class FlowConsumerContextTest { + @Test + fun testFlushWithoutCommand() = runBlockingSimulation { + val engine = FlowEngineImpl(coroutineContext, clock) + val consumer = object : FlowSource { + override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long { + return if (now == 0L) { + conn.push(1.0) + 1000 + } else { + conn.close() + Long.MAX_VALUE + } + } + } + + val logic = object : FlowConsumerLogic {} + val context = FlowConsumerContextImpl(engine, consumer, logic) + + engine.scheduleSync(engine.clock.millis(), context) + } + + @Test + fun testDoubleStart() = runBlockingSimulation { + val engine = FlowEngineImpl(coroutineContext, clock) + val consumer = object : FlowSource { + override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long { + return if (now == 0L) { + conn.push(0.0) + 1000 + } else { + conn.close() + Long.MAX_VALUE + } + } + } + + val logic = object : FlowConsumerLogic {} + val context = FlowConsumerContextImpl(engine, consumer, logic) + + context.start() + + assertThrows<IllegalStateException> { + context.start() + } + } + + @Test + fun testIdempotentCapacityChange() = runBlockingSimulation { + val engine = FlowEngineImpl(coroutineContext, clock) + val consumer = spyk(object : FlowSource { + override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long { + return if (now == 0L) { + conn.push(1.0) + 1000 + } else { + conn.close() + Long.MAX_VALUE + } + } + }) + + val logic = object : FlowConsumerLogic {} + val context = FlowConsumerContextImpl(engine, consumer, logic) + context.capacity = 4200.0 + context.start() + context.capacity = 4200.0 + + verify(exactly = 1) { consumer.onPull(any(), any(), any()) } + } +} diff --git a/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/FlowForwarderTest.kt b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/FlowForwarderTest.kt new file mode 100644 index 00000000..12e72b8f --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/FlowForwarderTest.kt @@ -0,0 +1,321 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.flow + +import io.mockk.* +import kotlinx.coroutines.* +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.opendc.simulator.core.runBlockingSimulation +import org.opendc.simulator.flow.internal.FlowEngineImpl +import org.opendc.simulator.flow.source.FixedFlowSource + +/** + * A test suite for the [FlowForwarder] class. + */ +internal class FlowForwarderTest { + @Test + fun testCancelImmediately() = runBlockingSimulation { + val engine = FlowEngineImpl(coroutineContext, clock) + val forwarder = FlowForwarder(engine) + val source = FlowSink(engine, 2000.0) + + launch { source.consume(forwarder) } + + forwarder.consume(object : FlowSource { + override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long { + conn.close() + return Long.MAX_VALUE + } + }) + + forwarder.close() + source.cancel() + } + + @Test + fun testCancel() = runBlockingSimulation { + val engine = FlowEngineImpl(coroutineContext, clock) + val forwarder = FlowForwarder(engine) + val source = FlowSink(engine, 2000.0) + + launch { source.consume(forwarder) } + + forwarder.consume(object : FlowSource { + var isFirst = true + + override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long { + return if (isFirst) { + isFirst = false + conn.push(1.0) + 10 * 1000 + } else { + conn.close() + Long.MAX_VALUE + } + } + }) + + forwarder.close() + source.cancel() + } + + @Test + fun testState() = runBlockingSimulation { + val engine = FlowEngineImpl(coroutineContext, clock) + val forwarder = FlowForwarder(engine) + val consumer = object : FlowSource { + override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long { + conn.close() + return Long.MAX_VALUE + } + } + + assertFalse(forwarder.isActive) + + forwarder.startConsumer(consumer) + assertTrue(forwarder.isActive) + + assertThrows<IllegalStateException> { forwarder.startConsumer(consumer) } + + forwarder.cancel() + assertFalse(forwarder.isActive) + + forwarder.close() + assertFalse(forwarder.isActive) + } + + @Test + fun testCancelPendingDelegate() = runBlockingSimulation { + val engine = FlowEngineImpl(coroutineContext, clock) + val forwarder = FlowForwarder(engine) + + val consumer = spyk(object : FlowSource { + override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long { + conn.close() + return Long.MAX_VALUE + } + }) + + forwarder.startConsumer(consumer) + forwarder.cancel() + + verify(exactly = 0) { consumer.onStop(any(), any(), any()) } + } + + @Test + fun testCancelStartedDelegate() = runBlockingSimulation { + val engine = FlowEngineImpl(coroutineContext, clock) + val forwarder = FlowForwarder(engine) + val source = FlowSink(engine, 2000.0) + + val consumer = spyk(FixedFlowSource(2000.0, 1.0)) + + source.startConsumer(forwarder) + yield() + forwarder.startConsumer(consumer) + yield() + forwarder.cancel() + + verify(exactly = 1) { consumer.onStart(any(), any()) } + verify(exactly = 1) { consumer.onStop(any(), any(), any()) } + } + + @Test + fun testCancelPropagation() = runBlockingSimulation { + val engine = FlowEngineImpl(coroutineContext, clock) + val forwarder = FlowForwarder(engine) + val source = FlowSink(engine, 2000.0) + + val consumer = spyk(FixedFlowSource(2000.0, 1.0)) + + source.startConsumer(forwarder) + yield() + forwarder.startConsumer(consumer) + yield() + source.cancel() + + verify(exactly = 1) { consumer.onStart(any(), any()) } + verify(exactly = 1) { consumer.onStop(any(), any(), any()) } + } + + @Test + fun testExitPropagation() = runBlockingSimulation { + val engine = FlowEngineImpl(coroutineContext, clock) + val forwarder = FlowForwarder(engine, isCoupled = true) + val source = FlowSink(engine, 2000.0) + + val consumer = object : FlowSource { + override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long { + conn.close() + return Long.MAX_VALUE + } + } + + source.startConsumer(forwarder) + forwarder.consume(consumer) + yield() + + assertFalse(forwarder.isActive) + } + + @Test + @Disabled // Due to Kotlin bug: https://github.com/mockk/mockk/issues/368 + fun testAdjustCapacity() = runBlockingSimulation { + val engine = FlowEngineImpl(coroutineContext, clock) + val forwarder = FlowForwarder(engine) + val sink = FlowSink(engine, 1.0) + + val source = spyk(FixedFlowSource(2.0, 1.0)) + sink.startConsumer(forwarder) + + coroutineScope { + launch { forwarder.consume(source) } + delay(1000) + sink.capacity = 0.5 + } + + assertEquals(3000, clock.millis()) + verify(exactly = 1) { source.onPull(any(), any(), any()) } + } + + @Test + fun testCounters() = runBlockingSimulation { + val engine = FlowEngineImpl(coroutineContext, clock) + val forwarder = FlowForwarder(engine) + val source = FlowSink(engine, 1.0) + + val consumer = FixedFlowSource(2.0, 1.0) + source.startConsumer(forwarder) + + forwarder.consume(consumer) + + yield() + + assertEquals(2.0, source.counters.actual) + assertEquals(source.counters.actual, forwarder.counters.actual) { "Actual work" } + assertEquals(source.counters.demand, forwarder.counters.demand) { "Work demand" } + assertEquals(source.counters.remaining, forwarder.counters.remaining) { "Overcommitted work" } + assertEquals(2000, clock.millis()) + } + + @Test + fun testCoupledExit() = runBlockingSimulation { + val engine = FlowEngineImpl(coroutineContext, clock) + val forwarder = FlowForwarder(engine, isCoupled = true) + val source = FlowSink(engine, 2000.0) + + launch { source.consume(forwarder) } + + forwarder.consume(FixedFlowSource(2000.0, 1.0)) + + yield() + + assertFalse(source.isActive) + } + + @Test + fun testPullFailureCoupled() = runBlockingSimulation { + val engine = FlowEngineImpl(coroutineContext, clock) + val forwarder = FlowForwarder(engine, isCoupled = true) + val source = FlowSink(engine, 2000.0) + + launch { source.consume(forwarder) } + + try { + forwarder.consume(object : FlowSource { + override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long { + throw IllegalStateException("Test") + } + }) + } catch (cause: Throwable) { + // Ignore + } + + yield() + + assertFalse(source.isActive) + } + + @Test + fun testStartFailure() = runBlockingSimulation { + val engine = FlowEngineImpl(coroutineContext, clock) + val forwarder = FlowForwarder(engine) + val source = FlowSink(engine, 2000.0) + + launch { source.consume(forwarder) } + + try { + forwarder.consume(object : FlowSource { + override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long { + return Long.MAX_VALUE + } + + override fun onStart(conn: FlowConnection, now: Long) { + throw IllegalStateException("Test") + } + }) + } catch (cause: Throwable) { + // Ignore + } + + yield() + + assertTrue(source.isActive) + source.cancel() + } + + @Test + fun testConvergeFailure() = runBlockingSimulation { + val engine = FlowEngineImpl(coroutineContext, clock) + val forwarder = FlowForwarder(engine) + val source = FlowSink(engine, 2000.0) + + launch { source.consume(forwarder) } + + try { + forwarder.consume(object : FlowSource { + override fun onStart(conn: FlowConnection, now: Long) { + conn.shouldSourceConverge = true + } + + override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long { + return Long.MAX_VALUE + } + + override fun onConverge(conn: FlowConnection, now: Long, delta: Long) { + throw IllegalStateException("Test") + } + }) + } catch (cause: Throwable) { + // Ignore + } + + yield() + + assertTrue(source.isActive) + source.cancel() + } +} diff --git a/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/FlowSinkTest.kt b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/FlowSinkTest.kt new file mode 100644 index 00000000..70c75864 --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/FlowSinkTest.kt @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.flow + +import io.mockk.spyk +import io.mockk.verify +import kotlinx.coroutines.* +import org.junit.jupiter.api.* +import org.junit.jupiter.api.Assertions.assertEquals +import org.opendc.simulator.core.runBlockingSimulation +import org.opendc.simulator.flow.internal.FlowEngineImpl +import org.opendc.simulator.flow.source.FixedFlowSource +import org.opendc.simulator.flow.source.FlowSourceRateAdapter + +/** + * A test suite for the [FlowSink] class. + */ +internal class FlowSinkTest { + @Test + fun testSpeed() = runBlockingSimulation { + val engine = FlowEngineImpl(coroutineContext, clock) + val capacity = 4200.0 + val provider = FlowSink(engine, capacity) + + val consumer = FixedFlowSource(4200.0, 1.0) + + val res = mutableListOf<Double>() + val adapter = FlowSourceRateAdapter(consumer, res::add) + + provider.consume(adapter) + + assertEquals(listOf(0.0, capacity, 0.0), res) { "Speed is reported correctly" } + } + + @Test + fun testAdjustCapacity() = runBlockingSimulation { + val engine = FlowEngineImpl(coroutineContext, clock) + val provider = FlowSink(engine, 1.0) + + val consumer = spyk(FixedFlowSource(2.0, 1.0)) + + coroutineScope { + launch { provider.consume(consumer) } + delay(1000) + provider.capacity = 0.5 + } + assertEquals(3000, clock.millis()) + verify(exactly = 3) { consumer.onPull(any(), any(), any()) } + } + + @Test + fun testSpeedLimit() = runBlockingSimulation { + val engine = FlowEngineImpl(coroutineContext, clock) + val capacity = 4200.0 + val provider = FlowSink(engine, capacity) + + val consumer = FixedFlowSource(capacity, 2.0) + + val res = mutableListOf<Double>() + val adapter = FlowSourceRateAdapter(consumer, res::add) + + provider.consume(adapter) + + assertEquals(listOf(0.0, capacity, 0.0), res) { "Speed is reported correctly" } + } + + /** + * Test to see whether no infinite recursion occurs when interrupting during [FlowSource.onStart] or + * [FlowSource.onPull]. + */ + @Test + fun testIntermediateInterrupt() = runBlockingSimulation { + val engine = FlowEngineImpl(coroutineContext, clock) + val capacity = 4200.0 + val provider = FlowSink(engine, capacity) + + val consumer = object : FlowSource { + override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long { + conn.close() + return Long.MAX_VALUE + } + + override fun onStart(conn: FlowConnection, now: Long) { + conn.pull() + } + } + + provider.consume(consumer) + } + + @Test + fun testInterrupt() = runBlockingSimulation { + val engine = FlowEngineImpl(coroutineContext, clock) + val capacity = 4200.0 + val provider = FlowSink(engine, capacity) + lateinit var resCtx: FlowConnection + + val consumer = object : FlowSource { + var isFirst = true + + override fun onStart(conn: FlowConnection, now: Long) { + resCtx = conn + } + + override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long { + return if (isFirst) { + isFirst = false + conn.push(1.0) + 4000 + } else { + conn.close() + Long.MAX_VALUE + } + } + } + + launch { + yield() + resCtx.pull() + } + provider.consume(consumer) + + assertEquals(0, clock.millis()) + } + + @Test + fun testFailure() = runBlockingSimulation { + val engine = FlowEngineImpl(coroutineContext, clock) + val capacity = 4200.0 + val provider = FlowSink(engine, capacity) + + val consumer = object : FlowSource { + override fun onStart(conn: FlowConnection, now: Long) { + throw IllegalStateException("Hi") + } + + override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long { + return Long.MAX_VALUE + } + } + + assertThrows<IllegalStateException> { + provider.consume(consumer) + } + } + + @Test + fun testExceptionPropagationOnNext() = runBlockingSimulation { + val engine = FlowEngineImpl(coroutineContext, clock) + val capacity = 4200.0 + val provider = FlowSink(engine, capacity) + + val consumer = object : FlowSource { + var isFirst = true + + override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long { + return if (isFirst) { + isFirst = false + conn.push(1.0) + 1000 + } else { + throw IllegalStateException() + } + } + } + + assertThrows<IllegalStateException> { + provider.consume(consumer) + } + } + + @Test + fun testConcurrentConsumption() = runBlockingSimulation { + val engine = FlowEngineImpl(coroutineContext, clock) + val capacity = 4200.0 + val provider = FlowSink(engine, capacity) + + val consumer = FixedFlowSource(capacity, 1.0) + + assertThrows<IllegalStateException> { + coroutineScope { + launch { provider.consume(consumer) } + provider.consume(consumer) + } + } + } + + @Test + fun testCancelDuringConsumption() = runBlockingSimulation { + val engine = FlowEngineImpl(coroutineContext, clock) + val capacity = 4200.0 + val provider = FlowSink(engine, capacity) + + val consumer = FixedFlowSource(capacity, 1.0) + + launch { provider.consume(consumer) } + delay(500) + provider.cancel() + + yield() + + assertEquals(500, clock.millis()) + } + + @Test + fun testInfiniteSleep() { + assertThrows<IllegalStateException> { + runBlockingSimulation { + val engine = FlowEngineImpl(coroutineContext, clock) + val capacity = 4200.0 + val provider = FlowSink(engine, capacity) + + val consumer = object : FlowSource { + override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long = Long.MAX_VALUE + } + + provider.consume(consumer) + } + } + } +} diff --git a/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/mux/ForwardingFlowMultiplexerTest.kt b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/mux/ForwardingFlowMultiplexerTest.kt new file mode 100644 index 00000000..187dacd9 --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/mux/ForwardingFlowMultiplexerTest.kt @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.flow.mux + +import kotlinx.coroutines.yield +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertAll +import org.junit.jupiter.api.assertThrows +import org.opendc.simulator.core.runBlockingSimulation +import org.opendc.simulator.flow.* +import org.opendc.simulator.flow.internal.FlowEngineImpl +import org.opendc.simulator.flow.source.FixedFlowSource +import org.opendc.simulator.flow.source.FlowSourceRateAdapter +import org.opendc.simulator.flow.source.TraceFlowSource + +/** + * Test suite for the [ForwardingFlowMultiplexer] class. + */ +internal class ForwardingFlowMultiplexerTest { + /** + * Test a trace workload. + */ + @Test + fun testTrace() = runBlockingSimulation { + val engine = FlowEngineImpl(coroutineContext, clock) + + val speed = mutableListOf<Double>() + + val duration = 5 * 60L + val workload = + TraceFlowSource( + sequenceOf( + TraceFlowSource.Fragment(duration * 1000, 28.0), + TraceFlowSource.Fragment(duration * 1000, 3500.0), + TraceFlowSource.Fragment(duration * 1000, 0.0), + TraceFlowSource.Fragment(duration * 1000, 183.0) + ), + ) + + val switch = ForwardingFlowMultiplexer(engine) + val source = FlowSink(engine, 3200.0) + val forwarder = FlowForwarder(engine) + val adapter = FlowSourceRateAdapter(forwarder, speed::add) + source.startConsumer(adapter) + forwarder.startConsumer(switch.newOutput()) + + val provider = switch.newInput() + provider.consume(workload) + yield() + + assertAll( + { assertEquals(listOf(0.0, 28.0, 3200.0, 0.0, 183.0, 0.0), speed) { "Correct speed" } }, + { assertEquals(5 * 60L * 4000, clock.millis()) { "Took enough time" } } + ) + } + + /** + * Test runtime workload on hypervisor. + */ + @Test + fun testRuntimeWorkload() = runBlockingSimulation { + val engine = FlowEngineImpl(coroutineContext, clock) + + val duration = 5 * 60L * 1000 + val workload = FixedFlowSource(duration * 3.2, 1.0) + + val switch = ForwardingFlowMultiplexer(engine) + val source = FlowSink(engine, 3200.0) + + source.startConsumer(switch.newOutput()) + + val provider = switch.newInput() + provider.consume(workload) + yield() + + assertEquals(duration, clock.millis()) { "Took enough time" } + } + + /** + * Test two workloads running sequentially. + */ + @Test + fun testTwoWorkloads() = runBlockingSimulation { + val engine = FlowEngineImpl(coroutineContext, clock) + + val duration = 5 * 60L * 1000 + val workload = object : FlowSource { + var isFirst = true + + override fun onStart(conn: FlowConnection, now: Long) { + isFirst = true + } + + override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long { + return if (isFirst) { + isFirst = false + conn.push(1.0) + duration + } else { + conn.close() + Long.MAX_VALUE + } + } + } + + val switch = ForwardingFlowMultiplexer(engine) + val source = FlowSink(engine, 3200.0) + + source.startConsumer(switch.newOutput()) + + val provider = switch.newInput() + provider.consume(workload) + yield() + provider.consume(workload) + assertEquals(duration * 2, clock.millis()) { "Took enough time" } + } + + /** + * Test concurrent workloads on the machine. + */ + @Test + fun testConcurrentWorkloadFails() = runBlockingSimulation { + val engine = FlowEngineImpl(coroutineContext, clock) + + val switch = ForwardingFlowMultiplexer(engine) + val source = FlowSink(engine, 3200.0) + + source.startConsumer(switch.newOutput()) + + switch.newInput() + assertThrows<IllegalStateException> { switch.newInput() } + } +} diff --git a/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/mux/MaxMinFlowMultiplexerTest.kt b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/mux/MaxMinFlowMultiplexerTest.kt new file mode 100644 index 00000000..6e2cdb98 --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/mux/MaxMinFlowMultiplexerTest.kt @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.flow.mux + +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.yield +import org.junit.jupiter.api.* +import org.junit.jupiter.api.Assertions.assertEquals +import org.opendc.simulator.core.runBlockingSimulation +import org.opendc.simulator.flow.FlowSink +import org.opendc.simulator.flow.consume +import org.opendc.simulator.flow.internal.FlowEngineImpl +import org.opendc.simulator.flow.source.FixedFlowSource +import org.opendc.simulator.flow.source.TraceFlowSource + +/** + * Test suite for the [FlowMultiplexer] implementations + */ +internal class MaxMinFlowMultiplexerTest { + @Test + fun testSmoke() = runBlockingSimulation { + val scheduler = FlowEngineImpl(coroutineContext, clock) + val switch = MaxMinFlowMultiplexer(scheduler) + + val sources = List(2) { FlowSink(scheduler, 2000.0) } + sources.forEach { it.startConsumer(switch.newOutput()) } + + val provider = switch.newInput() + val consumer = FixedFlowSource(2000.0, 1.0) + + try { + provider.consume(consumer) + yield() + } finally { + switch.clear() + } + } + + /** + * Test overcommitting of resources via the hypervisor with a single VM. + */ + @Test + fun testOvercommittedSingle() = runBlockingSimulation { + val scheduler = FlowEngineImpl(coroutineContext, clock) + + val duration = 5 * 60L + val workload = + TraceFlowSource( + sequenceOf( + TraceFlowSource.Fragment(duration * 1000, 28.0), + TraceFlowSource.Fragment(duration * 1000, 3500.0), + TraceFlowSource.Fragment(duration * 1000, 0.0), + TraceFlowSource.Fragment(duration * 1000, 183.0) + ), + ) + + val switch = MaxMinFlowMultiplexer(scheduler) + val sink = FlowSink(scheduler, 3200.0) + val provider = switch.newInput() + + try { + sink.startConsumer(switch.newOutput()) + provider.consume(workload) + yield() + } finally { + switch.clear() + } + + assertAll( + { assertEquals(1113300.0, switch.counters.demand, "Requested work does not match") }, + { assertEquals(1023300.0, switch.counters.actual, "Actual work does not match") }, + { assertEquals(2816700.0, switch.counters.remaining, "Remaining capacity does not match") }, + { assertEquals(1200000, clock.millis()) } + ) + } + + /** + * Test overcommitting of resources via the hypervisor with two VMs. + */ + @Test + fun testOvercommittedDual() = runBlockingSimulation { + val scheduler = FlowEngineImpl(coroutineContext, clock) + + val duration = 5 * 60L + val workloadA = + TraceFlowSource( + sequenceOf( + TraceFlowSource.Fragment(duration * 1000, 28.0), + TraceFlowSource.Fragment(duration * 1000, 3500.0), + TraceFlowSource.Fragment(duration * 1000, 0.0), + TraceFlowSource.Fragment(duration * 1000, 183.0) + ), + ) + val workloadB = + TraceFlowSource( + sequenceOf( + TraceFlowSource.Fragment(duration * 1000, 28.0), + TraceFlowSource.Fragment(duration * 1000, 3100.0), + TraceFlowSource.Fragment(duration * 1000, 0.0), + TraceFlowSource.Fragment(duration * 1000, 73.0) + ) + ) + + val switch = MaxMinFlowMultiplexer(scheduler) + val sink = FlowSink(scheduler, 3200.0) + val providerA = switch.newInput() + val providerB = switch.newInput() + + try { + sink.startConsumer(switch.newOutput()) + + coroutineScope { + launch { providerA.consume(workloadA) } + providerB.consume(workloadB) + } + + yield() + } finally { + switch.clear() + } + assertAll( + { assertEquals(2073600.0, switch.counters.demand, "Requested work does not match") }, + { assertEquals(1053600.0, switch.counters.actual, "Granted work does not match") }, + { assertEquals(2786400.0, switch.counters.remaining, "Remaining capacity does not match") }, + { assertEquals(1200000, clock.millis()) } + ) + } +} diff --git a/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/source/FixedFlowSourceTest.kt b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/source/FixedFlowSourceTest.kt new file mode 100644 index 00000000..8396d346 --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/source/FixedFlowSourceTest.kt @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.flow.source + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.opendc.simulator.core.runBlockingSimulation +import org.opendc.simulator.flow.FlowSink +import org.opendc.simulator.flow.consume +import org.opendc.simulator.flow.internal.FlowEngineImpl + +/** + * A test suite for the [FixedFlowSource] class. + */ +internal class FixedFlowSourceTest { + @Test + fun testSmoke() = runBlockingSimulation { + val scheduler = FlowEngineImpl(coroutineContext, clock) + val provider = FlowSink(scheduler, 1.0) + + val consumer = FixedFlowSource(1.0, 1.0) + + provider.consume(consumer) + assertEquals(1000, clock.millis()) + } + + @Test + fun testUtilization() = runBlockingSimulation { + val scheduler = FlowEngineImpl(coroutineContext, clock) + val provider = FlowSink(scheduler, 1.0) + + val consumer = FixedFlowSource(1.0, 0.5) + + provider.consume(consumer) + assertEquals(2000, clock.millis()) + } +} diff --git a/opendc-simulator/opendc-simulator-network/build.gradle.kts b/opendc-simulator/opendc-simulator-network/build.gradle.kts new file mode 100644 index 00000000..f8931053 --- /dev/null +++ b/opendc-simulator/opendc-simulator-network/build.gradle.kts @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +description = "Library for simulating datacenter network components" + +plugins { + `kotlin-library-conventions` + `testing-conventions` + `jacoco-conventions` +} + +dependencies { + api(platform(projects.opendcPlatform)) + api(projects.opendcSimulator.opendcSimulatorFlow) + implementation(projects.opendcSimulator.opendcSimulatorCore) + + testImplementation(libs.slf4j.simple) +} diff --git a/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkLink.kt b/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkLink.kt new file mode 100644 index 00000000..67562640 --- /dev/null +++ b/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkLink.kt @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.network + +/** + * A physical bi-directional communication link between two [SimNetworkPort]s. + * + * @param left The first port of the link. + * @param right The second port of the link. + */ +public class SimNetworkLink(public val left: SimNetworkPort, public val right: SimNetworkPort) { + /** + * Determine whether the specified [port] participates in this network link. + */ + public operator fun contains(port: SimNetworkPort): Boolean = port == left || port == right + + /** + * Obtain the opposite port to which the specified [port] is connected through this link. + */ + public fun opposite(port: SimNetworkPort): SimNetworkPort { + return when (port) { + left -> right + right -> left + else -> throw IllegalArgumentException("Invalid port given") + } + } + + override fun toString(): String = "SimNetworkLink[left=$left,right=$right]" +} diff --git a/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkPort.kt b/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkPort.kt new file mode 100644 index 00000000..4b66d5cf --- /dev/null +++ b/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkPort.kt @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.network + +import org.opendc.simulator.flow.FlowConsumer +import org.opendc.simulator.flow.FlowSource + +/** + * A network port allows network devices to be connected to network through links. + */ +public abstract class SimNetworkPort { + /** + * A flag to indicate that the network port is connected to another port. + */ + public val isConnected: Boolean + get() = _link != null + + /** + * The network link which connects this port to another port. + */ + public val link: SimNetworkLink? + get() = _link + private var _link: SimNetworkLink? = null + + /** + * Connect this port to the specified [port]. + */ + public fun connect(port: SimNetworkPort) { + require(port !== this) { "Circular reference" } + check(!isConnected) { "Port already connected" } + check(!port.isConnected) { "Target port already connected" } + + val link = SimNetworkLink(this, port) + _link = link + port._link = link + + // Start bi-directional flow channel between the two ports + try { + provider.startConsumer(port.createConsumer()) + port.provider.startConsumer(createConsumer()) + } catch (e: Throwable) { + disconnect() + throw e + } + } + + /** + * Disconnect the current network link if it exists. + */ + public fun disconnect() { + val link = _link ?: return + val opposite = link.opposite(this) + _link = null + opposite._link = null + + provider.cancel() + opposite.provider.cancel() + } + + /** + * Create a [FlowSource] which generates the outgoing traffic of this port. + */ + protected abstract fun createConsumer(): FlowSource + + /** + * The [FlowConsumer] which processes the ingoing traffic of this port. + */ + protected abstract val provider: FlowConsumer + + override fun toString(): String = "SimNetworkPort[isConnected=$isConnected]" +} diff --git a/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkSink.kt b/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkSink.kt new file mode 100644 index 00000000..4b0d7bbd --- /dev/null +++ b/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkSink.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.network + +import org.opendc.simulator.flow.* + +/** + * A network sink which discards all received traffic and does not generate any traffic itself. + */ +public class SimNetworkSink( + engine: FlowEngine, + public val capacity: Double +) : SimNetworkPort() { + override fun createConsumer(): FlowSource = object : FlowSource { + override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long = Long.MAX_VALUE + + override fun toString(): String = "SimNetworkSink.Consumer" + } + + override val provider: FlowConsumer = FlowSink(engine, capacity) + + override fun toString(): String = "SimNetworkSink[capacity=$capacity]" +} diff --git a/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkSwitch.kt b/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkSwitch.kt new file mode 100644 index 00000000..7dc249ab --- /dev/null +++ b/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkSwitch.kt @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.network + +/** + * A network device connects devices on a network by switching the traffic over its ports. + */ +public interface SimNetworkSwitch { + /** + * The ports of the switch. + */ + public val ports: List<SimNetworkPort> +} diff --git a/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkSwitchVirtual.kt b/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkSwitchVirtual.kt new file mode 100644 index 00000000..6667c80c --- /dev/null +++ b/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkSwitchVirtual.kt @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.network + +import org.opendc.simulator.flow.* +import org.opendc.simulator.flow.mux.MaxMinFlowMultiplexer + +/** + * A [SimNetworkSwitch] that can support new networking ports on demand. + */ +public class SimNetworkSwitchVirtual(private val engine: FlowEngine) : SimNetworkSwitch { + /** + * The ports of this switch. + */ + override val ports: List<Port> + get() = _ports + private val _ports = mutableListOf<Port>() + + /** + * The [MaxMinFlowMultiplexer] to actually perform the switching. + */ + private val mux = MaxMinFlowMultiplexer(engine) + + /** + * Open a new port on the switch. + */ + public fun newPort(): Port { + val port = Port() + _ports.add(port) + return port + } + + /** + * A port on the network switch. + */ + public inner class Port : SimNetworkPort(), AutoCloseable { + /** + * A flag to indicate that this virtual port was removed from the switch. + */ + private var isClosed: Boolean = false + + override val provider: FlowConsumer + get() = _provider + private val _provider = mux.newInput() + + private val _source = mux.newOutput() + + override fun createConsumer(): FlowSource = _source + + override fun close() { + isClosed = true + mux.removeInput(_provider) + _ports.remove(this) + } + } +} diff --git a/opendc-simulator/opendc-simulator-network/src/test/kotlin/org/opendc/simulator/network/SimNetworkLinkTest.kt b/opendc-simulator/opendc-simulator-network/src/test/kotlin/org/opendc/simulator/network/SimNetworkLinkTest.kt new file mode 100644 index 00000000..3480c9df --- /dev/null +++ b/opendc-simulator/opendc-simulator-network/src/test/kotlin/org/opendc/simulator/network/SimNetworkLinkTest.kt @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.network + +import io.mockk.mockk +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + +/** + * Test suite for [SimNetworkLink] class. + */ +class SimNetworkLinkTest { + @Test + fun testContainsLeft() { + val left = mockk<SimNetworkPort>() + val right = mockk<SimNetworkPort>() + + val link = SimNetworkLink(left, right) + assertTrue(left in link) + } + + @Test + fun testContainsRight() { + val left = mockk<SimNetworkPort>() + val right = mockk<SimNetworkPort>() + + val link = SimNetworkLink(left, right) + assertTrue(right in link) + } + + @Test + fun testContainsNone() { + val left = mockk<SimNetworkPort>() + val right = mockk<SimNetworkPort>() + val none = mockk<SimNetworkPort>() + + val link = SimNetworkLink(left, right) + assertFalse(none in link) + } + + @Test + fun testOppositeLeft() { + val left = mockk<SimNetworkPort>() + val right = mockk<SimNetworkPort>() + + val link = SimNetworkLink(left, right) + assertEquals(right, link.opposite(left)) + } + + @Test + fun testOppositeRight() { + val left = mockk<SimNetworkPort>() + val right = mockk<SimNetworkPort>() + + val link = SimNetworkLink(left, right) + assertEquals(left, link.opposite(right)) + } + + @Test + fun testOppositeNone() { + val left = mockk<SimNetworkPort>() + val right = mockk<SimNetworkPort>() + val none = mockk<SimNetworkPort>() + + val link = SimNetworkLink(left, right) + assertThrows<IllegalArgumentException> { link.opposite(none) } + } +} diff --git a/opendc-simulator/opendc-simulator-network/src/test/kotlin/org/opendc/simulator/network/SimNetworkSinkTest.kt b/opendc-simulator/opendc-simulator-network/src/test/kotlin/org/opendc/simulator/network/SimNetworkSinkTest.kt new file mode 100644 index 00000000..14d22162 --- /dev/null +++ b/opendc-simulator/opendc-simulator-network/src/test/kotlin/org/opendc/simulator/network/SimNetworkSinkTest.kt @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.network + +import io.mockk.every +import io.mockk.mockk +import io.mockk.spyk +import io.mockk.verify +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertDoesNotThrow +import org.junit.jupiter.api.assertThrows +import org.opendc.simulator.core.runBlockingSimulation +import org.opendc.simulator.flow.* +import org.opendc.simulator.flow.source.FixedFlowSource + +/** + * Test suite for the [SimNetworkSink] class. + */ +class SimNetworkSinkTest { + @Test + fun testInitialState() = runBlockingSimulation { + val engine = FlowEngine(coroutineContext, clock) + val sink = SimNetworkSink(engine, capacity = 100.0) + + assertFalse(sink.isConnected) + assertNull(sink.link) + assertEquals(100.0, sink.capacity) + } + + @Test + fun testDisconnectIdempotent() = runBlockingSimulation { + val engine = FlowEngine(coroutineContext, clock) + val sink = SimNetworkSink(engine, capacity = 100.0) + + assertDoesNotThrow { sink.disconnect() } + assertFalse(sink.isConnected) + } + + @Test + fun testConnectCircular() = runBlockingSimulation { + val engine = FlowEngine(coroutineContext, clock) + val sink = SimNetworkSink(engine, capacity = 100.0) + + assertThrows<IllegalArgumentException> { + sink.connect(sink) + } + } + + @Test + fun testConnectAlreadyConnectedTarget() = runBlockingSimulation { + val engine = FlowEngine(coroutineContext, clock) + val sink = SimNetworkSink(engine, capacity = 100.0) + val source = mockk<SimNetworkPort>(relaxUnitFun = true) + every { source.isConnected } returns true + + assertThrows<IllegalStateException> { + sink.connect(source) + } + } + + @Test + fun testConnectAlreadyConnected() = runBlockingSimulation { + val engine = FlowEngine(coroutineContext, clock) + val sink = SimNetworkSink(engine, capacity = 100.0) + val source1 = Source(engine) + + val source2 = mockk<SimNetworkPort>(relaxUnitFun = true) + + every { source2.isConnected } returns false + + sink.connect(source1) + assertThrows<IllegalStateException> { + sink.connect(source2) + } + } + + @Test + fun testConnect() = runBlockingSimulation { + val engine = FlowEngine(coroutineContext, clock) + val sink = SimNetworkSink(engine, capacity = 100.0) + val source = spyk(Source(engine)) + val consumer = source.consumer + + sink.connect(source) + + assertTrue(sink.isConnected) + assertTrue(source.isConnected) + + verify { source.createConsumer() } + verify { consumer.onStart(any(), any()) } + } + + @Test + fun testDisconnect() = runBlockingSimulation { + val engine = FlowEngine(coroutineContext, clock) + val sink = SimNetworkSink(engine, capacity = 100.0) + val source = spyk(Source(engine)) + val consumer = source.consumer + + sink.connect(source) + sink.disconnect() + + assertFalse(sink.isConnected) + assertFalse(source.isConnected) + + verify { consumer.onStop(any(), any(), any()) } + } + + private class Source(engine: FlowEngine) : SimNetworkPort() { + val consumer = spyk(FixedFlowSource(Double.POSITIVE_INFINITY, utilization = 0.8)) + + public override fun createConsumer(): FlowSource = consumer + + override val provider: FlowConsumer = FlowSink(engine, 0.0) + } +} diff --git a/opendc-simulator/opendc-simulator-network/src/test/kotlin/org/opendc/simulator/network/SimNetworkSwitchVirtualTest.kt b/opendc-simulator/opendc-simulator-network/src/test/kotlin/org/opendc/simulator/network/SimNetworkSwitchVirtualTest.kt new file mode 100644 index 00000000..62e54ffb --- /dev/null +++ b/opendc-simulator/opendc-simulator-network/src/test/kotlin/org/opendc/simulator/network/SimNetworkSwitchVirtualTest.kt @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.network + +import io.mockk.spyk +import io.mockk.verify +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.opendc.simulator.core.runBlockingSimulation +import org.opendc.simulator.flow.* +import org.opendc.simulator.flow.source.FixedFlowSource + +/** + * Test suite for the [SimNetworkSwitchVirtual] class. + */ +class SimNetworkSwitchVirtualTest { + @Test + fun testConnect() = runBlockingSimulation { + val engine = FlowEngine(coroutineContext, clock) + val sink = SimNetworkSink(engine, capacity = 100.0) + val source = spyk(Source(engine)) + val switch = SimNetworkSwitchVirtual(engine) + val consumer = source.consumer + + switch.newPort().connect(sink) + switch.newPort().connect(source) + + assertTrue(sink.isConnected) + assertTrue(source.isConnected) + + verify { source.createConsumer() } + verify { consumer.onStart(any(), any()) } + } + + @Test + fun testConnectClosedPort() = runBlockingSimulation { + val engine = FlowEngine(coroutineContext, clock) + val sink = SimNetworkSink(engine, capacity = 100.0) + val switch = SimNetworkSwitchVirtual(engine) + + val port = switch.newPort() + port.close() + + assertThrows<IllegalStateException> { + port.connect(sink) + } + } + + private class Source(engine: FlowEngine) : SimNetworkPort() { + val consumer = spyk(FixedFlowSource(Double.POSITIVE_INFINITY, utilization = 0.8)) + + public override fun createConsumer(): FlowSource = consumer + + override val provider: FlowConsumer = FlowSink(engine, 0.0) + } +} diff --git a/opendc-simulator/opendc-simulator-power/build.gradle.kts b/opendc-simulator/opendc-simulator-power/build.gradle.kts new file mode 100644 index 00000000..5d8c8949 --- /dev/null +++ b/opendc-simulator/opendc-simulator-power/build.gradle.kts @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +description = "Library for simulating datacenter power components" + +plugins { + `kotlin-library-conventions` + `testing-conventions` + `jacoco-conventions` +} + +dependencies { + api(platform(projects.opendcPlatform)) + api(projects.opendcSimulator.opendcSimulatorFlow) + implementation(projects.opendcSimulator.opendcSimulatorCore) + + testImplementation(libs.slf4j.simple) +} diff --git a/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPdu.kt b/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPdu.kt new file mode 100644 index 00000000..9f88fecc --- /dev/null +++ b/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPdu.kt @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.power + +import org.opendc.simulator.flow.* +import org.opendc.simulator.flow.mux.FlowMultiplexer +import org.opendc.simulator.flow.mux.MaxMinFlowMultiplexer + +/** + * A model of a Power Distribution Unit (PDU). + * + * @param engine The underlying [FlowEngine] to drive the simulation under the hood. + * @param idlePower The idle power consumption of the PDU independent of the load on the PDU. + * @param lossCoefficient The coefficient for the power loss of the PDU proportional to the square load. + */ +public class SimPdu( + engine: FlowEngine, + private val idlePower: Double = 0.0, + private val lossCoefficient: Double = 0.0, +) : SimPowerInlet() { + /** + * The [FlowMultiplexer] that distributes the electricity over the PDU outlets. + */ + private val mux = MaxMinFlowMultiplexer(engine) + + /** + * The [FlowForwarder] that represents the input of the PDU. + */ + private val output = mux.newOutput() + + /** + * Create a new PDU outlet. + */ + public fun newOutlet(): Outlet = Outlet(mux, mux.newInput()) + + override fun createSource(): FlowSource = FlowMapper(output) { _, rate -> + val loss = computePowerLoss(rate) + rate + loss + } + + override fun toString(): String = "SimPdu" + + /** + * Compute the power loss that occurs in the PDU. + */ + private fun computePowerLoss(load: Double): Double { + // See https://download.schneider-electric.com/files?p_Doc_Ref=SPD_NRAN-66CK3D_EN + return idlePower + lossCoefficient * (load * load) + } + + /** + * A PDU outlet. + */ + public class Outlet(private val switch: FlowMultiplexer, private val provider: FlowConsumer) : SimPowerOutlet(), AutoCloseable { + override fun onConnect(inlet: SimPowerInlet) { + provider.startConsumer(inlet.createSource()) + } + + override fun onDisconnect(inlet: SimPowerInlet) { + provider.cancel() + } + + /** + * Remove the outlet from the PDU. + */ + override fun close() { + switch.removeInput(provider) + } + + override fun toString(): String = "SimPdu.Outlet" + } +} diff --git a/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerInlet.kt b/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerInlet.kt new file mode 100644 index 00000000..de587b7f --- /dev/null +++ b/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerInlet.kt @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.power + +import org.opendc.simulator.flow.FlowSource + +/** + * An abstract inlet that consumes electricity from a power outlet. + */ +public abstract class SimPowerInlet { + /** + * A flag to indicate that the inlet is currently connected to an outlet. + */ + public val isConnected: Boolean + get() = _outlet != null + + /** + * The [SimPowerOutlet] to which the inlet is connected. + */ + public val outlet: SimPowerOutlet? + get() = _outlet + internal var _outlet: SimPowerOutlet? = null + + /** + * Create a [FlowSource] which represents the consumption of electricity from the power outlet. + */ + public abstract fun createSource(): FlowSource +} diff --git a/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerOutlet.kt b/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerOutlet.kt new file mode 100644 index 00000000..72f52acc --- /dev/null +++ b/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerOutlet.kt @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.power + +/** + * An abstract outlet that provides a source of electricity for datacenter components. + */ +public abstract class SimPowerOutlet { + /** + * A flag to indicate that the inlet is currently connected to an outlet. + */ + public val isConnected: Boolean + get() = _inlet != null + + /** + * The inlet that is connected to this outlet currently. + */ + public val inlet: SimPowerInlet? + get() = _inlet + private var _inlet: SimPowerInlet? = null + + /** + * Connect the specified power [inlet] to this outlet. + * + * @param inlet The inlet to connect to the outlet. + */ + public fun connect(inlet: SimPowerInlet) { + check(!isConnected) { "Outlet already connected" } + check(!inlet.isConnected) { "Inlet already connected" } + + _inlet = inlet + inlet._outlet = this + + onConnect(inlet) + } + + /** + * Disconnect the connected power outlet from this inlet + */ + public fun disconnect() { + val inlet = _inlet + if (inlet != null) { + _inlet = null + assert(inlet._outlet == this) { "Inlet state incorrect" } + inlet._outlet = null + + onDisconnect(inlet) + } + } + + /** + * This method is invoked when an inlet is connected to the outlet. + */ + protected abstract fun onConnect(inlet: SimPowerInlet) + + /** + * This method is invoked when an inlet is disconnected from the outlet. + */ + protected abstract fun onDisconnect(inlet: SimPowerInlet) +} diff --git a/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerSource.kt b/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerSource.kt new file mode 100644 index 00000000..07e9f52e --- /dev/null +++ b/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerSource.kt @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.power + +import org.opendc.simulator.flow.FlowEngine +import org.opendc.simulator.flow.FlowSink + +/** + * A [SimPowerOutlet] that represents a source of electricity. + * + * @param engine The underlying [FlowEngine] to drive the simulation under the hood. + */ +public class SimPowerSource(engine: FlowEngine, public val capacity: Double) : SimPowerOutlet() { + /** + * The resource source that drives this power source. + */ + private val source = FlowSink(engine, capacity) + + /** + * The power draw at this instant. + */ + public val powerDraw: Double + get() = source.rate + + override fun onConnect(inlet: SimPowerInlet) { + source.startConsumer(inlet.createSource()) + } + + override fun onDisconnect(inlet: SimPowerInlet) { + source.cancel() + } + + override fun toString(): String = "SimPowerSource" +} diff --git a/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimUps.kt b/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimUps.kt new file mode 100644 index 00000000..46d659f8 --- /dev/null +++ b/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimUps.kt @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.power + +import org.opendc.simulator.flow.* +import org.opendc.simulator.flow.mux.MaxMinFlowMultiplexer + +/** + * A model of an Uninterruptible Power Supply (UPS). + * + * This model aggregates multiple power sources into a single source in order to ensure that power is always available. + * + * @param engine The underlying [FlowEngine] to drive the simulation under the hood. + * @param idlePower The idle power consumption of the UPS independent of the load. + * @param lossCoefficient The coefficient for the power loss of the UPS proportional to the load. + */ +public class SimUps( + private val engine: FlowEngine, + private val idlePower: Double = 0.0, + private val lossCoefficient: Double = 0.0, +) : SimPowerOutlet() { + /** + * The resource aggregator used to combine the input sources. + */ + private val mux = MaxMinFlowMultiplexer(engine) + + /** + * The [FlowConsumer] that represents the output of the UPS. + */ + private val provider = mux.newInput() + + /** + * Create a new UPS outlet. + */ + public fun newInlet(): SimPowerInlet { + val forward = FlowForwarder(engine, isCoupled = true) + forward.startConsumer(mux.newOutput()) + return Inlet(forward) + } + + override fun onConnect(inlet: SimPowerInlet) { + val source = inlet.createSource() + val mapper = FlowMapper(source) { _, rate -> + val loss = computePowerLoss(rate) + rate + loss + } + + provider.startConsumer(mapper) + } + + override fun onDisconnect(inlet: SimPowerInlet) { + provider.cancel() + } + + /** + * Compute the power loss that occurs in the UPS. + */ + private fun computePowerLoss(load: Double): Double { + // See https://download.schneider-electric.com/files?p_Doc_Ref=SPD_NRAN-66CK3D_EN + return idlePower + lossCoefficient * load + } + + /** + * A UPS inlet. + */ + public inner class Inlet(private val forwarder: FlowForwarder) : SimPowerInlet(), AutoCloseable { + override fun createSource(): FlowSource = forwarder + + /** + * Remove the inlet from the PSU. + */ + override fun close() { + forwarder.close() + } + + override fun toString(): String = "SimPsu.Inlet" + } +} diff --git a/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPduTest.kt b/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPduTest.kt new file mode 100644 index 00000000..eb823eb1 --- /dev/null +++ b/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPduTest.kt @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.power + +import io.mockk.spyk +import io.mockk.verify +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.opendc.simulator.core.runBlockingSimulation +import org.opendc.simulator.flow.FlowEngine +import org.opendc.simulator.flow.FlowSource +import org.opendc.simulator.flow.source.FixedFlowSource + +/** + * Test suite for the [SimPdu] class. + */ +internal class SimPduTest { + @Test + fun testZeroOutlets() = runBlockingSimulation { + val engine = FlowEngine(coroutineContext, clock) + val source = SimPowerSource(engine, capacity = 100.0) + val pdu = SimPdu(engine) + source.connect(pdu) + + assertEquals(0.0, source.powerDraw) + } + + @Test + fun testSingleOutlet() = runBlockingSimulation { + val engine = FlowEngine(coroutineContext, clock) + val source = SimPowerSource(engine, capacity = 100.0) + val pdu = SimPdu(engine) + source.connect(pdu) + pdu.newOutlet().connect(SimpleInlet()) + + assertEquals(50.0, source.powerDraw) + } + + @Test + fun testDoubleOutlet() = runBlockingSimulation { + val engine = FlowEngine(coroutineContext, clock) + val source = SimPowerSource(engine, capacity = 100.0) + val pdu = SimPdu(engine) + source.connect(pdu) + + pdu.newOutlet().connect(SimpleInlet()) + pdu.newOutlet().connect(SimpleInlet()) + + assertEquals(100.0, source.powerDraw) + } + + @Test + fun testDisconnect() = runBlockingSimulation { + val engine = FlowEngine(coroutineContext, clock) + val source = SimPowerSource(engine, capacity = 100.0) + val pdu = SimPdu(engine) + source.connect(pdu) + val consumer = spyk(FixedFlowSource(100.0, utilization = 1.0)) + val inlet = object : SimPowerInlet() { + override fun createSource(): FlowSource = consumer + } + + val outlet = pdu.newOutlet() + outlet.connect(inlet) + outlet.disconnect() + + verify { consumer.onStop(any(), any(), any()) } + } + + @Test + fun testLoss() = runBlockingSimulation { + val engine = FlowEngine(coroutineContext, clock) + val source = SimPowerSource(engine, capacity = 100.0) + // https://download.schneider-electric.com/files?p_Doc_Ref=SPD_NRAN-66CK3D_EN + val pdu = SimPdu(engine, idlePower = 1.5, lossCoefficient = 0.015) + source.connect(pdu) + pdu.newOutlet().connect(SimpleInlet()) + assertEquals(89.0, source.powerDraw, 0.01) + } + + @Test + fun testOutletClose() = runBlockingSimulation { + val engine = FlowEngine(coroutineContext, clock) + val source = SimPowerSource(engine, capacity = 100.0) + val pdu = SimPdu(engine) + source.connect(pdu) + val outlet = pdu.newOutlet() + outlet.close() + + assertThrows<IllegalStateException> { + outlet.connect(SimpleInlet()) + } + } + + class SimpleInlet : SimPowerInlet() { + override fun createSource(): FlowSource = FixedFlowSource(100.0, utilization = 0.5) + } +} diff --git a/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPowerSourceTest.kt b/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPowerSourceTest.kt new file mode 100644 index 00000000..76142103 --- /dev/null +++ b/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPowerSourceTest.kt @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.power + +import io.mockk.every +import io.mockk.mockk +import io.mockk.spyk +import io.mockk.verify +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertDoesNotThrow +import org.junit.jupiter.api.assertThrows +import org.opendc.simulator.core.runBlockingSimulation +import org.opendc.simulator.flow.FlowEngine +import org.opendc.simulator.flow.FlowSource +import org.opendc.simulator.flow.source.FixedFlowSource + +/** + * Test suite for the [SimPowerSource] + */ +internal class SimPowerSourceTest { + @Test + fun testInitialState() = runBlockingSimulation { + val engine = FlowEngine(coroutineContext, clock) + val source = SimPowerSource(engine, capacity = 100.0) + + assertFalse(source.isConnected) + assertNull(source.inlet) + assertEquals(100.0, source.capacity) + } + + @Test + fun testDisconnectIdempotent() = runBlockingSimulation { + val engine = FlowEngine(coroutineContext, clock) + val source = SimPowerSource(engine, capacity = 100.0) + + assertDoesNotThrow { source.disconnect() } + assertFalse(source.isConnected) + } + + @Test + fun testConnect() = runBlockingSimulation { + val engine = FlowEngine(coroutineContext, clock) + val source = SimPowerSource(engine, capacity = 100.0) + val inlet = SimpleInlet() + + source.connect(inlet) + + assertTrue(source.isConnected) + assertEquals(inlet, source.inlet) + assertTrue(inlet.isConnected) + assertEquals(source, inlet.outlet) + assertEquals(100.0, source.powerDraw) + } + + @Test + fun testDisconnect() = runBlockingSimulation { + val engine = FlowEngine(coroutineContext, clock) + val source = SimPowerSource(engine, capacity = 100.0) + val consumer = spyk(FixedFlowSource(100.0, utilization = 1.0)) + val inlet = object : SimPowerInlet() { + override fun createSource(): FlowSource = consumer + } + + source.connect(inlet) + source.disconnect() + + verify { consumer.onStop(any(), any(), any()) } + } + + @Test + fun testDisconnectAssertion() = runBlockingSimulation { + val engine = FlowEngine(coroutineContext, clock) + val source = SimPowerSource(engine, capacity = 100.0) + val inlet = mockk<SimPowerInlet>(relaxUnitFun = true) + every { inlet.isConnected } returns false + every { inlet._outlet } returns null + every { inlet.createSource() } returns FixedFlowSource(100.0, utilization = 1.0) + + source.connect(inlet) + + assertThrows<AssertionError> { + source.disconnect() + } + } + + @Test + fun testOutletAlreadyConnected() = runBlockingSimulation { + val engine = FlowEngine(coroutineContext, clock) + val source = SimPowerSource(engine, capacity = 100.0) + val inlet = SimpleInlet() + + source.connect(inlet) + assertThrows<IllegalStateException> { + source.connect(SimpleInlet()) + } + + assertEquals(inlet, source.inlet) + } + + @Test + fun testInletAlreadyConnected() = runBlockingSimulation { + val engine = FlowEngine(coroutineContext, clock) + val source = SimPowerSource(engine, capacity = 100.0) + val inlet = mockk<SimPowerInlet>(relaxUnitFun = true) + every { inlet.isConnected } returns true + + assertThrows<IllegalStateException> { + source.connect(inlet) + } + } + + class SimpleInlet : SimPowerInlet() { + override fun createSource(): FlowSource = FixedFlowSource(100.0, utilization = 1.0) + } +} diff --git a/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimUpsTest.kt b/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimUpsTest.kt new file mode 100644 index 00000000..a764a368 --- /dev/null +++ b/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimUpsTest.kt @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.power + +import io.mockk.spyk +import io.mockk.verify +import org.junit.jupiter.api.Assertions.assertAll +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.opendc.simulator.core.runBlockingSimulation +import org.opendc.simulator.flow.FlowEngine +import org.opendc.simulator.flow.FlowSource +import org.opendc.simulator.flow.source.FixedFlowSource + +/** + * Test suite for the [SimUps] class. + */ +internal class SimUpsTest { + @Test + fun testSingleInlet() = runBlockingSimulation { + val engine = FlowEngine(coroutineContext, clock) + val source = SimPowerSource(engine, capacity = 100.0) + val ups = SimUps(engine) + source.connect(ups.newInlet()) + ups.connect(SimpleInlet()) + + assertEquals(50.0, source.powerDraw) + } + + @Test + fun testDoubleInlet() = runBlockingSimulation { + val engine = FlowEngine(coroutineContext, clock) + val source1 = SimPowerSource(engine, capacity = 100.0) + val source2 = SimPowerSource(engine, capacity = 100.0) + val ups = SimUps(engine) + source1.connect(ups.newInlet()) + source2.connect(ups.newInlet()) + + ups.connect(SimpleInlet()) + + assertAll( + { assertEquals(50.0, source1.powerDraw) }, + { assertEquals(50.0, source2.powerDraw) } + ) + } + + @Test + fun testLoss() = runBlockingSimulation { + val engine = FlowEngine(coroutineContext, clock) + val source = SimPowerSource(engine, capacity = 100.0) + // https://download.schneider-electric.com/files?p_Doc_Ref=SPD_NRAN-66CK3D_EN + val ups = SimUps(engine, idlePower = 4.0, lossCoefficient = 0.05) + source.connect(ups.newInlet()) + ups.connect(SimpleInlet()) + + assertEquals(56.5, source.powerDraw) + } + + @Test + fun testDisconnect() = runBlockingSimulation { + val engine = FlowEngine(coroutineContext, clock) + val source1 = SimPowerSource(engine, capacity = 100.0) + val source2 = SimPowerSource(engine, capacity = 100.0) + val ups = SimUps(engine) + source1.connect(ups.newInlet()) + source2.connect(ups.newInlet()) + val consumer = spyk(FixedFlowSource(100.0, utilization = 1.0)) + val inlet = object : SimPowerInlet() { + override fun createSource(): FlowSource = consumer + } + + ups.connect(inlet) + ups.disconnect() + + verify { consumer.onStop(any(), any(), any()) } + } + + class SimpleInlet : SimPowerInlet() { + override fun createSource(): FlowSource = FixedFlowSource(100.0, utilization = 0.5) + } +} diff --git a/opendc-simulator/opendc-simulator-resources/build.gradle.kts b/opendc-simulator/opendc-simulator-resources/build.gradle.kts deleted file mode 100644 index e4ffc3ff..00000000 --- a/opendc-simulator/opendc-simulator-resources/build.gradle.kts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -description = "Uniform resource consumption simulation model" - -plugins { - `kotlin-library-conventions` - `testing-conventions` - `jacoco-conventions` - `benchmark-conventions` -} - -dependencies { - api(platform(projects.opendcPlatform)) - api(libs.kotlinx.coroutines) - implementation(projects.opendcUtils) - - jmhImplementation(projects.opendcSimulator.opendcSimulatorCore) - testImplementation(projects.opendcSimulator.opendcSimulatorCore) -} diff --git a/opendc-simulator/opendc-simulator-resources/src/jmh/kotlin/org/opendc/simulator/resources/SimResourceBenchmarks.kt b/opendc-simulator/opendc-simulator-resources/src/jmh/kotlin/org/opendc/simulator/resources/SimResourceBenchmarks.kt deleted file mode 100644 index cd5f33bd..00000000 --- a/opendc-simulator/opendc-simulator-resources/src/jmh/kotlin/org/opendc/simulator/resources/SimResourceBenchmarks.kt +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.resources - -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.launch -import org.opendc.simulator.core.SimulationCoroutineScope -import org.opendc.simulator.core.runBlockingSimulation -import org.opendc.simulator.resources.consumer.SimTraceConsumer -import org.openjdk.jmh.annotations.* -import java.util.concurrent.TimeUnit - -@State(Scope.Thread) -@Fork(1) -@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS) -@Measurement(iterations = 5, time = 3, timeUnit = TimeUnit.SECONDS) -@OptIn(ExperimentalCoroutinesApi::class) -class SimResourceBenchmarks { - private lateinit var scope: SimulationCoroutineScope - private lateinit var scheduler: SimResourceScheduler - - @Setup - fun setUp() { - scope = SimulationCoroutineScope() - scheduler = SimResourceSchedulerTrampoline(scope.coroutineContext, scope.clock) - } - - @State(Scope.Thread) - class Workload { - lateinit var trace: Sequence<SimTraceConsumer.Fragment> - - @Setup - fun setUp() { - trace = sequenceOf( - SimTraceConsumer.Fragment(1000, 28.0), - SimTraceConsumer.Fragment(1000, 3500.0), - SimTraceConsumer.Fragment(1000, 0.0), - SimTraceConsumer.Fragment(1000, 183.0), - SimTraceConsumer.Fragment(1000, 400.0), - SimTraceConsumer.Fragment(1000, 100.0), - SimTraceConsumer.Fragment(1000, 3000.0), - SimTraceConsumer.Fragment(1000, 4500.0), - ) - } - } - - @Benchmark - fun benchmarkSource(state: Workload) { - return scope.runBlockingSimulation { - val provider = SimResourceSource(4200.0, scheduler) - return@runBlockingSimulation provider.consume(SimTraceConsumer(state.trace)) - } - } - - @Benchmark - fun benchmarkForwardOverhead(state: Workload) { - return scope.runBlockingSimulation { - val provider = SimResourceSource(4200.0, scheduler) - val forwarder = SimResourceForwarder() - provider.startConsumer(forwarder) - return@runBlockingSimulation forwarder.consume(SimTraceConsumer(state.trace)) - } - } - - @Benchmark - fun benchmarkSwitchMaxMinSingleConsumer(state: Workload) { - return scope.runBlockingSimulation { - val switch = SimResourceSwitchMaxMin(scheduler) - - switch.addInput(SimResourceSource(3000.0, scheduler)) - switch.addInput(SimResourceSource(3000.0, scheduler)) - - val provider = switch.addOutput(3500.0) - return@runBlockingSimulation provider.consume(SimTraceConsumer(state.trace)) - } - } - - @Benchmark - fun benchmarkSwitchMaxMinTripleConsumer(state: Workload) { - return scope.runBlockingSimulation { - val switch = SimResourceSwitchMaxMin(scheduler) - - switch.addInput(SimResourceSource(3000.0, scheduler)) - switch.addInput(SimResourceSource(3000.0, scheduler)) - - repeat(3) { i -> - launch { - val provider = switch.addOutput(3500.0) - provider.consume(SimTraceConsumer(state.trace)) - } - } - } - } - - @Benchmark - fun benchmarkSwitchExclusiveSingleConsumer(state: Workload) { - return scope.runBlockingSimulation { - val switch = SimResourceSwitchExclusive() - - switch.addInput(SimResourceSource(3000.0, scheduler)) - switch.addInput(SimResourceSource(3000.0, scheduler)) - - val provider = switch.addOutput(3500.0) - return@runBlockingSimulation provider.consume(SimTraceConsumer(state.trace)) - } - } - - @Benchmark - fun benchmarkSwitchExclusiveTripleConsumer(state: Workload) { - return scope.runBlockingSimulation { - val switch = SimResourceSwitchExclusive() - - switch.addInput(SimResourceSource(3000.0, scheduler)) - switch.addInput(SimResourceSource(3000.0, scheduler)) - - repeat(2) { - launch { - val provider = switch.addOutput(3500.0) - provider.consume(SimTraceConsumer(state.trace)) - } - } - } - } -} diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimAbstractResourceAggregator.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimAbstractResourceAggregator.kt deleted file mode 100644 index 6ae04f27..00000000 --- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimAbstractResourceAggregator.kt +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.resources - -/** - * Abstract implementation of [SimResourceAggregator]. - */ -public abstract class SimAbstractResourceAggregator(private val scheduler: SimResourceScheduler) : SimResourceAggregator { - /** - * This method is invoked when the resource consumer consumes resources. - */ - protected abstract fun doConsume(work: Double, limit: Double, deadline: Long) - - /** - * This method is invoked when the resource consumer enters an idle state. - */ - protected abstract fun doIdle(deadline: Long) - - /** - * This method is invoked when the resource consumer finishes processing. - */ - protected abstract fun doFinish(cause: Throwable?) - - /** - * This method is invoked when an input context is started. - */ - protected abstract fun onInputStarted(input: Input) - - /** - * This method is invoked when an input is stopped. - */ - protected abstract fun onInputFinished(input: Input) - - override fun addInput(input: SimResourceProvider) { - check(output.state != SimResourceState.Stopped) { "Aggregator has been stopped" } - - val consumer = Consumer() - _inputs.add(input) - _inputConsumers.add(consumer) - input.startConsumer(consumer) - } - - override fun close() { - output.close() - } - - override val output: SimResourceProvider - get() = _output - private val _output = SimResourceForwarder() - - override val inputs: Set<SimResourceProvider> - get() = _inputs - private val _inputs = mutableSetOf<SimResourceProvider>() - private val _inputConsumers = mutableListOf<Consumer>() - - protected val outputContext: SimResourceContext - get() = context - private val context = object : SimAbstractResourceContext(0.0, scheduler, _output) { - override val remainingWork: Double - get() { - val now = clock.millis() - - return if (_remainingWorkFlush < now) { - _remainingWorkFlush = now - _inputConsumers.sumByDouble { it._ctx?.remainingWork ?: 0.0 }.also { _remainingWork = it } - } else { - _remainingWork - } - } - private var _remainingWork: Double = 0.0 - private var _remainingWorkFlush: Long = Long.MIN_VALUE - - override fun onConsume(work: Double, limit: Double, deadline: Long) = doConsume(work, limit, deadline) - - override fun onIdle(deadline: Long) = doIdle(deadline) - - override fun onFinish() { - doFinish(null) - } - } - - /** - * An input for the resource aggregator. - */ - public interface Input { - /** - * The [SimResourceContext] associated with the input. - */ - public val ctx: SimResourceContext - - /** - * Push the specified [SimResourceCommand] to the input. - */ - public fun push(command: SimResourceCommand) - } - - /** - * An internal [SimResourceConsumer] implementation for aggregator inputs. - */ - private inner class Consumer : Input, SimResourceConsumer { - /** - * The resource context associated with the input. - */ - override val ctx: SimResourceContext - get() = _ctx!! - var _ctx: SimResourceContext? = null - - /** - * The resource command to run next. - */ - private var command: SimResourceCommand? = null - - private fun updateCapacity() { - // Adjust capacity of output resource - context.capacity = _inputConsumers.sumByDouble { it._ctx?.capacity ?: 0.0 } - } - - /* Input */ - override fun push(command: SimResourceCommand) { - this.command = command - _ctx?.interrupt() - } - - /* SimResourceConsumer */ - override fun onNext(ctx: SimResourceContext): SimResourceCommand { - var next = command - - return if (next != null) { - this.command = null - next - } else { - context.flush(isIntermediate = true) - next = command - this.command = null - next ?: SimResourceCommand.Idle() - } - } - - override fun onEvent(ctx: SimResourceContext, event: SimResourceEvent) { - when (event) { - SimResourceEvent.Start -> { - _ctx = ctx - updateCapacity() - - // Make sure we initialize the output if we have not done so yet - if (context.state == SimResourceState.Pending) { - context.start() - } - - onInputStarted(this) - } - SimResourceEvent.Capacity -> updateCapacity() - SimResourceEvent.Exit -> onInputFinished(this) - else -> {} - } - } - } -} diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimAbstractResourceContext.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimAbstractResourceContext.kt deleted file mode 100644 index c03bfad5..00000000 --- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimAbstractResourceContext.kt +++ /dev/null @@ -1,362 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.resources - -import java.time.Clock -import kotlin.math.max -import kotlin.math.min - -/** - * Partial implementation of a [SimResourceContext] managing the communication between resources and resource consumers. - */ -public abstract class SimAbstractResourceContext( - initialCapacity: Double, - private val scheduler: SimResourceScheduler, - private val consumer: SimResourceConsumer -) : SimResourceContext, SimResourceFlushable { - - /** - * The clock of the context. - */ - public override val clock: Clock - get() = scheduler.clock - - /** - * The capacity of the resource. - */ - public final override var capacity: Double = initialCapacity - set(value) { - val oldValue = field - - // Only changes will be propagated - if (value != oldValue) { - field = value - onCapacityChange() - } - } - - /** - * The amount of work still remaining at this instant. - */ - override val remainingWork: Double - get() { - val activeCommand = activeCommand ?: return 0.0 - val now = clock.millis() - - return if (_remainingWorkFlush < now) { - _remainingWorkFlush = now - computeRemainingWork(activeCommand, now).also { _remainingWork = it } - } else { - _remainingWork - } - } - private var _remainingWork: Double = 0.0 - private var _remainingWorkFlush: Long = Long.MIN_VALUE - - /** - * A flag to indicate the state of the context. - */ - public var state: SimResourceState = SimResourceState.Pending - private set - - /** - * The current processing speed of the resource. - */ - final override var speed: Double = 0.0 - private set - - /** - * This method is invoked when the resource will idle until the specified [deadline]. - */ - public abstract fun onIdle(deadline: Long) - - /** - * This method is invoked when the resource will be consumed until the specified [work] was processed or the - * [deadline] was reached. - */ - public abstract fun onConsume(work: Double, limit: Double, deadline: Long) - - /** - * This method is invoked when the resource consumer has finished. - */ - public abstract fun onFinish() - - /** - * Get the remaining work to process after a resource consumption. - * - * @param work The size of the resource consumption. - * @param speed The speed of consumption. - * @param duration The duration from the start of the consumption until now. - * @return The amount of work remaining. - */ - protected open fun getRemainingWork(work: Double, speed: Double, duration: Long): Double { - return if (duration > 0L) { - val processed = duration / 1000.0 * speed - max(0.0, work - processed) - } else { - 0.0 - } - } - - /** - * Start the consumer. - */ - public fun start() { - check(state == SimResourceState.Pending) { "Consumer is already started" } - - val now = clock.millis() - - state = SimResourceState.Active - isProcessing = true - latestFlush = now - - try { - consumer.onEvent(this, SimResourceEvent.Start) - activeCommand = interpret(consumer.onNext(this), now) - } catch (cause: Throwable) { - doFail(cause) - } finally { - isProcessing = false - } - } - - /** - * Immediately stop the consumer. - */ - public fun stop() { - try { - isProcessing = true - latestFlush = clock.millis() - - flush(isIntermediate = true) - doStop() - } finally { - isProcessing = false - } - } - - override fun flush(isIntermediate: Boolean) { - // Flush is no-op when the consumer is finished or not yet started - if (state != SimResourceState.Active) { - return - } - - val now = clock.millis() - - // Fast path: if the intermediate progress was already flushed at the current instant, we can skip it. - if (isIntermediate && latestFlush >= now) { - return - } - - try { - val activeCommand = activeCommand ?: return - val (timestamp, command) = activeCommand - - // Note: accessor is reliant on activeCommand being set - val remainingWork = remainingWork - - isProcessing = true - - val duration = now - timestamp - assert(duration >= 0) { "Flush in the past" } - - this.activeCommand = when (command) { - is SimResourceCommand.Idle -> { - // We should only continue processing the next command if: - // 1. The resource consumer reached its deadline. - // 2. The resource consumer should be interrupted (e.g., someone called .interrupt()) - if (command.deadline <= now || !isIntermediate) { - next(now) - } else { - interpret(SimResourceCommand.Idle(command.deadline), now) - } - } - is SimResourceCommand.Consume -> { - // We should only continue processing the next command if: - // 1. The resource consumption was finished. - // 2. The resource capacity cannot satisfy the demand. - // 4. The resource consumer should be interrupted (e.g., someone called .interrupt()) - if (remainingWork == 0.0 || command.deadline <= now || !isIntermediate) { - next(now) - } else { - interpret(SimResourceCommand.Consume(remainingWork, command.limit, command.deadline), now) - } - } - SimResourceCommand.Exit -> - // Flush may not be called when the resource consumer has finished - throw IllegalStateException() - } - - // Flush remaining work cache - _remainingWorkFlush = Long.MIN_VALUE - } catch (cause: Throwable) { - doFail(cause) - } finally { - latestFlush = now - isProcessing = false - } - } - - override fun interrupt() { - // Prevent users from interrupting the resource while they are constructing their next command, as this will - // only lead to infinite recursion. - if (isProcessing) { - return - } - - scheduler.schedule(this, isIntermediate = false) - } - - override fun toString(): String = "SimAbstractResourceContext[capacity=$capacity]" - - /** - * A flag to indicate that the resource is currently processing a command. - */ - private var isProcessing: Boolean = false - - /** - * The current command that is being processed. - */ - private var activeCommand: CommandWrapper? = null - - /** - * The latest timestamp at which the resource was flushed. - */ - private var latestFlush: Long = Long.MIN_VALUE - - /** - * Finish the consumer and resource provider. - */ - private fun doStop() { - val state = state - this.state = SimResourceState.Stopped - - if (state == SimResourceState.Active) { - activeCommand = null - try { - consumer.onEvent(this, SimResourceEvent.Exit) - onFinish() - } catch (cause: Throwable) { - doFail(cause) - } - } - } - - /** - * Interpret the specified [SimResourceCommand] that was submitted by the resource consumer. - */ - private fun interpret(command: SimResourceCommand, now: Long): CommandWrapper? { - when (command) { - is SimResourceCommand.Idle -> { - val deadline = command.deadline - - require(deadline >= now) { "Deadline already passed" } - - speed = 0.0 - - onIdle(deadline) - consumer.onEvent(this, SimResourceEvent.Run) - } - is SimResourceCommand.Consume -> { - val work = command.work - val limit = command.limit - val deadline = command.deadline - - require(deadline >= now) { "Deadline already passed" } - - speed = min(capacity, limit) - onConsume(work, limit, deadline) - consumer.onEvent(this, SimResourceEvent.Run) - } - is SimResourceCommand.Exit -> { - speed = 0.0 - - doStop() - - // No need to set the next active command - return null - } - } - - return CommandWrapper(now, command) - } - - /** - * Request the workload for more work. - */ - private fun next(now: Long): CommandWrapper? = interpret(consumer.onNext(this), now) - - /** - * Compute the remaining work based on the specified [wrapper] and [timestamp][now]. - */ - private fun computeRemainingWork(wrapper: CommandWrapper, now: Long): Double { - val (timestamp, command) = wrapper - val duration = now - timestamp - return when (command) { - is SimResourceCommand.Consume -> getRemainingWork(command.work, speed, duration) - is SimResourceCommand.Idle, SimResourceCommand.Exit -> 0.0 - } - } - - /** - * Fail the resource consumer. - */ - private fun doFail(cause: Throwable) { - state = SimResourceState.Stopped - activeCommand = null - - try { - consumer.onFailure(this, cause) - } catch (e: Throwable) { - e.addSuppressed(cause) - e.printStackTrace() - } - - onFinish() - } - - /** - * Indicate that the capacity of the resource has changed. - */ - private fun onCapacityChange() { - // Do not inform the consumer if it has not been started yet - if (state != SimResourceState.Active) { - return - } - - val isThrottled = speed > capacity - - consumer.onEvent(this, SimResourceEvent.Capacity) - - // Optimization: only flush changes if the new capacity cannot satisfy the active resource command. - // Alternatively, if the consumer already interrupts the resource, the fast-path will be taken in flush(). - if (isThrottled) { - flush(isIntermediate = true) - } - } - - /** - * This class wraps a [command] with the timestamp it was started and possibly the task associated with it. - */ - private data class CommandWrapper(val timestamp: Long, val command: SimResourceCommand) -} diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceAggregator.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceAggregator.kt deleted file mode 100644 index bb4e6a2c..00000000 --- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceAggregator.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.resources - -/** - * A [SimResourceAggregator] aggregates the capacity of multiple resources into a single resource. - */ -public interface SimResourceAggregator : AutoCloseable { - /** - * The output resource provider to which resource consumers can be attached. - */ - public val output: SimResourceProvider - - /** - * The input resources that will be switched between the output providers. - */ - public val inputs: Set<SimResourceProvider> - - /** - * Add the specified [input] to the switch. - */ - public fun addInput(input: SimResourceProvider) - - /** - * End the lifecycle of the aggregator. - */ - public override fun close() -} diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceAggregatorMaxMin.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceAggregatorMaxMin.kt deleted file mode 100644 index 5665abd1..00000000 --- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceAggregatorMaxMin.kt +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.resources - -/** - * A [SimResourceAggregator] that distributes the load equally across the input resources. - */ -public class SimResourceAggregatorMaxMin(scheduler: SimResourceScheduler) : SimAbstractResourceAggregator(scheduler) { - private val consumers = mutableListOf<Input>() - - override fun doConsume(work: Double, limit: Double, deadline: Long) { - // Sort all consumers by their capacity - consumers.sortWith(compareBy { it.ctx.capacity }) - - // Divide the requests over the available capacity of the input resources fairly - for (input in consumers) { - val inputCapacity = input.ctx.capacity - val fraction = inputCapacity / outputContext.capacity - val grantedSpeed = limit * fraction - val grantedWork = fraction * work - - val command = if (grantedWork > 0.0 && grantedSpeed > 0.0) - SimResourceCommand.Consume(grantedWork, grantedSpeed, deadline) - else - SimResourceCommand.Idle(deadline) - input.push(command) - } - } - - override fun doIdle(deadline: Long) { - for (input in consumers) { - input.push(SimResourceCommand.Idle(deadline)) - } - } - - override fun doFinish(cause: Throwable?) { - val iterator = consumers.iterator() - for (input in iterator) { - iterator.remove() - input.push(SimResourceCommand.Exit) - } - } - - override fun onInputStarted(input: Input) { - consumers.add(input) - } - - override fun onInputFinished(input: Input) { - consumers.remove(input) - } -} diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceCommand.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceCommand.kt deleted file mode 100644 index f7f3fa4d..00000000 --- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceCommand.kt +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.resources - -/** - * A SimResourceCommand communicates to a resource how it is consumed by a [SimResourceConsumer]. - */ -public sealed class SimResourceCommand { - /** - * A request to the resource to perform the specified amount of work before the given [deadline]. - * - * @param work The amount of work to process. - * @param limit The maximum amount of work to be processed per second. - * @param deadline The instant at which the work needs to be fulfilled. - */ - public data class Consume(val work: Double, val limit: Double, val deadline: Long = Long.MAX_VALUE) : SimResourceCommand() { - init { - require(work > 0) { "Amount of work must be positive" } - require(limit > 0) { "Limit must be positive" } - } - } - - /** - * An indication to the resource that the consumer will idle until the specified [deadline] or if it is interrupted. - */ - public data class Idle(val deadline: Long = Long.MAX_VALUE) : SimResourceCommand() - - /** - * An indication to the resource that the consumer has finished. - */ - public object Exit : SimResourceCommand() -} diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceConsumer.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceConsumer.kt deleted file mode 100644 index 4d937514..00000000 --- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceConsumer.kt +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.resources - -/** - * A [SimResourceConsumer] characterizes how a resource is consumed. - * - * Implementors of this interface should be considered stateful and must be assumed not to be re-usable (concurrently) - * for multiple resource providers, unless explicitly said otherwise. - */ -public interface SimResourceConsumer { - /** - * This method is invoked when a resource asks for the next [command][SimResourceCommand] to process, either because - * the resource finished processing, reached its deadline or was interrupted. - * - * @param ctx The execution context in which the consumer runs. - * @return The next command that the resource should execute. - */ - public fun onNext(ctx: SimResourceContext): SimResourceCommand - - /** - * This method is invoked when an event has occurred. - * - * @param ctx The execution context in which the consumer runs. - * @param event The event that has occurred. - */ - public fun onEvent(ctx: SimResourceContext, event: SimResourceEvent) {} - - /** - * This method is invoked when a resource consumer throws an exception. - * - * @param ctx The execution context in which the consumer runs. - * @param cause The cause of the failure. - */ - public fun onFailure(ctx: SimResourceContext, cause: Throwable) {} -} diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceContext.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceContext.kt deleted file mode 100644 index 7c76c634..00000000 --- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceContext.kt +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.resources - -import java.time.Clock - -/** - * The execution context in which a [SimResourceConsumer] runs. It facilitates the communication and control between a - * resource and a resource consumer. - */ -public interface SimResourceContext { - /** - * The virtual clock tracking simulation time. - */ - public val clock: Clock - - /** - * The resource capacity available at this instant. - */ - public val capacity: Double - - /** - * The resource processing speed at this instant. - */ - public val speed: Double - - /** - * The amount of work still remaining at this instant. - */ - public val remainingWork: Double - - /** - * Ask the resource provider to interrupt its resource. - */ - public fun interrupt() -} diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceDistributor.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceDistributor.kt deleted file mode 100644 index b2759b7f..00000000 --- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceDistributor.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.resources - -/** - * A [SimResourceDistributor] distributes the capacity of some resource over multiple resource consumers. - */ -public interface SimResourceDistributor : AutoCloseable { - /** - * The output resource providers to which resource consumers can be attached. - */ - public val outputs: Set<SimResourceProvider> - - /** - * The input resource that will be distributed over the consumers. - */ - public val input: SimResourceProvider - - /** - * Add an output to the switch with the specified [capacity]. - */ - public fun addOutput(capacity: Double): SimResourceProvider -} diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceDistributorMaxMin.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceDistributorMaxMin.kt deleted file mode 100644 index a76cb1e3..00000000 --- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceDistributorMaxMin.kt +++ /dev/null @@ -1,427 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.resources - -import kotlin.math.max -import kotlin.math.min - -/** - * A [SimResourceDistributor] that distributes the capacity of a resource over consumers using max-min fair sharing. - */ -public class SimResourceDistributorMaxMin( - override val input: SimResourceProvider, - private val scheduler: SimResourceScheduler, - private val listener: Listener? = null -) : SimResourceDistributor { - override val outputs: Set<SimResourceProvider> - get() = _outputs - private val _outputs = mutableSetOf<OutputProvider>() - - /** - * The active output contexts. - */ - private val outputContexts: MutableList<OutputContext> = mutableListOf() - - /** - * The total speed requested by the output resources. - */ - private var totalRequestedSpeed = 0.0 - - /** - * The total amount of work requested by the output resources. - */ - private var totalRequestedWork = 0.0 - - /** - * The total allocated speed for the output resources. - */ - private var totalAllocatedSpeed = 0.0 - - /** - * The total allocated work requested for the output resources. - */ - private var totalAllocatedWork = 0.0 - - /** - * The amount of work that could not be performed due to over-committing resources. - */ - private var totalOvercommittedWork = 0.0 - - /** - * The amount of work that was lost due to interference. - */ - private var totalInterferedWork = 0.0 - - /** - * A flag to indicate that the switch is closed. - */ - private var isClosed: Boolean = false - - /** - * An internal [SimResourceConsumer] implementation for switch inputs. - */ - private val consumer = object : SimResourceConsumer { - /** - * The resource context of the consumer. - */ - private lateinit var ctx: SimResourceContext - - val remainingWork: Double - get() = ctx.remainingWork - - override fun onNext(ctx: SimResourceContext): SimResourceCommand { - return doNext(ctx.capacity) - } - - override fun onEvent(ctx: SimResourceContext, event: SimResourceEvent) { - when (event) { - SimResourceEvent.Start -> { - this.ctx = ctx - } - SimResourceEvent.Exit -> { - val iterator = _outputs.iterator() - while (iterator.hasNext()) { - val output = iterator.next() - - // Remove the output from the outputs to prevent ConcurrentModificationException when removing it - // during the call to output.close() - iterator.remove() - - output.close() - } - } - else -> {} - } - } - } - - /** - * The total amount of remaining work. - */ - private val totalRemainingWork: Double - get() = consumer.remainingWork - - override fun addOutput(capacity: Double): SimResourceProvider { - check(!isClosed) { "Distributor has been closed" } - - val provider = OutputProvider(capacity) - _outputs.add(provider) - return provider - } - - override fun close() { - if (!isClosed) { - isClosed = true - input.cancel() - } - } - - init { - input.startConsumer(consumer) - } - - /** - * Indicate that the workloads should be re-scheduled. - */ - private fun schedule() { - input.interrupt() - } - - /** - * Schedule the work over the physical CPUs. - */ - private fun doSchedule(capacity: Double): SimResourceCommand { - // If there is no work yet, mark all inputs as idle. - if (outputContexts.isEmpty()) { - return SimResourceCommand.Idle() - } - - val maxUsage = capacity - var duration: Double = Double.MAX_VALUE - var deadline: Long = Long.MAX_VALUE - var availableSpeed = maxUsage - var totalRequestedSpeed = 0.0 - var totalRequestedWork = 0.0 - - // Flush the work of the outputs - var outputIterator = outputContexts.listIterator() - while (outputIterator.hasNext()) { - val output = outputIterator.next() - - output.flush(isIntermediate = true) - - if (output.activeCommand == SimResourceCommand.Exit) { - // Apparently the output consumer has exited, so remove it from the scheduling queue. - outputIterator.remove() - } - } - - // Sort the outputs based on their requested usage - // Profiling shows that it is faster to sort every slice instead of maintaining some kind of sorted set - outputContexts.sort() - - // Divide the available input capacity fairly across the outputs using max-min fair sharing - outputIterator = outputContexts.listIterator() - var remaining = outputContexts.size - while (outputIterator.hasNext()) { - val output = outputIterator.next() - val availableShare = availableSpeed / remaining-- - - when (val command = output.activeCommand) { - is SimResourceCommand.Idle -> { - // Take into account the minimum deadline of this slice before we possible continue - deadline = min(deadline, command.deadline) - - output.actualSpeed = 0.0 - } - is SimResourceCommand.Consume -> { - val grantedSpeed = min(output.allowedSpeed, availableShare) - - // Take into account the minimum deadline of this slice before we possible continue - deadline = min(deadline, command.deadline) - - // Ignore idle computation - if (grantedSpeed <= 0.0 || command.work <= 0.0) { - output.actualSpeed = 0.0 - continue - } - - totalRequestedSpeed += command.limit - totalRequestedWork += command.work - - output.actualSpeed = grantedSpeed - availableSpeed -= grantedSpeed - - // The duration that we want to run is that of the shortest request from an output - duration = min(duration, command.work / grantedSpeed) - } - SimResourceCommand.Exit -> assert(false) { "Did not expect output to be stopped" } - } - } - - assert(deadline >= scheduler.clock.millis()) { "Deadline already passed" } - - this.totalRequestedSpeed = totalRequestedSpeed - this.totalRequestedWork = totalRequestedWork - this.totalAllocatedSpeed = maxUsage - availableSpeed - this.totalAllocatedWork = min(totalRequestedWork, totalAllocatedSpeed * duration) - - return if (totalAllocatedWork > 0.0 && totalAllocatedSpeed > 0.0) - SimResourceCommand.Consume(totalAllocatedWork, totalAllocatedSpeed, deadline) - else - SimResourceCommand.Idle(deadline) - } - - /** - * Obtain the next command to perform. - */ - private fun doNext(capacity: Double): SimResourceCommand { - val totalRequestedWork = totalRequestedWork.toLong() - val totalRemainingWork = totalRemainingWork.toLong() - val totalAllocatedWork = totalAllocatedWork.toLong() - val totalRequestedSpeed = totalRequestedSpeed - val totalAllocatedSpeed = totalAllocatedSpeed - - // Force all inputs to re-schedule their work. - val command = doSchedule(capacity) - - // Report metrics - listener?.onSliceFinish( - this, - totalRequestedWork, - totalAllocatedWork - totalRemainingWork, - totalOvercommittedWork.toLong(), - totalInterferedWork.toLong(), - totalAllocatedSpeed, - totalRequestedSpeed - ) - - totalInterferedWork = 0.0 - totalOvercommittedWork = 0.0 - - return command - } - - /** - * Event listener for hypervisor events. - */ - public interface Listener { - /** - * This method is invoked when a slice is finished. - */ - public fun onSliceFinish( - switch: SimResourceDistributor, - requestedWork: Long, - grantedWork: Long, - overcommittedWork: Long, - interferedWork: Long, - cpuUsage: Double, - cpuDemand: Double - ) - } - - /** - * An internal [SimResourceProvider] implementation for switch outputs. - */ - private inner class OutputProvider(val capacity: Double) : SimResourceProvider { - /** - * The [OutputContext] that is currently running. - */ - private var ctx: OutputContext? = null - - override var state: SimResourceState = SimResourceState.Pending - internal set - - override fun startConsumer(consumer: SimResourceConsumer) { - check(state == SimResourceState.Pending) { "Resource cannot be consumed" } - - val ctx = OutputContext(this, consumer) - this.ctx = ctx - this.state = SimResourceState.Active - outputContexts += ctx - - ctx.start() - schedule() - } - - override fun close() { - cancel() - - if (state != SimResourceState.Stopped) { - state = SimResourceState.Stopped - _outputs.remove(this) - } - } - - override fun interrupt() { - ctx?.interrupt() - } - - override fun cancel() { - val ctx = ctx - if (ctx != null) { - this.ctx = null - ctx.stop() - } - - if (state != SimResourceState.Stopped) { - state = SimResourceState.Pending - } - } - } - - /** - * A [SimAbstractResourceContext] for the output resources. - */ - private inner class OutputContext( - private val provider: OutputProvider, - consumer: SimResourceConsumer - ) : SimAbstractResourceContext(provider.capacity, scheduler, consumer), Comparable<OutputContext> { - /** - * The current command that is processed by the vCPU. - */ - var activeCommand: SimResourceCommand = SimResourceCommand.Idle() - - /** - * The processing speed that is allowed by the model constraints. - */ - var allowedSpeed: Double = 0.0 - - /** - * The actual processing speed. - */ - var actualSpeed: Double = 0.0 - - private fun reportOvercommit() { - val remainingWork = remainingWork - totalOvercommittedWork += remainingWork - } - - override fun onIdle(deadline: Long) { - reportOvercommit() - - allowedSpeed = 0.0 - activeCommand = SimResourceCommand.Idle(deadline) - } - - override fun onConsume(work: Double, limit: Double, deadline: Long) { - reportOvercommit() - - allowedSpeed = speed - activeCommand = SimResourceCommand.Consume(work, limit, deadline) - } - - override fun onFinish() { - reportOvercommit() - - activeCommand = SimResourceCommand.Exit - provider.cancel() - } - - override fun getRemainingWork(work: Double, speed: Double, duration: Long): Double { - // Apply performance interference model - val performanceScore = 1.0 - - // Compute the remaining amount of work - return if (work > 0.0) { - // Compute the fraction of compute time allocated to the VM - val fraction = actualSpeed / totalAllocatedSpeed - - // Compute the work that was actually granted to the VM. - val processingAvailable = max(0.0, totalAllocatedWork - totalRemainingWork) * fraction - val processed = processingAvailable * performanceScore - - val interferedWork = processingAvailable - processed - - totalInterferedWork += interferedWork - - max(0.0, work - processed) - } else { - 0.0 - } - } - - private var isProcessing: Boolean = false - - override fun interrupt() { - // Prevent users from interrupting the CPU while it is constructing its next command, this will only lead - // to infinite recursion. - if (isProcessing) { - return - } - - try { - isProcessing = false - - super.interrupt() - - // Force the scheduler to re-schedule - schedule() - } finally { - isProcessing = true - } - } - - override fun compareTo(other: OutputContext): Int = allowedSpeed.compareTo(other.allowedSpeed) - } -} diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceEvent.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceEvent.kt deleted file mode 100644 index 959427f1..00000000 --- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceEvent.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.resources - -/** - * A resource event that is communicated to the resource consumer. - */ -public enum class SimResourceEvent { - /** - * This event is emitted to the consumer when it has started. - */ - Start, - - /** - * This event is emitted to the consumer when it has exited. - */ - Exit, - - /** - * This event is emitted to the consumer when it has started a new resource consumption or idle cycle. - */ - Run, - - /** - * This event is emitted to the consumer when the capacity of the resource has changed. - */ - Capacity, -} diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceFlow.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceFlow.kt deleted file mode 100644 index bbf6ad44..00000000 --- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceFlow.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.resources - -/** - * A [SimResourceFlow] acts as both a resource consumer and resource provider at the same time, simplifying bridging - * between different components. - */ -public interface SimResourceFlow : SimResourceConsumer, SimResourceProvider diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceFlushable.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceFlushable.kt deleted file mode 100644 index f6a1a42e..00000000 --- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceFlushable.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.resources - -/** - * An interface used by the [SimResourceScheduler] to flush the progress of resource consumer. - */ -public interface SimResourceFlushable { - /** - * Flush the current active resource consumption. - * - * @param isIntermediate A flag to indicate that the intermediate progress of the resource consumer should be - * flushed, but without interrupting the resource consumer to submit a new command. If false, the resource consumer - * will be asked to deliver a new command and is essentially interrupted. - */ - public fun flush(isIntermediate: Boolean) -} diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceProvider.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceProvider.kt deleted file mode 100644 index 2f567a5e..00000000 --- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceProvider.kt +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.resources - -import kotlinx.coroutines.suspendCancellableCoroutine -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException - -/** - * A [SimResourceProvider] provides some resource of type [R]. - */ -public interface SimResourceProvider : AutoCloseable { - /** - * The state of the resource. - */ - public val state: SimResourceState - - /** - * Start the specified [resource consumer][consumer] in the context of this resource provider asynchronously. - * - * @throws IllegalStateException if there is already a consumer active or the resource lifetime has ended. - */ - public fun startConsumer(consumer: SimResourceConsumer) - - /** - * Interrupt the resource consumer. If there is no consumer active, this operation will be a no-op. - */ - public fun interrupt() - - /** - * Cancel the current resource consumer. If there is no consumer active, this operation will be a no-op. - */ - public fun cancel() - - /** - * End the lifetime of the resource. - * - * This operation terminates the existing resource consumer. - */ - public override fun close() -} - -/** - * Consume the resource provided by this provider using the specified [consumer] and suspend execution until - * the consumer has finished. - */ -public suspend fun SimResourceProvider.consume(consumer: SimResourceConsumer) { - return suspendCancellableCoroutine { cont -> - startConsumer(object : SimResourceConsumer by consumer { - override fun onEvent(ctx: SimResourceContext, event: SimResourceEvent) { - consumer.onEvent(ctx, event) - - if (event == SimResourceEvent.Exit && !cont.isCompleted) { - cont.resume(Unit) - } - } - - override fun onFailure(ctx: SimResourceContext, cause: Throwable) { - try { - consumer.onFailure(ctx, cause) - cont.resumeWithException(cause) - } catch (e: Throwable) { - e.addSuppressed(cause) - cont.resumeWithException(e) - } - } - - override fun toString(): String = "SimSuspendingResourceConsumer" - }) - - cont.invokeOnCancellation { cancel() } - } -} diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceScheduler.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceScheduler.kt deleted file mode 100644 index a228c47b..00000000 --- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceScheduler.kt +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.resources - -import java.time.Clock - -/** - * A resource scheduler is responsible for scheduling the communication and synchronization between multiple resource - * providers and consumers. - * - * By centralizing the scheduling logic, updates of resources within a single system can be scheduled and tracked more - * efficiently, reducing the overall work needed per update. - */ -public interface SimResourceScheduler { - /** - * The [Clock] associated with this scheduler. - */ - public val clock: Clock - - /** - * Schedule a direct interrupt for the resource context represented by [flushable]. - * - * @param flushable The resource context that needs to be flushed. - * @param isIntermediate A flag to indicate that the intermediate progress of the resource consumer should be - * flushed, but without interrupting the resource consumer to submit a new command. If false, the resource consumer - * will be asked to deliver a new command and is essentially interrupted. - */ - public fun schedule(flushable: SimResourceFlushable, isIntermediate: Boolean = false) - - /** - * Schedule an interrupt in the future for the resource context represented by [flushable]. - * - * This method will override earlier calls to this method for the same [flushable]. - * - * @param flushable The resource context that needs to be flushed. - * @param timestamp The timestamp when the interrupt should happen. - * @param isIntermediate A flag to indicate that the intermediate progress of the resource consumer should be - * flushed, but without interrupting the resource consumer to submit a new command. If false, the resource consumer - * will be asked to deliver a new command and is essentially interrupted. - */ - public fun schedule(flushable: SimResourceFlushable, timestamp: Long, isIntermediate: Boolean = false) - - /** - * Batch the execution of several interrupts into a single call. - * - * This method is useful if you want to propagate the start of multiple resources (e.g., CPUs) in a single update. - */ - public fun batch(block: () -> Unit) -} diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSchedulerTrampoline.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSchedulerTrampoline.kt deleted file mode 100644 index cdbb4a6c..00000000 --- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSchedulerTrampoline.kt +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.resources - -import org.opendc.utils.TimerScheduler -import java.time.Clock -import java.util.ArrayDeque -import kotlin.coroutines.CoroutineContext - -/** - * A [SimResourceScheduler] queues all interrupts that occur during execution to be executed after. - * - * @param clock The virtual simulation clock. - */ -public class SimResourceSchedulerTrampoline(context: CoroutineContext, override val clock: Clock) : SimResourceScheduler { - /** - * The [TimerScheduler] to actually schedule the interrupts. - */ - private val timers = TimerScheduler<Any>(context, clock) - - /** - * A flag to indicate that an interrupt is currently running already. - */ - private var isRunning: Boolean = false - - /** - * The queue of resources to be flushed. - */ - private val queue = ArrayDeque<Pair<SimResourceFlushable, Boolean>>() - - override fun schedule(flushable: SimResourceFlushable, isIntermediate: Boolean) { - queue.add(flushable to isIntermediate) - - if (isRunning) { - return - } - - flush() - } - - override fun schedule(flushable: SimResourceFlushable, timestamp: Long, isIntermediate: Boolean) { - timers.startSingleTimerTo(flushable, timestamp) { - schedule(flushable, isIntermediate) - } - } - - override fun batch(block: () -> Unit) { - val wasAlreadyRunning = isRunning - try { - isRunning = true - block() - } finally { - if (!wasAlreadyRunning) { - isRunning = false - } - } - } - - /** - * Flush the scheduled queue. - */ - private fun flush() { - val visited = mutableSetOf<SimResourceFlushable>() - try { - isRunning = true - while (queue.isNotEmpty()) { - val (flushable, isIntermediate) = queue.poll() - flushable.flush(isIntermediate) - visited.add(flushable) - } - } finally { - isRunning = false - } - } -} diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSource.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSource.kt deleted file mode 100644 index 3277b889..00000000 --- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSource.kt +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.resources - -import kotlin.math.ceil -import kotlin.math.min - -/** - * A [SimResourceSource] represents a source for some resource of type [R] that provides bounded processing capacity. - * - * @param initialCapacity The initial capacity of the resource. - * @param scheduler The scheduler to schedule the interrupts. - */ -public class SimResourceSource( - initialCapacity: Double, - private val scheduler: SimResourceScheduler -) : SimResourceProvider { - /** - * The current processing speed of the resource. - */ - public val speed: Double - get() = ctx?.speed ?: 0.0 - - /** - * The capacity of the resource. - */ - public var capacity: Double = initialCapacity - set(value) { - field = value - ctx?.capacity = value - } - - /** - * The [Context] that is currently running. - */ - private var ctx: Context? = null - - override var state: SimResourceState = SimResourceState.Pending - private set - - override fun startConsumer(consumer: SimResourceConsumer) { - check(state == SimResourceState.Pending) { "Resource is in invalid state" } - val ctx = Context(consumer) - - this.ctx = ctx - this.state = SimResourceState.Active - - ctx.start() - } - - override fun close() { - cancel() - state = SimResourceState.Stopped - } - - override fun interrupt() { - ctx?.interrupt() - } - - override fun cancel() { - val ctx = ctx - if (ctx != null) { - this.ctx = null - ctx.stop() - } - - if (state != SimResourceState.Stopped) { - state = SimResourceState.Pending - } - } - - /** - * Internal implementation of [SimResourceContext] for this class. - */ - private inner class Context(consumer: SimResourceConsumer) : SimAbstractResourceContext(capacity, scheduler, consumer) { - override fun onIdle(deadline: Long) { - // Do not resume if deadline is "infinite" - if (deadline != Long.MAX_VALUE) { - scheduler.schedule(this, deadline) - } - } - - override fun onConsume(work: Double, limit: Double, deadline: Long) { - val until = min(deadline, clock.millis() + getDuration(work, speed)) - scheduler.schedule(this, until) - } - - override fun onFinish() { - cancel() - - ctx = null - - if (this@SimResourceSource.state != SimResourceState.Stopped) { - this@SimResourceSource.state = SimResourceState.Pending - } - } - - override fun toString(): String = "SimResourceSource.Context[capacity=$capacity]" - } - - /** - * Compute the duration that a resource consumption will take with the specified [speed]. - */ - private fun getDuration(work: Double, speed: Double): Long { - return ceil(work / speed * 1000).toLong() - } -} diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceState.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceState.kt deleted file mode 100644 index c72951d0..00000000 --- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceState.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.resources - -/** - * The state of a resource provider. - */ -public enum class SimResourceState { - /** - * The resource provider is pending and the resource is waiting to be consumed. - */ - Pending, - - /** - * The resource provider is active and the resource is currently being consumed. - */ - Active, - - /** - * The resource provider is stopped and the resource cannot be consumed anymore. - */ - Stopped -} diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSwitch.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSwitch.kt deleted file mode 100644 index 53fec16a..00000000 --- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSwitch.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.resources - -/** - * A [SimResourceSwitch] enables switching of capacity of multiple resources between multiple consumers. - */ -public interface SimResourceSwitch : AutoCloseable { - /** - * The output resource providers to which resource consumers can be attached. - */ - public val outputs: Set<SimResourceProvider> - - /** - * The input resources that will be switched between the output providers. - */ - public val inputs: Set<SimResourceProvider> - - /** - * Add an output to the switch with the specified [capacity]. - */ - public fun addOutput(capacity: Double): SimResourceProvider - - /** - * Add the specified [input] to the switch. - */ - public fun addInput(input: SimResourceProvider) -} diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSwitchExclusive.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSwitchExclusive.kt deleted file mode 100644 index 1a9dd0bc..00000000 --- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSwitchExclusive.kt +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.resources - -import java.util.ArrayDeque - -/** - * A [SimResourceSwitch] implementation that allocates outputs to the inputs of the switch exclusively. This means that - * a single output is directly connected to an input and that the switch can only support as much outputs as inputs. - */ -public class SimResourceSwitchExclusive : SimResourceSwitch { - /** - * A flag to indicate that the switch is closed. - */ - private var isClosed: Boolean = false - - private val _outputs = mutableSetOf<Provider>() - override val outputs: Set<SimResourceProvider> - get() = _outputs - - private val availableResources = ArrayDeque<SimResourceTransformer>() - - private val _inputs = mutableSetOf<SimResourceProvider>() - override val inputs: Set<SimResourceProvider> - get() = _inputs - - override fun addOutput(capacity: Double): SimResourceProvider { - check(!isClosed) { "Switch has been closed" } - check(availableResources.isNotEmpty()) { "No capacity to serve request" } - val forwarder = availableResources.poll() - val output = Provider(capacity, forwarder) - _outputs += output - return output - } - - override fun addInput(input: SimResourceProvider) { - check(!isClosed) { "Switch has been closed" } - - if (input in inputs) { - return - } - - val forwarder = SimResourceForwarder() - - _inputs += input - availableResources += forwarder - - input.startConsumer(object : SimResourceConsumer by forwarder { - override fun onEvent(ctx: SimResourceContext, event: SimResourceEvent) { - if (event == SimResourceEvent.Exit) { - // De-register the input after it has finished - _inputs -= input - } - - forwarder.onEvent(ctx, event) - } - }) - } - - override fun close() { - isClosed = true - - // Cancel all upstream subscriptions - _inputs.forEach(SimResourceProvider::cancel) - } - - private inner class Provider( - private val capacity: Double, - private val forwarder: SimResourceTransformer - ) : SimResourceProvider by forwarder { - override fun close() { - // We explicitly do not close the forwarder here in order to re-use it across output resources. - - _outputs -= this - availableResources += forwarder - } - } -} diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSwitchMaxMin.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSwitchMaxMin.kt deleted file mode 100644 index 5dc1e68d..00000000 --- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSwitchMaxMin.kt +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.resources - -import kotlinx.coroutines.* - -/** - * A [SimResourceSwitch] implementation that switches resource consumptions over the available resources using max-min - * fair sharing. - */ -public class SimResourceSwitchMaxMin( - scheduler: SimResourceScheduler, - private val listener: Listener? = null -) : SimResourceSwitch { - private val _outputs = mutableSetOf<SimResourceProvider>() - override val outputs: Set<SimResourceProvider> - get() = _outputs - - private val _inputs = mutableSetOf<SimResourceProvider>() - override val inputs: Set<SimResourceProvider> - get() = _inputs - - /** - * A flag to indicate that the switch was closed. - */ - private var isClosed = false - - /** - * The aggregator to aggregate the resources. - */ - private val aggregator = SimResourceAggregatorMaxMin(scheduler) - - /** - * The distributor to distribute the aggregated resources. - */ - private val distributor = SimResourceDistributorMaxMin( - aggregator.output, scheduler, - object : SimResourceDistributorMaxMin.Listener { - override fun onSliceFinish( - switch: SimResourceDistributor, - requestedWork: Long, - grantedWork: Long, - overcommittedWork: Long, - interferedWork: Long, - cpuUsage: Double, - cpuDemand: Double - ) { - listener?.onSliceFinish(this@SimResourceSwitchMaxMin, requestedWork, grantedWork, overcommittedWork, interferedWork, cpuUsage, cpuDemand) - } - } - ) - - /** - * Add an output to the switch represented by [resource]. - */ - override fun addOutput(capacity: Double): SimResourceProvider { - check(!isClosed) { "Switch has been closed" } - - val provider = distributor.addOutput(capacity) - _outputs.add(provider) - return provider - } - - /** - * Add the specified [input] to the switch. - */ - override fun addInput(input: SimResourceProvider) { - check(!isClosed) { "Switch has been closed" } - - aggregator.addInput(input) - } - - override fun close() { - if (!isClosed) { - isClosed = true - distributor.close() - aggregator.close() - } - } - - /** - * Event listener for hypervisor events. - */ - public interface Listener { - /** - * This method is invoked when a slice is finished. - */ - public fun onSliceFinish( - switch: SimResourceSwitchMaxMin, - requestedWork: Long, - grantedWork: Long, - overcommittedWork: Long, - interferedWork: Long, - cpuUsage: Double, - cpuDemand: Double - ) - } -} diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceTransformer.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceTransformer.kt deleted file mode 100644 index 32f3f573..00000000 --- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceTransformer.kt +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.resources - -/** - * A [SimResourceFlow] that transforms the resource commands emitted by the resource commands to the resource provider. - * - * @param isCoupled A flag to indicate that the transformer will exit when the resource consumer exits. - * @param transform The function to transform the received resource command. - */ -public class SimResourceTransformer( - private val isCoupled: Boolean = false, - private val transform: (SimResourceContext, SimResourceCommand) -> SimResourceCommand -) : SimResourceFlow { - /** - * The [SimResourceContext] in which the forwarder runs. - */ - private var ctx: SimResourceContext? = null - - /** - * The delegate [SimResourceConsumer]. - */ - private var delegate: SimResourceConsumer? = null - - /** - * A flag to indicate that the delegate was started. - */ - private var hasDelegateStarted: Boolean = false - - /** - * The state of the forwarder. - */ - override var state: SimResourceState = SimResourceState.Pending - private set - - override fun startConsumer(consumer: SimResourceConsumer) { - check(state == SimResourceState.Pending) { "Resource is in invalid state" } - - state = SimResourceState.Active - delegate = consumer - - // Interrupt the provider to replace the consumer - interrupt() - } - - override fun interrupt() { - ctx?.interrupt() - } - - override fun cancel() { - val delegate = delegate - val ctx = ctx - - state = SimResourceState.Pending - - if (delegate != null && ctx != null) { - this.delegate = null - delegate.onEvent(ctx, SimResourceEvent.Exit) - } - } - - override fun close() { - val ctx = ctx - - state = SimResourceState.Stopped - - if (ctx != null) { - this.ctx = null - ctx.interrupt() - } - } - - override fun onNext(ctx: SimResourceContext): SimResourceCommand { - val delegate = delegate - - if (!hasDelegateStarted) { - start() - } - - return if (state == SimResourceState.Stopped) { - SimResourceCommand.Exit - } else if (delegate != null) { - val command = transform(ctx, delegate.onNext(ctx)) - if (command == SimResourceCommand.Exit) { - // Warning: resumption of the continuation might change the entire state of the forwarder. Make sure we - // reset beforehand the existing state and check whether it has been updated afterwards - reset() - - delegate.onEvent(ctx, SimResourceEvent.Exit) - - if (isCoupled || state == SimResourceState.Stopped) - SimResourceCommand.Exit - else - onNext(ctx) - } else { - command - } - } else { - SimResourceCommand.Idle() - } - } - - override fun onEvent(ctx: SimResourceContext, event: SimResourceEvent) { - when (event) { - SimResourceEvent.Start -> { - this.ctx = ctx - } - SimResourceEvent.Exit -> { - this.ctx = null - - val delegate = delegate - if (delegate != null) { - reset() - delegate.onEvent(ctx, SimResourceEvent.Exit) - } - } - else -> delegate?.onEvent(ctx, event) - } - } - - override fun onFailure(ctx: SimResourceContext, cause: Throwable) { - this.ctx = null - - val delegate = delegate - if (delegate != null) { - reset() - delegate.onFailure(ctx, cause) - } - } - - /** - * Start the delegate. - */ - private fun start() { - val delegate = delegate ?: return - delegate.onEvent(checkNotNull(ctx), SimResourceEvent.Start) - - hasDelegateStarted = true - } - - /** - * Reset the delegate. - */ - private fun reset() { - delegate = null - hasDelegateStarted = false - - if (state != SimResourceState.Stopped) { - state = SimResourceState.Pending - } - } -} - -/** - * Constructs a [SimResourceTransformer] that forwards the received resource command with an identity transform. - * - * @param isCoupled A flag to indicate that the transformer will exit when the resource consumer exits. - */ -public fun SimResourceForwarder(isCoupled: Boolean = false): SimResourceTransformer { - return SimResourceTransformer(isCoupled) { _, command -> command } -} diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/consumer/SimConsumerBarrier.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/consumer/SimConsumerBarrier.kt deleted file mode 100644 index 52a42241..00000000 --- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/consumer/SimConsumerBarrier.kt +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.resources.consumer - -/** - * The [SimConsumerBarrier] is a barrier that allows consumers to wait for a select number of other consumers to - * complete, before proceeding its operation. - */ -public class SimConsumerBarrier(public val parties: Int) { - private var counter = 0 - - /** - * Enter the barrier and determine whether the caller is the last to reach the barrier. - * - * @return `true` if the caller is the last to reach the barrier, `false` otherwise. - */ - public fun enter(): Boolean { - val last = ++counter == parties - if (last) { - counter = 0 - return true - } - return false - } - - /** - * Reset the barrier. - */ - public fun reset() { - counter = 0 - } -} diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/consumer/SimSpeedConsumerAdapter.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/consumer/SimSpeedConsumerAdapter.kt deleted file mode 100644 index 4f4ebb14..00000000 --- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/consumer/SimSpeedConsumerAdapter.kt +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.resources.consumer - -import org.opendc.simulator.resources.SimResourceCommand -import org.opendc.simulator.resources.SimResourceConsumer -import org.opendc.simulator.resources.SimResourceContext -import org.opendc.simulator.resources.SimResourceEvent -import kotlin.math.min - -/** - * Helper class to expose an observable [speed] field describing the speed of the consumer. - */ -public class SimSpeedConsumerAdapter( - private val delegate: SimResourceConsumer, - private val callback: (Double) -> Unit = {} -) : SimResourceConsumer by delegate { - /** - * The resource processing speed at this instant. - */ - public var speed: Double = 0.0 - private set(value) { - if (field != value) { - callback(value) - field = value - } - } - - init { - callback(0.0) - } - - override fun onNext(ctx: SimResourceContext): SimResourceCommand { - return delegate.onNext(ctx) - } - - override fun onEvent(ctx: SimResourceContext, event: SimResourceEvent) { - val oldSpeed = speed - - delegate.onEvent(ctx, event) - - when (event) { - SimResourceEvent.Run -> speed = ctx.speed - SimResourceEvent.Capacity -> { - // Check if the consumer interrupted the consumer and updated the resource consumption. If not, we might - // need to update the current speed. - if (oldSpeed == speed) { - speed = min(ctx.capacity, speed) - } - } - SimResourceEvent.Exit -> speed = 0.0 - else -> {} - } - } - - override fun onFailure(ctx: SimResourceContext, cause: Throwable) { - speed = 0.0 - - delegate.onFailure(ctx, cause) - } - - override fun toString(): String = "SimSpeedConsumerAdapter[delegate=$delegate]" -} diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/consumer/SimTraceConsumer.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/consumer/SimTraceConsumer.kt deleted file mode 100644 index 2e94e1c1..00000000 --- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/consumer/SimTraceConsumer.kt +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.resources.consumer - -import org.opendc.simulator.resources.SimResourceCommand -import org.opendc.simulator.resources.SimResourceConsumer -import org.opendc.simulator.resources.SimResourceContext -import org.opendc.simulator.resources.SimResourceEvent - -/** - * A [SimResourceConsumer] that replays a workload trace consisting of multiple fragments, each indicating the resource - * consumption for some period of time. - */ -public class SimTraceConsumer(private val trace: Sequence<Fragment>) : SimResourceConsumer { - private var iterator: Iterator<Fragment>? = null - - override fun onNext(ctx: SimResourceContext): SimResourceCommand { - val iterator = checkNotNull(iterator) - return if (iterator.hasNext()) { - val now = ctx.clock.millis() - val fragment = iterator.next() - val work = (fragment.duration / 1000) * fragment.usage - val deadline = now + fragment.duration - - assert(deadline >= now) { "Deadline already passed" } - - if (work > 0.0) - SimResourceCommand.Consume(work, fragment.usage, deadline) - else - SimResourceCommand.Idle(deadline) - } else { - SimResourceCommand.Exit - } - } - - override fun onEvent(ctx: SimResourceContext, event: SimResourceEvent) { - when (event) { - SimResourceEvent.Start -> { - check(iterator == null) { "Consumer already running" } - iterator = trace.iterator() - } - SimResourceEvent.Exit -> { - iterator = null - } - else -> {} - } - } - - /** - * A fragment of the workload. - */ - public data class Fragment(val duration: Long, val usage: Double) -} diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/consumer/SimWorkConsumer.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/consumer/SimWorkConsumer.kt deleted file mode 100644 index faa693c4..00000000 --- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/consumer/SimWorkConsumer.kt +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.resources.consumer - -import org.opendc.simulator.resources.SimResourceCommand -import org.opendc.simulator.resources.SimResourceConsumer -import org.opendc.simulator.resources.SimResourceContext - -/** - * A [SimResourceConsumer] that consumes the specified amount of work at the specified utilization. - */ -public class SimWorkConsumer( - private val work: Double, - private val utilization: Double -) : SimResourceConsumer { - - init { - require(work >= 0.0) { "Work must be positive" } - require(utilization > 0.0 && utilization <= 1.0) { "Utilization must be in (0, 1]" } - } - - private var isFirst = true - - override fun onNext(ctx: SimResourceContext): SimResourceCommand { - val limit = ctx.capacity * utilization - val work = if (isFirst) { - isFirst = false - work - } else { - ctx.remainingWork - } - return if (work > 0.0) { - SimResourceCommand.Consume(work, limit) - } else { - SimResourceCommand.Exit - } - } -} diff --git a/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceAggregatorMaxMinTest.kt b/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceAggregatorMaxMinTest.kt deleted file mode 100644 index 2b32300e..00000000 --- a/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceAggregatorMaxMinTest.kt +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.resources - -import io.mockk.every -import io.mockk.mockk -import io.mockk.verify -import kotlinx.coroutines.* -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertAll -import org.junit.jupiter.api.assertThrows -import org.opendc.simulator.core.runBlockingSimulation -import org.opendc.simulator.resources.consumer.SimSpeedConsumerAdapter -import org.opendc.simulator.resources.consumer.SimWorkConsumer - -/** - * Test suite for the [SimResourceAggregatorMaxMin] class. - */ -@OptIn(ExperimentalCoroutinesApi::class) -internal class SimResourceAggregatorMaxMinTest { - @Test - fun testSingleCapacity() = runBlockingSimulation { - val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock) - - val aggregator = SimResourceAggregatorMaxMin(scheduler) - val forwarder = SimResourceForwarder() - val sources = listOf( - forwarder, - SimResourceSource(1.0, scheduler) - ) - sources.forEach(aggregator::addInput) - - val consumer = SimWorkConsumer(1.0, 0.5) - val usage = mutableListOf<Double>() - val source = SimResourceSource(1.0, scheduler) - val adapter = SimSpeedConsumerAdapter(forwarder, usage::add) - source.startConsumer(adapter) - - try { - aggregator.output.consume(consumer) - yield() - - assertAll( - { assertEquals(1000, clock.millis()) }, - { assertEquals(listOf(0.0, 0.5, 0.0), usage) } - ) - } finally { - aggregator.output.close() - } - } - - @Test - fun testDoubleCapacity() = runBlockingSimulation { - val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock) - - val aggregator = SimResourceAggregatorMaxMin(scheduler) - val sources = listOf( - SimResourceSource(1.0, scheduler), - SimResourceSource(1.0, scheduler) - ) - sources.forEach(aggregator::addInput) - - val consumer = SimWorkConsumer(2.0, 1.0) - val usage = mutableListOf<Double>() - val adapter = SimSpeedConsumerAdapter(consumer, usage::add) - - try { - aggregator.output.consume(adapter) - yield() - assertAll( - { assertEquals(1000, clock.millis()) }, - { assertEquals(listOf(0.0, 2.0, 0.0), usage) } - ) - } finally { - aggregator.output.close() - } - } - - @Test - fun testOvercommit() = runBlockingSimulation { - val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock) - - val aggregator = SimResourceAggregatorMaxMin(scheduler) - val sources = listOf( - SimResourceSource(1.0, scheduler), - SimResourceSource(1.0, scheduler) - ) - sources.forEach(aggregator::addInput) - - val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true) - every { consumer.onNext(any()) } - .returns(SimResourceCommand.Consume(4.0, 4.0, 1000)) - .andThen(SimResourceCommand.Exit) - - try { - aggregator.output.consume(consumer) - yield() - assertEquals(1000, clock.millis()) - - verify(exactly = 2) { consumer.onNext(any()) } - } finally { - aggregator.output.close() - } - } - - @Test - fun testException() = runBlockingSimulation { - val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock) - - val aggregator = SimResourceAggregatorMaxMin(scheduler) - val sources = listOf( - SimResourceSource(1.0, scheduler), - SimResourceSource(1.0, scheduler) - ) - sources.forEach(aggregator::addInput) - - val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true) - every { consumer.onNext(any()) } - .returns(SimResourceCommand.Consume(1.0, 1.0)) - .andThenThrows(IllegalStateException("Test Exception")) - - try { - assertThrows<IllegalStateException> { aggregator.output.consume(consumer) } - yield() - assertEquals(SimResourceState.Pending, sources[0].state) - } finally { - aggregator.output.close() - } - } - - @Test - fun testAdjustCapacity() = runBlockingSimulation { - val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock) - - val aggregator = SimResourceAggregatorMaxMin(scheduler) - val sources = listOf( - SimResourceSource(1.0, scheduler), - SimResourceSource(1.0, scheduler) - ) - sources.forEach(aggregator::addInput) - - val consumer = SimWorkConsumer(4.0, 1.0) - try { - coroutineScope { - launch { aggregator.output.consume(consumer) } - delay(1000) - sources[0].capacity = 0.5 - } - yield() - assertEquals(2334, clock.millis()) - } finally { - aggregator.output.close() - } - } - - @Test - fun testFailOverCapacity() = runBlockingSimulation { - val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock) - - val aggregator = SimResourceAggregatorMaxMin(scheduler) - val sources = listOf( - SimResourceSource(1.0, scheduler), - SimResourceSource(1.0, scheduler) - ) - sources.forEach(aggregator::addInput) - - val consumer = SimWorkConsumer(1.0, 0.5) - try { - coroutineScope { - launch { aggregator.output.consume(consumer) } - delay(500) - sources[0].capacity = 0.5 - } - yield() - assertEquals(1000, clock.millis()) - } finally { - aggregator.output.close() - } - } -} diff --git a/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceCommandTest.kt b/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceCommandTest.kt deleted file mode 100644 index 02d456ff..00000000 --- a/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceCommandTest.kt +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.resources - -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertDoesNotThrow -import org.junit.jupiter.api.assertThrows - -/** - * Test suite for [SimResourceCommand]. - */ -class SimResourceCommandTest { - @Test - fun testZeroWork() { - assertThrows<IllegalArgumentException> { - SimResourceCommand.Consume(0.0, 1.0) - } - } - - @Test - fun testNegativeWork() { - assertThrows<IllegalArgumentException> { - SimResourceCommand.Consume(-1.0, 1.0) - } - } - - @Test - fun testZeroLimit() { - assertThrows<IllegalArgumentException> { - SimResourceCommand.Consume(1.0, 0.0) - } - } - - @Test - fun testNegativeLimit() { - assertThrows<IllegalArgumentException> { - SimResourceCommand.Consume(1.0, -1.0, 1) - } - } - - @Test - fun testConsumeCorrect() { - assertDoesNotThrow { - SimResourceCommand.Consume(1.0, 1.0) - } - } - - @Test - fun testIdleCorrect() { - assertDoesNotThrow { - SimResourceCommand.Idle(1) - } - } -} diff --git a/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceContextTest.kt b/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceContextTest.kt deleted file mode 100644 index 2e2d6588..00000000 --- a/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceContextTest.kt +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.resources - -import io.mockk.* -import kotlinx.coroutines.* -import org.junit.jupiter.api.* -import org.opendc.simulator.core.runBlockingSimulation - -/** - * A test suite for the [SimAbstractResourceContext] class. - */ -@OptIn(ExperimentalCoroutinesApi::class) -class SimResourceContextTest { - @Test - fun testFlushWithoutCommand() = runBlockingSimulation { - val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock) - val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true) - every { consumer.onNext(any()) } returns SimResourceCommand.Consume(10.0, 1.0) andThen SimResourceCommand.Exit - - val context = object : SimAbstractResourceContext(4200.0, scheduler, consumer) { - override fun onIdle(deadline: Long) {} - override fun onConsume(work: Double, limit: Double, deadline: Long) {} - override fun onFinish() {} - } - - context.flush(isIntermediate = false) - } - - @Test - fun testIntermediateFlush() = runBlockingSimulation { - val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock) - val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true) - every { consumer.onNext(any()) } returns SimResourceCommand.Consume(10.0, 1.0) andThen SimResourceCommand.Exit - - val context = spyk(object : SimAbstractResourceContext(4200.0, scheduler, consumer) { - override fun onIdle(deadline: Long) {} - override fun onFinish() {} - override fun onConsume(work: Double, limit: Double, deadline: Long) {} - }) - - context.start() - delay(1) // Delay 1 ms to prevent hitting the fast path - context.flush(isIntermediate = true) - - verify(exactly = 2) { context.onConsume(any(), any(), any()) } - } - - @Test - fun testIntermediateFlushIdle() = runBlockingSimulation { - val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock) - val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true) - every { consumer.onNext(any()) } returns SimResourceCommand.Idle(10) andThen SimResourceCommand.Exit - - val context = spyk(object : SimAbstractResourceContext(4200.0, scheduler, consumer) { - override fun onIdle(deadline: Long) {} - override fun onFinish() {} - override fun onConsume(work: Double, limit: Double, deadline: Long) {} - }) - - context.start() - delay(5) - context.flush(isIntermediate = true) - delay(5) - context.flush(isIntermediate = true) - - assertAll( - { verify(exactly = 2) { context.onIdle(any()) } }, - { verify(exactly = 1) { context.onFinish() } } - ) - } - - @Test - fun testDoubleStart() = runBlockingSimulation { - val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock) - val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true) - every { consumer.onNext(any()) } returns SimResourceCommand.Idle(10) andThen SimResourceCommand.Exit - - val context = object : SimAbstractResourceContext(4200.0, scheduler, consumer) { - override fun onIdle(deadline: Long) {} - override fun onFinish() {} - override fun onConsume(work: Double, limit: Double, deadline: Long) {} - } - - context.start() - assertThrows<IllegalStateException> { context.start() } - } - - @Test - fun testIdempodentCapacityChange() = runBlockingSimulation { - val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock) - val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true) - every { consumer.onNext(any()) } returns SimResourceCommand.Consume(10.0, 1.0) andThen SimResourceCommand.Exit - - val context = object : SimAbstractResourceContext(4200.0, scheduler, consumer) { - override fun onIdle(deadline: Long) {} - override fun onConsume(work: Double, limit: Double, deadline: Long) {} - override fun onFinish() {} - } - - context.start() - context.capacity = 4200.0 - - verify(exactly = 0) { consumer.onEvent(any(), SimResourceEvent.Capacity) } - } - - @Test - fun testFailureNoInfiniteLoop() = runBlockingSimulation { - val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock) - val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true) - every { consumer.onNext(any()) } returns SimResourceCommand.Exit - every { consumer.onEvent(any(), SimResourceEvent.Exit) } throws IllegalStateException("onEvent") - every { consumer.onFailure(any(), any()) } throws IllegalStateException("onFailure") - - val context = object : SimAbstractResourceContext(4200.0, scheduler, consumer) { - override fun onIdle(deadline: Long) {} - override fun onConsume(work: Double, limit: Double, deadline: Long) {} - override fun onFinish() {} - } - - context.start() - - delay(1) - - verify(exactly = 1) { consumer.onFailure(any(), any()) } - } -} diff --git a/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceSourceTest.kt b/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceSourceTest.kt deleted file mode 100644 index 5e86088d..00000000 --- a/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceSourceTest.kt +++ /dev/null @@ -1,341 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.resources - -import io.mockk.every -import io.mockk.mockk -import io.mockk.spyk -import io.mockk.verify -import kotlinx.coroutines.* -import org.junit.jupiter.api.* -import org.junit.jupiter.api.Assertions.assertEquals -import org.opendc.simulator.core.runBlockingSimulation -import org.opendc.simulator.resources.consumer.SimSpeedConsumerAdapter -import org.opendc.simulator.resources.consumer.SimWorkConsumer - -/** - * A test suite for the [SimResourceSource] class. - */ -@OptIn(ExperimentalCoroutinesApi::class) -class SimResourceSourceTest { - @Test - fun testSpeed() = runBlockingSimulation { - val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock) - val capacity = 4200.0 - val provider = SimResourceSource(capacity, scheduler) - - val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true) - every { consumer.onNext(any()) } - .returns(SimResourceCommand.Consume(1000 * capacity, capacity)) - .andThen(SimResourceCommand.Exit) - - try { - val res = mutableListOf<Double>() - val adapter = SimSpeedConsumerAdapter(consumer, res::add) - - provider.consume(adapter) - - assertEquals(listOf(0.0, capacity, 0.0), res) { "Speed is reported correctly" } - } finally { - provider.close() - } - } - - @Test - fun testAdjustCapacity() = runBlockingSimulation { - val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock) - val provider = SimResourceSource(1.0, scheduler) - - val consumer = spyk(SimWorkConsumer(2.0, 1.0)) - - try { - coroutineScope { - launch { provider.consume(consumer) } - delay(1000) - provider.capacity = 0.5 - } - assertEquals(3000, clock.millis()) - verify(exactly = 1) { consumer.onEvent(any(), SimResourceEvent.Capacity) } - } finally { - provider.close() - } - } - - @Test - fun testSpeedLimit() = runBlockingSimulation { - val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock) - val capacity = 4200.0 - val provider = SimResourceSource(capacity, scheduler) - - val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true) - every { consumer.onNext(any()) } - .returns(SimResourceCommand.Consume(1000 * capacity, 2 * capacity)) - .andThen(SimResourceCommand.Exit) - - try { - val res = mutableListOf<Double>() - val adapter = SimSpeedConsumerAdapter(consumer, res::add) - - provider.consume(adapter) - - assertEquals(listOf(0.0, capacity, 0.0), res) { "Speed is reported correctly" } - } finally { - provider.close() - } - } - - /** - * Test to see whether no infinite recursion occurs when interrupting during [SimResourceConsumer.onStart] or - * [SimResourceConsumer.onNext]. - */ - @Test - fun testIntermediateInterrupt() = runBlockingSimulation { - val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock) - val capacity = 4200.0 - val provider = SimResourceSource(capacity, scheduler) - - val consumer = object : SimResourceConsumer { - override fun onNext(ctx: SimResourceContext): SimResourceCommand { - return SimResourceCommand.Exit - } - - override fun onEvent(ctx: SimResourceContext, event: SimResourceEvent) { - ctx.interrupt() - } - } - - try { - provider.consume(consumer) - } finally { - provider.close() - } - } - - @Test - fun testInterrupt() = runBlockingSimulation { - val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock) - val capacity = 4200.0 - val provider = SimResourceSource(capacity, scheduler) - lateinit var resCtx: SimResourceContext - - val consumer = object : SimResourceConsumer { - var isFirst = true - - override fun onEvent(ctx: SimResourceContext, event: SimResourceEvent) { - when (event) { - SimResourceEvent.Start -> resCtx = ctx - else -> {} - } - } - - override fun onNext(ctx: SimResourceContext): SimResourceCommand { - assertEquals(0.0, ctx.remainingWork) - return if (isFirst) { - isFirst = false - SimResourceCommand.Consume(4.0, 1.0) - } else { - SimResourceCommand.Exit - } - } - } - - try { - launch { - yield() - resCtx.interrupt() - } - provider.consume(consumer) - - assertEquals(0, clock.millis()) - } finally { - provider.close() - } - } - - @Test - fun testFailure() = runBlockingSimulation { - val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock) - val capacity = 4200.0 - val provider = SimResourceSource(capacity, scheduler) - - val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true) - every { consumer.onEvent(any(), eq(SimResourceEvent.Start)) } - .throws(IllegalStateException()) - - try { - assertThrows<IllegalStateException> { - provider.consume(consumer) - } - } finally { - provider.close() - } - } - - @Test - fun testExceptionPropagationOnNext() = runBlockingSimulation { - val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock) - val capacity = 4200.0 - val provider = SimResourceSource(capacity, scheduler) - - val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true) - every { consumer.onNext(any()) } - .returns(SimResourceCommand.Consume(1.0, 1.0)) - .andThenThrows(IllegalStateException()) - - try { - assertThrows<IllegalStateException> { - provider.consume(consumer) - } - } finally { - provider.close() - } - } - - @Test - fun testConcurrentConsumption() = runBlockingSimulation { - val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock) - val capacity = 4200.0 - val provider = SimResourceSource(capacity, scheduler) - - val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true) - every { consumer.onNext(any()) } - .returns(SimResourceCommand.Consume(1.0, 1.0)) - .andThenThrows(IllegalStateException()) - - try { - assertThrows<IllegalStateException> { - coroutineScope { - launch { provider.consume(consumer) } - provider.consume(consumer) - } - } - } finally { - provider.close() - } - } - - @Test - fun testClosedConsumption() = runBlockingSimulation { - val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock) - val capacity = 4200.0 - val provider = SimResourceSource(capacity, scheduler) - - val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true) - every { consumer.onNext(any()) } - .returns(SimResourceCommand.Consume(1.0, 1.0)) - .andThenThrows(IllegalStateException()) - - try { - assertThrows<IllegalStateException> { - provider.close() - provider.consume(consumer) - } - } finally { - provider.close() - } - } - - @Test - fun testCloseDuringConsumption() = runBlockingSimulation { - val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock) - val capacity = 4200.0 - val provider = SimResourceSource(capacity, scheduler) - - val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true) - every { consumer.onNext(any()) } - .returns(SimResourceCommand.Consume(1.0, 1.0)) - .andThenThrows(IllegalStateException()) - - try { - launch { provider.consume(consumer) } - delay(500) - provider.close() - - assertEquals(500, clock.millis()) - } finally { - provider.close() - } - } - - @Test - fun testIdle() = runBlockingSimulation { - val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock) - val capacity = 4200.0 - val provider = SimResourceSource(capacity, scheduler) - - val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true) - every { consumer.onNext(any()) } - .returns(SimResourceCommand.Idle(clock.millis() + 500)) - .andThen(SimResourceCommand.Exit) - - try { - provider.consume(consumer) - - assertEquals(500, clock.millis()) - } finally { - provider.close() - } - } - - @Test - fun testInfiniteSleep() { - assertThrows<IllegalStateException> { - runBlockingSimulation { - val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock) - val capacity = 4200.0 - val provider = SimResourceSource(capacity, scheduler) - - val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true) - every { consumer.onNext(any()) } - .returns(SimResourceCommand.Idle()) - .andThenThrows(IllegalStateException()) - - try { - provider.consume(consumer) - } finally { - provider.close() - } - } - } - } - - @Test - fun testIncorrectDeadline() = runBlockingSimulation { - val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock) - val capacity = 4200.0 - val provider = SimResourceSource(capacity, scheduler) - - val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true) - every { consumer.onNext(any()) } - .returns(SimResourceCommand.Idle(2)) - .andThen(SimResourceCommand.Exit) - - try { - delay(10) - - assertThrows<IllegalArgumentException> { provider.consume(consumer) } - } finally { - provider.close() - } - } -} diff --git a/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceSwitchExclusiveTest.kt b/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceSwitchExclusiveTest.kt deleted file mode 100644 index 32b6d8ad..00000000 --- a/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceSwitchExclusiveTest.kt +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.resources - -import io.mockk.every -import io.mockk.mockk -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.yield -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertAll -import org.junit.jupiter.api.assertThrows -import org.opendc.simulator.core.runBlockingSimulation -import org.opendc.simulator.resources.consumer.SimSpeedConsumerAdapter -import org.opendc.simulator.resources.consumer.SimTraceConsumer - -/** - * Test suite for the [SimResourceSwitchExclusive] class. - */ -@OptIn(ExperimentalCoroutinesApi::class) -internal class SimResourceSwitchExclusiveTest { - /** - * Test a trace workload. - */ - @Test - fun testTrace() = runBlockingSimulation { - val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock) - - val speed = mutableListOf<Double>() - - val duration = 5 * 60L - val workload = - SimTraceConsumer( - sequenceOf( - SimTraceConsumer.Fragment(duration * 1000, 28.0), - SimTraceConsumer.Fragment(duration * 1000, 3500.0), - SimTraceConsumer.Fragment(duration * 1000, 0.0), - SimTraceConsumer.Fragment(duration * 1000, 183.0) - ), - ) - - val switch = SimResourceSwitchExclusive() - val source = SimResourceSource(3200.0, scheduler) - val forwarder = SimResourceForwarder() - val adapter = SimSpeedConsumerAdapter(forwarder, speed::add) - source.startConsumer(adapter) - switch.addInput(forwarder) - - val provider = switch.addOutput(3200.0) - - try { - provider.consume(workload) - yield() - } finally { - provider.close() - } - - assertAll( - { assertEquals(listOf(0.0, 28.0, 3200.0, 0.0, 183.0, 0.0), speed) { "Correct speed" } }, - { assertEquals(5 * 60L * 4000, clock.millis()) { "Took enough time" } } - ) - } - - /** - * Test runtime workload on hypervisor. - */ - @Test - fun testRuntimeWorkload() = runBlockingSimulation { - val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock) - - val duration = 5 * 60L * 1000 - val workload = mockk<SimResourceConsumer>(relaxUnitFun = true) - every { workload.onNext(any()) } returns SimResourceCommand.Consume(duration / 1000.0, 1.0) andThen SimResourceCommand.Exit - - val switch = SimResourceSwitchExclusive() - val source = SimResourceSource(3200.0, scheduler) - - switch.addInput(source) - - val provider = switch.addOutput(3200.0) - - try { - provider.consume(workload) - yield() - } finally { - provider.close() - } - assertEquals(duration, clock.millis()) { "Took enough time" } - } - - /** - * Test two workloads running sequentially. - */ - @Test - fun testTwoWorkloads() = runBlockingSimulation { - val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock) - - val duration = 5 * 60L * 1000 - val workload = object : SimResourceConsumer { - var isFirst = true - - override fun onEvent(ctx: SimResourceContext, event: SimResourceEvent) { - when (event) { - SimResourceEvent.Start -> isFirst = true - else -> {} - } - } - - override fun onNext(ctx: SimResourceContext): SimResourceCommand { - return if (isFirst) { - isFirst = false - SimResourceCommand.Consume(duration / 1000.0, 1.0) - } else { - SimResourceCommand.Exit - } - } - } - - val switch = SimResourceSwitchExclusive() - val source = SimResourceSource(3200.0, scheduler) - - switch.addInput(source) - - val provider = switch.addOutput(3200.0) - - try { - provider.consume(workload) - yield() - provider.consume(workload) - } finally { - provider.close() - } - assertEquals(duration * 2, clock.millis()) { "Took enough time" } - } - - /** - * Test concurrent workloads on the machine. - */ - @Test - fun testConcurrentWorkloadFails() = runBlockingSimulation { - val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock) - - val duration = 5 * 60L * 1000 - val workload = mockk<SimResourceConsumer>(relaxUnitFun = true) - every { workload.onNext(any()) } returns SimResourceCommand.Consume(duration / 1000.0, 1.0) andThen SimResourceCommand.Exit - - val switch = SimResourceSwitchExclusive() - val source = SimResourceSource(3200.0, scheduler) - - switch.addInput(source) - - switch.addOutput(3200.0) - assertThrows<IllegalStateException> { switch.addOutput(3200.0) } - } -} diff --git a/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceSwitchMaxMinTest.kt b/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceSwitchMaxMinTest.kt deleted file mode 100644 index e7dec172..00000000 --- a/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceSwitchMaxMinTest.kt +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.resources - -import io.mockk.every -import io.mockk.mockk -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.launch -import kotlinx.coroutines.yield -import org.junit.jupiter.api.* -import org.junit.jupiter.api.Assertions.assertEquals -import org.opendc.simulator.core.runBlockingSimulation -import org.opendc.simulator.resources.consumer.SimTraceConsumer - -/** - * Test suite for the [SimResourceSwitch] implementations - */ -@OptIn(ExperimentalCoroutinesApi::class) -internal class SimResourceSwitchMaxMinTest { - @Test - fun testSmoke() = runBlockingSimulation { - val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock) - val switch = SimResourceSwitchMaxMin(scheduler) - - val sources = List(2) { SimResourceSource(2000.0, scheduler) } - sources.forEach { switch.addInput(it) } - - val provider = switch.addOutput(1000.0) - - val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true) - every { consumer.onNext(any()) } returns SimResourceCommand.Consume(1.0, 1.0) andThen SimResourceCommand.Exit - - try { - provider.consume(consumer) - yield() - } finally { - switch.close() - } - } - - /** - * Test overcommitting of resources via the hypervisor with a single VM. - */ - @Test - fun testOvercommittedSingle() = runBlockingSimulation { - val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock) - - val listener = object : SimResourceSwitchMaxMin.Listener { - var totalRequestedWork = 0L - var totalGrantedWork = 0L - var totalOvercommittedWork = 0L - - override fun onSliceFinish( - switch: SimResourceSwitchMaxMin, - requestedWork: Long, - grantedWork: Long, - overcommittedWork: Long, - interferedWork: Long, - cpuUsage: Double, - cpuDemand: Double - ) { - totalRequestedWork += requestedWork - totalGrantedWork += grantedWork - totalOvercommittedWork += overcommittedWork - } - } - - val duration = 5 * 60L - val workload = - SimTraceConsumer( - sequenceOf( - SimTraceConsumer.Fragment(duration * 1000, 28.0), - SimTraceConsumer.Fragment(duration * 1000, 3500.0), - SimTraceConsumer.Fragment(duration * 1000, 0.0), - SimTraceConsumer.Fragment(duration * 1000, 183.0) - ), - ) - - val switch = SimResourceSwitchMaxMin(scheduler, listener) - val provider = switch.addOutput(3200.0) - - try { - switch.addInput(SimResourceSource(3200.0, scheduler)) - provider.consume(workload) - yield() - } finally { - switch.close() - } - - assertAll( - { assertEquals(1113300, listener.totalRequestedWork, "Requested Burst does not match") }, - { assertEquals(1023300, listener.totalGrantedWork, "Granted Burst does not match") }, - { assertEquals(90000, listener.totalOvercommittedWork, "Overcommissioned Burst does not match") }, - { assertEquals(1200000, clock.millis()) } - ) - } - - /** - * Test overcommitting of resources via the hypervisor with two VMs. - */ - @Test - fun testOvercommittedDual() = runBlockingSimulation { - val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock) - - val listener = object : SimResourceSwitchMaxMin.Listener { - var totalRequestedWork = 0L - var totalGrantedWork = 0L - var totalOvercommittedWork = 0L - - override fun onSliceFinish( - switch: SimResourceSwitchMaxMin, - requestedWork: Long, - grantedWork: Long, - overcommittedWork: Long, - interferedWork: Long, - cpuUsage: Double, - cpuDemand: Double - ) { - totalRequestedWork += requestedWork - totalGrantedWork += grantedWork - totalOvercommittedWork += overcommittedWork - } - } - - val duration = 5 * 60L - val workloadA = - SimTraceConsumer( - sequenceOf( - SimTraceConsumer.Fragment(duration * 1000, 28.0), - SimTraceConsumer.Fragment(duration * 1000, 3500.0), - SimTraceConsumer.Fragment(duration * 1000, 0.0), - SimTraceConsumer.Fragment(duration * 1000, 183.0) - ), - ) - val workloadB = - SimTraceConsumer( - sequenceOf( - SimTraceConsumer.Fragment(duration * 1000, 28.0), - SimTraceConsumer.Fragment(duration * 1000, 3100.0), - SimTraceConsumer.Fragment(duration * 1000, 0.0), - SimTraceConsumer.Fragment(duration * 1000, 73.0) - ) - ) - - val switch = SimResourceSwitchMaxMin(scheduler, listener) - val providerA = switch.addOutput(3200.0) - val providerB = switch.addOutput(3200.0) - - try { - switch.addInput(SimResourceSource(3200.0, scheduler)) - - coroutineScope { - launch { providerA.consume(workloadA) } - providerB.consume(workloadB) - } - - yield() - } finally { - switch.close() - } - assertAll( - { assertEquals(2082000, listener.totalRequestedWork, "Requested Burst does not match") }, - { assertEquals(1062000, listener.totalGrantedWork, "Granted Burst does not match") }, - { assertEquals(1020000, listener.totalOvercommittedWork, "Overcommissioned Burst does not match") }, - { assertEquals(1200000, clock.millis()) } - ) - } -} diff --git a/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceTransformerTest.kt b/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceTransformerTest.kt deleted file mode 100644 index 880e1755..00000000 --- a/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceTransformerTest.kt +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.resources - -import io.mockk.every -import io.mockk.mockk -import io.mockk.spyk -import io.mockk.verify -import kotlinx.coroutines.* -import org.junit.jupiter.api.Assertions.* -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows -import org.opendc.simulator.core.runBlockingSimulation -import org.opendc.simulator.resources.consumer.SimWorkConsumer - -/** - * A test suite for the [SimResourceTransformer] class. - */ -@OptIn(ExperimentalCoroutinesApi::class) -internal class SimResourceTransformerTest { - @Test - fun testExitImmediately() = runBlockingSimulation { - val forwarder = SimResourceForwarder() - val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock) - val source = SimResourceSource(2000.0, scheduler) - - launch { - source.consume(forwarder) - source.close() - } - - forwarder.consume(object : SimResourceConsumer { - override fun onNext(ctx: SimResourceContext): SimResourceCommand { - return SimResourceCommand.Exit - } - }) - - forwarder.close() - } - - @Test - fun testExit() = runBlockingSimulation { - val forwarder = SimResourceForwarder() - val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock) - val source = SimResourceSource(2000.0, scheduler) - - launch { - source.consume(forwarder) - source.close() - } - - forwarder.consume(object : SimResourceConsumer { - var isFirst = true - - override fun onNext(ctx: SimResourceContext): SimResourceCommand { - return if (isFirst) { - isFirst = false - SimResourceCommand.Consume(10.0, 1.0) - } else { - SimResourceCommand.Exit - } - } - }) - - forwarder.close() - } - - @Test - fun testState() = runBlockingSimulation { - val forwarder = SimResourceForwarder() - val consumer = object : SimResourceConsumer { - override fun onNext(ctx: SimResourceContext): SimResourceCommand = SimResourceCommand.Exit - } - - assertEquals(SimResourceState.Pending, forwarder.state) - - forwarder.startConsumer(consumer) - assertEquals(SimResourceState.Active, forwarder.state) - - assertThrows<IllegalStateException> { forwarder.startConsumer(consumer) } - - forwarder.cancel() - assertEquals(SimResourceState.Pending, forwarder.state) - - forwarder.close() - assertEquals(SimResourceState.Stopped, forwarder.state) - } - - @Test - fun testCancelPendingDelegate() = runBlockingSimulation { - val forwarder = SimResourceForwarder() - - val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true) - every { consumer.onNext(any()) } returns SimResourceCommand.Exit - - forwarder.startConsumer(consumer) - forwarder.cancel() - - verify(exactly = 0) { consumer.onEvent(any(), SimResourceEvent.Exit) } - } - - @Test - fun testCancelStartedDelegate() = runBlockingSimulation { - val forwarder = SimResourceForwarder() - val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock) - val source = SimResourceSource(2000.0, scheduler) - - val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true) - every { consumer.onNext(any()) } returns SimResourceCommand.Idle(10) - - source.startConsumer(forwarder) - yield() - forwarder.startConsumer(consumer) - yield() - forwarder.cancel() - - verify(exactly = 1) { consumer.onEvent(any(), SimResourceEvent.Start) } - verify(exactly = 1) { consumer.onEvent(any(), SimResourceEvent.Exit) } - } - - @Test - fun testCancelPropagation() = runBlockingSimulation { - val forwarder = SimResourceForwarder() - val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock) - val source = SimResourceSource(2000.0, scheduler) - - val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true) - every { consumer.onNext(any()) } returns SimResourceCommand.Idle(10) - - source.startConsumer(forwarder) - yield() - forwarder.startConsumer(consumer) - yield() - source.cancel() - - verify(exactly = 1) { consumer.onEvent(any(), SimResourceEvent.Start) } - verify(exactly = 1) { consumer.onEvent(any(), SimResourceEvent.Exit) } - } - - @Test - fun testExitPropagation() = runBlockingSimulation { - val forwarder = SimResourceForwarder(isCoupled = true) - val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock) - val source = SimResourceSource(2000.0, scheduler) - - val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true) - every { consumer.onNext(any()) } returns SimResourceCommand.Exit - - source.startConsumer(forwarder) - forwarder.consume(consumer) - yield() - - assertEquals(SimResourceState.Pending, source.state) - } - - @Test - fun testAdjustCapacity() = runBlockingSimulation { - val forwarder = SimResourceForwarder() - val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock) - val source = SimResourceSource(1.0, scheduler) - - val consumer = spyk(SimWorkConsumer(2.0, 1.0)) - source.startConsumer(forwarder) - - coroutineScope { - launch { forwarder.consume(consumer) } - delay(1000) - source.capacity = 0.5 - } - - assertEquals(3000, clock.millis()) - verify(exactly = 1) { consumer.onEvent(any(), SimResourceEvent.Capacity) } - } - - @Test - fun testTransformExit() = runBlockingSimulation { - val forwarder = SimResourceTransformer { _, _ -> SimResourceCommand.Exit } - val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock) - val source = SimResourceSource(1.0, scheduler) - - val consumer = spyk(SimWorkConsumer(2.0, 1.0)) - source.startConsumer(forwarder) - forwarder.consume(consumer) - - assertEquals(0, clock.millis()) - verify(exactly = 1) { consumer.onNext(any()) } - } -} diff --git a/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimWorkConsumerTest.kt b/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimWorkConsumerTest.kt deleted file mode 100644 index ac8b5814..00000000 --- a/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimWorkConsumerTest.kt +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.resources - -import kotlinx.coroutines.ExperimentalCoroutinesApi -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test -import org.opendc.simulator.core.runBlockingSimulation -import org.opendc.simulator.resources.consumer.SimWorkConsumer - -/** - * A test suite for the [SimWorkConsumer] class. - */ -@OptIn(ExperimentalCoroutinesApi::class) -internal class SimWorkConsumerTest { - @Test - fun testSmoke() = runBlockingSimulation { - val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock) - val provider = SimResourceSource(1.0, scheduler) - - val consumer = SimWorkConsumer(1.0, 1.0) - - try { - provider.consume(consumer) - assertEquals(1000, clock.millis()) - } finally { - provider.close() - } - } - - @Test - fun testUtilization() = runBlockingSimulation { - val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock) - val provider = SimResourceSource(1.0, scheduler) - - val consumer = SimWorkConsumer(1.0, 0.5) - - try { - provider.consume(consumer) - assertEquals(2000, clock.millis()) - } finally { - provider.close() - } - } -} diff --git a/opendc-telemetry/opendc-telemetry-compute/build.gradle.kts b/opendc-telemetry/opendc-telemetry-compute/build.gradle.kts new file mode 100644 index 00000000..cd8cb57a --- /dev/null +++ b/opendc-telemetry/opendc-telemetry-compute/build.gradle.kts @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +description = "Telemetry for OpenDC Compute" + +/* Build configuration */ +plugins { + `kotlin-library-conventions` +} + +dependencies { + api(platform(projects.opendcPlatform)) + api(projects.opendcTelemetry.opendcTelemetrySdk) + + implementation(libs.opentelemetry.semconv) + implementation(libs.kotlin.logging) +} diff --git a/opendc-telemetry/opendc-telemetry-compute/src/main/kotlin/org/opendc/telemetry/compute/ComputeMetricAggregator.kt b/opendc-telemetry/opendc-telemetry-compute/src/main/kotlin/org/opendc/telemetry/compute/ComputeMetricAggregator.kt new file mode 100644 index 00000000..738ec38b --- /dev/null +++ b/opendc-telemetry/opendc-telemetry-compute/src/main/kotlin/org/opendc/telemetry/compute/ComputeMetricAggregator.kt @@ -0,0 +1,486 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.telemetry.compute + +import io.opentelemetry.api.common.AttributeKey +import io.opentelemetry.api.common.Attributes +import io.opentelemetry.sdk.metrics.data.MetricData +import io.opentelemetry.sdk.metrics.data.PointData +import io.opentelemetry.sdk.resources.Resource +import io.opentelemetry.semconv.resource.attributes.ResourceAttributes +import org.opendc.telemetry.compute.table.* +import java.time.Instant +import kotlin.math.roundToLong + +/** + * Helper class responsible for aggregating [MetricData] into [ServiceData], [HostData] and [ServerData]. + */ +public class ComputeMetricAggregator { + private val _service = ServiceAggregator() + private val _hosts = mutableMapOf<String, HostAggregator>() + private val _servers = mutableMapOf<String, ServerAggregator>() + + /** + * Process the specified [metrics] for this cycle. + */ + public fun process(metrics: Collection<MetricData>) { + val service = _service + val hosts = _hosts + val servers = _servers + + for (metric in metrics) { + val resource = metric.resource + + when (metric.name) { + // ComputeService + "scheduler.hosts" -> { + for (point in metric.longSumData.points) { + // Record the timestamp for the service + service.recordTimestamp(point) + + when (point.attributes[STATE_KEY]) { + "up" -> service.hostsUp = point.value.toInt() + "down" -> service.hostsDown = point.value.toInt() + } + } + } + "scheduler.servers" -> { + for (point in metric.longSumData.points) { + when (point.attributes[STATE_KEY]) { + "pending" -> service.serversPending = point.value.toInt() + "active" -> service.serversActive = point.value.toInt() + } + } + } + "scheduler.attempts" -> { + for (point in metric.longSumData.points) { + when (point.attributes[RESULT_KEY]) { + "success" -> service.attemptsSuccess = point.value.toInt() + "failure" -> service.attemptsFailure = point.value.toInt() + "error" -> service.attemptsError = point.value.toInt() + } + } + } + "scheduler.latency" -> { + for (point in metric.doubleHistogramData.points) { + val server = getServer(servers, point) ?: continue + server.schedulingLatency = (point.sum / point.count).roundToLong() + } + } + + // SimHost + "system.guests" -> { + val agg = getHost(hosts, resource) ?: continue + + for (point in metric.longSumData.points) { + when (point.attributes[STATE_KEY]) { + "terminated" -> agg.guestsTerminated = point.value.toInt() + "running" -> agg.guestsRunning = point.value.toInt() + "error" -> agg.guestsRunning = point.value.toInt() + "invalid" -> agg.guestsInvalid = point.value.toInt() + } + } + } + "system.cpu.limit" -> { + val agg = getHost(hosts, resource) ?: continue + + for (point in metric.doubleGaugeData.points) { + val server = getServer(servers, point) + + if (server != null) { + server.cpuLimit = point.value + server.host = agg.host + } else { + agg.cpuLimit = point.value + } + } + } + "system.cpu.usage" -> { + val agg = getHost(hosts, resource) ?: continue + agg.cpuUsage = metric.doubleGaugeData.points.first().value + } + "system.cpu.demand" -> { + val agg = getHost(hosts, resource) ?: continue + agg.cpuDemand = metric.doubleGaugeData.points.first().value + } + "system.cpu.utilization" -> { + val agg = getHost(hosts, resource) ?: continue + agg.cpuUtilization = metric.doubleGaugeData.points.first().value + } + "system.cpu.time" -> { + val agg = getHost(hosts, resource) ?: continue + + for (point in metric.longSumData.points) { + val server = getServer(servers, point) + val state = point.attributes[STATE_KEY] + if (server != null) { + when (state) { + "active" -> server.cpuActiveTime = point.value + "idle" -> server.cpuIdleTime = point.value + "steal" -> server.cpuStealTime = point.value + "lost" -> server.cpuLostTime = point.value + } + server.host = agg.host + } else { + when (state) { + "active" -> agg.cpuActiveTime = point.value + "idle" -> agg.cpuIdleTime = point.value + "steal" -> agg.cpuStealTime = point.value + "lost" -> agg.cpuLostTime = point.value + } + } + } + } + "system.power.usage" -> { + val agg = getHost(hosts, resource) ?: continue + agg.powerUsage = metric.doubleGaugeData.points.first().value + } + "system.power.total" -> { + val agg = getHost(hosts, resource) ?: continue + agg.powerTotal = metric.doubleSumData.points.first().value + } + "system.time" -> { + val agg = getHost(hosts, resource) ?: continue + + for (point in metric.longSumData.points) { + val server = getServer(servers, point) + + if (server != null) { + server.recordTimestamp(point) + + when (point.attributes[STATE_KEY]) { + "up" -> server.uptime = point.value + "down" -> server.downtime = point.value + } + server.host = agg.host + } else { + agg.recordTimestamp(point) + + when (point.attributes[STATE_KEY]) { + "up" -> agg.uptime = point.value + "down" -> agg.downtime = point.value + } + } + } + } + "system.time.boot" -> { + val agg = getHost(hosts, resource) ?: continue + + for (point in metric.longGaugeData.points) { + val server = getServer(servers, point) + + if (server != null) { + server.bootTime = point.value + server.host = agg.host + } else { + agg.bootTime = point.value + } + } + } + } + } + } + + /** + * Collect the data via the [monitor]. + */ + public fun collect(monitor: ComputeMonitor) { + monitor.record(_service.collect()) + + for (host in _hosts.values) { + monitor.record(host.collect()) + } + + for (server in _servers.values) { + monitor.record(server.collect()) + } + } + + /** + * Obtain the [HostAggregator] for the specified [resource]. + */ + private fun getHost(hosts: MutableMap<String, HostAggregator>, resource: Resource): HostAggregator? { + val id = resource.attributes[HOST_ID] + return if (id != null) { + hosts.computeIfAbsent(id) { HostAggregator(resource) } + } else { + null + } + } + + /** + * Obtain the [ServerAggregator] for the specified [point]. + */ + private fun getServer(servers: MutableMap<String, ServerAggregator>, point: PointData): ServerAggregator? { + val id = point.attributes[ResourceAttributes.HOST_ID] + return if (id != null) { + servers.computeIfAbsent(id) { ServerAggregator(point.attributes) } + } else { + null + } + } + + /** + * An aggregator for service metrics before they are reported. + */ + internal class ServiceAggregator { + private var timestamp = Long.MIN_VALUE + + @JvmField var hostsUp = 0 + @JvmField var hostsDown = 0 + + @JvmField var serversPending = 0 + @JvmField var serversActive = 0 + + @JvmField var attemptsSuccess = 0 + @JvmField var attemptsFailure = 0 + @JvmField var attemptsError = 0 + + /** + * Finish the aggregation for this cycle. + */ + fun collect(): ServiceData { + val now = Instant.ofEpochMilli(timestamp) + return toServiceData(now) + } + + /** + * Convert the aggregator state to an immutable [ServiceData]. + */ + private fun toServiceData(now: Instant): ServiceData { + return ServiceData(now, hostsUp, hostsDown, serversPending, serversActive, attemptsSuccess, attemptsFailure, attemptsError) + } + + /** + * Record the timestamp of a [point] for this aggregator. + */ + fun recordTimestamp(point: PointData) { + timestamp = point.epochNanos / 1_000_000L // ns to ms + } + } + + /** + * An aggregator for host metrics before they are reported. + */ + internal class HostAggregator(resource: Resource) { + /** + * The static information about this host. + */ + val host = HostInfo( + resource.attributes[HOST_ID]!!, + resource.attributes[HOST_NAME] ?: "", + resource.attributes[HOST_ARCH] ?: "", + resource.attributes[HOST_NCPUS]?.toInt() ?: 0, + resource.attributes[HOST_MEM_CAPACITY] ?: 0, + ) + + private var timestamp = Long.MIN_VALUE + + @JvmField var guestsTerminated = 0 + @JvmField var guestsRunning = 0 + @JvmField var guestsError = 0 + @JvmField var guestsInvalid = 0 + + @JvmField var cpuLimit = 0.0 + @JvmField var cpuUsage = 0.0 + @JvmField var cpuDemand = 0.0 + @JvmField var cpuUtilization = 0.0 + + @JvmField var cpuActiveTime = 0L + @JvmField var cpuIdleTime = 0L + @JvmField var cpuStealTime = 0L + @JvmField var cpuLostTime = 0L + private var previousCpuActiveTime = 0L + private var previousCpuIdleTime = 0L + private var previousCpuStealTime = 0L + private var previousCpuLostTime = 0L + + @JvmField var powerUsage = 0.0 + @JvmField var powerTotal = 0.0 + private var previousPowerTotal = 0.0 + + @JvmField var uptime = 0L + private var previousUptime = 0L + @JvmField var downtime = 0L + private var previousDowntime = 0L + @JvmField var bootTime = Long.MIN_VALUE + + /** + * Finish the aggregation for this cycle. + */ + fun collect(): HostData { + val now = Instant.ofEpochMilli(timestamp) + val data = toHostData(now) + + // Reset intermediate state for next aggregation + previousCpuActiveTime = cpuActiveTime + previousCpuIdleTime = cpuIdleTime + previousCpuStealTime = cpuStealTime + previousCpuLostTime = cpuLostTime + previousPowerTotal = powerTotal + previousUptime = uptime + previousDowntime = downtime + + guestsTerminated = 0 + guestsRunning = 0 + guestsError = 0 + guestsInvalid = 0 + + cpuLimit = 0.0 + cpuUsage = 0.0 + cpuDemand = 0.0 + cpuUtilization = 0.0 + + powerUsage = 0.0 + + return data + } + + /** + * Convert the aggregator state to an immutable [HostData] instance. + */ + private fun toHostData(now: Instant): HostData { + return HostData( + now, + host, + guestsTerminated, + guestsRunning, + guestsError, + guestsInvalid, + cpuLimit, + cpuUsage, + cpuDemand, + cpuUtilization, + cpuActiveTime - previousCpuActiveTime, + cpuIdleTime - previousCpuIdleTime, + cpuStealTime - previousCpuStealTime, + cpuLostTime - previousCpuLostTime, + powerUsage, + powerTotal - previousPowerTotal, + uptime - previousUptime, + downtime - previousDowntime, + if (bootTime != Long.MIN_VALUE) Instant.ofEpochMilli(bootTime) else null + ) + } + + /** + * Record the timestamp of a [point] for this aggregator. + */ + fun recordTimestamp(point: PointData) { + timestamp = point.epochNanos / 1_000_000L // ns to ms + } + } + + /** + * An aggregator for server metrics before they are reported. + */ + internal class ServerAggregator(attributes: Attributes) { + /** + * The static information about this server. + */ + val server = ServerInfo( + attributes[ResourceAttributes.HOST_ID]!!, + attributes[ResourceAttributes.HOST_NAME]!!, + attributes[ResourceAttributes.HOST_TYPE]!!, + attributes[ResourceAttributes.HOST_ARCH]!!, + attributes[ResourceAttributes.HOST_IMAGE_ID]!!, + attributes[ResourceAttributes.HOST_IMAGE_NAME]!!, + attributes[AttributeKey.longKey("host.num_cpus")]!!.toInt(), + attributes[AttributeKey.longKey("host.mem_capacity")]!!, + ) + + /** + * The [HostInfo] of the host on which the server is hosted. + */ + @JvmField var host: HostInfo? = null + + private var timestamp = Long.MIN_VALUE + @JvmField var uptime: Long = 0 + private var previousUptime = 0L + @JvmField var downtime: Long = 0 + private var previousDowntime = 0L + @JvmField var bootTime: Long = 0 + @JvmField var schedulingLatency = 0L + @JvmField var cpuLimit = 0.0 + @JvmField var cpuActiveTime = 0L + @JvmField var cpuIdleTime = 0L + @JvmField var cpuStealTime = 0L + @JvmField var cpuLostTime = 0L + private var previousCpuActiveTime = 0L + private var previousCpuIdleTime = 0L + private var previousCpuStealTime = 0L + private var previousCpuLostTime = 0L + + /** + * Finish the aggregation for this cycle. + */ + fun collect(): ServerData { + val now = Instant.ofEpochMilli(timestamp) + val data = toServerData(now) + + previousUptime = uptime + previousDowntime = downtime + previousCpuActiveTime = cpuActiveTime + previousCpuIdleTime = cpuIdleTime + previousCpuStealTime = cpuStealTime + previousCpuLostTime = cpuLostTime + + host = null + cpuLimit = 0.0 + + return data + } + + /** + * Convert the aggregator state into an immutable [ServerData]. + */ + private fun toServerData(now: Instant): ServerData { + return ServerData( + now, + server, + host, + uptime - previousUptime, + downtime - previousDowntime, + if (bootTime != Long.MIN_VALUE) Instant.ofEpochMilli(bootTime) else null, + schedulingLatency, + cpuLimit, + cpuActiveTime - previousCpuActiveTime, + cpuIdleTime - previousCpuIdleTime, + cpuStealTime - previousCpuStealTime, + cpuLostTime - previousCpuLostTime + ) + } + + /** + * Record the timestamp of a [point] for this aggregator. + */ + fun recordTimestamp(point: PointData) { + timestamp = point.epochNanos / 1_000_000L // ns to ms + } + } + + private companion object { + private val STATE_KEY = AttributeKey.stringKey("state") + private val RESULT_KEY = AttributeKey.stringKey("result") + } +} diff --git a/opendc-telemetry/opendc-telemetry-compute/src/main/kotlin/org/opendc/telemetry/compute/ComputeMetricExporter.kt b/opendc-telemetry/opendc-telemetry-compute/src/main/kotlin/org/opendc/telemetry/compute/ComputeMetricExporter.kt new file mode 100644 index 00000000..3ab6c7b2 --- /dev/null +++ b/opendc-telemetry/opendc-telemetry-compute/src/main/kotlin/org/opendc/telemetry/compute/ComputeMetricExporter.kt @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.telemetry.compute + +import io.opentelemetry.sdk.common.CompletableResultCode +import io.opentelemetry.sdk.metrics.data.* +import io.opentelemetry.sdk.metrics.export.MetricExporter +import mu.KotlinLogging + +/** + * A [MetricExporter] that redirects data to a [ComputeMonitor] implementation. + */ +public abstract class ComputeMetricExporter : MetricExporter, ComputeMonitor { + /** + * The logging instance for this exporter. + */ + private val logger = KotlinLogging.logger {} + + /** + * A [ComputeMetricAggregator] that actually performs the aggregation. + */ + private val agg = ComputeMetricAggregator() + + override fun export(metrics: Collection<MetricData>): CompletableResultCode { + return try { + agg.process(metrics) + agg.collect(this) + + CompletableResultCode.ofSuccess() + } catch (e: Throwable) { + logger.warn(e) { "Failed to export results" } + CompletableResultCode.ofFailure() + } + } + + override fun flush(): CompletableResultCode = CompletableResultCode.ofSuccess() + + override fun shutdown(): CompletableResultCode = CompletableResultCode.ofSuccess() +} diff --git a/opendc-telemetry/opendc-telemetry-compute/src/main/kotlin/org/opendc/telemetry/compute/ComputeMonitor.kt b/opendc-telemetry/opendc-telemetry-compute/src/main/kotlin/org/opendc/telemetry/compute/ComputeMonitor.kt new file mode 100644 index 00000000..d51bcab4 --- /dev/null +++ b/opendc-telemetry/opendc-telemetry-compute/src/main/kotlin/org/opendc/telemetry/compute/ComputeMonitor.kt @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.telemetry.compute + +import org.opendc.telemetry.compute.table.HostData +import org.opendc.telemetry.compute.table.ServerData +import org.opendc.telemetry.compute.table.ServiceData + +/** + * A monitor that tracks the metrics and events of the OpenDC Compute service. + */ +public interface ComputeMonitor { + /** + * Record the specified [data]. + */ + public fun record(data: ServerData) {} + + /** + * Record the specified [data]. + */ + public fun record(data: HostData) {} + + /** + * Record the specified [data]. + */ + public fun record(data: ServiceData) {} +} diff --git a/opendc-telemetry/opendc-telemetry-compute/src/main/kotlin/org/opendc/telemetry/compute/Helpers.kt b/opendc-telemetry/opendc-telemetry-compute/src/main/kotlin/org/opendc/telemetry/compute/Helpers.kt new file mode 100644 index 00000000..ce89061b --- /dev/null +++ b/opendc-telemetry/opendc-telemetry-compute/src/main/kotlin/org/opendc/telemetry/compute/Helpers.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.telemetry.compute + +import io.opentelemetry.sdk.metrics.export.MetricProducer +import org.opendc.telemetry.compute.table.ServiceData + +/** + * Collect the metrics of the compute service. + */ +public fun collectServiceMetrics(metricProducer: MetricProducer): ServiceData { + lateinit var serviceData: ServiceData + val agg = ComputeMetricAggregator() + val monitor = object : ComputeMonitor { + override fun record(data: ServiceData) { + serviceData = data + } + } + + agg.process(metricProducer.collectAllMetrics()) + agg.collect(monitor) + return serviceData +} diff --git a/opendc-telemetry/opendc-telemetry-compute/src/main/kotlin/org/opendc/telemetry/compute/HostAttributes.kt b/opendc-telemetry/opendc-telemetry-compute/src/main/kotlin/org/opendc/telemetry/compute/HostAttributes.kt new file mode 100644 index 00000000..7dca6186 --- /dev/null +++ b/opendc-telemetry/opendc-telemetry-compute/src/main/kotlin/org/opendc/telemetry/compute/HostAttributes.kt @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +@file:JvmName("HostAttributes") +package org.opendc.telemetry.compute + +import io.opentelemetry.api.common.AttributeKey + +/** + * The identifier of the node hosting virtual machines. + */ +public val HOST_ID: AttributeKey<String> = AttributeKey.stringKey("node.id") + +/** + * The name of the node hosting virtual machines. + */ +public val HOST_NAME: AttributeKey<String> = AttributeKey.stringKey("node.name") + +/** + * The CPU architecture of the host node. + */ +public val HOST_ARCH: AttributeKey<String> = AttributeKey.stringKey("node.arch") + +/** + * The number of CPUs in the host node. + */ +public val HOST_NCPUS: AttributeKey<Long> = AttributeKey.longKey("node.num_cpus") + +/** + * The amount of memory installed in the host node in MiB. + */ +public val HOST_MEM_CAPACITY: AttributeKey<Long> = AttributeKey.longKey("node.mem_capacity") diff --git a/opendc-telemetry/opendc-telemetry-compute/src/main/kotlin/org/opendc/telemetry/compute/table/HostData.kt b/opendc-telemetry/opendc-telemetry-compute/src/main/kotlin/org/opendc/telemetry/compute/table/HostData.kt new file mode 100644 index 00000000..8e787b97 --- /dev/null +++ b/opendc-telemetry/opendc-telemetry-compute/src/main/kotlin/org/opendc/telemetry/compute/table/HostData.kt @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.telemetry.compute.table + +import java.time.Instant + +/** + * A trace entry for a particular host. + */ +public data class HostData( + val timestamp: Instant, + val host: HostInfo, + val guestsTerminated: Int, + val guestsRunning: Int, + val guestsError: Int, + val guestsInvalid: Int, + val cpuLimit: Double, + val cpuUsage: Double, + val cpuDemand: Double, + val cpuUtilization: Double, + val cpuActiveTime: Long, + val cpuIdleTime: Long, + val cpuStealTime: Long, + val cpuLostTime: Long, + val powerUsage: Double, + val powerTotal: Double, + val uptime: Long, + val downtime: Long, + val bootTime: Instant? +) diff --git a/opendc-telemetry/opendc-telemetry-compute/src/main/kotlin/org/opendc/telemetry/compute/table/HostInfo.kt b/opendc-telemetry/opendc-telemetry-compute/src/main/kotlin/org/opendc/telemetry/compute/table/HostInfo.kt new file mode 100644 index 00000000..d9a5906b --- /dev/null +++ b/opendc-telemetry/opendc-telemetry-compute/src/main/kotlin/org/opendc/telemetry/compute/table/HostInfo.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.telemetry.compute.table + +/** + * Information about a host exposed to the telemetry service. + */ +public data class HostInfo(val id: String, val name: String, val arch: String, val cpuCount: Int, val memCapacity: Long) diff --git a/opendc-telemetry/opendc-telemetry-compute/src/main/kotlin/org/opendc/telemetry/compute/table/ServerData.kt b/opendc-telemetry/opendc-telemetry-compute/src/main/kotlin/org/opendc/telemetry/compute/table/ServerData.kt new file mode 100644 index 00000000..c48bff3a --- /dev/null +++ b/opendc-telemetry/opendc-telemetry-compute/src/main/kotlin/org/opendc/telemetry/compute/table/ServerData.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.telemetry.compute.table + +import java.time.Instant + +/** + * A trace entry for a particular server. + */ +public data class ServerData( + val timestamp: Instant, + val server: ServerInfo, + val host: HostInfo?, + val uptime: Long, + val downtime: Long, + val bootTime: Instant?, + val schedulingLatency: Long, + val cpuLimit: Double, + val cpuActiveTime: Long, + val cpuIdleTime: Long, + val cpuStealTime: Long, + val cpuLostTime: Long, +) diff --git a/opendc-telemetry/opendc-telemetry-compute/src/main/kotlin/org/opendc/telemetry/compute/table/ServerInfo.kt b/opendc-telemetry/opendc-telemetry-compute/src/main/kotlin/org/opendc/telemetry/compute/table/ServerInfo.kt new file mode 100644 index 00000000..b16e5f3d --- /dev/null +++ b/opendc-telemetry/opendc-telemetry-compute/src/main/kotlin/org/opendc/telemetry/compute/table/ServerInfo.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.telemetry.compute.table + +/** + * Static information about a server exposed to the telemetry service. + */ +public data class ServerInfo( + val id: String, + val name: String, + val type: String, + val arch: String, + val imageId: String, + val imageName: String, + val cpuCount: Int, + val memCapacity: Long +) diff --git a/opendc-telemetry/opendc-telemetry-compute/src/main/kotlin/org/opendc/telemetry/compute/table/ServiceData.kt b/opendc-telemetry/opendc-telemetry-compute/src/main/kotlin/org/opendc/telemetry/compute/table/ServiceData.kt new file mode 100644 index 00000000..6db1399d --- /dev/null +++ b/opendc-telemetry/opendc-telemetry-compute/src/main/kotlin/org/opendc/telemetry/compute/table/ServiceData.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.telemetry.compute.table + +import java.time.Instant + +/** + * A trace entry for the compute service. + */ +public data class ServiceData( + val timestamp: Instant, + val hostsUp: Int, + val hostsDown: Int, + val serversPending: Int, + val serversActive: Int, + val attemptsSuccess: Int, + val attemptsFailure: Int, + val attemptsError: Int +) diff --git a/opendc-telemetry/opendc-telemetry-sdk/src/main/kotlin/org/opendc/telemetry/sdk/metrics/export/CoroutineMetricReader.kt b/opendc-telemetry/opendc-telemetry-sdk/src/main/kotlin/org/opendc/telemetry/sdk/metrics/export/CoroutineMetricReader.kt index 9ee16fac..1de235e7 100644 --- a/opendc-telemetry/opendc-telemetry-sdk/src/main/kotlin/org/opendc/telemetry/sdk/metrics/export/CoroutineMetricReader.kt +++ b/opendc-telemetry/opendc-telemetry-sdk/src/main/kotlin/org/opendc/telemetry/sdk/metrics/export/CoroutineMetricReader.kt @@ -22,18 +22,11 @@ package org.opendc.telemetry.sdk.metrics.export -import io.opentelemetry.sdk.metrics.data.MetricData import io.opentelemetry.sdk.metrics.export.MetricExporter import io.opentelemetry.sdk.metrics.export.MetricProducer import kotlinx.coroutines.* -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.flow.consumeAsFlow -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach import mu.KotlinLogging -import java.util.* -import kotlin.coroutines.resume -import kotlin.coroutines.suspendCoroutine +import java.time.Duration /** * A helper class to read the metrics from a list of [MetricProducer]s and automatically export the metrics every @@ -44,56 +37,45 @@ import kotlin.coroutines.suspendCoroutine * @param scope The [CoroutineScope] to run the reader in. * @param producers The metric producers to gather metrics from. * @param exporter The export to export the metrics to. - * @param exportInterval The export interval in milliseconds. + * @param exportInterval The export interval. */ public class CoroutineMetricReader( scope: CoroutineScope, private val producers: List<MetricProducer>, private val exporter: MetricExporter, - private val exportInterval: Long = 60_000 + private val exportInterval: Duration = Duration.ofMinutes(5) ) : AutoCloseable { private val logger = KotlinLogging.logger {} - private val chan = Channel<List<MetricData>>(Channel.RENDEZVOUS) /** - * The metric reader job. + * The background job that is responsible for collecting the metrics every cycle. */ - private val readerJob = scope.launch { - while (isActive) { - delay(exportInterval) + private val job = scope.launch { + val intervalMs = exportInterval.toMillis() - val metrics = mutableListOf<MetricData>() - for (producer in producers) { - metrics.addAll(producer.collectAllMetrics()) - } - chan.send(Collections.unmodifiableList(metrics)) - } - } + try { + while (isActive) { + delay(intervalMs) + + val metrics = producers.flatMap(MetricProducer::collectAllMetrics) - /** - * The exporter job runs in the background to actually export the metrics. - */ - private val exporterJob = chan.consumeAsFlow() - .onEach { metrics -> - suspendCoroutine<Unit> { cont -> try { val result = exporter.export(metrics) result.whenComplete { if (!result.isSuccess) { - logger.trace { "Exporter failed" } + logger.warn { "Exporter failed" } } - cont.resume(Unit) } } catch (cause: Throwable) { logger.warn(cause) { "Exporter threw an Exception" } - cont.resume(Unit) } } + } finally { + exporter.shutdown() } - .launchIn(scope) + } override fun close() { - readerJob.cancel() - exporterJob.cancel() + job.cancel() } } diff --git a/opendc-trace/build.gradle.kts b/opendc-trace/build.gradle.kts new file mode 100644 index 00000000..7edfd134 --- /dev/null +++ b/opendc-trace/build.gradle.kts @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ diff --git a/opendc-trace/opendc-trace-api/build.gradle.kts b/opendc-trace/opendc-trace-api/build.gradle.kts new file mode 100644 index 00000000..b2f91593 --- /dev/null +++ b/opendc-trace/opendc-trace-api/build.gradle.kts @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +description = "Workload trace library for OpenDC" + +/* Build configuration */ +plugins { + `kotlin-library-conventions` +} + +dependencies { + api(platform(projects.opendcPlatform)) +} diff --git a/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/ResourceColumns.kt b/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/ResourceColumns.kt new file mode 100644 index 00000000..f1977945 --- /dev/null +++ b/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/ResourceColumns.kt @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +@file:JvmName("ResourceColumns") +package org.opendc.trace + +import java.time.Instant + +/** + * Identifier of the resource. + */ +@JvmField +public val RESOURCE_ID: TableColumn<String> = column("resource:id") + +/** + * The cluster to which the resource belongs. + */ +@JvmField +public val RESOURCE_CLUSTER_ID: TableColumn<String> = column("resource:cluster_id") + +/** + * Start time for the resource. + */ +@JvmField +public val RESOURCE_START_TIME: TableColumn<Instant> = column("resource:start_time") + +/** + * End time for the resource. + */ +@JvmField +public val RESOURCE_STOP_TIME: TableColumn<Instant> = column("resource:stop_time") + +/** + * Number of CPUs for the resource. + */ +@JvmField +public val RESOURCE_CPU_COUNT: TableColumn<Int> = column("resource:cpu_count") + +/** + * Total CPU capacity of the resource in MHz. + */ +@JvmField +public val RESOURCE_CPU_CAPACITY: TableColumn<Double> = column("resource:cpu_capacity") + +/** + * Memory capacity for the resource in KB. + */ +@JvmField +public val RESOURCE_MEM_CAPACITY: TableColumn<Double> = column("resource:mem_capacity") diff --git a/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/ResourceStateColumns.kt b/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/ResourceStateColumns.kt new file mode 100644 index 00000000..44762da5 --- /dev/null +++ b/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/ResourceStateColumns.kt @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +@file:JvmName("ResourceStateColumns") +package org.opendc.trace + +import java.time.Duration +import java.time.Instant + +/** + * Timestamp for the state. + */ +@JvmField +public val RESOURCE_STATE_TIMESTAMP: TableColumn<Instant> = column("resource_state:timestamp") + +/** + * Duration for the state. + */ +@JvmField +public val RESOURCE_STATE_DURATION: TableColumn<Duration> = column("resource_state:duration") + +/** + * A flag to indicate that the resource is powered on. + */ +@JvmField +public val RESOURCE_STATE_POWERED_ON: TableColumn<Boolean> = column("resource_state:powered_on") + +/** + * Total CPU usage of the resource in MHz. + */ +@JvmField +public val RESOURCE_STATE_CPU_USAGE: TableColumn<Double> = column("resource_state:cpu_usage") + +/** + * Total CPU usage of the resource in percentage. + */ +@JvmField +public val RESOURCE_STATE_CPU_USAGE_PCT: TableColumn<Double> = column("resource_state:cpu_usage_pct") + +/** + * Total CPU demand of the resource in MHz. + */ +@JvmField +public val RESOURCE_STATE_CPU_DEMAND: TableColumn<Double> = column("resource_state:cpu_demand") + +/** + * CPU ready percentage. + */ +@JvmField +public val RESOURCE_STATE_CPU_READY_PCT: TableColumn<Double> = column("resource_state:cpu_ready_pct") + +/** + * Memory usage of the resource in KB. + */ +@JvmField +public val RESOURCE_STATE_MEM_USAGE: TableColumn<Double> = column("resource_state:mem_usage") + +/** + * Disk read throughput of the resource in KB/s. + */ +@JvmField +public val RESOURCE_STATE_DISK_READ: TableColumn<Double> = column("resource_state:disk_read") + +/** + * Disk write throughput of the resource in KB/s. + */ +@JvmField +public val RESOURCE_STATE_DISK_WRITE: TableColumn<Double> = column("resource_state:disk_write") + +/** + * Network receive throughput of the resource in KB/s. + */ +@JvmField +public val RESOURCE_STATE_NET_RX: TableColumn<Double> = column("resource_state:net_rx") + +/** + * Network transmit throughput of the resource in KB/s. + */ +@JvmField +public val RESOURCE_STATE_NET_TX: TableColumn<Double> = column("resource_state:net_tx") diff --git a/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/Table.kt b/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/Table.kt new file mode 100644 index 00000000..b0181cbc --- /dev/null +++ b/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/Table.kt @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.trace + +/** + * A table is collection of rows consisting of typed columns. + */ +public interface Table { + /** + * The name of the table. + */ + public val name: String + + /** + * The list of columns supported in this table. + */ + public val columns: List<TableColumn<*>> + + /** + * The columns by which the table is partitioned. + */ + public val partitionKeys: List<TableColumn<*>> + + /** + * Open a [TableReader] for this table. + */ + public fun newReader(): TableReader + + /** + * Open a [TableWriter] for this table. + * + * @throws UnsupportedOperationException if writing is not supported by the table. + */ + public fun newWriter(): TableWriter +} diff --git a/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/TableColumn.kt b/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/TableColumn.kt new file mode 100644 index 00000000..776c40c0 --- /dev/null +++ b/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/TableColumn.kt @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.trace + +import java.util.* + +/** + * A column in a trace table. + * + * @param name The universal name of this column. + */ +public class TableColumn<out T>(public val name: String, type: Class<T>) { + /** + * The type of the column. + */ + private val type: Class<*> = type + + /** + * Determine whether the type of the column is a subtype of [column]. + */ + public fun isAssignableTo(column: TableColumn<*>): Boolean { + return name == column.name && type.isAssignableFrom(column.type) + } + + /** + * Compute a hash code for this column. + */ + public override fun hashCode(): Int = Objects.hash(name, type) + + /** + * Determine whether this column is equal to [other]. + */ + public override fun equals(other: Any?): Boolean { + // Fast-path: reference equality + if (this === other) { + return true + } else if (other == null || other !is TableColumn<*>) { + return false + } + + return name == other.name && type == other.type + } + + /** + * Return a string representation of this column. + */ + public override fun toString(): String = "TableColumn[$name,$type]" +} diff --git a/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/TableColumns.kt b/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/TableColumns.kt new file mode 100644 index 00000000..31a58360 --- /dev/null +++ b/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/TableColumns.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +@file:JvmName("TableColumns") +package org.opendc.trace + +/** + * Construct a [TableColumn] with the specified [name] and type [T]. + */ +public inline fun <reified T> column(name: String): TableColumn<T> = column(name, T::class.java) + +/** + * Construct a [TableColumn] with the specified [name] and [type]. + */ +public fun <T> column(name: String, type: Class<T>): TableColumn<T> = TableColumn(name, type) diff --git a/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/TableReader.kt b/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/TableReader.kt new file mode 100644 index 00000000..8a796e6c --- /dev/null +++ b/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/TableReader.kt @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.trace + +/** + * Base class for reading entities from a workload trace table in streaming fashion. + */ +public interface TableReader : AutoCloseable { + /** + * Advance the stream until the next row is reached. + * + * @return `true` if the row is valid, `false` if there are no more rows. + */ + public fun nextRow(): Boolean + + /** + * Resolve the index of the specified [column] for this reader. + * + * @param column The column to lookup. + * @return The zero-based index of the column or a negative value if the column is not present in this table. + */ + public fun resolve(column: TableColumn<*>): Int + + /** + * Determine whether the [TableReader] supports the specified [column]. + */ + public fun hasColumn(column: TableColumn<*>): Boolean = resolve(column) >= 0 + + /** + * Determine whether the specified [column] has a `null` value for the current row. + * + * @param index The zero-based index of the column to check for a null value. + * @throws IllegalArgumentException if the column index is not valid for this reader. + * @return `true` if the column value for the current value has a `null` value, `false` otherwise. + */ + public fun isNull(index: Int): Boolean + + /** + * Obtain the object value of the column with the specified [index]. + * + * @param index The zero-based index of the column to obtain the value for. + * @throws IllegalArgumentException if the column index is not valid for this reader. + * @return The object value of the column. + */ + public fun get(index: Int): Any? + + /** + * Obtain the boolean value of the column with the specified [index]. + * + * @param index The zero-based index of the column to obtain the value for. + * @throws IllegalArgumentException if the column index is not valid for this reader. + * @return The boolean value of the column or `false` if the column is `null`. + */ + public fun getBoolean(index: Int): Boolean + + /** + * Obtain the integer value of the column with the specified [index]. + * + * @param index The zero-based index of the column to obtain the value for. + * @throws IllegalArgumentException if the column index is not valid for this reader. + * @return The integer value of the column or `0` if the column is `null`. + */ + public fun getInt(index: Int): Int + + /** + * Obtain the double value of the column with the specified [index]. + * + * @param index The zero-based index of the column to obtain the value for. + * @throws IllegalArgumentException if the column index is not valid for this reader. + * @return The long value of the column or `0` if the column is `null`. + */ + public fun getLong(index: Int): Long + + /** + * Obtain the double value of the column with the specified [index]. + * + * @param index The zero-based index of the column to obtain the value for. + * @throws IllegalArgumentException if the column index is not valid for this reader. + * @return The double value of the column or [Double.NaN] if the column is `null`. + */ + public fun getDouble(index: Int): Double + + /** + * Determine whether the specified [column] has a `null` value for the current row. + * + * @param column The column to lookup. + * @throws IllegalArgumentException if the column is not valid for this table. + * @return `true` if the column value for the current value has a `null` value, `false` otherwise. + */ + public fun isNull(column: TableColumn<*>): Boolean = isNull(resolve(column)) + + /** + * Obtain the value of the current column with type [T]. + * + * @param column The column to obtain the value for. + * @throws IllegalArgumentException if the column is not valid for this reader. + * @return The object value of the column. + */ + public fun <T> get(column: TableColumn<T>): T { + // This cast should always succeed since the resolve the index of the typed column + @Suppress("UNCHECKED_CAST") + return get(resolve(column)) as T + } + + /** + * Read the specified [column] as boolean. + * + * @param column The column to obtain the value for. + * @throws IllegalArgumentException if the column is not valid for this reader. + * @return The boolean value of the column or `false` if the column is `null`. + */ + public fun getBoolean(column: TableColumn<Boolean>): Boolean = getBoolean(resolve(column)) + + /** + * Read the specified [column] as integer. + * + * @param column The column to obtain the value for. + * @throws IllegalArgumentException if the column is not valid for this reader. + * @return The integer value of the column or `0` if the column is `null`. + */ + public fun getInt(column: TableColumn<Int>): Int = getInt(resolve(column)) + + /** + * Read the specified [column] as long. + * + * @param column The column to obtain the value for. + * @throws IllegalArgumentException if the column is not valid for this reader. + * @return The long value of the column or `0` if the column is `null`. + */ + public fun getLong(column: TableColumn<Long>): Long = getLong(resolve(column)) + + /** + * Read the specified [column] as double. + * + * @param column The column to obtain the value for. + * @throws IllegalArgumentException if the column is not valid for this reader. + * @return The double value of the column or [Double.NaN] if the column is `null`. + */ + public fun getDouble(column: TableColumn<Double>): Double = getDouble(resolve(column)) + + /** + * Closes the reader so that no further iteration or data access can be made. + */ + public override fun close() +} diff --git a/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/TableWriter.kt b/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/TableWriter.kt new file mode 100644 index 00000000..423ce86a --- /dev/null +++ b/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/TableWriter.kt @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.trace + +/** + * Base class for writing workload traces. + */ +public interface TableWriter : AutoCloseable { + /** + * Start a new row in the table. + */ + public fun startRow() + + /** + * Flush the current row to the table. + */ + public fun endRow() + + /** + * Resolve the index of the specified [column] for this writer. + * + * @param column The column to lookup. + * @return The zero-based index of the column or a negative value if the column is not present in this table. + */ + public fun resolve(column: TableColumn<*>): Int + + /** + * Determine whether the [TableReader] supports the specified [column]. + */ + public fun hasColumn(column: TableColumn<*>): Boolean = resolve(column) >= 0 + + /** + * Set [column] to [value]. + * + * @param index The zero-based index of the column to set the value for. + * @param value The value to set the column to. + * @throws IllegalArgumentException if the column is not valid for this method. + */ + public fun set(index: Int, value: Any) + + /** + * Set [column] to boolean [value]. + * + * @param index The zero-based index of the column to set the value for. + * @param value The boolean value to set the column to. + * @throws IllegalArgumentException if the column is not valid for this method. + */ + public fun setBoolean(index: Int, value: Boolean) + + /** + * Set [column] to integer [value]. + * + * @param index The zero-based index of the column to set the value for. + * @param value The integer value to set the column to. + * @throws IllegalArgumentException if the column is not valid for this method. + */ + public fun setInt(index: Int, value: Int) + + /** + * Set [column] to long [value]. + * + * @param index The zero-based index of the column to set the value for. + * @param value The long value to set the column to. + * @throws IllegalArgumentException if the column is not valid for this method. + */ + public fun setLong(index: Int, value: Long) + + /** + * Set [column] to double [value]. + * + * @param index The zero-based index of the column to set the value for. + * @param value The double value to set the column to. + * @throws IllegalArgumentException if the column is not valid for this method. + */ + public fun setDouble(index: Int, value: Double) + + /** + * Set [column] to [value]. + * + * @param column The column to set the value for. + * @param value The value to set the column to. + * @throws IllegalArgumentException if the column is not valid for this method. + */ + public fun <T : Any> set(column: TableColumn<T>, value: T): Unit = set(resolve(column), value) + + /** + * Set [column] to boolean [value]. + * + * @param column The column to set the value for. + * @param value The boolean value to set the column to. + * @throws IllegalArgumentException if the column is not valid for this method. + */ + public fun setBoolean(column: TableColumn<Boolean>, value: Boolean): Unit = setBoolean(resolve(column), value) + + /** + * Set [column] to integer [value]. + * + * @param column The column to set the value for. + * @param value The integer value to set the column to. + * @throws IllegalArgumentException if the column is not valid for this method. + */ + public fun setInt(column: TableColumn<Int>, value: Int): Unit = setInt(resolve(column), value) + + /** + * Set [column] to long [value]. + * + * @param column The column to set the value for. + * @param value The long value to set the column to. + * @throws IllegalArgumentException if the column is not valid for this method. + */ + public fun setLong(column: TableColumn<Long>, value: Long): Unit = setLong(resolve(column), value) + + /** + * Set [column] to double [value]. + * + * @param column The column to set the value for. + * @param value The double value to set the column to. + * @throws IllegalArgumentException if the column is not valid for this method. + */ + public fun setDouble(column: TableColumn<Double>, value: Double): Unit = setDouble(resolve(column), value) + + /** + * Flush any buffered content to the underlying target. + */ + public fun flush() + + /** + * Close the writer so that no more rows can be written. + */ + public override fun close() +} diff --git a/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/Tables.kt b/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/Tables.kt new file mode 100644 index 00000000..bb9d93e2 --- /dev/null +++ b/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/Tables.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +@file:JvmName("Tables") +package org.opendc.trace + +/** + * A table containing all workflows in a workload. + */ +public const val TABLE_WORKFLOWS: String = "workflows" + +/** + * A table containing all tasks in a workload. + */ +public const val TABLE_TASKS: String = "tasks" + +/** + * A table containing all resources in a workload. + */ +public const val TABLE_RESOURCES: String = "resources" + +/** + * A table containing all resource states in a workload. + */ +public const val TABLE_RESOURCE_STATES: String = "resource_states" diff --git a/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/TaskColumns.kt b/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/TaskColumns.kt new file mode 100644 index 00000000..d103bce4 --- /dev/null +++ b/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/TaskColumns.kt @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +@file:JvmName("TaskColumns") +package org.opendc.trace + +import java.time.Duration +import java.time.Instant + +/** + * A column containing the task identifier. + */ +@JvmField +public val TASK_ID: TableColumn<String> = column("task:id") + +/** + * A column containing the identifier of the workflow. + */ +@JvmField +public val TASK_WORKFLOW_ID: TableColumn<String> = column("task:workflow_id") + +/** + * A column containing the submission time of the task. + */ +@JvmField +public val TASK_SUBMIT_TIME: TableColumn<Instant> = column("task:submit_time") + +/** + * A column containing the wait time of the task. + */ +@JvmField +public val TASK_WAIT_TIME: TableColumn<Instant> = column("task:wait_time") + +/** + * A column containing the runtime time of the task. + */ +@JvmField +public val TASK_RUNTIME: TableColumn<Duration> = column("task:runtime") + +/** + * A column containing the parents of a task. + */ +@JvmField +public val TASK_PARENTS: TableColumn<Set<String>> = column("task:parents") + +/** + * A column containing the children of a task. + */ +@JvmField +public val TASK_CHILDREN: TableColumn<Set<String>> = column("task:children") + +/** + * A column containing the requested CPUs of a task. + */ +@JvmField +public val TASK_REQ_NCPUS: TableColumn<Int> = column("task:req_ncpus") + +/** + * A column containing the allocated CPUs of a task. + */ +@JvmField +public val TASK_ALLOC_NCPUS: TableColumn<Int> = column("task:alloc_ncpus") + +/** + * A column containing the status of a task. + */ +@JvmField +public val TASK_STATUS: TableColumn<Int> = column("task:status") + +/** + * A column containing the group id of a task. + */ +@JvmField +public val TASK_GROUP_ID: TableColumn<Int> = column("task:group_id") + +/** + * A column containing the user id of a task. + */ +@JvmField +public val TASK_USER_ID: TableColumn<Int> = column("task:user_id") diff --git a/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/Trace.kt b/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/Trace.kt new file mode 100644 index 00000000..64e8f272 --- /dev/null +++ b/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/Trace.kt @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.trace + +import org.opendc.trace.internal.TraceImpl +import org.opendc.trace.spi.TraceFormat +import java.io.File +import java.nio.file.Path + +/** + * A trace is a collection of related tables that characterize a workload. + */ +public interface Trace { + /** + * The list of table names in the workload trace. + */ + public val tables: List<String> + + /** + * Determine if the trace contains a table with the specified [name]. + */ + public fun containsTable(name: String): Boolean + + /** + * Obtain a [Table] with the specified [name]. + */ + public fun getTable(name: String): Table? + + public companion object { + /** + * Open a [Trace] at the specified [path] in the given [format]. + * + * @param path The path to the trace. + * @param format The format of the trace to open. + * @throws IllegalArgumentException if [format] is not supported. + */ + @JvmStatic + public fun open(path: File, format: String): Trace = open(path.toPath(), format) + + /** + * Open a [Trace] at the specified [path] in the given [format]. + * + * @param path The [Path] to the trace. + * @param format The format of the trace to open. + * @throws IllegalArgumentException if [format] is not supported. + */ + @JvmStatic + public fun open(path: Path, format: String): Trace { + val provider = requireNotNull(TraceFormat.byName(format)) { "Unknown format $format" } + return TraceImpl(provider, path) + } + + /** + * Create a [Trace] at the specified [path] in the given [format]. + * + * @param path The [Path] to the trace. + * @param format The format of the trace to create. + */ + @JvmStatic + public fun create(path: File, format: String): Trace = create(path.toPath(), format) + + /** + * Create a [Trace] at the specified [path] in the given [format]. + * + * @param path The [Path] to the trace. + * @param format The format of the trace to create. + */ + @JvmStatic + public fun create(path: Path, format: String): Trace { + val provider = requireNotNull(TraceFormat.byName(format)) { "Unknown format $format" } + provider.create(path) + return TraceImpl(provider, path) + } + } +} diff --git a/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/internal/TableImpl.kt b/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/internal/TableImpl.kt new file mode 100644 index 00000000..24551edb --- /dev/null +++ b/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/internal/TableImpl.kt @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.trace.internal + +import org.opendc.trace.Table +import org.opendc.trace.TableColumn +import org.opendc.trace.TableReader +import org.opendc.trace.TableWriter +import java.util.* + +/** + * Internal implementation of [Table]. + */ +internal class TableImpl(val trace: TraceImpl, override val name: String) : Table { + /** + * The details of this table. + */ + private val details = trace.format.getDetails(trace.path, name) + + override val columns: List<TableColumn<*>> + get() = details.columns + + override val partitionKeys: List<TableColumn<*>> + get() = details.partitionKeys + + override fun newReader(): TableReader = trace.format.newReader(trace.path, name) + + override fun newWriter(): TableWriter = trace.format.newWriter(trace.path, name) + + override fun toString(): String = "Table[name=$name]" + + override fun hashCode(): Int = Objects.hash(trace, name) + + override fun equals(other: Any?): Boolean = other is TableImpl && trace == other.trace && name == other.name +} diff --git a/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/internal/TraceImpl.kt b/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/internal/TraceImpl.kt new file mode 100644 index 00000000..fd9536ab --- /dev/null +++ b/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/internal/TraceImpl.kt @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.trace.internal + +import org.opendc.trace.Table +import org.opendc.trace.Trace +import org.opendc.trace.spi.TraceFormat +import java.nio.file.Path +import java.util.* +import java.util.concurrent.ConcurrentHashMap + +/** + * Internal implementation of the [Trace] interface. + */ +internal class TraceImpl(val format: TraceFormat, val path: Path) : Trace { + /** + * A map containing the [TableImpl] instances associated with the trace. + */ + private val tableMap = ConcurrentHashMap<String, TableImpl>() + + override val tables: List<String> = format.getTables(path) + + init { + for (table in tables) { + tableMap.computeIfAbsent(table) { TableImpl(this, it) } + } + } + + override fun containsTable(name: String): Boolean = tableMap.containsKey(name) + + override fun getTable(name: String): Table? = tableMap[name] + + override fun hashCode(): Int = Objects.hash(format, path) + + override fun equals(other: Any?): Boolean = other is TraceImpl && format == other.format && path == other.path +} diff --git a/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/spi/TableDetails.kt b/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/spi/TableDetails.kt new file mode 100644 index 00000000..1a9b9ee1 --- /dev/null +++ b/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/spi/TableDetails.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.trace.spi + +import org.opendc.trace.Table +import org.opendc.trace.TableColumn + +/** + * A class used by the [TraceFormat] interface for describing the metadata of a [Table]. + * + * @param columns The available columns in the table. + * @param partitionKeys The table columns that act as partition keys for the table. + */ +public data class TableDetails( + val columns: List<TableColumn<*>>, + val partitionKeys: List<TableColumn<*>> = emptyList() +) diff --git a/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/spi/TraceFormat.kt b/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/spi/TraceFormat.kt new file mode 100644 index 00000000..f2e610db --- /dev/null +++ b/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/spi/TraceFormat.kt @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.trace.spi + +import org.opendc.trace.TableReader +import org.opendc.trace.TableWriter +import java.nio.file.Path +import java.util.* + +/** + * A service-provider class for parsing trace formats. + */ +public interface TraceFormat { + /** + * The name of the trace format. + */ + public val name: String + + /** + * Construct an empty trace at [path]. + * + * @param path The path where to create the empty trace. + * @throws IllegalArgumentException If [path] is invalid. + * @throws UnsupportedOperationException If the table does not support trace creation. + */ + public fun create(path: Path) + + /** + * Return the name of the tables available in the trace at the specified [path]. + * + * @param path The path to the trace. + * @return The list of tables available in the trace. + */ + public fun getTables(path: Path): List<String> + + /** + * Return the details of [table] in the trace at the specified [path]. + * + * @param path The path to the trace. + * @param table The name of the table to obtain the details for. + * @throws IllegalArgumentException If [table] does not exist. + * @return The [TableDetails] for the specified [table]. + */ + public fun getDetails(path: Path, table: String): TableDetails + + /** + * Open a [TableReader] for the specified [table]. + * + * @param path The path to the trace to open. + * @param table The name of the table to open a [TableReader] for. + * @throws IllegalArgumentException If [table] does not exist. + * @return A [TableReader] instance for the table. + */ + public fun newReader(path: Path, table: String): TableReader + + /** + * Open a [TableWriter] for the specified [table]. + * + * @param path The path to the trace to open. + * @param table The name of the table to open a [TableWriter] for. + * @throws IllegalArgumentException If [table] does not exist. + * @throws UnsupportedOperationException If the format does not support writing. + * @return A [TableWriter] instance for the table. + */ + public fun newWriter(path: Path, table: String): TableWriter + + /** + * A helper object for resolving providers. + */ + public companion object { + /** + * A list of [TraceFormat] that are available on this system. + */ + @JvmStatic + public val installedProviders: List<TraceFormat> by lazy { + val loader = ServiceLoader.load(TraceFormat::class.java) + loader.toList() + } + + /** + * Obtain a [TraceFormat] implementation by [name]. + */ + @JvmStatic + public fun byName(name: String): TraceFormat? = installedProviders.find { it.name == name } + } +} diff --git a/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/util/CompositeTableReader.kt b/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/util/CompositeTableReader.kt new file mode 100644 index 00000000..dafc0798 --- /dev/null +++ b/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/util/CompositeTableReader.kt @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.trace.util + +import org.opendc.trace.TableColumn +import org.opendc.trace.TableReader + +/** + * A helper class to chain multiple [TableReader]s. + */ +public abstract class CompositeTableReader : TableReader { + /** + * A flag to indicate that the reader has starting, meaning the user called [nextRow] at least once + * (and in turn [nextReader]). + */ + private var hasStarted = false + + /** + * The active [TableReader] instance. + */ + private var delegate: TableReader? = null + + /** + * Obtain the next [TableReader] instance to read from or `null` if there are no more readers to read from. + */ + protected abstract fun nextReader(): TableReader? + + override fun nextRow(): Boolean { + if (!hasStarted) { + assert(delegate == null) { "Duplicate initialization" } + delegate = nextReader() + hasStarted = true + } + + var delegate = delegate + + while (delegate != null) { + if (delegate.nextRow()) { + break + } + + delegate.close() + delegate = nextReader() + this.delegate = delegate + } + + return delegate != null + } + + override fun resolve(column: TableColumn<*>): Int { + val delegate = delegate + return delegate?.resolve(column) ?: -1 + } + + override fun isNull(index: Int): Boolean { + val delegate = checkNotNull(delegate) { "Invalid reader state" } + return delegate.isNull(index) + } + + override fun get(index: Int): Any? { + val delegate = checkNotNull(delegate) { "Invalid reader state" } + return delegate.get(index) + } + + override fun getBoolean(index: Int): Boolean { + val delegate = checkNotNull(delegate) { "Invalid reader state" } + return delegate.getBoolean(index) + } + + override fun getInt(index: Int): Int { + val delegate = checkNotNull(delegate) { "Invalid reader state" } + return delegate.getInt(index) + } + + override fun getLong(index: Int): Long { + val delegate = checkNotNull(delegate) { "Invalid reader state" } + return delegate.getLong(index) + } + + override fun getDouble(index: Int): Double { + val delegate = checkNotNull(delegate) { "Invalid reader state" } + return delegate.getDouble(index) + } + + override fun close() { + delegate?.close() + } + + override fun toString(): String = "CompositeTableReader" +} diff --git a/opendc-trace/opendc-trace-azure/build.gradle.kts b/opendc-trace/opendc-trace-azure/build.gradle.kts new file mode 100644 index 00000000..8bde56cb --- /dev/null +++ b/opendc-trace/opendc-trace-azure/build.gradle.kts @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +description = "Support for Azure VM traces in OpenDC" + +/* Build configuration */ +plugins { + `kotlin-library-conventions` + `testing-conventions` + `jacoco-conventions` +} + +dependencies { + api(platform(projects.opendcPlatform)) + api(projects.opendcTrace.opendcTraceApi) + implementation(libs.jackson.dataformat.csv) +} diff --git a/opendc-trace/opendc-trace-azure/src/main/kotlin/org/opendc/trace/azure/AzureResourceStateTableReader.kt b/opendc-trace/opendc-trace-azure/src/main/kotlin/org/opendc/trace/azure/AzureResourceStateTableReader.kt new file mode 100644 index 00000000..da8181fe --- /dev/null +++ b/opendc-trace/opendc-trace-azure/src/main/kotlin/org/opendc/trace/azure/AzureResourceStateTableReader.kt @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.trace.azure + +import com.fasterxml.jackson.core.JsonToken +import com.fasterxml.jackson.dataformat.csv.CsvParser +import com.fasterxml.jackson.dataformat.csv.CsvSchema +import org.opendc.trace.* +import java.time.Instant + +/** + * A [TableReader] for the Azure v1 VM resource state table. + */ +internal class AzureResourceStateTableReader(private val parser: CsvParser) : TableReader { + init { + parser.schema = schema + } + + override fun nextRow(): Boolean { + reset() + + if (!nextStart()) { + return false + } + + while (true) { + val token = parser.nextValue() + + if (token == null || token == JsonToken.END_OBJECT) { + break + } + + when (parser.currentName) { + "timestamp" -> timestamp = Instant.ofEpochSecond(parser.longValue) + "vm id" -> id = parser.text + "CPU avg cpu" -> cpuUsagePct = parser.doubleValue + } + } + + return true + } + + override fun resolve(column: TableColumn<*>): Int = columns[column] ?: -1 + + override fun isNull(index: Int): Boolean { + require(index in 0..columns.size) { "Invalid column index" } + return false + } + + override fun get(index: Int): Any? { + return when (index) { + COL_ID -> id + COL_TIMESTAMP -> timestamp + COL_CPU_USAGE_PCT -> cpuUsagePct + else -> throw IllegalArgumentException("Invalid column index") + } + } + + override fun getBoolean(index: Int): Boolean { + throw IllegalArgumentException("Invalid column") + } + + override fun getInt(index: Int): Int { + throw IllegalArgumentException("Invalid column") + } + + override fun getLong(index: Int): Long { + throw IllegalArgumentException("Invalid column") + } + + override fun getDouble(index: Int): Double { + return when (index) { + COL_CPU_USAGE_PCT -> cpuUsagePct + else -> throw IllegalArgumentException("Invalid column") + } + } + + override fun close() { + parser.close() + } + + /** + * Advance the parser until the next object start. + */ + private fun nextStart(): Boolean { + var token = parser.nextValue() + + while (token != null && token != JsonToken.START_OBJECT) { + token = parser.nextValue() + } + + return token != null + } + + /** + * State fields of the reader. + */ + private var id: String? = null + private var timestamp: Instant? = null + private var cpuUsagePct = Double.NaN + + /** + * Reset the state. + */ + private fun reset() { + id = null + timestamp = null + cpuUsagePct = Double.NaN + } + + private val COL_ID = 0 + private val COL_TIMESTAMP = 1 + private val COL_CPU_USAGE_PCT = 2 + private val columns = mapOf( + RESOURCE_ID to COL_ID, + RESOURCE_STATE_TIMESTAMP to COL_TIMESTAMP, + RESOURCE_STATE_CPU_USAGE_PCT to COL_CPU_USAGE_PCT + ) + + companion object { + /** + * The [CsvSchema] that is used to parse the trace. + */ + private val schema = CsvSchema.builder() + .addColumn("timestamp", CsvSchema.ColumnType.NUMBER) + .addColumn("vm id", CsvSchema.ColumnType.STRING) + .addColumn("CPU min cpu", CsvSchema.ColumnType.NUMBER) + .addColumn("CPU max cpu", CsvSchema.ColumnType.NUMBER) + .addColumn("CPU avg cpu", CsvSchema.ColumnType.NUMBER) + .setAllowComments(true) + .build() + } +} diff --git a/opendc-trace/opendc-trace-azure/src/main/kotlin/org/opendc/trace/azure/AzureResourceTableReader.kt b/opendc-trace/opendc-trace-azure/src/main/kotlin/org/opendc/trace/azure/AzureResourceTableReader.kt new file mode 100644 index 00000000..a6352613 --- /dev/null +++ b/opendc-trace/opendc-trace-azure/src/main/kotlin/org/opendc/trace/azure/AzureResourceTableReader.kt @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.trace.azure + +import com.fasterxml.jackson.core.JsonToken +import com.fasterxml.jackson.dataformat.csv.CsvParser +import com.fasterxml.jackson.dataformat.csv.CsvSchema +import org.opendc.trace.* +import java.time.Instant + +/** + * A [TableReader] for the Azure v1 VM resources table. + */ +internal class AzureResourceTableReader(private val parser: CsvParser) : TableReader { + init { + parser.schema = schema + } + + override fun nextRow(): Boolean { + reset() + + if (!nextStart()) { + return false + } + + while (true) { + val token = parser.nextValue() + + if (token == null || token == JsonToken.END_OBJECT) { + break + } + + when (parser.currentName) { + "vm id" -> id = parser.text + "vm created" -> startTime = Instant.ofEpochSecond(parser.longValue) + "vm deleted" -> stopTime = Instant.ofEpochSecond(parser.longValue) + "vm virtual core count" -> cpuCores = parser.intValue + "vm memory" -> memCapacity = parser.doubleValue * 1e6 // GB to KB + } + } + + return true + } + + override fun resolve(column: TableColumn<*>): Int = columns[column] ?: -1 + + override fun isNull(index: Int): Boolean { + require(index in 0..columns.size) { "Invalid column index" } + return false + } + + override fun get(index: Int): Any? { + return when (index) { + COL_ID -> id + COL_START_TIME -> startTime + COL_STOP_TIME -> stopTime + COL_CPU_COUNT -> getInt(index) + COL_MEM_CAPACITY -> getDouble(index) + else -> throw IllegalArgumentException("Invalid column") + } + } + + override fun getBoolean(index: Int): Boolean { + throw IllegalArgumentException("Invalid column") + } + + override fun getInt(index: Int): Int { + return when (index) { + COL_CPU_COUNT -> cpuCores + else -> throw IllegalArgumentException("Invalid column") + } + } + + override fun getLong(index: Int): Long { + throw IllegalArgumentException("Invalid column") + } + + override fun getDouble(index: Int): Double { + return when (index) { + COL_MEM_CAPACITY -> memCapacity + else -> throw IllegalArgumentException("Invalid column") + } + } + + override fun close() { + parser.close() + } + + /** + * Advance the parser until the next object start. + */ + private fun nextStart(): Boolean { + var token = parser.nextValue() + + while (token != null && token != JsonToken.START_OBJECT) { + token = parser.nextValue() + } + + return token != null + } + + /** + * State fields of the reader. + */ + private var id: String? = null + private var startTime: Instant? = null + private var stopTime: Instant? = null + private var cpuCores = -1 + private var memCapacity = Double.NaN + + /** + * Reset the state. + */ + private fun reset() { + id = null + startTime = null + stopTime = null + cpuCores = -1 + memCapacity = Double.NaN + } + + private val COL_ID = 0 + private val COL_START_TIME = 1 + private val COL_STOP_TIME = 2 + private val COL_CPU_COUNT = 3 + private val COL_MEM_CAPACITY = 4 + private val columns = mapOf( + RESOURCE_ID to COL_ID, + RESOURCE_START_TIME to COL_START_TIME, + RESOURCE_STOP_TIME to COL_STOP_TIME, + RESOURCE_CPU_COUNT to COL_CPU_COUNT, + RESOURCE_MEM_CAPACITY to COL_MEM_CAPACITY + ) + + companion object { + /** + * The [CsvSchema] that is used to parse the trace. + */ + private val schema = CsvSchema.builder() + .addColumn("vm id", CsvSchema.ColumnType.NUMBER) + .addColumn("subscription id", CsvSchema.ColumnType.STRING) + .addColumn("deployment id", CsvSchema.ColumnType.NUMBER) + .addColumn("timestamp vm created", CsvSchema.ColumnType.NUMBER) + .addColumn("timestamp vm deleted", CsvSchema.ColumnType.NUMBER) + .addColumn("max cpu", CsvSchema.ColumnType.NUMBER) + .addColumn("avg cpu", CsvSchema.ColumnType.NUMBER) + .addColumn("p95 cpu", CsvSchema.ColumnType.NUMBER) + .addColumn("vm category", CsvSchema.ColumnType.NUMBER) + .addColumn("vm virtual core count", CsvSchema.ColumnType.NUMBER) + .addColumn("vm memory", CsvSchema.ColumnType.NUMBER) + .setAllowComments(true) + .build() + } +} diff --git a/opendc-trace/opendc-trace-azure/src/main/kotlin/org/opendc/trace/azure/AzureTraceFormat.kt b/opendc-trace/opendc-trace-azure/src/main/kotlin/org/opendc/trace/azure/AzureTraceFormat.kt new file mode 100644 index 00000000..253c7057 --- /dev/null +++ b/opendc-trace/opendc-trace-azure/src/main/kotlin/org/opendc/trace/azure/AzureTraceFormat.kt @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.trace.azure + +import com.fasterxml.jackson.dataformat.csv.CsvFactory +import com.fasterxml.jackson.dataformat.csv.CsvParser +import org.opendc.trace.* +import org.opendc.trace.spi.TableDetails +import org.opendc.trace.spi.TraceFormat +import org.opendc.trace.util.CompositeTableReader +import java.nio.file.Files +import java.nio.file.Path +import java.util.stream.Collectors +import kotlin.io.path.extension +import kotlin.io.path.nameWithoutExtension + +/** + * A format implementation for the Azure v1 format. + */ +public class AzureTraceFormat : TraceFormat { + /** + * The name of this trace format. + */ + override val name: String = "azure" + + /** + * The [CsvFactory] used to create the parser. + */ + private val factory = CsvFactory() + .enable(CsvParser.Feature.ALLOW_COMMENTS) + .enable(CsvParser.Feature.TRIM_SPACES) + + override fun create(path: Path) { + throw UnsupportedOperationException("Writing not supported for this format") + } + + override fun getTables(path: Path): List<String> = listOf(TABLE_RESOURCES, TABLE_RESOURCE_STATES) + + override fun getDetails(path: Path, table: String): TableDetails { + return when (table) { + TABLE_RESOURCES -> TableDetails( + listOf( + RESOURCE_ID, + RESOURCE_START_TIME, + RESOURCE_STOP_TIME, + RESOURCE_CPU_COUNT, + RESOURCE_MEM_CAPACITY + ) + ) + TABLE_RESOURCE_STATES -> TableDetails( + listOf( + RESOURCE_ID, + RESOURCE_STATE_TIMESTAMP, + RESOURCE_STATE_CPU_USAGE_PCT + ), + listOf(RESOURCE_STATE_TIMESTAMP) + ) + else -> throw IllegalArgumentException("Table $table not supported") + } + } + + override fun newReader(path: Path, table: String): TableReader { + return when (table) { + TABLE_RESOURCES -> AzureResourceTableReader(factory.createParser(path.resolve("vmtable/vmtable.csv").toFile())) + TABLE_RESOURCE_STATES -> newResourceStateReader(path) + else -> throw IllegalArgumentException("Table $table not supported") + } + } + + override fun newWriter(path: Path, table: String): TableWriter { + throw UnsupportedOperationException("Writing not supported for this format") + } + + /** + * Construct a [TableReader] for reading over all VM CPU readings. + */ + private fun newResourceStateReader(path: Path): TableReader { + val partitions = Files.walk(path.resolve("vm_cpu_readings"), 1) + .filter { !Files.isDirectory(it) && it.extension == "csv" } + .collect(Collectors.toMap({ it.nameWithoutExtension }, { it })) + .toSortedMap() + val it = partitions.iterator() + + return object : CompositeTableReader() { + override fun nextReader(): TableReader? { + return if (it.hasNext()) { + val (_, partPath) = it.next() + return AzureResourceStateTableReader(factory.createParser(partPath.toFile())) + } else { + null + } + } + + override fun toString(): String = "AzureCompositeTableReader" + } + } +} diff --git a/opendc-trace/opendc-trace-azure/src/main/resources/META-INF/services/org.opendc.trace.spi.TraceFormat b/opendc-trace/opendc-trace-azure/src/main/resources/META-INF/services/org.opendc.trace.spi.TraceFormat new file mode 100644 index 00000000..08e75529 --- /dev/null +++ b/opendc-trace/opendc-trace-azure/src/main/resources/META-INF/services/org.opendc.trace.spi.TraceFormat @@ -0,0 +1 @@ +org.opendc.trace.azure.AzureTraceFormat diff --git a/opendc-trace/opendc-trace-azure/src/test/kotlin/org/opendc/trace/azure/AzureTraceFormatTest.kt b/opendc-trace/opendc-trace-azure/src/test/kotlin/org/opendc/trace/azure/AzureTraceFormatTest.kt new file mode 100644 index 00000000..b73bb728 --- /dev/null +++ b/opendc-trace/opendc-trace-azure/src/test/kotlin/org/opendc/trace/azure/AzureTraceFormatTest.kt @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.trace.azure + +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.opendc.trace.* +import java.nio.file.Paths + +/** + * Test suite for the [AzureTraceFormat] class. + */ +class AzureTraceFormatTest { + private val format = AzureTraceFormat() + + @Test + fun testTables() { + val path = Paths.get("src/test/resources/trace") + + assertEquals(listOf(TABLE_RESOURCES, TABLE_RESOURCE_STATES), format.getTables(path)) + } + + @Test + fun testTableExists() { + val path = Paths.get("src/test/resources/trace") + + assertDoesNotThrow { format.getDetails(path, TABLE_RESOURCE_STATES) } + } + + @Test + fun testTableDoesNotExist() { + val path = Paths.get("src/test/resources/trace") + assertThrows<IllegalArgumentException> { format.getDetails(path, "test") } + } + + @Test + fun testResources() { + val path = Paths.get("src/test/resources/trace") + val reader = format.newReader(path, TABLE_RESOURCES) + assertAll( + { assertTrue(reader.nextRow()) }, + { assertEquals("x/XsOfHO4ocsV99i4NluqKDuxctW2MMVmwqOPAlg4wp8mqbBOe3wxBlQo0+Qx+uf", reader.get(RESOURCE_ID)) }, + { assertEquals(1, reader.getInt(RESOURCE_CPU_COUNT)) }, + { assertEquals(1750000.0, reader.getDouble(RESOURCE_MEM_CAPACITY)) }, + ) + + reader.close() + } + + @Test + fun testSmoke() { + val path = Paths.get("src/test/resources/trace") + val reader = format.newReader(path, TABLE_RESOURCE_STATES) + + assertAll( + { assertTrue(reader.nextRow()) }, + { assertEquals("+ZcrOp5/c/fJ6mVgP5qMZlOAGDwyjaaDNM0WoWOt2IDb47gT0UwK9lFwkPQv3C7Q", reader.get(RESOURCE_ID)) }, + { assertEquals(0, reader.get(RESOURCE_STATE_TIMESTAMP).epochSecond) }, + { assertEquals(2.86979, reader.getDouble(RESOURCE_STATE_CPU_USAGE_PCT), 0.01) } + ) + + reader.close() + } +} diff --git a/opendc-trace/opendc-trace-azure/src/test/resources/trace/vm_cpu_readings/vm_cpu_readings-file-1-of-125.csv b/opendc-trace/opendc-trace-azure/src/test/resources/trace/vm_cpu_readings/vm_cpu_readings-file-1-of-125.csv new file mode 100644 index 00000000..db6ddf8a --- /dev/null +++ b/opendc-trace/opendc-trace-azure/src/test/resources/trace/vm_cpu_readings/vm_cpu_readings-file-1-of-125.csv @@ -0,0 +1,100 @@ +0,+ZcrOp5/c/fJ6mVgP5qMZlOAGDwyjaaDNM0WoWOt2IDb47gT0UwK9lFwkPQv3C7Q,2.052803,3.911587,2.86979
+0,2zrgeOqUDy+l0GVi5NXudU+3sqZH+nLowfcz+D/JsCymTXbKrRf1Hr3OjAtxjnKm,1.64695,8.794403,3.254472
+0,/34Wh1Kq/qkNkW0tQrMiQ1eZ8hg9hHopydCzsXriefhgrn+0Rg1j22k1IHcV6PIQ,2.440088,6.941048,4.33624
+0,2lzdXk1Rqn1ibH2kZhGamYTMvVcRP6+x8b5zGiD/8t++5BQhzU18hGaL5sfR01Lo,0.302992,2.046712,0.970692
+0,0GrUQuLhCER5bWWcoJAgblPJWkaU4v3nf+NUrZnFTlXWEK99qgTRBTkjjUjJVAqA,1.515922,4.471657,2.438805
+0,2I8OpI6bMkdzL3HYLz4KlBcDhy2VTEm3skQbOvEo9rLoxryB0iB9iVh3rGd5DW2j,0.148552,0.315007,0.264341
+0,2IuuDcRMd97gln/+CrgPqI/fwffx67s87T1odKrA0wLYf8YuzGooHdKihitv2Q+s,0.169838,2.277277,0.859669
+0,2KaB1faO0ZB2KqB8MGwasWkqRLJHIE6+2wPuhzlzLNEUyeGzo0dU7brFa/cll/VJ,0.539162,1.371926,0.782212
+0,2BMVXt472mr/Y8m1vaaGoyGTSXcLvXk968PCHixwCDjPSgCm7yYSimGuBw7VPIiS,3.625195,7.211996,4.807884
+0,/3+EY60PnzKwod6nAUGBFSDDpBBOVEVUi90JWWWjPAlNyTUrGwlfQcSDoSkRumD7,0.180582,1.313473,0.43792
+0,+hulsuci78MKSG60G/gHJLqmz5/TFEB3WpS6HI1G1mm052le8oeemF3kz3eoPsnS,2.653344,9.983403,4.262461
+0,0O4otykohyRcsqqsg68kqo6ZCY6sL6eQLHUMYZxGVRhwQmTXRUN89izib3pOucrC,0.72983,4.516831,1.846142
+0,239KfRqrlUdyYuU0ubcASPKztu3q7hernahrolO5AczjUFI/QgoU+OoKzPuivFHQ,1.42953,11.553488,4.271241
+0,/SVzWHvPhr7KAIOUFr10EK8WdKXbrJojgcc4IGvutJ2S6HpRMD0zTfv/h0720+Q6,1.676634,6.703915,3.102252
+0,2a7bYEHqZvcgOeos5Q3J5qxpY4lXinv8M9mORfel5DlWRut0JynZtobNGNlBWn41,0.54178,8.239316,2.234877
+0,1NwFYwEAgv8qnCaWzzWv9hHj0TIJAZ2HT+iH+dsZKeSAPGoJGyVSDB+Zj4EuqWRC,2.933124,4.630701,3.975568
+0,3rg4SRyS/p6eMuGCJpjkz4oHzXSeeF16a7jJ9GAAYPiAQAsQNOEjHOe07on5RbjK,0.719536,3.383279,1.506528
+0,0DVV+uR/jr4XbwYQhVf2Yg0Kg7DfIDa7qJNzqvjVgEqGRJAUisrnYFv7AWr1k7by,0.949333,22.157649,3.751856
+0,3bHtb6EIFo9yXByIhpVDOJ7bzbIQvnGGb+jm8eOsEf0eKbrKMJvUiOYc6Wq3DXbR,3.504534,15.581505,6.388424
+0,0O5yc/ZVSHWxf4UHf/1b8Nut9raakorgqDwGV9k7TJdq55alNeMDB7CREuxZystP,7.587743,20.323464,16.540802
+0,0ZbYi+cMH7hCzT+8ICYVp5ZgcRUFNKsODuH09bbPdPioUPCPkBK2PM2oHhE3y4I6,2.694185,6.361789,4.55337
+0,1jw70sEl89jY2iRpd38PuYSBiOcuwe6tF4Q+YuGBJg20+gRIW3A7H3WZ+uL0EVmb,3.570395,5.707428,4.233997
+0,24MvpVXzcNO2qxwF4hMwCToFTBfoAE5xUQ4L6fwfWuBZ1GW06hHh5jWwWCu+8lPm,5.102273,8.678012,6.369649
+0,2gHzFAqM+fL7f1wtNETuzSoM7I6xlEWk2BJmj1SNXly/7z1RQFmwYRXU49DiYciJ,0.27374,54.447146,5.445003
+0,/hCom+lGMIkeE1wQi+VTFh+zzgbikbO0jQDzchDMCUNSgo6cEJfD1sIT2Ok4NlD6,0.170892,1.843549,0.737087
+0,2UwesOu8HXTdHyj0jd1agckz1KH5+Z4KOFe+wKFo9uvRI4GalozAPaxsMrBmx7Wo,3.349887,6.272554,4.425039
+0,1V8Fr/ZhjQcxql5s9p3hA1b0Wx6Sx9e+np1OImlp3GKyleH87bYjmQLZJouKYJR2,2.022219,4.724097,2.616506
+0,23E/SPMZKCUWz8nBmuCdbNBWf9ou6IQuZjmh0x2/icPrbLLvUk5SvbTjwqoLQxBX,0,0.46365,0.178483
+0,0Mj8nT0fnkeMIbcTBf27pOtUuTtMZH8uAZqAViSaye+9mBIjsNPmU6Z5hLK6f2I0,15.023186,23.297875,18.965327
+0,2xM0uOcqSowNzsbFbzhy5J1Ms2vv0jVQ5aM+J2E/LCBzTVKPrCCeWQ/r/cKmS1Tm,8.272075,9.415241,8.797159
+0,0MYQXyW75q9UURkn+O/V6iww0JaBl2qRG0Mh2bqRcuU5/Ws+7HJMPKSzVKlUEgcU,3.798828,8.915124,4.856879
+0,/HQfnMjgclpCxPod9jmGVQxfTnsjyNWA4KNkLMn4IKRlqheUo9AhhWv4vAumZNqg,4.788548,7.269977,6.640435
+0,0Q2PP+9O7LcnNI7AJQQR7pwM4ISG4024Z+INOw+TWgf2DCl8/prdGC7QJRGjc+Aa,0.10703,0.183798,0.136907
+0,/zLQxB1DGXC7iK7JeyYrUSguf6DjNA1MVTJzieRWmcobm0M+xgd28r842y3p5u5J,1.306953,3.22913,2.226509
+0,42cXpXkVqdXH/ok/tD46zKKCToy0k6HXoH3x7eeo4+zIva3IJKle5xfSEW3R45ON,1.018462,3.240817,2.196357
+0,+9HYwMx1Ckj15bJswEycBgiBSfrBw5NJE3p86IeFpFYKKxdw3NzMPTFKpg67XhsF,1.859664,7.255261,3.501303
+0,10KKTL95cApo6Pf24KZqgrM67v4M6rgZBoX+w/I3j4KS66FNhKomGnap9H8SVAvy,0.041225,2.593651,0.25894
+0,+LyaeKb1faiLEjAzynXF3xO/ZAho1R/Zyh1H4d45+NGsIJR6ryUTDmhyNvMh1wQ9,4.614357,11.692623,6.05005
+0,1SS5EeD9rxdWRFYBkR36PAd96w+Q7V2V4fDcc/2IJ1L07In7RGpQk/HVcOTKd78w,0.020435,0.515471,0.135453
+0,+HFoxb6Eu9kwzVkxs+A+9Q7zXa4aSIcOFm3AnYDCTQQMYyf6EST9nSHslGhUkgAD,8.53904,48.459572,16.166212
+0,+N+B5FPJIUVyH9v1Zcc+kjSTNvULkosDBM48N2JkDjhuVhQtWSfYQMQTQkGeVjLi,3.139119,99.036916,51.090982
+0,1ey9c7Hc1FyxLVbESoty7AkXbuENFSDXRAZiizFifRmJNM6IEx9eNu3bkUR+qCUJ,2.466582,5.842213,3.765056
+0,35F/52yPsKPGondM8xnzX68EKiKiKiZMDqsVnvc9ZOAc/rS3zvQ6YYj3QkLAHFhN,1.963258,43.494868,16.459037
+0,2KX+BTc0TPZOtCgbzKtKvP1yrM+Cc3WQU9DPkZDFD/5aNN/aPV40aQCKwW/HeTzh,1.040522,5.961609,3.305858
+0,+8X+qRHRLwwgj70uuXrkus7lrNtjMeTHfy5yQgymNJI+yFd5pbhRfStfS7lkVOhP,0.436353,15.995153,1.431229
+0,/g5MAtFnYaMO5MpJg40BsFmhS22s0tfwHiivGhPbcZ+KgEAtNxKkFdZYDtrDUUFO,6.905489,8.196952,7.527238
+0,/ke0seVq80UFQeXSTUh5hTrjghtn5qqWf38lQVTis+/ZR6Pdv5vdAotz4dvZcKDp,6.444482,23.136676,15.470455
+0,+tQeKqKqbAui7YXK0Efk3GUnvbzM+0pOpmOJ6OhkMSozjRyl5tHl7+mZwFznU3Mk,17.90259,20.095464,18.937014
+0,/hiC5yD45GhNtMpJTVwVF5ZnNNWfEHttESv/+KH6go9FBoncns+CuQ1M92c0xzFA,2.290396,2.609893,2.523336
+0,0i9+1LVd2t4m1KScDuoJnAAEL0bz9UGXh2iLAGV/8Eq5hTsAliyraV7j6wsf2MZX,4.266491,16.607137,6.929279
+0,2PVcv0/vy8mIjzH7CiB9cJU737jRi6kAO7PhqkxEWA4GrxvaCsK3ZDckhD8YR04U,1.048596,2.309172,1.447266
+0,/kbT+MIfY7jEW2Nn+TKf5BKkLAmBslDqKuZ8HI2Ire6eMKinGP7aTt6SY77vt8PK,2.409783,7.79851,5.552826
+0,2cCRKSXs9v9tPskjJn8UmV15qynI3I3GLPTor/i81nxh5Ocwb7Fq1zwEN5zmtXyx,0.356014,1.468193,0.781642
+0,2qsVNbcvPD0H3cs/p/6MTpuvUBtr5QN3iavAmkCQBCtrHcEpgskYVJf/6WQkEhOF,2.688901,85.501739,37.676562
+0,30FpxnoytvMKoGeJYqwnuL2mPbvKlxpjPIfVT8LKqqFl9smEksQjEzG3lgxhT4U7,2.499018,6.534664,3.508567
+0,/f1C+4xtoPaBxD+FoFdM52MiaWXZEqPqSnBxz4q4XMzoXabJvdddHchLrxc6SlYc,1.894231,15.683948000000001,3.199591
+0,30tz9NOV1bIKUB6uIOy4qZT8BVk3escZ0bWXBD9oedOQN1Qi06pplm7WM9iMvvvL,0.959278,63.599827,14.983399
+0,2q0sA6/4VZfksnucqVASzYgruD9T0219afuGrf3O/u8jpGHpn0k3oWvY35I7x8F/,2.694575,11.900751,5.254742
+0,/Qq/SKTnRJ4RZPWKIdCyPmYQUf+csOcFYS+rVD+kc1OkLboeKHK7CLV88wVVLlm9,55.553347,99.204744,93.215797
+0,2PJIXiy3/m1MNf4SQAQ9xU+LDqsHvyyCIWA2X0nB9kgLyVNh3g9xxpAeUpkXgvK6,0.591771,0.676084,0.628958
+0,/VIH23Tzi+711eCdsc7apDAoSBY6hcNqCu8oaZcPrUQmUXUyH8HJS7Z1DyhR6j/I,3.136726,5.477124,4.036594
+0,3/bNFRCZog1M2qwSCcwMYYos07f/9kRsfeFyaOmT0mNx3ldbNvRRbMBhoseq0DIg,2.993954,5.787727,4.272684
+0,3F+42xbLAiVPTJeHpyDwx6ZXcxArLFiMGGZTa9jmsLIpxxkBqC1QwN8mAwzDqWsU,3.488578,6.178318,4.692753
+0,08iqvtN8ilXeJdfiL86fde5JRTrjuLTp8guNabblV7QqkkAL23TwtLdwuFtg4P9G,3.64316,22.992153,10.256498
+0,0ZiQ/5P4mgnYud0uaI1lZCIJaCzrlEJdnAz8bcFMLDFryCrUJJDecbWQbLo6K69J,2.924592,4.261972,3.543138
+0,28JHlDFu72v9lIhjKLF+h9g1pyPq9+ruVET8NnBGKksclnvwx0WlQ066nh6doanS,1.2833,1.589682,1.353967
+0,3ClcWgHBEw8WzFSqnMYKUib9Abx6RDf3ITN8ivUilopa4t+UTJU0Y/U25sT/1okS,1.387814,2.764987,2.116221
+0,/qj8bL8dARqa83U6HwU/bUF5kLq12PKaebM0/2WrM2a3oH+BCC/IxFf1PjIWBNC5,23.139855,97.95723,75.918613
+0,17KWFIkHqLQpslptyD70Qof2iISdFN4IzZBc/WffQeds/tDjuZ/1O4KY68u10srE,2.374392,4.461708,3.201956
+0,3fNyZ1Bf9hUvTVDbHwh8Fh3E2i0BgPPL3QkkS9T0cjanDQA0u0z/Y5TSdXldEJM8,1.199056,3.188352,2.14033
+0,3DYNNYBvhBlVPHsg1uoo7ZVjKX5k1c0gZsfc8W0o0cJ1WJAI8f049TnSu/yIfp/m,1.305688,4.700476,2.216015
+0,2e9qO7smv0DTuXeR3VEzG2jztbM9wntJ3bMt6/LlN3RZBQzIY9vP7FFsphJC9bsW,0.087859,22.556549,10.203507
+0,3EeP6Vgbh292ahLWQJrInzehyR4Nuj2vNtdWuEbvFjKcmCc2i6VZVN4dQTRfIVxR,7.663198,22.199953,15.461753
+0,/Vi7oNg70eAzJHXwsCM9nzwBMg4l7cMyZhUT14V48AWjIAQzVYsbdI0KwNlBAXhK,0.61977,2.24158,1.181003
+0,4/c7nkT3SrtRRrRCsZxUJXxJjUr61iivwZxdihwPAtpCDUawKfPUzaq/05zFYBAk,2.667104,7.383679,4.050989
+0,1HYzfmk+s4SedWtOeHk4j5Zj52ateGX5bRFK5K3rwTVdB2A2m+3iwbL1IEzx8ir8,5.366892,12.404488,6.877072
+0,3vPq2HsXQ9SQT+URugEaQ3ezvstcGd5Bt9FIiFx1SrUfUrvvi/Gj8Nyw5DZhvyAR,3.014601,13.363316,4.535414
+0,2YbmUab2MqBMpvMaoaMP3zVxOhgqkNytraWdt/GG261oZ/tmgEB239WsbKJh1bE3,3.121409,98.73306,51.009852
+0,1IYQhDD8NGuAFnVPnffmt1yk20B9JHQI5DMC4Ny09pe6Sedik6YCIIVeBHIEo34W,1.512222,3.53396,2.379989
+0,1SSMSUcJ7qKM7q2yka80+ZP0yYWiYxGQxcJ8KBi4+TsDpv5FLUS6i2DHLMtXB3An,3.9704,4.345802,4.126586
+0,17CA6zpUCxW+Pdh2g5W0kTdlPlgWbBKz4YrHvbGP/Hmf13nZQBc/VZO7EL6nM75C,8.052588,16.023168,13.600106
+0,04rwScmEvRr0aU/mAE7aKtKFwowolGaTAPyQHuaVKEFmEVMAKxo+7UBCk3vRRRBd,2.221999,5.809178,3.021269
+0,1H9K/TW4c28Aob/H1O53cyQT7pHRww0L1ocyn19z1+MxC+k+5M/PgEx9B3zT/CNf,2.985884,7.584636,3.995057
+0,2fgXOaNZld/i7o20ULRNhCeL+o+vgZYzDOIhQ2n28TcGxXR047+F1b7QiD+l1Ypf,0.068074,0.884132,0.239792
+0,+0bAvqEMTl/RGyFmuz4zJH3DLMI6Q+iHapYn5BpbZI+0PNNfM7PXm/mojw+e8Xpn,3.238927,4.259525,3.611511
+0,3OdFPkhA5Q99wyfxmgyxPAhWyDLkV++XFtPL8pD3w5f8mBWbokeBwgk4gmNIxCOL,0.461767,10.466777,4.985617
+0,0UE8gxQAdCGY+WGN9yd9CL2ZGGqoyGQ2PzQGndwecce24GyTUnuvREbnMWBZZ7bG,0.730279,6.785359,3.363408
+0,/Uk/U5u4d+KNQVPD63pklfxeWc2zDAkUnrVmvxgRTuqNFbn90h8TuU5GZ+OamGQ5,0.105853,1.739301,0.262678
+0,2im96EJfLyxm7TPrtOR9m6Inq4E4/qR+AvP0TbnSdvzXI+N9gHh7C2fzppzcR0i8,0.325895,2.012216,0.802437
+0,+CrXBNhT3ch1hYU2e9IGs7wfjSLRkKYgidJYc42LlsH39cYtwdAX3wKm1OGlf+Kl,18.815771,40.850218,22.470045
+0,/hXRrrjPrAw8xDSsJnEwLdkRN1e42zJLE/HO5DXk5gbGLRmRx5H9n4T0UmraZ8uW,0.361838,0.831517,0.423214
+0,/sTadDDv8poFeLWS7lD/SEtEgWCBHXB1IaiitjCru4AcK8Z32hNXlccdY8hlFzTp,3.203254,5.682829,3.859569
+0,333YaK054AGlUYuw0XWxYn5K8NwzhfzJ3mm4YNwB1YXKjgnO64ZItBNaBRQoOgXn,0.124811,0.384592,0.257066
+0,+ZkQz7QrPZIODz45A+60ZFnG18jnyYlSY/IgEe1Yj8c4cU8h+L8WDIKMv2uB7EwD,1.022656,6.508863,3.368929
+0,+X4DW7zA6whRfOWSHHONJ1u3f0DyBvC9PqDmXGFfbxT4aUGCC6kVm6fuGu9IsQyL,3.428286,15.183059,5.743137
+0,2KXdN0Pb4iyu0jVPocTTf3dwk2Z1LjIlAcydV3HURGIUn1dTycCDDCHg5G6l6i9t,0.282044,0.40582,0.311669
+0,2lGxRtUbBrRZmIYagONMp6vj0zHk4EGhu0aSH5Ws/CAXwBNZpCavBFDNCEcPsOkt,3.662958,8.660027,5.281077
+0,+IR6CKA4zeO742dCx1l2hR0plhTanlaxPWAbckkZNo6UAti83TpYPRXrrfdmm9Ar,0.086237,2.450893,0.969819
+0,2/hWJ+i+1FSHiD44Rr3S4xWMUHC6hIgoVBX2XGZ7cOFyLn9FWQ3Kevsocw7CGaxJ,1.499537,2.832775,1.900258
+0,1WnALZnCvRlfqnuRyrIf0wxQOGLhGuvxInHelnMBM6cw9G9hydTBxqV60JSL/48p,0.717535,5.066802,1.448937
diff --git a/opendc-trace/opendc-trace-azure/src/test/resources/trace/vmtable/vmtable.csv b/opendc-trace/opendc-trace-azure/src/test/resources/trace/vmtable/vmtable.csv new file mode 100644 index 00000000..299c518c --- /dev/null +++ b/opendc-trace/opendc-trace-azure/src/test/resources/trace/vmtable/vmtable.csv @@ -0,0 +1,10 @@ +x/XsOfHO4ocsV99i4NluqKDuxctW2MMVmwqOPAlg4wp8mqbBOe3wxBlQo0+Qx+uf,VDU4C8cqdr+ORcqquwMRcsBA2l0SC6lCPys0wdghKROuxPYysA2XYii9Y5ZkaYaq,Pc2VLB8aDxK2DCC96itq4vW/zVDp4wioAUiB3HoGSFYQ0o6/ZCegTpb9vEH4LeMTEWVObHTPRYEY81TYivZCMQ==,0,2591700,99.369869,3.4240942342446719,10.194309,Delay-insensitive,1,1.75
+H5CxmMoVcZSpjgGbohnVA3R+7uCTe/hM2ht2uIYi3t7KwXB4tkBxmZHBrt2A4x+n,BSXOcywx8pUU0DueDo6UMol1YzR6tn47KLEKaoXp0a1bf2PpzJ7n7lLlmhQ0OJf9,3J17LcV4gXjFat62qhVFRfoiWArHnY763HVqqI6orJCfV8h5j9yeotRMnCLlX1ooGkMyQ2MDOuY1oz111AGN9Q==,0,1539300,100,6.18178366757598,33.98136,Interactive,1,0.75
+wR/G1YUjpMP4zUbxGM/XJNhYS8cAK3SGKM2tqhF7VdeTUYHGktQiKQNoDTtYvnAc,VDU4C8cqdr+ORcqquwMRcsBA2l0SC6lCPys0wdghKROuxPYysA2XYii9Y5ZkaYaq,Pc2VLB8aDxK2DCC96itq4vW/zVDp4wioAUiB3HoGSFYQ0o6/ZCegTpb9vEH4LeMT+hzuAPZnYJMu61JNhTDF/Q==,2188800,2591700,99.569027,3.5736346071428589,7.92425,Delay-insensitive,1,1.75
+1XiU+KpvIa3T1XP8kk3ZY71Of03+ogFL5Pag9Mc2jBuh0YqeW0Zcb9lepKLdPEDg,8u+M3WcFp8pq183WoMB79PhK7xUzbaviOBv0qWN6Xn4mbuNVM1GYJlIjswgit+k1,DHbeI+pYTYFjH8JAF8SewM0z/4SqQctvxcBRGIRglBmeLW5VjISVEw7/IpY345kHwHtk7+SKlEwc1upnT3PigA==,0,2591700,99.405085,16.2876105408034,95.69789,Delay-insensitive,8,56
+z5i2HiSaz6ZdLR6PXdnDjGva3jIlkMPXx23VtfXx9q3dXFRBQrxCOj7sHUsrmFLa,VDU4C8cqdr+ORcqquwMRcsBA2l0SC6lCPys0wdghKROuxPYysA2XYii9Y5ZkaYaq,Pc2VLB8aDxK2DCC96itq4vW/zVDp4wioAUiB3HoGSFYQ0o6/ZCegTpb9vEH4LeMTEWVObHTPRYEY81TYivZCMQ==,0,2188500,98.967961,3.036037969572376,9.445484,Delay-insensitive,1,1.75
+n77nP00/UpJmT+Yx1ZkDphvAqPoHU8yUpDCwyUtPNlRENqvNp6Inya1eiy7VP1+x,8u+M3WcFp8pq183WoMB79PhK7xUzbaviOBv0qWN6Xn4mbuNVM1GYJlIjswgit+k1,DHbeI+pYTYFjH8JAF8SewM0z/4SqQctvxcBRGIRglBmeLW5VjISVEw7/IpY345kHwHtk7+SKlEwc1upnT3PigA==,0,2591700,99.448473,34.17401179027781,98.553018,Delay-insensitive,8,56
+aTSXW3N1KepxKYwKumd7T1+f7DkGolSKV8EArYAdctjD26YqSMKezCVSdvmSgqIQ,dBub/K+8I6jD9t2ExqUdRNlVxPPvDWqICA9Sr+yzcBZ/nNuC0W2swapPoBNIRoF+,C9GnRqFF2lzW/elUsLEwhyAQj9D/d5JIOOgvwfPL1aINf+m1f29G7nXhr6mRPGbiofmjfP9GkepcWz9LX5tp7Q==,2290500,2292300,94.113335,32.461745857142866,94.113335,Unkown,1,1.75
+uSkGH3DS6BVo3RFnw3GZb6WCFSmGgvgKi4HIj08yxO4f5ladUQc3pqDOtqRN0W9+,8u+M3WcFp8pq183WoMB79PhK7xUzbaviOBv0qWN6Xn4mbuNVM1GYJlIjswgit+k1,DHbeI+pYTYFjH8JAF8SewM0z/4SqQctvxcBRGIRglBmeLW5VjISVEw7/IpY345kHwHtk7+SKlEwc1upnT3PigA==,0,2591700,99.276369,1.3500837561060346,23.450372,Delay-insensitive,8,56
+ztRY/Sk5mrSFFcpy2usZ0YZZ7Eumq130/5BB8WVXfWaYvFkU+EhXUQ2kOFkCXuCw,dBub/K+8I6jD9t2ExqUdRNlVxPPvDWqICA9Sr+yzcBZ/nNuC0W2swapPoBNIRoF+,C9GnRqFF2lzW/elUsLEwhyAQj9D/d5JIOOgvwfPL1aINf+m1f29G7nXhr6mRPGbiofmjfP9GkepcWz9LX5tp7Q==,2281200,2300100,98.671595,43.724999781249991,98.13707,Unkown,1,1.75
+bJoIb8ras2ZNNSdAz3CAu4HYRd6k9MOqij/+6/+/5XaYw4+EoGdUEr74DCi974gJ,8u+M3WcFp8pq183WoMB79PhK7xUzbaviOBv0qWN6Xn4mbuNVM1GYJlIjswgit+k1,DHbeI+pYTYFjH8JAF8SewM0z/4SqQctvxcBRGIRglBmeLW5VjISVEw7/IpY345kHwHtk7+SKlEwc1upnT3PigA==,0,2591700,99.498748,18.989459534151351,94.751666,Interactive,8,56
diff --git a/opendc-trace/opendc-trace-bitbrains/build.gradle.kts b/opendc-trace/opendc-trace-bitbrains/build.gradle.kts new file mode 100644 index 00000000..d195cbbb --- /dev/null +++ b/opendc-trace/opendc-trace-bitbrains/build.gradle.kts @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +description = "Support for GWF traces in OpenDC" + +/* Build configuration */ +plugins { + `kotlin-library-conventions` + `testing-conventions` + `jacoco-conventions` +} + +dependencies { + api(platform(projects.opendcPlatform)) + api(projects.opendcTrace.opendcTraceApi) + implementation(libs.jackson.dataformat.csv) +} diff --git a/opendc-trace/opendc-trace-bitbrains/src/main/kotlin/org/opendc/trace/bitbrains/BitbrainsExResourceStateTableReader.kt b/opendc-trace/opendc-trace-bitbrains/src/main/kotlin/org/opendc/trace/bitbrains/BitbrainsExResourceStateTableReader.kt new file mode 100644 index 00000000..c1b6f5ba --- /dev/null +++ b/opendc-trace/opendc-trace-bitbrains/src/main/kotlin/org/opendc/trace/bitbrains/BitbrainsExResourceStateTableReader.kt @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.trace.bitbrains + +import org.opendc.trace.* +import java.io.BufferedReader +import java.time.Instant + +/** + * A [TableReader] for the Bitbrains resource state table. + */ +internal class BitbrainsExResourceStateTableReader(private val reader: BufferedReader) : TableReader { + override fun nextRow(): Boolean { + reset() + + var line: String + var num = 0 + + while (true) { + line = reader.readLine() ?: return false + num++ + + if (line[0] == '#' || line.isBlank()) { + // Ignore empty lines or comments + continue + } + + break + } + + line = line.trim() + + val length = line.length + var col = 0 + var start: Int + var end = 0 + + while (end < length) { + // Trim all whitespace before the field + start = end + while (start < length && line[start].isWhitespace()) { + start++ + } + + end = line.indexOf(' ', start) + + if (end < 0) { + end = length + } + + val field = line.subSequence(start, end) as String + when (col++) { + COL_TIMESTAMP -> timestamp = Instant.ofEpochSecond(field.toLong(10)) + COL_CPU_USAGE -> cpuUsage = field.toDouble() + COL_CPU_DEMAND -> cpuDemand = field.toDouble() + COL_DISK_READ -> diskRead = field.toDouble() + COL_DISK_WRITE -> diskWrite = field.toDouble() + COL_CLUSTER_ID -> cluster = field.trim() + COL_NCPUS -> cpuCores = field.toInt(10) + COL_CPU_READY_PCT -> cpuReadyPct = field.toDouble() + COL_POWERED_ON -> poweredOn = field.toInt(10) == 1 + COL_CPU_CAPACITY -> cpuCapacity = field.toDouble() + COL_ID -> id = field.trim() + COL_MEM_CAPACITY -> memCapacity = field.toDouble() * 1000 // Convert from MB to KB + } + } + + return true + } + + override fun resolve(column: TableColumn<*>): Int = columns[column] ?: -1 + + override fun isNull(index: Int): Boolean { + require(index in 0..COL_MAX) { "Invalid column index" } + return false + } + + override fun get(index: Int): Any? { + return when (index) { + COL_ID -> id + COL_CLUSTER_ID -> cluster + COL_TIMESTAMP -> timestamp + COL_NCPUS -> getInt(index) + COL_POWERED_ON -> getInt(index) + COL_CPU_CAPACITY, COL_CPU_USAGE, COL_CPU_USAGE_PCT, COL_CPU_READY_PCT, COL_CPU_DEMAND, COL_MEM_CAPACITY, COL_DISK_READ, COL_DISK_WRITE -> getDouble(index) + else -> throw IllegalArgumentException("Invalid column") + } + } + + override fun getBoolean(index: Int): Boolean { + return when (index) { + COL_POWERED_ON -> poweredOn + else -> throw IllegalArgumentException("Invalid column") + } + } + + override fun getInt(index: Int): Int { + return when (index) { + COL_NCPUS -> cpuCores + else -> throw IllegalArgumentException("Invalid column") + } + } + + override fun getLong(index: Int): Long { + throw IllegalArgumentException("Invalid column") + } + + override fun getDouble(index: Int): Double { + return when (index) { + COL_CPU_CAPACITY -> cpuCapacity + COL_CPU_USAGE -> cpuUsage + COL_CPU_USAGE_PCT -> cpuUsage / cpuCapacity + COL_CPU_READY_PCT -> cpuReadyPct + COL_CPU_DEMAND -> cpuDemand + COL_MEM_CAPACITY -> memCapacity + COL_DISK_READ -> diskRead + COL_DISK_WRITE -> diskWrite + else -> throw IllegalArgumentException("Invalid column") + } + } + + override fun close() { + reader.close() + } + + /** + * State fields of the reader. + */ + private var id: String? = null + private var cluster: String? = null + private var timestamp: Instant? = null + private var cpuCores = -1 + private var cpuCapacity = Double.NaN + private var cpuUsage = Double.NaN + private var cpuDemand = Double.NaN + private var cpuReadyPct = Double.NaN + private var memCapacity = Double.NaN + private var diskRead = Double.NaN + private var diskWrite = Double.NaN + private var poweredOn: Boolean = false + + /** + * Reset the state of the reader. + */ + private fun reset() { + id = null + timestamp = null + cluster = null + cpuCores = -1 + cpuCapacity = Double.NaN + cpuUsage = Double.NaN + cpuDemand = Double.NaN + cpuReadyPct = Double.NaN + memCapacity = Double.NaN + diskRead = Double.NaN + diskWrite = Double.NaN + poweredOn = false + } + + /** + * Default column indices for the extended Bitbrains format. + */ + private val COL_TIMESTAMP = 0 + private val COL_CPU_USAGE = 1 + private val COL_CPU_DEMAND = 2 + private val COL_DISK_READ = 4 + private val COL_DISK_WRITE = 6 + private val COL_CLUSTER_ID = 10 + private val COL_NCPUS = 12 + private val COL_CPU_READY_PCT = 13 + private val COL_POWERED_ON = 14 + private val COL_CPU_CAPACITY = 18 + private val COL_ID = 19 + private val COL_MEM_CAPACITY = 20 + private val COL_CPU_USAGE_PCT = 21 + private val COL_MAX = COL_CPU_USAGE_PCT + 1 + + private val columns = mapOf( + RESOURCE_ID to COL_ID, + RESOURCE_CLUSTER_ID to COL_CLUSTER_ID, + RESOURCE_STATE_TIMESTAMP to COL_TIMESTAMP, + RESOURCE_CPU_COUNT to COL_NCPUS, + RESOURCE_CPU_CAPACITY to COL_CPU_CAPACITY, + RESOURCE_STATE_CPU_USAGE to COL_CPU_USAGE, + RESOURCE_STATE_CPU_USAGE_PCT to COL_CPU_USAGE_PCT, + RESOURCE_STATE_CPU_DEMAND to COL_CPU_DEMAND, + RESOURCE_STATE_CPU_READY_PCT to COL_CPU_READY_PCT, + RESOURCE_MEM_CAPACITY to COL_MEM_CAPACITY, + RESOURCE_STATE_DISK_READ to COL_DISK_READ, + RESOURCE_STATE_DISK_WRITE to COL_DISK_WRITE + ) +} diff --git a/opendc-trace/opendc-trace-bitbrains/src/main/kotlin/org/opendc/trace/bitbrains/BitbrainsExTraceFormat.kt b/opendc-trace/opendc-trace-bitbrains/src/main/kotlin/org/opendc/trace/bitbrains/BitbrainsExTraceFormat.kt new file mode 100644 index 00000000..20222c8a --- /dev/null +++ b/opendc-trace/opendc-trace-bitbrains/src/main/kotlin/org/opendc/trace/bitbrains/BitbrainsExTraceFormat.kt @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.trace.bitbrains + +import org.opendc.trace.* +import org.opendc.trace.spi.TableDetails +import org.opendc.trace.spi.TraceFormat +import org.opendc.trace.util.CompositeTableReader +import java.nio.file.Files +import java.nio.file.Path +import java.util.stream.Collectors +import kotlin.io.path.bufferedReader +import kotlin.io.path.extension +import kotlin.io.path.nameWithoutExtension + +/** + * A format implementation for the extended Bitbrains trace format. + */ +public class BitbrainsExTraceFormat : TraceFormat { + /** + * The name of this trace format. + */ + override val name: String = "bitbrains-ex" + + override fun create(path: Path) { + throw UnsupportedOperationException("Writing not supported for this format") + } + + override fun getTables(path: Path): List<String> = listOf(TABLE_RESOURCE_STATES) + + override fun getDetails(path: Path, table: String): TableDetails { + return when (table) { + TABLE_RESOURCE_STATES -> TableDetails( + listOf( + RESOURCE_ID, + RESOURCE_CLUSTER_ID, + RESOURCE_STATE_TIMESTAMP, + RESOURCE_CPU_COUNT, + RESOURCE_CPU_CAPACITY, + RESOURCE_STATE_CPU_USAGE, + RESOURCE_STATE_CPU_USAGE_PCT, + RESOURCE_STATE_CPU_DEMAND, + RESOURCE_STATE_CPU_READY_PCT, + RESOURCE_MEM_CAPACITY, + RESOURCE_STATE_DISK_READ, + RESOURCE_STATE_DISK_WRITE + ), + listOf(RESOURCE_ID, RESOURCE_STATE_TIMESTAMP) + ) + else -> throw IllegalArgumentException("Table $table not supported") + } + } + + override fun newReader(path: Path, table: String): TableReader { + return when (table) { + TABLE_RESOURCE_STATES -> newResourceStateReader(path) + else -> throw IllegalArgumentException("Table $table not supported") + } + } + + override fun newWriter(path: Path, table: String): TableWriter { + throw UnsupportedOperationException("Writing not supported for this format") + } + + /** + * Construct a [TableReader] for reading over all resource state partitions. + */ + private fun newResourceStateReader(path: Path): TableReader { + val partitions = Files.walk(path, 1) + .filter { !Files.isDirectory(it) && it.extension == "txt" } + .collect(Collectors.toMap({ it.nameWithoutExtension }, { it })) + .toSortedMap() + val it = partitions.iterator() + + return object : CompositeTableReader() { + override fun nextReader(): TableReader? { + return if (it.hasNext()) { + val (_, partPath) = it.next() + return BitbrainsExResourceStateTableReader(partPath.bufferedReader()) + } else { + null + } + } + + override fun toString(): String = "BitbrainsExCompositeTableReader" + } + } +} diff --git a/opendc-trace/opendc-trace-bitbrains/src/main/kotlin/org/opendc/trace/bitbrains/BitbrainsResourceStateTableReader.kt b/opendc-trace/opendc-trace-bitbrains/src/main/kotlin/org/opendc/trace/bitbrains/BitbrainsResourceStateTableReader.kt new file mode 100644 index 00000000..3a8839b4 --- /dev/null +++ b/opendc-trace/opendc-trace-bitbrains/src/main/kotlin/org/opendc/trace/bitbrains/BitbrainsResourceStateTableReader.kt @@ -0,0 +1,288 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.trace.bitbrains + +import com.fasterxml.jackson.core.JsonParseException +import com.fasterxml.jackson.core.JsonToken +import com.fasterxml.jackson.dataformat.csv.CsvParser +import com.fasterxml.jackson.dataformat.csv.CsvSchema +import org.opendc.trace.* +import java.text.NumberFormat +import java.time.Instant +import java.time.LocalDateTime +import java.time.ZoneOffset +import java.time.format.DateTimeFormatter +import java.time.format.DateTimeParseException +import java.util.* + +/** + * A [TableReader] for the Bitbrains resource state table. + */ +internal class BitbrainsResourceStateTableReader(private val partition: String, private val parser: CsvParser) : TableReader { + /** + * The [DateTimeFormatter] used to parse the timestamps in case of the Materna trace. + */ + private val formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss") + + /** + * The type of timestamps in the trace. + */ + private var timestampType: TimestampType = TimestampType.UNDECIDED + + /** + * The [NumberFormat] used to parse doubles containing a comma. + */ + private val nf = NumberFormat.getInstance(Locale.GERMAN) + + /** + * A flag to indicate that the trace contains decimals with a comma separator. + */ + private var usesCommaDecimalSeparator = false + + init { + parser.schema = schema + } + + override fun nextRow(): Boolean { + // Reset the row state + reset() + + if (!nextStart()) { + return false + } + + while (true) { + val token = parser.nextValue() + + if (token == null || token == JsonToken.END_OBJECT) { + break + } + + when (parser.currentName) { + "Timestamp [ms]" -> { + timestamp = when (timestampType) { + TimestampType.UNDECIDED -> { + try { + val res = LocalDateTime.parse(parser.text, formatter).toInstant(ZoneOffset.UTC) + timestampType = TimestampType.DATE_TIME + res + } catch (e: DateTimeParseException) { + timestampType = TimestampType.EPOCH_MILLIS + Instant.ofEpochSecond(parser.longValue) + } + } + TimestampType.DATE_TIME -> LocalDateTime.parse(parser.text, formatter).toInstant(ZoneOffset.UTC) + TimestampType.EPOCH_MILLIS -> Instant.ofEpochSecond(parser.longValue) + } + } + "CPU cores" -> cpuCores = parser.intValue + "CPU capacity provisioned [MHZ]" -> cpuCapacity = parseSafeDouble() + "CPU usage [MHZ]" -> cpuUsage = parseSafeDouble() + "CPU usage [%]" -> cpuUsagePct = parseSafeDouble() / 100.0 // Convert to range [0, 1] + "Memory capacity provisioned [KB]" -> memCapacity = parseSafeDouble() + "Memory usage [KB]" -> memUsage = parseSafeDouble() + "Disk read throughput [KB/s]" -> diskRead = parseSafeDouble() + "Disk write throughput [KB/s]" -> diskWrite = parseSafeDouble() + "Network received throughput [KB/s]" -> netReceived = parseSafeDouble() + "Network transmitted throughput [KB/s]" -> netTransmitted = parseSafeDouble() + } + } + + return true + } + + override fun resolve(column: TableColumn<*>): Int = columns[column] ?: -1 + + override fun isNull(index: Int): Boolean { + check(index in 0..columns.size) { "Invalid column index" } + return false + } + + override fun get(index: Int): Any? { + return when (index) { + COL_ID -> partition + COL_TIMESTAMP -> timestamp + COL_CPU_COUNT -> getInt(index) + COL_CPU_CAPACITY, COL_CPU_USAGE, COL_CPU_USAGE_PCT, COL_MEM_CAPACITY, COL_MEM_USAGE, COL_DISK_READ, COL_DISK_WRITE, COL_NET_RX, COL_NET_TX -> getDouble(index) + else -> throw IllegalArgumentException("Invalid column") + } + } + + override fun getBoolean(index: Int): Boolean { + throw IllegalArgumentException("Invalid column") + } + + override fun getInt(index: Int): Int { + return when (index) { + COL_CPU_COUNT -> cpuCores + else -> throw IllegalArgumentException("Invalid column") + } + } + + override fun getLong(index: Int): Long { + throw IllegalArgumentException("Invalid column") + } + + override fun getDouble(index: Int): Double { + return when (index) { + COL_CPU_CAPACITY -> cpuCapacity + COL_CPU_USAGE -> cpuUsage + COL_CPU_USAGE_PCT -> cpuUsagePct + COL_MEM_CAPACITY -> memCapacity + COL_MEM_USAGE -> memUsage + COL_DISK_READ -> diskRead + COL_DISK_WRITE -> diskWrite + COL_NET_RX -> netReceived + COL_NET_TX -> netTransmitted + else -> throw IllegalArgumentException("Invalid column") + } + } + + override fun close() { + parser.close() + } + + /** + * Advance the parser until the next object start. + */ + private fun nextStart(): Boolean { + var token = parser.nextValue() + + while (token != null && token != JsonToken.START_OBJECT) { + token = parser.nextValue() + } + + return token != null + } + + /** + * Try to parse the current value safely as double. + */ + private fun parseSafeDouble(): Double { + if (!usesCommaDecimalSeparator) { + try { + return parser.doubleValue + } catch (e: JsonParseException) { + usesCommaDecimalSeparator = true + } + } + + val text = parser.text + if (text.isBlank()) { + return 0.0 + } + + return nf.parse(text).toDouble() + } + + /** + * State fields of the reader. + */ + private var timestamp: Instant? = null + private var cpuCores = -1 + private var cpuCapacity = Double.NaN + private var cpuUsage = Double.NaN + private var cpuUsagePct = Double.NaN + private var memCapacity = Double.NaN + private var memUsage = Double.NaN + private var diskRead = Double.NaN + private var diskWrite = Double.NaN + private var netReceived = Double.NaN + private var netTransmitted = Double.NaN + + /** + * Reset the state. + */ + private fun reset() { + timestamp = null + cpuCores = -1 + cpuCapacity = Double.NaN + cpuUsage = Double.NaN + cpuUsagePct = Double.NaN + memCapacity = Double.NaN + memUsage = Double.NaN + diskRead = Double.NaN + diskWrite = Double.NaN + netReceived = Double.NaN + netTransmitted = Double.NaN + } + + private val COL_TIMESTAMP = 0 + private val COL_CPU_COUNT = 1 + private val COL_CPU_CAPACITY = 2 + private val COL_CPU_USAGE = 3 + private val COL_CPU_USAGE_PCT = 4 + private val COL_MEM_CAPACITY = 5 + private val COL_MEM_USAGE = 6 + private val COL_DISK_READ = 7 + private val COL_DISK_WRITE = 8 + private val COL_NET_RX = 9 + private val COL_NET_TX = 10 + private val COL_ID = 11 + + private val columns = mapOf( + RESOURCE_ID to COL_ID, + RESOURCE_STATE_TIMESTAMP to COL_TIMESTAMP, + RESOURCE_CPU_COUNT to COL_CPU_COUNT, + RESOURCE_CPU_CAPACITY to COL_CPU_CAPACITY, + RESOURCE_STATE_CPU_USAGE to COL_CPU_USAGE, + RESOURCE_STATE_CPU_USAGE_PCT to COL_CPU_USAGE_PCT, + RESOURCE_MEM_CAPACITY to COL_MEM_CAPACITY, + RESOURCE_STATE_MEM_USAGE to COL_MEM_USAGE, + RESOURCE_STATE_DISK_READ to COL_DISK_READ, + RESOURCE_STATE_DISK_WRITE to COL_DISK_WRITE, + RESOURCE_STATE_NET_RX to COL_NET_RX, + RESOURCE_STATE_NET_TX to COL_NET_TX + ) + + /** + * The type of the timestamp in the trace. + */ + private enum class TimestampType { + UNDECIDED, DATE_TIME, EPOCH_MILLIS + } + + companion object { + /** + * The [CsvSchema] that is used to parse the trace. + */ + private val schema = CsvSchema.builder() + .addColumn("Timestamp [ms]", CsvSchema.ColumnType.NUMBER_OR_STRING) + .addColumn("CPU cores", CsvSchema.ColumnType.NUMBER) + .addColumn("CPU capacity provisioned [MHZ]", CsvSchema.ColumnType.NUMBER) + .addColumn("CPU usage [MHZ]", CsvSchema.ColumnType.NUMBER) + .addColumn("CPU usage [%]", CsvSchema.ColumnType.NUMBER) + .addColumn("Memory capacity provisioned [KB]", CsvSchema.ColumnType.NUMBER) + .addColumn("Memory usage [KB]", CsvSchema.ColumnType.NUMBER) + .addColumn("Memory usage [%]", CsvSchema.ColumnType.NUMBER) + .addColumn("Disk read throughput [KB/s]", CsvSchema.ColumnType.NUMBER) + .addColumn("Disk write throughput [KB/s]", CsvSchema.ColumnType.NUMBER) + .addColumn("Disk size [GB]", CsvSchema.ColumnType.NUMBER) + .addColumn("Network received throughput [KB/s]", CsvSchema.ColumnType.NUMBER) + .addColumn("Network transmitted throughput [KB/s]", CsvSchema.ColumnType.NUMBER) + .setAllowComments(true) + .setUseHeader(true) + .setColumnSeparator(';') + .build() + } +} diff --git a/opendc-trace/opendc-trace-bitbrains/src/main/kotlin/org/opendc/trace/bitbrains/BitbrainsResourceTableReader.kt b/opendc-trace/opendc-trace-bitbrains/src/main/kotlin/org/opendc/trace/bitbrains/BitbrainsResourceTableReader.kt new file mode 100644 index 00000000..3701994a --- /dev/null +++ b/opendc-trace/opendc-trace-bitbrains/src/main/kotlin/org/opendc/trace/bitbrains/BitbrainsResourceTableReader.kt @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.trace.bitbrains + +import com.fasterxml.jackson.dataformat.csv.CsvFactory +import org.opendc.trace.* +import java.nio.file.Path + +/** + * A [TableReader] for the Bitbrains resource table. + */ +internal class BitbrainsResourceTableReader(private val factory: CsvFactory, vms: Map<String, Path>) : TableReader { + /** + * An iterator to iterate over the resource entries. + */ + private val it = vms.iterator() + + override fun nextRow(): Boolean { + reset() + + while (it.hasNext()) { + val (name, path) = it.next() + + val parser = factory.createParser(path.toFile()) + val reader = BitbrainsResourceStateTableReader(name, parser) + val idCol = reader.resolve(RESOURCE_ID) + + try { + if (!reader.nextRow()) { + continue + } + + id = reader.get(idCol) as String + return true + } finally { + reader.close() + } + } + + return false + } + + override fun resolve(column: TableColumn<*>): Int = columns[column] ?: -1 + + override fun isNull(index: Int): Boolean { + check(index in 0..columns.size) { "Invalid column index" } + return false + } + + override fun get(index: Int): Any? { + return when (index) { + COL_ID -> id + else -> throw IllegalArgumentException("Invalid column") + } + } + + override fun getBoolean(index: Int): Boolean { + throw IllegalArgumentException("Invalid column") + } + + override fun getInt(index: Int): Int { + throw IllegalArgumentException("Invalid column") + } + + override fun getLong(index: Int): Long { + throw IllegalArgumentException("Invalid column") + } + + override fun getDouble(index: Int): Double { + throw IllegalArgumentException("Invalid column") + } + + override fun close() {} + + /** + * State fields of the reader. + */ + private var id: String? = null + + /** + * Reset the state of the reader. + */ + private fun reset() { + id = null + } + + private val COL_ID = 0 + private val columns = mapOf(RESOURCE_ID to COL_ID) +} diff --git a/opendc-trace/opendc-trace-bitbrains/src/main/kotlin/org/opendc/trace/bitbrains/BitbrainsTraceFormat.kt b/opendc-trace/opendc-trace-bitbrains/src/main/kotlin/org/opendc/trace/bitbrains/BitbrainsTraceFormat.kt new file mode 100644 index 00000000..3885c931 --- /dev/null +++ b/opendc-trace/opendc-trace-bitbrains/src/main/kotlin/org/opendc/trace/bitbrains/BitbrainsTraceFormat.kt @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.trace.bitbrains + +import com.fasterxml.jackson.dataformat.csv.CsvFactory +import com.fasterxml.jackson.dataformat.csv.CsvParser +import org.opendc.trace.* +import org.opendc.trace.spi.TableDetails +import org.opendc.trace.spi.TraceFormat +import org.opendc.trace.util.CompositeTableReader +import java.nio.file.Files +import java.nio.file.Path +import java.util.stream.Collectors +import kotlin.io.path.extension +import kotlin.io.path.nameWithoutExtension + +/** + * A format implementation for the GWF trace format. + */ +public class BitbrainsTraceFormat : TraceFormat { + /** + * The name of this trace format. + */ + override val name: String = "bitbrains" + + /** + * The [CsvFactory] used to create the parser. + */ + private val factory = CsvFactory() + .enable(CsvParser.Feature.ALLOW_COMMENTS) + .enable(CsvParser.Feature.TRIM_SPACES) + + override fun create(path: Path) { + throw UnsupportedOperationException("Writing not supported for this format") + } + + override fun getTables(path: Path): List<String> = listOf(TABLE_RESOURCES, TABLE_RESOURCE_STATES) + + override fun getDetails(path: Path, table: String): TableDetails { + return when (table) { + TABLE_RESOURCES -> TableDetails(listOf(RESOURCE_ID)) + TABLE_RESOURCE_STATES -> TableDetails( + listOf( + RESOURCE_ID, + RESOURCE_STATE_TIMESTAMP, + RESOURCE_CPU_COUNT, + RESOURCE_CPU_CAPACITY, + RESOURCE_STATE_CPU_USAGE, + RESOURCE_STATE_CPU_USAGE_PCT, + RESOURCE_MEM_CAPACITY, + RESOURCE_STATE_MEM_USAGE, + RESOURCE_STATE_DISK_READ, + RESOURCE_STATE_DISK_WRITE, + RESOURCE_STATE_NET_RX, + RESOURCE_STATE_NET_TX, + ), + listOf(RESOURCE_ID, RESOURCE_STATE_TIMESTAMP) + ) + else -> throw IllegalArgumentException("Table $table not supported") + } + } + + override fun newReader(path: Path, table: String): TableReader { + return when (table) { + TABLE_RESOURCES -> { + val vms = Files.walk(path, 1) + .filter { !Files.isDirectory(it) && it.extension == "csv" } + .collect(Collectors.toMap({ it.nameWithoutExtension }, { it })) + .toSortedMap() + BitbrainsResourceTableReader(factory, vms) + } + TABLE_RESOURCE_STATES -> newResourceStateReader(path) + else -> throw IllegalArgumentException("Table $table not supported") + } + } + + override fun newWriter(path: Path, table: String): TableWriter { + throw UnsupportedOperationException("Writing not supported for this format") + } + + /** + * Construct a [TableReader] for reading over all resource state partitions. + */ + private fun newResourceStateReader(path: Path): TableReader { + val partitions = Files.walk(path, 1) + .filter { !Files.isDirectory(it) && it.extension == "csv" } + .collect(Collectors.toMap({ it.nameWithoutExtension }, { it })) + .toSortedMap() + val it = partitions.iterator() + + return object : CompositeTableReader() { + override fun nextReader(): TableReader? { + return if (it.hasNext()) { + val (partition, partPath) = it.next() + return BitbrainsResourceStateTableReader(partition, factory.createParser(partPath.toFile())) + } else { + null + } + } + + override fun toString(): String = "BitbrainsCompositeTableReader" + } + } +} diff --git a/opendc-trace/opendc-trace-bitbrains/src/main/resources/META-INF/services/org.opendc.trace.spi.TraceFormat b/opendc-trace/opendc-trace-bitbrains/src/main/resources/META-INF/services/org.opendc.trace.spi.TraceFormat new file mode 100644 index 00000000..fd6a2180 --- /dev/null +++ b/opendc-trace/opendc-trace-bitbrains/src/main/resources/META-INF/services/org.opendc.trace.spi.TraceFormat @@ -0,0 +1,2 @@ +org.opendc.trace.bitbrains.BitbrainsTraceFormat +org.opendc.trace.bitbrains.BitbrainsExTraceFormat diff --git a/opendc-trace/opendc-trace-bitbrains/src/test/kotlin/org/opendc/trace/bitbrains/BitbrainsExTraceFormatTest.kt b/opendc-trace/opendc-trace-bitbrains/src/test/kotlin/org/opendc/trace/bitbrains/BitbrainsExTraceFormatTest.kt new file mode 100644 index 00000000..d734cf5f --- /dev/null +++ b/opendc-trace/opendc-trace-bitbrains/src/test/kotlin/org/opendc/trace/bitbrains/BitbrainsExTraceFormatTest.kt @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.trace.bitbrains + +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.opendc.trace.* +import java.nio.file.Paths + +/** + * Test suite for the [BitbrainsExTraceFormat] class. + */ +internal class BitbrainsExTraceFormatTest { + private val format = BitbrainsExTraceFormat() + + @Test + fun testTables() { + val path = Paths.get("src/test/resources/vm.txt") + + assertEquals(listOf(TABLE_RESOURCE_STATES), format.getTables(path)) + } + + @Test + fun testTableExists() { + val path = Paths.get("src/test/resources/vm.txt") + + assertDoesNotThrow { format.getDetails(path, TABLE_RESOURCE_STATES) } + } + + @Test + fun testTableDoesNotExist() { + val path = Paths.get("src/test/resources/vm.txt") + assertThrows<IllegalArgumentException> { format.getDetails(path, "test") } + } + + @Test + fun testSmoke() { + val path = Paths.get("src/test/resources/vm.txt") + val reader = format.newReader(path, TABLE_RESOURCE_STATES) + + assertAll( + { assertTrue(reader.nextRow()) }, + { assertEquals(1631911500, reader.get(RESOURCE_STATE_TIMESTAMP).epochSecond) }, + { assertEquals(21.2, reader.getDouble(RESOURCE_STATE_CPU_USAGE), 0.01) } + ) + + reader.close() + } +} diff --git a/opendc-trace/opendc-trace-bitbrains/src/test/kotlin/org/opendc/trace/bitbrains/BitbrainsTraceFormatTest.kt b/opendc-trace/opendc-trace-bitbrains/src/test/kotlin/org/opendc/trace/bitbrains/BitbrainsTraceFormatTest.kt new file mode 100644 index 00000000..41e7def2 --- /dev/null +++ b/opendc-trace/opendc-trace-bitbrains/src/test/kotlin/org/opendc/trace/bitbrains/BitbrainsTraceFormatTest.kt @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.trace.bitbrains + +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.opendc.trace.* +import java.nio.file.Paths + +/** + * Test suite for the [BitbrainsTraceFormat] class. + */ +class BitbrainsTraceFormatTest { + private val format = BitbrainsTraceFormat() + + @Test + fun testTables() { + val path = Paths.get("src/test/resources/bitbrains.csv") + + assertEquals(listOf(TABLE_RESOURCES, TABLE_RESOURCE_STATES), format.getTables(path)) + } + + @Test + fun testTableExists() { + val path = Paths.get("src/test/resources/bitbrains.csv") + + assertDoesNotThrow { format.getDetails(path, TABLE_RESOURCE_STATES) } + } + + @Test + fun testTableDoesNotExist() { + val path = Paths.get("src/test/resources/bitbrains.csv") + assertThrows<IllegalArgumentException> { format.getDetails(path, "test") } + } + + @Test + fun testResources() { + val path = Paths.get("src/test/resources/bitbrains.csv") + val reader = format.newReader(path, TABLE_RESOURCES) + + assertAll( + { assertTrue(reader.nextRow()) }, + { assertEquals("bitbrains", reader.get(RESOURCE_ID)) }, + { assertFalse(reader.nextRow()) } + ) + + reader.close() + } + + @Test + fun testSmoke() { + val path = Paths.get("src/test/resources/bitbrains.csv") + val reader = format.newReader(path, TABLE_RESOURCE_STATES) + + assertAll( + { assertTrue(reader.nextRow()) }, + { assertEquals(1376314846, reader.get(RESOURCE_STATE_TIMESTAMP).epochSecond) }, + { assertEquals(19.066, reader.getDouble(RESOURCE_STATE_CPU_USAGE), 0.01) } + ) + + reader.close() + } +} diff --git a/opendc-trace/opendc-trace-bitbrains/src/test/resources/bitbrains.csv b/opendc-trace/opendc-trace-bitbrains/src/test/resources/bitbrains.csv new file mode 100644 index 00000000..f5e300e8 --- /dev/null +++ b/opendc-trace/opendc-trace-bitbrains/src/test/resources/bitbrains.csv @@ -0,0 +1,8620 @@ +Timestamp [ms]; CPU cores; CPU capacity provisioned [MHZ]; CPU usage [MHZ]; CPU usage [%]; Memory capacity provisioned [KB]; Memory usage [KB]; Disk read throughput [KB/s]; Disk write throughput [KB/s]; Network received throughput [KB/s]; Network transmitted throughput [KB/s] +1376314846; 1; 2599.999309; 19.06666159933333; 0.7333333333333333; 2097152.0; 110448.53333333334; 0.0; 1.4666666666666666; 0.0; 0.0 +1376315146; 1; 2599.999309; 19.06666159933333; 0.7333333333333333; 2097152.0; 106254.13333333333; 0.0; 1.3333333333333333; 0.0; 0.0 +1376315446; 1; 2599.999309; 17.333328726666668; 0.6666666666666667; 2097152.0; 103457.86666666667; 0.0; 1.3333333333333333; 0.0; 0.0 +1376315746; 1; 2599.999309; 13.86666298133333; 0.5333333333333333; 2097152.0; 76893.33333333333; 0.0; 1.2666666666666666; 0.06666666666666667; 0.0 +1376316046; 1; 2599.999309; 13.86666298133333; 0.5333333333333333; 2097152.0; 111846.66666666667; 0.0; 1.3333333333333333; 0.0; 0.0 +1376316346; 1; 2599.999309; 17.333328726666668; 0.6666666666666667; 2097152.0; 90874.66666666667; 0.0; 1.2; 0.0; 0.0 +1376316646; 1; 2599.999309; 19.06666159933333; 0.7333333333333333; 2097152.0; 128624.26666666666; 0.06666666666666667; 8.733333333333333; 0.3333333333333333; 0.6 +1376316946; 1; 2599.999309; 19.06666159933333; 0.7333333333333333; 2097152.0; 134216.8; 0.0; 1.2; 0.0; 0.0 +1376317246; 1; 2599.999309; 13.86666298133333; 0.5333333333333333; 2097152.0; 121633.6; 0.0; 1.2666666666666666; 0.0; 0.0 +1376317546; 1; 2599.999309; 13.86666298133333; 0.5333333333333333; 2097152.0; 117439.2; 0.0; 1.5333333333333334; 0.0; 0.0 +1376317846; 1; 2599.999309; 13.86666298133333; 0.5333333333333333; 2097152.0; 81087.73333333334; 0.0; 1.2666666666666666; 0.0; 0.0 +1376318146; 1; 2599.999309; 17.333328726666668; 0.6666666666666667; 2097152.0; 95069.06666666667; 0.06666666666666667; 1.6; 0.06666666666666667; 0.13333333333333333 +1376318446; 1; 2599.999309; 20.799994471999998; 0.8; 2097152.0; 82485.86666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1376318746; 1; 2599.999309; 13.86666298133333; 0.5333333333333333; 2097152.0; 100661.6; 0.0; 1.2; 0.0; 0.0 +1376319046; 1; 2599.999309; 13.86666298133333; 0.5333333333333333; 2097152.0; 97865.33333333333; 0.0; 1.2; 0.0; 0.0 +1376319346; 1; 2599.999626; 38.99999439; 1.5; 2097152.0; 104856.0; 0.0; 1.2; 0.0; 0.0 +1376319646; 1; 2599.999626; 13.866664671999999; 0.5333333333333333; 2097152.0; 113244.8; 0.0; 1.0; 0.0; 0.0 +1376319946; 1; 2599.999626; 8.666665420000001; 0.33333333333333337; 2097152.0; 100661.6; 0.0; 1.0; 0.0; 0.0 +1376320246; 1; 2599.999626; 41.599994016000004; 1.6; 2097152.0; 150993.6; 0.0; 2.7333333333333334; 67.46666666666667; 0.5333333333333333 +1376320546; 1; 2599.999626; 10.399998504000001; 0.4; 2097152.0; 141207.46666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1376320846; 1; 2599.999626; 0.0; 0.0; 2097152.0; 130022.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1376321146; 1; 2599.999626; 1.7333330839999999; 0.06666666666666667; 2097152.0; 71300.8; 0.0; 1.0; 0.06666666666666667; 0.0 +1376321446; 1; 2599.999626; 0.0; 0.0; 2097152.0; 89476.53333333334; 0.0; 1.1333333333333333; 0.0; 0.0 +1376321746; 1; 2599.999626; 5.1999992520000005; 0.2; 2097152.0; 121633.6; 0.0; 1.3333333333333333; 0.06666666666666667; 0.0 +1376322046; 1; 2599.999626; 8.666665420000001; 0.33333333333333337; 2097152.0; 104856.0; 0.0; 1.1333333333333333; 0.0; 0.0 +1376322346; 1; 2599.999626; 8.666665420000001; 0.33333333333333337; 2097152.0; 121633.6; 0.0; 1.4666666666666666; 0.6; 0.0 +1376322647; 1; 2599.999626; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1376322947; 1; 2599.999626; 1.7333330839999999; 0.06666666666666667; 2097152.0; 75495.2; 0.0; 1.2666666666666666; 0.0; 0.0 +1376323247; 1; 2599.999626; 5.1999992520000005; 0.2; 2097152.0; 117439.2; 0.0; 7.266666666666667; 0.2; 0.13333333333333333 +1376323547; 1; 2599.999626; 12.133331587999999; 0.4666666666666666; 2097152.0; 130022.4; 0.0; 1.2; 0.0; 0.0 +1376323847; 1; 2599.999626; 13.866664671999999; 0.5333333333333333; 2097152.0; 146800.0; 0.0; 2.466666666666667; 0.0; 0.5333333333333333 +1376324147; 1; 2599.999626; 12.133331587999999; 0.4666666666666666; 2097152.0; 110448.53333333334; 0.2; 1.5333333333333334; 0.0; 0.0 +1376324447; 1; 2599.999626; 3.4666661679999997; 0.13333333333333333; 2097152.0; 100661.6; 0.0; 1.5333333333333334; 0.0; 0.0 +1376324747; 1; 2599.999626; 0.0; 0.0; 2097152.0; 62912.0; 0.0; 1.1333333333333333; 0.0; 0.0 +1376325047; 1; 2599.999626; 3.4666661679999997; 0.13333333333333333; 2097152.0; 82485.86666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1376325347; 1; 2599.999626; 3.4666661679999997; 0.13333333333333333; 2097152.0; 125828.0; 0.0; 1.0; 0.0; 0.0 +1376325647; 1; 2599.999626; 3.4666661679999997; 0.13333333333333333; 2097152.0; 125827.2; 0.0; 1.2; 0.0; 0.0 +1376325947; 1; 2599.999626; 3.4666661679999997; 0.13333333333333333; 2097152.0; 83884.0; 0.0; 1.3333333333333333; 0.06666666666666667; 0.0 +1376326247; 1; 2599.999626; 1.7333330839999999; 0.06666666666666667; 2097152.0; 100661.6; 0.0; 1.2666666666666666; 0.0; 0.0 +1376326547; 1; 2599.999626; 1.7333330839999999; 0.06666666666666667; 2097152.0; 86680.26666666666; 0.0; 1.2666666666666666; 0.0; 0.0 +1376326847; 1; 2599.999626; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 1.2666666666666666; 0.0; 0.0 +1376327147; 1; 2599.999626; 8.666665420000001; 0.33333333333333337; 2097152.0; 102059.73333333334; 0.0; 1.3333333333333333; 0.0; 0.0 +1376327447; 1; 2599.999626; 13.866664671999999; 0.5333333333333333; 2097152.0; 198527.73333333334; 0.0; 2.533333333333333; 0.06666666666666667; 0.4666666666666667 +1376327747; 1; 2599.999626; 6.933332335999999; 0.26666666666666666; 2097152.0; 130021.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1376328047; 1; 2599.999626; 3.4666661679999997; 0.13333333333333333; 2097152.0; 83884.0; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1376328347; 1; 2599.999626; 3.4666661679999997; 0.13333333333333333; 2097152.0; 113244.8; 0.0; 1.1333333333333333; 0.0; 0.0 +1376328647; 1; 2599.999626; 3.4666661679999997; 0.13333333333333333; 2097152.0; 117439.2; 0.0; 1.4; 0.0; 0.0 +1376328947; 1; 2599.999626; 5.1999992520000005; 0.2; 2097152.0; 96467.2; 0.0; 1.2666666666666666; 0.0; 0.0 +1376329247; 1; 2599.999626; 3.4666661679999997; 0.13333333333333333; 2097152.0; 106254.13333333333; 0.0; 1.2; 0.0; 0.0 +1376329547; 1; 2599.999626; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 1.2; 0.0; 0.0 +1376329847; 1; 2599.999626; 5.1999992520000005; 0.2; 2097152.0; 85282.13333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1376330147; 1; 2599.999626; 6.933332335999999; 0.26666666666666666; 2097152.0; 125828.0; 0.2; 7.466666666666667; 0.2; 0.13333333333333333 +1376330447; 1; 2599.999626; 8.666665420000001; 0.33333333333333337; 2097152.0; 113244.8; 0.0; 1.2; 0.0; 0.0 +1376330747; 1; 2599.999626; 3.4666661679999997; 0.13333333333333333; 2097152.0; 155188.0; 0.0; 1.1333333333333333; 0.0; 0.0 +1376331047; 1; 2599.999626; 12.133331587999999; 0.4666666666666666; 2097152.0; 167771.2; 0.0; 2.466666666666667; 0.3333333333333333; 0.4666666666666667 +1376331347; 1; 2599.999626; 8.666665420000001; 0.33333333333333337; 2097152.0; 132817.6; 0.0; 1.6666666666666667; 0.0; 0.0 +1376331647; 1; 2599.999626; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 1.2666666666666666; 0.0; 0.0 +1376331947; 1; 2599.999626; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 1.1333333333333333; 0.0; 0.0 +1376332247; 1; 2599.999626; 1.7333330839999999; 0.06666666666666667; 2097152.0; 113244.8; 0.0; 1.1333333333333333; 0.0; 0.0 +1376332547; 1; 2599.999626; 1.7333330839999999; 0.06666666666666667; 2097152.0; 117439.2; 0.0; 1.1333333333333333; 0.0; 0.0 +1376332847; 1; 2599.999626; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 1.6; 0.0; 0.0 +1376333147; 1; 2599.999626; 1.7333330839999999; 0.06666666666666667; 2097152.0; 88078.4; 0.0; 1.2666666666666666; 0.0; 0.0 +1376333447; 1; 2599.999626; 3.4666661679999997; 0.13333333333333333; 2097152.0; 111846.66666666667; 0.0; 1.2666666666666666; 0.0; 0.0 +1376333747; 1; 2599.999626; 3.4666661679999997; 0.13333333333333333; 2097152.0; 88078.4; 0.0; 1.3333333333333333; 0.06666666666666667; 0.0 +1376334047; 1; 2599.999626; 3.4666661679999997; 0.13333333333333333; 2097152.0; 82485.86666666667; 0.0; 1.4666666666666666; 0.0; 0.0 +1376334347; 1; 2599.999626; 5.1999992520000005; 0.2; 2097152.0; 97865.33333333333; 0.0; 1.2666666666666666; 0.0; 0.0 +1376334647; 1; 2599.999626; 17.333330840000002; 0.6666666666666667; 2097152.0; 170565.86666666667; 0.0; 2.2666666666666666; 0.13333333333333333; 0.4666666666666667 +1376334947; 1; 2599.999626; 6.933332335999999; 0.26666666666666666; 2097152.0; 128624.0; 0.0; 1.1333333333333333; 0.0; 0.0 +1376335247; 1; 2599.999626; 0.0; 0.0; 2097152.0; 123031.73333333334; 0.0; 1.2; 0.0; 0.0 +1376335547; 1; 2599.999626; 46.799993268; 1.8; 2097152.0; 341134.4; 161.73333333333332; 14.666666666666666; 0.0; 0.2 +1376335847; 1; 2599.999626; 13.866664671999999; 0.5333333333333333; 2097152.0; 216702.93333333332; 0.0; 7.733333333333333; 0.26666666666666666; 0.13333333333333333 +1376336147; 1; 2599.999626; 5.1999992520000005; 0.2; 2097152.0; 160780.53333333333; 31.8; 3.8666666666666667; 0.0; 0.0 +1376336447; 1; 2599.999626; 1.7333330839999999; 0.06666666666666667; 2097152.0; 135614.13333333333; 0.0; 1.3333333333333333; 0.26666666666666666; 0.0 +1376336747; 1; 2599.999626; 5.1999992520000005; 0.2; 2097152.0; 106254.13333333333; 0.0; 1.3333333333333333; 0.06666666666666667; 0.0 +1376337047; 1; 2599.999626; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 1.2; 0.0; 0.0 +1376337347; 1; 2599.999626; 5.1999992520000005; 0.2; 2097152.0; 111846.66666666667; 0.0; 1.0; 0.0; 0.0 +1376337647; 1; 2599.999626; 5.1999992520000005; 0.2; 2097152.0; 117439.2; 0.0; 1.1333333333333333; 0.0; 0.0 +1376337947; 1; 2599.999626; 5.1999992520000005; 0.2; 2097152.0; 135614.93333333332; 0.0; 1.0; 0.0; 0.0 +1376338248; 1; 2599.999626; 12.133331587999999; 0.4666666666666666; 2097152.0; 205518.66666666666; 0.0; 3.066666666666667; 0.0; 0.4666666666666667 +1376338548; 1; 2599.999626; 5.1999992520000005; 0.2; 2097152.0; 138410.93333333332; 0.06666666666666667; 1.5333333333333334; 0.0; 0.0 +1376338848; 1; 2599.999626; 3.4666661679999997; 0.13333333333333333; 2097152.0; 118837.33333333333; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1376339148; 1; 2599.999626; 3.4666661679999997; 0.13333333333333333; 2097152.0; 89476.53333333334; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1376339448; 1; 2599.999626; 0.0; 0.0; 2097152.0; 95069.06666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1376339748; 1; 2599.999626; 1.7333330839999999; 0.06666666666666667; 2097152.0; 118837.33333333333; 0.0; 1.3333333333333333; 0.0; 0.0 +1376340048; 1; 2599.999626; 1.7333330839999999; 0.06666666666666667; 2097152.0; 131420.53333333333; 0.0; 1.2; 0.0; 0.0 +1376340348; 1; 2599.999626; 1.7333330839999999; 0.06666666666666667; 2097152.0; 120235.46666666666; 0.0; 1.2; 0.6666666666666666; 0.0 +1376340648; 1; 2599.999626; 0.0; 0.0; 2097152.0; 118837.33333333333; 0.0; 1.3333333333333333; 0.0; 0.0 +1376340948; 1; 2599.999626; 3.4666661679999997; 0.13333333333333333; 2097152.0; 111846.66666666667; 0.0; 1.4666666666666666; 0.0; 0.0 +1376341248; 1; 2599.999626; 1.7333330839999999; 0.06666666666666667; 2097152.0; 123030.93333333333; 0.0; 1.3333333333333333; 0.0; 0.0 +1376341548; 1; 2599.999626; 1.7333330839999999; 0.06666666666666667; 2097152.0; 118837.33333333333; 0.0; 1.2; 0.0; 0.0 +1376341848; 1; 2599.999626; 17.333330840000002; 0.6666666666666667; 2097152.0; 127225.33333333333; 0.2; 2.6666666666666665; 0.0; 0.4666666666666667 +1376342148; 1; 2599.999626; 6.933332335999999; 0.26666666666666666; 2097152.0; 130022.4; 0.0; 1.2; 0.06666666666666667; 0.0 +1376342448; 1; 2599.999626; 1.7333330839999999; 0.06666666666666667; 2097152.0; 121633.6; 0.0; 1.4; 0.0; 0.0 +1376342748; 1; 2599.999626; 8.666665420000001; 0.33333333333333337; 2097152.0; 111846.66666666667; 0.06666666666666667; 7.2; 0.26666666666666666; 0.13333333333333333 +1376343048; 1; 2599.999626; 8.666665420000001; 0.33333333333333337; 2097152.0; 100661.6; 0.0; 1.3333333333333333; 0.0; 0.0 +1376343348; 1; 2599.999626; 3.4666661679999997; 0.13333333333333333; 2097152.0; 118837.33333333333; 0.0; 1.3333333333333333; 0.13333333333333333; 0.0 +1376343648; 1; 2599.999626; 3.4666661679999997; 0.13333333333333333; 2097152.0; 106254.13333333333; 0.0; 1.3333333333333333; 0.3333333333333333; 0.0 +1376343948; 1; 2599.999626; 3.4666661679999997; 0.13333333333333333; 2097152.0; 149594.4; 0.0; 1.3333333333333333; 0.0; 0.0 +1376344248; 1; 2599.999626; 6.933332335999999; 0.26666666666666666; 2097152.0; 150993.6; 0.0; 1.1333333333333333; 0.6; 0.0 +1376344548; 1; 2599.999626; 5.1999992520000005; 0.2; 2097152.0; 100661.6; 0.0; 1.2666666666666666; 0.06666666666666667; 0.0 +1376344848; 1; 2599.999626; 6.933332335999999; 0.26666666666666666; 2097152.0; 96467.2; 0.0; 1.1333333333333333; 0.0; 0.0 +1376345148; 1; 2599.999626; 6.933332335999999; 0.26666666666666666; 2097152.0; 109050.4; 0.0; 1.5333333333333334; 0.0; 0.0 +1376345448; 1; 2599.999626; 12.133331587999999; 0.4666666666666666; 2097152.0; 162177.86666666667; 0.0; 2.8; 0.06666666666666667; 0.4666666666666667 +1376345748; 1; 2599.999626; 1.7333330839999999; 0.06666666666666667; 2097152.0; 131420.26666666666; 0.0; 1.2666666666666666; 0.0; 0.0 +1376346048; 1; 2599.999626; 1.7333330839999999; 0.06666666666666667; 2097152.0; 118837.33333333333; 0.0; 1.2; 0.0; 0.0 +1376346348; 1; 2599.999626; 5.1999992520000005; 0.2; 2097152.0; 90874.66666666667; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1376346648; 1; 2599.999626; 5.1999992520000005; 0.2; 2097152.0; 74097.06666666667; 2.6666666666666665; 1.4666666666666666; 0.13333333333333333; 0.0 +1376346948; 1; 2599.999626; 10.399998504000001; 0.4; 2097152.0; 118837.33333333333; 0.0; 1.4666666666666666; 0.06666666666666667; 0.13333333333333333 +1376347248; 1; 2599.999626; 8.666665420000001; 0.33333333333333337; 2097152.0; 113244.8; 0.0; 1.4; 0.0; 0.0 +1376347548; 1; 2599.999626; 0.0; 0.0; 2097152.0; 74097.06666666667; 0.0; 1.3333333333333333; 0.0; 0.0 +1376347848; 1; 2599.999626; 3.4666661679999997; 0.13333333333333333; 2097152.0; 83884.0; 0.0; 1.4666666666666666; 0.0; 0.0 +1376348148; 1; 2599.999626; 8.666665420000001; 0.33333333333333337; 2097152.0; 106254.13333333333; 0.0; 7.466666666666667; 0.26666666666666666; 0.2 +1376348448; 1; 2599.999626; 3.4666661679999997; 0.13333333333333333; 2097152.0; 120235.46666666666; 0.0; 1.2; 0.0; 0.0 +1376348749; 1; 2599.999626; 5.1999992520000005; 0.2; 2097152.0; 107652.26666666666; 0.0; 1.1333333333333333; 0.0; 0.0 +1376349049; 1; 2599.999626; 8.666665420000001; 0.33333333333333337; 2097152.0; 137012.26666666666; 0.0; 2.533333333333333; 0.06666666666666667; 0.4666666666666667 +1376349349; 1; 2599.999626; 41.599994016000004; 1.6; 2097152.0; 187343.73333333334; 161.0; 21.6; 0.06666666666666667; 0.4 +1376349649; 1; 2599.999626; 22.533330092; 0.8666666666666667; 2097152.0; 577414.1333333333; 0.0; 2.466666666666667; 0.0; 0.0 +1376349949; 1; 2599.999626; 5.1999992520000005; 0.2; 2097152.0; 234878.4; 31.8; 3.6666666666666665; 0.0; 0.0 +1376350249; 1; 2599.999626; 6.933332335999999; 0.26666666666666666; 2097152.0; 178954.4; 0.8; 2.466666666666667; 0.0; 0.06666666666666667 +1376350549; 1; 2599.999626; 6.933332335999999; 0.26666666666666666; 2097152.0; 142605.6; 0.0; 1.7333333333333334; 0.0; 0.0 +1376350849; 1; 2599.999626; 6.933332335999999; 0.26666666666666666; 2097152.0; 121633.6; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1376351149; 1; 2599.999626; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 1.2; 0.06666666666666667; 0.0 +1376351449; 1; 2599.999626; 3.4666661679999997; 0.13333333333333333; 2097152.0; 141206.66666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1376351749; 1; 2599.999626; 0.0; 0.0; 2097152.0; 83884.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1376352049; 1; 2599.999626; 3.4666661679999997; 0.13333333333333333; 2097152.0; 82485.86666666667; 1.4666666666666666; 1.6666666666666667; 0.0; 0.0 +1376352349; 1; 2599.999626; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 1.1333333333333333; 0.0; 0.0 +1376352649; 1; 2599.999626; 8.666665420000001; 0.33333333333333337; 2097152.0; 178954.13333333333; 3.8666666666666667; 2.7333333333333334; 0.06666666666666667; 0.5333333333333333 +1376352949; 1; 2599.999626; 3.4666661679999997; 0.13333333333333333; 2097152.0; 141206.4; 0.0; 1.0; 0.0; 0.0 +1376353249; 1; 2599.999626; 0.0; 0.0; 2097152.0; 114642.93333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1376353549; 1; 2599.999626; 6.933332335999999; 0.26666666666666666; 2097152.0; 95069.06666666667; 0.0; 1.2; 0.0; 0.0 +1376353849; 1; 2599.999626; 3.4666661679999997; 0.13333333333333333; 2097152.0; 114642.93333333333; 0.6; 12.4; 0.0; 0.0 +1376354149; 1; 2599.999626; 6.933332335999999; 0.26666666666666666; 2097152.0; 192936.0; 12.733333333333333; 13.266666666666667; 0.3333333333333333; 0.13333333333333333 +1376354449; 1; 2599.999626; 5.1999992520000005; 0.2; 2097152.0; 130022.4; 0.0; 1.6666666666666667; 0.06666666666666667; 0.0 +1376354749; 1; 2599.999626; 0.0; 0.0; 2097152.0; 134216.8; 0.0; 1.4; 0.0; 0.0 +1376355049; 1; 2599.999626; 1.7333330839999999; 0.06666666666666667; 2097152.0; 131420.53333333333; 0.0; 1.3333333333333333; 0.0; 0.0 +1376355349; 1; 2599.999626; 3.4666661679999997; 0.13333333333333333; 2097152.0; 134216.0; 0.0; 1.2; 0.0; 0.0 +1376355649; 1; 2599.999626; 1.7333330839999999; 0.06666666666666667; 2097152.0; 135614.93333333332; 0.0; 1.1333333333333333; 0.0; 0.0 +1376355949; 1; 2599.999626; 1.7333330839999999; 0.06666666666666667; 2097152.0; 99263.46666666666; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1376356249; 1; 2599.999626; 13.866664671999999; 0.5333333333333333; 2097152.0; 138410.66666666666; 0.0; 2.8; 0.06666666666666667; 0.4666666666666667 +1376356549; 1; 2599.999626; 12.133331587999999; 0.4666666666666666; 2097152.0; 121633.33333333333; 0.0; 2.066666666666667; 0.0; 0.0 +1376356849; 1; 2599.999626; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 1.2; 0.06666666666666667; 0.0 +1376357149; 1; 2599.999626; 1.7333330839999999; 0.06666666666666667; 2097152.0; 121633.6; 0.0; 1.3333333333333333; 0.0; 0.0 +1376357449; 1; 2599.999626; 0.0; 0.0; 2097152.0; 114642.93333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1376357749; 1; 2599.999626; 0.0; 0.0; 2097152.0; 83884.0; 0.0; 1.2666666666666666; 0.0; 0.0 +1376358049; 1; 2599.999626; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1376358349; 1; 2599.999626; 0.0; 0.0; 2097152.0; 106254.13333333333; 0.0; 1.2666666666666666; 0.0; 0.0 +1376358649; 1; 2599.999626; 1.7333330839999999; 0.06666666666666667; 2097152.0; 118837.33333333333; 0.0; 1.2; 0.0; 0.0 +1376358949; 1; 2599.999626; 0.0; 0.0; 2097152.0; 60115.73333333333; 0.0; 1.4666666666666666; 0.0; 0.0 +1376359250; 1; 2599.999626; 0.0; 0.0; 2097152.0; 110448.53333333334; 0.0; 1.3333333333333333; 0.06666666666666667; 0.0 +1376359550; 1; 2599.999626; 5.1999992520000005; 0.2; 2097152.0; 138411.2; 0.0; 7.4; 0.2; 0.13333333333333333 +1376359850; 1; 2599.999626; 10.399998504000001; 0.4; 2097152.0; 160780.53333333333; 0.0; 2.6; 0.06666666666666667; 0.4666666666666667 +1376360150; 1; 2599.999626; 5.1999992520000005; 0.2; 2097152.0; 107652.26666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1376360450; 1; 2599.999626; 1.7333330839999999; 0.06666666666666667; 2097152.0; 82485.86666666667; 0.0; 1.2666666666666666; 0.0; 0.0 +1376360750; 1; 2599.999626; 3.4666661679999997; 0.13333333333333333; 2097152.0; 111846.66666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1376361050; 1; 2599.999626; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 1.2; 0.06666666666666667; 0.0 +1376361350; 1; 2599.999626; 3.4666661679999997; 0.13333333333333333; 2097152.0; 117439.2; 0.0; 1.2; 0.0; 0.0 +1376361650; 1; 2599.999626; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 1.4; 0.06666666666666667; 0.0 +1376361950; 1; 2599.999626; 0.0; 0.0; 2097152.0; 75495.2; 0.0; 1.4666666666666666; 0.0; 0.0 +1376362250; 1; 2599.999626; 1.7333330839999999; 0.06666666666666667; 2097152.0; 131420.53333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1376362550; 1; 2599.999626; 0.0; 0.0; 2097152.0; 124429.86666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1376362849; 1; 2599.999626; 0.0; 0.0; 2097152.0; 79689.6; 0.0; 1.0; 0.0; 0.0 +1376363149; 1; 2599.999626; 3.4666661679999997; 0.13333333333333333; 2097152.0; 109050.4; 0.0; 1.3333333333333333; 0.0; 0.0 +1376363449; 1; 2599.999626; 8.666665420000001; 0.33333333333333337; 2097152.0; 169168.53333333333; 0.06666666666666667; 2.933333333333333; 0.06666666666666667; 0.4666666666666667 +1376363749; 1; 2599.999626; 3.4666661679999997; 0.13333333333333333; 2097152.0; 137013.06666666668; 0.0; 1.2666666666666666; 0.0; 0.0 +1376364049; 1; 2599.999626; 0.0; 0.0; 2097152.0; 123030.93333333333; 0.0; 1.2666666666666666; 0.0; 0.0 +1376364349; 1; 2599.999626; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 1.2; 0.0; 0.0 +1376364650; 1; 2599.999626; 0.0; 0.0; 2097152.0; 135614.66666666666; 0.0; 1.3333333333333333; 0.0; 0.0 +1376364950; 1; 2599.999626; 0.0; 0.0; 2097152.0; 111846.13333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1376365250; 1; 2599.999626; 0.0; 0.0; 2097152.0; 114642.93333333333; 0.0; 1.2; 0.0; 0.0 +1376365550; 1; 2599.999626; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 1.5333333333333334; 0.0; 0.0 +1376365850; 1; 2599.999626; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 1.3333333333333333; 0.0; 0.0 +1376366150; 1; 2599.999626; 0.0; 0.0; 2097152.0; 78291.46666666666; 0.0; 1.4666666666666666; 0.0; 0.0 +1376366450; 1; 2599.999626; 5.1999992520000005; 0.2; 2097152.0; 111846.66666666667; 0.06666666666666667; 7.466666666666667; 0.2; 0.13333333333333333 +1376366750; 1; 2599.999626; 1.7333330839999999; 0.06666666666666667; 2097152.0; 109050.4; 0.0; 1.0666666666666667; 0.0; 0.0 +1376367050; 1; 2599.999626; 8.666665420000001; 0.33333333333333337; 2097152.0; 117439.2; 0.0; 2.6; 0.4; 0.5333333333333333 +1376367350; 1; 2599.999626; 6.933332335999999; 0.26666666666666666; 2097152.0; 139809.33333333334; 0.0; 1.3333333333333333; 0.0; 0.0 +1376367650; 1; 2599.999626; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1376367950; 1; 2599.999626; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 1.2; 0.0; 0.0 +1376368250; 1; 2599.999626; 0.0; 0.0; 2097152.0; 95069.06666666667; 0.0; 1.3333333333333333; 0.0; 0.0 +1376368550; 1; 2599.999626; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 1.2666666666666666; 0.0; 0.0 +1376368850; 1; 2599.999626; 0.0; 0.0; 2097152.0; 144002.93333333332; 0.0; 1.6; 0.0; 0.0 +1376369150; 1; 2599.999626; 0.0; 0.0; 2097152.0; 114642.93333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1376369450; 1; 2599.999626; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 1.1333333333333333; 0.26666666666666666; 0.0 +1376369750; 1; 2599.999626; 6.933332335999999; 0.26666666666666666; 2097152.0; 75495.2; 0.0; 1.1333333333333333; 0.0; 0.0 +1376370050; 1; 2599.999626; 0.0; 0.0; 2097152.0; 58717.6; 0.0; 1.3333333333333333; 0.0; 0.0 +1376370350; 1; 2599.999626; 3.4666661679999997; 0.13333333333333333; 2097152.0; 86680.26666666666; 0.0; 1.3333333333333333; 0.0; 0.0 +1376370650; 1; 2599.999626; 8.666665420000001; 0.33333333333333337; 2097152.0; 142604.0; 0.06666666666666667; 3.066666666666667; 0.06666666666666667; 0.4666666666666667 +1376370950; 1; 2599.999626; 1.7333330839999999; 0.06666666666666667; 2097152.0; 114642.93333333333; 0.0; 1.3333333333333333; 0.6666666666666666; 0.0 +1376371250; 1; 2599.999626; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 1.2; 0.2; 0.0 +1376371550; 1; 2599.999626; 0.0; 0.0; 2097152.0; 95069.06666666667; 0.0; 1.3333333333333333; 0.0; 0.0 +1376371850; 1; 2599.999626; 6.933332335999999; 0.26666666666666666; 2097152.0; 167770.13333333333; 0.0; 7.066666666666666; 0.3333333333333333; 0.13333333333333333 +1376372150; 1; 2599.999626; 0.0; 0.0; 2097152.0; 139808.8; 0.0; 1.2; 0.0; 0.0 +1376372450; 1; 2599.999626; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 1.3333333333333333; 0.0; 0.0 +1376372750; 1; 2599.999626; 1.7333330839999999; 0.06666666666666667; 2097152.0; 96467.2; 0.0; 1.4; 0.0; 0.0 +1376373050; 1; 2599.999626; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 1.5333333333333334; 0.0; 0.0 +1376373350; 1; 2599.999626; 3.4666661679999997; 0.13333333333333333; 2097152.0; 125827.2; 0.0; 1.2666666666666666; 0.0; 0.0 +1376373651; 1; 2599.999626; 1.7333330839999999; 0.06666666666666667; 2097152.0; 116041.06666666667; 0.0; 1.2; 0.0; 0.0 +1376373951; 1; 2599.999626; 1.7333330839999999; 0.06666666666666667; 2097152.0; 106254.13333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1376374251; 1; 2599.999626; 10.399998504000001; 0.4; 2097152.0; 163575.46666666667; 0.06666666666666667; 2.8666666666666667; 0.06666666666666667; 0.4666666666666667 +1376374551; 1; 2599.999626; 5.1999992520000005; 0.2; 2097152.0; 128623.73333333334; 0.0; 1.2; 0.0; 0.0 +1376374851; 1; 2599.999626; 3.4666661679999997; 0.13333333333333333; 2097152.0; 86680.26666666666; 0.0; 1.2666666666666666; 0.06666666666666667; 0.0 +1376375151; 1; 2599.999626; 0.0; 0.0; 2097152.0; 81087.73333333334; 0.0; 1.2666666666666666; 0.0; 0.0 +1376375451; 1; 2599.999626; 3.4666661679999997; 0.13333333333333333; 2097152.0; 123031.73333333334; 0.0; 1.2666666666666666; 0.0; 0.0 +1376375751; 1; 2599.999626; 12.133331587999999; 0.4666666666666666; 2097152.0; 117439.2; 0.0; 3.0; 0.13333333333333333; 0.13333333333333333 +1376376051; 1; 2599.999626; 8.666665420000001; 0.33333333333333337; 2097152.0; 142604.8; 0.0; 2.066666666666667; 0.06666666666666667; 0.0 +1376376351; 1; 2599.999626; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1376376651; 1; 2599.999626; 3.4666661679999997; 0.13333333333333333; 2097152.0; 127225.33333333333; 0.0; 1.2; 0.0; 0.0 +1376376951; 1; 2599.999626; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 1.4; 0.0; 0.0 +1376377251; 1; 2599.999626; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 1.2666666666666666; 0.0; 0.0 +1376377551; 1; 2599.999626; 3.4666661679999997; 0.13333333333333333; 2097152.0; 118837.33333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1376377851; 1; 2599.999626; 12.133331587999999; 0.4666666666666666; 2097152.0; 180353.6; 0.6; 2.6666666666666665; 0.13333333333333333; 0.4666666666666667 +1376378151; 1; 2599.999626; 5.1999992520000005; 0.2; 2097152.0; 99263.46666666666; 0.0; 1.2666666666666666; 0.0; 0.0 +1376378451; 1; 2599.999626; 10.399998504000001; 0.4; 2097152.0; 116041.06666666667; 0.0; 7.333333333333333; 0.2; 0.13333333333333333 +1376378751; 1; 2599.999626; 3.4666661679999997; 0.13333333333333333; 2097152.0; 110448.53333333334; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1376379051; 1; 2599.999626; 3.4666661679999997; 0.13333333333333333; 2097152.0; 86680.26666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1376379351; 1; 2599.999626; 1.7333330839999999; 0.06666666666666667; 2097152.0; 96467.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1376379651; 1; 2599.999626; 0.0; 0.0; 2097152.0; 118836.53333333334; 0.0; 1.3333333333333333; 0.0; 0.0 +1376379951; 1; 2599.999626; 1.7333330839999999; 0.06666666666666667; 2097152.0; 111846.66666666667; 0.0; 2.0; 0.0; 0.0 +1376380251; 1; 2599.999626; 1.7333330839999999; 0.06666666666666667; 2097152.0; 104856.0; 0.0; 1.1333333333333333; 0.0; 0.0 +1376380551; 1; 2599.999626; 0.0; 0.0; 2097152.0; 74097.06666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1376380851; 1; 2599.999626; 5.1999992520000005; 0.2; 2097152.0; 131420.53333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1376381151; 1; 2599.999626; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 1.3333333333333333; 0.0; 0.0 +1376381451; 1; 2599.999626; 5.1999992520000005; 0.2; 2097152.0; 144003.73333333334; 0.0; 2.533333333333333; 0.06666666666666667; 0.5333333333333333 +1376381751; 1; 2599.999626; 0.0; 0.0; 2097152.0; 131420.53333333333; 0.0; 1.2; 0.06666666666666667; 0.0 +1376382051; 1; 2599.999626; 0.0; 0.0; 2097152.0; 138409.6; 0.0; 1.2666666666666666; 0.0; 0.0 +1376382351; 1; 2599.999626; 3.4666661679999997; 0.13333333333333333; 2097152.0; 75495.2; 0.0; 1.2; 0.0; 0.0 +1376382651; 1; 2599.999626; 5.1999992520000005; 0.2; 2097152.0; 81087.73333333334; 0.0; 1.4; 0.0; 0.0 +1376382951; 1; 2599.999626; 0.0; 0.0; 2097152.0; 81087.73333333334; 0.0; 1.0666666666666667; 0.0; 0.0 +1376383251; 1; 2599.999626; 0.0; 0.0; 2097152.0; 71300.8; 0.0; 1.2; 0.0; 0.0 +1376383551; 1; 2599.999626; 0.0; 0.0; 2097152.0; 110448.53333333334; 0.0; 1.0666666666666667; 0.0; 0.0 +1376383851; 1; 2599.999626; 1.7333330839999999; 0.06666666666666667; 2097152.0; 107652.26666666666; 0.0; 1.4; 0.0; 0.0 +1376384151; 1; 2599.999626; 1.7333330839999999; 0.06666666666666667; 2097152.0; 89476.53333333334; 0.0; 1.3333333333333333; 0.0; 0.0 +1376384451; 1; 2599.999626; 3.4666661679999997; 0.13333333333333333; 2097152.0; 99263.46666666666; 0.0; 1.2; 0.0; 0.0 +1376384752; 1; 2599.999626; 0.0; 0.0; 2097152.0; 83884.0; 0.0; 1.1333333333333333; 0.0; 0.0 +1376385052; 1; 2599.999626; 8.666665420000001; 0.33333333333333337; 2097152.0; 113244.8; 0.0; 2.6666666666666665; 0.06666666666666667; 0.4666666666666667 +1376385352; 1; 2599.999626; 5.1999992520000005; 0.2; 2097152.0; 111846.66666666667; 0.0; 1.2666666666666666; 0.06666666666666667; 0.0 +1376385652; 1; 2599.999626; 5.1999992520000005; 0.2; 2097152.0; 95069.06666666667; 0.0; 7.0; 1.0666666666666667; 0.2 +1376385952; 1; 2599.999626; 0.0; 0.0; 2097152.0; 76893.33333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1376386252; 1; 2599.999626; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 1.0; 0.0; 0.0 +1376386552; 1; 2599.999626; 0.0; 0.0; 2097152.0; 127225.86666666667; 0.0; 1.2; 0.0; 0.0 +1376386852; 1; 2599.999626; 0.0; 0.0; 2097152.0; 132818.4; 0.0; 1.3333333333333333; 0.06666666666666667; 0.0 +1376387152; 1; 2599.999626; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 1.2666666666666666; 0.0; 0.0 +1376387452; 1; 2599.999626; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 1.0; 0.2; 0.0 +1376387752; 1; 2599.999626; 0.0; 0.0; 2097152.0; 128624.26666666666; 0.0; 1.2; 0.0; 0.0 +1376388052; 1; 2599.999626; 6.933332335999999; 0.26666666666666666; 2097152.0; 130022.4; 0.0; 1.2666666666666666; 0.0; 0.0 +1376388352; 1; 2599.999626; 1.7333330839999999; 0.06666666666666667; 2097152.0; 111846.66666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1376388652; 1; 2599.999626; 10.399998504000001; 0.4; 2097152.0; 169168.26666666666; 0.0; 2.933333333333333; 0.06666666666666667; 0.4666666666666667 +1376388952; 1; 2599.999626; 3.4666661679999997; 0.13333333333333333; 2097152.0; 146799.2; 0.0; 1.2; 0.0; 0.0 +1376389252; 1; 2599.999626; 0.0; 0.0; 2097152.0; 110448.53333333334; 0.0; 1.1333333333333333; 0.0; 0.0 +1376389552; 1; 2599.999626; 3.4666661679999997; 0.13333333333333333; 2097152.0; 128624.26666666666; 0.0; 1.4; 0.0; 0.0 +1376389852; 1; 2599.999626; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 1.1333333333333333; 0.0; 0.0 +1376390152; 1; 2599.999626; 0.0; 0.0; 2097152.0; 116041.06666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1376390452; 1; 2599.999626; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 1.1333333333333333; 0.0; 0.0 +1376390752; 1; 2599.999626; 5.1999992520000005; 0.2; 2097152.0; 97865.33333333333; 0.0; 1.4666666666666666; 0.0; 0.0 +1376391052; 1; 2599.999626; 1.7333330839999999; 0.06666666666666667; 2097152.0; 117439.2; 0.0; 1.2666666666666666; 0.0; 0.0 +1376391352; 1; 2599.999626; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1376391652; 1; 2599.999626; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 1.1333333333333333; 0.0; 0.0 +1376391952; 1; 2599.999626; 0.0; 0.0; 2097152.0; 106254.13333333333; 0.0; 1.2; 0.0; 0.0 +1376392252; 1; 2599.999626; 6.933332335999999; 0.26666666666666666; 2097152.0; 191537.33333333334; 0.0; 2.8666666666666667; 0.06666666666666667; 0.4666666666666667 +1376392552; 1; 2599.999626; 8.666665420000001; 0.33333333333333337; 2097152.0; 163576.0; 0.0; 7.266666666666667; 0.26666666666666666; 0.13333333333333333 +1376392853; 1; 2599.999626; 0.0; 0.0; 2097152.0; 138409.6; 0.0; 1.2; 0.13333333333333333; 0.0 +1376393153; 1; 2599.999602; 38.999994029999996; 1.5; 2097152.0; 111846.66666666667; 0.0; 1.2; 0.0; 0.0 +1376393453; 1; 2599.999602; 20.799996815999997; 0.8; 2097152.0; 109049.6; 0.0; 1.5333333333333334; 0.0; 0.0 +1376393753; 1; 2599.999602; 17.333330680000003; 0.6666666666666667; 2097152.0; 93670.93333333333; 0.0; 1.3333333333333333; 0.0; 0.0 +1376394053; 1; 2599.999602; 15.599997612; 0.6; 2097152.0; 107652.26666666666; 0.0; 1.2666666666666666; 0.0; 0.0 +1376394353; 1; 2599.999602; 15.599997612; 0.6; 2097152.0; 111846.66666666667; 0.0; 1.6666666666666667; 0.0; 0.0 +1376394653; 1; 2599.999602; 17.333330680000003; 0.6666666666666667; 2097152.0; 75495.2; 0.0; 1.1333333333333333; 0.0; 0.0 +1376394953; 1; 2599.999602; 15.599997612; 0.6; 2097152.0; 60115.73333333333; 0.0; 1.3333333333333333; 0.0; 0.0 +1376395253; 1; 2599.999602; 17.333330680000003; 0.6666666666666667; 2097152.0; 110448.53333333334; 0.0; 1.2; 0.0; 0.0 +1376395552; 1; 2599.999602; 19.066663748; 0.7333333333333333; 2097152.0; 120235.46666666666; 0.0; 1.3333333333333333; 0.0; 0.0 +1376395852; 1; 2599.999602; 19.066663748; 0.7333333333333333; 2097152.0; 113244.8; 0.0; 2.6; 0.06666666666666667; 0.4666666666666667 +1376396152; 1; 2599.999602; 15.599997612; 0.6; 2097152.0; 103457.86666666667; 0.0; 1.4666666666666666; 0.0; 0.0 +1376396452; 1; 2599.999602; 12.133331475999999; 0.4666666666666666; 2097152.0; 116041.06666666667; 0.0; 1.4666666666666666; 0.0; 0.0 +1376396752; 1; 2599.999602; 10.399998407999998; 0.4; 2097152.0; 117439.2; 0.0; 1.2; 0.0; 0.0 +1376397052; 1; 2599.999602; 17.333330680000003; 0.6666666666666667; 2097152.0; 113244.8; 0.0; 1.2; 0.0; 0.0 +1376397352; 1; 2599.999602; 6.933332272; 0.26666666666666666; 2097152.0; 92272.8; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1376397652; 1; 2599.999602; 15.599997612; 0.6; 2097152.0; 97865.33333333333; 0.0; 12.533333333333333; 0.0; 0.0 +1376397952; 1; 2599.999602; 15.599997612; 0.6; 2097152.0; 81087.73333333334; 0.0; 1.5333333333333334; 0.0; 0.0 +1376398252; 1; 2599.999602; 12.133331475999999; 0.4666666666666666; 2097152.0; 106254.13333333333; 0.0; 1.3333333333333333; 0.0; 0.0 +1376398552; 1; 2599.999602; 10.399998407999998; 0.4; 2097152.0; 100661.6; 0.0; 1.3333333333333333; 0.0; 0.0 +1376398853; 1; 2599.999602; 13.866664544; 0.5333333333333333; 2097152.0; 99263.46666666666; 0.0; 7.2; 7.2; 0.13333333333333333 +1376399153; 1; 2599.999602; 13.866664544; 0.5333333333333333; 2097152.0; 155188.0; 0.0; 1.4; 0.0; 0.0 +1376399453; 1; 2599.999602; 22.533329884; 0.8666666666666667; 2097152.0; 178955.46666666667; 0.0; 2.6666666666666665; 0.06666666666666667; 0.5333333333333333 +1376399753; 1; 2599.999602; 12.133331475999999; 0.4666666666666666; 2097152.0; 100661.6; 0.0; 1.2; 0.0; 0.0 +1376400053; 1; 2599.999602; 8.666665340000002; 0.33333333333333337; 2097152.0; 111846.66666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1376400353; 1; 2599.999602; 10.399998407999998; 0.4; 2097152.0; 74097.06666666667; 0.0; 1.4666666666666666; 0.0; 0.0 +1376400653; 1; 2599.999602; 15.599997612; 0.6; 2097152.0; 92272.8; 0.0; 1.6666666666666667; 0.0; 0.0 +1376400953; 1; 2599.999602; 12.133331475999999; 0.4666666666666666; 2097152.0; 88078.4; 0.0; 2.0; 0.0; 0.0 +1376401253; 1; 2599.999602; 12.133331475999999; 0.4666666666666666; 2097152.0; 69902.66666666667; 0.0; 1.2; 0.0; 0.0 +1376401553; 1; 2599.999602; 12.133331475999999; 0.4666666666666666; 2097152.0; 114642.93333333333; 0.0; 1.2666666666666666; 0.0; 0.0 +1376401853; 1; 2599.999602; 15.599997612; 0.6; 2097152.0; 130022.4; 0.0; 1.4; 0.0; 0.0 +1376402153; 1; 2599.999602; 3.466666136; 0.13333333333333333; 2097152.0; 130022.4; 0.0; 1.2; 0.0; 0.0 +1376402453; 1; 2599.999602; 13.866664544; 0.5333333333333333; 2097152.0; 92272.8; 0.0; 1.2666666666666666; 0.0; 0.0 +1376402753; 1; 2599.999602; 15.599997612; 0.6; 2097152.0; 83884.0; 0.0; 1.3333333333333333; 0.06666666666666667; 0.0 +1376403053; 1; 2599.999602; 15.599997612; 0.6; 2097152.0; 113244.8; 0.0; 3.1333333333333333; 0.0; 0.4666666666666667 +1376403353; 1; 2599.999602; 17.333330680000003; 0.6666666666666667; 2097152.0; 113244.8; 0.0; 1.3333333333333333; 0.0; 0.0 +1376403653; 1; 2599.999602; 12.133331475999999; 0.4666666666666666; 2097152.0; 111846.66666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1376403953; 1; 2599.999602; 12.133331475999999; 0.4666666666666666; 2097152.0; 93670.93333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1376404253; 1; 2599.999602; 12.133331475999999; 0.4666666666666666; 2097152.0; 74097.06666666667; 0.0; 1.2; 0.06666666666666667; 0.0 +1376404553; 1; 2599.999602; 17.333330680000003; 0.6666666666666667; 2097152.0; 90874.66666666667; 0.06666666666666667; 2.066666666666667; 0.06666666666666667; 0.13333333333333333 +1376404853; 1; 2599.999602; 8.666665340000002; 0.33333333333333337; 2097152.0; 92272.8; 0.0; 1.2666666666666666; 0.0; 0.0 +1376405153; 1; 2599.999602; 8.666665340000002; 0.33333333333333337; 2097152.0; 65708.26666666666; 0.0; 1.3333333333333333; 0.0; 0.0 +1376405453; 1; 2599.999602; 8.666665340000002; 0.33333333333333337; 2097152.0; 100661.6; 0.0; 1.3333333333333333; 0.0; 0.0 +1376405753; 1; 2599.999602; 10.399998407999998; 0.4; 2097152.0; 106254.13333333333; 0.0; 7.4; 0.2; 0.13333333333333333 +1376406053; 1; 2599.999602; 13.866664544; 0.5333333333333333; 2097152.0; 83884.0; 0.0; 1.1333333333333333; 0.0; 0.0 +1376406353; 1; 2599.999602; 12.133331475999999; 0.4666666666666666; 2097152.0; 135614.93333333332; 0.0; 0.9333333333333333; 0.0; 0.0 +1376406653; 1; 2599.999602; 74.53332192399999; 2.8666666666666667; 2097152.0; 630542.1333333333; 161.13333333333333; 23.266666666666666; 0.06666666666666667; 0.8666666666666667 +1376406954; 1; 2599.999602; 19.066663748; 0.7333333333333333; 2097152.0; 369096.8; 0.3333333333333333; 20.2; 0.0; 0.0 +1376407254; 1; 2599.999602; 12.133331475999999; 0.4666666666666666; 2097152.0; 237674.93333333332; 31.8; 3.933333333333333; 0.0; 0.0 +1376407554; 1; 2599.999602; 19.066663748; 0.7333333333333333; 2097152.0; 141207.46666666667; 4.266666666666667; 2.533333333333333; 0.06666666666666667; 0.0 +1376407854; 1; 2599.999602; 17.333330680000003; 0.6666666666666667; 2097152.0; 96467.2; 0.0; 1.7333333333333334; 0.0; 0.0 +1376408154; 1; 2599.999602; 12.133331475999999; 0.4666666666666666; 2097152.0; 106254.13333333333; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1376408454; 1; 2599.999602; 5.199999203999999; 0.2; 2097152.0; 132818.66666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1376408754; 1; 2599.999602; 13.866664544; 0.5333333333333333; 2097152.0; 100661.6; 0.0; 1.5333333333333334; 0.2; 0.0 +1376409054; 1; 2599.999602; 10.399998407999998; 0.4; 2097152.0; 110448.53333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1376409354; 1; 2599.999602; 3.466666136; 0.13333333333333333; 2097152.0; 99263.46666666666; 0.0; 1.1333333333333333; 0.0; 0.0 +1376409654; 1; 2599.999602; 15.599997612; 0.6; 2097152.0; 81087.73333333334; 0.0; 1.2; 0.0; 0.0 +1376409954; 1; 2599.999602; 13.866664544; 0.5333333333333333; 2097152.0; 92272.8; 0.0; 1.1333333333333333; 0.0; 0.0 +1376410254; 1; 2599.999602; 15.599997612; 0.6; 2097152.0; 149595.46666666667; 0.0; 2.8; 0.06666666666666667; 0.4666666666666667 +1376410554; 1; 2599.999602; 17.333330680000003; 0.6666666666666667; 2097152.0; 114642.93333333333; 0.0; 1.2; 0.0; 0.0 +1376410854; 1; 2599.999602; 13.866664544; 0.5333333333333333; 2097152.0; 118837.33333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1376411154; 1; 2599.999602; 17.333330680000003; 0.6666666666666667; 2097152.0; 102059.73333333334; 0.0; 1.1333333333333333; 0.0; 0.0 +1376411454; 1; 2599.999602; 15.599997612; 0.6; 2097152.0; 109050.4; 0.0; 1.4; 0.0; 0.0 +1376411754; 1; 2599.999602; 20.799996815999997; 0.8; 2097152.0; 100661.6; 12.733333333333333; 13.466666666666667; 0.3333333333333333; 0.2 +1376412054; 1; 2599.999602; 13.866664544; 0.5333333333333333; 2097152.0; 128624.26666666666; 0.0; 1.6666666666666667; 0.0; 0.0 +1376412354; 1; 2599.999602; 12.133331475999999; 0.4666666666666666; 2097152.0; 106254.13333333333; 0.0; 1.3333333333333333; 0.0; 0.0 +1376412654; 1; 2599.999602; 12.133331475999999; 0.4666666666666666; 2097152.0; 121633.6; 0.0; 1.2; 0.0; 0.0 +1376412954; 1; 2599.999602; 12.133331475999999; 0.4666666666666666; 2097152.0; 85282.13333333333; 0.0; 1.2; 0.0; 0.0 +1376413255; 1; 2599.999602; 10.399998407999998; 0.4; 2097152.0; 90874.66666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1376413555; 1; 2599.999602; 8.666665340000002; 0.33333333333333337; 2097152.0; 74097.06666666667; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1376413855; 1; 2599.999602; 20.799996815999997; 0.8; 2097152.0; 138409.6; 0.06666666666666667; 2.8; 0.06666666666666667; 0.4666666666666667 +1376414155; 1; 2599.999602; 10.399998407999998; 0.4; 2097152.0; 96467.2; 0.0; 1.4666666666666666; 0.0; 0.0 +1376414455; 1; 2599.999602; 13.866664544; 0.5333333333333333; 2097152.0; 90874.66666666667; 0.0; 1.3333333333333333; 0.0; 0.0 +1376414755; 1; 2599.999602; 15.599997612; 0.6; 2097152.0; 92272.8; 0.0; 1.0666666666666667; 0.0; 0.0 +1376415055; 1; 2599.999602; 8.666665340000002; 0.33333333333333337; 2097152.0; 96467.2; 0.0; 1.2666666666666666; 0.0; 0.0 +1376415355; 1; 2599.999602; 10.399998407999998; 0.4; 2097152.0; 95069.06666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1376415655; 1; 2599.999602; 13.866664544; 0.5333333333333333; 2097152.0; 93670.93333333333; 0.0; 1.3333333333333333; 0.0; 0.0 +1376415955; 1; 2599.999602; 13.866664544; 0.5333333333333333; 2097152.0; 85282.13333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1376416255; 1; 2599.999602; 6.933332272; 0.26666666666666666; 2097152.0; 95069.06666666667; 0.0; 1.3333333333333333; 0.0; 0.0 +1376416555; 1; 2599.999602; 13.866664544; 0.5333333333333333; 2097152.0; 95069.06666666667; 0.0; 1.2; 0.0; 0.0 +1376416855; 1; 2599.999602; 15.599997612; 0.6; 2097152.0; 82485.86666666667; 1.0666666666666667; 1.9333333333333333; 0.0; 0.0 +1376417155; 1; 2599.999602; 13.866664544; 0.5333333333333333; 2097152.0; 120235.46666666666; 0.0; 1.4666666666666666; 0.0; 0.0 +1376417455; 1; 2599.999602; 20.799996815999997; 0.8; 2097152.0; 195732.26666666666; 0.0; 2.4; 0.0; 0.5333333333333333 +1376417755; 1; 2599.999602; 8.666665340000002; 0.33333333333333337; 2097152.0; 102059.73333333334; 0.0; 1.0666666666666667; 0.0; 0.0 +1376418055; 1; 2599.999602; 15.599997612; 0.6; 2097152.0; 95069.06666666667; 0.0; 1.2; 0.0; 0.0 +1376418355; 1; 2599.999602; 12.133331475999999; 0.4666666666666666; 2097152.0; 125828.0; 0.0; 7.333333333333333; 0.26666666666666666; 0.2 +1376418655; 1; 2599.999602; 12.133331475999999; 0.4666666666666666; 2097152.0; 102059.73333333334; 0.0; 1.2666666666666666; 0.0; 0.0 +1376418955; 1; 2599.999602; 12.133331475999999; 0.4666666666666666; 2097152.0; 117438.4; 0.0; 1.2666666666666666; 0.0; 0.0 +1376419255; 1; 2599.999602; 6.933332272; 0.26666666666666666; 2097152.0; 102059.73333333334; 0.0; 1.3333333333333333; 0.0; 0.0 +1376419555; 1; 2599.999602; 12.133331475999999; 0.4666666666666666; 2097152.0; 118836.53333333334; 0.0; 1.2; 0.0; 0.0 +1376419855; 1; 2599.999602; 8.666665340000002; 0.33333333333333337; 2097152.0; 64310.13333333333; 0.0; 1.4666666666666666; 0.0; 0.0 +1376420155; 1; 2599.999602; 0.0; 0.0; 2097152.0; 76893.33333333333; 0.0; 1.2; 0.0; 0.0 +1376420455; 1; 2599.999602; 8.666665340000002; 0.33333333333333337; 2097152.0; 109050.4; 0.0; 1.6; 0.0; 0.06666666666666667 +1376420755; 1; 2599.999602; 6.933332272; 0.26666666666666666; 2097152.0; 109050.4; 0.0; 1.1333333333333333; 0.0; 0.0 +1376421055; 1; 2599.999602; 10.399998407999998; 0.4; 2097152.0; 202721.86666666667; 0.0; 2.8666666666666667; 0.06666666666666667; 0.5333333333333333 +1376421355; 1; 2599.999602; 5.199999203999999; 0.2; 2097152.0; 100661.6; 0.0; 1.4666666666666666; 0.0; 0.0 +1376421655; 1; 2599.999602; 0.0; 0.0; 2097152.0; 76893.33333333333; 0.0; 1.2666666666666666; 0.06666666666666667; 0.0 +1376421955; 1; 2599.999602; 45.066659768; 1.7333333333333334; 2097152.0; 360707.73333333334; 161.73333333333332; 14.466666666666667; 0.0; 0.2 +1376422255; 1; 2599.999602; 8.666665340000002; 0.33333333333333337; 2097152.0; 282414.4; 0.0; 1.5333333333333334; 0.0; 0.0 +1376422555; 1; 2599.999602; 6.933332272; 0.26666666666666666; 2097152.0; 194334.13333333333; 31.8; 3.8666666666666667; 0.0; 0.0 +1376422856; 1; 2599.999602; 0.0; 0.0; 2097152.0; 137012.26666666666; 0.0; 1.2666666666666666; 0.0; 0.0 +1376423156; 1; 2599.999602; 3.466666136; 0.13333333333333333; 2097152.0; 110448.53333333334; 0.0; 1.3333333333333333; 0.0; 0.0 +1376423456; 1; 2599.999602; 3.466666136; 0.13333333333333333; 2097152.0; 120235.46666666666; 0.0; 1.2; 0.0; 0.0 +1376423756; 1; 2599.999602; 0.0; 0.0; 2097152.0; 121633.6; 0.0; 1.2; 0.0; 0.0 +1376424056; 1; 2599.999602; 6.933332272; 0.26666666666666666; 2097152.0; 99263.46666666666; 0.0; 1.3333333333333333; 0.0; 0.0 +1376424356; 1; 2599.999602; 0.0; 0.0; 2097152.0; 79689.6; 0.0; 1.2; 0.0; 0.0 +1376424656; 1; 2599.999602; 15.599997612; 0.6; 2097152.0; 153790.13333333333; 0.06666666666666667; 8.933333333333334; 0.26666666666666666; 0.6 +1376424956; 1; 2599.999602; 15.599997612; 0.6; 2097152.0; 127226.13333333333; 0.0; 1.0; 0.0; 0.0 +1376425256; 1; 2599.999602; 10.399998407999998; 0.4; 2097152.0; 82485.86666666667; 0.0; 1.3333333333333333; 0.0; 0.0 +1376425556; 1; 2599.999602; 8.666665340000002; 0.33333333333333337; 2097152.0; 102059.73333333334; 0.0; 1.2; 0.0; 0.0 +1376425856; 1; 2599.999602; 8.666665340000002; 0.33333333333333337; 2097152.0; 99263.46666666666; 0.0; 1.2; 0.0; 0.0 +1376426156; 1; 2599.999602; 12.133331475999999; 0.4666666666666666; 2097152.0; 89476.53333333334; 0.0; 1.2; 0.0; 0.0 +1376426456; 1; 2599.999602; 10.399998407999998; 0.4; 2097152.0; 83884.0; 0.0; 1.4; 0.0; 0.0 +1376426756; 1; 2599.999602; 6.933332272; 0.26666666666666666; 2097152.0; 82485.86666666667; 0.0; 1.2666666666666666; 0.0; 0.0 +1376427056; 1; 2599.999602; 10.399998407999998; 0.4; 2097152.0; 100661.6; 0.0; 1.0; 0.06666666666666667; 0.0 +1376427356; 1; 2599.999602; 6.933332272; 0.26666666666666666; 2097152.0; 89476.53333333334; 0.0; 1.1333333333333333; 0.0; 0.0 +1376427656; 1; 2599.999602; 13.866664544; 0.5333333333333333; 2097152.0; 100661.6; 0.0; 1.2; 0.06666666666666667; 0.0 +1376427956; 1; 2599.999602; 10.399998407999998; 0.4; 2097152.0; 100661.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1376428256; 1; 2599.999602; 12.133331475999999; 0.4666666666666666; 2097152.0; 199926.4; 0.06666666666666667; 3.1333333333333333; 0.06666666666666667; 0.4666666666666667 +1376428556; 1; 2599.999602; 15.599997612; 0.6; 2097152.0; 163576.8; 0.0; 1.3333333333333333; 0.0; 0.0 +1376428856; 1; 2599.999602; 13.866664544; 0.5333333333333333; 2097152.0; 104856.0; 0.0; 1.2; 0.0; 0.0 +1376429156; 1; 2599.999602; 8.666665340000002; 0.33333333333333337; 2097152.0; 113244.8; 0.0; 1.2; 0.0; 0.0 +1376429456; 1; 2599.999602; 13.866664544; 0.5333333333333333; 2097152.0; 109050.4; 0.0; 1.2; 0.0; 0.0 +1376429756; 1; 2599.999602; 8.666665340000002; 0.33333333333333337; 2097152.0; 74097.06666666667; 0.0; 1.2; 0.0; 0.0 +1376430056; 1; 2599.999602; 10.399998407999998; 0.4; 2097152.0; 111846.66666666667; 0.0; 1.2; 0.0; 0.0 +1376430356; 1; 2599.999602; 8.666665340000002; 0.33333333333333337; 2097152.0; 134216.0; 0.0; 1.3333333333333333; 0.0; 0.0 +1376430656; 1; 2599.999602; 1.733333068; 0.06666666666666667; 2097152.0; 92272.8; 0.0; 1.3333333333333333; 0.0; 0.0 +1376430956; 1; 2599.999602; 12.133331475999999; 0.4666666666666666; 2097152.0; 96467.2; 0.0; 1.5333333333333334; 0.0; 0.0 +1376431256; 1; 2599.999602; 10.399998407999998; 0.4; 2097152.0; 121633.6; 0.0; 7.266666666666667; 0.2; 0.13333333333333333 +1376431556; 1; 2599.999602; 8.666665340000002; 0.33333333333333337; 2097152.0; 125828.0; 0.0; 1.2; 0.0; 0.0 +1376431856; 1; 2599.999602; 12.133331475999999; 0.4666666666666666; 2097152.0; 131420.53333333333; 0.0; 2.6; 0.06666666666666667; 0.4666666666666667 +1376432156; 1; 2599.999602; 8.666665340000002; 0.33333333333333337; 2097152.0; 102059.73333333334; 0.0; 1.4; 0.0; 0.0 +1376432456; 1; 2599.999602; 5.199999203999999; 0.2; 2097152.0; 92272.8; 0.0; 1.4; 0.0; 0.0 +1376432756; 1; 2599.999602; 6.933332272; 0.26666666666666666; 2097152.0; 96467.2; 0.0; 1.3333333333333333; 0.06666666666666667; 0.0 +1376433056; 1; 2599.999602; 15.599997612; 0.6; 2097152.0; 131419.73333333334; 0.06666666666666667; 2.7333333333333334; 0.0; 0.0 +1376433356; 1; 2599.999602; 15.599997612; 0.6; 2097152.0; 137013.06666666668; 0.0; 1.6666666666666667; 0.4666666666666667; 0.13333333333333333 +1376433656; 1; 2599.999602; 10.399998407999998; 0.4; 2097152.0; 92272.8; 0.0; 1.4; 0.0; 0.0 +1376433956; 1; 2599.999602; 6.933332272; 0.26666666666666666; 2097152.0; 114642.93333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1376434256; 1; 2599.999602; 6.933332272; 0.26666666666666666; 2097152.0; 117439.2; 0.0; 1.2; 0.0; 0.0 +1376434556; 1; 2599.999602; 6.933332272; 0.26666666666666666; 2097152.0; 100661.6; 0.0; 1.2; 0.0; 0.0 +1376434856; 1; 2599.999602; 8.666665340000002; 0.33333333333333337; 2097152.0; 79689.6; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1376435156; 1; 2599.999602; 5.199999203999999; 0.2; 2097152.0; 103457.86666666667; 0.0; 1.6; 0.0; 0.0 +1376435456; 1; 2599.999602; 15.599997612; 0.6; 2097152.0; 178955.46666666667; 0.13333333333333333; 3.066666666666667; 0.06666666666666667; 0.4666666666666667 +1376435756; 1; 2599.999602; 8.666665340000002; 0.33333333333333337; 2097152.0; 117439.2; 0.0; 1.1333333333333333; 0.0; 0.0 +1376436056; 1; 2599.999602; 6.933332272; 0.26666666666666666; 2097152.0; 137013.06666666668; 0.0; 1.2; 0.0; 0.0 +1376436356; 1; 2599.999602; 8.666665340000002; 0.33333333333333337; 2097152.0; 100661.6; 0.0; 1.4; 0.0; 0.0 +1376436656; 1; 2599.999602; 8.666665340000002; 0.33333333333333337; 2097152.0; 113244.8; 0.0; 1.1333333333333333; 0.0; 0.0 +1376436956; 1; 2599.999602; 3.466666136; 0.13333333333333333; 2097152.0; 85282.13333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1376437256; 1; 2599.999602; 8.666665340000002; 0.33333333333333337; 2097152.0; 104856.0; 0.0; 1.4; 0.0; 0.0 +1376437556; 1; 2599.999602; 12.133331475999999; 0.4666666666666666; 2097152.0; 97865.33333333333; 0.06666666666666667; 7.8; 0.3333333333333333; 0.13333333333333333 +1376437856; 1; 2599.999602; 13.866664544; 0.5333333333333333; 2097152.0; 89476.53333333334; 0.0; 1.5333333333333334; 0.0; 0.0 +1376438156; 1; 2599.999602; 3.466666136; 0.13333333333333333; 2097152.0; 90874.66666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1376438456; 1; 2599.999602; 10.399998407999998; 0.4; 2097152.0; 113244.8; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1376438756; 1; 2599.999602; 5.199999203999999; 0.2; 2097152.0; 93670.93333333333; 0.0; 1.2; 0.0; 0.0 +1376439056; 1; 2599.999602; 15.599997612; 0.6; 2097152.0; 197130.4; 0.0; 2.8666666666666667; 0.06666666666666667; 0.4666666666666667 +1376439356; 1; 2599.999602; 12.133331475999999; 0.4666666666666666; 2097152.0; 118836.53333333334; 0.0; 1.2; 0.0; 0.0 +1376439656; 1; 2599.999602; 8.666665340000002; 0.33333333333333337; 2097152.0; 103457.86666666667; 0.0; 1.3333333333333333; 0.0; 0.0 +1376439956; 1; 2599.999602; 8.666665340000002; 0.33333333333333337; 2097152.0; 93670.93333333333; 0.0; 1.3333333333333333; 0.2; 0.0 +1376440256; 1; 2599.999602; 8.666665340000002; 0.33333333333333337; 2097152.0; 67106.4; 0.0; 1.8; 0.0; 0.0 +1376440557; 1; 2599.999602; 13.866664544; 0.5333333333333333; 2097152.0; 69902.66666666667; 0.0; 1.4; 0.0; 0.0 +1376440857; 1; 2599.999602; 8.666665340000002; 0.33333333333333337; 2097152.0; 75495.2; 0.0; 1.2; 0.0; 0.0 +1376441157; 1; 2599.999602; 6.933332272; 0.26666666666666666; 2097152.0; 81087.73333333334; 0.0; 12.0; 0.06666666666666667; 0.0 +1376441457; 1; 2599.999602; 5.199999203999999; 0.2; 2097152.0; 92272.8; 0.0; 1.0666666666666667; 0.0; 0.0 +1376441757; 1; 2599.999602; 1.733333068; 0.06666666666666667; 2097152.0; 99263.46666666666; 0.0; 1.0; 0.0; 0.0 +1376442057; 1; 2599.999602; 10.399998407999998; 0.4; 2097152.0; 134216.0; 0.0; 1.3333333333333333; 0.0; 0.0 +1376442357; 1; 2599.999602; 8.666665340000002; 0.33333333333333337; 2097152.0; 96467.2; 0.0; 1.0; 0.0; 0.0 +1376442657; 1; 2599.999602; 8.666665340000002; 0.33333333333333337; 2097152.0; 180353.6; 0.0; 2.4; 0.06666666666666667; 0.5333333333333333 +1376442957; 1; 2599.99945; 31.777771055555554; 1.2222222222222223; 2097152.0; 97865.33333333333; 0.0; 0.625; 0.0; 0.0 +1376443257; 1; 2599.99945; 8.666664833333334; 0.33333333333333337; 2097152.0; 156585.33333333334; 0.0; 7.2; 0.3333333333333333; 0.13333333333333333 +1376443557; 1; 2599.99945; 1.7333329666666666; 0.06666666666666667; 2097152.0; 146800.0; 0.0; 1.2; 0.0; 0.0 +1376443857; 1; 2599.99945; 3.466665933333333; 0.13333333333333333; 2097152.0; 123031.73333333334; 0.0; 1.2; 0.0; 0.0 +1376444157; 1; 2599.99945; 6.933331866666666; 0.26666666666666666; 2097152.0; 114642.4; 0.0; 1.2666666666666666; 0.0; 0.0 +1376444457; 1; 2599.99945; 12.133330766666665; 0.4666666666666666; 2097152.0; 148197.33333333334; 0.0; 1.2; 0.06666666666666667; 0.0 +1376444757; 1; 2599.99945; 1.7333329666666666; 0.06666666666666667; 2097152.0; 142604.8; 0.0; 1.4666666666666666; 0.0; 0.0 +1376445057; 1; 2599.99945; 1.7333329666666666; 0.06666666666666667; 2097152.0; 121633.6; 0.0; 1.0; 0.0; 0.0 +1376445357; 1; 2599.99945; 0.0; 0.0; 2097152.0; 83884.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1376445657; 1; 2599.99945; 5.1999989; 0.2; 2097152.0; 96467.2; 0.0; 1.8666666666666667; 10.933333333333334; 0.0 +1376445957; 1; 2599.99945; 1.7333329666666666; 0.06666666666666667; 2097152.0; 88078.4; 0.0; 1.1333333333333333; 0.0; 0.0 +1376446257; 1; 2599.99945; 10.3999978; 0.4; 2097152.0; 134216.0; 0.0; 2.933333333333333; 0.06666666666666667; 0.4666666666666667 +1376446557; 1; 2599.99945; 1.7333329666666666; 0.06666666666666667; 2097152.0; 138411.2; 0.0; 1.2666666666666666; 0.06666666666666667; 0.0 +1376446857; 1; 2599.99945; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 1.2; 0.0; 0.0 +1376447157; 1; 2599.99945; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1376447457; 1; 2599.99945; 8.666664833333334; 0.33333333333333337; 2097152.0; 141206.66666666666; 0.0; 1.2; 0.06666666666666667; 0.0 +1376447757; 1; 2599.99945; 8.666664833333334; 0.33333333333333337; 2097152.0; 153789.6; 0.0; 1.2; 0.13333333333333333; 0.0 +1376448057; 1; 2599.99945; 3.466665933333333; 0.13333333333333333; 2097152.0; 104856.0; 0.0; 1.2; 0.0; 0.0 +1376448357; 1; 2599.99945; 3.466665933333333; 0.13333333333333333; 2097152.0; 120235.46666666666; 0.0; 1.2; 0.0; 0.0 +1376448657; 1; 2599.99945; 0.0; 0.0; 2097152.0; 123031.73333333334; 0.0; 1.0666666666666667; 0.0; 0.0 +1376448957; 1; 2599.99945; 10.3999978; 0.4; 2097152.0; 103457.86666666667; 0.0; 1.6; 0.3333333333333333; 0.0 +1376449257; 1; 2599.99945; 12.133330766666665; 0.4666666666666666; 2097152.0; 90874.66666666667; 0.0; 1.2; 0.0; 0.0 +1376449558; 1; 2599.99945; 8.666664833333334; 0.33333333333333337; 2097152.0; 104856.0; 0.0; 1.2; 0.0; 0.0 +1376449858; 1; 2599.99945; 15.599996699999998; 0.6; 2097152.0; 152390.93333333332; 0.06666666666666667; 2.6666666666666665; 0.06666666666666667; 0.4666666666666667 +1376450158; 1; 2599.99945; 12.133330766666665; 0.4666666666666666; 2097152.0; 137012.8; 0.0; 7.333333333333333; 0.26666666666666666; 0.13333333333333333 +1376450458; 1; 2599.99945; 1.7333329666666666; 0.06666666666666667; 2097152.0; 132818.66666666666; 0.0; 1.2666666666666666; 0.0; 0.0 +1376450758; 1; 2599.99945; 6.933331866666666; 0.26666666666666666; 2097152.0; 149595.46666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1376451058; 1; 2599.99945; 5.1999989; 0.2; 2097152.0; 110448.53333333334; 0.0; 1.2666666666666666; 0.0; 0.0 +1376451358; 1; 2599.99945; 3.466665933333333; 0.13333333333333333; 2097152.0; 100661.6; 0.0; 1.2; 0.0; 0.0 +1376451658; 1; 2599.99945; 0.0; 0.0; 2097152.0; 76893.33333333333; 0.0; 1.6666666666666667; 0.0; 0.0 +1376451958; 1; 2599.99945; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 1.4; 0.0; 0.0 +1376452258; 1; 2599.99945; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 1.1333333333333333; 0.0; 0.0 +1376452558; 1; 2599.99945; 3.466665933333333; 0.13333333333333333; 2097152.0; 95069.06666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1376452858; 1; 2599.99945; 5.1999989; 0.2; 2097152.0; 111846.66666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1376453158; 1; 2599.99945; 5.1999989; 0.2; 2097152.0; 102059.73333333334; 0.0; 1.4666666666666666; 0.0; 0.0 +1376453458; 1; 2599.99945; 13.866663733333333; 0.5333333333333333; 2097152.0; 130022.4; 0.0; 2.6; 0.2; 0.4666666666666667 +1376453758; 1; 2599.99945; 5.1999989; 0.2; 2097152.0; 104856.0; 0.0; 1.3333333333333333; 0.0; 0.0 +1376454058; 1; 2599.99945; 1.7333329666666666; 0.06666666666666667; 2097152.0; 92272.8; 0.0; 1.1333333333333333; 0.0; 0.0 +1376454358; 1; 2599.99945; 3.466665933333333; 0.13333333333333333; 2097152.0; 76893.33333333333; 0.0; 1.2; 0.0; 0.0 +1376454658; 1; 2599.99945; 3.466665933333333; 0.13333333333333333; 2097152.0; 67106.4; 0.0; 1.1333333333333333; 0.0; 0.0 +1376454958; 1; 2599.99945; 6.933331866666666; 0.26666666666666666; 2097152.0; 124429.86666666667; 0.0; 1.2; 0.0; 0.0 +1376455258; 1; 2599.99945; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 1.1333333333333333; 0.0; 0.0 +1376455558; 1; 2599.99945; 5.1999989; 0.2; 2097152.0; 89476.53333333334; 0.0; 6.933333333333334; 0.3333333333333333; 0.2 +1376455858; 1; 2599.99945; 5.1999989; 0.2; 2097152.0; 116041.06666666667; 0.0; 2.1333333333333333; 0.0; 0.0 +1376456158; 1; 2599.99945; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 1.3333333333333333; 0.0; 0.0 +1376456458; 1; 2599.99945; 5.1999989; 0.2; 2097152.0; 90874.66666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1376456758; 1; 2599.99945; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 1.2; 0.0; 0.0 +1376457058; 1; 2599.99945; 6.933331866666666; 0.26666666666666666; 2097152.0; 107652.26666666666; 0.0; 2.533333333333333; 0.0; 0.4666666666666667 +1376457359; 1; 2599.99945; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 1.3333333333333333; 0.06666666666666667; 0.0 +1376457659; 1; 2599.99945; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 1.1333333333333333; 0.0; 0.0 +1376457959; 1; 2599.99945; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 1.3333333333333333; 0.0; 0.0 +1376458259; 1; 2599.99945; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 1.3333333333333333; 0.0; 0.0 +1376458559; 1; 2599.99945; 0.0; 0.0; 2097152.0; 83884.0; 0.0; 1.5333333333333334; 0.06666666666666667; 0.0 +1376458859; 1; 2599.99945; 0.0; 0.0; 2097152.0; 83884.0; 0.0; 1.1333333333333333; 0.0; 0.0 +1376459159; 1; 2599.99945; 1.7333329666666666; 0.06666666666666667; 2097152.0; 106254.13333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1376459459; 1; 2599.99945; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1376459759; 1; 2599.99945; 0.0; 0.0; 2097152.0; 95069.06666666667; 0.0; 1.2; 0.0; 0.0 +1376460059; 1; 2599.99945; 0.0; 0.0; 2097152.0; 69902.66666666667; 0.0; 1.4; 0.0; 0.0 +1376460359; 1; 2599.99945; 0.0; 0.0; 2097152.0; 86680.26666666666; 0.0; 1.3333333333333333; 1.6; 0.0 +1376460659; 1; 2599.99945; 5.1999989; 0.2; 2097152.0; 150993.6; 0.06666666666666667; 2.8; 0.06666666666666667; 0.4666666666666667 +1376460959; 1; 2599.99945; 0.0; 0.0; 2097152.0; 125828.0; 0.0; 1.2666666666666666; 0.06666666666666667; 0.0 +1376461258; 1; 2599.99945; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 1.2; 0.0; 0.0 +1376461558; 1; 2599.99945; 3.466665933333333; 0.13333333333333333; 2097152.0; 75495.2; 0.06666666666666667; 6.8; 0.26666666666666666; 0.2 +1376461858; 1; 2599.99945; 124.79997359999999; 4.8; 2097152.0; 524285.86666666664; 161.46666666666667; 170.26666666666668; 20.866666666666667; 1.0666666666666667 +1376462158; 1; 2599.99945; 17.333329666666668; 0.6666666666666667; 2097152.0; 541063.2; 5.066666666666666; 12.2; 0.13333333333333333; 0.06666666666666667 +1376462458; 1; 2599.99945; 15.599996699999998; 0.6; 2097152.0; 243267.2; 31.8; 4.6; 0.06666666666666667; 0.0 +1376462758; 1; 2599.99945; 6.933331866666666; 0.26666666666666666; 2097152.0; 187343.2; 0.0; 2.8666666666666667; 0.0; 0.0 +1376463059; 1; 2599.99945; 1.7333329666666666; 0.06666666666666667; 2097152.0; 123030.93333333333; 0.0; 2.066666666666667; 0.0; 0.0 +1376463359; 1; 2599.99945; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 1.2; 0.06666666666666667; 0.0 +1376463659; 1; 2599.99945; 20.7999956; 0.8; 2097152.0; 116041.06666666667; 0.0; 1.2; 0.0; 0.0 +1376463959; 1; 2599.99945; 5.1999989; 0.2; 2097152.0; 111846.66666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1376464259; 1; 2599.999602; 28.599995622; 1.1; 2097152.0; 188740.0; 0.0; 1.4444444444444444; 0.1111111111111111; 0.0 +1376464559; 1; 2599.999602; 17.333330680000003; 0.6666666666666667; 2097152.0; 125827.46666666666; 0.0; 1.2; 0.06666666666666667; 0.0 +1376464859; 1; 2599.999602; 10.399998407999998; 0.4; 2097152.0; 109050.4; 0.0; 1.3333333333333333; 0.0; 0.0 +1376465159; 1; 2599.999602; 5.199999203999999; 0.2; 2097152.0; 104856.0; 0.0; 1.1333333333333333; 0.0; 0.0 +1376465459; 1; 2599.999602; 8.666665340000002; 0.33333333333333337; 2097152.0; 92272.8; 0.0; 1.5333333333333334; 0.0; 0.0 +1376465759; 1; 2599.999602; 12.133331475999999; 0.4666666666666666; 2097152.0; 81087.73333333334; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1376466059; 1; 2599.999602; 3.466666136; 0.13333333333333333; 2097152.0; 103457.86666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1376466359; 1; 2599.999602; 5.199999203999999; 0.2; 2097152.0; 125828.0; 0.0; 1.6; 0.0; 0.0 +1376466659; 1; 2599.999602; 3.466666136; 0.13333333333333333; 2097152.0; 138411.2; 0.0; 1.2; 0.0; 0.0 +1376466959; 1; 2599.999602; 3.466666136; 0.13333333333333333; 2097152.0; 124429.86666666667; 0.0; 1.3333333333333333; 0.0; 0.0 +1376467259; 1; 2599.999602; 1.733333068; 0.06666666666666667; 2097152.0; 107651.46666666666; 0.0; 1.2; 0.0; 0.0 +1376467559; 1; 2599.999602; 0.0; 0.0; 2097152.0; 125827.2; 0.0; 1.1333333333333333; 0.0; 0.0 +1376467859; 1; 2599.999602; 8.666665340000002; 0.33333333333333337; 2097152.0; 152391.73333333334; 0.06666666666666667; 2.4; 0.06666666666666667; 0.4666666666666667 +1376468159; 1; 2599.999602; 3.466666136; 0.13333333333333333; 2097152.0; 92272.8; 0.0; 1.2666666666666666; 0.06666666666666667; 0.0 +1376468459; 1; 2599.999602; 5.199999203999999; 0.2; 2097152.0; 131420.53333333333; 0.0; 7.4; 0.26666666666666666; 0.13333333333333333 +1376468759; 1; 2599.999602; 0.0; 0.0; 2097152.0; 125827.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1376469059; 1; 2599.999602; 0.0; 0.0; 2097152.0; 83884.0; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1376469359; 1; 2599.999602; 3.466666136; 0.13333333333333333; 2097152.0; 120235.46666666666; 0.0; 1.3333333333333333; 0.06666666666666667; 0.0 +1376469659; 1; 2599.999602; 1.733333068; 0.06666666666666667; 2097152.0; 114642.93333333333; 0.0; 1.6; 0.0; 0.0 +1376469959; 1; 2599.999602; 0.0; 0.0; 2097152.0; 95069.06666666667; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1376470259; 1; 2599.999602; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1376470559; 1; 2599.999602; 0.0; 0.0; 2097152.0; 116041.06666666667; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1376470859; 1; 2599.999602; 3.466666136; 0.13333333333333333; 2097152.0; 85282.13333333333; 0.0; 1.3333333333333333; 0.9333333333333333; 0.0 +1376471159; 1; 2599.999602; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 1.1333333333333333; 0.0; 0.0 +1376471459; 1; 2599.999602; 6.933332272; 0.26666666666666666; 2097152.0; 159381.6; 0.06666666666666667; 2.8; 0.13333333333333333; 0.4666666666666667 +1376471759; 1; 2599.999602; 3.466666136; 0.13333333333333333; 2097152.0; 88078.4; 0.0; 1.2; 0.0; 0.0 +1376472059; 1; 2599.999602; 0.0; 0.0; 2097152.0; 62912.0; 0.0; 1.2; 0.0; 0.0 +1376472359; 1; 2599.999602; 3.466666136; 0.13333333333333333; 2097152.0; 72698.93333333333; 0.0; 1.4; 0.0; 0.0 +1376472659; 1; 2599.999602; 1.733333068; 0.06666666666666667; 2097152.0; 97865.33333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1376472959; 1; 2599.999602; 8.666665340000002; 0.33333333333333337; 2097152.0; 150993.6; 20.6; 2.2666666666666666; 0.0; 0.06666666666666667 +1376473260; 1; 2599.999602; 20.799996815999997; 0.8; 2097152.0; 114642.93333333333; 0.0; 4.2; 0.0; 0.0 +1376473560; 1; 2599.999602; 5.199999203999999; 0.2; 2097152.0; 110448.53333333334; 0.0; 2.933333333333333; 0.0; 0.0 +1376473860; 1; 2599.999602; 6.933332272; 0.26666666666666666; 2097152.0; 142604.53333333333; 12.733333333333333; 13.666666666666666; 0.26666666666666666; 0.13333333333333333 +1376474160; 1; 2599.999602; 0.0; 0.0; 2097152.0; 139808.53333333333; 0.0; 1.4666666666666666; 0.0; 0.0 +1376474460; 1; 2599.999602; 0.0; 0.0; 2097152.0; 131420.53333333333; 0.0; 1.3333333333333333; 0.0; 0.0 +1376474760; 1; 2599.999602; 3.466666136; 0.13333333333333333; 2097152.0; 131420.53333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1376475060; 1; 2599.999602; 12.133331475999999; 0.4666666666666666; 2097152.0; 162177.86666666667; 0.0; 2.4; 0.06666666666666667; 0.4666666666666667 +1376475360; 1; 2599.999602; 3.466666136; 0.13333333333333333; 2097152.0; 130021.6; 0.0; 1.0666666666666667; 0.13333333333333333; 0.0 +1376475660; 1; 2599.999602; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 1.1333333333333333; 0.0; 0.0 +1376475960; 1; 2599.999602; 0.0; 0.0; 2097152.0; 106254.13333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1376476260; 1; 2599.999602; 3.466666136; 0.13333333333333333; 2097152.0; 127226.13333333333; 0.0; 1.2; 0.0; 0.0 +1376476560; 1; 2599.999602; 0.0; 0.0; 2097152.0; 113244.0; 0.0; 1.4666666666666666; 0.0; 0.0 +1376476860; 1; 2599.999602; 1.733333068; 0.06666666666666667; 2097152.0; 92272.8; 0.0; 1.2666666666666666; 0.06666666666666667; 0.0 +1376477160; 1; 2599.999602; 1.733333068; 0.06666666666666667; 2097152.0; 88078.4; 0.0; 1.0666666666666667; 0.0; 0.0 +1376477460; 1; 2599.999602; 0.0; 0.0; 2097152.0; 148197.06666666668; 0.0; 1.1333333333333333; 0.0; 0.0 +1376477760; 1; 2599.99945; 31.199993399999997; 1.2; 2097152.0; 134216.8; 0.0; 1.2222222222222223; 0.0; 0.0 +1376478060; 1; 2599.99945; 13.866663733333333; 0.5333333333333333; 2097152.0; 109050.4; 0.0; 1.4; 0.0; 0.0 +1376478360; 1; 2599.99945; 15.599996699999998; 0.6; 2097152.0; 93670.93333333333; 0.0; 1.3333333333333333; 0.0; 0.0 +1376478660; 1; 2599.99945; 12.133330766666665; 0.4666666666666666; 2097152.0; 174760.0; 0.0; 2.6; 0.06666666666666667; 0.5333333333333333 +1376478960; 1; 2599.99945; 12.133330766666665; 0.4666666666666666; 2097152.0; 121632.8; 0.0; 1.3333333333333333; 0.06666666666666667; 0.0 +1376479260; 1; 2599.99945; 17.333329666666668; 0.6666666666666667; 2097152.0; 96467.2; 0.0; 1.6; 0.06666666666666667; 0.0 +1376479560; 1; 2599.99945; 6.933331866666666; 0.26666666666666666; 2097152.0; 83884.0; 0.0; 1.1333333333333333; 0.0; 0.0 +1376479860; 1; 2599.99945; 8.666664833333334; 0.33333333333333337; 2097152.0; 113244.8; 0.0; 7.333333333333333; 0.26666666666666666; 0.13333333333333333 +1376480160; 1; 2599.99945; 10.3999978; 0.4; 2097152.0; 124429.06666666667; 0.0; 1.2; 0.0; 0.0 +1376480460; 1; 2599.999343; 37.55554606555556; 1.4444444444444446; 2097152.0; 83884.0; 0.0; 1.0; 0.0; 0.0 +1376480760; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 97865.33333333333; 0.0; 1.8; 0.06666666666666667; 0.0 +1376481060; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 111846.66666666667; 0.0; 1.3333333333333333; 0.0; 0.0 +1376481360; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 117439.2; 0.0; 1.2666666666666666; 0.0; 0.0 +1376481660; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 113244.0; 0.0; 1.2666666666666666; 0.0; 0.0 +1376481960; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 92272.8; 0.0; 1.4; 0.0; 0.0 +1376482261; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 142604.0; 0.0; 2.533333333333333; 0.0; 0.4666666666666667 +1376482561; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 92272.8; 0.0; 1.2666666666666666; 0.0; 0.0 +1376482861; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 109050.4; 0.0; 1.2; 0.0; 0.0 +1376483161; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 97865.33333333333; 0.0; 1.3333333333333333; 0.0; 0.0 +1376483461; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 117439.2; 0.0; 1.6; 0.0; 0.0 +1376483761; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 86680.26666666666; 0.0; 1.2; 0.0; 0.0 +1376484061; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 83884.0; 0.0; 1.2; 0.0; 0.0 +1376484361; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 81087.73333333334; 0.0; 1.1333333333333333; 0.0; 0.0 +1376484661; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 116041.06666666667; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1376484961; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 79689.6; 0.0; 12.266666666666667; 0.0; 0.0 +1376485261; 1; 2599.999343; 22.533327639333333; 0.8666666666666667; 2097152.0; 142605.6; 0.06666666666666667; 7.6; 0.2; 0.13333333333333333 +1376485561; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 130021.6; 0.0; 1.3333333333333333; 0.06666666666666667; 0.0 +1376485861; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 166371.46666666667; 0.06666666666666667; 2.7333333333333334; 0.0; 0.4666666666666667 +1376486161; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 113244.8; 0.0; 1.5333333333333334; 0.0; 0.0 +1376486461; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 100661.6; 0.0; 1.2; 0.0; 0.0 +1376486761; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 109050.4; 0.0; 1.1333333333333333; 0.0; 0.0 +1376487061; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 79689.6; 0.0; 1.2; 0.0; 0.0 +1376487361; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 104856.0; 0.0; 1.2; 0.0; 0.0 +1376487661; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 130021.6; 0.0; 1.6; 0.0; 0.0 +1376487961; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 97865.33333333333; 0.0; 1.3333333333333333; 0.0; 0.0 +1376488261; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 102059.73333333334; 0.0; 1.2666666666666666; 0.0; 0.0 +1376488561; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 134216.8; 0.0; 1.1333333333333333; 0.0; 0.0 +1376488861; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 163576.8; 1.8; 1.5333333333333334; 0.0; 0.06666666666666667 +1376489161; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 100661.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1376489461; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 148197.33333333334; 0.0; 2.3333333333333335; 0.0; 0.5333333333333333 +1376489761; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 83884.0; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1376490061; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 109050.4; 0.0; 1.8; 0.06666666666666667; 0.0 +1376490362; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 121633.6; 0.0; 1.4; 0.0; 0.0 +1376490662; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 142605.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1376490962; 1; 2599.999343; 22.533327639333333; 0.8666666666666667; 2097152.0; 130022.4; 0.0; 7.6; 0.26666666666666666; 0.26666666666666666 +1376491262; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 111846.66666666667; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1376491562; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 106254.13333333333; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1376491862; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 125827.2; 0.0; 1.3333333333333333; 0.13333333333333333; 0.0 +1376492162; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 121633.6; 0.0; 1.0; 0.0; 0.0 +1376492462; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 83884.0; 0.0; 1.2666666666666666; 0.13333333333333333; 0.0 +1376492762; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 111846.66666666667; 0.0; 1.2666666666666666; 0.0; 0.0 +1376493062; 1; 2599.999343; 24.266660534666663; 0.9333333333333332; 2097152.0; 162177.86666666667; 0.0; 2.8666666666666667; 0.06666666666666667; 0.4666666666666667 +1376493362; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 121632.8; 0.0; 1.2666666666666666; 0.0; 0.0 +1376493662; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 95069.06666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1376493962; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 75495.2; 0.0; 1.0; 2.4; 0.0 +1376494261; 1; 2599.999309; 25.999993089999997; 1.0; 2097152.0; 71300.8; 0.0; 1.1111111111111112; 0.0; 0.0 +1376494561; 1; 2599.999309; 19.06666159933333; 0.7333333333333333; 2097152.0; 97865.33333333333; 0.0; 1.2666666666666666; 0.0; 0.0 +1376494861; 1; 2599.999309; 19.06666159933333; 0.7333333333333333; 2097152.0; 99263.46666666666; 0.0; 1.3333333333333333; 0.06666666666666667; 0.0 +1376495161; 1; 2599.999309; 17.333328726666668; 0.6666666666666667; 2097152.0; 86680.26666666666; 0.0; 1.5333333333333334; 0.0; 0.0 +1376495461; 1; 2599.999309; 20.799994471999998; 0.8; 2097152.0; 93670.93333333333; 0.0; 1.0; 0.0; 0.0 +1376495761; 1; 2599.999309; 19.06666159933333; 0.7333333333333333; 2097152.0; 85282.13333333333; 0.0; 1.2; 0.06666666666666667; 0.0 +1376496061; 1; 2599.999309; 17.333328726666668; 0.6666666666666667; 2097152.0; 97865.33333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1376496362; 1; 2599.999309; 20.799994471999998; 0.8; 2097152.0; 96467.2; 0.0; 1.1333333333333333; 0.0; 0.0 +1376496662; 1; 2599.999309; 20.799994471999998; 0.8; 2097152.0; 163575.2; 0.2; 2.8666666666666667; 0.13333333333333333; 0.5333333333333333 +1376496962; 1; 2599.999309; 19.06666159933333; 0.7333333333333333; 2097152.0; 93670.93333333333; 0.0; 7.466666666666667; 0.3333333333333333; 0.13333333333333333 +1376497262; 1; 2599.999309; 20.799994471999998; 0.8; 2097152.0; 120235.46666666666; 0.0; 1.6; 0.0; 0.0 +1376497562; 1; 2599.999309; 13.86666298133333; 0.5333333333333333; 2097152.0; 72698.93333333333; 0.0; 1.2; 0.0; 0.0 +1376497862; 1; 2599.999309; 19.06666159933333; 0.7333333333333333; 2097152.0; 85282.13333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1376498162; 1; 2599.999309; 20.799994471999998; 0.8; 2097152.0; 89476.53333333334; 0.0; 1.0666666666666667; 0.0; 0.0 +1376498462; 1; 2599.999297; 29.249992091249997; 1.125; 2097152.0; 76019.5; 0.0; 1.0; 0.0; 0.0 +1376498762; 1; 2599.999297; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 1.3333333333333333; 0.06666666666666667; 0.0 +1376499062; 1; 2599.999297; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 1.2; 0.0; 0.0 +1376499362; 1; 2599.999297; 0.0; 0.0; 2097152.0; 95069.06666666667; 0.0; 1.2666666666666666; 0.0; 0.0 +1376499662; 1; 2599.999297; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1376499962; 1; 2599.999297; 5.199998594; 0.2; 2097152.0; 86680.26666666666; 0.0; 1.4; 0.0; 0.0 +1376500262; 1; 2599.999297; 6.933331458666666; 0.26666666666666666; 2097152.0; 152391.2; 0.06666666666666667; 2.533333333333333; 0.06666666666666667; 0.5333333333333333 +1376500562; 1; 2599.999297; 0.0; 0.0; 2097152.0; 117438.93333333333; 0.0; 1.2; 0.2; 0.0 +1376500862; 1; 2599.999297; 0.0; 0.0; 2097152.0; 114642.93333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1376501162; 1; 2599.999297; 8.666664323333334; 0.33333333333333337; 2097152.0; 114642.93333333333; 0.0; 1.1333333333333333; 143.66666666666666; 0.0 +1376501462; 1; 2599.999297; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 1.5333333333333334; 0.0; 0.0 +1376501762; 1; 2599.999297; 0.0; 0.0; 2097152.0; 76893.33333333333; 0.0; 1.2666666666666666; 0.26666666666666666; 0.0 +1376502062; 1; 2599.999297; 0.0; 0.0; 2097152.0; 123031.73333333334; 0.0; 1.4; 0.0; 0.0 +1376502362; 1; 2599.999297; 3.466665729333333; 0.13333333333333333; 2097152.0; 121633.6; 0.0; 1.0; 0.0; 0.0 +1376502662; 1; 2599.999297; 3.466665729333333; 0.13333333333333333; 2097152.0; 104856.0; 0.0; 1.2; 8.4; 0.0 +1376502962; 1; 2599.999297; 0.0; 0.0; 2097152.0; 128624.26666666666; 0.0; 1.2; 0.0; 0.0 +1376503263; 1; 2599.999297; 0.0; 0.0; 2097152.0; 113244.26666666666; 0.0; 1.2; 0.0; 0.0 +1376503563; 1; 2599.999297; 5.199998594; 0.2; 2097152.0; 138410.93333333332; 0.0; 7.466666666666667; 0.2; 0.13333333333333333 +1376503863; 1; 2599.999297; 6.933331458666666; 0.26666666666666666; 2097152.0; 169168.0; 0.0; 2.7333333333333334; 0.06666666666666667; 0.4666666666666667 +1376504163; 1; 2599.999297; 0.0; 0.0; 2097152.0; 130022.13333333333; 0.0; 1.6; 0.0; 0.0 +1376504463; 1; 2599.999297; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 1.2666666666666666; 0.0; 0.0 +1376504763; 1; 2599.999297; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1376505063; 1; 2599.999297; 0.0; 0.0; 2097152.0; 134216.8; 0.0; 1.2; 0.0; 0.0 +1376505363; 1; 2599.999297; 0.0; 0.0; 2097152.0; 116041.06666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1376505663; 1; 2599.999297; 0.0; 0.0; 2097152.0; 123031.73333333334; 0.0; 1.5333333333333334; 0.0; 0.0 +1376505963; 1; 2599.999297; 0.0; 0.0; 2097152.0; 72698.93333333333; 0.0; 1.2; 0.0; 0.0 +1376506263; 1; 2599.999297; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 1.2666666666666666; 0.0; 0.0 +1376506563; 1; 2599.999297; 0.0; 0.0; 2097152.0; 81087.73333333334; 0.0; 1.2666666666666666; 0.0; 0.0 +1376506863; 1; 2599.999297; 0.0; 0.0; 2097152.0; 83884.0; 0.0; 1.4; 0.0; 0.0 +1376507163; 1; 2599.999297; 0.0; 0.0; 2097152.0; 82485.86666666667; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1376507463; 1; 2599.999297; 5.199998594; 0.2; 2097152.0; 162177.86666666667; 0.06666666666666667; 2.7333333333333334; 0.0; 0.4666666666666667 +1376507763; 1; 2599.999297; 3.466665729333333; 0.13333333333333333; 2097152.0; 116041.06666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1376508063; 1; 2599.999297; 317.19991423399995; 12.2; 2097152.0; 437603.2; 907.8; 1409.7333333333333; 246.6; 7.866666666666666 +1376508363; 1; 2599.999297; 109.199970474; 4.2; 2097152.0; 829071.7333333333; 1.4666666666666666; 6.466666666666667; 0.0; 0.13333333333333333 +1376508663; 1; 2599.999304; 31.199991648; 1.2; 2097152.0; 419428.0; 0.0; 1.7777777777777777; 0.0; 0.0 +1376508963; 1; 2599.999304; 34.66665738666667; 1.3333333333333335; 2097152.0; 321561.86666666664; 0.8; 5.0; 0.0; 0.06666666666666667 +1376509263; 1; 2599.999304; 20.799994432; 0.8; 2097152.0; 223693.33333333334; 0.0; 1.5333333333333334; 0.0; 0.0 +1376509563; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 130022.4; 0.0; 1.1333333333333333; 0.0; 0.0 +1376509863; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 88078.4; 0.0; 1.0; 0.13333333333333333; 0.0 +1376510163; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 78291.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1376510463; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 92272.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1376510763; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 131420.53333333333; 0.0; 7.133333333333334; 0.2; 0.13333333333333333 +1376511064; 1; 2599.999304; 20.799994432; 0.8; 2097152.0; 201324.8; 17.266666666666666; 2.6; 0.06666666666666667; 0.4666666666666667 +1376511364; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 152391.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1376511664; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 113244.8; 0.0; 1.2; 0.0; 0.0 +1376511964; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 102059.73333333334; 0.0; 0.8666666666666667; 0.13333333333333333; 0.0 +1376512264; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 83884.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1376512564; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 83884.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1376512864; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 93670.93333333333; 0.0; 1.0; 0.0; 0.0 +1376513164; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 97865.33333333333; 0.0; 1.0; 0.0; 0.0 +1376513464; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 130022.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1376513764; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 75495.2; 0.0; 1.0; 0.0; 0.0 +1376514064; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 90874.66666666667; 0.0; 1.0; 0.0; 0.0 +1376514364; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 110448.53333333334; 0.0; 0.6; 0.06666666666666667; 0.0 +1376514664; 1; 2599.999304; 17.333328693333335; 0.6666666666666667; 2097152.0; 155187.2; 0.0; 2.3333333333333335; 0.0; 0.5333333333333333 +1376514964; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 100661.6; 0.0; 0.8; 0.0; 0.0 +1376515264; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 114642.93333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1376515564; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 130022.4; 0.0; 0.6666666666666666; 0.0; 0.0 +1376515864; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 134216.8; 0.0; 0.7333333333333333; 0.0; 0.0 +1376516164; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 113244.8; 0.0; 0.8; 0.0; 0.0 +1376516464; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 90874.66666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1376516764; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 75495.2; 0.0; 1.0; 0.0; 0.0 +1376517064; 1; 2599.999304; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1376517365; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 121632.8; 0.0; 6.866666666666666; 0.2; 0.13333333333333333 +1376517665; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 121632.8; 0.0; 0.8; 0.0; 0.0 +1376517965; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 96467.2; 0.0; 1.1333333333333333; 0.0; 0.0 +1376518265; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 163576.0; 0.06666666666666667; 2.6; 0.06666666666666667; 0.5333333333333333 +1376518565; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 125827.2; 0.0; 0.7333333333333333; 0.0; 0.0 +1376518865; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 100661.6; 0.0; 0.5333333333333333; 0.0; 0.0 +1376519165; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 131420.53333333333; 0.0; 0.6; 0.0; 0.0 +1376519465; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 85282.13333333333; 2.7333333333333334; 1.2; 0.0; 0.0 +1376519765; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 127226.13333333333; 0.0; 1.0; 0.06666666666666667; 0.06666666666666667 +1376520065; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 137013.06666666668; 0.0; 0.8; 0.0; 0.0 +1376520365; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 125828.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1376520665; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 121633.6; 0.0; 0.6; 0.0; 0.0 +1376520965; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 97865.33333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1376521265; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 120235.46666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1376521565; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 141206.66666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1376521865; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 164974.13333333333; 0.0; 2.1333333333333333; 0.06666666666666667; 0.4666666666666667 +1376522165; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 113244.8; 0.0; 1.0; 0.0; 0.0 +1376522465; 1; 2599.999304; 136.93329667733332; 5.266666666666667; 2097152.0; 171964.0; 161.86666666666667; 14.466666666666667; 0.0; 0.0 +1376522765; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 289404.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1376523065; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 170567.46666666667; 31.8; 3.7333333333333334; 0.0; 0.0 +1376523365; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 226490.4; 0.0; 0.8; 0.0; 0.0 +1376523665; 1; 2599.999304; 17.333328693333335; 0.6666666666666667; 2097152.0; 117439.2; 0.0; 1.1333333333333333; 0.0; 0.0 +1376523965; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 76893.33333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1376524265; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 120235.46666666666; 0.0; 7.0; 0.2; 0.13333333333333333 +1376524565; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 146798.4; 0.0; 0.8; 0.0; 0.0 +1376524865; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 100661.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1376525165; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 116041.06666666667; 0.0; 1.0; 0.0; 0.0 +1376525465; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 159380.8; 0.0; 2.1333333333333333; 0.06666666666666667; 0.4666666666666667 +1376525765; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 117439.2; 0.06666666666666667; 0.9333333333333333; 0.0; 0.0 +1376526065; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 142605.6; 0.0; 0.8; 0.0; 0.0 +1376526365; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 107652.26666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1376526665; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 120235.46666666666; 0.6666666666666666; 1.1333333333333333; 0.0; 0.0 +1376526965; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 116041.06666666667; 0.0; 0.8; 0.0; 0.0 +1376527265; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 107652.26666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1376527565; 1; 2599.999304; 15.599995824; 0.6; 2097152.0; 109050.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1376527865; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 96467.2; 0.0; 0.8; 0.0; 0.0 +1376528165; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 83884.0; 0.0; 0.8; 0.0; 0.0 +1376528465; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 86680.26666666666; 0.0; 11.666666666666666; 0.0; 0.0 +1376528765; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 142604.8; 0.0; 0.6666666666666666; 0.0; 0.0 +1376529065; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 163576.0; 0.06666666666666667; 2.6; 0.06666666666666667; 0.4666666666666667 +1376529365; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 114642.93333333333; 0.0; 0.6666666666666666; 0.0; 0.0 +1376529666; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 109050.4; 0.0; 0.6; 0.0; 0.0 +1376529966; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 89476.53333333334; 0.0; 0.6; 0.06666666666666667; 0.0 +1376530266; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 128624.26666666666; 0.06666666666666667; 0.8666666666666667; 0.0; 0.0 +1376530566; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 130022.4; 0.0; 0.6; 0.0; 0.0 +1376530866; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 159382.4; 0.0; 6.933333333333334; 0.26666666666666666; 0.13333333333333333 +1376531166; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 93670.93333333333; 0.0; 0.8; 0.0; 0.0 +1376531466; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 100661.6; 0.0; 0.8; 0.0; 0.0 +1376531766; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 120235.46666666666; 0.0; 0.8; 0.0; 0.0 +1376532066; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 100661.6; 0.0; 0.6666666666666666; 0.0; 0.0 +1376532366; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 79689.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1376532666; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 163575.73333333334; 0.0; 2.066666666666667; 0.0; 0.5333333333333333 +1376532966; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 155188.0; 0.0; 0.8; 0.0; 0.0 +1376533266; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 138411.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1376533566; 1; 2599.999304; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1376533866; 1; 2599.999304; 0.0; 0.0; 2097152.0; 124429.86666666667; 0.0; 0.5333333333333333; 0.0; 0.0 +1376534166; 1; 2599.999304; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 0.6; 0.0; 0.0 +1376534466; 1; 2599.999304; 17.333328693333335; 0.6666666666666667; 2097152.0; 116041.06666666667; 0.0; 1.6; 0.0; 0.0 +1376534766; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 83884.0; 0.0; 0.8; 0.0; 0.0 +1376535066; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 93670.93333333333; 0.0; 1.0; 0.0; 0.0 +1376535366; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 121633.6; 0.0; 0.7333333333333333; 0.0; 0.0 +1376535666; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 104856.0; 0.0; 0.6666666666666666; 0.0; 0.0 +1376535966; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 114642.93333333333; 0.0; 1.0; 0.0; 0.0 +1376536266; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 199926.13333333333; 0.0; 2.1333333333333333; 0.06666666666666667; 0.4666666666666667 +1376536566; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 128624.0; 0.0; 0.7333333333333333; 0.0; 0.0 +1376536866; 1; 2599.999304; 0.0; 0.0; 2097152.0; 132818.66666666666; 0.0; 0.6; 0.0; 0.0 +1376537166; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 103457.86666666667; 0.0; 0.5333333333333333; 0.0; 0.0 +1376537466; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 103457.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1376537766; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 104856.0; 12.733333333333333; 13.066666666666666; 0.3333333333333333; 0.2 +1376538066; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 155188.8; 0.0; 0.8; 0.0; 0.0 +1376538366; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 121633.6; 0.0; 1.4; 0.0; 0.0 +1376538666; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 128624.26666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1376538967; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 174761.33333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1376539267; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 117438.13333333333; 0.0; 0.6; 0.0; 0.0 +1376539567; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 125828.0; 0.0; 0.8; 0.0; 0.0 +1376539867; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 174760.53333333333; 0.06666666666666667; 2.4; 0.06666666666666667; 0.4666666666666667 +1376540167; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 100661.33333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1376540466; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 104856.0; 0.0; 0.5333333333333333; 0.0; 0.0 +1376540766; 1; 2599.999304; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.6; 0.0; 0.0 +1376541066; 1; 2599.999304; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.6666666666666666; 0.0; 0.0 +1376541366; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 97865.33333333333; 0.0; 0.6; 0.0; 0.0 +1376541666; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 88078.4; 0.0; 1.0; 0.0; 0.0 +1376541966; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 71300.8; 0.0; 0.5333333333333333; 0.0; 0.0 +1376542267; 1; 2599.999304; 0.0; 0.0; 2097152.0; 110448.26666666666; 0.0; 0.6666666666666666; 0.0; 0.0 +1376542567; 1; 2599.999304; 0.0; 0.0; 2097152.0; 148196.26666666666; 0.0; 0.6666666666666666; 0.0; 0.0 +1376542867; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 86680.26666666666; 0.0; 0.8; 0.0; 0.0 +1376543167; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 102059.73333333334; 0.0; 0.6666666666666666; 0.0; 0.0 +1376543467; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 188741.33333333334; 0.06666666666666667; 1.8666666666666667; 0.06666666666666667; 0.4666666666666667 +1376543767; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 142605.33333333334; 0.0; 6.8; 0.2; 0.13333333333333333 +1376544067; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 121633.6; 0.0; 0.6; 0.0; 0.0 +1376544367; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1376544667; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 93670.93333333333; 0.0; 0.6; 0.0; 0.0 +1376544967; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 132818.66666666666; 0.0; 0.6; 0.0; 0.0 +1376545267; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 106254.13333333333; 0.0; 0.6; 0.0; 0.0 +1376545567; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 100661.6; 0.0; 0.6666666666666666; 0.0; 0.0 +1376545867; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 109050.4; 0.0; 1.0666666666666667; 0.0; 0.0 +1376546167; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 83884.0; 0.0; 0.6666666666666666; 0.0; 0.0 +1376546467; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 103457.86666666667; 0.0; 0.6666666666666666; 0.0; 0.0 +1376546767; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 103457.86666666667; 0.0; 0.5333333333333333; 0.0; 0.0 +1376547067; 1; 2599.999304; 19.066661562666667; 0.7333333333333333; 2097152.0; 166371.73333333334; 0.0; 2.466666666666667; 0.06666666666666667; 0.4666666666666667 +1376547367; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 131420.26666666666; 0.0; 0.8; 0.0; 0.0 +1376547667; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 97865.33333333333; 0.0; 0.6; 0.0; 0.0 +1376547967; 1; 2599.999304; 0.0; 0.0; 2097152.0; 118836.53333333334; 0.0; 0.6; 0.0; 0.0 +1376548267; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 114642.93333333333; 0.0; 0.6; 0.0; 0.0 +1376548567; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 159382.4; 0.0; 1.9333333333333333; 0.0; 0.0 +1376548867; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 118837.33333333333; 0.0; 1.3333333333333333; 0.0; 0.0 +1376549167; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 72698.93333333333; 0.0; 0.6666666666666666; 0.06666666666666667; 0.0 +1376549467; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 93670.93333333333; 0.0; 0.8; 0.0; 0.0 +1376549767; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 103457.86666666667; 0.0; 0.8; 0.0; 0.0 +1376550067; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 153789.06666666668; 0.0; 6.866666666666666; 0.2; 0.13333333333333333 +1376550367; 1; 2599.999304; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.6666666666666666; 0.0; 0.0 +1376550667; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 192936.0; 0.0; 2.066666666666667; 0.06666666666666667; 0.4666666666666667 +1376550968; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 134216.53333333333; 0.0; 0.6; 0.0; 0.0 +1376551268; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 102059.73333333334; 0.0; 0.8; 0.0; 0.0 +1376551568; 1; 2599.999304; 0.0; 0.0; 2097152.0; 72698.93333333333; 0.0; 0.6; 0.0; 0.0 +1376551868; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 81087.73333333334; 0.0; 0.6666666666666666; 0.0; 0.0 +1376552168; 1; 2599.999304; 0.0; 0.0; 2097152.0; 79689.6; 0.0; 0.5333333333333333; 0.0; 0.0 +1376552468; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 95069.06666666667; 0.0; 1.0; 0.0; 0.0 +1376552768; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 125828.0; 0.0; 1.6; 0.0; 0.0 +1376553068; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 82485.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1376553368; 1; 2599.999304; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1376553668; 1; 2599.999304; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1376553968; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 102059.73333333334; 0.7333333333333333; 1.9333333333333333; 1.2; 0.0 +1376554268; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 159381.86666666667; 0.06666666666666667; 2.6; 0.06666666666666667; 0.4666666666666667 +1376554568; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 155187.73333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1376554868; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 118837.33333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1376555168; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 89476.53333333334; 0.0; 1.0; 0.0; 0.0 +1376555468; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 113244.8; 0.0; 1.1333333333333333; 0.0; 0.0 +1376555768; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 110448.53333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1376556068; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 109050.4; 0.0; 1.0; 0.0; 0.0 +1376556368; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 141207.46666666667; 0.0; 6.8; 0.2; 0.13333333333333333 +1376556668; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 150993.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1376556968; 1; 2599.999304; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1376557268; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 99263.46666666666; 1.5333333333333334; 1.4666666666666666; 0.0; 0.0 +1376557568; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 134216.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1376557868; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 176159.2; 0.06666666666666667; 2.2666666666666666; 0.06666666666666667; 0.5333333333333333 +1376558168; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 123031.73333333334; 0.0; 1.0; 0.06666666666666667; 0.0 +1376558468; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 97865.33333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1376558768; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 71300.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1376559068; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 69902.66666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1376559368; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 71300.8; 0.0; 1.1333333333333333; 0.0; 0.0 +1376559668; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 116041.06666666667; 0.0; 1.0; 0.0; 0.0 +1376559968; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 141207.46666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1376560268; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 90874.66666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1376560568; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1376560868; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 130021.6; 0.0; 1.1333333333333333; 0.0; 0.0 +1376561168; 1; 2599.999304; 0.0; 0.0; 2097152.0; 134215.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1376561468; 1; 2599.999304; 17.333328693333335; 0.6666666666666667; 2097152.0; 132818.4; 0.0; 2.4; 0.06666666666666667; 0.4666666666666667 +1376561768; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 92272.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1376562068; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 96467.2; 0.0; 7.2; 0.26666666666666666; 0.13333333333333333 +1376562368; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 110448.53333333334; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1376562668; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1376562968; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 95069.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1376563269; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 92272.8; 0.0; 1.0; 0.0; 0.0 +1376563569; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 117439.2; 0.0; 1.0; 0.0; 0.0 +1376563869; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 117439.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1376564169; 1; 2599.999304; 195.86661423466666; 7.533333333333334; 2097152.0; 359310.4; 162.26666666666668; 117.66666666666667; 0.0; 0.4 +1376564469; 1; 2599.999304; 15.599995824; 0.6; 2097152.0; 655707.7333333333; 0.0; 6.0; 0.06666666666666667; 0.0 +1376564769; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 292200.8; 31.8; 3.466666666666667; 0.0; 0.0 +1376565069; 1; 2599.999304; 24.266660170666665; 0.9333333333333332; 2097152.0; 255850.4; 18.0; 3.933333333333333; 0.7333333333333333; 0.4666666666666667 +1376565369; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 169168.26666666666; 0.0; 1.5333333333333334; 0.0; 0.0 +1376565669; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 103457.86666666667; 0.0; 0.6; 0.06666666666666667; 0.0 +1376565969; 1; 2599.999304; 0.0; 0.0; 2097152.0; 86680.26666666666; 0.0; 0.6666666666666666; 0.0; 0.0 +1376566269; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 121633.33333333333; 0.0; 0.6; 0.06666666666666667; 0.0 +1376566569; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 113244.53333333334; 0.0; 1.0666666666666667; 0.0; 0.0 +1376566869; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 107652.26666666666; 0.0; 0.8; 0.0; 0.0 +1376567169; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 111846.66666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1376567469; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 82485.86666666667; 0.0; 0.6666666666666666; 0.0; 0.0 +1376567769; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 95069.06666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1376568069; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 104856.0; 0.0; 0.5333333333333333; 0.0; 0.0 +1376568369; 1; 2599.999304; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.6666666666666666; 0.0; 0.0 +1376568669; 1; 2599.999304; 15.599995824; 0.6; 2097152.0; 211111.2; 0.06666666666666667; 2.3333333333333335; 0.06666666666666667; 0.4666666666666667 +1376568969; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 149595.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1376569269; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 109050.4; 0.0; 7.333333333333333; 0.26666666666666666; 0.2 +1376569569; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 100661.6; 0.0; 0.8; 0.0; 0.0 +1376569869; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 78291.46666666666; 0.0; 0.6; 0.0; 0.0 +1376570169; 1; 2599.999304; 0.0; 0.0; 2097152.0; 71300.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1376570469; 1; 2599.999304; 17.333328693333335; 0.6666666666666667; 2097152.0; 92272.8; 0.0; 1.0; 0.06666666666666667; 0.0 +1376570769; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 124429.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1376571069; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 107652.26666666666; 0.0; 0.6666666666666666; 0.0; 0.0 +1376571369; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 113244.8; 0.0; 0.9333333333333333; 0.3333333333333333; 0.0 +1376571669; 1; 2599.999304; 0.0; 0.0; 2097152.0; 89476.53333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1376571969; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 79689.6; 0.0; 11.6; 0.0; 0.0 +1376572269; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 141207.2; 1.0; 3.2666666666666666; 0.06666666666666667; 0.4666666666666667 +1376572569; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 132818.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1376572869; 1; 2599.999304; 0.0; 0.0; 2097152.0; 125828.0; 0.0; 0.6; 0.0; 0.0 +1376573169; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 93670.93333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1376573469; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 118837.33333333333; 0.0; 1.0; 0.0; 0.0 +1376573769; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 116041.06666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1376574069; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 104856.0; 0.0; 0.7333333333333333; 0.0; 0.0 +1376574369; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 100661.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1376574669; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 72698.93333333333; 0.0; 1.0; 0.0; 0.0 +1376574969; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 76893.33333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1376575269; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 92272.8; 0.0; 0.6666666666666666; 0.0; 0.0 +1376575569; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 137013.06666666668; 0.0; 0.6; 0.0; 0.0 +1376575869; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 163576.0; 0.06666666666666667; 2.533333333333333; 0.06666666666666667; 0.5333333333333333 +1376576169; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 155188.0; 0.06666666666666667; 6.866666666666666; 0.26666666666666666; 0.13333333333333333 +1376576469; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 121633.6; 0.0; 0.8; 0.13333333333333333; 0.0 +1376576769; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 92272.8; 0.0; 0.6666666666666666; 0.3333333333333333; 0.0 +1376577069; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 89476.53333333334; 0.0; 1.0666666666666667; 0.8; 0.06666666666666667 +1376577369; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 111846.66666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1376577669; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 111846.66666666667; 0.0; 0.8; 0.0; 0.0 +1376577970; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 100661.6; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1376578270; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.26666666666666666; 0.0 +1376578570; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 117438.4; 0.0; 0.6; 0.0; 0.0 +1376578870; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 120235.46666666666; 0.0; 1.8; 0.0; 0.0 +1376579170; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 124429.86666666667; 0.0; 0.8; 0.0; 0.0 +1376579470; 1; 2599.999304; 19.066661562666667; 0.7333333333333333; 2097152.0; 155188.0; 0.0; 1.8666666666666667; 0.06666666666666667; 0.4666666666666667 +1376579770; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 114642.93333333333; 0.0; 0.6; 0.06666666666666667; 0.0 +1376580070; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 125827.2; 0.0; 0.6; 0.0; 0.0 +1376580370; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 123031.73333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1376580670; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 141206.66666666666; 0.0; 0.6; 0.0; 0.0 +1376580970; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 86680.26666666666; 0.0; 0.6666666666666666; 0.0; 0.0 +1376581270; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 141206.66666666666; 0.0; 0.6666666666666666; 0.0; 0.0 +1376581570; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 155188.0; 0.0; 7.4; 0.3333333333333333; 0.13333333333333333 +1376581870; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 144002.93333333332; 0.0; 0.6666666666666666; 0.0; 0.0 +1376582170; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 127226.13333333333; 0.0; 0.9333333333333333; 0.13333333333333333; 0.0 +1376582470; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 106254.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1376582770; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 111846.66666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1376583070; 1; 2599.999304; 19.066661562666667; 0.7333333333333333; 2097152.0; 192936.0; 0.0; 2.3333333333333335; 0.06666666666666667; 0.4666666666666667 +1376583370; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 90874.66666666667; 0.0; 0.6666666666666666; 0.0; 0.0 +1376583670; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 93670.93333333333; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1376583970; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 79689.6; 0.0; 0.8; 0.0; 0.0 +1376584270; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 86680.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1376584570; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 89476.53333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1376584870; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 89476.53333333334; 0.0; 0.6666666666666666; 0.0; 0.0 +1376585170; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 90874.66666666667; 0.0; 0.6; 0.0; 0.0 +1376585470; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 86680.26666666666; 0.0; 0.6666666666666666; 2.8; 0.0 +1376585770; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 82485.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1376586070; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 99263.46666666666; 0.0; 0.6; 0.13333333333333333; 0.0 +1376586370; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 142604.0; 0.0; 0.6666666666666666; 0.0; 0.0 +1376586670; 1; 2599.999304; 15.599995824; 0.6; 2097152.0; 176159.2; 0.06666666666666667; 2.2666666666666666; 0.06666666666666667; 0.4666666666666667 +1376586970; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 99263.46666666666; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1376587270; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 93670.93333333333; 0.0; 1.0; 0.0; 0.0 +1376587571; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 118837.33333333333; 0.06666666666666667; 6.933333333333334; 0.2; 0.13333333333333333 +1376587871; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 90874.66666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1376588171; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 86680.26666666666; 0.0; 0.6666666666666666; 0.0; 0.0 +1376588471; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 145401.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1376588771; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 121633.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1376589071; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 107652.26666666666; 0.0; 0.6; 0.0; 0.0 +1376589371; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 118837.33333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1376589670; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 113244.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1376589970; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 109050.4; 0.0; 0.8; 0.0; 0.0 +1376590270; 1; 2599.999304; 15.599995824; 0.6; 2097152.0; 150993.6; 0.2; 2.0; 0.06666666666666667; 0.4666666666666667 +1376590570; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 123031.73333333334; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1376590870; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 95069.06666666667; 0.0; 0.6; 0.06666666666666667; 0.0 +1376591170; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 82485.86666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1376591470; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 95069.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1376591771; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 71300.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1376592071; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 74097.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1376592371; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 62912.0; 0.0; 0.5333333333333333; 0.13333333333333333; 0.0 +1376592671; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 116041.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1376592971; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 104856.0; 0.0; 0.8; 0.0; 0.0 +1376593271; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 120235.46666666666; 0.0; 0.5333333333333333; 0.0; 0.0 +1376593571; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 103457.86666666667; 0.0; 0.6; 0.0; 0.0 +1376593871; 1; 2599.999304; 20.799994432; 0.8; 2097152.0; 170565.86666666667; 0.0; 8.2; 0.26666666666666666; 0.6666666666666666 +1376594171; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 125828.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1376594471; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 79689.6; 0.0; 0.6; 0.0; 0.0 +1376594771; 1; 2599.999304; 65.86664903466666; 2.533333333333333; 2097152.0; 329950.13333333336; 161.73333333333332; 22.933333333333334; 0.0; 0.2 +1376595071; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 486536.8; 0.0; 1.2; 0.06666666666666667; 0.0 +1376595371; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 174760.8; 31.8; 3.6666666666666665; 0.0; 0.0 +1376595671; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 135614.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1376595971; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1376596271; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 121633.33333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1376596571; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 110448.26666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1376596871; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 97865.33333333333; 0.0; 1.2; 0.0; 0.0 +1376597171; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 92272.8; 0.0; 0.6; 0.0; 0.0 +1376597471; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 190138.66666666666; 0.0; 2.066666666666667; 0.06666666666666667; 0.5333333333333333 +1376597771; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 127226.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1376598071; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 89476.53333333334; 0.0; 1.0; 0.0; 0.0 +1376598371; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 81087.73333333334; 0.0; 0.6666666666666666; 0.0; 0.0 +1376598671; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 100661.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1376598971; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 106254.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1376599271; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 109050.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1376599571; 1; 2599.999304; 17.333328693333335; 0.6666666666666667; 2097152.0; 159381.6; 12.733333333333333; 13.0; 0.4; 0.13333333333333333 +1376599871; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 111846.66666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1376600171; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 118837.33333333333; 0.0; 0.8; 0.0; 0.0 +1376600471; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 127226.13333333333; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1376600771; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 109050.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1376601072; 1; 2599.999304; 17.333328693333335; 0.6666666666666667; 2097152.0; 156586.13333333333; 0.06666666666666667; 2.7333333333333334; 0.06666666666666667; 0.4666666666666667 +1376601372; 1; 2599.999304; 15.599995824; 0.6; 2097152.0; 132818.66666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1376601672; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 118837.33333333333; 0.0; 0.8; 0.0; 0.0 +1376601972; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 145401.86666666667; 0.0; 0.6666666666666666; 0.0; 0.0 +1376602272; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 97865.33333333333; 0.0; 1.0; 0.0; 0.0 +1376602572; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 103457.86666666667; 0.0; 0.8; 0.06666666666666667; 0.0 +1376602872; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 99263.46666666666; 0.0; 0.8; 0.06666666666666667; 0.0 +1376603172; 1; 2599.999304; 15.599995824; 0.6; 2097152.0; 85282.13333333333; 0.0; 0.6; 0.0; 0.0 +1376603472; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 107652.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1376603772; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 103457.86666666667; 0.0; 1.0; 0.0; 0.0 +1376604072; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 75495.2; 0.0; 0.6666666666666666; 0.0; 0.0 +1376604372; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 106254.13333333333; 0.0; 0.6666666666666666; 0.0; 0.0 +1376604672; 1; 2599.999304; 17.333328693333335; 0.6666666666666667; 2097152.0; 220897.6; 0.0; 2.2; 0.06666666666666667; 0.4666666666666667 +1376604972; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 128624.26666666666; 0.0; 7.266666666666667; 0.3333333333333333; 0.13333333333333333 +1376605272; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 100661.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1376605572; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 95069.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1376605872; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 97865.33333333333; 2.6666666666666665; 1.2666666666666666; 0.06666666666666667; 0.13333333333333333 +1376606172; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 127226.13333333333; 0.0; 1.2; 0.0; 0.0 +1376606472; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 125828.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1376606772; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 107652.26666666666; 0.0; 0.6; 0.06666666666666667; 0.0 +1376607072; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 99263.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1376607372; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 99263.46666666666; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1376607672; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 92272.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1376607972; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 110448.53333333334; 0.0; 1.2; 0.0; 0.0 +1376608272; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 145401.06666666668; 0.0; 2.2666666666666666; 0.13333333333333333; 0.4666666666666667 +1376608572; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 104856.0; 0.0; 0.6666666666666666; 0.0; 0.0 +1376608872; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 103457.86666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1376609172; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 111846.66666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1376609472; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 103457.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1376609772; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 62912.0; 0.0; 0.6666666666666666; 0.0; 0.0 +1376610072; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 82485.86666666667; 0.0; 0.8; 0.06666666666666667; 0.0 +1376610372; 1; 2599.999304; 15.599995824; 0.6; 2097152.0; 99263.46666666666; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1376610672; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 121633.6; 0.0; 1.0; 0.0; 0.0 +1376610972; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 135614.93333333332; 0.0; 0.6666666666666666; 0.0; 0.0 +1376611272; 1; 2599.999304; 17.333328693333335; 0.6666666666666667; 2097152.0; 109050.4; 0.0; 6.866666666666666; 0.2; 0.13333333333333333 +1376611572; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 118837.33333333333; 0.0; 0.6666666666666666; 0.0; 0.0 +1376611872; 1; 2599.999304; 19.066661562666667; 0.7333333333333333; 2097152.0; 201324.53333333333; 0.06666666666666667; 2.2666666666666666; 0.06666666666666667; 0.4666666666666667 +1376612172; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 114642.93333333333; 0.0; 1.2; 0.0; 0.0 +1376612472; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 116041.06666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1376612772; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 89476.53333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1376613072; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 92272.8; 0.6; 1.2; 0.0; 0.0 +1376613372; 1; 2599.999304; 19.066661562666667; 0.7333333333333333; 2097152.0; 123031.73333333334; 0.0; 0.8; 0.0; 0.0 +1376613672; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 117439.2; 0.0; 0.8; 0.0; 0.0 +1376613972; 1; 2599.999304; 17.333328693333335; 0.6666666666666667; 2097152.0; 125827.73333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1376614272; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 142605.06666666668; 0.0; 0.8666666666666667; 0.0; 0.0 +1376614572; 1; 2599.999304; 15.599995824; 0.6; 2097152.0; 90874.66666666667; 0.0; 0.6; 0.0; 0.0 +1376614872; 1; 2599.999304; 19.066661562666667; 0.7333333333333333; 2097152.0; 97865.33333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1376615172; 1; 2599.999304; 17.333328693333335; 0.6666666666666667; 2097152.0; 83884.0; 0.0; 0.6; 0.0; 0.0 +1376615472; 1; 2599.999304; 17.333328693333335; 0.6666666666666667; 2097152.0; 170565.86666666667; 0.0; 2.1333333333333333; 0.06666666666666667; 0.4666666666666667 +1376615772; 1; 2599.999304; 17.333328693333335; 0.6666666666666667; 2097152.0; 107652.26666666666; 0.0; 11.733333333333333; 0.06666666666666667; 0.0 +1376616072; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 111846.66666666667; 0.0; 0.8; 0.0; 0.0 +1376616372; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 95069.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1376616672; 1; 2599.999304; 17.333328693333335; 0.6666666666666667; 2097152.0; 104856.0; 0.0; 0.6666666666666666; 0.0; 0.0 +1376616973; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 106254.13333333333; 0.0; 0.6666666666666666; 0.0; 0.0 +1376617273; 1; 2599.999304; 15.599995824; 0.6; 2097152.0; 113244.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1376617573; 1; 2599.999304; 19.066661562666667; 0.7333333333333333; 2097152.0; 117439.2; 0.0; 1.1333333333333333; 0.0; 0.0 +1376617873; 1; 2599.999304; 19.066661562666667; 0.7333333333333333; 2097152.0; 124429.86666666667; 0.0; 6.8; 0.26666666666666666; 0.13333333333333333 +1376618173; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 102059.73333333334; 0.0; 0.6666666666666666; 0.13333333333333333; 0.0 +1376618473; 1; 2599.999304; 71.06664764266665; 2.733333333333333; 2097152.0; 571821.6; 166.6; 21.533333333333335; 0.06666666666666667; 0.4 +1376618773; 1; 2599.999304; 17.333328693333335; 0.6666666666666667; 2097152.0; 569025.0666666667; 0.0; 2.466666666666667; 0.0; 0.0 +1376619073; 1; 2599.999304; 24.266660170666665; 0.9333333333333332; 2097152.0; 293599.4666666667; 31.866666666666667; 5.466666666666667; 0.06666666666666667; 0.5333333333333333 +1376619373; 1; 2599.999304; 20.799994432; 0.8; 2097152.0; 197130.93333333332; 0.0; 2.2666666666666666; 0.0; 0.0 +1376619673; 1; 2599.999304; 15.599995824; 0.6; 2097152.0; 139808.8; 0.0; 1.5333333333333334; 0.0; 0.0 +1376619973; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 95069.06666666667; 0.0; 0.6666666666666666; 0.0; 0.0 +1376620273; 1; 2599.999304; 19.066661562666667; 0.7333333333333333; 2097152.0; 125828.0; 0.0; 4.333333333333333; 0.0; 0.0 +1376620573; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 110448.53333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1376620873; 1; 2599.999304; 17.333328693333335; 0.6666666666666667; 2097152.0; 93670.93333333333; 0.0; 0.6; 0.0; 0.0 +1376621173; 1; 2599.999304; 17.333328693333335; 0.6666666666666667; 2097152.0; 132818.4; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1376621473; 1; 2599.999304; 15.599995824; 0.6; 2097152.0; 109049.86666666667; 0.0; 0.6; 0.0; 0.0 +1376621773; 1; 2599.999304; 17.333328693333335; 0.6666666666666667; 2097152.0; 135614.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1376622073; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 113244.8; 0.0; 0.6; 0.0; 0.0 +1376622373; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 102059.73333333334; 0.0; 0.6666666666666666; 0.0; 0.0 +1376622673; 1; 2599.999304; 20.799994432; 0.8; 2097152.0; 146798.93333333332; 0.2; 2.3333333333333335; 0.06666666666666667; 0.4666666666666667 +1376622973; 1; 2599.999304; 17.333328693333335; 0.6666666666666667; 2097152.0; 123030.93333333333; 0.0; 0.8; 0.0; 0.0 +1376623273; 1; 2599.999304; 17.333328693333335; 0.6666666666666667; 2097152.0; 104856.0; 0.0; 0.6; 0.0; 0.0 +1376623573; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 104856.0; 0.0; 1.4666666666666666; 0.0; 0.0 +1376623873; 1; 2599.999304; 17.333328693333335; 0.6666666666666667; 2097152.0; 100661.6; 0.06666666666666667; 6.933333333333334; 0.2; 0.13333333333333333 +1376624173; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 113244.8; 0.0; 0.6; 0.0; 0.0 +1376624473; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 83884.0; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1376624773; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 103457.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1376625073; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 118837.33333333333; 0.0; 0.8; 0.0; 0.0 +1376625373; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 100661.6; 0.0; 0.6666666666666666; 0.0; 0.0 +1376625673; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 113244.8; 1.4666666666666666; 1.0; 0.06666666666666667; 0.06666666666666667 +1376625973; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 95069.06666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1376626273; 1; 2599.999304; 22.533327301333333; 0.8666666666666667; 2097152.0; 104856.0; 0.0; 2.2; 0.06666666666666667; 0.4666666666666667 +1376626573; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 123031.73333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1376626873; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 88078.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1376627173; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 90874.66666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1376627473; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 125828.0; 0.0; 0.6666666666666666; 0.0; 0.0 +1376627773; 1; 2599.999304; 15.599995824; 0.6; 2097152.0; 132818.66666666666; 0.0; 0.5333333333333333; 0.0; 0.0 +1376628073; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 103457.86666666667; 0.0; 0.8; 0.0; 0.0 +1376628373; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 106254.13333333333; 0.0; 0.8; 0.0; 0.0 +1376628673; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 142605.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1376628973; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 150993.6; 0.0; 0.6; 0.0; 0.0 +1376629273; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 132818.66666666666; 0.0; 0.5333333333333333; 0.0; 0.0 +1376629573; 1; 2599.999304; 15.599995824; 0.6; 2097152.0; 103457.86666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1376629873; 1; 2599.999304; 22.533327301333333; 0.8666666666666667; 2097152.0; 139808.8; 0.0; 8.2; 0.26666666666666666; 0.6 +1376630173; 1; 2599.999304; 15.599995824; 0.6; 2097152.0; 152391.46666666667; 0.0; 0.6666666666666666; 0.0; 0.0 +1376630474; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 103457.86666666667; 0.0; 0.5333333333333333; 0.0; 0.0 +1376630774; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 120235.46666666666; 0.0; 0.8; 0.0; 0.0 +1376631074; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 110448.53333333334; 0.0; 0.8; 0.0; 0.0 +1376631374; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 113244.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1376631674; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 125828.0; 0.0; 0.6666666666666666; 0.0; 0.0 +1376631974; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 109050.4; 0.0; 0.6666666666666666; 0.0; 0.0 +1376632274; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 99263.46666666666; 0.0; 0.6; 0.0; 0.0 +1376632574; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 121633.6; 0.0; 0.6; 0.06666666666666667; 0.0 +1376632874; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1376633174; 1; 2599.999304; 0.0; 0.0; 2097152.0; 85282.13333333333; 0.0; 0.6; 0.06666666666666667; 0.0 +1376633474; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 173362.93333333332; 0.0; 2.2666666666666666; 0.06666666666666667; 0.4666666666666667 +1376633774; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 123031.73333333334; 0.0; 0.6666666666666666; 0.0; 0.0 +1376634074; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 117439.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1376634374; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 109050.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1376634674; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 125827.2; 0.0; 1.0666666666666667; 0.06666666666666667; 0.06666666666666667 +1376634974; 1; 2599.999304; 19.066661562666667; 0.7333333333333333; 2097152.0; 96467.2; 0.0; 2.066666666666667; 0.0; 0.0 +1376635274; 1; 2599.999304; 15.599995824; 0.6; 2097152.0; 106254.13333333333; 0.0; 1.6666666666666667; 0.0; 0.0 +1376635574; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 121633.6; 0.0; 1.1333333333333333; 0.0; 0.0 +1376635874; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 110448.53333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1376636174; 1; 2599.999304; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1376636474; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 83884.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1376636774; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 113244.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1376637074; 1; 2599.999304; 17.333328693333335; 0.6666666666666667; 2097152.0; 157983.46666666667; 0.6; 8.666666666666666; 0.3333333333333333; 0.6666666666666666 +1376637374; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 113244.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1376637674; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 104856.0; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1376637974; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 113244.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1376638274; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 111846.66666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1376638574; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 104856.0; 0.0; 0.8; 0.0; 0.0 +1376638874; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 88078.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1376639174; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 83884.0; 0.0; 1.4666666666666666; 0.0; 0.0 +1376639474; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 96467.2; 0.0; 0.8; 0.0; 0.0 +1376639774; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 117439.2; 0.0; 1.1333333333333333; 0.0; 0.0 +1376640074; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 107652.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1376640374; 1; 2599.999304; 0.0; 0.0; 2097152.0; 130022.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1376640674; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 130022.4; 0.0; 2.3333333333333335; 0.06666666666666667; 0.5333333333333333 +1376640974; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 132818.66666666666; 0.0; 1.0; 0.0; 0.0 +1376641275; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 146799.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1376641575; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 96467.2; 0.0; 0.8; 0.0; 0.0 +1376641875; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 93670.93333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1376642175; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 109050.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1376642475; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 117439.2; 0.0; 1.2; 0.0; 0.0 +1376642775; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 104856.0; 0.0; 1.0; 0.0; 0.0 +1376643075; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 113244.8; 0.0; 0.8; 0.0; 0.0 +1376643375; 1; 2599.999304; 0.0; 0.0; 2097152.0; 125828.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1376643675; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 83884.0; 0.0; 7.0; 0.26666666666666666; 0.13333333333333333 +1376643975; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 134216.8; 0.0; 1.0666666666666667; 0.0; 0.0 +1376644275; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 155187.2; 0.0; 2.2666666666666666; 0.06666666666666667; 0.4666666666666667 +1376644575; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 109050.4; 0.0; 1.0; 0.0; 0.0 +1376644875; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 130022.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1376645175; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 109050.4; 0.0; 1.1333333333333333; 0.0; 0.0 +1376645475; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 2.2666666666666666; 0.0 +1376645775; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1376646075; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 100661.6; 0.0; 1.0; 0.0; 0.0 +1376646375; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 89476.53333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1376646675; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 103457.86666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1376646975; 1; 2599.999304; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.6; 0.0; 0.0 +1376647275; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 83884.0; 0.0; 0.6666666666666666; 0.0; 0.0 +1376647575; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 130022.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1376647875; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 209713.6; 0.0; 2.2; 0.0; 0.4666666666666667 +1376648175; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 130021.6; 0.0; 0.7333333333333333; 0.0; 0.0 +1376648475; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 92272.8; 0.0; 0.7333333333333333; 0.0; 0.0 +1376648775; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 83884.0; 0.0; 0.8; 0.0; 0.0 +1376649075; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 113244.8; 0.0; 7.266666666666667; 0.2; 0.13333333333333333 +1376649375; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 134216.0; 0.0; 1.0; 0.0; 0.0 +1376649675; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 109050.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1376649975; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 155188.0; 0.0; 0.8; 0.0; 0.0 +1376650276; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 138410.4; 0.0; 0.8; 0.0; 0.0 +1376650576; 1; 2599.999304; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.7333333333333333; 0.0; 0.0 +1376650876; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 74097.06666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1376651176; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1376651476; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 170566.66666666666; 0.0; 2.2; 0.0; 0.4666666666666667 +1376651776; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 113244.8; 0.0; 0.6666666666666666; 0.0; 0.0 +1376652076; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 104856.0; 0.0; 1.0666666666666667; 2.933333333333333; 0.0 +1376652376; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 90874.66666666667; 0.0; 0.8; 0.0; 0.0 +1376652676; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 88078.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1376652976; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 120235.46666666666; 0.0; 0.6; 4.933333333333334; 0.0 +1376653276; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 96467.2; 0.0; 0.7333333333333333; 0.0; 0.0 +1376653576; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 121632.8; 0.0; 1.2; 0.0; 0.0 +1376653876; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1376654176; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 86680.26666666666; 0.0; 0.9333333333333333; 0.6666666666666666; 0.0 +1376654476; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 93670.93333333333; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1376654776; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 97865.33333333333; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1376655076; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 157983.46666666667; 0.06666666666666667; 2.3333333333333335; 0.06666666666666667; 0.4666666666666667 +1376655376; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 128624.0; 0.0; 0.8; 0.06666666666666667; 0.0 +1376655676; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 62912.0; 0.0; 0.7333333333333333; 0.0; 0.0 +1376655976; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 86680.26666666666; 0.0; 7.066666666666666; 0.26666666666666666; 0.13333333333333333 +1376656276; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 92272.8; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1376656576; 1; 2599.999304; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1376656876; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 100661.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1376657176; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 1.3333333333333333; 0.0 +1376657476; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 100661.6; 0.0; 0.8; 1.5333333333333334; 0.0 +1376657776; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 113244.8; 0.0; 1.0666666666666667; 0.3333333333333333; 0.0 +1376658076; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 118837.33333333333; 0.0; 0.8666666666666667; 0.6; 0.0 +1376658376; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 118837.33333333333; 0.0; 0.8666666666666667; 0.7333333333333333; 0.0 +1376658676; 1; 2599.999304; 19.066661562666667; 0.7333333333333333; 2097152.0; 188741.6; 0.06666666666666667; 2.4; 0.4; 0.5333333333333333 +1376658976; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 139809.06666666668; 0.0; 1.0666666666666667; 0.13333333333333333; 0.0 +1376659276; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 125828.0; 0.0; 11.533333333333333; 0.0; 0.0 +1376659576; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 96467.2; 0.0; 0.7333333333333333; 0.0; 0.0 +1376659876; 1; 2599.999304; 0.0; 0.0; 2097152.0; 106254.13333333333; 0.0; 0.7333333333333333; 0.13333333333333333; 0.0 +1376660176; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 102059.73333333334; 0.0; 0.6666666666666666; 0.13333333333333333; 0.0 +1376660476; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 99263.46666666666; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1376660776; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 106254.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1376661076; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 142604.53333333333; 0.0; 0.6666666666666666; 0.0; 0.0 +1376661376; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 117439.2; 0.0; 0.6666666666666666; 0.13333333333333333; 0.0 +1376661676; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 103457.86666666667; 0.0; 0.6; 0.13333333333333333; 0.0 +1376661976; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 106254.13333333333; 0.0; 1.0; 0.06666666666666667; 0.0 +1376662276; 1; 2599.999304; 24.266660170666665; 0.9333333333333332; 2097152.0; 198528.53333333333; 12.733333333333333; 14.733333333333333; 0.4; 0.6 +1376662576; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 169169.06666666668; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1376662876; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 82485.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1376663176; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 57319.46666666667; 0.0; 1.2; 0.06666666666666667; 0.0 +1376663476; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 62912.0; 0.0; 0.8; 0.2; 0.13333333333333333 +1376663777; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 75495.2; 0.0; 1.1333333333333333; 0.0; 0.0 +1376664077; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 117439.2; 0.0; 0.7333333333333333; 0.0; 0.0 +1376664377; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 121632.8; 0.0; 0.6666666666666666; 0.0; 0.0 +1376664677; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 116041.06666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1376664977; 1; 2599.999304; 0.0; 0.0; 2097152.0; 132818.66666666666; 0.0; 0.6; 0.06666666666666667; 0.0 +1376665277; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 76893.33333333333; 0.0; 0.6; 0.13333333333333333; 0.0 +1376665577; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 96467.2; 0.0; 0.6; 0.06666666666666667; 0.0 +1376665877; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 171964.0; 0.0; 2.466666666666667; 0.06666666666666667; 0.4666666666666667 +1376666177; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 118837.33333333333; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1376666477; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 72698.93333333333; 0.0; 0.8; 0.0; 0.0 +1376666777; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 75495.2; 0.0; 0.7333333333333333; 0.0; 0.0 +1376667077; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 83884.0; 0.0; 0.6666666666666666; 0.06666666666666667; 0.0 +1376667377; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 88078.4; 0.0; 1.1333333333333333; 0.13333333333333333; 0.0 +1376667677; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 78291.46666666666; 0.0; 1.0; 0.0; 0.0 +1376667977; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 125827.2; 0.0; 1.7333333333333334; 0.0; 0.0 +1376668277; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 83884.0; 0.0; 1.0; 0.0; 0.0 +1376668577; 1; 2599.999304; 0.0; 0.0; 2097152.0; 86680.26666666666; 0.0; 0.5333333333333333; 0.06666666666666667; 0.0 +1376668877; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 79689.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1376669177; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 118837.33333333333; 0.0; 7.2; 0.2; 0.13333333333333333 +1376669477; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 169168.53333333333; 0.0; 2.1333333333333333; 0.06666666666666667; 0.4666666666666667 +1376669777; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 131420.26666666666; 0.0; 0.6666666666666666; 0.0; 0.0 +1376670077; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 83884.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1376670377; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 90874.66666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1376670677; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 85282.13333333333; 0.0; 0.6; 0.0; 0.0 +1376670977; 1; 2599.999304; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.8; 0.0; 0.0 +1376671277; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 82485.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1376671577; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 106254.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1376671878; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 110448.53333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1376672178; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 110448.53333333334; 0.0; 0.6; 0.0; 0.0 +1376672478; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 146799.2; 0.0; 0.6; 0.0; 0.0 +1376672778; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 106254.13333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1376673078; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 195732.26666666666; 0.06666666666666667; 2.1333333333333333; 0.06666666666666667; 0.4666666666666667 +1376673378; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 104856.0; 0.0; 0.8; 0.0; 0.0 +1376673678; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 95069.06666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1376673978; 1; 2599.999304; 0.0; 0.0; 2097152.0; 67106.4; 0.0; 0.5333333333333333; 0.06666666666666667; 0.0 +1376674278; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 76893.33333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1376674578; 1; 2599.999304; 25.99999304; 1.0; 2097152.0; 155188.26666666666; 161.06666666666666; 27.466666666666665; 0.4; 0.5333333333333333 +1376674878; 1; 2599.999304; 48.53332034133333; 1.8666666666666665; 2097152.0; 722816.5333333333; 0.0; 2.6; 0.0; 0.0 +1376675178; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 324357.86666666664; 31.8; 3.0; 0.06666666666666667; 0.0 +1376675478; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 174760.8; 0.0; 2.4; 0.0; 0.0 +1376675778; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 187343.73333333334; 0.0; 1.7333333333333334; 0.0; 0.0 +1376676078; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 130022.13333333333; 0.0; 0.5333333333333333; 0.06666666666666667; 0.0 +1376676378; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 85282.13333333333; 0.0; 0.5333333333333333; 0.0; 0.0 +1376676678; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 156585.33333333334; 0.0; 2.0; 0.13333333333333333; 0.4666666666666667 +1376676978; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 131420.53333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1376677278; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 117439.2; 0.0; 0.6666666666666666; 0.0; 0.0 +1376677578; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 92272.8; 0.0; 0.6; 0.0; 0.0 +1376677878; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 83884.0; 0.0; 0.6; 0.13333333333333333; 0.0 +1376678178; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 137013.06666666668; 0.0; 0.8; 0.06666666666666667; 0.0 +1376678478; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 113244.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1376678778; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 106254.13333333333; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1376679078; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 142605.6; 0.0; 0.6; 0.0; 0.0 +1376679378; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 100661.6; 0.0; 0.8; 0.06666666666666667; 0.0 +1376679679; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 96467.2; 0.0; 0.5333333333333333; 0.13333333333333333; 0.0 +1376679979; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 90874.66666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1376680279; 1; 2599.999304; 15.599995824; 0.6; 2097152.0; 183149.86666666667; 0.0; 1.8666666666666667; 0.06666666666666667; 0.4666666666666667 +1376680579; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 142605.6; 0.0; 7.133333333333334; 0.26666666666666666; 0.13333333333333333 +1376680879; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 164973.33333333334; 0.0; 0.6; 0.0; 0.0 +1376681179; 1; 2599.999304; 43.333321733333335; 1.6666666666666665; 2097152.0; 267036.26666666666; 161.0; 14.466666666666667; 0.0; 0.2 +1376681479; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 459973.6; 0.0; 1.2666666666666666; 0.0; 0.0 +1376681779; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 187344.0; 31.8; 3.466666666666667; 0.0; 0.0 +1376682079; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 123031.73333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1376682379; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 96467.2; 0.0; 1.1333333333333333; 0.0; 0.0 +1376682679; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 116041.06666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1376682979; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 144003.73333333334; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1376683279; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 121633.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1376683579; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 121632.8; 0.0; 0.8666666666666667; 0.13333333333333333; 0.0 +1376683879; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 150994.4; 0.06666666666666667; 2.466666666666667; 0.06666666666666667; 0.4666666666666667 +1376684179; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 116041.06666666667; 0.06666666666666667; 1.6666666666666667; 0.0; 0.0 +1376684479; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 111846.66666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1376684779; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 113244.8; 0.0; 0.8; 0.0; 0.0 +1376685079; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 93670.93333333333; 0.0; 0.8; 0.06666666666666667; 0.0 +1376685379; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 149595.73333333334; 0.0; 1.0; 0.0; 0.0 +1376685679; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 102059.46666666666; 0.0; 0.6666666666666666; 0.0; 0.0 +1376685979; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 81087.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1376686279; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 97865.33333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1376686579; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 111846.66666666667; 0.0; 0.6; 0.0; 0.0 +1376686879; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 82485.86666666667; 0.0; 1.0; 0.0; 0.0 +1376687179; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 145401.33333333334; 0.0; 6.666666666666667; 0.2; 0.13333333333333333 +1376687479; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 192935.73333333334; 0.06666666666666667; 2.533333333333333; 0.06666666666666667; 0.4666666666666667 +1376687779; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 102059.73333333334; 0.0; 0.6666666666666666; 0.0; 0.0 +1376688079; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 97865.33333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1376688379; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 89476.53333333334; 0.0; 0.8; 0.0; 0.0 +1376688679; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 117439.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1376688979; 1; 2599.999304; 0.0; 0.0; 2097152.0; 124429.86666666667; 0.0; 0.6666666666666666; 0.0; 0.0 +1376689279; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 120235.46666666666; 0.0; 0.8; 0.0; 0.0 +1376689579; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 65708.26666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1376689879; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 69902.66666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1376690179; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 62912.0; 0.0; 1.4666666666666666; 0.0; 0.0 +1376690479; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 100661.6; 0.0; 0.6666666666666666; 0.0; 0.0 +1376690779; 1; 2599.999304; 0.0; 0.0; 2097152.0; 78291.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1376691079; 1; 2599.999304; 15.599995824; 0.6; 2097152.0; 187342.93333333332; 0.06666666666666667; 2.6; 0.06666666666666667; 0.5333333333333333 +1376691379; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 137012.8; 0.0; 0.8; 0.0; 0.0 +1376691679; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 82485.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1376691979; 1; 2599.999304; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 0.6666666666666666; 0.0; 0.0 +1376692279; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 111846.66666666667; 0.0; 1.0666666666666667; 0.13333333333333333; 0.13333333333333333 +1376692579; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 156586.4; 0.0; 1.2666666666666666; 0.0; 0.0 +1376692879; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 131420.53333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1376693179; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 137012.26666666666; 0.06666666666666667; 7.466666666666667; 0.2; 0.13333333333333333 +1376693480; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 107652.26666666666; 0.0; 0.6666666666666666; 0.0; 0.0 +1376693780; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 117439.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1376694080; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 102059.73333333334; 0.0; 1.0; 0.2; 0.06666666666666667 +1376694380; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 90874.66666666667; 0.0; 0.8; 0.0; 0.0 +1376694680; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 123031.73333333334; 0.06666666666666667; 2.2666666666666666; 0.06666666666666667; 0.4666666666666667 +1376694980; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 96467.2; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1376695280; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 90874.66666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1376695580; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 93670.93333333333; 0.0; 0.8; 0.0; 0.0 +1376695880; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 100661.6; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1376696180; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 114642.93333333333; 0.0; 0.6666666666666666; 0.0; 0.0 +1376696480; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 174760.8; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1376696780; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 146800.0; 0.0; 1.0; 0.0; 0.0 +1376697080; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 116041.06666666667; 0.0; 0.6; 0.0; 0.0 +1376697380; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 113244.8; 0.0; 0.8; 0.06666666666666667; 0.0 +1376697680; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 114642.93333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1376697980; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 81087.73333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1376698280; 1; 2599.999304; 17.333328693333335; 0.6666666666666667; 2097152.0; 139808.53333333333; 0.06666666666666667; 2.0; 0.06666666666666667; 0.5333333333333333 +1376698580; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 145401.06666666668; 0.0; 0.5333333333333333; 0.0; 0.0 +1376698880; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 120235.46666666666; 0.0; 0.6; 0.0; 0.0 +1376699180; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 124429.06666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1376699480; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 124429.86666666667; 0.0; 1.0; 0.0; 0.0 +1376699780; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 138411.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1376700080; 1; 2599.999304; 17.333328693333335; 0.6666666666666667; 2097152.0; 99263.46666666666; 0.06666666666666667; 6.8; 0.2; 0.13333333333333333 +1376700380; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1376700680; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 82485.86666666667; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1376700980; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 116041.06666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1376701280; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 86680.26666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1376701581; 1; 2599.999304; 15.599995824; 0.6; 2097152.0; 106254.13333333333; 0.0; 0.8; 0.0; 0.0 +1376701881; 1; 2599.999304; 17.333328693333335; 0.6666666666666667; 2097152.0; 198529.06666666668; 0.0; 2.2666666666666666; 0.06666666666666667; 0.4666666666666667 +1376702181; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 123031.73333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1376702481; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 79689.6; 0.0; 0.6666666666666666; 0.0; 0.0 +1376702781; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 110448.53333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1376703081; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 171964.53333333333; 0.0; 11.666666666666666; 0.0; 0.0 +1376703381; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 135614.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1376703681; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 113244.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1376703981; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 103457.86666666667; 0.0; 0.6666666666666666; 0.0; 0.0 +1376704281; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 113244.8; 0.0; 0.7333333333333333; 0.0; 0.0 +1376704581; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 93670.93333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1376704881; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 123031.73333333334; 0.0; 1.2; 0.0; 0.0 +1376705181; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 106254.13333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1376705481; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 146799.2; 0.0; 2.066666666666667; 0.06666666666666667; 0.4666666666666667 +1376705781; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 145401.06666666668; 0.0; 6.733333333333333; 0.26666666666666666; 0.13333333333333333 +1376706081; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 114642.93333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1376706381; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 107652.26666666666; 0.0; 0.6666666666666666; 0.0; 0.0 +1376706681; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 83884.0; 0.0; 0.6; 0.0; 0.0 +1376706981; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 92272.8; 0.0; 0.6; 0.0; 0.0 +1376707281; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 69902.66666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1376707581; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 106254.13333333333; 0.0; 0.9333333333333333; 0.4; 0.0 +1376707881; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 124429.86666666667; 0.0; 0.6666666666666666; 0.0; 0.0 +1376708181; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 118837.33333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1376708481; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 107652.26666666666; 0.0; 0.6666666666666666; 0.0; 0.0 +1376708781; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 110448.53333333334; 0.0; 1.0666666666666667; 0.0; 0.0 +1376709081; 1; 2599.999304; 19.066661562666667; 0.7333333333333333; 2097152.0; 159383.2; 0.06666666666666667; 2.2666666666666666; 1.0666666666666667; 0.5333333333333333 +1376709381; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 125827.2; 0.0; 0.6; 0.0; 0.0 +1376709681; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 72698.93333333333; 0.0; 0.6666666666666666; 0.0; 0.0 +1376709981; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 83884.0; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1376710281; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 97865.33333333333; 0.0; 0.8666666666666667; 0.13333333333333333; 0.0 +1376710581; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 107652.26666666666; 0.0; 0.5333333333333333; 0.06666666666666667; 0.0 +1376710881; 1; 2599.999304; 0.0; 0.0; 2097152.0; 89476.53333333334; 0.0; 0.8; 0.0; 0.0 +1376711181; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 76893.33333333333; 0.0; 0.9333333333333333; 0.13333333333333333; 0.0 +1376711481; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 95069.06666666667; 0.0; 0.6666666666666666; 0.0; 0.0 +1376711781; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 95069.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1376712082; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 86680.26666666666; 0.0; 6.933333333333334; 0.26666666666666666; 0.2 +1376712382; 1; 2599.999304; 0.0; 0.0; 2097152.0; 106254.13333333333; 0.0; 1.6; 0.06666666666666667; 0.0 +1376712682; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 134216.0; 0.0; 2.0; 0.06666666666666667; 0.4666666666666667 +1376712982; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 113244.8; 0.0; 0.8; 0.0; 0.0 +1376713282; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 121633.6; 0.0; 0.7333333333333333; 0.0; 0.0 +1376713582; 1; 2599.999304; 0.0; 0.0; 2097152.0; 86680.26666666666; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1376713882; 1; 2599.999304; 0.0; 0.0; 2097152.0; 85282.13333333333; 0.0; 0.6; 0.0; 0.0 +1376714182; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 69902.66666666667; 0.0; 0.6666666666666666; 0.06666666666666667; 0.0 +1376714482; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 85282.13333333333; 0.0; 1.0; 0.0; 0.0 +1376714782; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 82485.86666666667; 0.0; 0.6666666666666666; 0.0; 0.0 +1376715082; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 106254.13333333333; 0.0; 0.6; 0.0; 0.0 +1376715382; 1; 2599.999304; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.5333333333333333; 0.06666666666666667; 0.0 +1376715682; 1; 2599.999304; 0.0; 0.0; 2097152.0; 68504.53333333334; 0.0; 0.8; 0.0; 0.0 +1376715982; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 75495.2; 0.0; 1.0; 0.0; 0.0 +1376716282; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 150993.06666666668; 0.06666666666666667; 2.2666666666666666; 0.06666666666666667; 0.4666666666666667 +1376716582; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 131420.53333333333; 0.0; 0.5333333333333333; 0.0; 0.0 +1376716882; 1; 2599.999304; 0.0; 0.0; 2097152.0; 95069.06666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1376717182; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 89476.53333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1376717482; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 82485.86666666667; 0.0; 0.6; 0.0; 0.0 +1376717782; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 106254.13333333333; 0.0; 0.5333333333333333; 0.0; 0.0 +1376718082; 1; 2599.999304; 17.333328693333335; 0.6666666666666667; 2097152.0; 138410.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1376718382; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 190138.93333333332; 0.0; 6.733333333333333; 0.26666666666666666; 0.13333333333333333 +1376718682; 1; 2599.999304; 17.333328693333335; 0.6666666666666667; 2097152.0; 121633.6; 0.0; 1.0; 0.06666666666666667; 0.0 +1376718982; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 86680.26666666666; 0.0; 0.5333333333333333; 0.0; 0.0 +1376719282; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 117437.6; 0.0; 0.5333333333333333; 0.0; 0.0 +1376719582; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 104856.0; 0.0; 0.8; 0.0; 0.0 +1376719882; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 187343.2; 0.2; 2.466666666666667; 0.0; 0.5333333333333333 +1376720182; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 117439.2; 0.0; 0.6; 0.0; 0.0 +1376720482; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 118837.33333333333; 0.0; 0.6666666666666666; 0.0; 0.0 +1376720782; 1; 2599.999304; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1376721082; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 96467.2; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1376721382; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 123031.73333333334; 0.06666666666666667; 2.2666666666666666; 0.26666666666666666; 0.13333333333333333 +1376721682; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 142604.8; 0.0; 1.4666666666666666; 0.13333333333333333; 0.0 +1376721982; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 113244.8; 0.0; 0.6; 0.0; 0.0 +1376722282; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 127225.33333333333; 0.0; 0.6; 0.0; 0.0 +1376722582; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 92272.8; 0.0; 0.8; 3.7333333333333334; 0.0 +1376722882; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 83884.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1376723182; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 85282.13333333333; 0.0; 0.6666666666666666; 0.0; 0.0 +1376723482; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 173362.13333333333; 0.0; 2.2; 0.06666666666666667; 0.4666666666666667 +1376723782; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 134216.8; 12.733333333333333; 17.0; 0.3333333333333333; 0.2 +1376724082; 1; 2599.999304; 60.66665042666666; 2.3333333333333335; 2097152.0; 279618.6666666667; 161.0; 22.133333333333333; 0.06666666666666667; 0.4 +1376724382; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 763362.4; 0.0; 2.7333333333333334; 0.06666666666666667; 0.0 +1376724682; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 348124.8; 31.8; 3.533333333333333; 0.0; 0.0 +1376724982; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 188741.6; 0.0; 2.3333333333333335; 0.0; 0.0 +1376725282; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 134216.8; 0.0; 1.8; 0.0; 0.0 +1376725582; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 130022.4; 0.0; 1.7333333333333334; 0.0; 0.0 +1376725882; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 109050.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1376726182; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 121633.6; 0.0; 0.9333333333333333; 0.13333333333333333; 0.0 +1376726482; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 120235.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1376726782; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 130021.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1376727082; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 181750.93333333332; 0.0; 2.2666666666666666; 0.06666666666666667; 0.5333333333333333 +1376727382; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 113244.8; 0.0; 1.6666666666666667; 0.06666666666666667; 0.0 +1376727682; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 141207.46666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1376727982; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 121633.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1376728282; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 96467.2; 0.0; 1.1333333333333333; 0.2; 0.0 +1376728583; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 83884.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1376728883; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1376729183; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 100661.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1376729483; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 109050.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1376729783; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 125828.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1376730083; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 93670.93333333333; 0.0; 7.0; 0.3333333333333333; 0.13333333333333333 +1376730383; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 130021.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1376730683; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 166372.0; 0.06666666666666667; 2.4; 0.13333333333333333; 0.4666666666666667 +1376730983; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 117439.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1376731283; 1; 2599.999304; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1376731583; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 100661.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1376731883; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1376732183; 1; 2599.999304; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1376732483; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 121633.6; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1376732783; 1; 2599.999304; 0.0; 0.0; 2097152.0; 100660.8; 0.0; 1.0; 0.0; 0.0 +1376733083; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 88078.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1376733383; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1376733683; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 89476.53333333334; 0.0; 1.1333333333333333; 0.0; 0.0 +1376733983; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 114642.93333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1376734283; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 184547.2; 0.06666666666666667; 2.2666666666666666; 0.06666666666666667; 0.4666666666666667 +1376734583; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 92272.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1376734883; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 64310.13333333333; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1376735184; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 114642.93333333333; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1376735484; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 130022.4; 0.0; 0.8666666666666667; 7.866666666666666; 0.0 +1376735784; 1; 2599.999304; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1376736084; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 144002.93333333332; 0.0; 7.533333333333333; 0.2; 0.13333333333333333 +1376736384; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 155188.0; 0.0; 0.8; 0.13333333333333333; 0.0 +1376736684; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 88078.4; 0.0; 1.2; 0.06666666666666667; 0.0 +1376736984; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 72698.93333333333; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1376737284; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 93670.93333333333; 0.0; 0.7333333333333333; 0.2; 0.0 +1376737584; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 92272.8; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1376737884; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 160779.46666666667; 0.0; 2.466666666666667; 0.2; 0.5333333333333333 +1376738184; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 114642.93333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1376738484; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 130022.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1376738784; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 106254.13333333333; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1376739084; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 71300.8; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1376739384; 1; 2599.999304; 0.0; 0.0; 2097152.0; 75495.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1376739684; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 88078.4; 0.0; 0.6666666666666666; 0.0; 0.0 +1376739984; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 96467.2; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1376740284; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1376740584; 1; 2599.999304; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 1.0; 0.0; 0.0 +1376740884; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 104856.0; 0.0; 0.6666666666666666; 0.0; 0.0 +1376741184; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 114642.93333333333; 0.0; 0.6; 0.13333333333333333; 0.0 +1376741484; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 171964.8; 0.0; 2.2; 0.13333333333333333; 0.4666666666666667 +1376741784; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 110448.53333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1376742084; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 97865.33333333333; 0.0; 0.8; 0.0; 0.0 +1376742384; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 88078.4; 0.0; 6.866666666666666; 0.3333333333333333; 0.2 +1376742684; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 88078.4; 0.0; 0.8; 0.0; 0.0 +1376742984; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 92272.8; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1376743284; 1; 2599.999304; 0.0; 0.0; 2097152.0; 86680.26666666666; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1376743584; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 103457.86666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1376743885; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 130022.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1376744185; 1; 2599.999304; 0.0; 0.0; 2097152.0; 79689.6; 0.0; 0.7333333333333333; 0.0; 0.0 +1376744485; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 83884.0; 0.0; 0.7333333333333333; 0.13333333333333333; 0.0 +1376744785; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 103457.86666666667; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1376745085; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 139808.53333333333; 0.06666666666666667; 2.3333333333333335; 0.2; 0.4666666666666667 +1376745385; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 104856.0; 0.0; 0.7333333333333333; 0.0; 0.0 +1376745685; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 90874.66666666667; 0.0; 0.6; 0.0; 0.0 +1376745985; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 81087.73333333334; 0.0; 0.6666666666666666; 0.0; 0.0 +1376746285; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 81087.73333333334; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1376746585; 1; 2599.999304; 19.066661562666667; 0.7333333333333333; 2097152.0; 75495.2; 0.0; 11.6; 0.13333333333333333; 0.0 +1376746885; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 78291.46666666666; 0.0; 0.8; 0.0; 0.0 +1376747185; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 100661.6; 0.0; 0.8; 0.0; 0.0 +1376747485; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 110448.53333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1376747785; 1; 2599.999304; 0.0; 0.0; 2097152.0; 74097.06666666667; 0.0; 1.0; 0.06666666666666667; 0.0 +1376748085; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1376748385; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 116041.06666666667; 0.0; 7.0; 0.2; 0.13333333333333333 +1376748685; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 167769.86666666667; 0.0; 2.066666666666667; 0.06666666666666667; 0.4666666666666667 +1376748985; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 138411.2; 0.06666666666666667; 1.1333333333333333; 0.0; 0.0 +1376749285; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 100661.6; 0.0; 0.8; 0.0; 0.0 +1376749585; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 117439.2; 0.0; 0.6; 0.0; 0.0 +1376749885; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 123031.73333333334; 0.06666666666666667; 1.0666666666666667; 0.06666666666666667; 0.13333333333333333 +1376750185; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 128624.26666666666; 0.06666666666666667; 1.2666666666666666; 0.06666666666666667; 0.13333333333333333 +1376750485; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 103457.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1376750785; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 110448.53333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1376751085; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 102059.73333333334; 0.0; 0.6; 0.06666666666666667; 0.0 +1376751385; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 100661.6; 0.0; 0.6; 0.0; 0.0 +1376751685; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 117439.2; 0.0; 0.7333333333333333; 0.0; 0.0 +1376751985; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 97865.33333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1376752285; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 141207.46666666667; 0.0; 2.1333333333333333; 0.06666666666666667; 0.5333333333333333 +1376752586; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 93670.93333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1376752886; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 92272.8; 0.0; 0.6; 0.0; 0.0 +1376753186; 1; 2599.999304; 0.0; 0.0; 2097152.0; 120235.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1376753485; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 130022.4; 0.0; 0.6666666666666666; 0.0; 0.0 +1376753785; 1; 2599.999304; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 0.6666666666666666; 0.0; 0.0 +1376754085; 1; 2599.999304; 0.0; 0.0; 2097152.0; 86680.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1376754385; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 79689.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1376754685; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 81087.73333333334; 0.0; 7.2; 0.2; 0.13333333333333333 +1376754985; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 139808.53333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1376755285; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 95069.06666666667; 0.0; 0.6666666666666666; 0.0; 0.0 +1376755585; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 100661.6; 0.0; 0.7333333333333333; 0.0; 0.0 +1376755885; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 155188.26666666666; 0.06666666666666667; 2.466666666666667; 0.0; 0.4666666666666667 +1376756185; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 124429.6; 0.0; 0.8; 0.0; 0.0 +1376756485; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 125826.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1376756785; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 95068.8; 0.0; 0.5333333333333333; 0.0; 0.0 +1376757085; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 99263.46666666666; 0.0; 1.4; 0.0; 0.0 +1376757385; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 100661.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1376757685; 1; 2599.999304; 17.333328693333335; 0.6666666666666667; 2097152.0; 85282.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1376757985; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 121633.6; 0.0; 0.6666666666666666; 0.0; 0.0 +1376758285; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 155188.8; 0.0; 0.6; 0.0; 0.0 +1376758585; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 89476.53333333334; 0.0; 1.0; 0.0; 0.0 +1376758885; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 65708.26666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1376759186; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 81087.73333333334; 0.0; 0.6666666666666666; 0.0; 0.0 +1376759486; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 169168.8; 0.06666666666666667; 2.1333333333333333; 0.06666666666666667; 0.5333333333333333 +1376759786; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 125827.73333333334; 0.0; 1.0; 0.0; 0.0 +1376760086; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 137012.26666666666; 0.0; 1.1333333333333333; 0.0; 0.0 +1376760386; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 117438.66666666667; 0.0; 6.6; 0.2; 0.13333333333333333 +1376760686; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 117438.93333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1376760986; 1; 2599.999304; 20.799994432; 0.8; 2097152.0; 104856.0; 0.0; 0.6666666666666666; 0.0; 0.0 +1376761286; 1; 2599.999304; 17.333328693333335; 0.6666666666666667; 2097152.0; 125827.2; 0.0; 1.0; 0.0; 0.0 +1376761586; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 100661.6; 0.0; 1.2; 0.0; 0.0 +1376761886; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 97865.33333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1376762186; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 95069.06666666667; 0.0; 0.6; 0.0; 0.0 +1376762486; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 102059.73333333334; 0.0; 1.0666666666666667; 0.06666666666666667; 0.06666666666666667 +1376762786; 1; 2599.999304; 15.599995824; 0.6; 2097152.0; 135614.93333333332; 0.0; 0.8666666666666667; 0.0; 0.0 +1376763086; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 198528.8; 0.0; 2.2; 0.06666666666666667; 0.4666666666666667 +1376763386; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 125827.73333333334; 0.0; 0.8; 0.0; 0.0 +1376763686; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 100661.6; 0.0; 0.6666666666666666; 0.0; 0.0 +1376763986; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 88078.4; 0.0; 0.5333333333333333; 0.0; 0.0 +1376764286; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 100661.6; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1376764586; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 96467.2; 0.0; 0.7333333333333333; 0.0; 0.0 +1376764886; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 93670.93333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1376765186; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 93670.93333333333; 0.0; 0.6; 0.0; 0.0 +1376765486; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 74097.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1376765786; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 89476.53333333334; 0.0; 0.6666666666666666; 0.0; 0.0 +1376766086; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 104856.0; 0.0; 0.6666666666666666; 0.0; 0.0 +1376766386; 1; 2599.999304; 15.599995824; 0.6; 2097152.0; 139808.53333333333; 0.0; 7.0; 0.26666666666666666; 0.13333333333333333 +1376766686; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 185944.53333333333; 0.0; 2.066666666666667; 0.06666666666666667; 0.4666666666666667 +1376766986; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 116041.06666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1376767286; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 138411.2; 0.0; 0.5333333333333333; 0.0; 0.0 +1376767586; 1; 2599.999304; 45.06665460266667; 1.7333333333333334; 2097152.0; 166371.73333333334; 161.2; 14.133333333333333; 0.0; 0.2 +1376767886; 1; 2599.999304; 17.333328693333335; 0.6666666666666667; 2097152.0; 432010.93333333335; 0.0; 1.2; 0.0; 0.0 +1376768187; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 177556.53333333333; 31.8; 3.4; 0.0; 0.0 +1376768487; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 184546.93333333332; 0.0; 1.0666666666666667; 0.0; 0.0 +1376768787; 1; 2599.999304; 15.599995824; 0.6; 2097152.0; 130022.4; 0.0; 1.0666666666666667; 0.0; 0.0 +1376769087; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 130022.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1376769387; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 142605.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1376769687; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 125828.0; 0.0; 1.0; 0.0; 0.0 +1376769987; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 82485.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1376770287; 1; 2599.999304; 19.066661562666667; 0.7333333333333333; 2097152.0; 155188.0; 0.0; 2.1333333333333333; 0.06666666666666667; 0.5333333333333333 +1376770587; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 125828.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1376770887; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 106254.13333333333; 0.0; 1.0; 0.0; 0.0 +1376771187; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 100661.6; 0.0; 1.1333333333333333; 0.0; 0.0 +1376771487; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 92272.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1376771787; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 109050.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1376772087; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 106254.13333333333; 0.0; 1.0; 0.0; 0.0 +1376772387; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 86680.26666666666; 0.0; 1.0; 0.0; 0.0 +1376772687; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 82485.86666666667; 0.0; 1.0; 0.0; 0.0 +1376772987; 1; 2599.999304; 15.599995824; 0.6; 2097152.0; 117438.4; 0.0; 7.0; 0.26666666666666666; 0.13333333333333333 +1376773287; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 110448.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1376773587; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 75495.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1376773887; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 180352.8; 0.0; 2.1333333333333333; 0.06666666666666667; 0.5333333333333333 +1376774187; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 138411.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1376774487; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 90874.66666666667; 0.0; 0.8; 0.0; 0.0 +1376774787; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 95069.06666666667; 0.0; 0.8; 0.0; 0.0 +1376775087; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 102059.73333333334; 0.0; 0.8; 0.0; 0.0 +1376775387; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 117439.2; 0.0; 1.0; 0.0; 0.0 +1376775687; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 90874.66666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1376775988; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 90874.66666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1376776288; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1376776588; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 79689.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1376776891; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 79689.6; 0.0; 0.6666666666666666; 0.0; 0.0 +1376777191; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 109050.4; 0.0; 0.6; 0.0; 0.0 +1376777491; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 149594.4; 0.0; 2.2666666666666666; 0.06666666666666667; 0.4666666666666667 +1376777791; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 127225.33333333333; 0.0; 0.8; 0.0; 0.0 +1376778091; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 92272.8; 0.0; 1.0; 0.0; 0.0 +1376778391; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 97865.33333333333; 0.0; 0.6; 0.0; 0.0 +1376778691; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 123031.73333333334; 0.0; 1.3333333333333333; 0.06666666666666667; 0.13333333333333333 +1376778991; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 114642.93333333333; 0.06666666666666667; 0.9333333333333333; 0.0; 0.0 +1376779291; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 82485.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1376779591; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 95069.06666666667; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1376779891; 1; 2599.999304; 46.799987472000005; 1.8; 2097152.0; 153789.6; 161.46666666666667; 27.933333333333334; 0.3333333333333333; 0.6 +1376780191; 1; 2599.999304; 34.66665738666667; 1.3333333333333335; 2097152.0; 735398.9333333333; 0.0; 2.4; 0.0; 0.0 +1376780491; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 290802.93333333335; 31.8; 3.3333333333333335; 0.0; 0.0 +1376780791; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 178954.4; 0.0; 2.3333333333333335; 0.0; 0.0 +1376781091; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 184547.73333333334; 0.06666666666666667; 3.3333333333333335; 0.06666666666666667; 0.4666666666666667 +1376781391; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 132818.66666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1376781691; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 114642.93333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1376781991; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 82485.86666666667; 0.0; 0.8; 0.0; 0.0 +1376782291; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 121633.6; 0.06666666666666667; 1.0666666666666667; 0.0; 0.0 +1376782591; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 130021.33333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1376782891; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 99263.46666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1376783191; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 137012.53333333333; 0.0; 1.2; 0.0; 0.0 +1376783491; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 164973.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1376783791; 1; 2599.999304; 0.0; 0.0; 2097152.0; 68504.53333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1376784091; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 102059.73333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1376784392; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 117439.2; 0.0; 0.8; 0.0; 0.0 +1376784692; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 156586.13333333333; 0.0; 2.4; 0.06666666666666667; 0.4666666666666667 +1376784992; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 127226.13333333333; 0.0; 1.2; 0.0; 0.0 +1376785292; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 121632.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1376785592; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 83884.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1376785892; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 100661.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1376786191; 1; 2599.999304; 15.599995824; 0.6; 2097152.0; 134216.8; 12.733333333333333; 13.066666666666666; 0.3333333333333333; 0.13333333333333333 +1376786491; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 134216.8; 0.0; 1.3333333333333333; 0.0; 0.0 +1376786791; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 111846.66666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1376787091; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 120235.46666666666; 0.0; 0.6666666666666666; 0.0; 0.0 +1376787391; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 113244.8; 0.0; 0.8; 0.0; 0.0 +1376787691; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 138411.2; 0.0; 1.0; 0.0; 0.0 +1376787991; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 78291.46666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1376788291; 1; 2599.999304; 19.066661562666667; 0.7333333333333333; 2097152.0; 131419.73333333334; 0.0; 2.2; 0.06666666666666667; 0.5333333333333333 +1376788591; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 96467.2; 0.0; 0.8; 0.0; 0.0 +1376788891; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 89476.53333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1376789191; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 68504.53333333334; 0.0; 0.6666666666666666; 0.0; 0.0 +1376789491; 1; 2599.999304; 15.599995824; 0.6; 2097152.0; 99263.46666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1376789791; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 110448.53333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1376790092; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 88078.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1376790392; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 127225.6; 0.0; 12.0; 0.0; 0.0 +1376790692; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 130022.13333333333; 0.0; 0.5333333333333333; 0.0; 0.0 +1376790992; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 132818.66666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1376791292; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 100661.6; 0.0; 0.8; 0.0; 0.0 +1376791592; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 102059.73333333334; 0.0; 0.6; 0.0; 0.0 +1376791892; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 127226.13333333333; 0.0; 2.8; 0.06666666666666667; 0.4666666666666667 +1376792192; 1; 2599.999304; 19.066661562666667; 0.7333333333333333; 2097152.0; 106254.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1376792492; 1; 2599.999304; 15.599995824; 0.6; 2097152.0; 114642.93333333333; 0.0; 7.066666666666666; 0.26666666666666666; 0.13333333333333333 +1376792792; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 96467.2; 0.0; 0.7333333333333333; 0.0; 0.0 +1376793092; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 106254.13333333333; 0.0; 0.8; 0.0; 0.0 +1376793392; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 97865.33333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1376793692; 1; 2599.999304; 19.066661562666667; 0.7333333333333333; 2097152.0; 72698.93333333333; 0.0; 0.8; 0.0; 0.0 +1376793992; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 104856.0; 0.0; 0.6666666666666666; 0.0; 0.0 +1376794292; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 90874.66666666667; 0.0; 0.6666666666666666; 0.0; 0.0 +1376794592; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 86680.26666666666; 0.0; 1.1333333333333333; 0.0; 0.0 +1376794892; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 81087.73333333334; 0.0; 0.8; 0.0; 0.0 +1376795192; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 103457.86666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1376795492; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 159380.8; 0.0; 2.3333333333333335; 0.0; 0.4666666666666667 +1376795792; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 107652.26666666666; 0.0; 0.6; 0.0; 0.0 +1376796092; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 78291.46666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1376796392; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 114642.93333333333; 0.0; 0.6; 0.0; 0.0 +1376796692; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 128623.46666666666; 0.0; 0.6; 0.0; 0.0 +1376796992; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 74097.06666666667; 0.0; 0.6; 0.0; 0.0 +1376797292; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 111846.66666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1376797592; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 145401.86666666667; 0.0; 1.2; 0.0; 0.0 +1376797892; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 96467.2; 0.0; 0.6666666666666666; 0.0; 0.0 +1376798193; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 81087.73333333334; 0.0; 0.5333333333333333; 0.0; 0.0 +1376798493; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 95069.06666666667; 0.0; 0.6; 0.0; 0.0 +1376798793; 1; 2599.999304; 17.333328693333335; 0.6666666666666667; 2097152.0; 123031.73333333334; 0.0; 7.133333333333334; 0.2; 0.13333333333333333 +1376799093; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 185945.33333333334; 0.0; 2.2666666666666666; 0.06666666666666667; 0.4666666666666667 +1376799393; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 123031.73333333334; 0.0; 0.6666666666666666; 0.0; 0.0 +1376799693; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 82485.86666666667; 0.0; 0.8; 0.0; 0.0 +1376799993; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 79689.6; 0.0; 0.8; 0.0; 0.0 +1376800293; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 127226.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1376800593; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 116041.06666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1376800893; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 93670.93333333333; 0.0; 1.0; 0.0; 0.0 +1376801193; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 79689.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1376801493; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 76893.33333333333; 0.0; 1.6; 0.0; 0.0 +1376801793; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 109050.4; 0.0; 0.5333333333333333; 0.0; 0.0 +1376802093; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 144003.73333333334; 0.0; 0.6; 0.0; 0.0 +1376802393; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 138410.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1376802693; 1; 2599.999304; 17.333328693333335; 0.6666666666666667; 2097152.0; 195731.2; 0.0; 2.1333333333333333; 0.06666666666666667; 0.4666666666666667 +1376802993; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 127226.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1376803293; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 135614.93333333332; 0.0; 0.6666666666666666; 0.0; 0.0 +1376803593; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 117439.2; 0.0; 0.6; 0.0; 0.0 +1376803893; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 127226.13333333333; 0.0; 0.6; 0.0; 0.0 +1376804193; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 88078.4; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1376804493; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 113244.8; 0.0; 0.7333333333333333; 0.0; 0.0 +1376804793; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 138411.2; 0.0; 7.0; 0.2; 0.13333333333333333 +1376805093; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 130021.6; 0.0; 0.6; 0.0; 0.0 +1376805393; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 86680.26666666666; 0.0; 0.5333333333333333; 0.0; 0.0 +1376805693; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 107652.26666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1376805993; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 83884.0; 0.0; 0.6; 0.0; 0.0 +1376806293; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 208314.93333333332; 0.0; 2.1333333333333333; 0.06666666666666667; 0.4666666666666667 +1376806593; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 169169.33333333334; 0.0; 0.6666666666666666; 0.0; 0.0 +1376806893; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 149595.46666666667; 0.0; 0.8; 0.0; 0.0 +1376807193; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 132817.86666666667; 0.0; 0.8; 0.0; 0.0 +1376807493; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 103457.86666666667; 0.0; 0.8; 0.06666666666666667; 0.13333333333333333 +1376807793; 1; 2599.999304; 17.333328693333335; 0.6666666666666667; 2097152.0; 111846.66666666667; 0.0; 1.8; 0.06666666666666667; 0.0 +1376808093; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 81087.73333333334; 0.0; 1.4666666666666666; 0.06666666666666667; 0.0 +1376808393; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 88078.4; 0.0; 1.0666666666666667; 0.0; 0.0 +1376808693; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 79689.6; 0.0; 0.5333333333333333; 0.0; 0.0 +1376808993; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 102059.73333333334; 0.0; 0.6666666666666666; 0.0; 0.0 +1376809293; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 123030.93333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1376809593; 1; 2599.999304; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 0.5333333333333333; 0.0; 0.0 +1376809894; 1; 2599.999304; 15.599995824; 0.6; 2097152.0; 191537.86666666667; 0.0; 2.2666666666666666; 0.06666666666666667; 0.5333333333333333 +1376810194; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 99263.46666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1376810494; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 131420.53333333333; 0.0; 6.866666666666666; 0.2; 0.13333333333333333 +1376810794; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 97865.33333333333; 0.0; 0.6; 0.0; 0.0 +1376811094; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 102059.73333333334; 0.0; 0.6666666666666666; 0.0; 0.0 +1376811394; 1; 2599.999304; 0.0; 0.0; 2097152.0; 106254.13333333333; 0.0; 0.6; 0.0; 0.0 +1376811694; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 93670.93333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1376811994; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 95069.06666666667; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1376812294; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 95069.06666666667; 0.0; 0.6; 0.0; 0.0 +1376812594; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 88078.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1376812894; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1376813194; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 102059.73333333334; 0.0; 0.6; 0.0; 0.0 +1376813494; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 191537.86666666667; 0.06666666666666667; 2.2666666666666666; 0.06666666666666667; 0.4666666666666667 +1376813794; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 117439.2; 0.0; 1.0; 0.0; 0.0 +1376814094; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 110448.53333333334; 0.0; 0.6; 0.0; 0.0 +1376814394; 1; 2599.999304; 0.0; 0.0; 2097152.0; 67106.4; 0.0; 0.5333333333333333; 0.0; 0.0 +1376814694; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 118837.33333333333; 0.0; 0.6666666666666666; 0.0; 0.0 +1376814994; 1; 2599.999304; 0.0; 0.0; 2097152.0; 121633.6; 0.0; 0.6; 0.06666666666666667; 0.0 +1376815294; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 75495.2; 0.0; 1.2; 0.0; 0.0 +1376815594; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 89476.53333333334; 0.0; 0.6; 0.0; 0.0 +1376815894; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 93670.93333333333; 0.0; 0.6666666666666666; 0.0; 0.0 +1376816194; 1; 2599.999304; 0.0; 0.0; 2097152.0; 81087.73333333334; 0.0; 0.8; 0.0; 0.0 +1376816494; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 114642.93333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1376816794; 1; 2599.999304; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 1.1333333333333333; 0.0; 0.0 +1376817094; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 150993.6; 0.2; 2.533333333333333; 0.06666666666666667; 0.4666666666666667 +1376817394; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 114642.93333333333; 0.0; 7.066666666666666; 0.26666666666666666; 0.13333333333333333 +1376817694; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 102059.73333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1376817994; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 121633.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1376818294; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 132817.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1376818594; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 89476.53333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1376818894; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 121633.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1376819194; 1; 2599.999304; 0.0; 0.0; 2097152.0; 57319.46666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1376819494; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 96467.2; 0.0; 1.1333333333333333; 0.0; 0.0 +1376819794; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 109050.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1376820094; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 123031.73333333334; 0.0; 1.8; 0.0; 0.0 +1376820394; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 100661.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1376820694; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 150993.6; 0.0; 2.3333333333333335; 0.0; 0.4666666666666667 +1376820994; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 104856.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1376821294; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 121632.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1376821594; 1; 2599.999304; 0.0; 0.0; 2097152.0; 89476.53333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1376821894; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 103457.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1376822194; 1; 2599.999304; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 1.1333333333333333; 0.0; 0.0 +1376822494; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 95069.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1376822794; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 138411.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1376823094; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 173362.93333333332; 0.0; 7.133333333333334; 0.2; 0.13333333333333333 +1376823394; 1; 2599.999304; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1376823694; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 79689.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1376823995; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 83884.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1376824295; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 142604.8; 0.0; 2.2666666666666666; 0.06666666666666667; 0.4666666666666667 +1376824595; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 116041.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1376824895; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 106254.13333333333; 0.0; 1.0; 0.0; 0.0 +1376825195; 1; 2599.999304; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1376825495; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 114642.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1376825795; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 107652.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1376826095; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 107652.26666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1376826395; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 117439.2; 0.0; 1.4; 0.0; 0.0 +1376826695; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 120234.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1376826995; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 134216.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1376827295; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 110448.53333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1376827595; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 123031.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1376827895; 1; 2599.999304; 25.99999304; 1.0; 2097152.0; 185945.6; 161.06666666666666; 22.0; 0.13333333333333333; 0.8666666666666667 +1376828195; 1; 2599.999304; 51.99998608; 2.0; 2097152.0; 812294.1333333333; 0.0; 3.533333333333333; 0.0; 0.0 +1376828495; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 402650.93333333335; 31.8; 3.066666666666667; 0.0; 0.0 +1376828795; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 215304.53333333333; 0.06666666666666667; 2.3333333333333335; 0.0; 0.0 +1376829095; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 138411.2; 0.0; 1.8666666666666667; 0.0; 0.0 +1376829395; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 85282.13333333333; 0.0; 1.0; 0.0; 0.0 +1376829695; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 109050.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1376829995; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 111846.66666666667; 0.0; 6.666666666666667; 0.2; 0.13333333333333333 +1376830295; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 107652.26666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1376830595; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 121633.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1376830895; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 106254.13333333333; 0.0; 1.0666666666666667; 0.13333333333333333; 0.06666666666666667 +1376831195; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 83884.0; 0.0; 0.7333333333333333; 0.13333333333333333; 0.0 +1376831495; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 163575.73333333334; 0.0; 2.3333333333333335; 0.13333333333333333; 0.5333333333333333 +1376831795; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 111846.66666666667; 0.0; 0.8; 0.0; 0.0 +1376832096; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 104856.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1376832396; 1; 2599.999304; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1376832696; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 85282.13333333333; 0.0; 0.7333333333333333; 0.13333333333333333; 0.0 +1376832996; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 107652.26666666666; 0.0; 0.8; 0.0; 0.0 +1376833296; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 106254.13333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1376833596; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 95069.06666666667; 0.0; 1.0; 0.0; 0.0 +1376833896; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 125828.0; 0.0; 11.4; 0.0; 0.0 +1376834196; 1; 2599.999304; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1376834495; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 134215.73333333334; 0.0; 0.8; 0.0; 0.0 +1376834795; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 110448.53333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1376835095; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 187343.73333333334; 0.0; 2.2; 0.06666666666666667; 0.5333333333333333 +1376835395; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 117439.2; 0.0; 0.6666666666666666; 0.0; 0.0 +1376835695; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 88078.4; 0.0; 0.8; 0.13333333333333333; 0.0 +1376835995; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 102059.73333333334; 0.0; 1.0666666666666667; 0.0; 0.0 +1376836295; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 141206.66666666666; 0.0; 7.066666666666666; 0.26666666666666666; 0.2 +1376836595; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 146800.0; 0.0; 0.8; 0.0; 0.0 +1376836895; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 100661.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1376837195; 1; 2599.999304; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1376837495; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 121633.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1376837795; 1; 2599.999304; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1376838096; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 104856.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1376838396; 1; 2599.999304; 0.0; 0.0; 2097152.0; 110448.53333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1376838696; 1; 2599.999304; 15.599995824; 0.6; 2097152.0; 192935.73333333334; 0.06666666666666667; 2.3333333333333335; 0.06666666666666667; 0.5333333333333333 +1376838996; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 146800.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1376839296; 1; 2599.999304; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1376839596; 1; 2599.999304; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1376839896; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 109050.4; 0.0; 0.8; 0.0; 0.0 +1376840196; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 92272.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1376840496; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 92272.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1376840796; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 72698.93333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1376841096; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 81087.73333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1376841396; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 120235.46666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1376841696; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 120235.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1376841996; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 125828.0; 0.0; 0.8; 0.0; 0.0 +1376842296; 1; 2599.999304; 15.599995824; 0.6; 2097152.0; 155188.0; 0.06666666666666667; 8.266666666666667; 0.26666666666666666; 0.6 +1376842596; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 130022.4; 0.06666666666666667; 1.0; 0.06666666666666667; 0.0 +1376842896; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 95069.06666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1376843196; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 114642.93333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1376843496; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 100661.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1376843796; 1; 2599.999304; 0.0; 0.0; 2097152.0; 86680.26666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1376844096; 1; 2599.999304; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1376844396; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 130022.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1376844696; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 138410.4; 0.0; 0.6666666666666666; 0.0; 0.0 +1376844996; 1; 2599.999304; 0.0; 0.0; 2097152.0; 82485.86666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1376845296; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 88078.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1376845596; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 96467.2; 0.0; 0.8; 0.0; 0.0 +1376845896; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 104855.2; 0.06666666666666667; 2.8; 0.06666666666666667; 0.5333333333333333 +1376846196; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 137011.73333333334; 0.0; 0.8; 0.0; 0.0 +1376846496; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 138410.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1376846796; 1; 2599.999304; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1376847096; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 116041.06666666667; 0.0; 0.8; 0.0; 0.0 +1376847396; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 118837.33333333333; 0.0; 0.6666666666666666; 0.0; 0.0 +1376847696; 1; 2599.999304; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1376847996; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 79689.6; 0.0; 0.7333333333333333; 0.0; 0.0 +1376848296; 1; 2599.999304; 0.0; 0.0; 2097152.0; 72698.93333333333; 0.0; 0.6666666666666666; 0.0; 0.0 +1376848596; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 121633.6; 12.733333333333333; 13.4; 0.3333333333333333; 0.2 +1376848896; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 159381.6; 0.0; 1.0; 0.0; 0.0 +1376849196; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 167771.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1376849496; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 142605.6; 0.0; 1.6666666666666667; 0.06666666666666667; 0.4666666666666667 +1376849796; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 76893.33333333333; 0.0; 0.8; 0.0; 0.0 +1376850096; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 88078.4; 0.0; 0.8; 0.0; 0.0 +1376850396; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 96467.2; 0.0; 0.6666666666666666; 0.0; 0.0 +1376850696; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 110448.53333333334; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1376850996; 1; 2599.999304; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.6666666666666666; 0.0; 0.0 +1376851296; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 96467.2; 0.0; 1.1333333333333333; 0.0; 0.0 +1376851596; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 79689.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1376851896; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 114642.93333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1376852196; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 121633.6; 0.0; 0.6666666666666666; 0.0; 0.0 +1376852496; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 118837.33333333333; 0.0; 0.8; 0.0; 0.0 +1376852796; 1; 2599.999304; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 1.0; 0.0; 0.0 +1376853096; 1; 2599.999304; 15.599995824; 0.6; 2097152.0; 184548.0; 0.06666666666666667; 2.2; 0.06666666666666667; 0.4666666666666667 +1376853396; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 114642.93333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1376853697; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 96467.2; 0.0; 0.6; 0.06666666666666667; 0.0 +1376853996; 1; 2599.999304; 36.399990255999995; 1.4; 2097152.0; 171964.8; 161.06666666666666; 14.333333333333334; 0.0; 0.2 +1376854297; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 461372.0; 0.0; 1.3333333333333333; 0.0; 0.0 +1376854597; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 222295.2; 31.8; 9.666666666666666; 0.2; 0.13333333333333333 +1376854897; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 218100.8; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1376855197; 1; 2599.999304; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1376855497; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 111846.66666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1376855797; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 130021.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1376856097; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 118837.33333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1376856397; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 113244.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1376856697; 1; 2599.999304; 15.599995824; 0.6; 2097152.0; 167769.6; 0.0; 2.466666666666667; 0.06666666666666667; 0.4666666666666667 +1376856997; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 159381.6; 0.0; 1.0; 0.0; 0.0 +1376857297; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 100661.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1376857597; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 85282.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1376857897; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 148197.33333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1376858197; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 124429.86666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1376858497; 1; 2599.999304; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 1.0; 0.0; 0.0 +1376858797; 1; 2599.999304; 0.0; 0.0; 2097152.0; 74097.06666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1376859097; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 100661.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1376859397; 1; 2599.999304; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1376859697; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 113244.8; 0.0; 1.2; 0.0; 0.0 +1376859997; 1; 2599.999304; 0.0; 0.0; 2097152.0; 114642.93333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1376860297; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 197129.6; 0.0; 2.2; 0.06666666666666667; 0.4666666666666667 +1376860597; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 137012.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1376860897; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 132818.66666666666; 0.0; 7.266666666666667; 0.2; 0.13333333333333333 +1376861197; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 144002.66666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1376861497; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 113244.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1376861797; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 132818.13333333333; 0.0; 0.8; 0.06666666666666667; 0.0 +1376862097; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 110448.53333333334; 0.0; 1.0; 0.0; 0.0 +1376862397; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 86680.26666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1376862697; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 86680.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1376862997; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 93670.93333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1376863298; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 69902.66666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1376863598; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 69902.66666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1376863898; 1; 2599.999304; 15.599995824; 0.6; 2097152.0; 118837.33333333333; 0.06666666666666667; 2.2; 0.06666666666666667; 0.4666666666666667 +1376864198; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 97865.33333333333; 0.0; 0.8; 0.0; 0.0 +1376864498; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 107652.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1376864798; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1376865098; 1; 2599.999304; 9.2857118; 0.35714285714285715; 2097152.0; 140808.0; 0.07142857142857142; 1.4285714285714286; 0.14285714285714285; 0.14285714285714285 +1376865398; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 120235.46666666666; 0.0; 1.1333333333333333; 0.0; 0.0 +1376865698; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 113244.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1376865998; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 102059.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1376866298; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 110448.53333333334; 0.0; 0.6666666666666666; 0.0; 0.0 +1376866599; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 92272.8; 0.0; 7.133333333333334; 0.2; 0.13333333333333333 +1376866899; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 107652.26666666666; 0.2; 0.9333333333333333; 0.0; 0.0 +1376867199; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 128623.46666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1376867500; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 174761.06666666668; 0.0; 2.2666666666666666; 0.06666666666666667; 0.5333333333333333 +1376867801; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 130022.4; 0.0; 1.1333333333333333; 0.0; 0.0 +1376868101; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 92272.8; 0.0; 1.0; 0.0; 0.0 +1376868401; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 102059.73333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1376868701; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 83884.0; 0.0; 0.6; 0.0; 0.0 +1376869001; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 75495.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1376869301; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 85282.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1376869601; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 75495.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1376869901; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 90874.66666666667; 0.0; 0.6; 0.0; 0.0 +1376870201; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 100661.6; 0.0; 0.8666666666666667; 0.13333333333333333; 0.0 +1376870501; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 113244.8; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1376870801; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 162178.66666666666; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1376871101; 1; 2599.999304; 15.599995824; 0.6; 2097152.0; 167770.4; 0.0; 2.8; 0.06666666666666667; 0.5333333333333333 +1376871401; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 118837.33333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1376871701; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 109049.6; 0.0; 0.7333333333333333; 0.0; 0.0 +1376872001; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 104856.0; 0.0; 1.1333333333333333; 0.13333333333333333; 0.0 +1376872301; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 90874.66666666667; 0.06666666666666667; 1.4; 0.0; 0.0 +1376872601; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 102059.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1376872901; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 113244.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1376873201; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 117439.2; 0.0; 0.7333333333333333; 0.13333333333333333; 0.0 +1376873501; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 113244.8; 0.0; 6.866666666666666; 0.2; 0.13333333333333333 +1376873801; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 123031.73333333334; 0.0; 0.5333333333333333; 0.0; 0.0 +1376874101; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 106254.13333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1376874401; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 130022.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1376874701; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 181751.2; 0.2; 2.533333333333333; 0.06666666666666667; 0.4666666666666667 +1376875001; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 127225.86666666667; 0.0; 1.0; 0.0; 0.0 +1376875301; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 82485.86666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1376875601; 1; 2599.999304; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1376875901; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 102059.73333333334; 0.0; 0.8; 0.0; 0.0 +1376876201; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 82485.86666666667; 0.0; 1.0; 0.0; 0.0 +1376876501; 1; 2599.999304; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.7333333333333333; 0.0; 0.0 +1376876801; 1; 2599.999304; 0.0; 0.0; 2097152.0; 86680.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1376877101; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 113244.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1376877401; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 125827.2; 0.0; 11.6; 0.0; 0.0 +1376877701; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 125828.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1376878001; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 145401.06666666668; 0.0; 0.8; 0.0; 0.0 +1376878302; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 171965.06666666668; 0.06666666666666667; 1.9333333333333333; 0.06666666666666667; 0.4666666666666667 +1376878602; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 138410.93333333332; 0.0; 0.8; 0.0; 0.0 +1376878902; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 92272.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1376879202; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 118837.33333333333; 0.0; 0.8; 0.0; 0.0 +1376879502; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 82485.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1376879802; 1; 2599.999304; 71.06664764266665; 2.733333333333333; 2097152.0; 661299.7333333333; 161.0; 21.466666666666665; 0.06666666666666667; 0.4 +1376880102; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 416631.73333333334; 0.0; 8.733333333333333; 0.2; 0.13333333333333333 +1376880402; 1; 2599.999304; 15.599995824; 0.6; 2097152.0; 262841.06666666665; 31.8; 3.7333333333333334; 0.0; 0.0 +1376880702; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 163576.53333333333; 0.0; 2.2666666666666666; 0.0; 0.0 +1376881002; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 96467.2; 0.0; 1.4; 0.0; 0.0 +1376881302; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 113244.8; 0.0; 0.7333333333333333; 0.0; 0.0 +1376881602; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 128623.46666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1376881902; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 206917.33333333334; 0.0; 1.9333333333333333; 0.06666666666666667; 0.4666666666666667 +1376882202; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 131419.73333333334; 0.0; 0.6666666666666666; 0.0; 0.0 +1376882502; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 96466.66666666667; 0.0; 0.6666666666666666; 0.0; 0.0 +1376882802; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 83883.73333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1376883102; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 92272.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1376883402; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 104856.0; 0.0; 0.7333333333333333; 0.0; 0.0 +1376883702; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 104856.0; 0.0; 0.8; 0.0; 0.0 +1376884002; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 120235.46666666666; 0.0; 1.2; 0.0; 0.0 +1376884302; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 99263.46666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1376884602; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 103457.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1376884902; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 81087.73333333334; 0.0; 0.6666666666666666; 0.0; 0.0 +1376885202; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 69902.66666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1376885502; 1; 2599.999304; 15.599995824; 0.6; 2097152.0; 232082.93333333332; 0.06666666666666667; 2.3333333333333335; 0.06666666666666667; 0.5333333333333333 +1376885802; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 128624.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1376886102; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 95069.06666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1376886402; 1; 2599.999304; 0.0; 0.0; 2097152.0; 110448.53333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1376886702; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 99263.46666666666; 0.06666666666666667; 6.8; 0.26666666666666666; 0.2 +1376887002; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 155186.93333333332; 0.0; 0.7333333333333333; 0.0; 0.0 +1376887302; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 120235.2; 0.0; 1.0; 0.0; 0.0 +1376887602; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 125828.0; 0.0; 0.8; 0.0; 0.0 +1376887902; 1; 2599.999304; 0.0; 0.0; 2097152.0; 121633.6; 0.0; 0.7333333333333333; 0.0; 0.0 +1376888202; 1; 2599.999304; 0.0; 0.0; 2097152.0; 95069.06666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1376888502; 1; 2599.999304; 0.0; 0.0; 2097152.0; 79689.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1376888802; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 89476.53333333334; 0.0; 1.0666666666666667; 0.0; 0.0 +1376889102; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 180352.53333333333; 0.0; 2.1333333333333333; 0.06666666666666667; 0.4666666666666667 +1376889403; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 125828.0; 0.0; 0.6666666666666666; 0.0; 0.0 +1376889703; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 123031.73333333334; 0.0; 0.8; 0.0; 0.0 +1376890003; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 124429.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1376890303; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 81087.73333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1376890603; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 110448.53333333334; 0.0; 1.4666666666666666; 0.0; 0.0 +1376890903; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 93670.93333333333; 0.0; 0.8; 0.0; 0.0 +1376891203; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 116041.06666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1376891503; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 125828.0; 0.0; 1.0; 0.0; 0.0 +1376891803; 1; 2599.999304; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 0.8; 0.0; 0.0 +1376892103; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 82485.86666666667; 0.0; 0.6666666666666666; 0.0; 0.0 +1376892403; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 145401.06666666668; 0.0; 6.533333333333333; 0.2; 0.13333333333333333 +1376892703; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 176158.13333333333; 0.0; 2.4; 0.06666666666666667; 0.4666666666666667 +1376893003; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 111846.66666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1376893303; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 125828.0; 0.0; 0.6; 0.0; 0.0 +1376893603; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 109050.4; 0.0; 0.6; 0.0; 0.0 +1376893903; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 88078.4; 0.0; 0.9333333333333333; 0.06666666666666667; 0.06666666666666667 +1376894203; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 120235.46666666666; 0.0; 2.0; 0.06666666666666667; 0.0 +1376894503; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 144003.73333333334; 0.0; 1.4; 0.0; 0.0 +1376894803; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 104856.0; 0.0; 0.5333333333333333; 0.0; 0.0 +1376895103; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 81087.73333333334; 0.0; 0.6; 0.0; 0.0 +1376895403; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 92272.8; 0.0; 0.7333333333333333; 0.0; 0.0 +1376895703; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 110448.53333333334; 0.0; 1.2; 0.0; 0.0 +1376896003; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 109050.4; 0.0; 0.6; 0.0; 0.0 +1376896304; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 128623.46666666666; 0.0; 2.0; 0.06666666666666667; 0.4666666666666667 +1376896604; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 88078.4; 0.0; 0.6666666666666666; 0.0; 0.0 +1376896904; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 93670.93333333333; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1376897204; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 104856.0; 0.0; 0.5333333333333333; 0.0; 0.0 +1376897504; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 95069.06666666667; 0.0; 0.6; 0.0; 0.0 +1376897804; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 81087.73333333334; 0.0; 0.6666666666666666; 0.0; 0.0 +1376898104; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 111846.66666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1376898404; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 156586.13333333333; 0.0; 1.4666666666666666; 0.06666666666666667; 0.0 +1376898704; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 123031.73333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1376899004; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 117439.2; 0.0; 0.6; 0.0; 0.0 +1376899304; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 123031.73333333334; 0.0; 7.333333333333333; 0.26666666666666666; 0.13333333333333333 +1376899604; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 107652.26666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1376899904; 1; 2599.999304; 15.599995824; 0.6; 2097152.0; 167771.46666666667; 0.6; 2.2; 0.06666666666666667; 0.5333333333333333 +1376900204; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 135614.93333333332; 0.0; 0.9333333333333333; 0.0; 0.0 +1376900504; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 71300.8; 0.0; 0.6666666666666666; 0.0; 0.0 +1376900804; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 103457.86666666667; 0.0; 0.5333333333333333; 0.0; 0.0 +1376901104; 1; 2599.999304; 0.0; 0.0; 2097152.0; 78291.46666666666; 0.0; 0.8; 0.0; 0.0 +1376901404; 1; 2599.999304; 0.0; 0.0; 2097152.0; 71300.8; 0.0; 0.7333333333333333; 0.0; 0.0 +1376901704; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 116041.06666666667; 0.0; 0.8; 0.0; 0.0 +1376902004; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 113244.8; 0.0; 0.5333333333333333; 0.06666666666666667; 0.0 +1376902304; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 104856.0; 0.0; 0.8; 0.0; 0.0 +1376902604; 1; 2599.999304; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1376902904; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 102059.73333333334; 0.0; 0.6; 0.0; 0.0 +1376903204; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 132818.4; 0.0; 0.6; 0.0; 0.0 +1376903504; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 218101.06666666668; 0.0; 1.9333333333333333; 0.06666666666666667; 0.5333333333333333 +1376903804; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 90874.66666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1376904104; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 114642.93333333333; 0.0; 0.6; 0.0; 0.0 +1376904404; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 102059.73333333334; 0.0; 0.6666666666666666; 0.0; 0.0 +1376904704; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 127225.86666666667; 0.0; 6.733333333333333; 0.4; 0.06666666666666667 +1376905004; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 142605.06666666668; 0.0; 0.8666666666666667; 0.0; 0.0 +1376905304; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 99263.46666666666; 0.0; 1.2; 0.0; 0.0 +1376905604; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 93670.93333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1376905904; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 132818.4; 0.0; 0.8; 0.0; 0.0 +1376906204; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 117438.66666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1376906504; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 62912.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1376906804; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 85282.13333333333; 0.0; 1.0; 0.0; 0.0 +1376907104; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 184548.0; 0.06666666666666667; 2.533333333333333; 0.06666666666666667; 0.4666666666666667 +1376907404; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 132818.66666666666; 0.0; 1.0; 0.0; 0.0 +1376907704; 1; 2599.999304; 1.7333328693333334; 0.06666666666666667; 2097152.0; 132818.66666666666; 0.0; 1.1333333333333333; 0.0; 0.0 +1376908004; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 92272.8; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1376908304; 1; 2599.999304; 3.466665738666667; 0.13333333333333333; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1376908604; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 113244.8; 0.0; 0.8666666666666667; 0.26666666666666666; 0.0 +1376908904; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 106254.13333333333; 0.0; 0.8666666666666667; 0.4; 0.0 +1376909204; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 131420.53333333333; 0.0; 1.0; 0.0; 0.0 +1376909504; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 90874.66666666667; 0.0; 1.0666666666666667; 2.4; 0.0 +1376909804; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 118837.33333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1376910104; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 89476.53333333334; 0.0; 0.8; 0.13333333333333333; 0.0 +1376910404; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 121632.8; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1376910705; 1; 2599.999304; 22.533327301333333; 0.8666666666666667; 2097152.0; 170566.66666666666; 12.8; 16.466666666666665; 0.4666666666666667; 0.7333333333333333 +1376911005; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 106254.13333333333; 0.0; 1.2; 0.0; 0.0 +1376911305; 1; 2599.999304; 10.399997216; 0.4; 2097152.0; 89476.53333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1376911605; 1; 2599.999304; 5.199998608; 0.2; 2097152.0; 123031.73333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1376911905; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 162178.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1376912205; 1; 2599.999304; 8.666664346666668; 0.33333333333333337; 2097152.0; 148197.33333333334; 0.0; 1.1333333333333333; 0.0; 0.0 +1376912505; 1; 2599.999304; 6.933331477333334; 0.26666666666666666; 2097152.0; 96466.66666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1376912805; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 85282.13333333333; 0.0; 1.2; 0.0; 0.0 +1376913105; 1; 2599.999304; 13.866662954666667; 0.5333333333333333; 2097152.0; 110448.53333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1376913405; 1; 2599.999304; 12.133330085333332; 0.4666666666666666; 2097152.0; 85282.13333333333; 0.0; 1.0; 0.0; 0.0 +1376913705; 1; 2599.9993; 28.888881111111115; 1.1111111111111112; 2097152.0; 125828.0; 0.0; 1.125; 0.0; 0.0 +1376914005; 1; 2599.9993; 8.666664333333335; 0.33333333333333337; 2097152.0; 99263.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1376914305; 1; 2599.9993; 13.866662933333332; 0.5333333333333333; 2097152.0; 166372.26666666666; 0.0; 2.3333333333333335; 0.06666666666666667; 0.4666666666666667 +1376914605; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 121633.6; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1376914905; 1; 2599.9993; 0.0; 0.0; 2097152.0; 121633.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1376915205; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 61513.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1376915505; 1; 2599.999343; 28.599992773000004; 1.1; 2097152.0; 77592.4; 0.0; 1.0; 0.1111111111111111; 0.0 +1376915805; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 99263.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1376916105; 1; 2599.999343; 6.933331581333333; 0.26666666666666666; 2097152.0; 146800.0; 0.0; 6.8; 0.3333333333333333; 0.13333333333333333 +1376916405; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 104856.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1376916705; 1; 2599.999343; 5.199998686; 0.2; 2097152.0; 93670.93333333333; 0.0; 0.6; 0.0; 0.0 +1376917005; 1; 2599.999343; 5.199998686; 0.2; 2097152.0; 111846.66666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1376917305; 1; 2599.999343; 3.4666657906666667; 0.13333333333333333; 2097152.0; 97865.33333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1376917605; 1; 2599.999343; 6.933331581333333; 0.26666666666666666; 2097152.0; 124429.6; 0.0; 0.7333333333333333; 0.0; 0.0 +1376917905; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 199925.33333333334; 0.0; 2.1333333333333333; 0.06666666666666667; 0.4666666666666667 +1376918205; 1; 2599.999343; 3.4666657906666667; 0.13333333333333333; 2097152.0; 106254.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1376918505; 1; 2599.999343; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 0.6666666666666666; 0.0; 0.0 +1376918805; 1; 2599.999343; 5.199998686; 0.2; 2097152.0; 106254.13333333333; 0.0; 0.6; 0.0; 0.0 +1376919105; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 109050.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1376919405; 1; 2599.999343; 3.4666657906666667; 0.13333333333333333; 2097152.0; 107652.26666666666; 0.0; 0.8; 0.0; 0.0 +1376919705; 1; 2599.999343; 0.0; 0.0; 2097152.0; 95069.06666666667; 0.0; 0.8; 0.0; 0.0 +1376920005; 1; 2599.999343; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 0.8; 0.0; 0.0 +1376920306; 1; 2599.999343; 3.4666657906666667; 0.13333333333333333; 2097152.0; 118837.33333333333; 0.0; 0.8; 0.0; 0.0 +1376920606; 1; 2599.99945; 25.9999945; 1.0; 2097152.0; 76257.81818181818; 0.0; 1.1; 0.0; 0.0 +1376920906; 1; 2599.99945; 8.666664833333334; 0.33333333333333337; 2097152.0; 104856.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1376921206; 1; 2599.99945; 12.133330766666665; 0.4666666666666666; 2097152.0; 83884.0; 0.0; 11.6; 0.0; 0.0 +1376921506; 1; 2599.99945; 12.133330766666665; 0.4666666666666666; 2097152.0; 156584.8; 0.0; 2.2; 0.0; 0.4666666666666667 +1376921806; 1; 2599.99945; 10.3999978; 0.4; 2097152.0; 130022.13333333333; 0.0; 7.2; 0.4; 0.2 +1376922106; 1; 2599.99945; 1.7333329666666666; 0.06666666666666667; 2097152.0; 159382.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1376922406; 1; 2599.99945; 0.0; 0.0; 2097152.0; 106254.13333333333; 0.0; 0.8; 0.0; 0.0 +1376922706; 1; 2599.99945; 5.1999989; 0.2; 2097152.0; 86680.26666666666; 0.0; 1.1333333333333333; 0.06666666666666667; 0.13333333333333333 +1376923006; 1; 2599.99945; 5.1999989; 0.2; 2097152.0; 121633.06666666667; 0.0; 1.0; 0.0; 0.0 +1376923306; 1; 2599.99945; 5.1999989; 0.2; 2097152.0; 110448.26666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1376923606; 1; 2599.99945; 1.7333329666666666; 0.06666666666666667; 2097152.0; 107652.26666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1376923906; 1; 2599.999306; 21.272721594545455; 0.8181818181818181; 2097152.0; 114388.72727272728; 0.0; 1.0; 0.0; 0.0 +1376924206; 1; 2599.999306; 3.4666657413333337; 0.13333333333333333; 2097152.0; 114642.93333333333; 0.0; 0.8; 0.06666666666666667; 0.0 +1376924506; 1; 2599.999297; 28.88888107777778; 1.1111111111111112; 2097152.0; 88544.44444444444; 0.0; 0.75; 0.0; 0.0 +1376924806; 1; 2599.999334; 28.363629098181818; 1.0909090909090908; 2097152.0; 101042.90909090909; 0.0; 0.8; 0.1; 0.0 +1376925106; 1; 2599.999334; 20.799994672000004; 0.8; 2097152.0; 141206.66666666666; 0.0; 2.2; 1.0; 0.5333333333333333 +1376925406; 1; 2599.999334; 17.333328893333338; 0.6666666666666667; 2097152.0; 106254.13333333333; 0.0; 0.6666666666666666; 2.066666666666667; 0.0 +1376925706; 1; 2599.999334; 12.133330225333331; 0.4666666666666666; 2097152.0; 88078.4; 0.0; 0.6; 0.06666666666666667; 0.0 +1376926006; 1; 2599.999334; 13.866663114666666; 0.5333333333333333; 2097152.0; 114642.93333333333; 0.0; 1.0; 0.06666666666666667; 0.0 +1376926306; 1; 2599.999334; 12.133330225333331; 0.4666666666666666; 2097152.0; 89476.53333333334; 0.0; 0.6; 0.06666666666666667; 0.0 +1376926606; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 61513.86666666667; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1376926906; 1; 2599.999334; 10.399997336000002; 0.4; 2097152.0; 106254.13333333333; 0.0; 0.8; 0.13333333333333333; 0.0 +1376927206; 1; 2599.999334; 10.399997336000002; 0.4; 2097152.0; 102059.73333333334; 0.0; 1.1333333333333333; 0.13333333333333333; 0.0 +1376927506; 1; 2599.999334; 10.399997336000002; 0.4; 2097152.0; 104856.0; 0.0; 1.2666666666666666; 0.0; 0.0 +1376927806; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 85282.13333333333; 0.0; 0.9333333333333333; 0.13333333333333333; 0.0 +1376928106; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 71300.8; 0.0; 0.7333333333333333; 0.0; 0.0 +1376928406; 1; 2599.999334; 13.866663114666666; 0.5333333333333333; 2097152.0; 86680.26666666666; 0.0; 6.733333333333333; 0.2; 0.13333333333333333 +1376928706; 1; 2599.999334; 10.399997336000002; 0.4; 2097152.0; 155188.0; 0.0; 2.533333333333333; 0.06666666666666667; 0.5333333333333333 +1376929006; 1; 2599.999334; 8.666664446666669; 0.33333333333333337; 2097152.0; 145401.86666666667; 0.0; 0.8; 0.0; 0.0 +1376929306; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 124429.86666666667; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1376929606; 1; 2599.999334; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.8; 0.0; 0.0 +1376929906; 1; 2599.999334; 0.0; 0.0; 2097152.0; 116041.06666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1376930207; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 111846.66666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1376930507; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 75495.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1376930807; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 134216.26666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1376931107; 1; 2599.999334; 0.0; 0.0; 2097152.0; 90874.4; 0.0; 0.6; 0.0; 0.0 +1376931407; 1; 2599.999334; 57.19998534800001; 2.2; 2097152.0; 268433.3333333333; 161.0; 21.733333333333334; 0.0; 0.4 +1376931707; 1; 2599.999334; 10.399997336000002; 0.4; 2097152.0; 773148.0; 0.0; 2.6; 0.0; 0.0 +1376932007; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 304784.0; 31.8; 3.2666666666666666; 0.0; 0.0 +1376932307; 1; 2599.999334; 17.333328893333338; 0.6666666666666667; 2097152.0; 219498.93333333332; 0.0; 3.6; 0.0; 0.4666666666666667 +1376932607; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 124429.6; 0.0; 1.4666666666666666; 0.0; 0.0 +1376932907; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 102059.73333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1376933207; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 99263.46666666666; 0.0; 0.6; 0.0; 0.0 +1376933507; 1; 2599.999334; 0.0; 0.0; 2097152.0; 145400.53333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1376933807; 1; 2599.999334; 0.0; 0.0; 2097152.0; 134216.0; 0.0; 0.8; 0.0; 0.0 +1376934107; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 83884.0; 0.0; 0.8; 0.0; 0.0 +1376934407; 1; 2599.999334; 8.666664446666669; 0.33333333333333337; 2097152.0; 146799.46666666667; 0.0; 7.333333333333333; 0.2; 0.13333333333333333 +1376934707; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 138410.93333333332; 0.0; 0.7333333333333333; 0.0; 0.0 +1376935007; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 121633.6; 0.0; 1.4666666666666666; 0.0; 0.0 +1376935307; 1; 2599.999334; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 0.8; 0.0; 0.0 +1376935607; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 92272.8; 0.0; 0.8; 0.0; 0.0 +1376935907; 1; 2599.999334; 12.133330225333331; 0.4666666666666666; 2097152.0; 138410.4; 0.06666666666666667; 2.3333333333333335; 0.0; 0.4666666666666667 +1376936207; 1; 2599.999334; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 0.8; 0.0; 0.0 +1376936507; 1; 2599.999334; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1376936807; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 92272.8; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1376937107; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 107652.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1376937407; 1; 2599.999334; 0.0; 0.0; 2097152.0; 125828.0; 0.0; 0.7333333333333333; 0.0; 0.0 +1376937707; 1; 2599.999334; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 0.6666666666666666; 0.0; 0.0 +1376938007; 1; 2599.999334; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 0.6; 0.0; 0.0 +1376938307; 1; 2599.999334; 0.0; 0.0; 2097152.0; 110448.53333333334; 0.0; 0.8; 0.0; 0.0 +1376938607; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 121632.8; 0.0; 0.6; 0.06666666666666667; 0.0 +1376938907; 1; 2599.999334; 0.0; 0.0; 2097152.0; 118837.33333333333; 0.0; 0.6; 0.0; 0.0 +1376939208; 1; 2599.999334; 0.0; 0.0; 2097152.0; 127226.13333333333; 0.0; 0.6666666666666666; 0.0; 0.0 +1376939508; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 139809.33333333334; 0.0; 2.2; 0.06666666666666667; 0.4666666666666667 +1376939808; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 120235.46666666666; 0.06666666666666667; 1.1333333333333333; 0.0; 0.0 +1376940108; 1; 2599.999334; 0.0; 0.0; 2097152.0; 89476.53333333334; 0.0; 0.5333333333333333; 0.0; 0.0 +1376940408; 1; 2599.999334; 8.666664446666669; 0.33333333333333337; 2097152.0; 111846.13333333333; 161.0; 13.266666666666667; 0.0; 0.2 +1376940708; 1; 2599.999334; 38.13332356533333; 1.4666666666666666; 2097152.0; 518693.6; 0.0; 7.933333333333334; 0.26666666666666666; 0.13333333333333333 +1376941008; 1; 2599.999334; 0.0; 0.0; 2097152.0; 225092.53333333333; 31.8; 3.0; 0.0; 0.0 +1376941308; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 219499.73333333334; 0.0; 1.8; 0.0; 0.0 +1376941608; 1; 2599.999334; 0.0; 0.0; 2097152.0; 116040.8; 0.0; 1.2; 0.0; 0.0 +1376941908; 1; 2599.999334; 0.0; 0.0; 2097152.0; 68504.53333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1376942208; 1; 2599.999334; 0.0; 0.0; 2097152.0; 85282.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1376942508; 1; 2599.999334; 0.0; 0.0; 2097152.0; 106254.13333333333; 0.0; 1.0; 0.0; 0.0 +1376942808; 1; 2599.999334; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1376943108; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 190139.73333333334; 0.06666666666666667; 2.3333333333333335; 0.06666666666666667; 0.4666666666666667 +1376943408; 1; 2599.999334; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1376943708; 1; 2599.999334; 0.0; 0.0; 2097152.0; 131420.53333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1376944008; 1; 2599.999334; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1376944308; 1; 2599.999334; 0.0; 0.0; 2097152.0; 78291.46666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1376944608; 1; 2599.999334; 0.0; 0.0; 2097152.0; 85282.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1376944908; 1; 2599.999334; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1376945208; 1; 2599.999334; 0.0; 0.0; 2097152.0; 62912.0; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1376945508; 1; 2599.999334; 0.0; 0.0; 2097152.0; 82485.86666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1376945808; 1; 2599.999334; 0.0; 0.0; 2097152.0; 130022.4; 0.0; 0.8666666666666667; 0.4; 0.0 +1376946108; 1; 2599.999334; 0.0; 0.0; 2097152.0; 106254.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1376946408; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 92272.8; 0.0; 6.933333333333334; 0.2; 0.13333333333333333 +1376946708; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 195732.26666666666; 0.06666666666666667; 2.6666666666666665; 0.06666666666666667; 0.4666666666666667 +1376947008; 1; 2599.999334; 0.0; 0.0; 2097152.0; 121633.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1376947309; 1; 2599.999334; 0.0; 0.0; 2097152.0; 89476.53333333334; 0.0; 0.8; 0.0; 0.0 +1376947609; 1; 2599.999334; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1376947909; 1; 2599.999334; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 0.6666666666666666; 0.0; 0.0 +1376948209; 1; 2599.999334; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 1.1333333333333333; 0.0; 0.0 +1376948509; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 117439.2; 0.0; 0.7333333333333333; 0.0; 0.0 +1376948809; 1; 2599.999334; 0.0; 0.0; 2097152.0; 106254.13333333333; 0.0; 0.6; 0.0; 0.0 +1376949109; 1; 2599.999334; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 0.8; 0.0; 0.0 +1376949408; 1; 2599.999334; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 1.0666666666666667; 0.0; 0.0 +1376949709; 1; 2599.999334; 0.0; 0.0; 2097152.0; 120235.46666666666; 0.0; 0.6666666666666666; 0.0; 0.0 +1376950009; 1; 2599.999334; 0.0; 0.0; 2097152.0; 110448.53333333334; 0.0; 0.7333333333333333; 0.13333333333333333; 0.0 +1376950309; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 176158.4; 0.06666666666666667; 2.533333333333333; 0.06666666666666667; 0.4666666666666667 +1376950609; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 149594.66666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1376950909; 1; 2599.999334; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1376951209; 1; 2599.999334; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1376951509; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 114642.93333333333; 0.0; 1.2; 0.13333333333333333; 0.13333333333333333 +1376951809; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 111846.66666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1376952109; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 118837.33333333333; 0.0; 0.8; 0.0; 0.0 +1376952409; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 114642.93333333333; 0.0; 7.266666666666667; 0.26666666666666666; 0.13333333333333333 +1376952709; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 110448.53333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1376953009; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 109050.4; 0.0; 0.9333333333333333; 0.13333333333333333; 0.0 +1376953309; 1; 2599.999334; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 0.6666666666666666; 0.13333333333333333; 0.0 +1376953610; 1; 2599.999334; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1376953910; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 137012.26666666666; 0.0; 2.0; 0.06666666666666667; 0.4666666666666667 +1376954210; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 96467.2; 0.0; 0.8; 0.0; 0.0 +1376954510; 1; 2599.999334; 0.0; 0.0; 2097152.0; 114642.93333333333; 0.0; 0.6666666666666666; 0.0; 0.0 +1376954810; 1; 2599.999334; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1376955110; 1; 2599.999334; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1376955410; 1; 2599.999334; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.6; 0.0; 0.0 +1376955710; 1; 2599.999334; 0.0; 0.0; 2097152.0; 123031.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1376956010; 1; 2599.999334; 0.0; 0.0; 2097152.0; 132818.66666666666; 0.0; 1.2; 0.0; 0.0 +1376956310; 1; 2599.999334; 0.0; 0.0; 2097152.0; 118837.33333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1376956610; 1; 2599.999334; 0.0; 0.0; 2097152.0; 121633.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1376956910; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 99263.46666666666; 0.0; 0.6666666666666666; 0.0; 0.0 +1376957210; 1; 2599.999334; 0.0; 0.0; 2097152.0; 89476.53333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1376957510; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 176159.2; 0.06666666666666667; 2.533333333333333; 0.06666666666666667; 0.4666666666666667 +1376957810; 1; 2599.999334; 0.0; 0.0; 2097152.0; 124429.86666666667; 0.0; 1.2; 0.0; 0.0 +1376958110; 1; 2599.999334; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 0.8666666666666667; 8.466666666666667; 0.0 +1376958410; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 93670.93333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1376958710; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 96467.2; 0.06666666666666667; 7.4; 0.26666666666666666; 0.2 +1376959010; 1; 2599.999334; 0.0; 0.0; 2097152.0; 135614.93333333332; 0.0; 0.7333333333333333; 0.0; 0.0 +1376959310; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 95069.06666666667; 0.0; 1.0; 0.0; 0.0 +1376959610; 1; 2599.999334; 0.0; 0.0; 2097152.0; 95069.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1376959910; 1; 2599.999334; 0.0; 0.0; 2097152.0; 114642.93333333333; 0.0; 0.8; 0.0; 0.0 +1376960210; 1; 2599.999334; 0.0; 0.0; 2097152.0; 145401.86666666667; 0.0; 0.6666666666666666; 0.06666666666666667; 0.0 +1376960510; 1; 2599.999334; 0.0; 0.0; 2097152.0; 141206.66666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1376960810; 1; 2599.999334; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 0.8; 0.0; 0.0 +1376961110; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 141205.86666666667; 0.0; 2.2; 0.06666666666666667; 0.4666666666666667 +1376961410; 1; 2599.999334; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 0.6; 0.06666666666666667; 0.0 +1376961710; 1; 2599.999334; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1376962010; 1; 2599.999334; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1376962311; 1; 2599.999334; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1376962611; 1; 2599.999334; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 0.8; 0.0; 0.0 +1376962911; 1; 2599.999334; 0.0; 0.0; 2097152.0; 74097.06666666667; 0.0; 0.6666666666666666; 0.0; 0.0 +1376963211; 1; 2599.999334; 0.0; 0.0; 2097152.0; 110448.53333333334; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1376963511; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 139808.53333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1376963811; 1; 2599.999334; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.6666666666666666; 0.0; 0.0 +1376964111; 1; 2599.999334; 0.0; 0.0; 2097152.0; 89476.53333333334; 0.0; 0.8; 0.0; 0.0 +1376964411; 1; 2599.999334; 0.0; 0.0; 2097152.0; 85282.13333333333; 0.0; 0.8; 0.0; 0.0 +1376964711; 1; 2599.999334; 13.866663114666666; 0.5333333333333333; 2097152.0; 153789.86666666667; 0.0; 13.4; 0.06666666666666667; 0.4666666666666667 +1376965011; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 138410.4; 0.0; 0.6666666666666666; 0.0; 0.0 +1376965311; 1; 2599.999334; 0.0; 0.0; 2097152.0; 128623.46666666666; 0.0; 0.6; 0.0; 0.0 +1376965611; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 106254.13333333333; 0.0; 6.933333333333334; 0.26666666666666666; 0.13333333333333333 +1376965911; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 100661.6; 0.0; 0.7333333333333333; 0.0; 0.0 +1376966211; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 103457.86666666667; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1376966511; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 107652.26666666666; 0.0; 1.0; 0.0; 0.0 +1376966811; 1; 2599.999334; 0.0; 0.0; 2097152.0; 106254.13333333333; 0.0; 0.6666666666666666; 0.0; 0.0 +1376967111; 1; 2599.999334; 0.0; 0.0; 2097152.0; 86680.26666666666; 0.0; 0.6; 0.0; 0.0 +1376967411; 1; 2599.999334; 0.0; 0.0; 2097152.0; 76893.33333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1376967711; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 83884.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1376968011; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 131419.46666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1376968311; 1; 2599.999334; 10.399997336000002; 0.4; 2097152.0; 162177.86666666667; 0.06666666666666667; 2.1333333333333333; 0.06666666666666667; 0.4666666666666667 +1376968611; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 88078.4; 0.06666666666666667; 0.6666666666666666; 0.0; 0.0 +1376968911; 1; 2599.999334; 0.0; 0.0; 2097152.0; 106254.13333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1376969211; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 88078.4; 0.0; 0.8; 0.0; 0.0 +1376969511; 1; 2599.999334; 0.0; 0.0; 2097152.0; 81087.73333333334; 0.0; 0.6; 0.06666666666666667; 0.0 +1376969811; 1; 2599.999334; 0.0; 0.0; 2097152.0; 85282.13333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1376970111; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 104856.0; 0.0; 1.0; 0.0; 0.0 +1376970411; 1; 2599.999334; 0.0; 0.0; 2097152.0; 130022.4; 0.0; 1.2; 0.0; 0.0 +1376970711; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 118837.33333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1376971011; 1; 2599.999334; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1376971311; 1; 2599.999334; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 0.6666666666666666; 0.06666666666666667; 0.0 +1376971611; 1; 2599.999334; 10.399997336000002; 0.4; 2097152.0; 130021.6; 12.733333333333333; 13.466666666666667; 0.3333333333333333; 0.2 +1376971911; 1; 2599.999334; 12.133330225333331; 0.4666666666666666; 2097152.0; 156586.93333333332; 0.06666666666666667; 2.6; 0.06666666666666667; 0.4666666666666667 +1376972211; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 120235.46666666666; 0.0; 0.6666666666666666; 0.0; 0.0 +1376972512; 1; 2599.999334; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1376972812; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 88078.4; 0.0; 0.6666666666666666; 0.0; 0.0 +1376973112; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 156585.6; 0.0; 1.1333333333333333; 0.0; 0.0 +1376973412; 1; 2599.999334; 0.0; 0.0; 2097152.0; 149594.93333333332; 0.0; 0.6; 0.0; 0.0 +1376973712; 1; 2599.999334; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1376974012; 1; 2599.999334; 0.0; 0.0; 2097152.0; 76893.33333333333; 0.0; 0.6; 0.13333333333333333; 0.0 +1376974312; 1; 2599.999626; 28.599995886000002; 1.1; 2097152.0; 62912.0; 0.0; 0.6666666666666666; 0.0; 0.0 +1376974612; 1; 2599.999626; 19.066663923999997; 0.7333333333333333; 2097152.0; 97865.33333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1376974912; 1; 2599.999626; 13.866664671999999; 0.5333333333333333; 2097152.0; 106254.13333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1376975212; 1; 2599.999626; 17.333330840000002; 0.6666666666666667; 2097152.0; 86680.26666666666; 0.0; 0.6; 0.0; 0.0 +1376975512; 1; 2599.999626; 19.066663923999997; 0.7333333333333333; 2097152.0; 191537.86666666667; 0.0; 2.066666666666667; 0.06666666666666667; 0.4666666666666667 +1376975812; 1; 2599.999334; 28.363629098181818; 1.0909090909090908; 2097152.0; 83884.0; 0.0; 0.9; 0.0; 0.0 +1376976112; 1; 2599.999334; 13.866663114666666; 0.5333333333333333; 2097152.0; 113244.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1376976412; 1; 2599.999334; 10.399997336000002; 0.4; 2097152.0; 110448.53333333334; 0.0; 0.6; 0.0; 0.0 +1376976712; 1; 2599.999334; 8.666664446666669; 0.33333333333333337; 2097152.0; 74097.06666666667; 0.0; 0.6; 0.06666666666666667; 0.0 +1376977012; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 93670.93333333333; 0.0; 0.6; 0.06666666666666667; 0.0 +1376977312; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 97865.33333333333; 0.0; 1.1333333333333333; 0.13333333333333333; 0.0 +1376977612; 1; 2599.999334; 13.866663114666666; 0.5333333333333333; 2097152.0; 81087.73333333334; 0.0; 6.8; 0.2; 0.13333333333333333 +1376977912; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 116040.26666666666; 0.0; 0.5333333333333333; 0.0; 0.0 +1376978212; 1; 2599.999334; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1376978512; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 117439.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1376978812; 1; 2599.999334; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 0.6666666666666666; 0.0; 0.0 +1376979112; 1; 2599.999334; 13.866663114666666; 0.5333333333333333; 2097152.0; 185945.6; 0.0; 2.0; 0.06666666666666667; 0.5333333333333333 +1376979412; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 148197.86666666667; 0.0; 1.4666666666666666; 0.0; 0.0 +1376979712; 1; 2599.999334; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.6; 0.0; 0.0 +1376980012; 1; 2599.999334; 0.0; 0.0; 2097152.0; 95069.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1376980312; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 113244.8; 0.0; 0.5333333333333333; 0.26666666666666666; 0.13333333333333333 +1376980612; 1; 2599.999334; 10.399997336000002; 0.4; 2097152.0; 146799.2; 0.0; 2.2; 0.06666666666666667; 0.0 +1376980912; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 125828.0; 0.0; 1.4666666666666666; 0.0; 0.0 +1376981212; 1; 2599.999334; 0.0; 0.0; 2097152.0; 137013.06666666668; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1376981512; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 92272.8; 0.0; 1.0; 0.0; 0.0 +1376981812; 1; 2599.999334; 0.0; 0.0; 2097152.0; 86680.26666666666; 0.0; 0.6; 0.2; 0.0 +1376982112; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 79689.6; 0.0; 0.6; 0.2; 0.0 +1376982412; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 128623.46666666666; 0.0; 0.6666666666666666; 0.0; 0.0 +1376982712; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 150994.4; 0.06666666666666667; 2.8666666666666667; 2.3333333333333335; 0.4666666666666667 +1376983012; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 132818.66666666666; 0.06666666666666667; 6.533333333333333; 0.2; 0.13333333333333333 +1376983312; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 146799.2; 0.0; 0.8; 0.0; 0.0 +1376983612; 1; 2599.999334; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 0.6; 0.0; 0.0 +1376983912; 1; 2599.999334; 0.0; 0.0; 2097152.0; 131420.53333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1376984212; 1; 2599.999334; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 1.0; 0.0; 0.0 +1376984512; 1; 2599.999334; 0.0; 0.0; 2097152.0; 121633.6; 0.0; 0.6666666666666666; 0.0; 0.0 +1376984812; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 111845.86666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1376985112; 1; 2599.999334; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.8; 0.0; 0.0 +1376985412; 1; 2599.999334; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 1.0; 0.06666666666666667; 0.0 +1376985712; 1; 2599.999334; 0.0; 0.0; 2097152.0; 71300.8; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1376986012; 1; 2599.999334; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 0.8666666666666667; 0.26666666666666666; 0.0 +1376986312; 1; 2599.999334; 8.666664446666669; 0.33333333333333337; 2097152.0; 150993.06666666668; 0.0; 2.2666666666666666; 0.13333333333333333; 0.5333333333333333 +1376986612; 1; 2599.999334; 0.0; 0.0; 2097152.0; 144003.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1376986912; 1; 2599.999334; 0.0; 0.0; 2097152.0; 86680.26666666666; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1376987212; 1; 2599.999334; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1376987512; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 113244.8; 1.3333333333333333; 4.0; 0.0; 0.0 +1376987812; 1; 2599.999334; 45.066655122666674; 1.7333333333333334; 2097152.0; 232082.4; 181.86666666666667; 31.2; 0.5333333333333333; 0.4 +1376988112; 1; 2599.999334; 41.59998934400001; 1.6; 2097152.0; 844450.9333333333; 0.0; 5.2; 0.0; 0.0 +1376988412; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 409641.3333333333; 31.8; 3.3333333333333335; 0.0; 0.0 +1376988712; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 244665.33333333334; 0.0; 2.533333333333333; 0.0; 0.0 +1376989012; 1; 2599.999334; 8.666664446666669; 0.33333333333333337; 2097152.0; 184547.46666666667; 0.0; 7.666666666666667; 0.26666666666666666; 0.2 +1376989312; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 174761.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1376989612; 1; 2599.999334; 0.0; 0.0; 2097152.0; 127226.13333333333; 0.0; 1.2666666666666666; 0.6666666666666666; 0.0 +1376989912; 1; 2599.999334; 10.399997336000002; 0.4; 2097152.0; 192935.73333333334; 0.0; 2.2; 0.06666666666666667; 0.4666666666666667 +1376990212; 1; 2599.999334; 0.0; 0.0; 2097152.0; 130021.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1376990512; 1; 2599.999334; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1376990812; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 97865.33333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1376991112; 1; 2599.999334; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1376991412; 1; 2599.999334; 0.0; 0.0; 2097152.0; 135614.93333333332; 0.0; 0.8666666666666667; 0.0; 0.0 +1376991712; 1; 2599.999334; 0.0; 0.0; 2097152.0; 134216.0; 0.0; 0.8; 0.0; 0.0 +1376992012; 1; 2599.999334; 0.0; 0.0; 2097152.0; 128623.2; 0.0; 0.8666666666666667; 0.13333333333333333; 0.0 +1376992312; 1; 2599.999334; 0.0; 0.0; 2097152.0; 142604.8; 0.0; 1.1333333333333333; 0.0; 0.0 +1376992612; 1; 2599.999334; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 1.0; 0.0; 0.0 +1376992913; 1; 2599.999334; 0.0; 0.0; 2097152.0; 75495.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1376993213; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 104855.73333333334; 0.0; 1.5333333333333334; 0.06666666666666667; 0.4666666666666667 +1376993513; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 178955.2; 0.06666666666666667; 1.8; 0.0; 0.0 +1376993813; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 104856.0; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1376994123; 1; 2599.999334; 0.0; 0.0; 2097152.0; 110448.53333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1376994423; 1; 2599.999334; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1376994724; 1; 2599.999334; 0.0; 0.0; 2097152.0; 69902.66666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1376995024; 1; 2599.999334; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 1.1333333333333333; 0.0; 0.0 +1376995324; 1; 2599.999334; 10.399997336000002; 0.4; 2097152.0; 183150.13333333333; 0.06666666666666667; 7.066666666666666; 0.26666666666666666; 0.13333333333333333 +1376995624; 1; 2599.999334; 0.0; 0.0; 2097152.0; 130022.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1376995924; 1; 2599.999334; 0.0; 0.0; 2097152.0; 86680.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1376996224; 1; 2599.999334; 0.0; 0.0; 2097152.0; 83884.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1376996524; 1; 2599.999334; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 1.0666666666666667; 0.0; 0.0 +1376996824; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 117439.2; 0.0; 1.4; 0.06666666666666667; 0.4666666666666667 +1376997124; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 153789.06666666668; 0.0; 1.9333333333333333; 0.0; 0.0 +1376997424; 1; 2599.999334; 0.0; 0.0; 2097152.0; 120235.46666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1376997724; 1; 2599.999334; 0.0; 0.0; 2097152.0; 106254.13333333333; 0.0; 0.8; 0.06666666666666667; 0.0 +1376998024; 1; 2599.999334; 0.0; 0.0; 2097152.0; 89476.53333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1376998324; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 113244.8; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1376998624; 1; 2599.999334; 0.0; 0.0; 2097152.0; 137013.06666666668; 0.0; 0.8666666666666667; 0.0; 0.0 +1376998924; 1; 2599.999334; 0.0; 0.0; 2097152.0; 149596.26666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1376999224; 1; 2599.999334; 0.0; 0.0; 2097152.0; 123031.73333333334; 0.0; 1.3333333333333333; 0.2; 0.0 +1376999524; 1; 2599.999334; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1376999824; 1; 2599.999334; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.8; 0.13333333333333333; 0.0 +1377000124; 1; 2599.999334; 0.0; 0.0; 2097152.0; 110448.53333333334; 0.0; 0.6666666666666666; 0.0; 0.0 +1377000424; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 96467.2; 0.0; 1.0666666666666667; 0.7333333333333333; 0.4666666666666667 +1377000724; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 149595.46666666667; 0.0; 1.6; 0.0; 0.0 +1377001024; 1; 2599.999334; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.13333333333333333; 0.0 +1377001324; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 146799.46666666667; 0.0; 6.666666666666667; 0.2; 0.13333333333333333 +1377001624; 1; 2599.999334; 0.0; 0.0; 2097152.0; 137012.0; 0.0; 0.8; 0.0; 0.0 +1377001924; 1; 2599.999334; 0.0; 0.0; 2097152.0; 89476.53333333334; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1377002224; 1; 2599.999334; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 0.8; 0.0; 0.0 +1377002524; 1; 2599.999334; 0.0; 0.0; 2097152.0; 118837.33333333333; 0.0; 0.8; 0.0; 0.0 +1377002824; 1; 2599.999334; 0.0; 0.0; 2097152.0; 139808.8; 0.0; 0.6666666666666666; 0.13333333333333333; 0.0 +1377003125; 1; 2599.999334; 0.0; 0.0; 2097152.0; 130022.13333333333; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1377003425; 1; 2599.999334; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1377003725; 1; 2599.999334; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377004025; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 124429.33333333333; 0.0; 1.4666666666666666; 0.06666666666666667; 0.4666666666666667 +1377004325; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 159382.13333333333; 0.13333333333333333; 1.8666666666666667; 0.06666666666666667; 0.0 +1377004625; 1; 2599.999334; 0.0; 0.0; 2097152.0; 118837.33333333333; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1377004925; 1; 2599.999334; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 0.7333333333333333; 0.13333333333333333; 0.0 +1377005225; 1; 2599.999334; 0.0; 0.0; 2097152.0; 89476.53333333334; 0.0; 0.8; 0.06666666666666667; 0.0 +1377005525; 1; 2599.999334; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.7333333333333333; 0.0; 0.0 +1377005825; 1; 2599.999334; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 0.8; 0.06666666666666667; 0.0 +1377006125; 1; 2599.999334; 0.0; 0.0; 2097152.0; 95069.06666666667; 0.0; 1.2; 0.0; 0.0 +1377006425; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 78291.46666666666; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1377006725; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 103457.86666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1377007025; 1; 2599.999334; 0.0; 0.0; 2097152.0; 120234.66666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377007325; 1; 2599.999334; 0.0; 0.0; 2097152.0; 137013.06666666668; 0.0; 0.6666666666666666; 0.0; 0.0 +1377007625; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 109050.4; 0.0; 1.6; 0.06666666666666667; 0.4666666666666667 +1377007925; 1; 2599.999334; 15.599996004; 0.6; 2097152.0; 188741.86666666667; 0.06666666666666667; 7.733333333333333; 0.4666666666666667; 0.13333333333333333 +1377008225; 1; 2599.999334; 0.0; 0.0; 2097152.0; 137012.8; 0.0; 0.8; 0.0; 0.0 +1377008525; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 148197.33333333334; 0.0; 11.8; 0.06666666666666667; 0.0 +1377008825; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 96467.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1377009125; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 125828.0; 0.06666666666666667; 0.9333333333333333; 0.13333333333333333; 0.06666666666666667 +1377009425; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 139809.33333333334; 0.0; 0.8666666666666667; 0.13333333333333333; 0.0 +1377009725; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 90874.66666666667; 0.0; 0.8666666666666667; 0.13333333333333333; 0.0 +1377010025; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 120234.93333333333; 0.0; 0.7333333333333333; 1.0; 0.0 +1377010325; 1; 2599.999334; 0.0; 0.0; 2097152.0; 107652.0; 0.0; 1.0; 0.2; 0.0 +1377010625; 1; 2599.999334; 0.0; 0.0; 2097152.0; 121633.6; 0.0; 0.8; 0.13333333333333333; 0.0 +1377010925; 1; 2599.999334; 0.0; 0.0; 2097152.0; 127226.13333333333; 0.0; 0.8666666666666667; 0.13333333333333333; 0.0 +1377011226; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 82485.86666666667; 0.0; 1.1333333333333333; 0.13333333333333333; 0.4666666666666667 +1377011526; 1; 2599.999334; 8.666664446666669; 0.33333333333333337; 2097152.0; 137013.06666666668; 0.0; 1.7333333333333334; 0.0; 0.0 +1377011826; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 121633.6; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1377012126; 1; 2599.999334; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.8; 0.0; 0.0 +1377012426; 1; 2599.999334; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1377012726; 1; 2599.999334; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.6; 0.06666666666666667; 0.0 +1377013026; 1; 2599.999334; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 1.2; 0.06666666666666667; 0.0 +1377013326; 1; 2599.999334; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.8; 0.06666666666666667; 0.0 +1377013626; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.2; 0.0 +1377013926; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 176159.2; 0.0; 6.866666666666666; 0.26666666666666666; 0.13333333333333333 +1377014226; 1; 2599.999334; 0.0; 0.0; 2097152.0; 120235.46666666666; 0.0; 0.6666666666666666; 0.0; 0.0 +1377014526; 1; 2599.999334; 0.0; 0.0; 2097152.0; 145401.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377014825; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 103457.86666666667; 0.0; 1.2; 0.06666666666666667; 0.4666666666666667 +1377015125; 1; 2599.999334; 8.666664446666669; 0.33333333333333337; 2097152.0; 144003.73333333334; 0.0; 1.8666666666666667; 0.0; 0.0 +1377015425; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 134216.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1377015725; 1; 2599.999334; 0.0; 0.0; 2097152.0; 116041.06666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1377016025; 1; 2599.999334; 0.0; 0.0; 2097152.0; 120235.46666666666; 0.0; 0.8; 0.0; 0.0 +1377016325; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 97865.33333333333; 0.0; 0.6; 0.0; 0.0 +1377016625; 1; 2599.999334; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1377016925; 1; 2599.999334; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377017225; 1; 2599.999334; 0.0; 0.0; 2097152.0; 95069.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377017526; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 78291.46666666666; 0.0; 0.8; 0.2; 0.0 +1377017826; 1; 2599.999334; 0.0; 0.0; 2097152.0; 162177.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377018126; 1; 2599.999334; 0.0; 0.0; 2097152.0; 146798.93333333332; 0.0; 0.8; 0.13333333333333333; 0.0 +1377018426; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 107652.26666666666; 0.0; 1.2; 0.0; 0.4666666666666667 +1377018726; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 171964.8; 0.06666666666666667; 1.9333333333333333; 0.0; 0.0 +1377019026; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 110448.53333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1377019326; 1; 2599.999334; 8.666664446666669; 0.33333333333333337; 2097152.0; 110448.53333333334; 0.06666666666666667; 7.0; 1.4; 0.13333333333333333 +1377019626; 1; 2599.999334; 0.0; 0.0; 2097152.0; 95069.06666666667; 0.0; 0.8; 0.0; 0.0 +1377019926; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 104856.0; 0.0; 0.8; 0.0; 0.0 +1377020226; 1; 2599.999334; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 0.6; 0.0; 0.0 +1377020526; 1; 2599.999334; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377020826; 1; 2599.999334; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1377021126; 1; 2599.999334; 0.0; 0.0; 2097152.0; 71300.8; 0.0; 0.8; 0.0; 0.0 +1377021426; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 135614.93333333332; 0.0; 0.8666666666666667; 0.0; 0.0 +1377021726; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 89476.53333333334; 0.0; 0.8; 0.0; 0.0 +1377022026; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 93670.93333333333; 0.0; 1.3333333333333333; 0.0; 0.4666666666666667 +1377022326; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 152391.46666666667; 0.0; 1.6666666666666667; 0.0; 0.0 +1377022626; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 120234.93333333333; 0.0; 0.8; 0.0; 0.0 +1377022926; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 116041.06666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1377023226; 1; 2599.999334; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377023526; 1; 2599.999334; 0.0; 0.0; 2097152.0; 85282.13333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377023826; 1; 2599.999334; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 1.4666666666666666; 0.0; 0.0 +1377024126; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 123031.73333333334; 0.0; 1.0; 0.0; 0.0 +1377024426; 1; 2599.999334; 0.0; 0.0; 2097152.0; 128624.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1377024726; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 121633.06666666667; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1377025026; 1; 2599.999334; 0.0; 0.0; 2097152.0; 89476.53333333334; 0.0; 0.5333333333333333; 0.0; 0.0 +1377025326; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 106254.13333333333; 0.0; 0.7333333333333333; 2.2666666666666666; 0.0 +1377025627; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 83884.0; 0.0; 1.3333333333333333; 0.13333333333333333; 0.4666666666666667 +1377025927; 1; 2599.999334; 12.133330225333331; 0.4666666666666666; 2097152.0; 163576.8; 0.0; 7.6; 0.26666666666666666; 0.13333333333333333 +1377026227; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 142605.6; 0.0; 0.6; 0.06666666666666667; 0.0 +1377026527; 1; 2599.999334; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.6; 0.0; 0.0 +1377026827; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 100661.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1377027127; 1; 2599.999334; 43.33332223333333; 1.6666666666666665; 2097152.0; 510304.5333333333; 161.0; 14.333333333333334; 0.0; 0.2 +1377027427; 1; 2599.999334; 0.0; 0.0; 2097152.0; 223694.4; 0.0; 1.2; 0.0; 0.0 +1377027727; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 222296.8; 31.8; 3.6; 0.0; 0.0 +1377028027; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 109050.4; 0.0; 1.2; 0.0; 0.0 +1377028327; 1; 2599.999334; 0.0; 0.0; 2097152.0; 144003.73333333334; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1377028627; 1; 2599.999334; 0.0; 0.0; 2097152.0; 132818.66666666666; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377028927; 1; 2599.999334; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1377029227; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 93670.93333333333; 0.0; 1.6; 0.06666666666666667; 0.4666666666666667 +1377029527; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 201324.0; 0.06666666666666667; 1.7333333333333334; 0.0; 0.0 +1377029827; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 128624.26666666666; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1377030127; 1; 2599.999334; 0.0; 0.0; 2097152.0; 125828.0; 0.0; 0.8; 0.0; 0.0 +1377030427; 1; 2599.999334; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377030727; 1; 2599.999334; 0.0; 0.0; 2097152.0; 138411.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377031027; 1; 2599.999334; 0.0; 0.0; 2097152.0; 128623.46666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1377031327; 1; 2599.999334; 8.666664446666669; 0.33333333333333337; 2097152.0; 124428.8; 12.8; 13.4; 0.3333333333333333; 0.13333333333333333 +1377031627; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 145401.06666666668; 0.0; 1.2666666666666666; 0.0; 0.0 +1377031927; 1; 2599.999334; 0.0; 0.0; 2097152.0; 146799.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377032227; 1; 2599.999334; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377032527; 1; 2599.999334; 0.0; 0.0; 2097152.0; 95069.06666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1377032827; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 99263.46666666666; 0.0; 1.3333333333333333; 0.06666666666666667; 0.4666666666666667 +1377033127; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 160780.53333333333; 0.0; 1.6666666666666667; 0.0; 0.0 +1377033427; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 132817.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377033727; 1; 2599.999334; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1377034027; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 111846.66666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377034327; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 109050.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377034627; 1; 2599.999334; 0.0; 0.0; 2097152.0; 131420.53333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1377034928; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 103457.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377035228; 1; 2599.999334; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 1.0666666666666667; 0.13333333333333333; 0.0 +1377035528; 1; 2599.999334; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377035828; 1; 2599.999334; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 0.8; 0.0; 0.0 +1377036128; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 103457.86666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1377036428; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 162177.86666666667; 0.0; 1.3333333333333333; 0.06666666666666667; 0.4666666666666667 +1377036728; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 233480.8; 0.13333333333333333; 2.066666666666667; 0.0; 0.0 +1377037028; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 141207.46666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1377037328; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 132817.86666666667; 0.0; 0.6666666666666666; 0.0; 0.0 +1377037628; 1; 2599.999334; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 0.5333333333333333; 0.0; 0.0 +1377037928; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 120234.66666666667; 0.0; 1.6; 0.13333333333333333; 0.13333333333333333 +1377038229; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 96467.2; 0.0; 7.133333333333334; 0.26666666666666666; 0.13333333333333333 +1377038529; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 103457.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377038829; 1; 2599.999334; 0.0; 0.0; 2097152.0; 76893.33333333333; 0.0; 0.6666666666666666; 0.0; 0.0 +1377039129; 1; 2599.999334; 0.0; 0.0; 2097152.0; 81087.73333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1377039429; 1; 2599.999334; 50.266653790666666; 1.9333333333333333; 2097152.0; 225093.06666666668; 161.0; 21.333333333333332; 0.06666666666666667; 0.4 +1377039729; 1; 2599.999334; 13.866663114666666; 0.5333333333333333; 2097152.0; 724214.9333333333; 0.0; 2.2666666666666666; 0.0; 0.0 +1377040029; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 320164.0; 31.8; 4.0; 0.06666666666666667; 0.4666666666666667 +1377040329; 1; 2599.999334; 8.666664446666669; 0.33333333333333337; 2097152.0; 229286.4; 0.06666666666666667; 2.2666666666666666; 0.0; 0.0 +1377040629; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 149595.46666666667; 0.0; 1.8; 0.0; 0.0 +1377040929; 1; 2599.999334; 0.0; 0.0; 2097152.0; 135614.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1377041229; 1; 2599.999334; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 0.8; 0.0; 0.0 +1377041529; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 120234.66666666667; 0.06666666666666667; 0.9333333333333333; 0.0; 0.0 +1377041829; 1; 2599.999334; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 0.8; 0.0; 0.0 +1377042129; 1; 2599.999334; 0.0; 0.0; 2097152.0; 103457.06666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1377042429; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 90874.66666666667; 0.0; 1.9333333333333333; 0.0; 0.0 +1377042729; 1; 2599.999334; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377043029; 1; 2599.999334; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.6666666666666666; 0.0; 0.0 +1377043329; 1; 2599.999334; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.6; 0.0; 0.0 +1377043629; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 113244.8; 0.0; 1.5333333333333334; 0.06666666666666667; 0.4666666666666667 +1377043929; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 153790.66666666666; 0.0; 1.9333333333333333; 0.0; 0.0 +1377044229; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 128624.26666666666; 0.0; 0.8; 0.0; 0.0 +1377044529; 1; 2599.999334; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.6666666666666666; 0.0; 0.0 +1377044829; 1; 2599.999334; 0.0; 0.0; 2097152.0; 114642.93333333333; 0.0; 1.0; 0.0; 0.0 +1377045129; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 88078.4; 0.0; 1.4; 0.0; 0.0 +1377045429; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 88078.4; 0.0; 6.933333333333334; 0.2; 0.13333333333333333 +1377045729; 1; 2599.999334; 0.0; 0.0; 2097152.0; 79689.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1377046029; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 92272.8; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1377046329; 1; 2599.999334; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 1.0; 0.0; 0.0 +1377046629; 1; 2599.999334; 0.0; 0.0; 2097152.0; 125828.0; 0.0; 0.6; 0.0; 0.0 +1377046929; 1; 2599.999334; 0.0; 0.0; 2097152.0; 120235.46666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1377047230; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 127225.86666666667; 0.0; 1.2666666666666666; 0.06666666666666667; 0.4666666666666667 +1377047529; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 205517.86666666667; 0.0; 2.1333333333333333; 0.0; 0.0 +1377047829; 1; 2599.999334; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377048129; 1; 2599.999334; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8; 0.0; 0.0 +1377048429; 1; 2599.999334; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.6; 0.0; 0.0 +1377048729; 1; 2599.999334; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8; 0.0; 0.0 +1377049029; 1; 2599.999334; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8; 0.0; 0.0 +1377049329; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 71300.8; 0.0; 0.8; 0.0; 0.0 +1377049629; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 128624.26666666666; 0.0; 0.8; 0.0; 0.0 +1377049929; 1; 2599.999334; 0.0; 0.0; 2097152.0; 114642.13333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377050229; 1; 2599.999334; 0.0; 0.0; 2097152.0; 76893.33333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1377050529; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 85282.13333333333; 0.0; 1.0; 0.0; 0.0 +1377050829; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 72698.93333333333; 0.0; 1.1333333333333333; 0.06666666666666667; 0.4666666666666667 +1377051129; 1; 2599.999334; 8.666664446666669; 0.33333333333333337; 2097152.0; 167770.4; 0.0; 1.4666666666666666; 0.0; 0.0 +1377051429; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 100661.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1377051729; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 95069.06666666667; 0.0; 0.8; 0.0; 0.0 +1377052029; 1; 2599.999334; 12.133330225333331; 0.4666666666666666; 2097152.0; 109050.4; 0.0; 17.933333333333334; 0.26666666666666666; 0.13333333333333333 +1377052329; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 71300.8; 0.0; 0.8; 0.0; 0.0 +1377052629; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 118837.33333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377052929; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 109050.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1377053229; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 135614.13333333333; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1377053529; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 109050.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1377053830; 1; 2599.999334; 0.0; 0.0; 2097152.0; 114642.93333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377054130; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 83884.0; 0.0; 0.7333333333333333; 0.0; 0.0 +1377054430; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 109050.4; 0.0; 1.7333333333333334; 0.06666666666666667; 0.4666666666666667 +1377054730; 1; 2599.999334; 13.866663114666666; 0.5333333333333333; 2097152.0; 146799.2; 0.06666666666666667; 1.6666666666666667; 0.0; 0.0 +1377055030; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 113244.8; 0.0; 0.6666666666666666; 0.0; 0.0 +1377055330; 1; 2599.999334; 0.0; 0.0; 2097152.0; 125828.0; 0.0; 0.6666666666666666; 0.0; 0.0 +1377055630; 1; 2599.999334; 0.0; 0.0; 2097152.0; 121633.6; 0.0; 0.6666666666666666; 0.0; 0.0 +1377055930; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 95069.06666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1377056230; 1; 2599.999334; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.6; 0.0; 0.0 +1377056530; 1; 2599.999334; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1377056830; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 106254.13333333333; 0.0; 1.0; 0.0; 0.0 +1377057130; 1; 2599.999334; 0.0; 0.0; 2097152.0; 104855.2; 0.0; 0.7333333333333333; 0.0; 0.0 +1377057430; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 104856.0; 0.0; 0.8; 0.0; 0.0 +1377057730; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 88078.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1377058030; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 109050.4; 0.06666666666666667; 7.2; 0.3333333333333333; 0.6666666666666666 +1377058330; 1; 2599.999334; 13.866663114666666; 0.5333333333333333; 2097152.0; 176158.4; 0.0; 1.6666666666666667; 0.0; 0.0 +1377058630; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 99263.46666666666; 0.0; 1.0; 0.06666666666666667; 0.0 +1377058930; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 111846.66666666667; 0.0; 0.6; 0.0; 0.0 +1377059230; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 117439.2; 0.0; 0.6666666666666666; 0.0; 0.0 +1377059530; 1; 2599.999334; 0.0; 0.0; 2097152.0; 146799.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1377059830; 1; 2599.999334; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 0.6666666666666666; 0.0; 0.0 +1377060130; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 124429.86666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1377060430; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 114642.93333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377060730; 1; 2599.999334; 0.0; 0.0; 2097152.0; 114642.93333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377061030; 1; 2599.999334; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.7333333333333333; 0.0; 0.0 +1377061330; 1; 2599.999334; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1377061631; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 89476.53333333334; 0.0; 1.2; 0.06666666666666667; 0.4666666666666667 +1377061931; 1; 2599.999334; 10.399997336000002; 0.4; 2097152.0; 150994.4; 0.0; 1.5333333333333334; 0.0; 0.0 +1377062231; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 130021.6; 0.0; 0.8; 0.0; 0.0 +1377062531; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 109049.6; 0.0; 0.6666666666666666; 0.0; 0.0 +1377062831; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 121633.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1377063131; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 86680.26666666666; 0.0; 0.5333333333333333; 0.0; 0.0 +1377063431; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 114642.93333333333; 0.0; 0.6; 0.0; 0.0 +1377063731; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 130022.4; 0.0; 0.6666666666666666; 0.0; 0.0 +1377064031; 1; 2599.999334; 8.666664446666669; 0.33333333333333337; 2097152.0; 92272.8; 0.0; 6.733333333333333; 0.2; 0.13333333333333333 +1377064331; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 155187.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1377064631; 1; 2599.999334; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 0.6666666666666666; 0.0; 0.0 +1377064931; 1; 2599.999334; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.6666666666666666; 0.0; 0.0 +1377065231; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 111846.66666666667; 0.0; 1.4666666666666666; 0.06666666666666667; 0.4666666666666667 +1377065531; 1; 2599.999334; 10.399997336000002; 0.4; 2097152.0; 180353.6; 0.26666666666666666; 1.7333333333333334; 0.0; 0.0 +1377065831; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 138411.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1377066131; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 110448.53333333334; 0.0; 0.6; 0.0; 0.0 +1377066431; 1; 2599.999334; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.6; 0.0; 0.0 +1377066731; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 96467.2; 0.06666666666666667; 0.8666666666666667; 0.06666666666666667; 0.06666666666666667 +1377067031; 1; 2599.999334; 13.866663114666666; 0.5333333333333333; 2097152.0; 100661.6; 0.0; 1.9333333333333333; 0.0; 0.0 +1377067331; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 50328.8; 0.0; 1.4; 0.0; 0.0 +1377067631; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 78291.46666666666; 0.0; 0.6666666666666666; 0.0; 0.0 +1377067931; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 128624.26666666666; 0.0; 0.8; 0.2; 0.0 +1377068231; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 114642.93333333333; 0.0; 0.6; 0.2; 0.0 +1377068531; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 114642.93333333333; 0.0; 1.7333333333333334; 0.06666666666666667; 0.0 +1377068831; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 117439.2; 0.0; 1.0666666666666667; 0.06666666666666667; 0.4666666666666667 +1377069131; 1; 2599.999334; 13.866663114666666; 0.5333333333333333; 2097152.0; 146798.4; 0.0; 1.7333333333333334; 0.0; 0.0 +1377069432; 1; 2599.999334; 8.666664446666669; 0.33333333333333337; 2097152.0; 93670.93333333333; 0.0; 0.6; 0.06666666666666667; 0.0 +1377069732; 1; 2599.999334; 10.399997336000002; 0.4; 2097152.0; 113244.8; 0.0; 6.866666666666666; 0.6; 0.13333333333333333 +1377070032; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 92272.8; 0.0; 0.6; 0.0; 0.0 +1377070332; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 100661.6; 0.0; 0.7333333333333333; 0.0; 0.0 +1377070632; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 106254.13333333333; 0.0; 0.6; 0.0; 0.0 +1377070932; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 117439.2; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1377071232; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 114642.93333333333; 0.0; 1.2; 0.0; 0.0 +1377071532; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 117439.2; 0.0; 0.8; 0.0; 0.0 +1377071832; 1; 2599.999334; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.6; 0.0; 0.0 +1377072132; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 121633.6; 0.0; 0.6; 0.0; 0.0 +1377072432; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 89476.53333333334; 0.0; 1.2; 0.13333333333333333; 0.4666666666666667 +1377072732; 1; 2599.999334; 13.866663114666666; 0.5333333333333333; 2097152.0; 155188.0; 0.0; 2.2666666666666666; 0.0; 0.0 +1377073032; 1; 2599.999334; 8.666664446666669; 0.33333333333333337; 2097152.0; 121633.6; 0.0; 0.6666666666666666; 0.0; 0.0 +1377073332; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 117439.2; 0.0; 0.6; 0.06666666666666667; 0.0 +1377073632; 1; 2599.999334; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 0.6; 0.0; 0.0 +1377073932; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 92272.8; 0.0; 1.0666666666666667; 0.0; 0.0 +1377074232; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 71300.8; 0.0; 0.6; 0.0; 0.0 +1377074532; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 86680.26666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1377074832; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 109050.4; 0.0; 0.6666666666666666; 0.0; 0.0 +1377075132; 1; 2599.999334; 10.399997336000002; 0.4; 2097152.0; 117439.2; 0.0; 0.8; 0.0; 0.0 +1377075432; 1; 2599.999334; 13.866663114666666; 0.5333333333333333; 2097152.0; 79689.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1377075732; 1; 2599.999334; 13.866663114666666; 0.5333333333333333; 2097152.0; 71300.8; 0.0; 0.8; 0.0; 0.0 +1377076032; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 109050.13333333333; 0.0; 1.5333333333333334; 0.06666666666666667; 0.4666666666666667 +1377076332; 1; 2599.999334; 10.399997336000002; 0.4; 2097152.0; 176159.2; 0.0; 1.6666666666666667; 0.0; 0.0 +1377076632; 1; 2599.999334; 10.399997336000002; 0.4; 2097152.0; 130022.4; 0.0; 1.0666666666666667; 0.0; 0.0 +1377076932; 1; 2599.999334; 10.399997336000002; 0.4; 2097152.0; 141207.46666666667; 0.0; 6.8; 0.2; 0.13333333333333333 +1377077232; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 109050.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1377077532; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 90874.66666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377077832; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 99263.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377078132; 1; 2599.999334; 17.333328893333338; 0.6666666666666667; 2097152.0; 152390.13333333333; 20.2; 2.3333333333333335; 0.0; 0.06666666666666667 +1377078432; 1; 2599.999334; 24.266660450666663; 0.9333333333333332; 2097152.0; 138410.4; 0.0; 4.133333333333334; 0.0; 0.0 +1377078732; 1; 2599.999334; 13.866663114666666; 0.5333333333333333; 2097152.0; 125827.2; 0.2; 2.466666666666667; 0.0; 0.0 +1377079032; 1; 2599.999334; 15.599996004; 0.6; 2097152.0; 76893.33333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377079332; 1; 2599.999334; 10.399997336000002; 0.4; 2097152.0; 92272.8; 0.0; 1.0666666666666667; 0.0; 0.0 +1377079632; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 96467.2; 0.0; 1.7333333333333334; 0.06666666666666667; 0.4666666666666667 +1377079932; 1; 2599.999334; 12.133330225333331; 0.4666666666666666; 2097152.0; 150993.33333333334; 0.0; 1.4; 0.0; 0.0 +1377080232; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 102059.73333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1377080532; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 109050.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377080832; 1; 2599.999334; 10.399997336000002; 0.4; 2097152.0; 92272.8; 0.0; 1.7333333333333334; 0.0; 0.0 +1377081132; 1; 2599.999334; 8.666664446666669; 0.33333333333333337; 2097152.0; 90874.66666666667; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377081432; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 120235.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377081732; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 111846.66666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377082032; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 89476.53333333334; 0.0; 1.0; 0.0; 0.0 +1377082332; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 137013.06666666668; 0.0; 1.0666666666666667; 0.0; 0.0 +1377082632; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 95069.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377082932; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 111846.66666666667; 0.0; 0.8; 0.0; 0.0 +1377083232; 1; 2599.999334; 10.399997336000002; 0.4; 2097152.0; 124429.33333333333; 0.0; 7.533333333333333; 0.26666666666666666; 0.6666666666666666 +1377083532; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 206916.26666666666; 0.0; 1.8; 0.0; 0.0 +1377083832; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 144003.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377084132; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 128624.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377084432; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 117439.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1377084732; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 125828.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377085032; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 116041.06666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1377085332; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 124429.86666666667; 0.0; 1.0; 0.0; 0.0 +1377085632; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 75495.2; 0.0; 1.2; 0.0; 0.0 +1377085932; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 89476.53333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377086232; 1; 2599.999334; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 1.0; 0.0; 0.0 +1377086532; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 124429.86666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1377086832; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 132818.66666666666; 0.0; 1.6; 0.06666666666666667; 0.4666666666666667 +1377087132; 1; 2599.999334; 12.133330225333331; 0.4666666666666666; 2097152.0; 146799.2; 0.0; 1.6; 0.0; 0.0 +1377087432; 1; 2599.999334; 10.399997336000002; 0.4; 2097152.0; 104856.0; 0.0; 0.8; 0.2; 0.0 +1377087732; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 99263.46666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1377088032; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 82485.86666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1377088332; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 109050.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377088633; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 109050.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1377088933; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 103457.86666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1377089233; 1; 2599.999334; 15.599996004; 0.6; 2097152.0; 131419.73333333334; 12.733333333333333; 13.6; 0.4; 0.13333333333333333 +1377089533; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 146799.2; 0.0; 1.1333333333333333; 0.0; 0.0 +1377089833; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 113244.8; 0.0; 0.7333333333333333; 0.0; 0.0 +1377090133; 1; 2599.999334; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.8; 0.0; 0.0 +1377090433; 1; 2599.999334; 12.133330225333331; 0.4666666666666666; 2097152.0; 96467.2; 0.0; 1.5333333333333334; 0.06666666666666667; 0.4666666666666667 +1377090733; 1; 2599.999334; 20.799994672000004; 0.8; 2097152.0; 169169.33333333334; 0.06666666666666667; 1.4; 0.06666666666666667; 0.0 +1377091033; 1; 2599.999334; 10.399997336000002; 0.4; 2097152.0; 104856.0; 0.0; 0.6666666666666666; 0.0; 0.0 +1377091333; 1; 2599.999334; 10.399997336000002; 0.4; 2097152.0; 107652.26666666666; 0.0; 0.6; 0.0; 0.0 +1377091633; 1; 2599.999334; 12.133330225333331; 0.4666666666666666; 2097152.0; 92272.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377091933; 1; 2599.999334; 8.666664446666669; 0.33333333333333337; 2097152.0; 107652.26666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1377092233; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 124429.86666666667; 0.0; 0.6; 0.0; 0.0 +1377092533; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 116041.06666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1377092833; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 93670.93333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377093133; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 69902.66666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1377093433; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 113244.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1377093733; 1; 2599.999334; 62.399984016; 2.4; 2097152.0; 388670.93333333335; 161.06666666666666; 21.533333333333335; 0.13333333333333333; 0.4 +1377094033; 1; 2599.999334; 8.666664446666669; 0.33333333333333337; 2097152.0; 581607.7333333333; 0.0; 4.2; 0.06666666666666667; 0.5333333333333333 +1377094333; 1; 2599.999334; 10.399997336000002; 0.4; 2097152.0; 320163.4666666667; 31.8; 4.333333333333333; 0.0; 0.0 +1377094633; 1; 2599.999334; 15.599996004; 0.6; 2097152.0; 216703.2; 0.06666666666666667; 8.533333333333333; 0.26666666666666666; 0.13333333333333333 +1377094933; 1; 2599.999334; 8.666664446666669; 0.33333333333333337; 2097152.0; 142605.6; 0.0; 1.3333333333333333; 0.0; 0.0 +1377095233; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 142604.53333333333; 0.0; 0.8; 0.0; 0.0 +1377095533; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 150993.6; 0.06666666666666667; 11.866666666666667; 0.06666666666666667; 0.06666666666666667 +1377095833; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 156586.13333333333; 0.0; 11.733333333333333; 0.0; 0.0 +1377096133; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 116041.06666666667; 0.0; 1.2666666666666666; 0.0; 0.0 +1377096433; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 107652.26666666666; 0.0; 0.8; 0.0; 0.0 +1377096733; 1; 2599.999334; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377097033; 1; 2599.999334; 0.0; 0.0; 2097152.0; 118837.33333333333; 0.0; 0.6666666666666666; 0.0; 0.0 +1377097333; 1; 2599.999334; 0.0; 0.0; 2097152.0; 95069.06666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377097633; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 102059.46666666666; 0.0; 1.3333333333333333; 0.06666666666666667; 0.5333333333333333 +1377097933; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 215305.06666666668; 0.0; 1.8; 0.0; 0.0 +1377098234; 1; 2599.999334; 0.0; 0.0; 2097152.0; 120235.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377098534; 1; 2599.999334; 0.0; 0.0; 2097152.0; 124429.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377098834; 1; 2599.999334; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 1.0; 0.0; 0.0 +1377099134; 1; 2599.999334; 0.0; 0.0; 2097152.0; 89476.53333333334; 0.0; 0.6; 0.0; 0.0 +1377099434; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 76893.33333333333; 0.0; 0.8; 0.13333333333333333; 0.0 +1377099734; 1; 2599.999334; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377100034; 1; 2599.999334; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 1.0; 0.0; 0.0 +1377100334; 1; 2599.999334; 0.0; 0.0; 2097152.0; 141206.66666666666; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1377100634; 1; 2599.999334; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 0.6; 0.06666666666666667; 0.0 +1377100934; 1; 2599.999334; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.6; 3.6666666666666665; 0.0 +1377101234; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 102059.73333333334; 0.0; 1.2666666666666666; 0.2; 0.4666666666666667 +1377101534; 1; 2599.999334; 15.599996004; 0.6; 2097152.0; 156586.13333333333; 0.06666666666666667; 8.2; 0.26666666666666666; 0.13333333333333333 +1377101834; 1; 2599.999334; 0.0; 0.0; 2097152.0; 120235.46666666666; 0.0; 0.6666666666666666; 0.0; 0.0 +1377102134; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 110448.53333333334; 0.0; 0.8; 0.06666666666666667; 0.0 +1377102434; 1; 2599.999334; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.6; 0.0; 0.0 +1377102734; 1; 2599.999334; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.7333333333333333; 0.0; 0.0 +1377103034; 1; 2599.999334; 0.0; 0.0; 2097152.0; 134216.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1377103334; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 92272.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1377103634; 1; 2599.999334; 0.0; 0.0; 2097152.0; 82485.86666666667; 0.0; 0.6666666666666666; 0.06666666666666667; 0.0 +1377103934; 1; 2599.999334; 0.0; 0.0; 2097152.0; 54523.2; 0.0; 0.7333333333333333; 0.0; 0.0 +1377104234; 1; 2599.999334; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377104534; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 88078.4; 0.06666666666666667; 1.1333333333333333; 0.0; 0.0 +1377104834; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 92272.8; 0.0; 1.1333333333333333; 0.13333333333333333; 0.4666666666666667 +1377105134; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 155187.2; 0.0; 0.8; 0.0; 0.0 +1377105434; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 117439.2; 0.0; 0.8; 0.06666666666666667; 0.0 +1377105735; 1; 2599.999334; 0.0; 0.0; 2097152.0; 106254.13333333333; 0.0; 1.0; 0.0; 0.0 +1377106035; 1; 2599.999334; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377106335; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 54523.2; 0.0; 0.6; 0.06666666666666667; 0.0 +1377106635; 1; 2599.999334; 0.0; 0.0; 2097152.0; 64310.13333333333; 0.0; 0.8; 0.0; 0.0 +1377106935; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 111846.66666666667; 0.0; 0.8; 0.0; 0.0 +1377107235; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 74097.06666666667; 0.0; 6.8; 0.26666666666666666; 0.2 +1377107535; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 111846.66666666667; 0.0; 0.6666666666666666; 0.0; 0.0 +1377107835; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 104856.0; 0.0; 0.6666666666666666; 0.0; 0.0 +1377108135; 1; 2599.999334; 0.0; 0.0; 2097152.0; 95069.06666666667; 0.0; 0.6666666666666666; 0.06666666666666667; 0.0 +1377108435; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 89476.26666666666; 0.0; 1.4; 0.06666666666666667; 0.4666666666666667 +1377108735; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 187342.66666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377109035; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 96467.2; 0.0; 0.6666666666666666; 0.0; 0.0 +1377109335; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 92272.8; 0.0; 0.6666666666666666; 0.06666666666666667; 0.0 +1377109635; 1; 2599.999334; 0.0; 0.0; 2097152.0; 78291.46666666666; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1377109935; 1; 2599.999334; 0.0; 0.0; 2097152.0; 131420.53333333333; 0.0; 1.0; 0.06666666666666667; 0.0 +1377110235; 1; 2599.999334; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.7333333333333333; 0.0; 0.0 +1377110535; 1; 2599.999334; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.8; 0.0; 0.0 +1377110835; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 135614.93333333332; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377111135; 1; 2599.999334; 0.0; 0.0; 2097152.0; 132817.86666666667; 0.0; 1.0; 0.0; 0.0 +1377111435; 1; 2599.999334; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.6; 0.0; 0.0 +1377111735; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 92272.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1377112036; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 75495.2; 0.0; 2.3333333333333335; 0.13333333333333333; 0.4666666666666667 +1377112336; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 125828.0; 0.0; 1.8; 0.0; 0.0 +1377112636; 1; 2599.999334; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377112935; 1; 2599.999334; 0.0; 0.0; 2097152.0; 89476.53333333334; 0.0; 1.5333333333333334; 0.06666666666666667; 0.0 +1377113235; 1; 2599.999334; 0.0; 0.0; 2097152.0; 68504.53333333334; 0.0; 0.6666666666666666; 0.0; 0.0 +1377113535; 1; 2599.999334; 46.799988012; 1.8; 2097152.0; 466963.73333333334; 161.2; 20.266666666666666; 0.2; 0.3333333333333333 +1377113835; 1; 2599.999334; 0.0; 0.0; 2097152.0; 255851.2; 0.0; 1.3333333333333333; 0.0; 0.0 +1377114135; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 167771.46666666667; 31.8; 3.6666666666666665; 0.0; 0.0 +1377114435; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 146799.2; 0.0; 1.1333333333333333; 0.0; 0.0 +1377114735; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 134215.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377115035; 1; 2599.999334; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1377115335; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 47532.53333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1377115635; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 74097.06666666667; 0.0; 2.1333333333333333; 0.06666666666666667; 0.4666666666666667 +1377115935; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 146799.2; 0.0; 1.0; 0.0; 0.0 +1377116235; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 135614.13333333333; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377116535; 1; 2599.999334; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377116835; 1; 2599.999334; 0.0; 0.0; 2097152.0; 79689.6; 0.0; 1.2; 0.0; 0.0 +1377117135; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 103457.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377117435; 1; 2599.999334; 0.0; 0.0; 2097152.0; 85282.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377117735; 1; 2599.999334; 0.0; 0.0; 2097152.0; 95069.06666666667; 0.0; 1.0; 0.0; 0.0 +1377118036; 1; 2599.999334; 0.0; 0.0; 2097152.0; 89476.53333333334; 0.0; 1.0666666666666667; 0.0; 0.0 +1377118336; 1; 2599.999334; 0.0; 0.0; 2097152.0; 114642.93333333333; 0.0; 0.8; 0.0; 0.0 +1377118636; 1; 2599.999334; 0.0; 0.0; 2097152.0; 130022.4; 0.0; 0.8666666666666667; 0.13333333333333333; 0.0 +1377118936; 1; 2599.999334; 0.0; 0.0; 2097152.0; 116041.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377119236; 1; 2599.999334; 8.666664446666669; 0.33333333333333337; 2097152.0; 83884.0; 0.0; 9.066666666666666; 0.4; 0.6 +1377119536; 1; 2599.999334; 8.666664446666669; 0.33333333333333337; 2097152.0; 138411.2; 0.0; 1.2666666666666666; 0.0; 0.0 +1377119836; 1; 2599.999334; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1377120136; 1; 2599.999334; 0.0; 0.0; 2097152.0; 120235.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377120436; 1; 2599.999334; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 0.8666666666666667; 0.2; 0.0 +1377120736; 1; 2599.999334; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1377121036; 1; 2599.999334; 0.0; 0.0; 2097152.0; 152392.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1377121336; 1; 2599.999334; 0.0; 0.0; 2097152.0; 111846.4; 0.0; 1.0; 0.0; 0.0 +1377121636; 1; 2599.999334; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377121936; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 92272.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1377122236; 1; 2599.999334; 0.0; 0.0; 2097152.0; 85282.13333333333; 0.0; 1.0; 0.0; 0.0 +1377122537; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 109049.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1377122837; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 111846.66666666667; 0.0; 2.1333333333333333; 0.2; 0.4666666666666667 +1377123137; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 185945.33333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377123437; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377123737; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 90874.66666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1377124037; 1; 2599.999334; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1377124337; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 99263.46666666666; 0.06666666666666667; 1.6; 0.06666666666666667; 0.13333333333333333 +1377124637; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 144002.93333333332; 0.0; 0.8; 0.0; 0.0 +1377124937; 1; 2599.999334; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 1.1333333333333333; 0.0; 0.0 +1377125237; 1; 2599.999334; 20.799994672000004; 0.8; 2097152.0; 135614.13333333333; 169.73333333333332; 186.0; 0.2; 0.13333333333333333 +1377125537; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 260044.53333333333; 0.0; 0.8; 0.0; 0.0 +1377125837; 1; 2599.999334; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1377126137; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 81087.73333333334; 0.7333333333333333; 1.2; 0.0; 0.0 +1377126437; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 93670.93333333333; 0.0; 2.3333333333333335; 0.06666666666666667; 0.4666666666666667 +1377126737; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 176158.93333333332; 0.0; 0.8666666666666667; 0.0; 0.0 +1377127037; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 102059.73333333334; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1377127337; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 104856.0; 0.0; 0.8; 0.0; 0.0 +1377127637; 1; 2599.999334; 0.0; 0.0; 2097152.0; 81087.73333333334; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377127937; 1; 2599.999334; 0.0; 0.0; 2097152.0; 75495.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1377128237; 1; 2599.999334; 0.0; 0.0; 2097152.0; 79689.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1377128537; 1; 2599.999334; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377128837; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 106254.13333333333; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1377129137; 1; 2599.999334; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 1.1333333333333333; 0.0; 0.0 +1377129437; 1; 2599.999334; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1377129737; 1; 2599.999334; 0.0; 0.0; 2097152.0; 95069.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377130037; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 97864.8; 0.0; 2.1333333333333333; 0.06666666666666667; 0.4666666666666667 +1377130337; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 215304.53333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1377130637; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 167770.93333333332; 0.0; 1.1333333333333333; 0.0; 0.0 +1377130937; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 92272.8; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1377131237; 1; 2599.999334; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8; 0.0; 0.0 +1377131537; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 83884.0; 0.0; 1.1333333333333333; 0.0; 0.0 +1377131837; 1; 2599.999334; 0.0; 0.0; 2097152.0; 141207.46666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377132138; 1; 2599.999334; 0.0; 0.0; 2097152.0; 106254.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377132438; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 113244.8; 0.0; 6.8; 0.2; 0.13333333333333333 +1377132738; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 111846.66666666667; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377133038; 1; 2599.999334; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1377133338; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 99263.46666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1377133638; 1; 2599.999334; 8.666664446666669; 0.33333333333333337; 2097152.0; 90874.66666666667; 0.0; 1.7333333333333334; 0.06666666666666667; 0.4666666666666667 +1377133938; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 135614.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377134238; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 76893.33333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377134538; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 107652.26666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1377134838; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 97865.33333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1377135138; 1; 2599.999334; 8.666664446666669; 0.33333333333333337; 2097152.0; 78291.46666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1377135438; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 68504.53333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1377135738; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 58717.6; 0.0; 1.0; 0.0; 0.0 +1377136038; 1; 2599.999334; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 1.0; 0.0; 0.0 +1377136338; 1; 2599.999334; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 0.6666666666666666; 0.0; 0.0 +1377136638; 1; 2599.999334; 0.0; 0.0; 2097152.0; 95069.06666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1377136938; 1; 2599.999334; 0.0; 0.0; 2097152.0; 127225.33333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377137238; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 109050.4; 0.0; 2.4; 0.06666666666666667; 0.5333333333333333 +1377137538; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 170565.86666666667; 0.06666666666666667; 1.1333333333333333; 0.0; 0.0 +1377137838; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 111846.66666666667; 0.0; 0.8; 0.0; 0.0 +1377138138; 1; 2599.999334; 0.0; 0.0; 2097152.0; 89476.53333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1377138438; 1; 2599.999334; 0.0; 0.0; 2097152.0; 131420.53333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377138738; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 124429.86666666667; 0.06666666666666667; 7.133333333333334; 0.26666666666666666; 0.13333333333333333 +1377139038; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 95069.06666666667; 0.0; 0.8; 0.0; 0.0 +1377139338; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 83884.0; 0.0; 11.8; 0.0; 0.0 +1377139638; 1; 2599.999334; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1377139938; 1; 2599.999334; 0.0; 0.0; 2097152.0; 83884.0; 0.0; 0.8; 0.0; 0.0 +1377140238; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 83884.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1377140538; 1; 2599.999334; 67.59998268400001; 2.6; 2097152.0; 669688.5333333333; 161.06666666666666; 22.0; 0.06666666666666667; 0.4 +1377140838; 1; 2599.999334; 10.399997336000002; 0.4; 2097152.0; 408244.26666666666; 0.0; 3.533333333333333; 0.0; 0.4666666666666667 +1377141138; 1; 2599.999334; 8.666664446666669; 0.33333333333333337; 2097152.0; 258645.86666666667; 31.8; 3.533333333333333; 0.0; 0.0 +1377141438; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 149596.0; 0.0; 2.2666666666666666; 0.0; 0.0 +1377141738; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 160781.06666666668; 0.0; 1.9333333333333333; 0.0; 0.0 +1377142038; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 145400.8; 0.0; 0.8; 0.0; 0.0 +1377142338; 1; 2599.999334; 0.0; 0.0; 2097152.0; 148197.86666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1377142638; 1; 2599.999334; 0.0; 0.0; 2097152.0; 128624.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377142938; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 144002.93333333332; 0.0; 0.8666666666666667; 0.0; 0.0 +1377143238; 1; 2599.999334; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 1.1333333333333333; 0.0; 0.0 +1377143538; 1; 2599.999334; 0.0; 0.0; 2097152.0; 83884.0; 0.0; 0.7333333333333333; 0.0; 0.0 +1377143838; 1; 2599.999334; 0.0; 0.0; 2097152.0; 141207.46666666667; 0.0; 0.8; 0.0; 0.0 +1377144138; 1; 2599.999334; 0.0; 0.0; 2097152.0; 134216.0; 0.0; 0.8; 0.0; 0.0 +1377144438; 1; 2599.999334; 8.666664446666669; 0.33333333333333337; 2097152.0; 152391.73333333334; 0.0; 8.4; 0.26666666666666666; 0.6 +1377144738; 1; 2599.999334; 12.133330225333331; 0.4666666666666666; 2097152.0; 125828.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1377145039; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 106254.13333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377145339; 1; 2599.999334; 0.0; 0.0; 2097152.0; 116041.06666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1377145638; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 90874.66666666667; 0.0; 1.0; 0.0; 0.0 +1377145938; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 125828.0; 0.0; 0.8; 0.0; 0.0 +1377146238; 1; 2599.999334; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377146538; 1; 2599.999334; 0.0; 0.0; 2097152.0; 79689.6; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1377146838; 1; 2599.999334; 0.0; 0.0; 2097152.0; 89476.53333333334; 0.0; 0.8; 0.0; 0.0 +1377147138; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 86680.26666666666; 0.0; 1.0; 0.0; 0.0 +1377147438; 1; 2599.999334; 0.0; 0.0; 2097152.0; 85282.13333333333; 0.0; 0.6666666666666666; 0.0; 0.0 +1377147738; 1; 2599.999334; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.8; 0.0; 0.0 +1377148038; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 96467.2; 0.0; 2.4; 0.0; 0.5333333333333333 +1377148338; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 173362.93333333332; 0.06666666666666667; 0.7333333333333333; 0.0; 0.0 +1377148638; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 124429.86666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1377148938; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 106254.13333333333; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377149238; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 83884.0; 0.0; 0.7333333333333333; 0.0; 0.0 +1377149538; 1; 2599.999334; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.8; 0.0; 0.0 +1377149839; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377150139; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 93670.93333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1377150439; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 109050.4; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1377150739; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 110448.53333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377151039; 1; 2599.999334; 12.133330225333331; 0.4666666666666666; 2097152.0; 173363.46666666667; 12.8; 13.4; 0.26666666666666666; 0.13333333333333333 +1377151339; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 142604.26666666666; 0.0; 1.4; 0.0; 0.0 +1377151639; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 107652.0; 0.0; 2.2; 0.0; 0.4666666666666667 +1377151939; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 209712.26666666666; 0.0; 0.6; 0.0; 0.0 +1377152239; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 109050.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1377152539; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 130022.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1377152839; 1; 2599.999334; 0.0; 0.0; 2097152.0; 117438.4; 0.0; 0.6666666666666666; 0.0; 0.0 +1377153139; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 113244.8; 0.0; 1.0666666666666667; 0.06666666666666667; 0.13333333333333333 +1377153439; 1; 2599.999334; 15.599996004; 0.6; 2097152.0; 164973.86666666667; 0.0; 1.7333333333333334; 0.0; 0.0 +1377153739; 1; 2599.999334; 12.133330225333331; 0.4666666666666666; 2097152.0; 121633.6; 0.0; 1.3333333333333333; 0.0; 0.0 +1377154039; 1; 2599.999334; 15.599996004; 0.6; 2097152.0; 104856.0; 0.0; 1.1333333333333333; 0.0; 0.0 +1377154339; 1; 2599.999334; 8.666664446666669; 0.33333333333333337; 2097152.0; 116041.06666666667; 0.0; 0.5333333333333333; 0.0; 0.0 +1377154639; 1; 2599.999334; 10.399997336000002; 0.4; 2097152.0; 83884.0; 0.0; 0.6; 0.0; 0.0 +1377154939; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 109050.4; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1377155239; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 110448.53333333334; 0.0; 2.3333333333333335; 0.0; 0.4666666666666667 +1377155539; 1; 2599.999334; 10.399997336000002; 0.4; 2097152.0; 132817.86666666667; 0.06666666666666667; 1.0; 0.13333333333333333; 0.0 +1377155839; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 116041.06666666667; 0.0; 0.6666666666666666; 0.0; 0.0 +1377156139; 1; 2599.999334; 0.0; 0.0; 2097152.0; 134216.8; 0.0; 0.6666666666666666; 0.06666666666666667; 0.0 +1377156439; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 90874.66666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1377156739; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 100661.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1377157039; 1; 2599.999334; 13.866663114666666; 0.5333333333333333; 2097152.0; 96467.2; 0.0; 0.6666666666666666; 0.0; 0.0 +1377157339; 1; 2599.999334; 13.866663114666666; 0.5333333333333333; 2097152.0; 128624.26666666666; 0.0; 8.2; 0.26666666666666666; 0.13333333333333333 +1377157639; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 130022.4; 0.0; 1.1333333333333333; 0.0; 0.0 +1377157939; 1; 2599.999334; 0.0; 0.0; 2097152.0; 106254.13333333333; 0.0; 0.6; 0.13333333333333333; 0.0 +1377158239; 1; 2599.999334; 0.0; 0.0; 2097152.0; 110448.53333333334; 0.0; 1.0; 0.0; 0.0 +1377158539; 1; 2599.999334; 0.0; 0.0; 2097152.0; 78291.46666666666; 0.0; 0.6; 0.06666666666666667; 0.0 +1377158839; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 100661.6; 0.06666666666666667; 2.466666666666667; 0.0; 0.4666666666666667 +1377159140; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 178955.46666666667; 0.0; 0.6666666666666666; 0.06666666666666667; 0.0 +1377159440; 1; 2599.999334; 0.0; 0.0; 2097152.0; 131420.53333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377159740; 1; 2599.999334; 0.0; 0.0; 2097152.0; 131420.53333333333; 0.0; 0.6666666666666666; 0.06666666666666667; 0.0 +1377160040; 1; 2599.999334; 0.0; 0.0; 2097152.0; 127226.13333333333; 0.0; 0.6666666666666666; 0.06666666666666667; 0.0 +1377160340; 1; 2599.999334; 0.0; 0.0; 2097152.0; 131420.53333333333; 0.0; 0.6; 0.0; 0.0 +1377160640; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 97865.33333333333; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377160940; 1; 2599.999334; 15.599996004; 0.6; 2097152.0; 111846.66666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377161240; 1; 2599.999334; 0.0; 0.0; 2097152.0; 134216.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377161540; 1; 2599.999334; 0.0; 0.0; 2097152.0; 120235.46666666666; 0.0; 0.6; 0.0; 0.0 +1377161840; 1; 2599.999334; 0.0; 0.0; 2097152.0; 138411.2; 0.0; 0.6; 0.0; 0.0 +1377162140; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 121633.6; 0.0; 0.6; 0.0; 0.0 +1377162439; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 107652.26666666666; 0.2; 2.6666666666666665; 0.0; 0.4666666666666667 +1377162739; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 162177.86666666667; 0.0; 0.6; 0.0; 0.0 +1377163039; 1; 2599.999334; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.6; 0.0; 0.0 +1377163339; 1; 2599.999334; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.6; 0.0; 0.0 +1377163639; 1; 2599.999334; 0.0; 0.0; 2097152.0; 81087.73333333334; 0.0; 1.0; 0.0; 0.0 +1377163939; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 97865.33333333333; 0.0; 7.0; 0.2; 0.13333333333333333 +1377164239; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 81087.73333333334; 0.0; 0.5333333333333333; 0.0; 0.0 +1377164539; 1; 2599.999334; 8.666664446666669; 0.33333333333333337; 2097152.0; 71300.8; 0.0; 0.7333333333333333; 0.0; 0.0 +1377164840; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 117439.2; 0.0; 0.8; 0.0; 0.0 +1377165140; 1; 2599.999334; 0.0; 0.0; 2097152.0; 120235.46666666666; 0.0; 1.1333333333333333; 0.0; 0.0 +1377165440; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 125828.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377165740; 1; 2599.999334; 15.599996004; 0.6; 2097152.0; 81087.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377166040; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 113244.53333333334; 0.0; 2.066666666666667; 0.06666666666666667; 0.4666666666666667 +1377166340; 1; 2599.999334; 10.399997336000002; 0.4; 2097152.0; 184546.66666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377166640; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 86680.26666666666; 0.0; 1.1333333333333333; 0.0; 0.0 +1377166940; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 104856.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1377167240; 1; 2599.999334; 0.0; 0.0; 2097152.0; 132818.66666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1377167540; 1; 2599.999334; 0.0; 0.0; 2097152.0; 89476.53333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377167840; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 118837.33333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1377168140; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 86680.26666666666; 0.0; 0.8666666666666667; 4.733333333333333; 0.0 +1377168440; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 83884.0; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377168740; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 90874.66666666667; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1377169040; 1; 2599.999334; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377169340; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 96467.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1377169640; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 106254.13333333333; 0.0; 2.3333333333333335; 0.13333333333333333; 0.4666666666666667 +1377169940; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 150993.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1377170240; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 130021.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1377170540; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 113244.8; 0.0; 1.0666666666666667; 0.0; 0.0 +1377170840; 1; 2599.999334; 0.0; 0.0; 2097152.0; 72698.93333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377171140; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 92272.8; 0.0; 6.8; 0.26666666666666666; 0.13333333333333333 +1377171440; 1; 2599.999334; 0.0; 0.0; 2097152.0; 69902.66666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377171740; 1; 2599.999334; 0.0; 0.0; 2097152.0; 79689.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1377172041; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 88078.4; 0.0; 1.3333333333333333; 0.0; 0.0 +1377172341; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 125828.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1377172641; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 113244.8; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377172941; 1; 2599.999334; 8.666664446666669; 0.33333333333333337; 2097152.0; 102059.73333333334; 0.0; 1.2666666666666666; 0.0; 0.0 +1377173241; 1; 2599.999334; 20.799994672000004; 0.8; 2097152.0; 83884.0; 0.06666666666666667; 2.533333333333333; 0.06666666666666667; 0.4666666666666667 +1377173541; 1; 2599.999334; 19.066661782666664; 0.7333333333333333; 2097152.0; 159381.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1377173841; 1; 2599.999334; 20.799994672000004; 0.8; 2097152.0; 100661.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1377174141; 1; 2599.999334; 12.133330225333331; 0.4666666666666666; 2097152.0; 92272.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377174441; 1; 2599.999334; 15.599996004; 0.6; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377174741; 1; 2599.999334; 12.133330225333331; 0.4666666666666666; 2097152.0; 100661.6; 0.0; 1.1333333333333333; 0.0; 0.0 +1377175041; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 83884.0; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1377175341; 1; 2599.999334; 19.066661782666664; 0.7333333333333333; 2097152.0; 75495.2; 0.0; 1.0; 0.0; 0.0 +1377175641; 1; 2599.999334; 17.333328893333338; 0.6666666666666667; 2097152.0; 114642.93333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377175941; 1; 2599.999334; 15.599996004; 0.6; 2097152.0; 149594.66666666666; 0.0; 1.0; 0.0; 0.0 +1377176241; 1; 2599.999334; 17.333328893333338; 0.6666666666666667; 2097152.0; 121633.6; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1377176541; 1; 2599.999334; 13.866663114666666; 0.5333333333333333; 2097152.0; 95069.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377176841; 1; 2599.999334; 13.866663114666666; 0.5333333333333333; 2097152.0; 92272.8; 0.06666666666666667; 2.466666666666667; 0.06666666666666667; 0.4666666666666667 +1377177141; 1; 2599.999334; 20.799994672000004; 0.8; 2097152.0; 142604.8; 0.0; 7.0; 0.26666666666666666; 0.2 +1377177441; 1; 2599.999334; 13.866663114666666; 0.5333333333333333; 2097152.0; 109050.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1377177741; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 130022.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377178041; 1; 2599.999334; 8.666664446666669; 0.33333333333333337; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377178341; 1; 2599.999334; 8.666664446666669; 0.33333333333333337; 2097152.0; 83884.0; 0.0; 0.8; 0.0; 0.0 +1377178641; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 88078.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377178941; 1; 2599.999334; 10.399997336000002; 0.4; 2097152.0; 93670.93333333333; 0.0; 1.2666666666666666; 0.0; 0.0 +1377179241; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377179541; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 79689.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1377179841; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 117439.2; 0.0; 0.6; 0.0; 0.0 +1377180141; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 113244.8; 0.0; 0.7333333333333333; 0.0; 0.0 +1377180441; 1; 2599.999334; 10.399997336000002; 0.4; 2097152.0; 142604.8; 0.0; 2.533333333333333; 0.0; 0.4666666666666667 +1377180741; 1; 2599.999334; 15.599996004; 0.6; 2097152.0; 134216.0; 0.0; 0.8; 0.0; 0.0 +1377181041; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 125828.0; 0.0; 0.8; 0.0; 0.0 +1377181341; 1; 2599.999334; 1.7333328893333333; 0.06666666666666667; 2097152.0; 134216.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1377181641; 1; 2599.999334; 10.399997336000002; 0.4; 2097152.0; 121633.6; 0.0; 1.1333333333333333; 0.0; 0.0 +1377181941; 1; 2599.999334; 8.666664446666669; 0.33333333333333337; 2097152.0; 99263.46666666666; 0.0; 1.2666666666666666; 0.06666666666666667; 0.13333333333333333 +1377182241; 1; 2599.999334; 12.133330225333331; 0.4666666666666666; 2097152.0; 142604.0; 0.0; 0.8; 0.0; 0.0 +1377182542; 1; 2599.999334; 6.933331557333333; 0.26666666666666666; 2097152.0; 92272.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377182842; 1; 2599.999334; 10.399997336000002; 0.4; 2097152.0; 85282.13333333333; 0.0; 11.733333333333333; 0.0; 0.0 +1377183142; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 121633.6; 0.0; 1.1333333333333333; 0.0; 0.0 +1377183442; 1; 2599.999334; 10.399997336000002; 0.4; 2097152.0; 81087.73333333334; 0.0; 0.8; 0.0; 0.0 +1377183742; 1; 2599.999334; 13.866663114666666; 0.5333333333333333; 2097152.0; 130022.4; 0.0; 6.933333333333334; 0.2; 0.13333333333333333 +1377184042; 1; 2599.999334; 5.199998668000001; 0.2; 2097152.0; 110448.53333333334; 0.06666666666666667; 2.4; 0.06666666666666667; 0.5333333333333333 +1377184342; 1; 2599.999334; 12.133330225333331; 0.4666666666666666; 2097152.0; 155187.2; 0.0; 0.7333333333333333; 0.2; 0.0 +1377184642; 1; 2599.999334; 8.666664446666669; 0.33333333333333337; 2097152.0; 103457.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377184942; 1; 2599.999334; 10.399997336000002; 0.4; 2097152.0; 121633.6; 0.0; 0.7333333333333333; 0.0; 0.0 +1377185242; 1; 2599.999334; 10.399997336000002; 0.4; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377185542; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 100661.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1377185842; 1; 2599.999334; 12.133330225333331; 0.4666666666666666; 2097152.0; 100661.6; 0.0; 1.0; 0.0; 0.0 +1377186142; 1; 2599.999334; 13.866663114666666; 0.5333333333333333; 2097152.0; 92272.8; 0.0; 0.8; 0.06666666666666667; 0.0 +1377186442; 1; 2599.999334; 8.666664446666669; 0.33333333333333337; 2097152.0; 110448.53333333334; 0.0; 1.0; 0.0; 0.0 +1377186742; 1; 2599.999334; 19.066661782666664; 0.7333333333333333; 2097152.0; 109050.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377187042; 1; 2599.999334; 15.599996004; 0.6; 2097152.0; 109050.4; 0.0; 0.6666666666666666; 0.06666666666666667; 0.0 +1377187342; 1; 2599.999334; 17.333328893333338; 0.6666666666666667; 2097152.0; 103457.86666666667; 0.0; 1.0; 0.0; 0.0 +1377187642; 1; 2599.999334; 10.399997336000002; 0.4; 2097152.0; 88078.4; 0.13333333333333333; 2.4; 0.06666666666666667; 0.4666666666666667 +1377187942; 1; 2599.999334; 19.066661782666664; 0.7333333333333333; 2097152.0; 163576.0; 0.0; 1.0; 0.0; 0.0 +1377188242; 1; 2599.999334; 12.133330225333331; 0.4666666666666666; 2097152.0; 88078.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1377188542; 1; 2599.999334; 8.666664446666669; 0.33333333333333337; 2097152.0; 97865.33333333333; 0.0; 1.0; 0.0; 0.0 +1377188842; 1; 2599.999334; 10.399997336000002; 0.4; 2097152.0; 121633.6; 0.0; 0.8; 0.0; 0.0 +1377189142; 1; 2599.999334; 12.133330225333331; 0.4666666666666666; 2097152.0; 96467.2; 0.0; 0.6666666666666666; 0.06666666666666667; 0.0 +1377189442; 1; 2599.999334; 12.133330225333331; 0.4666666666666666; 2097152.0; 100661.6; 0.0; 6.866666666666666; 0.2; 0.13333333333333333 +1377189742; 1; 2599.999334; 10.399997336000002; 0.4; 2097152.0; 134216.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1377190042; 1; 2599.999334; 17.333328893333338; 0.6666666666666667; 2097152.0; 121633.6; 0.0; 1.1333333333333333; 0.0; 0.0 +1377190342; 1; 2599.999334; 19.066661782666664; 0.7333333333333333; 2097152.0; 109050.4; 0.0; 1.0; 0.0; 0.0 +1377190643; 1; 2599.999334; 12.133330225333331; 0.4666666666666666; 2097152.0; 86680.26666666666; 0.0; 0.8; 0.13333333333333333; 0.0 +1377190943; 1; 2599.999334; 15.599996004; 0.6; 2097152.0; 83884.0; 0.0; 0.6666666666666666; 0.0; 0.0 +1377191243; 1; 2599.999334; 13.866663114666666; 0.5333333333333333; 2097152.0; 106254.13333333333; 0.06666666666666667; 2.1333333333333333; 0.13333333333333333; 0.4666666666666667 +1377191543; 1; 2599.999334; 10.399997336000002; 0.4; 2097152.0; 142605.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1377191843; 1; 2599.999334; 13.866663114666666; 0.5333333333333333; 2097152.0; 124429.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377192143; 1; 2599.999334; 8.666664446666669; 0.33333333333333337; 2097152.0; 88078.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1377192443; 1; 2599.999334; 8.666664446666669; 0.33333333333333337; 2097152.0; 100661.6; 0.0; 0.8; 0.0; 0.0 +1377192743; 1; 2599.999334; 13.866663114666666; 0.5333333333333333; 2097152.0; 121633.6; 0.0; 1.1333333333333333; 0.0; 0.0 +1377193043; 1; 2599.999297; 27.999992429230765; 1.0769230769230769; 2097152.0; 77431.07692307692; 0.0; 0.8333333333333334; 0.08333333333333333; 0.0 +1377193343; 1; 2599.999297; 17.33332864666667; 0.6666666666666667; 2097152.0; 72698.93333333333; 0.0; 0.8; 0.06666666666666667; 0.0 +1377193643; 1; 2599.999297; 34.66665729333334; 1.3333333333333335; 2097152.0; 100661.06666666667; 161.13333333333333; 20.6; 0.06666666666666667; 0.4 +1377193943; 1; 2599.999297; 55.46665166933333; 2.1333333333333333; 2097152.0; 746584.8; 0.0; 3.6666666666666665; 0.0; 0.0 +1377194243; 1; 2599.999297; 6.933331458666666; 0.26666666666666666; 2097152.0; 327154.13333333336; 31.8; 3.4; 0.06666666666666667; 0.0 +1377194543; 1; 2599.999297; 6.933331458666666; 0.26666666666666666; 2097152.0; 181752.53333333333; 0.0; 2.466666666666667; 0.0; 0.0 +1377194843; 1; 2599.999297; 20.799994376; 0.8; 2097152.0; 121633.6; 0.0; 9.2; 0.26666666666666666; 0.6666666666666666 +1377195143; 1; 2599.999297; 8.666664323333334; 0.33333333333333337; 2097152.0; 181751.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377195443; 1; 2599.999297; 8.666664323333334; 0.33333333333333337; 2097152.0; 103457.86666666667; 0.0; 1.0; 0.0; 0.0 +1377195743; 1; 2599.999297; 8.666664323333334; 0.33333333333333337; 2097152.0; 123031.73333333334; 0.0; 0.8; 0.0; 0.0 +1377196043; 1; 2599.999297; 5.199998594; 0.2; 2097152.0; 97865.33333333333; 0.0; 0.8; 0.0; 0.0 +1377196343; 1; 2599.999297; 8.666664323333334; 0.33333333333333337; 2097152.0; 116041.06666666667; 0.0; 0.8; 0.0; 0.0 +1377196643; 1; 2599.999297; 10.399997188; 0.4; 2097152.0; 86680.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377196943; 1; 2599.999297; 15.599995781999999; 0.6; 2097152.0; 100661.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1377197243; 1; 2599.999297; 1.7333328646666666; 0.06666666666666667; 2097152.0; 96467.2; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1377197543; 1; 2599.999297; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 0.8; 0.0; 0.0 +1377197843; 1; 2599.999297; 1.7333328646666666; 0.06666666666666667; 2097152.0; 89476.53333333334; 0.0; 0.6666666666666666; 0.0; 0.0 +1377198143; 1; 2599.999297; 3.466665729333333; 0.13333333333333333; 2097152.0; 111845.86666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1377198443; 1; 2599.999297; 3.466665729333333; 0.13333333333333333; 2097152.0; 103457.33333333333; 0.06666666666666667; 2.6; 0.06666666666666667; 0.5333333333333333 +1377198743; 1; 2599.999297; 10.399997188; 0.4; 2097152.0; 157982.93333333332; 0.0; 0.8666666666666667; 0.0; 0.0 +1377199043; 1; 2599.999297; 6.933331458666666; 0.26666666666666666; 2097152.0; 104856.0; 0.0; 0.8; 0.0; 0.0 +1377199343; 1; 2599.999297; 1.7333328646666666; 0.06666666666666667; 2097152.0; 106254.13333333333; 0.0; 0.8; 0.0; 0.0 +1377199643; 1; 2599.999297; 6.933331458666666; 0.26666666666666666; 2097152.0; 107651.46666666666; 0.0; 0.9333333333333333; 0.2; 0.0 +1377199943; 1; 2599.999297; 45.06665448133334; 1.7333333333333334; 2097152.0; 392864.0; 161.06666666666666; 14.4; 0.06666666666666667; 0.2 +1377200243; 1; 2599.999297; 10.399997188; 0.4; 2097152.0; 290802.6666666667; 0.0; 1.3333333333333333; 0.0; 0.0 +1377200543; 1; 2599.999297; 10.399997188; 0.4; 2097152.0; 243268.0; 31.8; 4.466666666666667; 0.0; 0.0 +1377200843; 1; 2599.999297; 12.133330052666665; 0.4666666666666666; 2097152.0; 171965.06666666668; 0.0; 7.266666666666667; 0.2; 0.13333333333333333 +1377201143; 1; 2599.999297; 3.466665729333333; 0.13333333333333333; 2097152.0; 102059.73333333334; 0.0; 1.0666666666666667; 0.13333333333333333; 0.0 +1377201443; 1; 2599.999297; 5.199998594; 0.2; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377201743; 1; 2599.999297; 3.466665729333333; 0.13333333333333333; 2097152.0; 104856.0; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1377202043; 1; 2599.999297; 6.933331458666666; 0.26666666666666666; 2097152.0; 96467.2; 0.0; 3.1333333333333333; 0.0; 0.4666666666666667 +1377202343; 1; 2599.999297; 10.399997188; 0.4; 2097152.0; 160779.73333333334; 0.0; 1.0666666666666667; 0.0; 0.0 +1377202643; 1; 2599.999297; 3.466665729333333; 0.13333333333333333; 2097152.0; 106254.13333333333; 0.0; 1.0; 0.0; 0.0 +1377202943; 1; 2599.999297; 8.666664323333334; 0.33333333333333337; 2097152.0; 123031.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377203243; 1; 2599.999306; 18.571423614285717; 0.7142857142857143; 2097152.0; 101860.0; 0.0; 0.9230769230769231; 1.9230769230769231; 0.0 +1377203543; 1; 2599.999306; 3.4666657413333337; 0.13333333333333333; 2097152.0; 103457.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377203844; 1; 2599.999306; 1.7333328706666669; 0.06666666666666667; 2097152.0; 138410.4; 0.0; 1.1333333333333333; 0.0; 0.0 +1377204144; 1; 2599.999306; 0.0; 0.0; 2097152.0; 137012.26666666666; 0.0; 1.0; 0.0; 0.0 +1377204444; 1; 2599.999306; 3.4666657413333337; 0.13333333333333333; 2097152.0; 111846.66666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377204744; 1; 2599.999306; 1.7333328706666669; 0.06666666666666667; 2097152.0; 86680.26666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1377205044; 1; 2599.999306; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377205344; 1; 2599.999306; 3.4666657413333337; 0.13333333333333333; 2097152.0; 144002.93333333332; 0.0; 1.1333333333333333; 2.466666666666667; 0.0 +1377205644; 1; 2599.999306; 5.199998612000001; 0.2; 2097152.0; 117439.2; 0.0; 2.4; 0.06666666666666667; 0.4666666666666667 +1377205944; 1; 2599.999306; 3.4666657413333337; 0.13333333333333333; 2097152.0; 170566.66666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377206244; 1; 2599.999306; 3.4666657413333337; 0.13333333333333333; 2097152.0; 104856.0; 0.0; 0.9333333333333333; 0.2; 0.0 +1377206844; 1; 2599.999306; 8.666664353333335; 0.33333333333333337; 2097152.0; 100661.6; 0.0; 7.066666666666666; 0.26666666666666666; 0.13333333333333333 +1377207144; 1; 2599.999306; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377207444; 1; 2599.999306; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377208044; 1; 2599.999306; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1377208344; 1; 2599.999306; 3.4666657413333337; 0.13333333333333333; 2097152.0; 97865.33333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377208644; 1; 2599.999306; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1377208944; 1; 2599.999306; 8.666664353333335; 0.33333333333333337; 2097152.0; 82485.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377209245; 1; 2599.999306; 5.199998612000001; 0.2; 2097152.0; 134216.0; 0.06666666666666667; 2.6666666666666665; 0.06666666666666667; 0.4666666666666667 +1377209845; 1; 2599.999306; 1.7333328706666669; 0.06666666666666667; 2097152.0; 123030.93333333333; 0.0; 1.0; 0.0; 0.0 +1377210145; 1; 2599.999601; 25.99999601; 1.0; 2097152.0; 102059.73333333334; 0.0; 0.8571428571428571; 0.0; 0.0 +1377210445; 1; 2599.999601; 5.199999202; 0.2; 2097152.0; 120235.46666666666; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377210745; 1; 2599.999601; 13.866664538666667; 0.5333333333333333; 2097152.0; 130021.6; 0.06666666666666667; 1.7333333333333334; 0.06666666666666667; 0.13333333333333333 +1377211045; 1; 2599.999601; 5.199999202; 0.2; 2097152.0; 106254.13333333333; 0.0; 1.0; 0.0; 0.0 +1377211345; 1; 2599.999601; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 1.0; 0.0; 0.0 +1377211645; 1; 2599.999601; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1377211945; 1; 2599.999601; 0.0; 0.0; 2097152.0; 110448.53333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377212245; 1; 2599.999601; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1377212545; 1; 2599.999601; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377212845; 1; 2599.999601; 8.666665336666668; 0.33333333333333337; 2097152.0; 83884.0; 0.0; 2.4; 0.06666666666666667; 0.4666666666666667 +1377213145; 1; 2599.999601; 15.599997605999999; 0.6; 2097152.0; 177557.33333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377213445; 1; 2599.999601; 17.333330673333336; 0.6666666666666667; 2097152.0; 135614.93333333332; 12.733333333333333; 13.266666666666667; 0.3333333333333333; 0.13333333333333333 +1377213745; 1; 2599.999601; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 1.2666666666666666; 0.0; 0.0 +1377214045; 1; 2599.999601; 3.4666661346666667; 0.13333333333333333; 2097152.0; 113244.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377214345; 1; 2599.999601; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377214645; 1; 2599.999601; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1377214945; 1; 2599.999601; 3.4666661346666667; 0.13333333333333333; 2097152.0; 113244.8; 0.0; 1.0666666666666667; 0.0; 0.0 +1377215245; 1; 2599.999601; 0.0; 0.0; 2097152.0; 134216.0; 0.0; 1.2; 0.0; 0.0 +1377215545; 1; 2599.999601; 0.0; 0.0; 2097152.0; 121633.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1377215845; 1; 2599.999601; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377216145; 1; 2599.999601; 5.199999202; 0.2; 2097152.0; 130022.4; 0.0; 1.0; 0.0; 0.0 +1377216445; 1; 2599.999601; 10.399998404; 0.4; 2097152.0; 99263.46666666666; 0.0; 2.466666666666667; 0.06666666666666667; 0.4666666666666667 +1377216745; 1; 2599.999601; 15.599997605999999; 0.6; 2097152.0; 114642.93333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377217045; 1; 2599.999601; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1377217345; 1; 2599.999601; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377217645; 1; 2599.999601; 1.7333330673333334; 0.06666666666666667; 2097152.0; 131419.73333333334; 0.0; 1.1333333333333333; 0.0; 0.0 +1377217945; 1; 2599.999601; 1.7333330673333334; 0.06666666666666667; 2097152.0; 109049.6; 0.06666666666666667; 1.4666666666666666; 0.0; 0.0 +1377218245; 1; 2599.999601; 1.7333330673333334; 0.06666666666666667; 2097152.0; 99263.46666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1377218545; 1; 2599.999601; 0.0; 0.0; 2097152.0; 134216.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377218845; 1; 2599.999601; 12.133331471333332; 0.4666666666666666; 2097152.0; 117438.4; 0.0; 1.0; 0.0; 0.0 +1377219145; 1; 2599.999601; 0.0; 0.0; 2097152.0; 142604.8; 0.0; 1.0666666666666667; 0.0; 0.0 +1377219445; 1; 2599.999601; 1.7333330673333334; 0.06666666666666667; 2097152.0; 120235.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377219745; 1; 2599.999601; 6.9333322693333335; 0.26666666666666666; 2097152.0; 120235.46666666666; 0.0; 7.066666666666666; 0.2; 0.13333333333333333 +1377220045; 1; 2599.999601; 5.199999202; 0.2; 2097152.0; 118837.33333333333; 0.13333333333333333; 2.4; 0.0; 0.5333333333333333 +1377220345; 1; 2599.999601; 6.9333322693333335; 0.26666666666666666; 2097152.0; 163576.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1377220646; 1; 2599.999601; 10.399998404; 0.4; 2097152.0; 85282.13333333333; 0.06666666666666667; 1.7333333333333334; 0.0; 0.0 +1377220946; 1; 2599.999601; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 0.8666666666666667; 0.13333333333333333; 0.0 +1377221246; 1; 2599.999601; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.8666666666666667; 0.13333333333333333; 0.0 +1377221546; 1; 2599.999601; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377221846; 1; 2599.999601; 5.199999202; 0.2; 2097152.0; 86680.26666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1377222146; 1; 2599.999601; 1.7333330673333334; 0.06666666666666667; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377222446; 1; 2599.999601; 3.4666661346666667; 0.13333333333333333; 2097152.0; 90874.66666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377222746; 1; 2599.999601; 3.4666661346666667; 0.13333333333333333; 2097152.0; 111846.66666666667; 0.0; 1.0; 0.06666666666666667; 0.0 +1377223046; 1; 2599.999601; 0.0; 0.0; 2097152.0; 116041.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377223346; 1; 2599.999601; 3.4666661346666667; 0.13333333333333333; 2097152.0; 113244.8; 0.0; 1.0666666666666667; 0.0; 0.0 +1377223646; 1; 2599.999601; 3.4666661346666667; 0.13333333333333333; 2097152.0; 107652.26666666666; 0.0; 1.8666666666666667; 0.0; 0.4666666666666667 +1377223946; 1; 2599.999601; 5.199999202; 0.2; 2097152.0; 141206.66666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377224246; 1; 2599.999601; 0.0; 0.0; 2097152.0; 128624.26666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1377224546; 1; 2599.999601; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1377224846; 1; 2599.999601; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377225146; 1; 2599.999601; 0.0; 0.0; 2097152.0; 118837.33333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1377225446; 1; 2599.999601; 1.7333330673333334; 0.06666666666666667; 2097152.0; 123031.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377225746; 1; 2599.999601; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377226046; 1; 2599.999601; 0.0; 0.0; 2097152.0; 110448.53333333334; 0.0; 1.0666666666666667; 0.0; 0.0 +1377226346; 1; 2599.999601; 6.9333322693333335; 0.26666666666666666; 2097152.0; 93670.93333333333; 0.0; 6.866666666666666; 0.2; 0.13333333333333333 +1377226646; 1; 2599.999601; 3.4666661346666667; 0.13333333333333333; 2097152.0; 123031.73333333334; 0.0; 11.733333333333333; 0.0; 0.0 +1377226946; 1; 2599.999601; 0.0; 0.0; 2097152.0; 145401.86666666667; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1377227246; 1; 2599.999601; 5.199999202; 0.2; 2097152.0; 107652.0; 0.0; 2.466666666666667; 0.0; 0.4666666666666667 +1377227546; 1; 2599.999601; 1.7333330673333334; 0.06666666666666667; 2097152.0; 183148.53333333333; 0.0; 1.0; 0.0; 0.0 +1377227846; 1; 2599.999601; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377228146; 1; 2599.999601; 0.0; 0.0; 2097152.0; 106254.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377228446; 1; 2599.999601; 0.0; 0.0; 2097152.0; 116041.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377228746; 1; 2599.999601; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1377229046; 1; 2599.999601; 3.4666661346666667; 0.13333333333333333; 2097152.0; 130022.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377229346; 1; 2599.999601; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 1.0; 0.0; 0.0 +1377229646; 1; 2599.999601; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 1.2; 0.0; 0.0 +1377229946; 1; 2599.999601; 1.7333330673333334; 0.06666666666666667; 2097152.0; 97865.33333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377230246; 1; 2599.999601; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1377230546; 1; 2599.999601; 1.7333330673333334; 0.06666666666666667; 2097152.0; 97865.33333333333; 0.0; 0.8; 0.0; 0.0 +1377230846; 1; 2599.999601; 5.199999202; 0.2; 2097152.0; 83884.0; 0.0; 2.1333333333333333; 0.0; 0.4666666666666667 +1377231146; 1; 2599.999601; 0.0; 0.0; 2097152.0; 150992.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377231446; 1; 2599.999601; 0.0; 0.0; 2097152.0; 125828.0; 0.0; 1.1333333333333333; 0.0; 0.0 +1377231746; 1; 2599.999601; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377232046; 1; 2599.999601; 0.0; 0.0; 2097152.0; 130022.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377232346; 1; 2599.999601; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377232646; 1; 2599.999601; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377232946; 1; 2599.999601; 3.4666661346666667; 0.13333333333333333; 2097152.0; 170566.13333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1377233246; 1; 2599.999601; 3.4666661346666667; 0.13333333333333333; 2097152.0; 125827.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377233547; 1; 2599.999601; 6.9333322693333335; 0.26666666666666666; 2097152.0; 155187.2; 0.2; 6.933333333333334; 0.2; 0.13333333333333333 +1377233847; 1; 2599.999309; 15.166662635833333; 0.5833333333333334; 2097152.0; 83884.0; 0.0; 1.0; 0.0; 0.0 +1377234147; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 92272.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377234447; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 78291.46666666666; 0.0; 2.3333333333333335; 0.0; 0.4666666666666667 +1377234747; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 171964.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377235047; 1; 2599.999309; 0.0; 0.0; 2097152.0; 125828.0; 0.0; 0.7333333333333333; 0.0; 0.0 +1377235347; 1; 2599.999309; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 0.6; 0.0; 0.0 +1377235647; 1; 2599.999309; 0.0; 0.0; 2097152.0; 120235.46666666666; 0.0; 1.0; 0.0; 0.0 +1377235947; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 75495.2; 0.0; 0.7333333333333333; 0.0; 0.0 +1377236247; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 90874.66666666667; 0.0; 0.6; 0.0; 0.0 +1377236547; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 107652.26666666666; 0.0; 0.6666666666666666; 0.0; 0.0 +1377236847; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 125827.2; 0.0; 1.0; 0.06666666666666667; 0.0 +1377237147; 1; 2599.999309; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 1.0; 0.0; 0.0 +1377237447; 1; 2599.999309; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.6; 0.0; 0.0 +1377237747; 1; 2599.999309; 0.0; 0.0; 2097152.0; 82485.86666666667; 0.0; 0.6; 0.0; 0.0 +1377238047; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 96467.2; 0.0; 2.066666666666667; 0.0; 0.4666666666666667 +1377238347; 1; 2599.999309; 5.1999986179999995; 0.2; 2097152.0; 146798.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377238647; 1; 2599.999309; 0.0; 0.0; 2097152.0; 121633.6; 0.0; 0.6666666666666666; 0.0; 0.0 +1377238947; 1; 2599.999309; 0.0; 0.0; 2097152.0; 82485.86666666667; 0.0; 0.6666666666666666; 0.0; 0.0 +1377239247; 1; 2599.999309; 5.1999986179999995; 0.2; 2097152.0; 150992.8; 0.0; 6.666666666666667; 0.3333333333333333; 0.13333333333333333 +1377239547; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 111846.66666666667; 0.0; 1.2666666666666666; 0.06666666666666667; 0.06666666666666667 +1377239847; 1; 2599.999309; 10.399997235999999; 0.4; 2097152.0; 121633.6; 0.0; 1.8; 0.0; 0.0 +1377240147; 1; 2599.999309; 5.1999986179999995; 0.2; 2097152.0; 149595.46666666667; 0.0; 1.6666666666666667; 0.13333333333333333; 0.0 +1377240447; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 113244.8; 0.0; 0.5333333333333333; 0.0; 0.0 +1377240747; 1; 2599.999309; 0.0; 0.0; 2097152.0; 121633.6; 0.0; 0.6666666666666666; 0.0; 0.0 +1377241047; 1; 2599.999309; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.6666666666666666; 0.0; 0.0 +1377241347; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 95069.06666666667; 0.0; 1.4; 0.0; 0.0 +1377241647; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 118837.33333333333; 0.0; 2.0; 0.06666666666666667; 0.4666666666666667 +1377241947; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 192936.0; 0.0; 0.7333333333333333; 0.0; 0.0 +1377242247; 1; 2599.999309; 0.0; 0.0; 2097152.0; 121633.6; 0.0; 0.8; 0.0; 0.0 +1377242547; 1; 2599.999309; 0.0; 0.0; 2097152.0; 116041.06666666667; 0.0; 0.8; 0.0; 0.0 +1377242848; 1; 2599.999309; 0.0; 0.0; 2097152.0; 130022.4; 0.0; 0.6; 0.0; 0.0 +1377243148; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 104856.0; 0.0; 0.7333333333333333; 0.0; 0.0 +1377243448; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 75495.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1377243748; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 76893.33333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1377244048; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 100661.6; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1377244347; 1; 2599.999309; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.6; 0.06666666666666667; 0.0 +1377244647; 1; 2599.999309; 57.199984798; 2.2; 2097152.0; 736797.0666666667; 161.0; 21.866666666666667; 0.13333333333333333; 0.4 +1377244947; 1; 2599.999309; 8.666664363333334; 0.33333333333333337; 2097152.0; 394262.4; 0.0; 2.466666666666667; 0.06666666666666667; 0.0 +1377245247; 1; 2599.999309; 6.933331490666665; 0.26666666666666666; 2097152.0; 247461.6; 32.06666666666667; 4.8; 0.0; 0.4666666666666667 +1377245547; 1; 2599.999309; 10.399997235999999; 0.4; 2097152.0; 197130.4; 0.0; 2.4; 0.06666666666666667; 0.0 +1377245847; 1; 2599.999309; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 1.4; 0.0; 0.0 +1377246147; 1; 2599.999309; 6.933331490666665; 0.26666666666666666; 2097152.0; 117439.2; 0.0; 6.666666666666667; 0.3333333333333333; 0.2 +1377246447; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 138410.4; 0.0; 1.4; 1.2666666666666666; 0.0 +1377246748; 1; 2599.999309; 0.0; 0.0; 2097152.0; 134216.8; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1377247048; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 116041.06666666667; 0.0; 0.6666666666666666; 0.06666666666666667; 0.0 +1377247348; 1; 2599.999309; 0.0; 0.0; 2097152.0; 142605.6; 0.0; 0.6; 0.0; 0.0 +1377247648; 1; 2599.999309; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 0.8; 0.0; 0.0 +1377247948; 1; 2599.999309; 0.0; 0.0; 2097152.0; 79689.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1377248248; 1; 2599.999309; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 1.2666666666666666; 0.06666666666666667; 0.0 +1377248548; 1; 2599.999309; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377248848; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 111846.66666666667; 0.06666666666666667; 2.3333333333333335; 0.06666666666666667; 0.4666666666666667 +1377249148; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 176158.4; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1377249448; 1; 2599.999309; 0.0; 0.0; 2097152.0; 125828.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1377249748; 1; 2599.999309; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377250048; 1; 2599.999309; 0.0; 0.0; 2097152.0; 78291.46666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1377250348; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 88078.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377250648; 1; 2599.999309; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377250948; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 109050.4; 0.0; 1.2; 0.0; 0.0 +1377251248; 1; 2599.999309; 0.0; 0.0; 2097152.0; 123031.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377251548; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 100661.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1377251848; 1; 2599.999309; 8.666664363333334; 0.33333333333333337; 2097152.0; 97865.33333333333; 0.0; 6.933333333333334; 0.2; 0.13333333333333333 +1377252148; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 163576.0; 0.0; 1.0; 0.06666666666666667; 0.0 +1377252448; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 99263.46666666666; 0.06666666666666667; 2.3333333333333335; 0.0; 0.4666666666666667 +1377252748; 1; 2599.999309; 5.1999986179999995; 0.2; 2097152.0; 130022.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1377253048; 1; 2599.999309; 0.0; 0.0; 2097152.0; 121633.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1377253348; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 85282.13333333333; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377253648; 1; 2599.999309; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 1.0; 0.0; 0.0 +1377253948; 1; 2599.999309; 0.0; 0.0; 2097152.0; 65708.26666666666; 0.0; 0.8666666666666667; 0.13333333333333333; 0.0 +1377254248; 1; 2599.999309; 0.0; 0.0; 2097152.0; 78291.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377254548; 1; 2599.999309; 0.0; 0.0; 2097152.0; 118837.33333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1377254848; 1; 2599.999309; 0.0; 0.0; 2097152.0; 106254.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1377255148; 1; 2599.999309; 0.0; 0.0; 2097152.0; 120235.46666666666; 0.0; 1.1333333333333333; 0.0; 0.0 +1377255448; 1; 2599.999309; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377255748; 1; 2599.999309; 0.0; 0.0; 2097152.0; 89476.53333333334; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377256048; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 102059.73333333334; 0.06666666666666667; 2.4; 0.06666666666666667; 0.4666666666666667 +1377256348; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 163576.0; 0.0; 1.2; 0.0; 0.0 +1377256648; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 109050.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377256948; 1; 2599.999309; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1377257249; 1; 2599.999309; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1377257549; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 96467.2; 0.0; 1.0; 0.0; 0.0 +1377257849; 1; 2599.999309; 0.0; 0.0; 2097152.0; 121633.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1377258149; 1; 2599.999309; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377258449; 1; 2599.999309; 0.0; 0.0; 2097152.0; 132818.66666666666; 0.0; 1.1333333333333333; 0.0; 0.0 +1377258749; 1; 2599.999309; 0.0; 0.0; 2097152.0; 125828.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377259049; 1; 2599.999309; 5.1999986179999995; 0.2; 2097152.0; 118837.33333333333; 0.06666666666666667; 7.066666666666666; 0.2; 0.13333333333333333 +1377259349; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 148197.33333333334; 0.0; 1.1333333333333333; 0.0; 0.0 +1377259649; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 106254.13333333333; 0.0; 2.2666666666666666; 0.13333333333333333; 0.4666666666666667 +1377259949; 1; 2599.999309; 6.933331490666665; 0.26666666666666666; 2097152.0; 155186.93333333332; 0.0; 0.9333333333333333; 0.0; 0.0 +1377260249; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 117438.13333333333; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1377260549; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 156585.86666666667; 0.0; 1.0666666666666667; 0.13333333333333333; 0.0 +1377260849; 1; 2599.999309; 0.0; 0.0; 2097152.0; 145401.06666666668; 0.0; 0.8666666666666667; 0.8666666666666667; 0.0 +1377261149; 1; 2599.999309; 8.666664363333334; 0.33333333333333337; 2097152.0; 109050.4; 0.0; 0.8666666666666667; 82.06666666666666; 0.0 +1377261449; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.13333333333333333; 0.0 +1377261749; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 96467.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1377262049; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 104856.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1377262349; 1; 2599.999309; 0.0; 0.0; 2097152.0; 106254.13333333333; 0.0; 0.8; 0.0; 0.0 +1377262649; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 121633.6; 0.0; 0.8; 0.0; 0.0 +1377262949; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 118837.33333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377263249; 1; 2599.999309; 5.1999986179999995; 0.2; 2097152.0; 139808.53333333333; 0.0; 2.4; 0.06666666666666667; 0.4666666666666667 +1377263549; 1; 2599.999309; 5.1999986179999995; 0.2; 2097152.0; 197130.4; 0.0; 1.0; 0.0; 0.0 +1377263849; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 125828.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1377264149; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 85282.13333333333; 0.0; 0.8; 0.0; 0.0 +1377264449; 1; 2599.999309; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1377264749; 1; 2599.999309; 5.1999986179999995; 0.2; 2097152.0; 113244.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1377265049; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 81087.73333333334; 0.0; 0.6666666666666666; 0.0; 0.0 +1377265349; 1; 2599.999309; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 1.0; 0.0; 0.0 +1377265649; 1; 2599.999309; 5.1999986179999995; 0.2; 2097152.0; 116041.06666666667; 0.0; 7.133333333333334; 0.3333333333333333; 0.13333333333333333 +1377265949; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 106254.13333333333; 0.0; 0.8; 0.0; 0.0 +1377266249; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 127226.13333333333; 0.0; 1.2; 0.0; 0.0 +1377266549; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 78291.46666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1377266849; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 107651.73333333334; 0.0; 2.1333333333333333; 0.06666666666666667; 0.4666666666666667 +1377267149; 1; 2599.999309; 6.933331490666665; 0.26666666666666666; 2097152.0; 188742.13333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377267449; 1; 2599.999309; 6.933331490666665; 0.26666666666666666; 2097152.0; 123031.73333333334; 0.0; 1.0; 0.0; 0.0 +1377267749; 1; 2599.999309; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377268049; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 96467.2; 0.0; 0.7333333333333333; 0.0; 0.0 +1377268349; 1; 2599.999309; 15.599995854; 0.6; 2097152.0; 90874.66666666667; 0.06666666666666667; 1.4; 0.06666666666666667; 0.13333333333333333 +1377268649; 1; 2599.999309; 5.1999986179999995; 0.2; 2097152.0; 132818.66666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1377268949; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 89476.53333333334; 0.0; 1.2666666666666666; 0.06666666666666667; 0.0 +1377269249; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 82485.86666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1377269549; 1; 2599.999309; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 0.8666666666666667; 0.13333333333333333; 0.0 +1377269849; 1; 2599.999309; 0.0; 0.0; 2097152.0; 116041.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377270149; 1; 2599.999309; 6.933331490666665; 0.26666666666666666; 2097152.0; 97865.33333333333; 0.0; 11.933333333333334; 0.13333333333333333; 0.0 +1377270449; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 93670.4; 0.4666666666666667; 2.466666666666667; 0.06666666666666667; 0.4666666666666667 +1377270749; 1; 2599.999309; 13.86666298133333; 0.5333333333333333; 2097152.0; 137012.8; 0.0; 0.8666666666666667; 0.13333333333333333; 0.0 +1377271049; 1; 2599.999309; 8.666664363333334; 0.33333333333333337; 2097152.0; 121633.6; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1377271349; 1; 2599.999309; 5.1999986179999995; 0.2; 2097152.0; 117438.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1377271649; 1; 2599.999309; 0.0; 0.0; 2097152.0; 76893.33333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1377271950; 1; 2599.999309; 5.1999986179999995; 0.2; 2097152.0; 92272.8; 0.0; 0.7333333333333333; 0.0; 0.0 +1377272250; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 88078.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1377272550; 1; 2599.999309; 8.666664363333334; 0.33333333333333337; 2097152.0; 121633.6; 12.733333333333333; 16.733333333333334; 0.3333333333333333; 0.13333333333333333 +1377272850; 1; 2599.999309; 0.0; 0.0; 2097152.0; 131420.53333333333; 0.0; 1.6; 0.0; 0.0 +1377273150; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 85282.13333333333; 0.0; 1.2; 0.0; 0.0 +1377273450; 1; 2599.999309; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 0.8; 0.0; 0.0 +1377273750; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 100661.6; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1377274050; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 86680.26666666666; 0.0; 2.2; 0.06666666666666667; 0.4666666666666667 +1377274350; 1; 2599.999309; 6.933331490666665; 0.26666666666666666; 2097152.0; 114642.4; 0.0; 1.0; 0.0; 0.0 +1377274650; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 144002.66666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377274950; 1; 2599.999309; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1377275250; 1; 2599.999309; 0.0; 0.0; 2097152.0; 76893.33333333333; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1377275550; 1; 2599.999309; 0.0; 0.0; 2097152.0; 130021.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377275850; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 132818.4; 0.0; 1.2; 0.0; 0.0 +1377276150; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 121633.6; 0.0; 0.6666666666666666; 0.0; 0.0 +1377276450; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 99263.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377276750; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 100661.6; 0.0; 0.7333333333333333; 0.0; 0.0 +1377277050; 1; 2599.999309; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.7333333333333333; 0.0; 0.0 +1377277350; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 130021.86666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1377277650; 1; 2599.999309; 5.1999986179999995; 0.2; 2097152.0; 131420.26666666666; 0.0; 2.2666666666666666; 0.06666666666666667; 0.4666666666666667 +1377277950; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 142604.0; 0.0; 0.8; 0.0; 0.0 +1377278250; 1; 2599.999309; 0.0; 0.0; 2097152.0; 95069.06666666667; 0.0; 0.8; 0.0; 0.0 +1377278550; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 97865.33333333333; 0.0; 1.0; 0.0; 0.0 +1377278850; 1; 2599.999309; 5.1999986179999995; 0.2; 2097152.0; 103457.86666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1377279150; 1; 2599.999309; 6.933331490666665; 0.26666666666666666; 2097152.0; 130022.4; 0.0; 0.8; 0.0; 0.0 +1377279450; 1; 2599.999309; 5.1999986179999995; 0.2; 2097152.0; 128624.26666666666; 0.0; 6.866666666666666; 0.26666666666666666; 0.2 +1377279750; 1; 2599.999309; 0.0; 0.0; 2097152.0; 82485.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377280050; 1; 2599.999309; 0.0; 0.0; 2097152.0; 86680.26666666666; 0.0; 1.1333333333333333; 0.0; 0.0 +1377280350; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 100661.6; 0.0; 0.8; 0.0; 0.0 +1377280650; 1; 2599.999309; 0.0; 0.0; 2097152.0; 116041.06666666667; 0.0; 0.6666666666666666; 0.0; 0.0 +1377280950; 1; 2599.999309; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 0.8; 0.06666666666666667; 0.0 +1377281250; 1; 2599.999309; 5.1999986179999995; 0.2; 2097152.0; 99262.93333333333; 0.0; 2.533333333333333; 0.13333333333333333; 0.4666666666666667 +1377281551; 1; 2599.999309; 8.666664363333334; 0.33333333333333337; 2097152.0; 171964.0; 0.06666666666666667; 1.0666666666666667; 0.06666666666666667; 0.0 +1377281851; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 153790.4; 0.0; 0.6666666666666666; 0.0; 0.0 +1377282151; 1; 2599.999309; 0.0; 0.0; 2097152.0; 145401.06666666668; 0.0; 0.8; 0.0; 0.0 +1377282451; 1; 2599.999309; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.8; 0.0; 0.0 +1377282751; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 100661.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1377283051; 1; 2599.999309; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.6666666666666666; 0.06666666666666667; 0.0 +1377283351; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 178955.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1377283651; 1; 2599.999309; 0.0; 0.0; 2097152.0; 137012.8; 0.0; 0.7333333333333333; 0.0; 0.0 +1377283951; 1; 2599.999309; 0.0; 0.0; 2097152.0; 86680.26666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1377284251; 1; 2599.999309; 0.0; 0.0; 2097152.0; 85282.13333333333; 0.0; 1.2; 0.13333333333333333; 0.0 +1377284551; 1; 2599.999309; 5.1999986179999995; 0.2; 2097152.0; 65708.26666666666; 0.0; 0.8; 0.0; 0.0 +1377284851; 1; 2599.999309; 5.1999986179999995; 0.2; 2097152.0; 100661.6; 0.06666666666666667; 2.2666666666666666; 0.06666666666666667; 0.4666666666666667 +1377285151; 1; 2599.999309; 10.399997235999999; 0.4; 2097152.0; 159382.4; 0.0; 6.933333333333334; 0.2; 0.13333333333333333 +1377285451; 1; 2599.999309; 0.0; 0.0; 2097152.0; 138411.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377285751; 1; 2599.999309; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377286051; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 106254.13333333333; 0.0; 0.8; 0.0; 0.0 +1377286351; 1; 2599.999309; 38.13332319866666; 1.4666666666666666; 2097152.0; 430614.4; 161.53333333333333; 14.333333333333334; 0.0; 0.2 +1377286651; 1; 2599.999309; 0.0; 0.0; 2097152.0; 394262.6666666667; 0.0; 1.2666666666666666; 0.0; 0.0 +1377286951; 1; 2599.999309; 5.1999986179999995; 0.2; 2097152.0; 197129.86666666667; 31.8; 3.933333333333333; 0.0; 0.0 +1377287251; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 155187.73333333334; 0.0; 1.2; 0.06666666666666667; 0.0 +1377287551; 1; 2599.999309; 0.0; 0.0; 2097152.0; 85282.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377287851; 1; 2599.999309; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.8; 0.0; 0.0 +1377288151; 1; 2599.999309; 0.0; 0.0; 2097152.0; 71300.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377288451; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 139808.8; 0.0; 2.466666666666667; 0.06666666666666667; 0.4666666666666667 +1377288751; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 181750.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377289051; 1; 2599.999309; 0.0; 0.0; 2097152.0; 120235.46666666666; 0.0; 0.8; 0.0; 0.0 +1377289351; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 93670.93333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1377289651; 1; 2599.999309; 0.0; 0.0; 2097152.0; 79689.6; 0.0; 1.1333333333333333; 0.0; 0.0 +1377289951; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 92272.8; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1377290251; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 86680.26666666666; 0.0; 0.8; 0.0; 0.0 +1377290551; 1; 2599.999309; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377290852; 1; 2599.999309; 0.0; 0.0; 2097152.0; 121633.6; 0.0; 1.6666666666666667; 0.0; 0.0 +1377291152; 1; 2599.999309; 0.0; 0.0; 2097152.0; 110448.53333333334; 0.0; 1.0666666666666667; 0.0; 0.0 +1377291452; 1; 2599.999309; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.8; 0.0; 0.0 +1377291752; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 110448.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377292052; 1; 2599.999309; 8.666664363333334; 0.33333333333333337; 2097152.0; 139808.26666666666; 0.0; 8.466666666666667; 0.3333333333333333; 0.6 +1377292352; 1; 2599.999309; 10.399997235999999; 0.4; 2097152.0; 177556.0; 0.0; 1.2; 0.0; 0.0 +1377292652; 1; 2599.999309; 5.1999986179999995; 0.2; 2097152.0; 149596.26666666666; 0.0; 0.8; 0.0; 0.0 +1377292952; 1; 2599.999309; 5.1999986179999995; 0.2; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377293251; 1; 2599.999309; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.8; 0.0; 0.0 +1377293551; 1; 2599.999309; 55.46665192533332; 2.1333333333333333; 2097152.0; 268433.6; 161.0; 21.533333333333335; 0.06666666666666667; 0.4 +1377293851; 1; 2599.999309; 15.599995854; 0.6; 2097152.0; 717224.2666666667; 0.0; 2.6666666666666665; 0.0; 0.0 +1377294151; 1; 2599.999309; 12.133330108666664; 0.4666666666666666; 2097152.0; 317366.93333333335; 31.8; 3.6; 0.0; 0.0 +1377294451; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 212508.53333333333; 0.0; 2.1333333333333333; 0.0; 0.0 +1377294751; 1; 2599.999309; 5.1999986179999995; 0.2; 2097152.0; 121633.6; 0.0; 1.8; 0.0; 0.0 +1377295051; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 85282.13333333333; 0.0; 0.8; 0.0; 0.0 +1377295352; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 89476.53333333334; 0.0; 0.8; 0.0; 0.0 +1377295652; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 132818.66666666666; 0.0; 2.2666666666666666; 0.06666666666666667; 0.5333333333333333 +1377295952; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 153789.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377296252; 1; 2599.999309; 0.0; 0.0; 2097152.0; 72698.93333333333; 0.0; 0.8; 0.0; 0.0 +1377296552; 1; 2599.999309; 0.0; 0.0; 2097152.0; 82485.86666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1377296852; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 97865.33333333333; 0.0; 0.8; 0.0; 0.0 +1377297152; 1; 2599.999309; 6.933331490666665; 0.26666666666666666; 2097152.0; 120235.46666666666; 0.0; 1.3333333333333333; 0.06666666666666667; 0.13333333333333333 +1377297452; 1; 2599.999309; 13.86666298133333; 0.5333333333333333; 2097152.0; 120235.46666666666; 0.0; 6.933333333333334; 0.26666666666666666; 0.13333333333333333 +1377297752; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 121632.8; 0.0; 0.7333333333333333; 0.0; 0.0 +1377298052; 1; 2599.999309; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1377298352; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 120235.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377298652; 1; 2599.999309; 0.0; 0.0; 2097152.0; 118837.33333333333; 0.0; 0.8; 0.0; 0.0 +1377298952; 1; 2599.999309; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377299252; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 127225.86666666667; 0.0; 2.466666666666667; 0.06666666666666667; 0.4666666666666667 +1377299552; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 166371.73333333334; 0.0; 0.8; 0.0; 0.0 +1377299852; 1; 2599.999309; 0.0; 0.0; 2097152.0; 114642.93333333333; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1377300152; 1; 2599.999309; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1377300452; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377300752; 1; 2599.999309; 10.399997235999999; 0.4; 2097152.0; 142605.6; 0.0; 1.1333333333333333; 0.0; 0.0 +1377301052; 1; 2599.999309; 10.399997235999999; 0.4; 2097152.0; 111846.66666666667; 0.0; 0.8; 0.26666666666666666; 0.0 +1377301352; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 97865.33333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1377301652; 1; 2599.999309; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 1.0666666666666667; 0.0; 0.0 +1377301952; 1; 2599.999309; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377302252; 1; 2599.999309; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 1.0; 0.0; 0.0 +1377302552; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 110448.53333333334; 0.0; 0.8; 0.0; 0.0 +1377302852; 1; 2599.999309; 5.1999986179999995; 0.2; 2097152.0; 116041.06666666667; 0.06666666666666667; 2.4; 0.06666666666666667; 0.4666666666666667 +1377303152; 1; 2599.999309; 6.933331490666665; 0.26666666666666666; 2097152.0; 188741.6; 0.0; 6.866666666666666; 0.26666666666666666; 0.13333333333333333 +1377303452; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 130022.4; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1377303752; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 88078.4; 0.0; 0.8; 0.0; 0.0 +1377304052; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 106254.13333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377304352; 1; 2599.999309; 8.666664363333334; 0.33333333333333337; 2097152.0; 157984.26666666666; 0.06666666666666667; 1.4; 0.0; 0.0 +1377304652; 1; 2599.999309; 8.666664363333334; 0.33333333333333337; 2097152.0; 86680.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377304953; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 76893.33333333333; 0.0; 1.2; 0.0; 0.0 +1377305253; 1; 2599.999309; 0.0; 0.0; 2097152.0; 79689.6; 0.0; 0.8; 0.0; 0.0 +1377305553; 1; 2599.999309; 0.0; 0.0; 2097152.0; 118837.33333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1377305853; 1; 2599.999309; 0.0; 0.0; 2097152.0; 128624.26666666666; 0.0; 0.8; 0.0; 0.0 +1377306153; 1; 2599.999309; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1377306453; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 113244.8; 0.0; 2.1333333333333333; 0.06666666666666667; 0.4666666666666667 +1377306753; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 127225.33333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1377307053; 1; 2599.999309; 5.1999986179999995; 0.2; 2097152.0; 120234.66666666667; 0.0; 0.8; 0.0; 0.0 +1377307353; 1; 2599.999309; 0.0; 0.0; 2097152.0; 134216.8; 0.0; 0.8; 0.0; 0.0 +1377307653; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 97865.33333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1377307953; 1; 2599.999309; 6.933331490666665; 0.26666666666666666; 2097152.0; 102059.73333333334; 0.0; 0.8; 0.0; 0.0 +1377308253; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 109050.4; 0.0; 0.8; 0.0; 0.0 +1377308553; 1; 2599.999309; 5.1999986179999995; 0.2; 2097152.0; 97865.33333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377308853; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 97865.33333333333; 0.0; 0.8; 0.06666666666666667; 0.0 +1377309153; 1; 2599.999309; 6.933331490666665; 0.26666666666666666; 2097152.0; 109050.4; 0.0; 7.133333333333334; 0.2; 0.13333333333333333 +1377309453; 1; 2599.999309; 0.0; 0.0; 2097152.0; 149596.0; 0.0; 0.6; 0.0; 0.0 +1377309753; 1; 2599.999309; 10.399997235999999; 0.4; 2097152.0; 114642.4; 0.0; 1.1333333333333333; 0.0; 0.0 +1377310053; 1; 2599.999309; 15.599995854; 0.6; 2097152.0; 121633.33333333333; 0.0; 2.1333333333333333; 0.06666666666666667; 0.4666666666666667 +1377310353; 1; 2599.999309; 8.666664363333334; 0.33333333333333337; 2097152.0; 171965.06666666668; 0.0; 1.2; 0.0; 0.0 +1377310653; 1; 2599.999309; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377310953; 1; 2599.999309; 0.0; 0.0; 2097152.0; 132818.66666666666; 0.0; 0.6; 0.0; 0.0 +1377311253; 1; 2599.999309; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 0.6; 0.13333333333333333; 0.0 +1377311553; 1; 2599.999309; 0.0; 0.0; 2097152.0; 114642.93333333333; 0.0; 0.6; 0.0; 0.0 +1377311853; 1; 2599.999309; 0.0; 0.0; 2097152.0; 72698.93333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1377312153; 1; 2599.999309; 0.0; 0.0; 2097152.0; 75495.2; 0.0; 0.6666666666666666; 0.0; 0.0 +1377312453; 1; 2599.999309; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 0.8; 0.0; 0.0 +1377312753; 1; 2599.999309; 0.0; 0.0; 2097152.0; 72698.93333333333; 0.0; 0.6; 0.0; 0.0 +1377313053; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 93670.93333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1377313353; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 132818.66666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1377313653; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 162178.13333333333; 0.06666666666666667; 2.2666666666666666; 0.06666666666666667; 0.5333333333333333 +1377313953; 1; 2599.999309; 8.666664363333334; 0.33333333333333337; 2097152.0; 164974.4; 0.0; 11.6; 0.0; 0.0 +1377314253; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 102059.73333333334; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377314553; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 103457.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377314853; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 106254.13333333333; 0.0; 0.6666666666666666; 0.0; 0.0 +1377315154; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 109050.4; 0.0; 0.6666666666666666; 0.0; 0.0 +1377315454; 1; 2599.999309; 6.933331490666665; 0.26666666666666666; 2097152.0; 92272.8; 0.0; 0.8; 0.0; 0.0 +1377315754; 1; 2599.999309; 8.666664363333334; 0.33333333333333337; 2097152.0; 139807.73333333334; 0.0; 6.933333333333334; 0.26666666666666666; 0.2 +1377316054; 1; 2599.999309; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 1.2; 0.0; 0.0 +1377316354; 1; 2599.999309; 0.0; 0.0; 2097152.0; 86680.26666666666; 0.0; 0.6; 0.0; 0.0 +1377316654; 1; 2599.999309; 0.0; 0.0; 2097152.0; 116041.06666666667; 0.0; 0.6; 0.0; 0.0 +1377316954; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 125828.0; 0.0; 0.8; 0.0; 0.0 +1377317254; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 124429.6; 0.13333333333333333; 2.2666666666666666; 0.06666666666666667; 0.4666666666666667 +1377317554; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 180353.6; 0.0; 0.6666666666666666; 0.0; 0.0 +1377317854; 1; 2599.999309; 0.0; 0.0; 2097152.0; 144003.2; 0.0; 0.8; 0.0; 0.0 +1377318154; 1; 2599.999309; 0.0; 0.0; 2097152.0; 75495.2; 0.0; 0.7333333333333333; 0.0; 0.0 +1377318454; 1; 2599.999309; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 0.6; 0.0; 0.0 +1377318754; 1; 2599.999309; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 0.8; 0.0; 0.0 +1377319054; 1; 2599.999309; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 0.6; 0.0; 0.0 +1377319354; 1; 2599.999309; 0.0; 0.0; 2097152.0; 116041.06666666667; 0.0; 1.0; 0.0; 0.0 +1377319654; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 93670.93333333333; 0.0; 0.6666666666666666; 0.0; 0.0 +1377319954; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 123031.73333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1377320254; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 74097.06666666667; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1377320554; 1; 2599.999309; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377320854; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 128624.26666666666; 0.06666666666666667; 2.0; 0.06666666666666667; 0.4666666666666667 +1377321154; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 171963.73333333334; 0.0; 0.6666666666666666; 0.0; 0.0 +1377321454; 1; 2599.999309; 0.0; 0.0; 2097152.0; 110448.53333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1377321754; 1; 2599.999309; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.8; 0.0; 0.0 +1377322054; 1; 2599.999309; 5.1999986179999995; 0.2; 2097152.0; 128624.26666666666; 0.0; 6.6; 0.26666666666666666; 0.13333333333333333 +1377322354; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 131420.53333333333; 0.0; 0.6; 0.0; 0.0 +1377322655; 1; 2599.999309; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.6; 0.0; 0.0 +1377322955; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 104856.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1377323255; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 116041.06666666667; 0.0; 1.0; 0.0; 0.0 +1377323555; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 109050.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377323855; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 128624.26666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1377324155; 1; 2599.999309; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 1.0; 0.0; 0.0 +1377324455; 1; 2599.999309; 5.1999986179999995; 0.2; 2097152.0; 109050.4; 0.0; 2.3333333333333335; 0.0; 0.4666666666666667 +1377324755; 1; 2599.999309; 8.666664363333334; 0.33333333333333337; 2097152.0; 121633.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1377325055; 1; 2599.999309; 5.1999986179999995; 0.2; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377325355; 1; 2599.999309; 0.0; 0.0; 2097152.0; 127226.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377325655; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 100661.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1377325955; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 92272.8; 0.0; 1.1333333333333333; 0.13333333333333333; 0.06666666666666667 +1377326255; 1; 2599.999309; 5.1999986179999995; 0.2; 2097152.0; 146799.2; 0.0; 1.9333333333333333; 0.0; 0.0 +1377326554; 1; 2599.999309; 5.1999986179999995; 0.2; 2097152.0; 134216.8; 0.0; 1.7333333333333334; 0.0; 0.0 +1377326854; 1; 2599.999309; 0.0; 0.0; 2097152.0; 130022.4; 0.0; 1.0; 0.0; 0.0 +1377327154; 1; 2599.999309; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 1.1333333333333333; 0.0; 0.0 +1377327454; 1; 2599.999309; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377327755; 1; 2599.999309; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377328055; 1; 2599.999309; 10.399997235999999; 0.4; 2097152.0; 134216.8; 0.06666666666666667; 8.6; 0.26666666666666666; 0.6 +1377328355; 1; 2599.999309; 10.399997235999999; 0.4; 2097152.0; 146800.0; 0.0; 1.1333333333333333; 0.0; 0.0 +1377328655; 1; 2599.999309; 5.1999986179999995; 0.2; 2097152.0; 97865.33333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377328955; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 121633.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1377329255; 1; 2599.999309; 5.1999986179999995; 0.2; 2097152.0; 100661.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1377329555; 1; 2599.999309; 0.0; 0.0; 2097152.0; 130022.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1377329855; 1; 2599.999309; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1377330155; 1; 2599.999309; 0.0; 0.0; 2097152.0; 75495.2; 0.0; 1.2; 0.0; 0.0 +1377330455; 1; 2599.999309; 0.0; 0.0; 2097152.0; 110448.53333333334; 0.0; 1.2; 0.0; 0.0 +1377330755; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 95069.06666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377331055; 1; 2599.999309; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1377331355; 1; 2599.999309; 0.0; 0.0; 2097152.0; 116041.06666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377331655; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 118837.33333333333; 0.06666666666666667; 1.9333333333333333; 0.13333333333333333; 0.5333333333333333 +1377331955; 1; 2599.999309; 12.133330108666664; 0.4666666666666666; 2097152.0; 176158.4; 0.0; 1.0; 0.0; 0.0 +1377332255; 1; 2599.999309; 5.1999986179999995; 0.2; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377332555; 1; 2599.999309; 0.0; 0.0; 2097152.0; 74097.06666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1377332855; 1; 2599.999309; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377333155; 1; 2599.999309; 0.0; 0.0; 2097152.0; 121633.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1377333455; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 90874.66666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377333755; 1; 2599.999309; 0.0; 0.0; 2097152.0; 83884.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377334055; 1; 2599.999309; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 1.1333333333333333; 0.0; 0.0 +1377334355; 1; 2599.999309; 5.1999986179999995; 0.2; 2097152.0; 132818.66666666666; 12.733333333333333; 14.066666666666666; 0.3333333333333333; 0.2 +1377334655; 1; 2599.999309; 0.0; 0.0; 2097152.0; 106254.13333333333; 0.0; 1.2; 0.0; 0.0 +1377334955; 1; 2599.999309; 0.0; 0.0; 2097152.0; 134216.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377335255; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 100661.6; 0.0; 2.6666666666666665; 0.0; 0.4666666666666667 +1377335555; 1; 2599.999309; 10.399997235999999; 0.4; 2097152.0; 150993.6; 0.0; 1.7333333333333334; 0.0; 0.0 +1377335855; 1; 2599.999309; 6.933331490666665; 0.26666666666666666; 2097152.0; 109050.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1377336155; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 116041.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377336455; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 121633.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1377336755; 1; 2599.999309; 5.1999986179999995; 0.2; 2097152.0; 92272.8; 0.0; 1.0; 0.0; 0.0 +1377337055; 1; 2599.999309; 5.1999986179999995; 0.2; 2097152.0; 109050.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1377337355; 1; 2599.999309; 0.0; 0.0; 2097152.0; 116041.06666666667; 0.0; 0.8; 0.0; 0.0 +1377337655; 1; 2599.999309; 5.1999986179999995; 0.2; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377337955; 1; 2599.999309; 0.0; 0.0; 2097152.0; 79689.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1377338255; 1; 2599.999309; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377338555; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 92272.8; 0.0; 1.0; 0.0; 0.0 +1377338855; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 110448.53333333334; 0.0; 2.2666666666666666; 0.06666666666666667; 0.4666666666666667 +1377339156; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 130022.4; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1377339456; 1; 2599.999309; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 1.0; 0.0; 0.0 +1377339756; 1; 2599.999309; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.8; 0.0; 0.0 +1377340056; 1; 2599.999309; 0.0; 0.0; 2097152.0; 57319.46666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1377340356; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 90874.66666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377340656; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 100661.6; 0.0; 0.6666666666666666; 0.0; 0.0 +1377340956; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 110448.53333333334; 0.0; 1.0; 0.0; 0.0 +1377341256; 1; 2599.999309; 8.666664363333334; 0.33333333333333337; 2097152.0; 113244.8; 0.0; 6.933333333333334; 0.2; 0.13333333333333333 +1377341556; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 100661.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1377341856; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 100661.6; 0.0; 0.8; 0.0; 0.0 +1377342156; 1; 2599.999309; 1.7333328726666664; 0.06666666666666667; 2097152.0; 79689.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1377342456; 1; 2599.999309; 8.666664363333334; 0.33333333333333337; 2097152.0; 107652.0; 0.06666666666666667; 2.466666666666667; 0.06666666666666667; 0.4666666666666667 +1377342756; 1; 2599.999309; 8.666664363333334; 0.33333333333333337; 2097152.0; 180353.6; 0.0; 1.0; 0.0; 0.0 +1377343056; 1; 2599.999309; 64.13331628866666; 2.466666666666667; 2097152.0; 328552.5333333333; 161.06666666666666; 21.533333333333335; 0.2; 0.4 +1377343356; 1; 2599.999309; 15.599995854; 0.6; 2097152.0; 658504.0; 0.0; 2.533333333333333; 0.0; 0.0 +1377343656; 1; 2599.999309; 5.1999986179999995; 0.2; 2097152.0; 268433.6; 31.8; 3.7333333333333334; 0.0; 0.0 +1377343956; 1; 2599.999309; 5.1999986179999995; 0.2; 2097152.0; 167771.2; 0.0; 2.1333333333333333; 0.0; 0.0 +1377344256; 1; 2599.999309; 10.399997235999999; 0.4; 2097152.0; 109050.4; 0.0; 1.5333333333333334; 0.0; 0.0 +1377344556; 1; 2599.999309; 0.0; 0.0; 2097152.0; 113244.0; 0.0; 1.0; 0.0; 0.0 +1377344856; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 99263.46666666666; 0.0; 1.1333333333333333; 0.0; 0.0 +1377345156; 1; 2599.999309; 3.4666657453333327; 0.13333333333333333; 2097152.0; 141207.46666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1377345456; 1; 2599.999309; 0.0; 0.0; 2097152.0; 121633.6; 0.0; 0.8; 0.0; 0.0 +1377345756; 1; 2599.999309; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.8; 0.0; 0.0 +1377346056; 1; 2599.999309; 5.1999986179999995; 0.2; 2097152.0; 110448.53333333334; 0.0; 1.9333333333333333; 0.06666666666666667; 0.4666666666666667 +1377346356; 1; 2599.999309; 8.666664363333334; 0.33333333333333337; 2097152.0; 159381.6; 0.06666666666666667; 1.1333333333333333; 0.0; 0.0 +1377346656; 1; 2599.999309; 6.933331490666665; 0.26666666666666666; 2097152.0; 120235.46666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1377346956; 1; 2599.999309; 0.0; 0.0; 2097152.0; 68504.53333333334; 0.0; 0.8; 0.06666666666666667; 0.0 +1377347256; 1; 2599.999601; 11.999998158461537; 0.4615384615384615; 2097152.0; 79044.30769230769; 0.0; 0.9166666666666666; 0.0; 0.0 +1377347556; 1; 2599.999601; 5.199999202; 0.2; 2097152.0; 107652.26666666666; 0.0; 6.866666666666666; 0.26666666666666666; 0.2 +1377347856; 1; 2599.99931; 25.9999931; 1.0; 2097152.0; 116840.0; 0.0; 0.8461538461538461; 0.0; 0.0 +1377348156; 1; 2599.99931; 13.866662986666668; 0.5333333333333333; 2097152.0; 110448.53333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377348456; 1; 2599.99931; 12.133330113333335; 0.4666666666666666; 2097152.0; 99263.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377348757; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 88078.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1377349057; 1; 2599.99931; 10.39999724; 0.4; 2097152.0; 89476.53333333334; 0.0; 1.1333333333333333; 0.0; 0.0 +1377349357; 1; 2599.99931; 8.66666436666667; 0.33333333333333337; 2097152.0; 85282.13333333333; 0.0; 0.8; 0.0; 0.0 +1377349657; 1; 2599.99931; 6.933331493333334; 0.26666666666666666; 2097152.0; 142605.06666666668; 0.06666666666666667; 2.1333333333333333; 0.06666666666666667; 0.4666666666666667 +1377349957; 1; 2599.99931; 13.866662986666668; 0.5333333333333333; 2097152.0; 162178.4; 0.0; 0.6666666666666666; 0.0; 0.0 +1377350257; 1; 2599.99931; 8.66666436666667; 0.33333333333333337; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377350557; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 123031.73333333334; 0.0; 1.0; 0.06666666666666667; 0.0 +1377350857; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 106254.13333333333; 0.0; 0.8; 0.06666666666666667; 0.0 +1377351157; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 103457.86666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1377351457; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 123031.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377351757; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 139808.53333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1377352057; 1; 2599.99931; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 0.8; 0.0; 0.0 +1377352357; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 134216.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377352657; 1; 2599.99931; 0.0; 0.0; 2097152.0; 116041.06666666667; 0.0; 0.6666666666666666; 0.0; 0.0 +1377352957; 1; 2599.99931; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1377353257; 1; 2599.99931; 6.933331493333334; 0.26666666666666666; 2097152.0; 127226.13333333333; 0.06666666666666667; 2.6666666666666665; 0.06666666666666667; 0.4666666666666667 +1377353557; 1; 2599.99931; 10.39999724; 0.4; 2097152.0; 169169.33333333334; 0.0; 7.066666666666666; 0.2; 0.13333333333333333 +1377353857; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 113244.8; 0.0; 0.8; 0.0; 0.0 +1377354157; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 123031.73333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1377354457; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 111846.66666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377354757; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 72698.93333333333; 0.06666666666666667; 1.1333333333333333; 0.06666666666666667; 0.13333333333333333 +1377355057; 1; 2599.99931; 6.933331493333334; 0.26666666666666666; 2097152.0; 89476.53333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1377355357; 1; 2599.99931; 12.133330113333335; 0.4666666666666666; 2097152.0; 95069.06666666667; 0.0; 1.8; 0.26666666666666666; 0.0 +1377355657; 1; 2599.99931; 6.933331493333334; 0.26666666666666666; 2097152.0; 99263.46666666666; 0.0; 0.8; 0.06666666666666667; 0.0 +1377355957; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 99263.46666666666; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1377356257; 1; 2599.99931; 0.0; 0.0; 2097152.0; 110448.53333333334; 0.0; 0.8; 0.0; 0.0 +1377356557; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 138411.2; 0.0; 0.7333333333333333; 0.0; 0.0 +1377356857; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 109050.4; 0.0; 2.2; 0.0; 0.4666666666666667 +1377357157; 1; 2599.99931; 8.66666436666667; 0.33333333333333337; 2097152.0; 146798.4; 0.0; 1.5333333333333334; 0.0; 0.0 +1377357457; 1; 2599.99931; 12.133330113333335; 0.4666666666666666; 2097152.0; 127226.13333333333; 0.0; 11.933333333333334; 0.0; 0.0 +1377357757; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 130021.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1377358057; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 109050.4; 0.0; 0.8; 0.0; 0.0 +1377358357; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 93670.93333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377358658; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 86680.26666666666; 0.0; 1.0; 0.0; 0.0 +1377358957; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 88078.4; 0.0; 1.0; 0.06666666666666667; 0.0 +1377359257; 1; 2599.99931; 0.0; 0.0; 2097152.0; 82485.86666666667; 0.0; 1.0; 0.06666666666666667; 0.0 +1377359557; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 85282.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377359857; 1; 2599.99931; 6.933331493333334; 0.26666666666666666; 2097152.0; 92272.8; 0.0; 6.866666666666666; 0.2; 0.13333333333333333 +1377360157; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 121633.6; 0.0; 1.0; 0.0; 0.0 +1377360457; 1; 2599.99931; 8.66666436666667; 0.33333333333333337; 2097152.0; 110448.53333333334; 0.0; 2.2; 0.06666666666666667; 0.4666666666666667 +1377360757; 1; 2599.99931; 8.66666436666667; 0.33333333333333337; 2097152.0; 163576.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377361057; 1; 2599.99931; 0.0; 0.0; 2097152.0; 82485.86666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1377361357; 1; 2599.99931; 0.0; 0.0; 2097152.0; 79689.6; 0.0; 0.7333333333333333; 0.0; 0.0 +1377361657; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377361957; 1; 2599.99931; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1377362257; 1; 2599.99931; 0.0; 0.0; 2097152.0; 82485.86666666667; 0.0; 0.8; 0.0; 0.0 +1377362557; 1; 2599.99931; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.7333333333333333; 0.0; 0.0 +1377362857; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 81087.73333333334; 0.0; 1.2; 0.0; 0.0 +1377363157; 1; 2599.99931; 0.0; 0.0; 2097152.0; 120235.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377363457; 1; 2599.99931; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 0.6; 0.0; 0.0 +1377363757; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 76893.33333333333; 0.0; 0.6; 0.0; 0.0 +1377364057; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 103457.06666666667; 0.06666666666666667; 2.2666666666666666; 0.0; 0.4666666666666667 +1377364357; 1; 2599.99931; 8.66666436666667; 0.33333333333333337; 2097152.0; 164974.66666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1377364657; 1; 2599.99931; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 0.5333333333333333; 0.0; 0.0 +1377364957; 1; 2599.99931; 0.0; 0.0; 2097152.0; 114642.93333333333; 0.0; 0.6; 0.0; 0.0 +1377365257; 1; 2599.99931; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.6666666666666666; 0.0; 0.0 +1377365558; 1; 2599.99931; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1377365858; 1; 2599.99931; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 0.8; 0.0; 0.0 +1377366158; 1; 2599.99931; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1377366458; 1; 2599.99931; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.6666666666666666; 0.0; 0.0 +1377366758; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 145401.06666666668; 0.06666666666666667; 7.066666666666666; 0.2; 0.13333333333333333 +1377367058; 1; 2599.99931; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377367358; 1; 2599.99931; 0.0; 0.0; 2097152.0; 114642.93333333333; 0.0; 0.6666666666666666; 0.0; 0.0 +1377367658; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 88078.4; 0.0; 2.1333333333333333; 0.06666666666666667; 0.4666666666666667 +1377367958; 1; 2599.99931; 0.0; 0.0; 2097152.0; 138411.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377368258; 1; 2599.99931; 0.0; 0.0; 2097152.0; 127226.13333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377368558; 1; 2599.99931; 0.0; 0.0; 2097152.0; 114642.93333333333; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377368858; 1; 2599.99931; 0.0; 0.0; 2097152.0; 125828.0; 0.0; 0.5333333333333333; 0.0; 0.0 +1377369158; 1; 2599.99931; 0.0; 0.0; 2097152.0; 118837.33333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377369458; 1; 2599.99931; 0.0; 0.0; 2097152.0; 128624.26666666666; 0.0; 0.6666666666666666; 0.26666666666666666; 0.0 +1377369758; 1; 2599.99931; 0.0; 0.0; 2097152.0; 106254.13333333333; 0.0; 1.0; 0.9333333333333333; 0.0 +1377370058; 1; 2599.99931; 0.0; 0.0; 2097152.0; 127226.13333333333; 0.0; 0.8; 0.0; 0.0 +1377370358; 1; 2599.99931; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.7333333333333333; 0.0; 0.0 +1377370658; 1; 2599.99931; 0.0; 0.0; 2097152.0; 79689.6; 0.0; 0.7333333333333333; 0.0; 0.0 +1377370958; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 81087.73333333334; 0.0; 0.6; 0.0; 0.0 +1377371258; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 86680.26666666666; 0.0; 2.4; 0.0; 0.4666666666666667 +1377371558; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 174761.06666666668; 0.0; 0.6666666666666666; 0.0; 0.0 +1377371858; 1; 2599.99931; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377372158; 1; 2599.99931; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.6666666666666666; 0.0; 0.0 +1377372458; 1; 2599.99931; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.8; 0.0; 0.0 +1377372758; 1; 2599.99931; 41.59998896; 1.6; 2097152.0; 369097.3333333333; 161.0; 14.266666666666667; 0.0; 0.2 +1377373058; 1; 2599.99931; 6.933331493333334; 0.26666666666666666; 2097152.0; 426419.2; 0.0; 7.2; 0.2; 0.13333333333333333 +1377373358; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 208315.46666666667; 31.8; 3.4; 0.06666666666666667; 0.0 +1377373658; 1; 2599.99931; 0.0; 0.0; 2097152.0; 178954.93333333332; 0.0; 1.1333333333333333; 0.0; 0.0 +1377373958; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 109050.4; 0.0; 1.0; 0.0; 0.0 +1377374259; 1; 2599.99931; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 0.8; 0.06666666666666667; 0.0 +1377374559; 1; 2599.99931; 0.0; 0.0; 2097152.0; 82485.86666666667; 0.0; 0.6666666666666666; 0.0; 0.0 +1377374859; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 106254.13333333333; 0.0; 2.466666666666667; 0.0; 0.4666666666666667 +1377375159; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 152391.73333333334; 0.0; 0.8; 0.0; 0.0 +1377375459; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 120235.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377375759; 1; 2599.99931; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377376059; 1; 2599.99931; 0.0; 0.0; 2097152.0; 130022.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1377376359; 1; 2599.99931; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377376659; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 107652.26666666666; 0.0; 1.1333333333333333; 0.0; 0.0 +1377376959; 1; 2599.99931; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1377377259; 1; 2599.99931; 0.0; 0.0; 2097152.0; 83884.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377377559; 1; 2599.99931; 0.0; 0.0; 2097152.0; 72698.93333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377377859; 1; 2599.99931; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 0.8; 0.0; 0.0 +1377378159; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 96467.2; 0.6666666666666666; 1.5333333333333334; 0.0; 0.0 +1377378459; 1; 2599.99931; 6.933331493333334; 0.26666666666666666; 2097152.0; 100661.33333333333; 0.06666666666666667; 2.4; 0.06666666666666667; 0.4666666666666667 +1377378759; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 159381.86666666667; 0.0; 0.8; 0.0; 0.0 +1377379059; 1; 2599.99931; 0.0; 0.0; 2097152.0; 71300.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377379359; 1; 2599.99931; 6.933331493333334; 0.26666666666666666; 2097152.0; 75495.2; 0.06666666666666667; 7.2; 0.2; 0.13333333333333333 +1377379659; 1; 2599.99931; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 1.0; 0.0; 0.0 +1377379959; 1; 2599.99931; 0.0; 0.0; 2097152.0; 134216.8; 0.0; 1.5333333333333334; 0.0; 0.0 +1377380259; 1; 2599.99931; 0.0; 0.0; 2097152.0; 130021.6; 0.0; 0.6666666666666666; 0.0; 0.0 +1377380559; 1; 2599.99931; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1377380859; 1; 2599.99931; 0.0; 0.0; 2097152.0; 82485.86666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1377381159; 1; 2599.99931; 0.0; 0.0; 2097152.0; 74097.06666666667; 0.0; 0.8; 0.0; 0.0 +1377381459; 1; 2599.99931; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.6666666666666666; 0.0; 0.0 +1377381759; 1; 2599.99931; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1377382059; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 99262.66666666667; 0.0; 2.2666666666666666; 0.06666666666666667; 0.4666666666666667 +1377382359; 1; 2599.99931; 6.933331493333334; 0.26666666666666666; 2097152.0; 184547.46666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377382659; 1; 2599.99931; 0.0; 0.0; 2097152.0; 116041.06666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1377382959; 1; 2599.99931; 0.0; 0.0; 2097152.0; 86680.26666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1377383259; 1; 2599.99931; 0.0; 0.0; 2097152.0; 114642.93333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377383559; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 90874.66666666667; 0.0; 1.7333333333333334; 0.06666666666666667; 0.13333333333333333 +1377383859; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 90874.66666666667; 0.0; 1.0; 0.0; 0.0 +1377384159; 1; 2599.99931; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 0.8; 0.0; 0.0 +1377384459; 1; 2599.99931; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1377384759; 1; 2599.99931; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1377385059; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 113244.8; 0.06666666666666667; 7.066666666666666; 0.26666666666666666; 0.13333333333333333 +1377385360; 1; 2599.99931; 0.0; 0.0; 2097152.0; 139808.53333333333; 0.0; 0.6; 0.0; 0.0 +1377385660; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 138411.2; 0.0; 2.2666666666666666; 0.0; 0.4666666666666667 +1377385960; 1; 2599.99931; 0.0; 0.0; 2097152.0; 138410.4; 0.0; 0.6666666666666666; 0.0; 0.0 +1377386260; 1; 2599.99931; 0.0; 0.0; 2097152.0; 95069.06666666667; 0.0; 0.8; 0.06666666666666667; 0.0 +1377386560; 1; 2599.99931; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 0.8; 0.0; 0.0 +1377386860; 1; 2599.99931; 0.0; 0.0; 2097152.0; 113244.0; 0.0; 0.8; 0.0; 0.0 +1377387160; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 142604.8; 0.0; 0.8; 0.0; 0.0 +1377387460; 1; 2599.99931; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.6666666666666666; 0.0; 0.0 +1377387760; 1; 2599.99931; 0.0; 0.0; 2097152.0; 86680.26666666666; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377388060; 1; 2599.99931; 0.0; 0.0; 2097152.0; 69902.66666666667; 0.0; 1.0; 0.0; 0.0 +1377388360; 1; 2599.99931; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1377388660; 1; 2599.99931; 0.0; 0.0; 2097152.0; 82485.86666666667; 0.0; 0.6666666666666666; 0.0; 0.0 +1377388960; 1; 2599.99931; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 0.6; 0.0; 0.0 +1377389260; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 130021.86666666667; 0.0; 2.8; 0.0; 0.4666666666666667 +1377389560; 1; 2599.99931; 0.0; 0.0; 2097152.0; 178954.13333333333; 0.0; 0.8; 0.0; 0.0 +1377389860; 1; 2599.99931; 0.0; 0.0; 2097152.0; 137013.06666666668; 0.0; 0.6; 0.0; 0.0 +1377390160; 1; 2599.99931; 0.0; 0.0; 2097152.0; 114642.93333333333; 0.0; 0.6; 0.0; 0.0 +1377390460; 1; 2599.99931; 0.0; 0.0; 2097152.0; 71300.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377390760; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 61513.86666666667; 0.0; 1.3333333333333333; 0.0; 0.0 +1377391060; 1; 2599.99931; 0.0; 0.0; 2097152.0; 120234.66666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1377391360; 1; 2599.99931; 57.19998482000001; 2.2; 2097152.0; 357911.73333333334; 161.0; 21.733333333333334; 0.06666666666666667; 0.3333333333333333 +1377391660; 1; 2599.99931; 6.933331493333334; 0.26666666666666666; 2097152.0; 665494.4; 0.0; 2.2666666666666666; 0.0; 0.0 +1377391961; 1; 2599.99931; 8.66666436666667; 0.33333333333333337; 2097152.0; 247461.06666666668; 44.6; 15.8; 0.3333333333333333; 0.2 +1377392261; 1; 2599.99931; 6.933331493333334; 0.26666666666666666; 2097152.0; 178954.66666666666; 0.0; 2.466666666666667; 0.0; 0.0 +1377392561; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 146800.0; 0.0; 1.4666666666666666; 0.0; 0.0 +1377392861; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 121633.33333333333; 0.0; 2.066666666666667; 0.06666666666666667; 0.4666666666666667 +1377393161; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 174760.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377393461; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 88078.4; 0.0; 1.2; 0.0; 0.0 +1377393761; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 74097.06666666667; 0.0; 0.6; 0.0; 0.0 +1377394061; 1; 2599.99931; 0.0; 0.0; 2097152.0; 82485.86666666667; 0.0; 0.6; 0.0; 0.0 +1377394361; 1; 2599.99931; 0.0; 0.0; 2097152.0; 79689.6; 0.0; 0.6666666666666666; 0.0; 0.0 +1377394661; 1; 2599.99931; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 1.2; 0.0; 0.0 +1377394961; 1; 2599.99931; 0.0; 0.0; 2097152.0; 106254.13333333333; 0.0; 0.6666666666666666; 0.0; 0.0 +1377395261; 1; 2599.99931; 0.0; 0.0; 2097152.0; 82485.86666666667; 0.0; 0.8; 0.0; 0.0 +1377395561; 1; 2599.99931; 0.0; 0.0; 2097152.0; 82485.86666666667; 0.0; 0.6; 0.0; 0.0 +1377395861; 1; 2599.99931; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1377396161; 1; 2599.99931; 0.0; 0.0; 2097152.0; 110448.53333333334; 0.0; 1.0; 0.0; 0.0 +1377396461; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 120235.46666666666; 0.0; 2.066666666666667; 0.06666666666666667; 0.4666666666666667 +1377396761; 1; 2599.99931; 8.66666436666667; 0.33333333333333337; 2097152.0; 162178.66666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1377397061; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377397361; 1; 2599.99931; 0.0; 0.0; 2097152.0; 86680.26666666666; 0.0; 1.2; 0.0; 0.0 +1377397661; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 75495.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1377397961; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 82485.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377398261; 1; 2599.99931; 0.0; 0.0; 2097152.0; 114642.93333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377398561; 1; 2599.99931; 6.933331493333334; 0.26666666666666666; 2097152.0; 148197.06666666668; 0.0; 7.2; 0.2; 0.13333333333333333 +1377398861; 1; 2599.99931; 0.0; 0.0; 2097152.0; 130021.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1377399161; 1; 2599.99931; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1377399461; 1; 2599.99931; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377399761; 1; 2599.99931; 0.0; 0.0; 2097152.0; 62912.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377400061; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 71300.8; 0.0; 2.6; 0.06666666666666667; 0.4666666666666667 +1377400362; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 128624.26666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1377400662; 1; 2599.99931; 0.0; 0.0; 2097152.0; 120235.46666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1377400962; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 130022.4; 0.0; 11.733333333333333; 0.0; 0.0 +1377401262; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 130022.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1377401562; 1; 2599.99931; 0.0; 0.0; 2097152.0; 83884.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1377401862; 1; 2599.99931; 0.0; 0.0; 2097152.0; 60115.73333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377402162; 1; 2599.99931; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 1.0; 0.0; 0.0 +1377402462; 1; 2599.99931; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1377402762; 1; 2599.99931; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 1.0; 0.0; 0.0 +1377403062; 1; 2599.99931; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 1.0666666666666667; 0.0; 0.0 +1377403362; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 90874.66666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377403662; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 148197.33333333334; 0.0; 2.3333333333333335; 0.06666666666666667; 0.4666666666666667 +1377403962; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 205519.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377404262; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 125828.0; 0.0; 1.0; 0.0; 0.0 +1377404562; 1; 2599.99931; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 1.0666666666666667; 0.0; 0.0 +1377404862; 1; 2599.99931; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377405162; 1; 2599.99931; 8.66666436666667; 0.33333333333333337; 2097152.0; 104856.0; 0.0; 6.866666666666666; 0.2; 0.13333333333333333 +1377405462; 1; 2599.99931; 0.0; 0.0; 2097152.0; 138410.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377405762; 1; 2599.99931; 0.0; 0.0; 2097152.0; 128624.26666666666; 0.0; 1.2; 0.0; 0.0 +1377406062; 1; 2599.99931; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377406362; 1; 2599.99931; 0.0; 0.0; 2097152.0; 86680.26666666666; 0.0; 1.0; 0.0; 0.0 +1377406662; 1; 2599.99931; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377406962; 1; 2599.99931; 0.0; 0.0; 2097152.0; 95069.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377407262; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 83884.0; 0.0; 2.533333333333333; 0.06666666666666667; 0.5333333333333333 +1377407562; 1; 2599.99931; 0.0; 0.0; 2097152.0; 142604.8; 0.06666666666666667; 1.0666666666666667; 0.0; 0.0 +1377407862; 1; 2599.99931; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377408162; 1; 2599.99931; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377408462; 1; 2599.99931; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 1.0666666666666667; 0.0; 0.0 +1377408762; 1; 2599.99931; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377409062; 1; 2599.99931; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1377409362; 1; 2599.99931; 0.0; 0.0; 2097152.0; 125828.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1377409662; 1; 2599.99931; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1377409962; 1; 2599.99931; 0.0; 0.0; 2097152.0; 79689.6; 0.0; 1.2; 0.0; 0.0 +1377410262; 1; 2599.99931; 0.0; 0.0; 2097152.0; 83884.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377410562; 1; 2599.99931; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377410862; 1; 2599.99931; 10.39999724; 0.4; 2097152.0; 159382.4; 0.06666666666666667; 8.533333333333333; 0.26666666666666666; 0.6 +1377411162; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 163576.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377411462; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 113244.8; 0.0; 1.2; 0.0; 0.0 +1377411763; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 116041.06666666667; 0.0; 0.8; 0.0; 0.0 +1377412063; 1; 2599.99931; 0.0; 0.0; 2097152.0; 95069.06666666667; 0.0; 0.8; 0.0; 0.0 +1377412363; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 96467.2; 0.06666666666666667; 1.4666666666666666; 0.06666666666666667; 0.13333333333333333 +1377412663; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 138410.4; 0.0; 2.066666666666667; 0.0; 0.0 +1377412963; 1; 2599.99931; 0.0; 0.0; 2097152.0; 131420.53333333333; 0.0; 1.4666666666666666; 0.0; 0.0 +1377413263; 1; 2599.99931; 0.0; 0.0; 2097152.0; 83884.0; 0.0; 0.8; 0.0; 0.0 +1377413563; 1; 2599.99931; 0.0; 0.0; 2097152.0; 61513.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377413863; 1; 2599.99931; 0.0; 0.0; 2097152.0; 72698.93333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377414163; 1; 2599.99931; 0.0; 0.0; 2097152.0; 113244.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377414463; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 142604.8; 0.06666666666666667; 2.2666666666666666; 0.0; 0.4666666666666667 +1377414763; 1; 2599.99931; 6.933331493333334; 0.26666666666666666; 2097152.0; 176158.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377415063; 1; 2599.99931; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8; 0.0; 0.0 +1377415363; 1; 2599.99931; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377415663; 1; 2599.99931; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377415963; 1; 2599.99931; 0.0; 0.0; 2097152.0; 78291.46666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1377416263; 1; 2599.99931; 0.0; 0.0; 2097152.0; 95069.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377416563; 1; 2599.99931; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 1.2; 0.0; 0.0 +1377416863; 1; 2599.99931; 0.0; 0.0; 2097152.0; 83884.0; 0.0; 1.2666666666666666; 0.0; 0.0 +1377417163; 1; 2599.99931; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1377417463; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 82485.86666666667; 0.0; 6.866666666666666; 0.26666666666666666; 0.13333333333333333 +1377417763; 1; 2599.99931; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377418063; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 90874.66666666667; 0.0; 2.1333333333333333; 0.06666666666666667; 0.4666666666666667 +1377418363; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 138410.4; 0.0; 1.1333333333333333; 0.0; 0.0 +1377418663; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 113244.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1377418963; 1; 2599.99931; 0.0; 0.0; 2097152.0; 104855.2; 0.0; 0.8; 0.0; 0.0 +1377419263; 1; 2599.99931; 0.0; 0.0; 2097152.0; 64310.13333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377419564; 1; 2599.99931; 0.0; 0.0; 2097152.0; 76893.33333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1377419864; 1; 2599.99931; 0.0; 0.0; 2097152.0; 85282.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377420164; 1; 2599.99931; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1377420464; 1; 2599.99931; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.7333333333333333; 0.0; 0.0 +1377420764; 1; 2599.99931; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 1.0; 0.0; 0.0 +1377421064; 1; 2599.99931; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 1.0666666666666667; 0.0; 0.0 +1377421364; 1; 2599.99931; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1377421664; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 125827.2; 0.0; 2.3333333333333335; 0.06666666666666667; 0.4666666666666667 +1377421964; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 205519.2; 0.0; 0.7333333333333333; 0.0; 0.0 +1377422264; 1; 2599.99931; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1377422564; 1; 2599.99931; 0.0; 0.0; 2097152.0; 75495.2; 0.0; 0.8; 0.0; 0.0 +1377422864; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 97865.33333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377423164; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 88078.4; 0.0; 0.8; 0.0; 0.0 +1377423464; 1; 2599.99931; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.7333333333333333; 0.0; 0.0 +1377424064; 1; 2599.99931; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 0.6666666666666666; 0.0; 0.0 +1377424364; 1; 2599.99931; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 1.6; 0.0; 0.0 +1377424663; 1; 2599.99931; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8; 0.0; 0.0 +1377424963; 1; 2599.99931; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377425263; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 109050.4; 0.06666666666666667; 2.933333333333333; 0.0; 0.4666666666666667 +1377425563; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 130021.6; 0.0; 0.7333333333333333; 0.0; 0.0 +1377425863; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 111846.66666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1377426163; 1; 2599.99931; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.7333333333333333; 0.0; 0.0 +1377426463; 1; 2599.99931; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 1.0; 0.0; 0.0 +1377426763; 1; 2599.99931; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.8; 0.0; 0.0 +1377427063; 1; 2599.99931; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377427363; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 95069.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377427663; 1; 2599.99931; 0.0; 0.0; 2097152.0; 121633.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1377427963; 1; 2599.99931; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 1.1333333333333333; 0.0; 0.0 +1377428263; 1; 2599.99931; 0.0; 0.0; 2097152.0; 110448.53333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1377428563; 1; 2599.99931; 0.0; 0.0; 2097152.0; 64310.13333333333; 0.0; 0.8; 0.0; 0.0 +1377428863; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 81087.73333333334; 0.06666666666666667; 2.2; 0.06666666666666667; 0.4666666666666667 +1377429163; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 117439.2; 0.0; 0.8; 0.0; 0.0 +1377429463; 1; 2599.99931; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377429763; 1; 2599.99931; 0.0; 0.0; 2097152.0; 82485.86666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1377430063; 1; 2599.99931; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377430363; 1; 2599.99931; 0.0; 0.0; 2097152.0; 120235.46666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1377430664; 1; 2599.99931; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 1.0; 0.0; 0.0 +1377430964; 1; 2599.99931; 6.933331493333334; 0.26666666666666666; 2097152.0; 130021.6; 0.0; 6.866666666666666; 0.2; 0.13333333333333333 +1377431264; 1; 2599.99931; 0.0; 0.0; 2097152.0; 82485.86666666667; 0.0; 1.2; 0.0; 0.0 +1377431564; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 96467.2; 0.0; 0.7333333333333333; 0.0; 0.0 +1377431864; 1; 2599.99931; 0.0; 0.0; 2097152.0; 125828.0; 0.0; 0.6666666666666666; 0.0; 0.0 +1377432164; 1; 2599.99931; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 1.2; 0.0; 0.0 +1377432464; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 118837.33333333333; 0.0; 2.3333333333333335; 0.06666666666666667; 0.4666666666666667 +1377432764; 1; 2599.99931; 0.0; 0.0; 2097152.0; 144002.93333333332; 0.0; 0.6666666666666666; 0.0; 0.0 +1377433064; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 156585.6; 0.0; 0.7333333333333333; 0.0; 0.0 +1377433364; 1; 2599.99931; 0.0; 0.0; 2097152.0; 118837.06666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377433664; 1; 2599.99931; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377433964; 1; 2599.99931; 0.0; 0.0; 2097152.0; 166372.26666666666; 0.0; 0.8; 0.0; 0.0 +1377434264; 1; 2599.99931; 0.0; 0.0; 2097152.0; 125826.93333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1377434564; 1; 2599.99931; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 1.0; 0.0; 0.0 +1377434864; 1; 2599.99931; 0.0; 0.0; 2097152.0; 95069.06666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1377435164; 1; 2599.99931; 0.0; 0.0; 2097152.0; 114642.93333333333; 0.0; 0.8; 0.0; 0.0 +1377435464; 1; 2599.99931; 0.0; 0.0; 2097152.0; 123031.73333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1377435764; 1; 2599.99931; 0.0; 0.0; 2097152.0; 146800.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377436064; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 117439.2; 0.0; 2.2666666666666666; 0.06666666666666667; 0.4666666666666667 +1377436364; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 195731.46666666667; 0.0; 1.2; 0.0; 0.0 +1377436664; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 124429.6; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1377436964; 1; 2599.99931; 0.0; 0.0; 2097152.0; 123031.73333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1377437264; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 118837.33333333333; 0.06666666666666667; 7.133333333333334; 0.2; 0.13333333333333333 +1377437564; 1; 2599.99931; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1377437864; 1; 2599.99931; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 0.6; 0.0; 0.0 +1377438164; 1; 2599.99931; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.8; 0.0; 0.0 +1377438464; 1; 2599.99931; 0.0; 0.0; 2097152.0; 116041.06666666667; 0.0; 0.8; 0.0; 0.0 +1377438764; 1; 2599.99931; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.5333333333333333; 0.0; 0.0 +1377439064; 1; 2599.99931; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1377439364; 1; 2599.99931; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.6666666666666666; 0.0; 0.0 +1377439664; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 138411.2; 0.06666666666666667; 2.3333333333333335; 0.0; 0.4666666666666667 +1377439964; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 157984.26666666666; 0.0; 0.6666666666666666; 0.0; 0.0 +1377440264; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 100661.6; 0.0; 0.8; 0.0; 0.0 +1377440564; 1; 2599.99931; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 0.6666666666666666; 0.0; 0.0 +1377440864; 1; 2599.99931; 0.0; 0.0; 2097152.0; 79689.6; 0.0; 0.6666666666666666; 0.0; 0.0 +1377441164; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 135614.93333333332; 0.0; 1.0; 0.06666666666666667; 0.06666666666666667 +1377441464; 1; 2599.99931; 0.0; 0.0; 2097152.0; 146798.4; 0.0; 0.8; 0.0; 0.0 +1377441764; 1; 2599.99931; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 1.0; 0.0; 0.0 +1377442064; 1; 2599.99931; 0.0; 0.0; 2097152.0; 71300.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377442364; 1; 2599.99931; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.6; 0.0; 0.0 +1377442665; 1; 2599.99931; 0.0; 0.0; 2097152.0; 134216.8; 0.0; 0.5333333333333333; 0.0; 0.0 +1377442965; 1; 2599.99931; 0.0; 0.0; 2097152.0; 150993.6; 0.0; 0.6666666666666666; 0.0; 0.0 +1377443265; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 118837.33333333333; 0.06666666666666667; 2.466666666666667; 0.0; 0.4666666666666667 +1377443565; 1; 2599.99931; 0.0; 0.0; 2097152.0; 150993.6; 0.0; 0.7333333333333333; 0.0; 0.0 +1377443865; 1; 2599.99931; 6.933331493333334; 0.26666666666666666; 2097152.0; 123031.73333333334; 0.0; 6.866666666666666; 0.2; 0.13333333333333333 +1377444165; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 114642.93333333333; 0.0; 0.5333333333333333; 0.0; 0.0 +1377444465; 1; 2599.99931; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 1.0; 0.0; 0.0 +1377444765; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 93670.93333333333; 0.0; 11.533333333333333; 0.0; 0.0 +1377445065; 1; 2599.99931; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1377445365; 1; 2599.99931; 0.0; 0.0; 2097152.0; 89476.53333333334; 0.0; 0.6666666666666666; 0.0; 0.0 +1377445665; 1; 2599.99931; 0.0; 0.0; 2097152.0; 110448.53333333334; 0.0; 1.0666666666666667; 0.0; 0.0 +1377445965; 1; 2599.99931; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1377446265; 1; 2599.99931; 0.0; 0.0; 2097152.0; 68504.53333333334; 0.0; 0.6666666666666666; 0.0; 0.0 +1377446565; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 99263.46666666666; 0.06666666666666667; 1.1333333333333333; 0.0; 0.0 +1377446865; 1; 2599.99931; 6.933331493333334; 0.26666666666666666; 2097152.0; 144002.4; 0.0; 1.9333333333333333; 0.0; 0.4666666666666667 +1377447165; 1; 2599.99931; 62.39998344000001; 2.4; 2097152.0; 454381.6; 161.0; 21.733333333333334; 0.0; 0.4 +1377447465; 1; 2599.99931; 6.933331493333334; 0.26666666666666666; 2097152.0; 671087.2; 0.0; 2.533333333333333; 0.0; 0.0 +1377447765; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 264238.93333333335; 31.8; 3.3333333333333335; 0.0; 0.0 +1377448065; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 150993.33333333334; 0.0; 2.2666666666666666; 0.0; 0.0 +1377448365; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 139808.53333333333; 0.0; 1.3333333333333333; 0.0; 0.0 +1377448665; 1; 2599.99931; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 1.2; 0.0; 0.0 +1377448965; 1; 2599.99931; 0.0; 0.0; 2097152.0; 130022.4; 0.0; 0.6; 0.0; 0.0 +1377449265; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 159382.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1377449565; 1; 2599.99931; 0.0; 0.0; 2097152.0; 106254.13333333333; 0.0; 0.6; 0.0; 0.0 +1377449865; 1; 2599.99931; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.6666666666666666; 0.0; 0.0 +1377450165; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 130021.86666666667; 12.733333333333333; 13.333333333333334; 0.3333333333333333; 0.13333333333333333 +1377450465; 1; 2599.99931; 6.933331493333334; 0.26666666666666666; 2097152.0; 233481.06666666668; 0.06666666666666667; 2.4; 0.0; 0.4666666666666667 +1377450765; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 194334.66666666666; 0.0; 1.2666666666666666; 0.0; 0.0 +1377451065; 1; 2599.99931; 0.0; 0.0; 2097152.0; 120235.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377451365; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 120235.46666666666; 0.0; 1.0; 0.0; 0.0 +1377451665; 1; 2599.99931; 0.0; 0.0; 2097152.0; 118837.33333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1377451965; 1; 2599.99931; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377452265; 1; 2599.99931; 0.0; 0.0; 2097152.0; 89476.53333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377452566; 1; 2599.99931; 0.0; 0.0; 2097152.0; 106254.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1377452866; 1; 2599.99931; 0.0; 0.0; 2097152.0; 75495.2; 0.0; 1.2; 0.0; 0.0 +1377453166; 1; 2599.99931; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 1.0; 0.0; 0.0 +1377453466; 1; 2599.99931; 0.0; 0.0; 2097152.0; 89476.53333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377453766; 1; 2599.99931; 0.0; 0.0; 2097152.0; 127226.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377454066; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 114642.4; 0.0; 2.6; 0.06666666666666667; 0.5333333333333333 +1377454366; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 171964.53333333333; 0.0; 0.8; 0.0; 0.0 +1377454666; 1; 2599.99931; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377454966; 1; 2599.99931; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1377455266; 1; 2599.99931; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1377455566; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 117439.2; 0.0; 1.1333333333333333; 0.0; 0.0 +1377455866; 1; 2599.99931; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377456166; 1; 2599.99931; 0.0; 0.0; 2097152.0; 75495.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1377456466; 1; 2599.99931; 0.0; 0.0; 2097152.0; 130022.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377456766; 1; 2599.99931; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 1.0; 0.0; 0.0 +1377457066; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 141207.46666666667; 0.0; 7.266666666666667; 0.26666666666666666; 0.13333333333333333 +1377457366; 1; 2599.99931; 0.0; 0.0; 2097152.0; 118837.33333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377457666; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 142604.8; 0.0; 2.4; 0.06666666666666667; 0.4666666666666667 +1377457966; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 174760.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1377458266; 1; 2599.99931; 0.0; 0.0; 2097152.0; 110448.53333333334; 0.0; 1.0; 0.0; 0.0 +1377458566; 1; 2599.99931; 0.0; 0.0; 2097152.0; 121633.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1377458866; 1; 2599.99931; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377459166; 1; 2599.99931; 36.39999034; 1.4; 2097152.0; 297793.6; 161.0; 14.333333333333334; 0.0; 0.13333333333333333 +1377459466; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 466963.2; 0.0; 1.0; 0.0; 0.0 +1377459766; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 211110.66666666666; 31.8; 3.8; 0.0; 0.0 +1377460066; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 169169.06666666668; 0.0; 1.0; 0.0; 0.0 +1377460366; 1; 2599.99931; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377460666; 1; 2599.99931; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 0.6666666666666666; 0.0; 0.0 +1377460966; 1; 2599.99931; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 0.6; 0.0; 0.0 +1377461266; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 120234.66666666667; 0.06666666666666667; 2.533333333333333; 0.0; 0.4666666666666667 +1377461566; 1; 2599.99931; 0.0; 0.0; 2097152.0; 173362.4; 0.0; 0.8; 0.0; 0.0 +1377461866; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 134216.8; 0.0; 0.5333333333333333; 0.0; 0.0 +1377462166; 1; 2599.99931; 0.0; 0.0; 2097152.0; 89476.53333333334; 0.0; 0.5333333333333333; 0.0; 0.0 +1377462466; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 76893.33333333333; 0.06666666666666667; 7.2; 0.2; 0.13333333333333333 +1377462766; 1; 2599.99931; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 0.8; 0.0; 0.0 +1377463066; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 78291.46666666666; 0.0; 0.6666666666666666; 0.0; 0.0 +1377463366; 1; 2599.99931; 0.0; 0.0; 2097152.0; 106254.13333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377463666; 1; 2599.99931; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 0.6; 0.0; 0.0 +1377463966; 1; 2599.99931; 0.0; 0.0; 2097152.0; 123031.73333333334; 0.0; 1.0; 0.0; 0.0 +1377464266; 1; 2599.99931; 0.0; 0.0; 2097152.0; 114642.93333333333; 0.0; 0.6666666666666666; 0.0; 0.0 +1377464566; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 127226.13333333333; 0.0; 0.6; 0.0; 0.0 +1377464866; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 99263.46666666666; 0.0; 2.2; 0.0; 0.4666666666666667 +1377465167; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 146799.2; 0.0; 1.0; 0.0; 0.0 +1377465467; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 92272.8; 0.0; 0.8; 0.0; 0.0 +1377465767; 1; 2599.99931; 0.0; 0.0; 2097152.0; 65708.26666666666; 0.0; 0.5333333333333333; 0.0; 0.0 +1377466067; 1; 2599.99931; 0.0; 0.0; 2097152.0; 78291.46666666666; 0.0; 0.6; 0.0; 0.0 +1377466367; 1; 2599.99931; 0.0; 0.0; 2097152.0; 86680.26666666666; 0.0; 0.8; 0.0; 0.0 +1377466667; 1; 2599.99931; 0.0; 0.0; 2097152.0; 83884.0; 0.0; 0.8666666666666667; 0.13333333333333333; 0.0 +1377466967; 1; 2599.99931; 0.0; 0.0; 2097152.0; 118837.33333333333; 0.0; 0.8; 0.0; 0.0 +1377467267; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 104855.2; 0.0; 0.6666666666666666; 0.0; 0.0 +1377467567; 1; 2599.99931; 0.0; 0.0; 2097152.0; 128624.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377467867; 1; 2599.99931; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.6666666666666666; 0.0; 0.0 +1377468167; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 97865.33333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377468467; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 118837.33333333333; 0.0; 2.1333333333333333; 0.0; 0.4666666666666667 +1377468767; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 144002.13333333333; 0.0; 0.6; 0.0; 0.0 +1377469067; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 125828.0; 0.0; 1.4666666666666666; 0.0; 0.0 +1377469367; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 137013.06666666668; 0.0; 7.133333333333334; 0.2; 0.13333333333333333 +1377469667; 1; 2599.99931; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1377469967; 1; 2599.99931; 6.933331493333334; 0.26666666666666666; 2097152.0; 113244.8; 0.0; 1.4; 0.06666666666666667; 0.06666666666666667 +1377470267; 1; 2599.99931; 0.0; 0.0; 2097152.0; 135614.93333333332; 0.0; 0.6; 0.0; 0.0 +1377470567; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 102059.73333333334; 0.0; 0.8; 0.0; 0.0 +1377470867; 1; 2599.99931; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 1.0; 0.0; 0.0 +1377471167; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 97865.33333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377471467; 1; 2599.99931; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 0.6; 0.0; 0.0 +1377471767; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 114642.93333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1377472067; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 128624.26666666666; 0.2; 2.8; 0.0; 0.4666666666666667 +1377472367; 1; 2599.99931; 0.0; 0.0; 2097152.0; 148197.33333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1377472667; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 110448.53333333334; 0.0; 1.0; 0.0; 0.0 +1377472967; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377473267; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 89476.53333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377473567; 1; 2599.99931; 0.0; 0.0; 2097152.0; 83884.0; 0.0; 1.1333333333333333; 0.0; 0.0 +1377473867; 1; 2599.99931; 0.0; 0.0; 2097152.0; 72698.93333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1377474167; 1; 2599.99931; 0.0; 0.0; 2097152.0; 76893.33333333333; 0.0; 1.0; 0.0; 0.0 +1377474467; 1; 2599.99931; 0.0; 0.0; 2097152.0; 95069.06666666667; 0.0; 1.2; 0.0; 0.0 +1377474767; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 100661.6; 0.06666666666666667; 6.933333333333334; 0.2; 0.13333333333333333 +1377475067; 1; 2599.99931; 0.0; 0.0; 2097152.0; 118837.33333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1377475367; 1; 2599.99931; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1377475667; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 96466.93333333333; 0.06666666666666667; 2.2666666666666666; 0.06666666666666667; 0.4666666666666667 +1377475967; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 176158.66666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377476267; 1; 2599.99931; 8.66666436666667; 0.33333333333333337; 2097152.0; 116041.06666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1377476567; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 88078.4; 0.0; 1.0; 0.0; 0.0 +1377476867; 1; 2599.99931; 0.0; 0.0; 2097152.0; 89476.53333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377477168; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 75495.2; 0.0; 1.3333333333333333; 0.0; 0.0 +1377477468; 1; 2599.99931; 0.0; 0.0; 2097152.0; 85282.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377477768; 1; 2599.99931; 0.0; 0.0; 2097152.0; 85282.13333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1377478068; 1; 2599.99931; 0.0; 0.0; 2097152.0; 130022.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377478368; 1; 2599.99931; 0.0; 0.0; 2097152.0; 78291.46666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1377478668; 1; 2599.99931; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1377478968; 1; 2599.99931; 0.0; 0.0; 2097152.0; 120235.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377479268; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 117439.2; 0.0; 2.466666666666667; 0.0; 0.4666666666666667 +1377479568; 1; 2599.99931; 0.0; 0.0; 2097152.0; 155188.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1377479868; 1; 2599.99931; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377480168; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 113244.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377480468; 1; 2599.99931; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1377480768; 1; 2599.99931; 6.933331493333334; 0.26666666666666666; 2097152.0; 100661.6; 0.0; 7.066666666666666; 0.2; 0.13333333333333333 +1377481068; 1; 2599.99931; 0.0; 0.0; 2097152.0; 110448.53333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377481368; 1; 2599.99931; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1377481668; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 92272.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1377481968; 1; 2599.99931; 0.0; 0.0; 2097152.0; 85282.13333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1377482268; 1; 2599.99931; 0.0; 0.0; 2097152.0; 79689.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1377482568; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 128624.26666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1377482868; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 93670.93333333333; 0.0; 2.2666666666666666; 0.0; 0.4666666666666667 +1377483168; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 145401.86666666667; 0.0; 1.0; 0.0; 0.0 +1377483468; 1; 2599.99931; 0.0; 0.0; 2097152.0; 110448.53333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377483768; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 92272.8; 0.0; 0.8; 0.0; 0.0 +1377484068; 1; 2599.99931; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.8; 0.0; 0.0 +1377484368; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 128624.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377484668; 1; 2599.99931; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 1.0; 0.0; 0.0 +1377484968; 1; 2599.99931; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377485268; 1; 2599.99931; 0.0; 0.0; 2097152.0; 124429.06666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377485568; 1; 2599.99931; 0.0; 0.0; 2097152.0; 160780.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1377485868; 1; 2599.99931; 0.0; 0.0; 2097152.0; 124429.33333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1377486168; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 85282.13333333333; 0.06666666666666667; 7.2; 0.26666666666666666; 0.13333333333333333 +1377486468; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 99263.46666666666; 0.06666666666666667; 2.2; 0.0; 0.4666666666666667 +1377486768; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 190139.73333333334; 0.0; 0.8; 0.0; 0.0 +1377487068; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 104856.0; 0.0; 0.8; 0.0; 0.0 +1377487368; 1; 2599.99931; 0.0; 0.0; 2097152.0; 111845.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377487668; 1; 2599.99931; 0.0; 0.0; 2097152.0; 123031.73333333334; 0.0; 0.6666666666666666; 0.0; 0.0 +1377487968; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 107652.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377488269; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 106254.13333333333; 0.0; 11.733333333333333; 0.0; 0.0 +1377488569; 1; 2599.99931; 0.0; 0.0; 2097152.0; 106254.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1377488869; 1; 2599.99931; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 1.3333333333333333; 0.0; 0.0 +1377489169; 1; 2599.99931; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377489469; 1; 2599.99931; 0.0; 0.0; 2097152.0; 118837.33333333333; 0.0; 0.8; 0.0; 0.0 +1377489769; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 100661.6; 0.0; 1.6666666666666667; 0.0; 0.0 +1377490068; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 118837.33333333333; 0.0; 2.6; 0.06666666666666667; 0.4666666666666667 +1377490368; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 180353.6; 0.0; 1.0; 0.0; 0.0 +1377490668; 1; 2599.99931; 0.0; 0.0; 2097152.0; 142604.8; 0.0; 0.8; 0.0; 0.0 +1377490968; 1; 2599.99931; 0.0; 0.0; 2097152.0; 134216.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377491268; 1; 2599.99931; 0.0; 0.0; 2097152.0; 95069.06666666667; 0.0; 0.8; 0.0; 0.0 +1377491568; 1; 2599.99931; 0.0; 0.0; 2097152.0; 79689.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1377491868; 1; 2599.99931; 0.0; 0.0; 2097152.0; 114642.93333333333; 0.0; 0.6666666666666666; 0.0; 0.0 +1377492168; 1; 2599.99931; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1377492468; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 128624.26666666666; 0.0; 1.0; 0.0; 0.0 +1377492768; 1; 2599.99931; 0.0; 0.0; 2097152.0; 142604.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377493068; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 109050.4; 0.0; 6.866666666666666; 0.26666666666666666; 0.13333333333333333 +1377493368; 1; 2599.99931; 0.0; 0.0; 2097152.0; 83884.0; 0.0; 0.8; 0.0; 0.0 +1377493668; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 106254.13333333333; 0.0; 2.2; 0.06666666666666667; 0.4666666666666667 +1377493968; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 176157.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1377494268; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 155187.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1377494569; 1; 2599.99931; 0.0; 0.0; 2097152.0; 125828.0; 0.0; 0.8; 0.0; 0.0 +1377494869; 1; 2599.99931; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1377495169; 1; 2599.99931; 0.0; 0.0; 2097152.0; 81087.73333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1377495469; 1; 2599.99931; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.7333333333333333; 0.0; 0.0 +1377495769; 1; 2599.99931; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 1.1333333333333333; 0.0; 0.0 +1377496069; 1; 2599.99931; 0.0; 0.0; 2097152.0; 79689.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1377496369; 1; 2599.99931; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.8; 0.0; 0.0 +1377496669; 1; 2599.99931; 0.0; 0.0; 2097152.0; 81087.73333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1377496969; 1; 2599.99931; 0.0; 0.0; 2097152.0; 121633.6; 0.0; 1.0; 0.0; 0.0 +1377497269; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 107651.46666666666; 0.06666666666666667; 2.6666666666666665; 0.06666666666666667; 0.4666666666666667 +1377497569; 1; 2599.99931; 0.0; 0.0; 2097152.0; 146799.2; 0.0; 0.7333333333333333; 0.0; 0.0 +1377497869; 1; 2599.99931; 0.0; 0.0; 2097152.0; 139809.33333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1377498169; 1; 2599.99931; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.6666666666666666; 0.0; 0.0 +1377498469; 1; 2599.99931; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1377498769; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 61513.86666666667; 0.06666666666666667; 1.1333333333333333; 0.06666666666666667; 0.13333333333333333 +1377499069; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 58717.6; 0.0; 1.8; 0.0; 0.0 +1377499369; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 103457.86666666667; 0.0; 1.5333333333333334; 0.0; 0.0 +1377499669; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 104856.0; 0.0; 6.8; 0.2; 0.13333333333333333 +1377499969; 1; 2599.99931; 60.666650566666675; 2.3333333333333335; 2097152.0; 359311.4666666667; 161.0; 21.733333333333334; 0.06666666666666667; 0.4 +1377500269; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 654309.6; 0.0; 2.6; 0.0; 0.0 +1377500569; 1; 2599.99931; 8.66666436666667; 0.33333333333333337; 2097152.0; 310376.8; 31.8; 3.4; 0.0; 0.0 +1377500869; 1; 2599.99931; 8.66666436666667; 0.33333333333333337; 2097152.0; 192935.2; 0.0; 3.7333333333333334; 0.06666666666666667; 0.4666666666666667 +1377501169; 1; 2599.99931; 6.933331493333334; 0.26666666666666666; 2097152.0; 192936.0; 0.0; 1.8666666666666667; 0.0; 0.0 +1377501470; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 117439.2; 0.0; 0.8; 0.0; 0.0 +1377501770; 1; 2599.99931; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377502070; 1; 2599.99931; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 0.6666666666666666; 0.0; 0.0 +1377502370; 1; 2599.99931; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.8; 0.0; 0.0 +1377502670; 1; 2599.99931; 0.0; 0.0; 2097152.0; 75495.2; 0.0; 1.0; 0.06666666666666667; 0.0 +1377502970; 1; 2599.99931; 36.39999034; 1.4; 2097152.0; 92272.8; 0.0; 1.2; 104.86666666666666; 0.0 +1377503270; 1; 2599.99931; 0.0; 0.0; 2097152.0; 121633.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1377503570; 1; 2599.99931; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377503870; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 125828.0; 0.0; 0.8; 0.4; 0.0 +1377504170; 1; 2599.99931; 0.0; 0.0; 2097152.0; 83884.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1377504470; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 99262.66666666667; 0.06666666666666667; 2.4; 0.06666666666666667; 0.4666666666666667 +1377504770; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 159381.6; 0.0; 0.8; 0.0; 0.0 +1377505070; 1; 2599.99931; 0.0; 0.0; 2097152.0; 83884.0; 0.0; 0.8; 0.06666666666666667; 0.0 +1377505370; 1; 2599.99931; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 1.0; 0.0; 0.0 +1377505670; 1; 2599.99931; 0.0; 0.0; 2097152.0; 125828.0; 0.0; 0.8; 0.0; 0.0 +1377505970; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 89476.53333333334; 0.0; 0.8; 0.0; 0.0 +1377506270; 1; 2599.99931; 8.66666436666667; 0.33333333333333337; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377506570; 1; 2599.99931; 6.933331493333334; 0.26666666666666666; 2097152.0; 113244.8; 0.0; 6.866666666666666; 0.2; 0.13333333333333333 +1377506870; 1; 2599.99931; 6.933331493333334; 0.26666666666666666; 2097152.0; 110448.53333333334; 0.0; 1.0; 0.0; 0.0 +1377507170; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 88078.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1377507470; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 121633.6; 0.0; 0.8; 0.0; 0.0 +1377507770; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 99263.46666666666; 0.0; 0.8; 0.06666666666666667; 0.0 +1377508070; 1; 2599.99931; 13.866662986666668; 0.5333333333333333; 2097152.0; 142605.6; 0.13333333333333333; 2.466666666666667; 0.0; 0.4666666666666667 +1377508370; 1; 2599.99931; 10.39999724; 0.4; 2097152.0; 142605.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1377508670; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 83884.0; 0.0; 0.8; 0.0; 0.0 +1377508970; 1; 2599.99931; 6.933331493333334; 0.26666666666666666; 2097152.0; 89476.53333333334; 0.0; 0.6666666666666666; 0.0; 0.0 +1377509270; 1; 2599.99931; 10.39999724; 0.4; 2097152.0; 125828.0; 0.0; 0.7333333333333333; 0.0; 0.0 +1377509570; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 117439.2; 0.0; 1.0; 0.0; 0.0 +1377509870; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 104856.0; 0.0; 0.7333333333333333; 0.0; 0.0 +1377510170; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 109050.4; 0.0; 1.0; 0.0; 0.0 +1377510470; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 83884.0; 0.0; 0.7333333333333333; 0.0; 0.0 +1377510771; 1; 2599.99931; 6.933331493333334; 0.26666666666666666; 2097152.0; 93670.93333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1377511071; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 86680.26666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1377511371; 1; 2599.99931; 10.39999724; 0.4; 2097152.0; 152391.73333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1377511671; 1; 2599.99931; 6.933331493333334; 0.26666666666666666; 2097152.0; 95069.06666666667; 0.06666666666666667; 2.2666666666666666; 0.13333333333333333; 0.4666666666666667 +1377511971; 1; 2599.99931; 10.39999724; 0.4; 2097152.0; 156586.13333333333; 12.733333333333333; 12.933333333333334; 0.4666666666666667; 0.2 +1377512271; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 121633.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1377512571; 1; 2599.99931; 0.0; 0.0; 2097152.0; 89476.53333333334; 0.0; 1.0; 0.0; 0.0 +1377512871; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 125828.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377513171; 1; 2599.99931; 0.0; 0.0; 2097152.0; 125828.0; 0.0; 0.7333333333333333; 0.0; 0.0 +1377513471; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 102059.73333333334; 0.0; 1.4; 0.0; 0.0 +1377513771; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 106254.13333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1377514071; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 100661.6; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1377515571; 1; 2599.99931; 10.39999724; 0.4; 2097152.0; 125828.0; 0.0; 0.7333333333333333; 0.0; 0.0 +1377515871; 1; 2599.99931; 10.39999724; 0.4; 2097152.0; 111846.66666666667; 0.0; 0.6; 0.0; 0.0 +1377516171; 1; 2599.99931; 6.933331493333334; 0.26666666666666666; 2097152.0; 134216.8; 0.0; 0.6666666666666666; 0.13333333333333333; 0.0 +1377516471; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 100661.6; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1377516771; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 121633.6; 0.0; 0.6666666666666666; 3.1333333333333333; 0.0 +1377517071; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 116041.06666666667; 0.0; 0.5333333333333333; 0.0; 0.0 +1377517371; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 109050.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1377517671; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 118837.33333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1377517971; 1; 2599.99931; 8.66666436666667; 0.33333333333333337; 2097152.0; 85282.13333333333; 0.0; 6.866666666666666; 0.2; 0.13333333333333333 +1377518271; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 134216.8; 0.0; 0.8; 0.0; 0.0 +1377518571; 1; 2599.99931; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 0.6; 0.0; 0.0 +1377518871; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 106254.13333333333; 0.0; 2.1333333333333333; 0.06666666666666667; 0.5333333333333333 +1377519171; 1; 2599.99931; 0.0; 0.0; 2097152.0; 169168.53333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377519472; 1; 2599.99931; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.6666666666666666; 0.0; 0.0 +1377519772; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 95069.06666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1377520072; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 96467.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1377520372; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 82485.86666666667; 0.0; 0.8; 0.06666666666666667; 0.0 +1377520672; 1; 2599.99931; 8.66666436666667; 0.33333333333333337; 2097152.0; 117439.2; 0.0; 0.8; 0.0; 0.0 +1377520972; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 131420.53333333333; 0.0; 0.6666666666666666; 0.0; 0.0 +1377521272; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 121633.6; 0.0; 0.7333333333333333; 0.0; 0.0 +1377521572; 1; 2599.99931; 12.133330113333335; 0.4666666666666666; 2097152.0; 134216.0; 0.0; 0.7333333333333333; 0.0; 0.0 +1377521872; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 90874.66666666667; 0.0; 0.8; 0.0; 0.0 +1377522172; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 71300.8; 0.0; 0.6; 0.0; 0.0 +1377522472; 1; 2599.99931; 12.133330113333335; 0.4666666666666666; 2097152.0; 107652.26666666666; 0.06666666666666667; 2.533333333333333; 0.06666666666666667; 0.4666666666666667 +1377522771; 1; 2599.99931; 8.66666436666667; 0.33333333333333337; 2097152.0; 204121.06666666668; 0.0; 0.8; 0.0; 0.0 +1377523071; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 92272.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377523371; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 79689.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1377523671; 1; 2599.99931; 6.933331493333334; 0.26666666666666666; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377523971; 1; 2599.99931; 6.933331493333334; 0.26666666666666666; 2097152.0; 102059.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377524271; 1; 2599.99931; 12.133330113333335; 0.4666666666666666; 2097152.0; 96467.2; 0.0; 1.6666666666666667; 0.0; 0.0 +1377524571; 1; 2599.99931; 10.39999724; 0.4; 2097152.0; 100661.6; 0.0; 7.4; 0.6; 0.2 +1377524871; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 116041.06666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377525171; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 117439.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1377525471; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 78291.46666666666; 0.0; 0.8; 0.0; 0.0 +1377525771; 1; 2599.99931; 10.39999724; 0.4; 2097152.0; 97865.33333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377526071; 1; 2599.99931; 8.66666436666667; 0.33333333333333337; 2097152.0; 130022.4; 0.0; 2.6; 0.06666666666666667; 0.4666666666666667 +1377526371; 1; 2599.99931; 10.39999724; 0.4; 2097152.0; 139808.53333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1377526671; 1; 2599.99931; 6.933331493333334; 0.26666666666666666; 2097152.0; 89476.53333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1377526971; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 111846.66666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377527271; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 99263.46666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1377527571; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 102059.73333333334; 0.0; 1.4666666666666666; 0.06666666666666667; 0.13333333333333333 +1377527871; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 100661.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1377528171; 1; 2599.99931; 0.0; 0.0; 2097152.0; 95069.06666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377528472; 1; 2599.99931; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377528772; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 120235.46666666666; 0.0; 0.9333333333333333; 0.5333333333333333; 0.0 +1377529072; 1; 2599.99931; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1377529372; 1; 2599.99931; 0.0; 0.0; 2097152.0; 81087.73333333334; 0.0; 0.8666666666666667; 0.13333333333333333; 0.0 +1377529672; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 150992.8; 0.0; 2.4; 0.06666666666666667; 0.4666666666666667 +1377529972; 1; 2599.99931; 0.0; 0.0; 2097152.0; 171965.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1377530272; 1; 2599.99931; 0.0; 0.0; 2097152.0; 128624.26666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1377530572; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 103457.86666666667; 0.0; 6.6; 0.2; 0.13333333333333333 +1377530872; 1; 2599.99931; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1377531172; 1; 2599.99931; 0.0; 0.0; 2097152.0; 81087.73333333334; 0.0; 0.8666666666666667; 0.13333333333333333; 0.0 +1377531472; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 89476.53333333334; 0.0; 1.0; 0.06666666666666667; 0.0 +1377531772; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 150994.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1377532072; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 118837.33333333333; 0.0; 12.066666666666666; 0.06666666666666667; 0.0 +1377532372; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 114642.93333333333; 0.0; 1.0666666666666667; 1.8; 0.0 +1377532672; 1; 2599.99931; 0.0; 0.0; 2097152.0; 82485.86666666667; 0.0; 0.8666666666666667; 2.2666666666666666; 0.0 +1377532972; 1; 2599.99931; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 1.0666666666666667; 0.0; 0.0 +1377533272; 1; 2599.99931; 6.933331493333334; 0.26666666666666666; 2097152.0; 121633.6; 0.06666666666666667; 2.6; 0.06666666666666667; 0.4666666666666667 +1377533572; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 114642.93333333333; 0.0; 1.0; 0.0; 0.0 +1377533872; 1; 2599.99931; 0.0; 0.0; 2097152.0; 142604.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377534172; 1; 2599.99931; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.9333333333333333; 4.2; 0.0 +1377534472; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 100661.6; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1377534772; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 128624.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377535072; 1; 2599.99931; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377535372; 1; 2599.99931; 0.0; 0.0; 2097152.0; 85282.13333333333; 0.0; 1.0; 0.0; 0.0 +1377535672; 1; 2599.99931; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 1.0; 0.0; 0.0 +1377535972; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 97865.33333333333; 0.0; 1.2; 0.0; 0.0 +1377536272; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 102059.73333333334; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1377536572; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 113244.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1377536872; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 128623.46666666666; 0.13333333333333333; 2.3333333333333335; 0.06666666666666667; 0.4666666666666667 +1377537172; 1; 2599.99931; 6.933331493333334; 0.26666666666666666; 2097152.0; 155188.0; 0.0; 7.2; 0.2; 0.13333333333333333 +1377537472; 1; 2599.99931; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377537772; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 104856.0; 0.0; 0.8; 0.0; 0.0 +1377538072; 1; 2599.99931; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 1.0; 0.06666666666666667; 0.0 +1377538372; 1; 2599.99931; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 1.0666666666666667; 2.2666666666666666; 0.0 +1377538672; 1; 2599.99931; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1377538972; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 86680.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377539272; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 131420.53333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1377539572; 1; 2599.99931; 6.933331493333334; 0.26666666666666666; 2097152.0; 137013.06666666668; 0.0; 0.6666666666666666; 0.0; 0.0 +1377539873; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 142604.53333333333; 0.0; 1.0; 0.06666666666666667; 0.0 +1377540173; 1; 2599.99931; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.9333333333333333; 0.13333333333333333; 0.0 +1377540473; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 142604.8; 0.06666666666666667; 2.4; 0.06666666666666667; 0.4666666666666667 +1377540773; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 169169.33333333334; 0.0; 0.8; 0.0; 0.0 +1377541073; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 146799.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1377541373; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 146799.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1377541673; 1; 2599.99931; 6.933331493333334; 0.26666666666666666; 2097152.0; 125828.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377541973; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 111846.13333333333; 0.0; 0.8; 0.6; 0.0 +1377542273; 1; 2599.99931; 0.0; 0.0; 2097152.0; 118837.33333333333; 0.0; 0.6; 0.0; 0.0 +1377542573; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 116041.06666666667; 0.0; 0.8; 3.2; 0.0 +1377542873; 1; 2599.99931; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 1.0; 0.0; 0.0 +1377543173; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 141207.46666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377543473; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 173363.2; 0.0; 6.8; 0.2; 0.13333333333333333 +1377543773; 1; 2599.99931; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377544073; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 106254.13333333333; 0.0; 2.6666666666666665; 0.06666666666666667; 0.4666666666666667 +1377544373; 1; 2599.99931; 0.0; 0.0; 2097152.0; 156585.33333333334; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377544673; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 125827.2; 0.0; 0.7333333333333333; 0.0; 0.0 +1377544973; 1; 2599.99931; 0.0; 0.0; 2097152.0; 130021.6; 0.0; 0.8; 0.0; 0.0 +1377545273; 1; 2599.99931; 0.0; 0.0; 2097152.0; 86680.26666666666; 0.0; 0.8; 0.0; 0.0 +1377545573; 1; 2599.99931; 50.26665332666667; 1.9333333333333333; 2097152.0; 233481.6; 161.2; 14.6; 0.06666666666666667; 0.2 +1377545873; 1; 2599.99931; 13.866662986666668; 0.5333333333333333; 2097152.0; 486537.06666666665; 0.0; 1.2666666666666666; 0.0; 0.0 +1377546173; 1; 2599.99931; 12.133330113333335; 0.4666666666666666; 2097152.0; 208314.66666666666; 31.8; 3.6666666666666665; 0.0; 0.0 +1377546473; 1; 2599.99931; 0.0; 0.0; 2097152.0; 201324.53333333333; 0.0; 1.2666666666666666; 0.0; 0.0 +1377546773; 1; 2599.99931; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 1.0; 0.0; 0.0 +1377547073; 1; 2599.99931; 0.0; 0.0; 2097152.0; 78291.46666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1377547373; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 86680.26666666666; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377547673; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 128623.73333333334; 0.0; 2.533333333333333; 0.06666666666666667; 0.4666666666666667 +1377547973; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 190140.26666666666; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1377548273; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 97865.33333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1377548573; 1; 2599.99931; 8.66666436666667; 0.33333333333333337; 2097152.0; 127226.13333333333; 0.0; 0.8666666666666667; 1.8666666666666667; 0.0 +1377548873; 1; 2599.99931; 6.933331493333334; 0.26666666666666666; 2097152.0; 131420.53333333333; 0.0; 7.333333333333333; 0.2; 0.13333333333333333 +1377549173; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 124429.86666666667; 0.0; 1.0; 0.0; 0.0 +1377549473; 1; 2599.99931; 0.0; 0.0; 2097152.0; 114642.93333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1377549774; 1; 2599.99931; 0.0; 0.0; 2097152.0; 137013.06666666668; 0.0; 0.9333333333333333; 0.0; 0.0 +1377550074; 1; 2599.99931; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 1.0; 0.0; 0.0 +1377550374; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 107652.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377550674; 1; 2599.99931; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377550974; 1; 2599.99931; 0.0; 0.0; 2097152.0; 118837.33333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1377551274; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 132817.6; 0.0; 2.2666666666666666; 0.06666666666666667; 0.4666666666666667 +1377551574; 1; 2599.99931; 60.666650566666675; 2.3333333333333335; 2097152.0; 423622.93333333335; 161.0; 21.6; 0.5333333333333333; 0.4 +1377551874; 1; 2599.99931; 12.133330113333335; 0.4666666666666666; 2097152.0; 608172.0; 0.0; 2.533333333333333; 0.0; 0.0 +1377552174; 1; 2599.99931; 13.866662986666668; 0.5333333333333333; 2097152.0; 232082.13333333333; 31.8; 3.533333333333333; 0.0; 0.0 +1377552474; 1; 2599.99931; 8.66666436666667; 0.33333333333333337; 2097152.0; 149595.2; 0.0; 2.533333333333333; 0.0; 0.0 +1377552774; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 116041.06666666667; 0.0; 1.6666666666666667; 0.0; 0.0 +1377553074; 1; 2599.99931; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1377553374; 1; 2599.99931; 0.0; 0.0; 2097152.0; 128624.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377553674; 1; 2599.99931; 0.0; 0.0; 2097152.0; 124429.33333333333; 0.0; 1.2; 0.0; 0.0 +1377553974; 1; 2599.99931; 0.0; 0.0; 2097152.0; 120235.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377554274; 1; 2599.99931; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377554574; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 86680.26666666666; 0.0; 0.9333333333333333; 1.5333333333333334; 0.0 +1377554874; 1; 2599.99931; 8.66666436666667; 0.33333333333333337; 2097152.0; 102059.73333333334; 0.0; 2.4; 0.0; 0.4666666666666667 +1377555174; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 113244.0; 0.0; 1.2; 0.06666666666666667; 0.0 +1377555474; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 75495.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377555774; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 99263.46666666666; 0.0; 7.266666666666667; 0.26666666666666666; 0.13333333333333333 +1377556074; 1; 2599.99931; 0.0; 0.0; 2097152.0; 114642.93333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377556374; 1; 2599.99931; 6.933331493333334; 0.26666666666666666; 2097152.0; 149595.46666666667; 0.06666666666666667; 1.8; 0.13333333333333333; 0.06666666666666667 +1377556674; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 124429.86666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1377556974; 1; 2599.99931; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 1.0; 0.0; 0.0 +1377557274; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 120235.46666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1377557574; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 134216.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377557874; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 121633.6; 0.0; 1.9333333333333333; 0.0; 0.0 +1377558174; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 102059.73333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1377558474; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 106253.6; 0.06666666666666667; 2.1333333333333333; 0.06666666666666667; 0.4666666666666667 +1377558774; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 157984.0; 0.0; 0.8; 0.0; 0.0 +1377559074; 1; 2599.99931; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 1.0; 0.0; 0.0 +1377559374; 1; 2599.99931; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1377559674; 1; 2599.99931; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1377559974; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 110448.53333333334; 0.0; 1.0; 0.0; 0.0 +1377560274; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 120235.46666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1377560574; 1; 2599.99931; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 1.0; 0.0; 0.0 +1377560874; 1; 2599.99931; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 1.2; 0.0; 0.0 +1377561174; 1; 2599.99931; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.8; 0.0; 0.0 +1377561474; 1; 2599.99931; 0.0; 0.0; 2097152.0; 106254.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1377561774; 1; 2599.99931; 6.933331493333334; 0.26666666666666666; 2097152.0; 124429.86666666667; 0.0; 6.933333333333334; 0.26666666666666666; 0.13333333333333333 +1377562074; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 128623.73333333334; 0.0; 2.466666666666667; 0.0; 0.4666666666666667 +1377562374; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 148197.06666666668; 0.0; 0.7333333333333333; 0.0; 0.0 +1377562674; 1; 2599.99931; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377562974; 1; 2599.99931; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.6666666666666666; 0.0; 0.0 +1377563274; 1; 2599.99931; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1377563574; 1; 2599.99931; 6.933331493333334; 0.26666666666666666; 2097152.0; 95069.06666666667; 0.0; 1.4666666666666666; 0.0; 0.0 +1377563874; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 89476.53333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377564174; 1; 2599.99931; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1377564474; 1; 2599.99931; 0.0; 0.0; 2097152.0; 78291.46666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1377564774; 1; 2599.99931; 0.0; 0.0; 2097152.0; 67106.4; 0.0; 1.0666666666666667; 0.0; 0.0 +1377565074; 1; 2599.99931; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.8; 0.0; 0.0 +1377565374; 1; 2599.99931; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377565674; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 123031.2; 0.06666666666666667; 2.0; 0.06666666666666667; 0.4666666666666667 +1377565974; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 177557.06666666668; 0.0; 0.9333333333333333; 0.0; 0.0 +1377566274; 1; 2599.99931; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 1.1333333333333333; 0.0; 0.0 +1377566574; 1; 2599.99931; 0.0; 0.0; 2097152.0; 83884.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1377566874; 1; 2599.99931; 0.0; 0.0; 2097152.0; 75495.2; 0.0; 0.6666666666666666; 0.0; 0.0 +1377567175; 1; 2599.99931; 6.933331493333334; 0.26666666666666666; 2097152.0; 110448.53333333334; 0.0; 7.133333333333334; 0.2; 0.13333333333333333 +1377567475; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 107652.26666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1377567775; 1; 2599.99931; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377568075; 1; 2599.99931; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.7333333333333333; 0.0; 0.0 +1377568375; 1; 2599.99931; 0.0; 0.0; 2097152.0; 95069.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377568675; 1; 2599.99931; 0.0; 0.0; 2097152.0; 134216.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377568975; 1; 2599.99931; 0.0; 0.0; 2097152.0; 148198.13333333333; 0.0; 1.2; 0.0; 0.0 +1377569275; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 102059.73333333334; 0.2; 2.066666666666667; 0.0; 0.4666666666666667 +1377569575; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 164974.93333333332; 0.0; 0.7333333333333333; 0.0; 0.0 +1377569875; 1; 2599.99931; 0.0; 0.0; 2097152.0; 86680.26666666666; 0.0; 0.8; 0.0; 0.0 +1377570175; 1; 2599.99931; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 0.8; 0.0; 0.0 +1377570475; 1; 2599.99931; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1377570775; 1; 2599.99931; 0.0; 0.0; 2097152.0; 113244.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377571075; 1; 2599.99931; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 1.0; 0.0; 0.0 +1377571375; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 113244.26666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1377571675; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 96466.93333333333; 0.0; 1.0; 0.0; 0.0 +1377571975; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 76893.33333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377572275; 1; 2599.99931; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 0.8; 0.0; 0.0 +1377572575; 1; 2599.99931; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 0.8; 0.0; 0.0 +1377572875; 1; 2599.99931; 12.133330113333335; 0.4666666666666666; 2097152.0; 162177.86666666667; 12.8; 14.466666666666667; 0.4; 0.6 +1377573175; 1; 2599.99931; 6.933331493333334; 0.26666666666666666; 2097152.0; 152391.73333333334; 0.0; 1.4; 0.0; 0.0 +1377573475; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 104856.0; 0.0; 0.8; 0.0; 0.0 +1377573776; 1; 2599.99931; 0.0; 0.0; 2097152.0; 75495.2; 0.0; 0.6666666666666666; 0.0; 0.0 +1377574076; 1; 2599.99931; 0.0; 0.0; 2097152.0; 82485.86666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1377574376; 1; 2599.99931; 0.0; 0.0; 2097152.0; 81087.73333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1377574676; 1; 2599.99931; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377574976; 1; 2599.99931; 0.0; 0.0; 2097152.0; 89476.53333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377575276; 1; 2599.99931; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1377575576; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 128624.26666666666; 0.0; 11.8; 0.0; 0.0 +1377575876; 1; 2599.99931; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 1.0; 0.0; 0.0 +1377576176; 1; 2599.99931; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 0.6; 0.06666666666666667; 0.0 +1377576476; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 102058.66666666667; 0.0; 2.066666666666667; 0.06666666666666667; 0.4666666666666667 +1377576776; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 170566.93333333332; 0.0; 0.7333333333333333; 0.0; 0.0 +1377577076; 1; 2599.99931; 6.933331493333334; 0.26666666666666666; 2097152.0; 137013.06666666668; 0.0; 0.8; 0.0; 0.0 +1377577376; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 82485.86666666667; 0.0; 1.0; 0.0; 0.0 +1377577676; 1; 2599.99931; 0.0; 0.0; 2097152.0; 76893.33333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1377577976; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 85282.13333333333; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1377578276; 1; 2599.99931; 0.0; 0.0; 2097152.0; 138411.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377578576; 1; 2599.99931; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1377578876; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 97865.33333333333; 0.0; 7.066666666666666; 0.4; 0.13333333333333333 +1377579176; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 148196.53333333333; 0.0; 0.8; 0.0; 0.0 +1377579476; 1; 2599.99931; 0.0; 0.0; 2097152.0; 106254.13333333333; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377579776; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 106254.13333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377580076; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 113243.73333333334; 0.06666666666666667; 2.7333333333333334; 0.06666666666666667; 0.4666666666666667 +1377580376; 1; 2599.99931; 0.0; 0.0; 2097152.0; 198528.8; 0.0; 0.7333333333333333; 0.0; 0.0 +1377580676; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 127226.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377580976; 1; 2599.99931; 0.0; 0.0; 2097152.0; 138410.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377581276; 1; 2599.99931; 0.0; 0.0; 2097152.0; 118837.33333333333; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377581576; 1; 2599.99931; 0.0; 0.0; 2097152.0; 118837.33333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377581876; 1; 2599.99931; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1377582176; 1; 2599.99931; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.8; 0.0; 0.0 +1377582476; 1; 2599.99931; 0.0; 0.0; 2097152.0; 128624.26666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1377582776; 1; 2599.99931; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 1.0; 0.06666666666666667; 0.0 +1377583077; 1; 2599.99931; 0.0; 0.0; 2097152.0; 107651.46666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1377583377; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 65708.26666666666; 0.06666666666666667; 1.2; 0.0; 0.0 +1377583677; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 92272.8; 0.0; 2.1333333333333333; 0.06666666666666667; 0.5333333333333333 +1377583977; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 121633.6; 0.0; 0.8; 0.0; 0.0 +1377584277; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 109050.4; 0.0; 1.0666666666666667; 0.0; 0.0 +1377584577; 1; 2599.99931; 6.933331493333334; 0.26666666666666666; 2097152.0; 104856.0; 0.0; 6.866666666666666; 0.2; 0.13333333333333333 +1377584877; 1; 2599.99931; 0.0; 0.0; 2097152.0; 128624.26666666666; 0.0; 0.9333333333333333; 0.2; 0.0 +1377585177; 1; 2599.99931; 6.933331493333334; 0.26666666666666666; 2097152.0; 120235.46666666666; 0.0; 1.8666666666666667; 0.26666666666666666; 0.06666666666666667 +1377585477; 1; 2599.99931; 8.66666436666667; 0.33333333333333337; 2097152.0; 145401.06666666668; 0.0; 1.5333333333333334; 0.0; 0.0 +1377585777; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 106254.13333333333; 0.0; 1.0; 0.0; 0.0 +1377586077; 1; 2599.99931; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1377586377; 1; 2599.99931; 0.0; 0.0; 2097152.0; 121633.6; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377586677; 1; 2599.99931; 0.0; 0.0; 2097152.0; 89476.53333333334; 0.0; 0.6; 0.06666666666666667; 0.0 +1377586977; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 85282.13333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1377587277; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 107652.0; 0.0; 2.2; 0.2; 0.4666666666666667 +1377587577; 1; 2599.99931; 8.66666436666667; 0.33333333333333337; 2097152.0; 170566.13333333333; 0.0; 0.6; 0.26666666666666666; 0.0 +1377587877; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 137013.06666666668; 0.0; 0.6; 0.0; 0.0 +1377588177; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 159382.13333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377588477; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 132818.13333333333; 0.0; 0.7333333333333333; 0.13333333333333333; 0.0 +1377588776; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 148198.13333333333; 0.0; 0.6; 0.13333333333333333; 0.0 +1377589076; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 132818.66666666666; 0.0; 0.6666666666666666; 0.0; 0.0 +1377589376; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 111846.66666666667; 0.0; 1.1333333333333333; 0.13333333333333333; 0.0 +1377589676; 1; 2599.99931; 6.933331493333334; 0.26666666666666666; 2097152.0; 90874.66666666667; 0.0; 1.4666666666666666; 0.0; 0.0 +1377589976; 1; 2599.99931; 8.66666436666667; 0.33333333333333337; 2097152.0; 83884.0; 0.0; 0.6; 5.2; 0.0 +1377590277; 1; 2599.99931; 10.39999724; 0.4; 2097152.0; 118837.33333333333; 0.0; 6.733333333333333; 0.26666666666666666; 0.13333333333333333 +1377590577; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 93670.93333333333; 0.0; 0.6; 0.0; 0.0 +1377590877; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 113244.8; 0.0; 2.3333333333333335; 0.06666666666666667; 0.4666666666666667 +1377591177; 1; 2599.99931; 12.133330113333335; 0.4666666666666666; 2097152.0; 181751.73333333334; 0.0; 1.0; 0.06666666666666667; 0.0 +1377591477; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 123031.73333333334; 0.0; 0.6; 0.13333333333333333; 0.0 +1377591777; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 118837.33333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377592077; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 135614.93333333332; 0.0; 0.9333333333333333; 0.0; 0.0 +1377592377; 1; 2599.99931; 8.66666436666667; 0.33333333333333337; 2097152.0; 90874.66666666667; 0.0; 0.8; 0.06666666666666667; 0.0 +1377592677; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 114642.93333333333; 0.0; 0.6666666666666666; 0.0; 0.0 +1377592977; 1; 2599.99931; 0.0; 0.0; 2097152.0; 149595.46666666667; 0.0; 0.6; 0.0; 0.0 +1377593277; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 131420.53333333333; 0.0; 0.8; 0.06666666666666667; 0.0 +1377593577; 1; 2599.99931; 0.0; 0.0; 2097152.0; 128623.46666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1377593877; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 125828.0; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1377594177; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 137013.06666666668; 0.0; 0.5333333333333333; 0.06666666666666667; 0.0 +1377594477; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 93670.93333333333; 0.2; 2.066666666666667; 0.13333333333333333; 0.4666666666666667 +1377594777; 1; 2599.99931; 8.66666436666667; 0.33333333333333337; 2097152.0; 127226.13333333333; 0.0; 0.6; 0.5333333333333333; 0.0 +1377595077; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 107652.26666666666; 0.0; 0.6; 9.4; 0.0 +1377595377; 1; 2599.99931; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.8; 0.0; 0.0 +1377595677; 1; 2599.99931; 0.0; 0.0; 2097152.0; 79689.6; 0.0; 0.7333333333333333; 0.0; 0.0 +1377595977; 1; 2599.99931; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377596277; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 106254.13333333333; 0.0; 6.8; 0.3333333333333333; 0.13333333333333333 +1377596577; 1; 2599.99931; 0.0; 0.0; 2097152.0; 83884.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377596877; 1; 2599.99931; 0.0; 0.0; 2097152.0; 132817.86666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1377597177; 1; 2599.99931; 0.0; 0.0; 2097152.0; 132817.86666666667; 0.0; 0.8; 0.0; 0.0 +1377597477; 1; 2599.99931; 0.0; 0.0; 2097152.0; 123031.73333333334; 0.0; 0.6666666666666666; 0.0; 0.0 +1377597777; 1; 2599.99931; 0.0; 0.0; 2097152.0; 120235.46666666666; 0.0; 0.6666666666666666; 0.13333333333333333; 0.0 +1377598077; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 109050.4; 0.0; 2.4; 0.0; 0.5333333333333333 +1377598377; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 137012.26666666666; 0.06666666666666667; 0.9333333333333333; 1.2666666666666666; 0.0 +1377598677; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 111846.66666666667; 0.0; 0.6; 0.0; 0.0 +1377598977; 1; 2599.99931; 0.0; 0.0; 2097152.0; 118837.33333333333; 0.0; 0.6; 0.0; 0.0 +1377599277; 1; 2599.99931; 64.13331631333334; 2.466666666666667; 2097152.0; 603977.0666666667; 161.0; 22.8; 0.0; 0.4 +1377599577; 1; 2599.99931; 12.133330113333335; 0.4666666666666666; 2097152.0; 394262.4; 0.0; 4.8; 0.0; 0.0 +1377599877; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 190140.0; 31.8; 3.533333333333333; 0.0; 0.0 +1377600177; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 156586.13333333333; 0.0; 2.3333333333333335; 0.06666666666666667; 0.0 +1377600477; 1; 2599.99931; 6.933331493333334; 0.26666666666666666; 2097152.0; 118837.33333333333; 0.0; 1.5333333333333334; 0.0; 0.0 +1377600777; 1; 2599.99931; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1377601077; 1; 2599.99931; 0.0; 0.0; 2097152.0; 110448.53333333334; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377601377; 1; 2599.99931; 0.0; 0.0; 2097152.0; 116041.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377601677; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 96467.2; 0.06666666666666667; 2.0; 0.0; 0.4666666666666667 +1377601977; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 141207.46666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377602278; 1; 2599.99931; 10.39999724; 0.4; 2097152.0; 113244.8; 0.0; 1.8666666666666667; 0.0; 0.0 +1377602578; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 102059.73333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1377602878; 1; 2599.99931; 8.66666436666667; 0.33333333333333337; 2097152.0; 117439.2; 0.0; 6.866666666666666; 0.26666666666666666; 0.13333333333333333 +1377603178; 1; 2599.99931; 0.0; 0.0; 2097152.0; 125828.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1377603478; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 100661.6; 0.0; 1.1333333333333333; 0.0; 0.0 +1377603778; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 113244.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1377604078; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 95069.06666666667; 0.0; 1.2; 0.06666666666666667; 0.0 +1377604378; 1; 2599.99931; 0.0; 0.0; 2097152.0; 95069.06666666667; 0.0; 0.8666666666666667; 1.1333333333333333; 0.0 +1377604678; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 121632.8; 0.0; 0.8666666666666667; 2.6666666666666665; 0.0 +1377604978; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 106254.13333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1377605278; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 121632.8; 0.06666666666666667; 2.6; 0.0; 0.4666666666666667 +1377605578; 1; 2599.99931; 10.39999724; 0.4; 2097152.0; 187343.46666666667; 0.0; 1.0; 0.06666666666666667; 0.0 +1377605878; 1; 2599.99931; 0.0; 0.0; 2097152.0; 86680.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377606178; 1; 2599.99931; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1377606478; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 128624.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377606778; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 130022.4; 0.0; 0.8666666666666667; 0.13333333333333333; 0.0 +1377607078; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377607378; 1; 2599.99931; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1377607678; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 74097.06666666667; 0.0; 1.1333333333333333; 0.13333333333333333; 0.0 +1377607978; 1; 2599.99931; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377608278; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 83884.0; 0.0; 0.8666666666666667; 0.7333333333333333; 0.0 +1377608578; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 128624.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377608878; 1; 2599.99931; 10.39999724; 0.4; 2097152.0; 138410.4; 0.06666666666666667; 2.2666666666666666; 80.73333333333333; 0.4666666666666667 +1377609178; 1; 2599.99931; 10.39999724; 0.4; 2097152.0; 170566.66666666666; 0.0; 7.266666666666667; 0.26666666666666666; 0.13333333333333333 +1377609478; 1; 2599.99931; 5.19999862; 0.2; 2097152.0; 138410.66666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1377609778; 1; 2599.99931; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1377610078; 1; 2599.99931; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377610378; 1; 2599.99931; 0.0; 0.0; 2097152.0; 81087.73333333334; 0.0; 1.0666666666666667; 0.13333333333333333; 0.0 +1377610678; 1; 2599.99931; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1377610978; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 125828.0; 0.0; 0.8; 0.0; 0.0 +1377611278; 1; 2599.99931; 1.7333328733333335; 0.06666666666666667; 2097152.0; 139809.33333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377611578; 1; 2599.99931; 3.466665746666667; 0.13333333333333333; 2097152.0; 99263.46666666666; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377611878; 1; 2599.99931; 0.0; 0.0; 2097152.0; 89476.53333333334; 0.0; 1.0666666666666667; 0.0; 0.0 +1377612178; 1; 2599.99931; 10.39999724; 0.4; 2097152.0; 142604.8; 0.0; 0.8; 10.6; 0.0 +1377612478; 1; 2599.998991; 40.44442874888888; 1.5555555555555554; 2097152.0; 130486.66666666667; 0.0; 3.875; 0.125; 0.875 +1377612778; 1; 2599.998991; 19.066659267333332; 0.7333333333333333; 2097152.0; 201324.0; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377613078; 1; 2599.998991; 17.33332660666667; 0.6666666666666667; 2097152.0; 100661.6; 0.0; 0.8; 0.0; 0.0 +1377613378; 1; 2599.999334; 25.99999334; 1.0; 2097152.0; 83884.0; 0.0; 1.0; 0.0; 0.0 +1377613678; 1; 2599.999334; 10.399997336000002; 0.4; 2097152.0; 109050.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1377613978; 1; 2599.999334; 17.333328893333338; 0.6666666666666667; 2097152.0; 121633.6; 0.0; 1.0; 0.13333333333333333; 0.13333333333333333 +1377614279; 1; 2599.999334; 8.666664446666669; 0.33333333333333337; 2097152.0; 120235.46666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1377614579; 1; 2599.999334; 3.4666657786666666; 0.13333333333333333; 2097152.0; 121633.6; 0.0; 1.2; 0.0; 0.0 +1377614879; 1; 2599.999602; 34.666661360000006; 1.3333333333333335; 2097152.0; 83884.0; 0.0; 1.0; 0.0; 0.0 +1377615179; 1; 2599.999602; 17.333330680000003; 0.6666666666666667; 2097152.0; 110448.53333333334; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1377615479; 1; 2599.999602; 8.666665340000002; 0.33333333333333337; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377615779; 1; 2599.999602; 0.0; 0.0; 2097152.0; 79689.6; 0.0; 0.6666666666666666; 0.13333333333333333; 0.0 +1377616079; 1; 2599.999602; 8.666665340000002; 0.33333333333333337; 2097152.0; 150993.6; 0.06666666666666667; 2.8; 0.06666666666666667; 0.4666666666666667 +1377616379; 1; 2599.999602; 13.866664544; 0.5333333333333333; 2097152.0; 184547.2; 0.0; 1.0; 0.06666666666666667; 0.0 +1377616679; 1; 2599.999602; 3.466666136; 0.13333333333333333; 2097152.0; 106254.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377616979; 1; 2599.999602; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377617279; 1; 2599.999602; 0.0; 0.0; 2097152.0; 83884.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1377617579; 1; 2599.999602; 5.199999203999999; 0.2; 2097152.0; 134216.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377617879; 1; 2599.999602; 0.0; 0.0; 2097152.0; 125827.2; 0.0; 0.8; 0.06666666666666667; 0.0 +1377618179; 1; 2599.999602; 0.0; 0.0; 2097152.0; 71300.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377618479; 1; 2599.999602; 0.0; 0.0; 2097152.0; 67106.4; 0.0; 1.2; 0.0; 0.0 +1377618779; 1; 2599.999602; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 1.0666666666666667; 0.0; 0.0 +1377619079; 1; 2599.999602; 1.733333068; 0.06666666666666667; 2097152.0; 102059.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377619379; 1; 2599.999602; 6.933332272; 0.26666666666666666; 2097152.0; 100661.6; 0.0; 11.733333333333333; 0.0; 0.0 +1377619679; 1; 2599.999602; 5.199999203999999; 0.2; 2097152.0; 99263.46666666666; 0.06666666666666667; 2.4; 0.0; 0.4666666666666667 +1377619979; 1; 2599.999602; 3.466666136; 0.13333333333333333; 2097152.0; 153789.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377620279; 1; 2599.999602; 5.199999203999999; 0.2; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377620579; 1; 2599.999602; 0.0; 0.0; 2097152.0; 71300.8; 0.0; 0.7333333333333333; 0.0; 0.0 +1377620879; 1; 2599.999602; 0.0; 0.0; 2097152.0; 89476.53333333334; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377621179; 1; 2599.999602; 0.0; 0.0; 2097152.0; 120235.46666666666; 0.0; 0.8666666666666667; 0.13333333333333333; 0.0 +1377621479; 1; 2599.999602; 10.399998407999998; 0.4; 2097152.0; 90874.66666666667; 0.0; 7.266666666666667; 0.2; 0.13333333333333333 +1377621779; 1; 2599.999602; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 0.7333333333333333; 0.0; 0.0 +1377622079; 1; 2599.999602; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377622379; 1; 2599.999602; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1377622679; 1; 2599.999602; 0.0; 0.0; 2097152.0; 83884.0; 0.0; 0.7333333333333333; 0.0; 0.0 +1377622979; 1; 2599.999602; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 1.0; 0.0; 0.0 +1377623279; 1; 2599.999602; 5.199999203999999; 0.2; 2097152.0; 121633.6; 0.0; 2.1333333333333333; 0.0; 0.4666666666666667 +1377623579; 1; 2599.999602; 0.0; 0.0; 2097152.0; 171964.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377623879; 1; 2599.999602; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377624179; 1; 2599.999602; 1.733333068; 0.06666666666666667; 2097152.0; 100661.6; 0.0; 0.8666666666666667; 0.26666666666666666; 0.0 +1377624479; 1; 2599.999602; 1.733333068; 0.06666666666666667; 2097152.0; 93670.93333333333; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377624779; 1; 2599.999602; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.7333333333333333; 0.0; 0.0 +1377625079; 1; 2599.999602; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 1.0666666666666667; 0.0; 0.0 +1377625379; 1; 2599.999602; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1377625679; 1; 2599.999602; 1.733333068; 0.06666666666666667; 2097152.0; 150994.4; 0.0; 0.9333333333333333; 0.6; 0.0 +1377625979; 1; 2599.999602; 0.0; 0.0; 2097152.0; 142605.6; 0.0; 0.8; 0.0; 0.0 +1377626279; 1; 2599.999602; 0.0; 0.0; 2097152.0; 83884.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377626579; 1; 2599.999602; 1.733333068; 0.06666666666666667; 2097152.0; 114642.93333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377626879; 1; 2599.999602; 6.933332272; 0.26666666666666666; 2097152.0; 109050.4; 0.0; 2.2666666666666666; 0.0; 0.4666666666666667 +1377627179; 1; 2599.999602; 1.733333068; 0.06666666666666667; 2097152.0; 138411.2; 0.0; 1.0; 0.0; 0.0 +1377627479; 1; 2599.999602; 0.0; 0.0; 2097152.0; 159381.6; 0.0; 0.7333333333333333; 0.0; 0.0 +1377627779; 1; 2599.999602; 5.199999203999999; 0.2; 2097152.0; 113244.8; 0.0; 7.266666666666667; 0.26666666666666666; 0.2 +1377628079; 1; 2599.999602; 8.666665340000002; 0.33333333333333337; 2097152.0; 116041.06666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1377628379; 1; 2599.999602; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 1.1333333333333333; 0.0; 0.0 +1377628679; 1; 2599.999602; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1377628979; 1; 2599.999602; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 1.0; 0.06666666666666667; 0.0 +1377629279; 1; 2599.999602; 1.733333068; 0.06666666666666667; 2097152.0; 67106.4; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1377629580; 1; 2599.999602; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377629880; 1; 2599.999602; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 1.0; 0.0; 0.0 +1377630180; 1; 2599.999602; 3.466666136; 0.13333333333333333; 2097152.0; 104856.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1377630480; 1; 2599.999602; 5.199999203999999; 0.2; 2097152.0; 137012.26666666666; 0.06666666666666667; 2.2666666666666666; 0.0; 0.4666666666666667 +1377630780; 1; 2599.999602; 3.466666136; 0.13333333333333333; 2097152.0; 146799.2; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1377631080; 1; 2599.999602; 1.733333068; 0.06666666666666667; 2097152.0; 104856.0; 0.0; 0.8; 0.0; 0.0 +1377631380; 1; 2599.999602; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1377631680; 1; 2599.999602; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1377631980; 1; 2599.999602; 39.866660564; 1.5333333333333334; 2097152.0; 181751.73333333334; 161.06666666666666; 14.266666666666667; 0.13333333333333333; 0.2 +1377632280; 1; 2599.999602; 1.733333068; 0.06666666666666667; 2097152.0; 536869.6; 0.0; 1.2; 0.0; 0.0 +1377632580; 1; 2599.999602; 5.199999203999999; 0.2; 2097152.0; 184548.0; 31.8; 3.8; 0.0; 0.0 +1377632880; 1; 2599.999602; 0.0; 0.0; 2097152.0; 213908.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1377633180; 1; 2599.999602; 0.0; 0.0; 2097152.0; 134216.8; 0.0; 1.0; 0.0; 0.0 +1377633480; 1; 2599.999602; 0.0; 0.0; 2097152.0; 130022.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377633780; 1; 2599.999602; 6.933332272; 0.26666666666666666; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377634080; 1; 2599.999602; 5.199999203999999; 0.2; 2097152.0; 131419.46666666667; 0.06666666666666667; 2.933333333333333; 0.0; 0.5333333333333333 +1377634380; 1; 2599.999602; 0.0; 0.0; 2097152.0; 176159.2; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1377634680; 1; 2599.999602; 6.933332272; 0.26666666666666666; 2097152.0; 171964.0; 12.733333333333333; 13.066666666666666; 0.3333333333333333; 0.13333333333333333 +1377634980; 1; 2599.999602; 0.0; 0.0; 2097152.0; 142604.8; 0.0; 1.0; 0.0; 0.0 +1377635280; 1; 2599.999602; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 1.0; 0.06666666666666667; 0.0 +1377635580; 1; 2599.999602; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377635880; 1; 2599.999602; 0.0; 0.0; 2097152.0; 83884.0; 0.0; 0.7333333333333333; 0.0; 0.0 +1377636180; 1; 2599.999602; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377636480; 1; 2599.999602; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377636780; 1; 2599.999602; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 1.0666666666666667; 0.0; 0.0 +1377637080; 1; 2599.999602; 0.0; 0.0; 2097152.0; 83884.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377637380; 1; 2599.999602; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1377637680; 1; 2599.999602; 5.199999203999999; 0.2; 2097152.0; 123031.73333333334; 0.06666666666666667; 2.3333333333333335; 0.13333333333333333; 0.4666666666666667 +1377637980; 1; 2599.999602; 0.0; 0.0; 2097152.0; 146798.4; 0.0; 1.0; 0.0; 0.0 +1377638280; 1; 2599.999602; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1377638581; 1; 2599.999602; 0.0; 0.0; 2097152.0; 159382.4; 0.0; 0.8; 0.0; 0.0 +1377638881; 1; 2599.999602; 1.733333068; 0.06666666666666667; 2097152.0; 113244.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377639181; 1; 2599.999602; 0.0; 0.0; 2097152.0; 72698.93333333333; 0.0; 0.6666666666666666; 0.0; 0.0 +1377639481; 1; 2599.999602; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 1.0666666666666667; 0.0; 0.0 +1377639781; 1; 2599.999602; 0.0; 0.0; 2097152.0; 134216.8; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1377640081; 1; 2599.999602; 0.0; 0.0; 2097152.0; 121632.8; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377640381; 1; 2599.999602; 0.0; 0.0; 2097152.0; 78291.46666666666; 0.0; 1.0; 0.0; 0.0 +1377640681; 1; 2599.999602; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377640981; 1; 2599.999602; 5.199999203999999; 0.2; 2097152.0; 81087.73333333334; 0.0; 7.133333333333334; 0.2; 0.13333333333333333 +1377641281; 1; 2599.999602; 8.666665340000002; 0.33333333333333337; 2097152.0; 131420.53333333333; 0.06666666666666667; 2.466666666666667; 0.06666666666666667; 0.4666666666666667 +1377641581; 1; 2599.999602; 1.733333068; 0.06666666666666667; 2097152.0; 171963.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377641881; 1; 2599.999602; 1.733333068; 0.06666666666666667; 2097152.0; 117439.2; 0.0; 0.7333333333333333; 0.0; 0.0 +1377642181; 1; 2599.999602; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 1.0; 0.0; 0.0 +1377642481; 1; 2599.999602; 0.0; 0.0; 2097152.0; 85282.13333333333; 0.0; 0.8; 0.06666666666666667; 0.0 +1377642781; 1; 2599.999602; 10.399998407999998; 0.4; 2097152.0; 109050.4; 0.06666666666666667; 1.4666666666666666; 0.06666666666666667; 0.13333333333333333 +1377643081; 1; 2599.999602; 3.466666136; 0.13333333333333333; 2097152.0; 125828.0; 0.0; 0.7333333333333333; 0.0; 0.0 +1377643381; 1; 2599.999602; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 0.8; 0.0; 0.0 +1377643681; 1; 2599.999602; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377643981; 1; 2599.999602; 0.0; 0.0; 2097152.0; 121633.6; 0.0; 1.0; 0.0; 0.0 +1377644281; 1; 2599.999602; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 0.6666666666666666; 0.0; 0.0 +1377644581; 1; 2599.999602; 8.666665340000002; 0.33333333333333337; 2097152.0; 95069.06666666667; 0.0; 0.8666666666666667; 0.13333333333333333; 0.0 +1377644881; 1; 2599.999602; 6.933332272; 0.26666666666666666; 2097152.0; 107652.26666666666; 0.0; 2.3333333333333335; 0.06666666666666667; 0.5333333333333333 +1377645181; 1; 2599.999602; 1.733333068; 0.06666666666666667; 2097152.0; 152392.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1377645481; 1; 2599.999602; 1.733333068; 0.06666666666666667; 2097152.0; 137013.06666666668; 0.0; 0.8; 0.0; 0.0 +1377645781; 1; 2599.999602; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.7333333333333333; 0.0; 0.0 +1377646081; 1; 2599.999602; 0.0; 0.0; 2097152.0; 60115.73333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377646381; 1; 2599.999602; 3.466666136; 0.13333333333333333; 2097152.0; 128623.46666666666; 0.0; 1.0; 0.0; 0.0 +1377646681; 1; 2599.999602; 1.733333068; 0.06666666666666667; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377646981; 1; 2599.999602; 3.466666136; 0.13333333333333333; 2097152.0; 85282.13333333333; 0.0; 7.733333333333333; 0.2; 0.13333333333333333 +1377647281; 1; 2599.999602; 1.733333068; 0.06666666666666667; 2097152.0; 113244.8; 0.0; 1.0; 0.06666666666666667; 0.0 +1377647581; 1; 2599.999602; 0.0; 0.0; 2097152.0; 130022.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377647881; 1; 2599.999602; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377648181; 1; 2599.999602; 0.0; 0.0; 2097152.0; 83884.0; 0.0; 0.8; 0.0; 0.0 +1377648481; 1; 2599.999602; 5.199999203999999; 0.2; 2097152.0; 102059.73333333334; 0.0; 2.2666666666666666; 0.0; 0.4666666666666667 +1377648781; 1; 2599.999602; 3.466666136; 0.13333333333333333; 2097152.0; 160779.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377649081; 1; 2599.999602; 1.733333068; 0.06666666666666667; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 1.1333333333333333; 0.0 +1377649381; 1; 2599.999602; 1.733333068; 0.06666666666666667; 2097152.0; 117439.2; 0.0; 0.8; 0.0; 0.0 +1377649681; 1; 2599.999602; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.8; 0.0; 0.0 +1377649982; 1; 2599.999602; 3.466666136; 0.13333333333333333; 2097152.0; 89476.53333333334; 0.0; 1.2; 0.13333333333333333; 0.0 +1377650282; 1; 2599.999602; 60.66665738; 2.3333333333333335; 2097152.0; 592792.2666666667; 161.06666666666666; 21.8; 0.06666666666666667; 0.4 +1377650582; 1; 2599.999602; 8.666665340000002; 0.33333333333333337; 2097152.0; 596986.9333333333; 0.0; 2.8666666666666667; 0.06666666666666667; 0.0 +1377650882; 1; 2599.999602; 3.466666136; 0.13333333333333333; 2097152.0; 241868.8; 31.8; 3.533333333333333; 0.13333333333333333; 0.0 +1377651182; 1; 2599.999602; 6.933332272; 0.26666666666666666; 2097152.0; 171964.53333333333; 0.0; 2.2666666666666666; 0.06666666666666667; 0.0 +1377651482; 1; 2599.999602; 3.466666136; 0.13333333333333333; 2097152.0; 137013.06666666668; 0.0; 1.4666666666666666; 0.0; 0.0 +1377651782; 1; 2599.999602; 8.666665340000002; 0.33333333333333337; 2097152.0; 95069.06666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1377652082; 1; 2599.999602; 8.666665340000002; 0.33333333333333337; 2097152.0; 82485.33333333333; 0.06666666666666667; 2.6; 0.0; 0.4666666666666667 +1377652382; 1; 2599.999602; 0.0; 0.0; 2097152.0; 137012.53333333333; 0.0; 0.8; 0.0; 0.0 +1377652682; 1; 2599.999602; 0.0; 0.0; 2097152.0; 118837.33333333333; 0.0; 0.8; 0.0; 0.0 +1377652982; 1; 2599.999602; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.6; 0.0; 0.0 +1377653282; 1; 2599.999602; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1377653582; 1; 2599.999602; 13.866664544; 0.5333333333333333; 2097152.0; 121633.06666666667; 0.06666666666666667; 7.266666666666667; 0.26666666666666666; 0.13333333333333333 +1377653882; 1; 2599.999602; 6.933332272; 0.26666666666666666; 2097152.0; 100661.33333333333; 0.0; 0.6666666666666666; 0.0; 0.0 +1377654182; 1; 2599.999602; 5.199999203999999; 0.2; 2097152.0; 83884.0; 0.0; 0.6666666666666666; 0.2; 0.0 +1377654482; 1; 2599.999602; 0.0; 0.0; 2097152.0; 76893.33333333333; 0.0; 0.8; 0.0; 0.0 +1377654782; 1; 2599.999602; 0.0; 0.0; 2097152.0; 81087.73333333334; 0.0; 1.0666666666666667; 0.0; 0.0 +1377655082; 1; 2599.999602; 0.0; 0.0; 2097152.0; 75495.2; 0.0; 0.5333333333333333; 0.0; 0.0 +1377655382; 1; 2599.999602; 3.466666136; 0.13333333333333333; 2097152.0; 93670.93333333333; 0.0; 0.6; 0.0; 0.0 +1377655682; 1; 2599.999602; 5.199999203999999; 0.2; 2097152.0; 145401.33333333334; 0.0; 2.1333333333333333; 0.06666666666666667; 0.4666666666666667 +1377655982; 1; 2599.999602; 3.466666136; 0.13333333333333333; 2097152.0; 144003.46666666667; 0.0; 1.0; 0.06666666666666667; 0.0 +1377656282; 1; 2599.999602; 1.733333068; 0.06666666666666667; 2097152.0; 106254.13333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377656582; 1; 2599.999602; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 0.6; 0.0; 0.0 +1377656882; 1; 2599.999602; 0.0; 0.0; 2097152.0; 159381.6; 0.0; 0.6; 0.0; 0.0 +1377657182; 1; 2599.999602; 1.733333068; 0.06666666666666667; 2097152.0; 97865.33333333333; 0.0; 0.8; 0.06666666666666667; 0.0 +1377657482; 1; 2599.999602; 0.0; 0.0; 2097152.0; 82485.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377657782; 1; 2599.999602; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.6; 0.0; 0.0 +1377658082; 1; 2599.999602; 0.0; 0.0; 2097152.0; 134216.8; 0.0; 0.6666666666666666; 0.0; 0.0 +1377658382; 1; 2599.999602; 1.733333068; 0.06666666666666667; 2097152.0; 92272.8; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1377658682; 1; 2599.999602; 0.0; 0.0; 2097152.0; 82485.86666666667; 0.0; 0.6666666666666666; 0.0; 0.0 +1377658982; 1; 2599.999602; 0.0; 0.0; 2097152.0; 116041.06666666667; 0.0; 0.8; 0.0; 0.0 +1377659282; 1; 2599.999602; 8.666665340000002; 0.33333333333333337; 2097152.0; 127226.13333333333; 0.0; 2.1333333333333333; 0.06666666666666667; 0.4666666666666667 +1377659582; 1; 2599.999602; 5.199999203999999; 0.2; 2097152.0; 135614.13333333333; 0.0; 1.0; 0.0; 0.0 +1377659882; 1; 2599.999602; 0.0; 0.0; 2097152.0; 132818.66666666666; 0.0; 0.6; 0.0; 0.0 +1377660182; 1; 2599.999602; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 0.8; 0.0; 0.0 +1377660482; 1; 2599.999602; 5.199999203999999; 0.2; 2097152.0; 135614.93333333332; 0.0; 6.733333333333333; 0.26666666666666666; 0.2 +1377660782; 1; 2599.999602; 5.199999203999999; 0.2; 2097152.0; 152391.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377661082; 1; 2599.999602; 0.0; 0.0; 2097152.0; 125828.0; 0.0; 0.8; 0.0; 0.0 +1377661382; 1; 2599.999602; 3.466666136; 0.13333333333333333; 2097152.0; 109050.4; 0.0; 0.8; 0.0; 0.0 +1377661682; 1; 2599.999602; 1.733333068; 0.06666666666666667; 2097152.0; 90874.66666666667; 0.0; 1.2; 0.0; 0.0 +1377661982; 1; 2599.999602; 3.466666136; 0.13333333333333333; 2097152.0; 102059.73333333334; 0.0; 0.6; 3.8666666666666667; 0.0 +1377662282; 1; 2599.999602; 6.933332272; 0.26666666666666666; 2097152.0; 125828.0; 0.06666666666666667; 1.6666666666666667; 0.06666666666666667; 0.0 +1377662582; 1; 2599.999602; 5.199999203999999; 0.2; 2097152.0; 120235.46666666666; 0.0; 0.5333333333333333; 0.0; 0.0 +1377662882; 1; 2599.999602; 8.666665340000002; 0.33333333333333337; 2097152.0; 116040.0; 0.13333333333333333; 13.133333333333333; 0.06666666666666667; 0.4666666666666667 +1377663182; 1; 2599.999602; 1.733333068; 0.06666666666666667; 2097152.0; 166372.0; 0.0; 0.8; 0.0; 0.0 +1377663482; 1; 2599.999602; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.6; 1.0; 0.0 +1377663782; 1; 2599.999602; 0.0; 0.0; 2097152.0; 138411.2; 0.0; 0.6; 0.0; 0.0 +1377664082; 1; 2599.999602; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 0.8; 0.0; 0.0 +1377664382; 1; 2599.999602; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377664683; 1; 2599.999602; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 0.6; 0.06666666666666667; 0.0 +1377664983; 1; 2599.999602; 0.0; 0.0; 2097152.0; 123031.73333333334; 0.0; 0.6666666666666666; 0.0; 0.0 +1377665283; 1; 2599.999602; 0.0; 0.0; 2097152.0; 89476.53333333334; 0.0; 1.0666666666666667; 0.0; 0.0 +1377665583; 1; 2599.999602; 0.0; 0.0; 2097152.0; 83884.0; 0.0; 0.6666666666666666; 0.0; 0.0 +1377665883; 1; 2599.999602; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1377666183; 1; 2599.999602; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377666483; 1; 2599.999602; 5.199999203999999; 0.2; 2097152.0; 127225.06666666667; 0.06666666666666667; 2.3333333333333335; 0.0; 0.4666666666666667 +1377666783; 1; 2599.999602; 8.666665340000002; 0.33333333333333337; 2097152.0; 209713.33333333334; 0.0; 7.066666666666666; 0.26666666666666666; 0.13333333333333333 +1377667083; 1; 2599.999602; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1377667383; 1; 2599.999602; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377667683; 1; 2599.999602; 1.733333068; 0.06666666666666667; 2097152.0; 110448.53333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1377667983; 1; 2599.999602; 3.466666136; 0.13333333333333333; 2097152.0; 102059.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377668283; 1; 2599.999602; 0.0; 0.0; 2097152.0; 114642.13333333333; 0.0; 0.9333333333333333; 0.8666666666666667; 0.0 +1377668583; 1; 2599.999602; 0.0; 0.0; 2097152.0; 109049.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1377668883; 1; 2599.999602; 3.466666136; 0.13333333333333333; 2097152.0; 88078.4; 0.0; 1.0; 0.7333333333333333; 0.0 +1377669183; 1; 2599.999602; 0.0; 0.0; 2097152.0; 106254.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377669483; 1; 2599.999602; 0.0; 0.0; 2097152.0; 64310.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377669783; 1; 2599.999602; 3.466666136; 0.13333333333333333; 2097152.0; 76893.33333333333; 0.0; 0.9333333333333333; 0.3333333333333333; 0.0 +1377670083; 1; 2599.999602; 5.199999203999999; 0.2; 2097152.0; 81087.73333333334; 0.0; 2.7333333333333334; 0.0; 0.4666666666666667 +1377670383; 1; 2599.999602; 3.466666136; 0.13333333333333333; 2097152.0; 155188.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1377670683; 1; 2599.999602; 3.466666136; 0.13333333333333333; 2097152.0; 120234.66666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377670983; 1; 2599.999602; 0.0; 0.0; 2097152.0; 68504.53333333334; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377671283; 1; 2599.999602; 3.466666136; 0.13333333333333333; 2097152.0; 95069.06666666667; 0.0; 1.0666666666666667; 0.2; 0.0 +1377671583; 1; 2599.999602; 8.666665340000002; 0.33333333333333337; 2097152.0; 85282.13333333333; 0.0; 2.1333333333333333; 0.13333333333333333; 0.13333333333333333 +1377671883; 1; 2599.999602; 13.866664544; 0.5333333333333333; 2097152.0; 110448.53333333334; 0.0; 1.5333333333333334; 0.3333333333333333; 0.0 +1377672183; 1; 2599.999602; 8.666665340000002; 0.33333333333333337; 2097152.0; 137013.06666666668; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1377672483; 1; 2599.999602; 0.0; 0.0; 2097152.0; 142604.0; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1377672783; 1; 2599.9993; 25.999993; 1.0; 2097152.0; 62912.0; 0.0; 1.125; 0.0; 0.0 +1377673083; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 86680.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377673383; 1; 2599.9993; 12.133330066666666; 0.4666666666666666; 2097152.0; 96467.2; 0.0; 7.0; 0.26666666666666666; 0.2 +1377673683; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 123030.93333333333; 0.0; 2.533333333333333; 0.06666666666666667; 0.4666666666666667 +1377673983; 1; 2599.9993; 8.666664333333335; 0.33333333333333337; 2097152.0; 125828.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1377674283; 1; 2599.9993; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1377674583; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 109050.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377674883; 1; 2599.9993; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377675183; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 85282.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377675483; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 96467.2; 0.0; 1.2; 0.06666666666666667; 0.0 +1377675783; 1; 2599.9993; 0.0; 0.0; 2097152.0; 61513.86666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1377676083; 1; 2599.9993; 0.0; 0.0; 2097152.0; 71300.8; 0.0; 1.2; 0.06666666666666667; 0.0 +1377676383; 1; 2599.9993; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377676684; 1; 2599.9993; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377676984; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 148197.33333333334; 0.0; 1.0666666666666667; 0.0; 0.0 +1377677284; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 135614.13333333333; 0.2; 2.4; 0.0; 0.5333333333333333 +1377677584; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 138410.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377677884; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 92272.8; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377678184; 1; 2599.9993; 0.0; 0.0; 2097152.0; 134216.8; 0.0; 1.1333333333333333; 0.0; 0.0 +1377678484; 1; 2599.9993; 0.0; 0.0; 2097152.0; 117438.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377678784; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 92272.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1377679084; 1; 2599.9993; 10.3999972; 0.4; 2097152.0; 100661.6; 0.0; 1.0; 0.0; 0.0 +1377679384; 1; 2599.9993; 17.33332866666667; 0.6666666666666667; 2097152.0; 74097.06666666667; 0.0; 6.933333333333334; 0.2; 0.13333333333333333 +1377679684; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 150993.6; 0.0; 1.1333333333333333; 0.0; 0.0 +1377679984; 1; 2599.9993; 0.0; 0.0; 2097152.0; 146799.2; 0.0; 0.7333333333333333; 0.0; 0.0 +1377680284; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 96467.2; 0.0; 0.7333333333333333; 0.0; 0.0 +1377680584; 1; 2599.9993; 6.933331466666666; 0.26666666666666666; 2097152.0; 107652.26666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1377680884; 1; 2599.9993; 6.933331466666666; 0.26666666666666666; 2097152.0; 120234.66666666667; 0.0; 2.1333333333333333; 0.06666666666666667; 0.5333333333333333 +1377681184; 1; 2599.9993; 10.3999972; 0.4; 2097152.0; 113244.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377681484; 1; 2599.9993; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.8; 0.0; 0.0 +1377681784; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 109050.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1377682084; 1; 2599.9993; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 0.6666666666666666; 0.0; 0.0 +1377682384; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 96467.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1377682684; 1; 2599.9993; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 0.6; 0.0; 0.0 +1377682984; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 123030.66666666667; 0.0; 1.8666666666666667; 0.0; 0.06666666666666667 +1377683284; 1; 2599.9993; 24.266660133333332; 0.9333333333333332; 2097152.0; 184548.0; 0.0; 3.6666666666666665; 0.0; 0.0 +1377683584; 1; 2599.9993; 13.866662933333332; 0.5333333333333333; 2097152.0; 130022.4; 0.0; 2.8666666666666667; 0.0; 0.0 +1377683884; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 96467.2; 0.0; 1.2; 0.0; 0.0 +1377684184; 1; 2599.9993; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377684484; 1; 2599.9993; 8.666664333333335; 0.33333333333333337; 2097152.0; 109050.4; 0.0; 2.3333333333333335; 0.06666666666666667; 0.4666666666666667 +1377684784; 1; 2599.9993; 13.866662933333332; 0.5333333333333333; 2097152.0; 153789.86666666667; 0.0; 7.0; 0.2; 0.13333333333333333 +1377685084; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 150993.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1377685384; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377685684; 1; 2599.9993; 0.0; 0.0; 2097152.0; 78291.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377685984; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377686284; 1; 2599.9993; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 0.8; 0.06666666666666667; 0.0 +1377686584; 1; 2599.9993; 0.0; 0.0; 2097152.0; 142605.6; 0.0; 1.1333333333333333; 0.0; 0.0 +1377686884; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 142604.8; 0.0; 0.7333333333333333; 0.0; 0.0 +1377687184; 1; 2599.9993; 0.0; 0.0; 2097152.0; 134216.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377687484; 1; 2599.9993; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 0.7333333333333333; 0.0; 0.0 +1377687784; 1; 2599.9993; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.7333333333333333; 0.0; 0.0 +1377688084; 1; 2599.9993; 6.933331466666666; 0.26666666666666666; 2097152.0; 100661.6; 0.06666666666666667; 2.6; 0.06666666666666667; 0.4666666666666667 +1377688384; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 167770.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377688684; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 117439.2; 0.0; 0.8; 0.0; 0.0 +1377688984; 1; 2599.9993; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 0.8; 0.0; 0.0 +1377689284; 1; 2599.9993; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1377689584; 1; 2599.9993; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.7333333333333333; 0.0; 0.0 +1377689884; 1; 2599.9993; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.8; 0.0; 0.0 +1377690184; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 88078.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377690484; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 104856.0; 0.0; 1.1333333333333333; 0.0; 0.0 +1377690784; 1; 2599.9993; 6.933331466666666; 0.26666666666666666; 2097152.0; 157983.46666666667; 0.06666666666666667; 7.2; 0.2; 0.13333333333333333 +1377691084; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 130020.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377691384; 1; 2599.9993; 0.0; 0.0; 2097152.0; 128623.46666666666; 0.0; 1.5333333333333334; 0.0; 0.0 +1377691684; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 109050.4; 0.0; 2.1333333333333333; 0.06666666666666667; 0.4666666666666667 +1377691984; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 127226.13333333333; 0.06666666666666667; 1.1333333333333333; 0.0; 0.0 +1377692284; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 104856.0; 0.0; 0.8; 0.0; 0.0 +1377692584; 1; 2599.9993; 0.0; 0.0; 2097152.0; 95069.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377692884; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 110448.53333333334; 0.0; 0.8; 0.0; 0.0 +1377693184; 1; 2599.9993; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377693484; 1; 2599.9993; 0.0; 0.0; 2097152.0; 95069.06666666667; 0.0; 1.0; 0.0; 0.0 +1377693784; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377694084; 1; 2599.9993; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 0.6666666666666666; 0.0; 0.0 +1377694384; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377694684; 1; 2599.9993; 0.0; 0.0; 2097152.0; 89476.53333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377694984; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 106254.13333333333; 0.0; 1.0; 0.0; 0.0 +1377695284; 1; 2599.9993; 6.933331466666666; 0.26666666666666666; 2097152.0; 120234.93333333333; 0.06666666666666667; 2.1333333333333333; 0.06666666666666667; 0.4666666666666667 +1377695584; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 170567.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1377695884; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 90874.66666666667; 0.0; 0.8; 0.13333333333333333; 0.0 +1377696184; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 134216.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1377696484; 1; 2599.9993; 0.0; 0.0; 2097152.0; 127226.13333333333; 0.0; 0.8666666666666667; 0.2; 0.0 +1377696785; 1; 2599.9993; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.7333333333333333; 0.13333333333333333; 0.0 +1377697085; 1; 2599.9993; 6.933331466666666; 0.26666666666666666; 2097152.0; 124429.06666666667; 12.933333333333334; 13.133333333333333; 0.4666666666666667; 0.13333333333333333 +1377697385; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 95069.06666666667; 0.0; 1.2; 0.06666666666666667; 0.0 +1377697685; 1; 2599.9993; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 1.0; 0.0; 0.0 +1377697985; 1; 2599.9993; 0.0; 0.0; 2097152.0; 82485.86666666667; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377698285; 1; 2599.9993; 0.0; 0.0; 2097152.0; 76893.33333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377698585; 1; 2599.9993; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377698885; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 121633.6; 0.06666666666666667; 2.466666666666667; 0.06666666666666667; 0.5333333333333333 +1377699185; 1; 2599.9993; 0.0; 0.0; 2097152.0; 155188.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377699485; 1; 2599.9993; 0.0; 0.0; 2097152.0; 127226.13333333333; 0.0; 0.8; 0.0; 0.0 +1377699785; 1; 2599.9993; 0.0; 0.0; 2097152.0; 121633.6; 0.0; 0.8; 0.0; 0.0 +1377700085; 1; 2599.9993; 0.0; 0.0; 2097152.0; 138411.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377700385; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 149595.46666666667; 0.06666666666666667; 1.4666666666666666; 0.13333333333333333; 0.06666666666666667 +1377700685; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 109050.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1377700985; 1; 2599.9993; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1377701285; 1; 2599.9993; 0.0; 0.0; 2097152.0; 125828.0; 0.0; 0.8; 0.0; 0.0 +1377701585; 1; 2599.9993; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 0.8; 0.0; 0.0 +1377701885; 1; 2599.9993; 0.0; 0.0; 2097152.0; 83884.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1377702185; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 97865.33333333333; 0.0; 0.8; 0.0; 0.0 +1377702485; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 107652.26666666666; 0.0; 2.066666666666667; 0.06666666666666667; 0.4666666666666667 +1377702785; 1; 2599.9993; 0.0; 0.0; 2097152.0; 184547.2; 0.0; 0.6666666666666666; 0.0; 0.0 +1377703085; 1; 2599.9993; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 1.0; 0.0; 0.0 +1377703385; 1; 2599.9993; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377703685; 1; 2599.9993; 0.0; 0.0; 2097152.0; 95069.06666666667; 0.0; 0.8; 0.0; 0.0 +1377703985; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 123031.73333333334; 0.0; 6.8; 0.2; 0.13333333333333333 +1377704285; 1; 2599.9993; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.7333333333333333; 0.0; 0.0 +1377704585; 1; 2599.9993; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 1.2666666666666666; 0.0; 0.0 +1377704885; 1; 2599.9993; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 0.8; 0.0; 0.0 +1377705185; 1; 2599.9993; 74.53331326666667; 2.8666666666666667; 2097152.0; 657105.6; 161.33333333333334; 22.2; 0.26666666666666666; 0.4 +1377705485; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 374688.26666666666; 0.0; 5.133333333333334; 1.2666666666666666; 0.0 +1377705785; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 218101.33333333334; 31.8; 3.4; 0.0; 0.0 +1377706085; 1; 2599.9993; 10.3999972; 0.4; 2097152.0; 160780.53333333333; 0.0; 3.8; 0.0; 0.5333333333333333 +1377706385; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 137013.06666666668; 0.0; 1.3333333333333333; 0.0; 0.0 +1377706685; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 139809.33333333334; 0.0; 11.533333333333333; 0.0; 0.0 +1377706986; 1; 2599.9993; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1377707286; 1; 2599.9993; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 1.0; 0.0; 0.0 +1377707586; 1; 2599.9993; 0.0; 0.0; 2097152.0; 128624.26666666666; 0.0; 0.5333333333333333; 0.0; 0.0 +1377707886; 1; 2599.9993; 0.0; 0.0; 2097152.0; 78291.46666666666; 0.0; 0.6; 0.0; 0.0 +1377708186; 1; 2599.9993; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 0.6666666666666666; 0.0; 0.0 +1377708486; 1; 2599.9993; 0.0; 0.0; 2097152.0; 120235.46666666666; 0.0; 0.6; 0.06666666666666667; 0.0 +1377708786; 1; 2599.9993; 0.0; 0.0; 2097152.0; 124429.86666666667; 0.0; 1.0; 0.0; 0.0 +1377709086; 1; 2599.9993; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 0.6; 0.0; 0.0 +1377709386; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 102059.73333333334; 0.0; 6.866666666666666; 0.26666666666666666; 0.2 +1377709686; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 152392.53333333333; 0.0; 2.4; 0.06666666666666667; 0.4666666666666667 +1377709986; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 131420.53333333333; 0.0; 0.8; 0.0; 0.0 +1377710286; 1; 2599.9993; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 0.6666666666666666; 0.0; 0.0 +1377710586; 1; 2599.9993; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.6666666666666666; 0.0; 0.0 +1377710886; 1; 2599.9993; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 0.6666666666666666; 0.0; 0.0 +1377711186; 1; 2599.9993; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.6; 0.0; 0.0 +1377711486; 1; 2599.9993; 0.0; 0.0; 2097152.0; 95069.06666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377711786; 1; 2599.9993; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377712086; 1; 2599.9993; 0.0; 0.0; 2097152.0; 118837.33333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377712386; 1; 2599.9993; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.6666666666666666; 0.0; 0.0 +1377712686; 1; 2599.9993; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.6; 0.0; 0.0 +1377712986; 1; 2599.9993; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377713286; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 110448.26666666666; 0.2; 2.2666666666666666; 0.06666666666666667; 0.5333333333333333 +1377713586; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 142604.53333333333; 0.0; 0.6666666666666666; 0.0; 0.0 +1377713886; 1; 2599.9993; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 0.6666666666666666; 0.0; 0.0 +1377714186; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 111846.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1377714486; 1; 2599.9993; 0.0; 0.0; 2097152.0; 83884.0; 0.0; 0.7333333333333333; 0.0; 0.0 +1377714786; 1; 2599.9993; 0.0; 0.0; 2097152.0; 114642.93333333333; 0.0; 0.6; 0.0; 0.0 +1377715086; 1; 2599.9993; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.5333333333333333; 0.0; 0.0 +1377715386; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 89476.53333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1377715686; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 130021.6; 0.0; 7.066666666666666; 0.2; 0.13333333333333333 +1377715987; 1; 2599.9993; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1377716287; 1; 2599.9993; 0.0; 0.0; 2097152.0; 85282.13333333333; 0.0; 0.6; 0.0; 0.0 +1377716587; 1; 2599.9993; 0.0; 0.0; 2097152.0; 78291.46666666666; 0.0; 0.6; 0.0; 0.0 +1377716887; 1; 2599.9993; 6.933331466666666; 0.26666666666666666; 2097152.0; 86680.26666666666; 0.0; 2.3333333333333335; 0.06666666666666667; 0.4666666666666667 +1377717187; 1; 2599.9993; 0.0; 0.0; 2097152.0; 144003.73333333334; 0.0; 0.6666666666666666; 0.0; 0.0 +1377717487; 1; 2599.9993; 0.0; 0.0; 2097152.0; 110448.53333333334; 0.0; 0.5333333333333333; 0.0; 0.0 +1377717787; 1; 2599.9993; 0.0; 0.0; 2097152.0; 116041.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377718087; 1; 2599.9993; 0.0; 0.0; 2097152.0; 114642.93333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1377718387; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 88078.4; 161.73333333333332; 13.333333333333334; 0.0; 0.06666666666666667 +1377718687; 1; 2599.9993; 38.13332306666666; 1.4666666666666666; 2097152.0; 517296.0; 0.0; 2.2666666666666666; 0.0; 0.06666666666666667 +1377718987; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 223694.4; 0.0; 1.3333333333333333; 0.0; 0.0 +1377719286; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 197129.33333333334; 31.8; 3.8666666666666667; 0.0; 0.0 +1377719586; 1; 2599.9993; 0.0; 0.0; 2097152.0; 127225.6; 0.0; 0.8; 0.0; 0.0 +1377719886; 1; 2599.9993; 0.0; 0.0; 2097152.0; 118837.33333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1377720186; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 132818.4; 0.0; 1.2666666666666666; 0.0; 0.0 +1377720486; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 141206.66666666666; 0.0; 2.466666666666667; 0.06666666666666667; 0.4666666666666667 +1377720786; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 169168.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1377721086; 1; 2599.9993; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 1.1333333333333333; 0.0; 0.0 +1377721386; 1; 2599.9993; 0.0; 0.0; 2097152.0; 79689.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1377721686; 1; 2599.9993; 0.0; 0.0; 2097152.0; 76893.33333333333; 0.0; 0.8; 0.0; 0.0 +1377721986; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 93670.93333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377722286; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 104856.0; 0.0; 7.133333333333334; 0.2; 0.13333333333333333 +1377722586; 1; 2599.9993; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1377722886; 1; 2599.9993; 0.0; 0.0; 2097152.0; 106254.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377723186; 1; 2599.9993; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 0.7333333333333333; 0.0; 0.0 +1377723486; 1; 2599.9993; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377723787; 1; 2599.9993; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377724087; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 88078.4; 0.06666666666666667; 2.533333333333333; 0.06666666666666667; 0.5333333333333333 +1377724387; 1; 2599.9993; 0.0; 0.0; 2097152.0; 148196.53333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377724687; 1; 2599.9993; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.7333333333333333; 0.0; 0.0 +1377724987; 1; 2599.9993; 0.0; 0.0; 2097152.0; 124429.86666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1377725287; 1; 2599.9993; 0.0; 0.0; 2097152.0; 162177.33333333334; 0.0; 0.8; 0.0; 0.0 +1377725587; 1; 2599.9993; 0.0; 0.0; 2097152.0; 125828.0; 0.0; 0.6666666666666666; 0.0; 0.0 +1377725887; 1; 2599.9993; 0.0; 0.0; 2097152.0; 130022.4; 0.0; 0.8; 0.0; 0.0 +1377726187; 1; 2599.9993; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377726487; 1; 2599.9993; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.6666666666666666; 0.0; 0.0 +1377726787; 1; 2599.9993; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377727087; 1; 2599.9993; 0.0; 0.0; 2097152.0; 123031.73333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1377727387; 1; 2599.9993; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.7333333333333333; 0.0; 0.0 +1377727687; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 127225.86666666667; 0.06666666666666667; 2.3333333333333335; 0.06666666666666667; 0.4666666666666667 +1377727987; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 170566.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377728287; 1; 2599.9993; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377728587; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 128624.26666666666; 0.0; 7.0; 0.26666666666666666; 0.13333333333333333 +1377728887; 1; 2599.9993; 0.0; 0.0; 2097152.0; 79689.6; 0.0; 0.6666666666666666; 0.0; 0.0 +1377729187; 1; 2599.9993; 6.933331466666666; 0.26666666666666666; 2097152.0; 81087.73333333334; 0.06666666666666667; 1.3333333333333333; 0.06666666666666667; 0.06666666666666667 +1377729487; 1; 2599.9993; 0.0; 0.0; 2097152.0; 144003.73333333334; 0.0; 1.0666666666666667; 0.0; 0.0 +1377729787; 1; 2599.9993; 0.0; 0.0; 2097152.0; 113244.26666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1377730087; 1; 2599.9993; 0.0; 0.0; 2097152.0; 138409.86666666667; 0.0; 0.8; 0.0; 0.0 +1377730387; 1; 2599.9993; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 0.6666666666666666; 0.0; 0.0 +1377730687; 1; 2599.9993; 0.0; 0.0; 2097152.0; 114642.93333333333; 0.0; 1.0; 0.0; 0.0 +1377730987; 1; 2599.9993; 0.0; 0.0; 2097152.0; 82485.86666666667; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377731288; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 138410.4; 0.4666666666666667; 2.2666666666666666; 0.0; 0.5333333333333333 +1377731588; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 138410.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377731888; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 79689.6; 0.0; 0.7333333333333333; 0.0; 0.0 +1377732188; 1; 2599.9993; 0.0; 0.0; 2097152.0; 118837.33333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377732488; 1; 2599.9993; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 0.6; 0.0; 0.0 +1377732788; 1; 2599.9993; 0.0; 0.0; 2097152.0; 89476.53333333334; 0.0; 0.8; 0.0; 0.0 +1377733088; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 67106.4; 0.0; 1.0; 0.0; 0.0 +1377733388; 1; 2599.9993; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 0.6; 0.0; 0.0 +1377733688; 1; 2599.9993; 0.0; 0.0; 2097152.0; 131420.53333333333; 0.0; 1.2666666666666666; 0.0; 0.0 +1377733988; 1; 2599.9993; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 0.7333333333333333; 0.0; 0.0 +1377734288; 1; 2599.9993; 0.0; 0.0; 2097152.0; 128624.26666666666; 0.0; 0.8; 0.0; 0.0 +1377734588; 1; 2599.9993; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 0.6666666666666666; 0.0; 0.0 +1377734888; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 111846.66666666667; 0.06666666666666667; 2.533333333333333; 0.06666666666666667; 0.4666666666666667 +1377735188; 1; 2599.9993; 0.0; 0.0; 2097152.0; 171964.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377735488; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 146799.2; 0.06666666666666667; 6.933333333333334; 0.2; 0.13333333333333333 +1377735788; 1; 2599.9993; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 1.4; 0.0; 0.0 +1377736088; 1; 2599.9993; 0.0; 0.0; 2097152.0; 116041.06666666667; 0.0; 0.6666666666666666; 0.0; 0.0 +1377736388; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 130022.4; 0.0; 1.4; 0.0; 0.0 +1377736688; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 106254.13333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1377736988; 1; 2599.9993; 0.0; 0.0; 2097152.0; 69902.66666666667; 0.0; 0.6666666666666666; 0.0; 0.0 +1377737288; 1; 2599.9993; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 0.6666666666666666; 0.0; 0.0 +1377737588; 1; 2599.9993; 0.0; 0.0; 2097152.0; 121633.6; 0.0; 0.7333333333333333; 0.0; 0.0 +1377738188; 1; 2599.9993; 0.0; 0.0; 2097152.0; 81087.73333333334; 0.0; 0.6; 0.0; 0.0 +1377738489; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 106253.33333333333; 0.0; 2.066666666666667; 0.06666666666666667; 0.4666666666666667 +1377738789; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 149594.66666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1377739089; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 123031.73333333334; 0.0; 1.0666666666666667; 0.0; 0.0 +1377739389; 1; 2599.9993; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 0.8; 0.0; 0.0 +1377739689; 1; 2599.9993; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.6; 0.0; 0.0 +1377739989; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 99263.46666666666; 0.0; 1.1333333333333333; 0.0; 0.0 +1377740289; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 107652.26666666666; 0.0; 1.1333333333333333; 0.0; 0.0 +1377740589; 1; 2599.9993; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1377740889; 1; 2599.9993; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377741189; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 144002.66666666666; 0.0; 7.133333333333334; 0.26666666666666666; 0.13333333333333333 +1377741489; 1; 2599.9993; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1377741789; 1; 2599.9993; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 1.0; 0.0; 0.0 +1377742089; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 92272.8; 0.0; 2.4; 0.06666666666666667; 0.4666666666666667 +1377742389; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 118837.33333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1377742689; 1; 2599.9993; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377742989; 1; 2599.9993; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377743289; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 99263.46666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1377743589; 1; 2599.9993; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1377743889; 1; 2599.9993; 0.0; 0.0; 2097152.0; 127225.33333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1377744189; 1; 2599.9993; 0.0; 0.0; 2097152.0; 83884.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1377744489; 1; 2599.9993; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377744789; 1; 2599.9993; 0.0; 0.0; 2097152.0; 110448.53333333334; 0.0; 1.1333333333333333; 0.0; 0.0 +1377745089; 1; 2599.9993; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377745389; 1; 2599.9993; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1377745689; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 131420.53333333333; 0.0; 2.533333333333333; 0.06666666666666667; 0.4666666666666667 +1377745989; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 171964.8; 0.0; 1.0666666666666667; 0.0; 0.0 +1377746289; 1; 2599.9993; 0.0; 0.0; 2097152.0; 127225.33333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1377746589; 1; 2599.9993; 0.0; 0.0; 2097152.0; 142604.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1377746889; 1; 2599.9993; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377747189; 1; 2599.9993; 0.0; 0.0; 2097152.0; 86680.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377747489; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 92272.8; 0.2; 7.133333333333334; 0.3333333333333333; 0.13333333333333333 +1377747789; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 142605.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1377748089; 1; 2599.9993; 0.0; 0.0; 2097152.0; 146799.2; 0.0; 1.1333333333333333; 0.0; 0.0 +1377748389; 1; 2599.9993; 0.0; 0.0; 2097152.0; 134216.8; 0.0; 1.0; 0.0; 0.0 +1377748690; 1; 2599.9993; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1377748990; 1; 2599.9993; 0.0; 0.0; 2097152.0; 85282.13333333333; 0.0; 1.0; 0.0; 0.0 +1377749290; 1; 2599.9993; 8.666664333333335; 0.33333333333333337; 2097152.0; 96467.2; 0.0; 2.3333333333333335; 0.0; 0.4666666666666667 +1377749590; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 104856.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1377749890; 1; 2599.9993; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377750190; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 113244.8; 0.0; 11.933333333333334; 0.0; 0.0 +1377750490; 1; 2599.9993; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1377750790; 1; 2599.9993; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377751090; 1; 2599.9993; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377751390; 1; 2599.9993; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1377751690; 1; 2599.9993; 0.0; 0.0; 2097152.0; 138411.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1377751990; 1; 2599.9993; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 1.0; 0.0; 0.0 +1377752289; 1; 2599.9993; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377752589; 1; 2599.9993; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1377752889; 1; 2599.9993; 12.133330066666666; 0.4666666666666666; 2097152.0; 142604.53333333333; 0.0; 8.8; 0.26666666666666666; 0.6666666666666666 +1377753189; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 171965.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1377753489; 1; 2599.9993; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1377753789; 1; 2599.9993; 0.0; 0.0; 2097152.0; 83884.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1377754089; 1; 2599.9993; 0.0; 0.0; 2097152.0; 78291.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377754389; 1; 2599.9993; 0.0; 0.0; 2097152.0; 114642.93333333333; 0.0; 1.0; 0.0; 0.0 +1377754689; 1; 2599.9993; 0.0; 0.0; 2097152.0; 135614.93333333332; 0.0; 0.8; 0.0; 0.0 +1377754989; 1; 2599.9993; 0.0; 0.0; 2097152.0; 125827.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1377755289; 1; 2599.9993; 0.0; 0.0; 2097152.0; 86680.26666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1377755589; 1; 2599.9993; 0.0; 0.0; 2097152.0; 167770.4; 0.0; 1.0666666666666667; 0.0; 0.0 +1377755890; 1; 2599.9993; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377756190; 1; 2599.9993; 0.0; 0.0; 2097152.0; 71300.8; 0.0; 0.8; 0.0; 0.0 +1377756490; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 103456.8; 0.06666666666666667; 2.466666666666667; 0.06666666666666667; 0.4666666666666667 +1377756790; 1; 2599.9993; 0.0; 0.0; 2097152.0; 159381.6; 0.06666666666666667; 0.8666666666666667; 0.0; 0.0 +1377757090; 1; 2599.9993; 0.0; 0.0; 2097152.0; 116041.06666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377757390; 1; 2599.9993; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 1.0; 0.0; 0.0 +1377757690; 1; 2599.9993; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377757990; 1; 2599.9993; 6.933331466666666; 0.26666666666666666; 2097152.0; 96467.2; 0.0; 1.9333333333333333; 0.06666666666666667; 0.06666666666666667 +1377758290; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 121633.6; 0.0; 1.6; 0.0; 0.0 +1377758590; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 83884.0; 0.0; 1.3333333333333333; 0.0; 0.0 +1377758890; 1; 2599.9993; 6.933331466666666; 0.26666666666666666; 2097152.0; 149594.66666666666; 12.733333333333333; 12.866666666666667; 0.26666666666666666; 0.13333333333333333 +1377759190; 1; 2599.9993; 0.0; 0.0; 2097152.0; 138411.2; 0.0; 1.4; 0.0; 0.0 +1377759490; 1; 2599.9993; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.7333333333333333; 0.0; 0.0 +1377759790; 1; 2599.9993; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1377760090; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 120235.46666666666; 0.06666666666666667; 2.6; 0.0; 0.4666666666666667 +1377760390; 1; 2599.9993; 0.0; 0.0; 2097152.0; 176158.4; 0.0; 0.8; 0.0; 0.0 +1377760690; 1; 2599.9993; 0.0; 0.0; 2097152.0; 125827.2; 0.0; 0.8; 0.0; 0.0 +1377760990; 1; 2599.9993; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.8; 0.0; 0.0 +1377761290; 1; 2599.9993; 0.0; 0.0; 2097152.0; 62912.0; 0.0; 1.0; 0.0; 0.0 +1377761590; 1; 2599.9993; 0.0; 0.0; 2097152.0; 79689.6; 0.0; 0.8; 0.0; 0.0 +1377761890; 1; 2599.9993; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377762190; 1; 2599.9993; 57.19998460000001; 2.2; 2097152.0; 634736.5333333333; 161.06666666666666; 22.533333333333335; 0.06666666666666667; 0.4 +1377762490; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 507508.8; 0.0; 2.7333333333333334; 0.0; 0.0 +1377762790; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 260044.8; 31.8; 3.8666666666666667; 0.0; 0.0 +1377763090; 1; 2599.9993; 6.933331466666666; 0.26666666666666666; 2097152.0; 159382.4; 0.0; 2.2; 0.0; 0.0 +1377763390; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 145401.06666666668; 0.0; 2.2666666666666666; 0.0; 0.0 +1377763690; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 117439.2; 0.0; 2.4; 0.06666666666666667; 0.4666666666666667 +1377763990; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 163576.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1377764290; 1; 2599.9993; 0.0; 0.0; 2097152.0; 121633.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1377764590; 1; 2599.9993; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.7333333333333333; 0.0; 0.0 +1377764890; 1; 2599.9993; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.7333333333333333; 0.0; 0.0 +1377765191; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 113244.8; 0.0; 6.733333333333333; 0.2; 0.13333333333333333 +1377765491; 1; 2599.9993; 6.933331466666666; 0.26666666666666666; 2097152.0; 117438.4; 0.0; 1.3333333333333333; 0.0; 0.0 +1377765791; 1; 2599.9993; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.8; 0.0; 0.0 +1377766091; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 117439.2; 0.0; 0.8; 0.06666666666666667; 0.0 +1377766391; 1; 2599.9993; 0.0; 0.0; 2097152.0; 67106.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1377766691; 1; 2599.9993; 0.0; 0.0; 2097152.0; 82485.86666666667; 0.0; 1.0; 0.0; 0.0 +1377766991; 1; 2599.9993; 0.0; 0.0; 2097152.0; 81087.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377767291; 1; 2599.9993; 6.933331466666666; 0.26666666666666666; 2097152.0; 180353.06666666668; 0.0; 2.2; 0.06666666666666667; 0.4666666666666667 +1377767591; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 216704.0; 0.0; 0.7333333333333333; 0.0; 0.0 +1377767891; 1; 2599.9993; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.6666666666666666; 0.0; 0.0 +1377768191; 1; 2599.9993; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 1.0; 0.0; 0.0 +1377768491; 1; 2599.9993; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.8; 0.0; 0.0 +1377768791; 1; 2599.9993; 0.0; 0.0; 2097152.0; 86680.26666666666; 0.0; 0.8; 0.0; 0.0 +1377769091; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 81087.73333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1377769391; 1; 2599.9993; 0.0; 0.0; 2097152.0; 72698.93333333333; 0.0; 1.0; 0.0; 0.0 +1377769691; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 57319.46666666667; 0.0; 1.2; 0.0; 0.0 +1377769991; 1; 2599.9993; 0.0; 0.0; 2097152.0; 68504.53333333334; 0.0; 0.8; 0.0; 0.0 +1377770291; 1; 2599.9993; 0.0; 0.0; 2097152.0; 64310.13333333333; 0.0; 0.6666666666666666; 0.0; 0.0 +1377770591; 1; 2599.9993; 0.0; 0.0; 2097152.0; 81087.73333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1377770891; 1; 2599.9993; 6.933331466666666; 0.26666666666666666; 2097152.0; 107652.26666666666; 0.06666666666666667; 2.6666666666666665; 0.0; 0.4666666666666667 +1377771191; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 145401.06666666668; 0.0; 0.8; 0.0; 0.0 +1377771491; 1; 2599.9993; 0.0; 0.0; 2097152.0; 131420.53333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377771791; 1; 2599.9993; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1377772091; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 137013.06666666668; 0.06666666666666667; 6.666666666666667; 0.2; 0.13333333333333333 +1377772391; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 106254.13333333333; 0.0; 1.3333333333333333; 0.0; 0.0 +1377772691; 1; 2599.9993; 0.0; 0.0; 2097152.0; 130022.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1377772991; 1; 2599.9993; 0.0; 0.0; 2097152.0; 131420.0; 0.0; 0.8; 0.0; 0.0 +1377773291; 1; 2599.9993; 0.0; 0.0; 2097152.0; 109050.13333333333; 0.0; 1.0; 0.0; 0.0 +1377773591; 1; 2599.9993; 0.0; 0.0; 2097152.0; 76893.33333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1377773891; 1; 2599.9993; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377774191; 1; 2599.9993; 0.0; 0.0; 2097152.0; 124429.86666666667; 0.0; 0.8; 0.0; 0.0 +1377774491; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 113244.8; 0.0; 2.2; 0.0; 0.4666666666666667 +1377774791; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 197130.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1377775091; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 120235.46666666666; 0.0; 1.0; 0.0; 0.0 +1377775391; 1; 2599.9993; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.8; 0.0; 0.0 +1377775691; 1; 2599.9993; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377775991; 1; 2599.9993; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377776291; 1; 2599.9993; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.7333333333333333; 0.0; 0.0 +1377776591; 1; 2599.9993; 0.0; 0.0; 2097152.0; 123031.73333333334; 0.0; 1.0666666666666667; 0.0; 0.0 +1377776892; 1; 2599.9993; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 1.2; 0.0; 0.0 +1377777192; 1; 2599.9993; 0.0; 0.0; 2097152.0; 121633.6; 0.0; 0.7333333333333333; 0.0; 0.0 +1377777492; 1; 2599.9993; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.7333333333333333; 0.0; 0.0 +1377777792; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 120235.46666666666; 0.0; 6.866666666666666; 0.26666666666666666; 0.13333333333333333 +1377778092; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 138410.4; 0.0; 2.2666666666666666; 0.06666666666666667; 0.4666666666666667 +1377778392; 1; 2599.9993; 0.0; 0.0; 2097152.0; 135614.93333333332; 0.0; 0.9333333333333333; 0.0; 0.0 +1377778692; 1; 2599.9993; 0.0; 0.0; 2097152.0; 114642.93333333333; 0.0; 0.6666666666666666; 0.0; 0.0 +1377778992; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 124429.86666666667; 0.0; 0.5333333333333333; 0.0; 0.0 +1377779292; 1; 2599.9993; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377779592; 1; 2599.9993; 0.0; 0.0; 2097152.0; 125828.0; 0.0; 0.6666666666666666; 0.06666666666666667; 0.0 +1377779892; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 93670.93333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377780192; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 102059.73333333334; 0.0; 0.6666666666666666; 0.0; 0.0 +1377780492; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 93670.93333333333; 0.0; 1.6666666666666667; 0.0; 0.0 +1377780792; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 109050.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1377781092; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 99263.46666666666; 0.0; 0.6; 4.066666666666666; 0.0 +1377781392; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 124429.86666666667; 0.0; 0.6; 0.0; 0.0 +1377781692; 1; 2599.9993; 8.666664333333335; 0.33333333333333337; 2097152.0; 103457.86666666667; 0.0; 2.1333333333333333; 0.0; 0.4666666666666667 +1377781992; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 166372.26666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1377782292; 1; 2599.9993; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 0.5333333333333333; 0.0; 0.0 +1377782592; 1; 2599.9993; 0.0; 0.0; 2097152.0; 76893.33333333333; 0.0; 0.6; 0.0; 0.0 +1377782892; 1; 2599.9993; 0.0; 0.0; 2097152.0; 68504.53333333334; 0.0; 0.6666666666666666; 0.0; 0.0 +1377783192; 1; 2599.9993; 0.0; 0.0; 2097152.0; 120234.66666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1377783492; 1; 2599.9993; 0.0; 0.0; 2097152.0; 110448.53333333334; 0.0; 1.0; 0.0; 0.0 +1377783792; 1; 2599.9993; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.6666666666666666; 0.0; 0.0 +1377784092; 1; 2599.9993; 0.0; 0.0; 2097152.0; 118837.33333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377784392; 1; 2599.9993; 0.0; 0.0; 2097152.0; 131419.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377784692; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 106254.13333333333; 0.0; 7.0; 0.2; 0.13333333333333333 +1377784992; 1; 2599.9993; 0.0; 0.0; 2097152.0; 89476.53333333334; 0.0; 0.6; 0.0; 0.0 +1377785292; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 96467.2; 0.06666666666666667; 2.066666666666667; 0.06666666666666667; 0.4666666666666667 +1377785591; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 159381.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1377785891; 1; 2599.9993; 0.0; 0.0; 2097152.0; 106254.13333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377786192; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 114642.93333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377786492; 1; 2599.9993; 0.0; 0.0; 2097152.0; 110448.53333333334; 0.0; 0.6; 0.0; 0.0 +1377786792; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 92272.8; 0.06666666666666667; 1.4; 0.06666666666666667; 0.13333333333333333 +1377787092; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 104856.0; 0.0; 0.7333333333333333; 0.0; 0.0 +1377787392; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 97865.33333333333; 0.0; 0.6; 0.0; 0.0 +1377787692; 1; 2599.9993; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1377787992; 1; 2599.9993; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 0.6666666666666666; 0.0; 0.0 +1377788292; 1; 2599.9993; 0.0; 0.0; 2097152.0; 118837.33333333333; 0.0; 0.6666666666666666; 0.0; 0.0 +1377788592; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 97865.33333333333; 0.06666666666666667; 1.0; 0.0; 0.0 +1377788892; 1; 2599.9993; 6.933331466666666; 0.26666666666666666; 2097152.0; 118837.33333333333; 0.0; 2.3333333333333335; 0.26666666666666666; 0.4666666666666667 +1377789192; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 162177.86666666667; 0.0; 0.8; 0.0; 0.0 +1377789492; 1; 2599.9993; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 0.6; 0.0; 0.0 +1377789792; 1; 2599.9993; 0.0; 0.0; 2097152.0; 68504.53333333334; 0.0; 0.6666666666666666; 0.06666666666666667; 0.0 +1377790092; 1; 2599.9993; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 0.6666666666666666; 0.0; 0.0 +1377790392; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 124429.06666666667; 0.0; 7.066666666666666; 0.2; 0.13333333333333333 +1377790692; 1; 2599.9993; 0.0; 0.0; 2097152.0; 128624.26666666666; 0.0; 0.8; 0.0; 0.0 +1377790992; 1; 2599.9993; 0.0; 0.0; 2097152.0; 114642.93333333333; 0.0; 0.6; 0.0; 0.0 +1377791292; 1; 2599.9993; 0.0; 0.0; 2097152.0; 123031.73333333334; 0.0; 1.0; 0.0; 0.0 +1377791592; 1; 2599.9993; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377791892; 1; 2599.9993; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1377792192; 1; 2599.9993; 0.0; 0.0; 2097152.0; 121633.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1377792492; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 114642.93333333333; 0.0; 2.2666666666666666; 0.0; 0.4666666666666667 +1377792792; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 146798.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1377793092; 1; 2599.9993; 0.0; 0.0; 2097152.0; 81087.73333333334; 0.0; 1.1333333333333333; 0.0; 0.0 +1377793392; 1; 2599.9993; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377793692; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 106254.13333333333; 0.0; 11.733333333333333; 0.0; 0.0 +1377793992; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 131420.53333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377794292; 1; 2599.9993; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1377794592; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 111846.66666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1377794892; 1; 2599.9993; 0.0; 0.0; 2097152.0; 135614.93333333332; 0.0; 1.0; 0.0; 0.0 +1377795192; 1; 2599.9993; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377795492; 1; 2599.9993; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377795793; 1; 2599.9993; 0.0; 0.0; 2097152.0; 74097.06666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1377796093; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 125828.0; 0.06666666666666667; 2.533333333333333; 0.06666666666666667; 0.4666666666666667 +1377796393; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 159381.6; 0.0; 7.066666666666666; 0.26666666666666666; 0.13333333333333333 +1377796693; 1; 2599.9993; 0.0; 0.0; 2097152.0; 131420.53333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377796993; 1; 2599.9993; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377797293; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 88078.4; 0.0; 1.1333333333333333; 0.0; 0.0 +1377797593; 1; 2599.9993; 0.0; 0.0; 2097152.0; 135614.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377797893; 1; 2599.9993; 0.0; 0.0; 2097152.0; 116041.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377798193; 1; 2599.9993; 0.0; 0.0; 2097152.0; 146800.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377798493; 1; 2599.9993; 0.0; 0.0; 2097152.0; 135614.93333333332; 0.0; 1.1333333333333333; 0.0; 0.0 +1377798793; 1; 2599.9993; 0.0; 0.0; 2097152.0; 155187.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1377799093; 1; 2599.9993; 0.0; 0.0; 2097152.0; 139809.33333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1377799393; 1; 2599.9993; 0.0; 0.0; 2097152.0; 79689.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1377799693; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 81087.73333333334; 0.06666666666666667; 2.4; 0.06666666666666667; 0.4666666666666667 +1377799993; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 107652.26666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1377800293; 1; 2599.9993; 0.0; 0.0; 2097152.0; 106254.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377800593; 1; 2599.9993; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377800893; 1; 2599.9993; 0.0; 0.0; 2097152.0; 169168.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377801193; 1; 2599.9993; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1377801493; 1; 2599.9993; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1377801793; 1; 2599.9993; 6.933331466666666; 0.26666666666666666; 2097152.0; 103457.86666666667; 0.0; 6.733333333333333; 0.2; 0.13333333333333333 +1377802093; 1; 2599.9993; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377802393; 1; 2599.9993; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 1.0; 0.0; 0.0 +1377802693; 1; 2599.9993; 0.0; 0.0; 2097152.0; 85282.13333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1377802993; 1; 2599.9993; 0.0; 0.0; 2097152.0; 81087.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377803293; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 99263.46666666666; 0.0; 2.066666666666667; 0.06666666666666667; 0.4666666666666667 +1377803593; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 160779.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377803893; 1; 2599.9993; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377804194; 1; 2599.9993; 0.0; 0.0; 2097152.0; 78291.46666666666; 0.0; 1.1333333333333333; 0.0; 0.0 +1377804494; 1; 2599.9993; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377804794; 1; 2599.9993; 0.0; 0.0; 2097152.0; 69902.66666666667; 0.0; 0.8; 0.0; 0.0 +1377805094; 1; 2599.9993; 39.866655933333334; 1.5333333333333334; 2097152.0; 436205.86666666664; 161.0; 14.2; 0.0; 0.2 +1377805394; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 257248.26666666666; 0.0; 1.4; 0.0; 0.0 +1377805694; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 184547.73333333334; 31.8; 3.7333333333333334; 0.0; 0.0 +1377805994; 1; 2599.9993; 0.0; 0.0; 2097152.0; 155187.46666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1377806294; 1; 2599.9993; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1377806594; 1; 2599.9993; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.6; 0.0; 0.0 +1377806894; 1; 2599.9993; 6.933331466666666; 0.26666666666666666; 2097152.0; 90874.66666666667; 0.06666666666666667; 2.3333333333333335; 0.06666666666666667; 0.4666666666666667 +1377807194; 1; 2599.9993; 0.0; 0.0; 2097152.0; 153789.06666666668; 0.0; 0.5333333333333333; 0.0; 0.0 +1377807494; 1; 2599.9993; 0.0; 0.0; 2097152.0; 169169.33333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377807794; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 131420.26666666666; 0.06666666666666667; 6.8; 0.2; 0.13333333333333333 +1377808094; 1; 2599.9993; 0.0; 0.0; 2097152.0; 130021.86666666667; 0.0; 0.6; 0.0; 0.0 +1377808394; 1; 2599.9993; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 1.0; 0.0; 0.0 +1377808694; 1; 2599.9993; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1377808994; 1; 2599.9993; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1377809294; 1; 2599.9993; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 0.6; 0.0; 0.0 +1377809594; 1; 2599.9993; 0.0; 0.0; 2097152.0; 141207.46666666667; 0.0; 1.0; 0.0; 0.0 +1377809894; 1; 2599.9993; 0.0; 0.0; 2097152.0; 132818.66666666666; 0.0; 0.8; 0.0; 0.0 +1377810194; 1; 2599.9993; 0.0; 0.0; 2097152.0; 127226.13333333333; 0.0; 0.6; 0.0; 0.0 +1377810494; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 124429.06666666667; 0.0; 1.8666666666666667; 0.06666666666666667; 0.4666666666666667 +1377810794; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 180353.6; 0.0; 0.7333333333333333; 0.0; 0.0 +1377811094; 1; 2599.9993; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 1.0; 0.0; 0.0 +1377811394; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 116041.06666666667; 0.0; 0.6; 0.06666666666666667; 0.0 +1377811694; 1; 2599.9993; 0.0; 0.0; 2097152.0; 116041.06666666667; 0.0; 0.6; 0.0; 0.0 +1377811994; 1; 2599.9993; 0.0; 0.0; 2097152.0; 114642.93333333333; 0.0; 0.6; 0.0; 0.0 +1377812294; 1; 2599.9993; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.8; 0.0; 0.0 +1377812594; 1; 2599.9993; 0.0; 0.0; 2097152.0; 163576.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1377812894; 1; 2599.9993; 0.0; 0.0; 2097152.0; 116041.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377813194; 1; 2599.9993; 0.0; 0.0; 2097152.0; 71300.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377813494; 1; 2599.9993; 0.0; 0.0; 2097152.0; 76893.33333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1377813794; 1; 2599.9993; 0.0; 0.0; 2097152.0; 81087.73333333334; 0.0; 1.1333333333333333; 0.0; 0.0 +1377814094; 1; 2599.9993; 10.3999972; 0.4; 2097152.0; 92272.8; 0.0; 8.0; 0.3333333333333333; 0.6666666666666666 +1377814395; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 145401.06666666668; 0.0; 0.8666666666666667; 0.0; 0.0 +1377814695; 1; 2599.9993; 0.0; 0.0; 2097152.0; 146800.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377814995; 1; 2599.9993; 0.0; 0.0; 2097152.0; 138411.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377815295; 1; 2599.9993; 17.33332866666667; 0.6666666666666667; 2097152.0; 160780.53333333333; 161.0; 20.6; 0.06666666666666667; 0.4 +1377815595; 1; 2599.9993; 48.533320266666664; 1.8666666666666665; 2097152.0; 781536.8; 0.0; 4.2; 0.06666666666666667; 0.13333333333333333 +1377815895; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 367698.93333333335; 31.8; 3.0; 0.0; 0.0 +1377816195; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 219500.0; 0.0; 2.6; 0.0; 0.0 +1377816495; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 134216.8; 0.0; 1.8; 0.0; 0.0 +1377816795; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 132818.66666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1377817095; 1; 2599.9993; 0.0; 0.0; 2097152.0; 134216.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377817395; 1; 2599.9993; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377817694; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 114642.66666666667; 0.2; 2.7333333333333334; 0.0; 0.4666666666666667 +1377817994; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 157982.93333333332; 0.0; 1.1333333333333333; 0.0; 0.0 +1377818294; 1; 2599.9993; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377818594; 1; 2599.9993; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377818894; 1; 2599.9993; 0.0; 0.0; 2097152.0; 128624.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377819194; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 102059.73333333334; 0.2; 1.0; 0.0; 0.0 +1377819494; 1; 2599.9993; 0.0; 0.0; 2097152.0; 127226.13333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1377819794; 1; 2599.9993; 0.0; 0.0; 2097152.0; 114642.93333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1377820094; 1; 2599.9993; 0.0; 0.0; 2097152.0; 141206.66666666666; 0.0; 1.2; 0.0; 0.0 +1377820394; 1; 2599.9993; 6.933331466666666; 0.26666666666666666; 2097152.0; 170566.66666666666; 12.733333333333333; 16.8; 0.4; 0.13333333333333333 +1377820694; 1; 2599.9993; 0.0; 0.0; 2097152.0; 123031.73333333334; 0.0; 1.5333333333333334; 0.0; 0.0 +1377820994; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377821294; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 114642.13333333333; 0.0; 2.3333333333333335; 0.06666666666666667; 0.4666666666666667 +1377821594; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 157983.46666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377821894; 1; 2599.9993; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377822194; 1; 2599.9993; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 1.1333333333333333; 0.0; 0.0 +1377822494; 1; 2599.9993; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1377822794; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 146800.0; 0.0; 1.4; 0.0; 0.0 +1377823094; 1; 2599.9993; 0.0; 0.0; 2097152.0; 85282.13333333333; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377823394; 1; 2599.9993; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1377823694; 1; 2599.9993; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377823994; 1; 2599.9993; 0.0; 0.0; 2097152.0; 81087.73333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1377824294; 1; 2599.9993; 0.0; 0.0; 2097152.0; 71300.8; 0.0; 0.8; 0.0; 0.0 +1377824595; 1; 2599.9993; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.7333333333333333; 0.0; 0.0 +1377824895; 1; 2599.9993; 6.933331466666666; 0.26666666666666666; 2097152.0; 117438.93333333333; 0.0; 3.2; 0.06666666666666667; 0.4666666666666667 +1377825195; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 145400.53333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1377825495; 1; 2599.9993; 0.0; 0.0; 2097152.0; 121633.6; 0.0; 0.8; 0.0; 0.0 +1377825795; 1; 2599.9993; 0.0; 0.0; 2097152.0; 114642.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377826095; 1; 2599.9993; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.7333333333333333; 0.0; 0.0 +1377826395; 1; 2599.9993; 8.666664333333335; 0.33333333333333337; 2097152.0; 90874.66666666667; 0.0; 7.2; 0.2; 0.13333333333333333 +1377826695; 1; 2599.9993; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377826995; 1; 2599.9993; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1377827295; 1; 2599.9993; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1377827595; 1; 2599.9993; 0.0; 0.0; 2097152.0; 128624.26666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1377827895; 1; 2599.9993; 0.0; 0.0; 2097152.0; 127226.13333333333; 0.0; 0.8; 0.0; 0.0 +1377828195; 1; 2599.9993; 0.0; 0.0; 2097152.0; 110448.53333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377828495; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 130021.6; 0.0; 2.2666666666666666; 0.06666666666666667; 0.4666666666666667 +1377828795; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 167771.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377829095; 1; 2599.9993; 0.0; 0.0; 2097152.0; 95069.06666666667; 0.0; 1.0; 0.0; 0.0 +1377829395; 1; 2599.9993; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.8; 0.0; 0.0 +1377829695; 1; 2599.9993; 0.0; 0.0; 2097152.0; 114642.93333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1377829995; 1; 2599.9993; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8; 0.0; 0.0 +1377830295; 1; 2599.9993; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1377830595; 1; 2599.9993; 0.0; 0.0; 2097152.0; 125827.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1377830895; 1; 2599.9993; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.8; 0.0; 0.0 +1377831195; 1; 2599.9993; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377831495; 1; 2599.9993; 0.0; 0.0; 2097152.0; 110448.53333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1377831795; 1; 2599.9993; 0.0; 0.0; 2097152.0; 78291.46666666666; 0.0; 1.0; 0.0; 0.0 +1377832095; 1; 2599.9993; 10.3999972; 0.4; 2097152.0; 125826.93333333333; 0.06666666666666667; 8.6; 0.2; 0.6 +1377832395; 1; 2599.9993; 0.0; 0.0; 2097152.0; 146798.4; 0.0; 0.6666666666666666; 0.0; 0.0 +1377832695; 1; 2599.9993; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377832995; 1; 2599.9993; 0.0; 0.0; 2097152.0; 110448.53333333334; 0.0; 0.8; 0.0; 0.0 +1377833295; 1; 2599.9993; 0.0; 0.0; 2097152.0; 130022.4; 0.0; 1.0666666666666667; 0.0; 0.0 +1377833595; 1; 2599.9993; 0.0; 0.0; 2097152.0; 159382.13333333333; 0.0; 0.8; 0.0; 0.0 +1377833895; 1; 2599.9993; 0.0; 0.0; 2097152.0; 106254.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1377834195; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 95069.06666666667; 0.0; 1.0; 0.0; 0.0 +1377834495; 1; 2599.9993; 0.0; 0.0; 2097152.0; 121633.6; 0.0; 1.2666666666666666; 0.0; 0.0 +1377834795; 1; 2599.9993; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8; 0.0; 0.0 +1377835095; 1; 2599.9993; 0.0; 0.0; 2097152.0; 134216.0; 0.0; 0.8; 0.0; 0.0 +1377835395; 1; 2599.9993; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 0.8; 0.0; 0.0 +1377835695; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 103457.86666666667; 0.0; 2.2; 0.0; 0.4666666666666667 +1377835996; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 142605.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1377836296; 1; 2599.9993; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1377836596; 1; 2599.9993; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1377836896; 1; 2599.9993; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 0.7333333333333333; 0.0; 0.0 +1377837196; 1; 2599.9993; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377837496; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 109050.4; 0.0; 11.733333333333333; 0.0; 0.0 +1377837796; 1; 2599.9993; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.8; 0.0; 0.0 +1377838096; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 127226.13333333333; 0.0; 6.933333333333334; 0.2; 0.13333333333333333 +1377838396; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 106254.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1377838696; 1; 2599.9993; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1377838996; 1; 2599.9993; 0.0; 0.0; 2097152.0; 67106.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1377839296; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 89476.53333333334; 0.0; 1.9333333333333333; 0.06666666666666667; 0.4666666666666667 +1377839596; 1; 2599.9993; 0.0; 0.0; 2097152.0; 163576.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377839896; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 99263.46666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1377840196; 1; 2599.9993; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 1.1333333333333333; 0.0; 0.0 +1377840496; 1; 2599.9993; 0.0; 0.0; 2097152.0; 121633.6; 0.0; 0.8; 0.0; 0.0 +1377840796; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 145401.86666666667; 0.0; 1.6666666666666667; 0.0; 0.0 +1377841096; 1; 2599.9993; 0.0; 0.0; 2097152.0; 146799.2; 0.0; 1.0; 0.0; 0.0 +1377841396; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 100661.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1377841696; 1; 2599.9993; 0.0; 0.0; 2097152.0; 89476.53333333334; 0.0; 0.8; 0.0; 0.0 +1377841996; 1; 2599.9993; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377842296; 1; 2599.9993; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.8; 0.0; 0.0 +1377842596; 1; 2599.9993; 0.0; 0.0; 2097152.0; 116041.06666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1377842896; 1; 2599.9993; 6.933331466666666; 0.26666666666666666; 2097152.0; 113244.8; 0.06666666666666667; 2.6666666666666665; 0.06666666666666667; 0.4666666666666667 +1377843196; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 159382.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377843496; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 113244.8; 0.06666666666666667; 6.933333333333334; 0.2; 0.13333333333333333 +1377843796; 1; 2599.9993; 0.0; 0.0; 2097152.0; 121633.6; 0.0; 0.7333333333333333; 0.0; 0.0 +1377844096; 1; 2599.9993; 0.0; 0.0; 2097152.0; 106253.6; 0.0; 0.8; 0.0; 0.0 +1377844396; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 141205.86666666667; 0.0; 1.9333333333333333; 0.06666666666666667; 0.13333333333333333 +1377844696; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 130022.4; 0.0; 1.4666666666666666; 0.0; 0.0 +1377844997; 1; 2599.9993; 0.0; 0.0; 2097152.0; 130022.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377845297; 1; 2599.9993; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.7333333333333333; 0.0; 0.0 +1377845597; 1; 2599.9993; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 1.0; 0.0; 0.0 +1377845897; 1; 2599.9993; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 0.8; 0.0; 0.0 +1377846197; 1; 2599.9993; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 0.8; 0.0; 0.0 +1377846497; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 106254.13333333333; 0.0; 2.2666666666666666; 0.06666666666666667; 0.4666666666666667 +1377846797; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 163576.0; 0.0; 0.7333333333333333; 0.0; 0.0 +1377847097; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 88078.4; 0.0; 1.0; 0.0; 0.0 +1377847397; 1; 2599.9993; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8; 0.0; 0.0 +1377847697; 1; 2599.9993; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.8; 0.0; 0.0 +1377847997; 1; 2599.9993; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.7333333333333333; 0.0; 0.0 +1377848297; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 138411.2; 0.0; 1.0; 0.0; 0.0 +1377848597; 1; 2599.9993; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 1.3333333333333333; 0.0; 0.0 +1377848897; 1; 2599.9993; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 1.2; 0.0; 0.0 +1377849197; 1; 2599.9993; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.8; 0.0; 0.0 +1377849497; 1; 2599.9993; 8.666664333333335; 0.33333333333333337; 2097152.0; 121633.6; 0.0; 7.333333333333333; 0.26666666666666666; 0.2 +1377849797; 1; 2599.9993; 0.0; 0.0; 2097152.0; 79689.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1377850097; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 100661.6; 0.06666666666666667; 2.533333333333333; 0.06666666666666667; 0.4666666666666667 +1377850397; 1; 2599.9993; 0.0; 0.0; 2097152.0; 130021.6; 0.0; 0.7333333333333333; 0.26666666666666666; 0.0 +1377850696; 1; 2599.9993; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.6; 0.06666666666666667; 0.0 +1377850996; 1; 2599.9993; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 0.8; 0.0; 0.0 +1377851296; 1; 2599.9993; 0.0; 0.0; 2097152.0; 134216.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1377851596; 1; 2599.9993; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.6; 0.0; 0.0 +1377851896; 1; 2599.9993; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.6666666666666666; 0.0; 0.0 +1377852196; 1; 2599.9993; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377852496; 1; 2599.9993; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 1.0; 0.0; 0.0 +1377852796; 1; 2599.9993; 0.0; 0.0; 2097152.0; 89476.53333333334; 0.0; 0.6666666666666666; 0.0; 0.0 +1377853096; 1; 2599.9993; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.6; 0.0; 0.0 +1377853396; 1; 2599.9993; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.8; 0.0; 0.0 +1377853696; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 120235.46666666666; 0.06666666666666667; 2.466666666666667; 0.06666666666666667; 0.4666666666666667 +1377853996; 1; 2599.9993; 8.666664333333335; 0.33333333333333337; 2097152.0; 159383.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377854297; 1; 2599.9993; 0.0; 0.0; 2097152.0; 100660.8; 0.0; 0.6666666666666666; 0.0; 0.0 +1377854597; 1; 2599.9993; 0.0; 0.0; 2097152.0; 127226.13333333333; 0.0; 0.8; 0.0; 0.0 +1377854897; 1; 2599.9993; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.8; 0.06666666666666667; 0.0 +1377855197; 1; 2599.9993; 0.0; 0.0; 2097152.0; 134216.8; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1377855497; 1; 2599.9993; 0.0; 0.0; 2097152.0; 138411.2; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1377855797; 1; 2599.9993; 0.0; 0.0; 2097152.0; 125828.0; 0.0; 0.8; 0.13333333333333333; 0.0 +1377856097; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 144003.73333333334; 0.2; 7.133333333333334; 0.2; 0.13333333333333333 +1377856397; 1; 2599.9993; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.5333333333333333; 0.0; 0.0 +1377856697; 1; 2599.9993; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.8666666666666667; 0.3333333333333333; 0.0 +1377856997; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 104856.0; 0.0; 1.2666666666666666; 0.0; 0.0 +1377857297; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 127225.06666666667; 0.0; 1.8666666666666667; 0.0; 0.4666666666666667 +1377857597; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 121632.8; 0.0; 0.6666666666666666; 0.0; 0.0 +1377857897; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 100661.6; 0.0; 0.6666666666666666; 0.06666666666666667; 0.0 +1377858197; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 99263.46666666666; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1377858497; 1; 2599.9993; 0.0; 0.0; 2097152.0; 125828.0; 0.0; 0.6666666666666666; 0.06666666666666667; 0.0 +1377858797; 1; 2599.9993; 0.0; 0.0; 2097152.0; 86680.26666666666; 0.0; 0.5333333333333333; 0.0; 0.0 +1377859097; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 92272.8; 0.0; 0.6666666666666666; 0.0; 0.0 +1377859397; 1; 2599.9993; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 1.1333333333333333; 0.0; 0.0 +1377859697; 1; 2599.9993; 0.0; 0.0; 2097152.0; 83884.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1377859997; 1; 2599.9993; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.6666666666666666; 0.06666666666666667; 0.0 +1377860297; 1; 2599.9993; 0.0; 0.0; 2097152.0; 78291.46666666666; 0.0; 0.6; 0.13333333333333333; 0.0 +1377860597; 1; 2599.9993; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377860897; 1; 2599.9993; 6.933331466666666; 0.26666666666666666; 2097152.0; 125827.46666666666; 0.0; 2.466666666666667; 0.06666666666666667; 0.4666666666666667 +1377861197; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 192935.73333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1377861497; 1; 2599.9993; 0.0; 0.0; 2097152.0; 121633.6; 0.0; 0.6; 0.0; 0.0 +1377861797; 1; 2599.9993; 0.0; 0.0; 2097152.0; 82485.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377862097; 1; 2599.9993; 6.933331466666666; 0.26666666666666666; 2097152.0; 130021.6; 0.0; 6.866666666666666; 0.4; 0.13333333333333333 +1377862397; 1; 2599.9993; 0.0; 0.0; 2097152.0; 130022.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377862698; 1; 2599.9993; 0.0; 0.0; 2097152.0; 89476.53333333334; 0.0; 0.6; 0.0; 0.0 +1377862998; 1; 2599.9993; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 0.6; 0.0; 0.0 +1377863298; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 89476.53333333334; 0.0; 1.0666666666666667; 0.13333333333333333; 0.0 +1377863598; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 79689.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1377863898; 1; 2599.9993; 0.0; 0.0; 2097152.0; 78291.46666666666; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1377864198; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 82485.86666666667; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377864498; 1; 2599.9993; 10.3999972; 0.4; 2097152.0; 128624.26666666666; 0.0; 2.1333333333333333; 0.06666666666666667; 0.4666666666666667 +1377864798; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 156586.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1377865098; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 128623.46666666666; 0.0; 1.0666666666666667; 0.13333333333333333; 0.0 +1377865398; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377865698; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.2; 0.0 +1377865998; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 132818.66666666666; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1377866298; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 103457.86666666667; 0.0; 1.0; 0.06666666666666667; 0.0 +1377866598; 1; 2599.9993; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 1.0666666666666667; 0.0; 0.0 +1377866898; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 75495.2; 0.0; 0.9333333333333333; 3.066666666666667; 0.0 +1377867198; 1; 2599.9993; 0.0; 0.0; 2097152.0; 83884.0; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1377867498; 1; 2599.9993; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377867798; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 99263.46666666666; 0.0; 1.0666666666666667; 0.2; 0.0 +1377868098; 1; 2599.9993; 10.3999972; 0.4; 2097152.0; 181750.93333333332; 0.06666666666666667; 8.533333333333333; 0.4; 0.6 +1377868398; 1; 2599.9993; 6.933331466666666; 0.26666666666666666; 2097152.0; 208315.2; 0.0; 1.0; 0.13333333333333333; 0.0 +1377868698; 1; 2599.9993; 60.66665033333334; 2.3333333333333335; 2097152.0; 227889.33333333334; 161.06666666666666; 21.933333333333334; 0.06666666666666667; 0.4 +1377868998; 1; 2599.9993; 13.866662933333332; 0.5333333333333333; 2097152.0; 673882.4; 0.0; 2.466666666666667; 0.06666666666666667; 0.0 +1377869298; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 285210.13333333336; 31.8; 4.466666666666667; 0.06666666666666667; 0.0 +1377869598; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 208314.4; 0.0; 2.2666666666666666; 0.0; 0.0 +1377869898; 1; 2599.9993; 8.666664333333335; 0.33333333333333337; 2097152.0; 149596.26666666666; 0.0; 1.6; 0.0; 0.0 +1377870198; 1; 2599.9993; 6.933331466666666; 0.26666666666666666; 2097152.0; 109050.4; 0.0; 1.0; 0.0; 0.0 +1377870498; 1; 2599.9993; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 1.0666666666666667; 0.26666666666666666; 0.0 +1377870798; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 139808.53333333333; 0.0; 1.0; 0.06666666666666667; 0.0 +1377871098; 1; 2599.9993; 0.0; 0.0; 2097152.0; 124429.86666666667; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1377871398; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 93670.93333333333; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377871698; 1; 2599.9993; 8.666664333333335; 0.33333333333333337; 2097152.0; 118837.33333333333; 0.0; 2.3333333333333335; 0.13333333333333333; 0.4666666666666667 +1377871998; 1; 2599.9993; 10.3999972; 0.4; 2097152.0; 174760.26666666666; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1377872299; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 138410.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377872599; 1; 2599.9993; 0.0; 0.0; 2097152.0; 95069.06666666667; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377872899; 1; 2599.9993; 0.0; 0.0; 2097152.0; 75495.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1377873199; 1; 2599.9993; 6.933331466666666; 0.26666666666666666; 2097152.0; 114642.93333333333; 0.0; 1.4; 0.06666666666666667; 0.06666666666666667 +1377873499; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 127226.13333333333; 0.0; 1.0; 0.06666666666666667; 0.0 +1377873799; 1; 2599.9993; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 1.0; 0.0; 0.0 +1377874099; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 100661.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1377874399; 1; 2599.9993; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377874699; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 106254.13333333333; 0.0; 1.0666666666666667; 6.666666666666667; 0.0 +1377874999; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 96467.2; 0.0; 6.0; 0.3333333333333333; 0.13333333333333333 +1377875299; 1; 2599.9993; 8.666664333333335; 0.33333333333333337; 2097152.0; 150993.86666666667; 0.0; 3.2; 0.0; 0.5333333333333333 +1377875599; 1; 2599.9993; 6.933331466666666; 0.26666666666666666; 2097152.0; 160780.26666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1377875899; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 96467.2; 0.0; 0.8; 0.13333333333333333; 0.0 +1377876199; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 88078.4; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1377876499; 1; 2599.9993; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 0.8666666666666667; 0.4; 0.0 +1377876799; 1; 2599.9993; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1377877099; 1; 2599.9993; 0.0; 0.0; 2097152.0; 85282.13333333333; 0.0; 0.8; 0.06666666666666667; 0.0 +1377877399; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 75495.2; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1377877699; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 79689.6; 0.0; 1.2666666666666666; 0.06666666666666667; 0.0 +1377877999; 1; 2599.9993; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 1.0; 0.06666666666666667; 0.0 +1377878299; 1; 2599.9993; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377878599; 1; 2599.9993; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377878899; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 95068.53333333334; 0.06666666666666667; 2.7333333333333334; 0.13333333333333333; 0.4666666666666667 +1377879199; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 124428.8; 0.0; 0.7333333333333333; 0.0; 0.0 +1377879499; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 125828.0; 0.0; 0.7333333333333333; 0.0; 0.0 +1377879799; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 120235.46666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1377880100; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 92272.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377880400; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 99263.46666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1377880700; 1; 2599.9993; 0.0; 0.0; 2097152.0; 71300.8; 0.0; 0.6666666666666666; 0.0; 0.0 +1377881000; 1; 2599.9993; 15.599995799999999; 0.6; 2097152.0; 128624.26666666666; 12.8; 23.933333333333334; 0.3333333333333333; 0.2 +1377881300; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 134216.8; 0.0; 1.1333333333333333; 0.0; 0.0 +1377881600; 1; 2599.9993; 0.0; 0.0; 2097152.0; 116041.06666666667; 0.0; 1.2; 0.0; 0.0 +1377881900; 1; 2599.9993; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377882200; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 83884.0; 0.0; 0.8666666666666667; 0.13333333333333333; 0.0 +1377882500; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 79689.6; 0.06666666666666667; 2.6; 0.2; 0.4666666666666667 +1377882800; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 132818.66666666666; 0.0; 0.8; 0.0; 0.0 +1377883100; 1; 2599.9993; 5.1999986; 0.2; 2097152.0; 109050.4; 0.0; 1.0; 0.13333333333333333; 0.0 +1377883399; 1; 2599.9993; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.8; 0.06666666666666667; 0.0 +1377883699; 1; 2599.9993; 1.7333328666666665; 0.06666666666666667; 2097152.0; 138411.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377883999; 1; 2599.9993; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1377884299; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 79689.6; 0.0; 1.2; 0.0; 0.0 +1377884599; 1; 2599.9993; 0.0; 0.0; 2097152.0; 83884.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1377884899; 1; 2599.9993; 3.466665733333333; 0.13333333333333333; 2097152.0; 86680.26666666666; 0.0; 1.0; 0.06666666666666667; 0.0 +1377885199; 1; 2599.9993; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 0.8; 0.06666666666666667; 0.0 +1377885499; 1; 2599.9993; 0.0; 0.0; 2097152.0; 132817.86666666667; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377885799; 1; 2599.999602; 11.99999816307692; 0.4615384615384615; 2097152.0; 96789.84615384616; 0.0; 0.8333333333333334; 0.0; 0.0 +1377886099; 1; 2599.999602; 5.199999203999999; 0.2; 2097152.0; 162178.4; 0.06666666666666667; 2.3333333333333335; 0.13333333333333333; 0.4666666666666667 +1377886400; 1; 2599.999602; 0.0; 0.0; 2097152.0; 163575.46666666667; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1377886700; 1; 2599.999602; 5.199999203999999; 0.2; 2097152.0; 162178.66666666666; 0.0; 7.0; 0.26666666666666666; 0.2 +1377887000; 1; 2599.999602; 0.0; 0.0; 2097152.0; 86680.26666666666; 0.0; 1.1333333333333333; 0.0; 0.0 +1377887300; 1; 2599.999602; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1377887600; 1; 2599.999343; 25.99999343; 1.0; 2097152.0; 119836.0; 0.0; 0.8461538461538461; 0.07692307692307693; 0.0 +1377887900; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 114642.93333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377888200; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 127226.13333333333; 0.0; 0.8; 0.0; 0.0 +1377888500; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 103457.86666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1377888800; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 125828.0; 0.0; 0.8; 0.5333333333333333; 0.0 +1377889100; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 106254.13333333333; 0.0; 0.8; 0.0; 0.0 +1377889400; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 68504.53333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377889700; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 117439.2; 0.0; 2.466666666666667; 0.0; 0.4666666666666667 +1377890000; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 157984.26666666666; 0.0; 0.8; 0.0; 0.0 +1377890300; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 103457.86666666667; 0.0; 0.8666666666666667; 0.13333333333333333; 0.0 +1377890600; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 82485.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377890900; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 86680.26666666666; 0.0; 0.8; 0.0; 0.0 +1377891200; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 95069.06666666667; 0.0; 1.0; 0.0; 0.0 +1377891500; 1; 2599.999343; 50.266653964666666; 1.9333333333333333; 2097152.0; 462770.4; 161.06666666666666; 14.533333333333333; 0.06666666666666667; 0.2 +1377891800; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 276821.6; 0.0; 1.4666666666666666; 0.0; 0.0 +1377892100; 1; 2599.999343; 22.533327639333333; 0.8666666666666667; 2097152.0; 212510.13333333333; 31.8; 4.0; 0.0; 0.0 +1377892400; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 150994.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377892700; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 125828.0; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1377893000; 1; 2599.999343; 22.533327639333333; 0.8666666666666667; 2097152.0; 124429.86666666667; 0.06666666666666667; 7.066666666666666; 0.2; 0.13333333333333333 +1377893300; 1; 2599.999343; 22.533327639333333; 0.8666666666666667; 2097152.0; 142604.26666666666; 0.0; 2.466666666666667; 0.0; 0.5333333333333333 +1377893600; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 148197.06666666668; 0.0; 0.8666666666666667; 0.0; 0.0 +1377893900; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 88078.4; 0.0; 1.0666666666666667; 0.0; 0.0 +1377894200; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 96467.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1377894500; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 86680.26666666666; 0.0; 0.9333333333333333; 0.4666666666666667; 0.0 +1377894800; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 124429.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377895100; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 125828.0; 0.0; 1.0; 1.0666666666666667; 0.0 +1377895400; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 114642.93333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1377895700; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 88078.4; 0.0; 1.0; 0.0; 0.0 +1377896000; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 100661.6; 0.0; 0.7333333333333333; 0.0; 0.0 +1377896300; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 93670.93333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1377896600; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 113244.8; 0.0; 1.0666666666666667; 0.0; 0.0 +1377896901; 1; 2599.999343; 22.533327639333333; 0.8666666666666667; 2097152.0; 141207.46666666667; 0.0; 2.466666666666667; 0.06666666666666667; 0.5333333333333333 +1377897201; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 144002.93333333332; 0.0; 0.8; 0.0; 0.0 +1377897501; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 93670.93333333333; 0.0; 0.8; 0.0; 0.0 +1377897801; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 95069.06666666667; 0.0; 0.8; 0.0; 0.0 +1377898101; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 81087.73333333334; 0.0; 1.0666666666666667; 0.0; 0.0 +1377898401; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 79689.6; 0.0; 0.8; 0.2; 0.0 +1377898701; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 139809.33333333334; 0.0; 6.866666666666666; 0.2; 0.13333333333333333 +1377899001; 1; 2599.999343; 6.933331581333333; 0.26666666666666666; 2097152.0; 131420.53333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1377899301; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 123031.73333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1377899601; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 116041.06666666667; 0.0; 0.8; 0.0; 0.0 +1377899901; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 88078.4; 0.0; 0.6666666666666666; 0.0; 0.0 +1377900201; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 106254.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1377900501; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 130022.13333333333; 0.0; 1.8666666666666667; 0.06666666666666667; 0.4666666666666667 +1377900801; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 159381.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1377901101; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 118836.8; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1377901401; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 106253.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1377901701; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 102059.73333333334; 0.0; 1.5333333333333334; 0.0; 0.0 +1377902001; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 114642.93333333333; 0.06666666666666667; 1.7333333333333334; 0.06666666666666667; 0.13333333333333333 +1377902301; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 106254.13333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1377902601; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 96467.2; 0.0; 0.7333333333333333; 0.0; 0.0 +1377902901; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 117439.2; 0.0; 0.8; 0.0; 0.0 +1377903201; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 116041.06666666667; 0.0; 0.9333333333333333; 0.13333333333333333; 0.0 +1377903502; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 104856.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1377903802; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 81087.73333333334; 0.0; 1.1333333333333333; 0.0; 0.0 +1377904102; 1; 2599.999343; 25.99999343; 1.0; 2097152.0; 156585.6; 0.26666666666666666; 2.0; 0.06666666666666667; 0.4666666666666667 +1377904402; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 176159.46666666667; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377904702; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 118837.33333333333; 0.0; 0.8; 0.0; 0.0 +1377905002; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 100661.6; 0.0; 7.0; 0.2; 0.13333333333333333 +1377905302; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 139808.53333333333; 0.0; 1.2; 0.06666666666666667; 0.0 +1377905602; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 102059.46666666666; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377905902; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 92272.26666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1377906202; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 90874.66666666667; 0.0; 0.8; 0.0; 0.0 +1377906502; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 85282.13333333333; 0.0; 1.3333333333333333; 0.0; 0.0 +1377906802; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 95069.06666666667; 0.0; 0.8; 0.06666666666666667; 0.0 +1377907102; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 113244.8; 0.0; 0.7333333333333333; 0.0; 0.0 +1377907402; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377907702; 1; 2599.999343; 24.266660534666663; 0.9333333333333332; 2097152.0; 125827.73333333334; 0.0; 2.533333333333333; 0.06666666666666667; 0.5333333333333333 +1377908002; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 169168.0; 0.0; 0.9333333333333333; 0.13333333333333333; 0.0 +1377908302; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 110448.53333333334; 0.0; 0.6666666666666666; 0.0; 0.0 +1377908602; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 90874.66666666667; 0.0; 0.6666666666666666; 0.0; 0.0 +1377908902; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 89476.53333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377909202; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 110448.53333333334; 0.0; 1.4666666666666666; 0.0; 0.0 +1377909502; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 110448.53333333334; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1377909802; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 111846.66666666667; 0.0; 0.7333333333333333; 0.26666666666666666; 0.0 +1377910102; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 97865.33333333333; 0.0; 0.9333333333333333; 0.26666666666666666; 0.0 +1377910402; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 121633.6; 0.0; 0.8; 0.0; 0.0 +1377910702; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 120235.46666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1377911002; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 123031.73333333334; 0.0; 0.6666666666666666; 0.0; 0.0 +1377911302; 1; 2599.999343; 22.533327639333333; 0.8666666666666667; 2097152.0; 139808.0; 0.06666666666666667; 2.2666666666666666; 0.0; 0.5333333333333333 +1377911602; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 152391.2; 0.06666666666666667; 0.7333333333333333; 0.0; 0.0 +1377911902; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 123031.46666666666; 0.0; 7.2; 0.2; 0.13333333333333333 +1377912202; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 134216.26666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1377912502; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 116041.06666666667; 0.0; 0.8; 0.0; 0.0 +1377912802; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377913102; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 85282.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377913402; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 83884.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1377913702; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 93670.93333333333; 0.0; 0.8; 0.06666666666666667; 0.0 +1377914003; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 96467.2; 0.0; 1.6; 0.0; 0.0 +1377914303; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 107652.26666666666; 0.0; 0.8; 0.0; 0.0 +1377914603; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 88078.4; 0.0; 1.0666666666666667; 0.0; 0.0 +1377914903; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 99263.46666666666; 0.0; 2.6; 0.06666666666666667; 0.5333333333333333 +1377915203; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 123031.73333333334; 0.0; 0.8; 0.0; 0.0 +1377915503; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 139809.33333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1377915803; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 134216.8; 0.0; 0.8; 0.0; 0.0 +1377916102; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 107652.26666666666; 0.0; 1.0; 0.0; 0.0 +1377916402; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 118837.33333333333; 0.0; 0.8; 0.0; 0.0 +1377916702; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 106254.13333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377917002; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 83884.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1377917302; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 104856.0; 0.0; 0.7333333333333333; 0.0; 0.0 +1377917602; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 111846.66666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1377917902; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 106254.13333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377918202; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 120234.4; 0.0; 6.8; 0.26666666666666666; 0.13333333333333333 +1377918502; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 155187.2; 0.0; 2.1333333333333333; 0.06666666666666667; 0.5333333333333333 +1377918802; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 128624.26666666666; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1377919102; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 5.133333333333334; 0.0 +1377919402; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 85282.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377919702; 1; 2599.999343; 6.933331581333333; 0.26666666666666666; 2097152.0; 93670.93333333333; 0.0; 0.8; 0.0; 0.0 +1377920002; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 130022.4; 0.0; 0.6; 0.0; 0.0 +1377920302; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 88078.4; 0.0; 1.1333333333333333; 0.0; 0.0 +1377920602; 1; 2599.999343; 67.59998291800001; 2.6; 2097152.0; 336940.0; 161.0; 21.8; 0.06666666666666667; 0.4 +1377920903; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 634736.0; 0.0; 2.7333333333333334; 0.0; 0.0 +1377921203; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 292201.3333333333; 31.8; 3.4; 0.06666666666666667; 0.0 +1377921503; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 205517.86666666667; 0.0; 2.3333333333333335; 0.0; 0.0 +1377921803; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 150993.6; 0.0; 1.5333333333333334; 0.0; 0.0 +1377922103; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 139808.53333333333; 0.0; 2.066666666666667; 0.06666666666666667; 0.4666666666666667 +1377922403; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 166372.26666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1377922703; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 123031.73333333334; 0.0; 0.6; 0.0; 0.0 +1377923003; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 92272.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377923303; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 125828.0; 0.0; 0.6; 0.0; 0.0 +1377923603; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 128624.26666666666; 0.0; 0.6666666666666666; 0.0; 0.0 +1377923903; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 88078.4; 0.0; 0.6; 0.0; 0.0 +1377924203; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 81087.73333333334; 0.0; 0.6666666666666666; 0.0; 0.0 +1377924503; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 61513.86666666667; 0.0; 1.0; 0.0; 0.0 +1377924803; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 71300.8; 0.0; 11.666666666666666; 0.0; 0.0 +1377925103; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 95069.06666666667; 0.0; 6.2; 0.2; 0.13333333333333333 +1377925403; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 116041.06666666667; 0.0; 1.7333333333333334; 0.0; 0.0 +1377925703; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 153789.6; 0.06666666666666667; 2.533333333333333; 0.13333333333333333; 0.5333333333333333 +1377926003; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 226490.13333333333; 0.0; 0.6666666666666666; 0.13333333333333333; 0.0 +1377926303; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 132818.66666666666; 0.0; 0.6; 0.06666666666666667; 0.0 +1377926603; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 92272.8; 0.0; 0.6; 0.0; 0.0 +1377926903; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 85282.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377927203; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 86680.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377927503; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 124429.86666666667; 0.0; 0.5333333333333333; 0.0; 0.0 +1377927803; 1; 2599.999343; 6.933331581333333; 0.26666666666666666; 2097152.0; 83884.0; 0.0; 0.6666666666666666; 0.0; 0.0 +1377928103; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 104856.0; 0.0; 0.7333333333333333; 0.0; 0.0 +1377928403; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 100661.6; 0.0; 0.6666666666666666; 0.0; 0.0 +1377928703; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 88078.4; 0.0; 0.9333333333333333; 0.13333333333333333; 0.0 +1377929003; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 81087.73333333334; 0.0; 0.6; 0.06666666666666667; 0.0 +1377929303; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 134215.73333333334; 0.06666666666666667; 2.2666666666666666; 0.2; 0.5333333333333333 +1377929604; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 121633.6; 0.0; 0.7333333333333333; 0.0; 0.0 +1377929904; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 92272.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377930204; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 132818.66666666666; 0.0; 0.5333333333333333; 0.0; 0.0 +1377930504; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 88078.4; 0.0; 0.8; 0.0; 0.0 +1377930804; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 104856.0; 0.0; 8.133333333333333; 0.26666666666666666; 0.2 +1377931104; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 118837.33333333333; 0.0; 1.4; 0.0; 0.0 +1377931404; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 100661.6; 0.0; 1.1333333333333333; 0.0; 0.0 +1377931704; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 104856.0; 0.0; 0.6; 0.0; 0.0 +1377932004; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 118837.33333333333; 0.0; 0.6666666666666666; 0.06666666666666667; 0.0 +1377932304; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 96467.2; 0.0; 0.6; 0.0; 0.0 +1377932604; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 88078.4; 0.0; 0.8; 0.0; 0.0 +1377932904; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 79689.6; 0.0; 2.2666666666666666; 0.06666666666666667; 0.4666666666666667 +1377933204; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 134216.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1377933504; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 121632.8; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377933804; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 83884.0; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1377934104; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 92272.8; 0.0; 1.0666666666666667; 0.0; 0.0 +1377934404; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377934704; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 118837.33333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377935004; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 103457.86666666667; 0.0; 1.3333333333333333; 0.0; 0.0 +1377935304; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 104856.0; 0.0; 1.2; 0.0; 0.0 +1377935604; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 109050.4; 0.0; 1.1333333333333333; 0.0; 0.0 +1377935904; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 110448.53333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1377936204; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 110448.53333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377936504; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 130021.6; 0.0; 2.4; 0.06666666666666667; 0.4666666666666667 +1377936804; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 152390.93333333332; 0.0; 6.8; 0.2; 0.13333333333333333 +1377937104; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 130022.4; 0.06666666666666667; 1.5333333333333334; 0.06666666666666667; 0.0 +1377937404; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 135614.93333333332; 0.0; 0.8666666666666667; 0.0; 0.0 +1377937704; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 121633.6; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377938004; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 103457.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377938304; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 117439.2; 0.0; 1.1333333333333333; 0.0; 0.0 +1377938604; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 96467.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1377938905; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377939205; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 1.2; 0.0 +1377939505; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 121632.8; 0.0; 1.0666666666666667; 0.0; 0.0 +1377939805; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 106254.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377940105; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 135613.6; 0.06666666666666667; 2.3333333333333335; 0.06666666666666667; 0.4666666666666667 +1377940405; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 176160.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1377940705; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 113244.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377941005; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 104856.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1377941305; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 83884.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377941605; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377941905; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 111846.66666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377942205; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 123031.73333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1377942505; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 142604.8; 12.733333333333333; 13.266666666666667; 0.3333333333333333; 0.2 +1377942805; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 102059.73333333334; 0.0; 1.1333333333333333; 0.0; 0.0 +1377943105; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 83884.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377943405; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 79689.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1377943705; 1; 2599.999343; 25.99999343; 1.0; 2097152.0; 103457.86666666667; 0.06666666666666667; 2.466666666666667; 0.06666666666666667; 0.5333333333333333 +1377944005; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 138410.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377944305; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 100661.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1377944605; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 106254.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377944905; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 113244.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377945205; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 113244.8; 0.0; 1.0666666666666667; 0.0; 0.0 +1377945505; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 75495.2; 0.0; 0.8; 0.0; 0.0 +1377945805; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377946105; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377946405; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 75495.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1377946706; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 72698.93333333333; 0.0; 1.0; 0.0; 0.0 +1377947006; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 92272.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377947306; 1; 2599.999343; 24.266660534666663; 0.9333333333333332; 2097152.0; 139808.53333333333; 0.06666666666666667; 2.2666666666666666; 0.0; 0.5333333333333333 +1377947606; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 188741.6; 0.0; 0.8; 0.0; 0.0 +1377947906; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 117439.2; 0.0; 1.0; 3.066666666666667; 0.0 +1377948206; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 64310.13333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377948506; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 97865.33333333333; 0.0; 0.8; 0.0; 0.0 +1377948805; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 103457.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1377949105; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 152391.73333333334; 0.0; 7.466666666666667; 0.26666666666666666; 0.13333333333333333 +1377949405; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 113244.8; 0.0; 1.2; 0.0; 0.0 +1377949705; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 118836.26666666666; 0.0; 1.0; 0.0; 0.0 +1377950005; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 130021.33333333333; 0.0; 0.6; 0.0; 0.0 +1377950305; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 85282.13333333333; 0.0; 0.6666666666666666; 0.0; 0.0 +1377950605; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 114642.93333333333; 0.0; 0.8; 0.0; 0.0 +1377950905; 1; 2599.999343; 24.266660534666663; 0.9333333333333332; 2097152.0; 125828.0; 0.06666666666666667; 2.4; 0.06666666666666667; 0.5333333333333333 +1377951205; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 159382.4; 0.0; 0.8; 0.0; 0.0 +1377951505; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 113244.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1377951805; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 110448.53333333334; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377952105; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 109049.6; 0.0; 1.2666666666666666; 0.0; 0.0 +1377952405; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 76893.33333333333; 0.0; 0.6666666666666666; 0.0; 0.0 +1377952705; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 96467.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1377953005; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 92272.8; 0.0; 0.6666666666666666; 0.0; 0.0 +1377953306; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 95069.06666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1377953606; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 103457.86666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1377953906; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 121633.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1377954206; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 124429.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377954506; 1; 2599.999343; 22.533327639333333; 0.8666666666666667; 2097152.0; 142604.0; 0.06666666666666667; 2.2666666666666666; 0.06666666666666667; 0.4666666666666667 +1377954806; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 167770.4; 0.0; 1.0; 0.0; 0.0 +1377955106; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 117439.2; 0.0; 0.8; 0.0; 0.0 +1377955406; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 89476.53333333334; 0.0; 0.8; 0.0; 0.0 +1377955706; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 106254.13333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377956006; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 118837.33333333333; 0.0; 7.066666666666666; 0.26666666666666666; 0.13333333333333333 +1377956306; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 178954.93333333332; 0.0; 1.1333333333333333; 0.0; 0.0 +1377956606; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 124429.86666666667; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377956906; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 100661.6; 0.0; 0.8; 0.0; 0.0 +1377957206; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 109050.4; 0.0; 0.8; 0.0; 0.0 +1377957506; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 96467.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1377957806; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 127226.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1377958106; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 150993.33333333334; 0.0; 2.3333333333333335; 0.06666666666666667; 0.4666666666666667 +1377958406; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 173363.46666666667; 0.0; 1.6666666666666667; 0.0; 0.0 +1377958706; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 107652.26666666666; 0.0; 0.8; 0.0; 0.0 +1377959006; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 100661.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1377959306; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 107652.26666666666; 0.0; 0.6666666666666666; 0.0; 0.0 +1377959606; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 106254.13333333333; 0.0; 1.0; 0.06666666666666667; 0.13333333333333333 +1377959906; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 102059.73333333334; 0.0; 1.0666666666666667; 0.0; 0.0 +1377960206; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 117439.2; 0.0; 0.8; 0.0; 0.0 +1377960506; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 117439.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1377960806; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 117439.2; 0.0; 0.8; 0.0; 0.0 +1377961106; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 110448.53333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1377961406; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 89476.53333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1377961706; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 118837.33333333333; 0.06666666666666667; 2.6; 0.06666666666666667; 0.5333333333333333 +1377962006; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 141206.66666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1377962307; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 121633.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1377962607; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 138411.2; 0.0; 0.8; 0.0; 0.0 +1377962907; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 125828.0; 0.06666666666666667; 6.8; 0.2; 0.13333333333333333 +1377963207; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 132818.66666666666; 0.0; 1.1333333333333333; 0.0; 0.0 +1377963507; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 121633.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1377963807; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377964107; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 95069.06666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1377964407; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 121632.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1377964707; 1; 2599.999343; 5.199998686; 0.2; 2097152.0; 85282.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377965007; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 50328.8; 0.0; 0.8; 0.0; 0.0 +1377965307; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 109049.86666666667; 0.0; 2.0; 0.06666666666666667; 0.4666666666666667 +1377965607; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 171964.53333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377965907; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 110448.53333333334; 0.0; 1.0666666666666667; 0.0; 0.0 +1377966207; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 118837.33333333333; 0.0; 0.8; 0.0; 0.0 +1377966507; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 109050.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377966807; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 90874.66666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1377967107; 1; 2599.999343; 6.933331581333333; 0.26666666666666666; 2097152.0; 68504.53333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377967407; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 109050.4; 0.0; 1.0; 0.0; 0.0 +1377967707; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 86680.26666666666; 0.0; 1.0; 0.0; 0.0 +1377968007; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 142604.8; 0.0; 0.7333333333333333; 0.0; 0.0 +1377968307; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 103457.86666666667; 0.0; 17.666666666666668; 0.2; 0.13333333333333333 +1377968607; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 88078.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1377968907; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 99263.46666666666; 0.0; 2.4; 0.06666666666666667; 0.4666666666666667 +1377969207; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 144002.93333333332; 0.0; 0.8; 0.0; 0.0 +1377969507; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 121633.6; 0.0; 0.8; 0.0; 0.0 +1377969807; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 97865.33333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377970107; 1; 2599.999343; 6.933331581333333; 0.26666666666666666; 2097152.0; 114642.93333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1377970407; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 117439.2; 0.0; 0.8; 0.0; 0.0 +1377970707; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 116041.06666666667; 0.0; 0.8666666666666667; 0.13333333333333333; 0.0 +1377971007; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 89476.53333333334; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1377971307; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 75495.2; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1377971607; 1; 2599.999343; 67.59998291800001; 2.6; 2097152.0; 232082.93333333332; 161.0; 21.866666666666667; 0.13333333333333333; 0.3333333333333333 +1377971907; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 727010.4; 0.0; 2.466666666666667; 0.0; 0.0 +1377972207; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 279618.4; 31.8; 3.6; 1.1333333333333333; 0.0 +1377972507; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 191537.6; 0.0; 3.6; 0.06666666666666667; 0.4666666666666667 +1377972807; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 164974.93333333332; 0.0; 1.6; 0.0; 0.0 +1377973107; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 124429.86666666667; 0.0; 0.8; 0.0; 0.0 +1377973407; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 110448.53333333334; 0.0; 0.6; 0.0; 0.0 +1377973707; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 95069.06666666667; 0.0; 0.6; 0.0; 0.0 +1377974008; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 110448.53333333334; 0.0; 6.733333333333333; 0.2; 0.13333333333333333 +1377974308; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 85282.13333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1377974608; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 89476.53333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377974908; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 93670.93333333333; 0.0; 0.6; 0.0; 0.0 +1377975208; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 110448.53333333334; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1377975508; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 95069.06666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1377975808; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 97865.33333333333; 0.0; 0.8; 0.0; 0.0 +1377976108; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 149595.46666666667; 0.06666666666666667; 2.3333333333333335; 0.06666666666666667; 0.5333333333333333 +1377976408; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 181751.73333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1377976708; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 111846.66666666667; 0.0; 0.8; 0.0; 0.0 +1377977008; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 116041.06666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1377977308; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 83884.0; 0.0; 0.6; 0.0; 0.0 +1377977608; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 79689.6; 0.0; 0.6666666666666666; 0.0; 0.0 +1377977908; 1; 2599.999343; 50.266653964666666; 1.9333333333333333; 2097152.0; 387272.0; 161.06666666666666; 14.6; 0.0; 0.2 +1377978208; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 315969.6; 0.0; 1.2666666666666666; 0.06666666666666667; 0.0 +1377978508; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 155188.0; 31.8; 3.8666666666666667; 0.0; 0.0 +1377978808; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 134216.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1377979108; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 93670.93333333333; 0.0; 0.8; 0.0; 0.0 +1377979408; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 93670.93333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377979708; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 130021.06666666667; 0.06666666666666667; 3.0; 0.06666666666666667; 0.4666666666666667 +1377980008; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 120235.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1377980308; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 113244.8; 0.0; 7.2; 0.2; 0.13333333333333333 +1377980608; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 111846.66666666667; 0.0; 0.6666666666666666; 0.0; 0.0 +1377980908; 1; 2599.999343; 6.933331581333333; 0.26666666666666666; 2097152.0; 128624.26666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1377981208; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 85282.13333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1377981508; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 114642.93333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1377981808; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 118837.33333333333; 0.0; 1.0; 0.0; 0.0 +1377982108; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 131420.53333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1377982408; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 100661.6; 0.0; 1.0; 0.0; 0.0 +1377982708; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 107652.26666666666; 0.0; 0.8; 0.0; 0.0 +1377983008; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 88078.4; 0.0; 0.8; 0.0; 0.0 +1377983308; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 124429.86666666667; 0.06666666666666667; 2.2666666666666666; 0.0; 0.5333333333333333 +1377983608; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 118837.33333333333; 0.0; 0.8; 0.0; 0.0 +1377983908; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 121633.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1377984208; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 110448.53333333334; 0.0; 0.8; 0.0; 0.0 +1377984508; 1; 2599.999343; 6.933331581333333; 0.26666666666666666; 2097152.0; 125828.0; 0.0; 0.7333333333333333; 0.0; 0.0 +1377984808; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 110448.53333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1377985108; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377985408; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 93670.93333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1377985708; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 149596.26666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1377986008; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 155188.26666666666; 0.0; 0.8; 0.0; 0.0 +1377986308; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 131420.0; 0.0; 0.7333333333333333; 0.0; 0.0 +1377986608; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 89476.53333333334; 0.0; 1.0666666666666667; 0.0; 0.0 +1377986908; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 110448.53333333334; 0.06666666666666667; 8.533333333333333; 0.26666666666666666; 0.7333333333333333 +1377987208; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 170566.66666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1377987508; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 148197.33333333334; 169.66666666666666; 179.2; 0.0; 0.0 +1377987808; 1; 2599.999343; 25.99999343; 1.0; 2097152.0; 243268.0; 0.0; 1.2; 0.0; 0.0 +1377988108; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 145401.33333333334; 0.0; 1.0; 0.0; 0.0 +1377988408; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 134216.8; 0.0; 1.5333333333333334; 0.06666666666666667; 0.13333333333333333 +1377988708; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 142604.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377989008; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 81087.73333333334; 0.0; 0.8; 0.0; 0.0 +1377989309; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1377989609; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 135614.13333333333; 0.0; 1.0; 0.0; 0.0 +1377989909; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 97865.33333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377990209; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 85282.13333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1377990509; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 138410.13333333333; 0.0; 2.4; 0.06666666666666667; 0.4666666666666667 +1377990809; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 148197.33333333334; 0.0; 1.1333333333333333; 0.0; 0.0 +1377991109; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 109050.4; 0.0; 0.8; 0.0; 0.0 +1377991409; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 89476.53333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1377991709; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 71300.8; 0.0; 0.7333333333333333; 0.0; 0.0 +1377992009; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 86680.26666666666; 0.0; 0.8; 0.0; 0.0 +1377992309; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 88078.4; 0.0; 1.0; 0.0; 0.0 +1377992609; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 88078.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1377992909; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 93670.93333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1377993209; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 86680.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1377993509; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 109050.4; 0.0; 1.0; 0.0; 0.0 +1377993809; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 131420.53333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1377994109; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 164974.66666666666; 0.0; 8.6; 0.26666666666666666; 0.6 +1377994409; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 185944.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1377994709; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 118837.06666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1377995009; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 86679.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1377995309; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 114642.93333333333; 0.0; 0.6; 0.0; 0.0 +1377995609; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 99263.46666666666; 0.0; 1.2666666666666666; 0.0; 0.0 +1377995909; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 68504.53333333334; 0.0; 0.6666666666666666; 0.0; 0.0 +1377996209; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 106254.13333333333; 0.0; 0.8; 0.0; 0.0 +1377996509; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 104856.0; 0.0; 0.7333333333333333; 0.0; 0.0 +1377996809; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 109050.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1377997109; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 135614.93333333332; 0.0; 0.6; 0.0; 0.0 +1377997409; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 117439.2; 0.0; 0.5333333333333333; 0.0; 0.0 +1377997709; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 104856.0; 0.06666666666666667; 2.7333333333333334; 0.06666666666666667; 0.5333333333333333 +1377998009; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 163576.0; 0.0; 1.0; 0.0; 0.0 +1377998310; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 95069.06666666667; 0.0; 0.6; 0.0; 0.0 +1377998610; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 93670.93333333333; 0.0; 0.6; 0.0; 0.0 +1377998910; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 120235.46666666666; 0.0; 0.6; 0.0; 0.0 +1377999210; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 125827.2; 0.0; 1.0; 0.0; 0.0 +1377999510; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 102059.73333333334; 0.0; 0.6666666666666666; 0.0; 0.0 +1377999810; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 113244.8; 0.06666666666666667; 6.866666666666666; 0.26666666666666666; 0.13333333333333333 +1378000110; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 128624.26666666666; 0.0; 0.8; 0.0; 0.0 +1378000410; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 116041.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378000710; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 106254.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378001010; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 111846.66666666667; 0.0; 0.6; 0.0; 0.0 +1378001310; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 124428.8; 0.0; 2.1333333333333333; 0.06666666666666667; 0.5333333333333333 +1378001610; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 177556.53333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378001910; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 110447.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1378002210; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 86680.26666666666; 0.0; 0.6666666666666666; 0.0; 0.0 +1378002510; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 163576.53333333333; 0.0; 0.6; 0.0; 0.0 +1378002810; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 141206.13333333333; 0.0; 1.5333333333333334; 0.13333333333333333; 0.0 +1378003110; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 144002.93333333332; 0.0; 0.8; 0.0; 0.0 +1378003410; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 114642.93333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378003710; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 116041.06666666667; 0.0; 0.6666666666666666; 0.0; 0.0 +1378004010; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 145400.53333333333; 0.0; 0.6666666666666666; 0.0; 0.0 +1378004310; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 144002.93333333332; 0.0; 0.6666666666666666; 0.06666666666666667; 0.0 +1378004610; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 120235.46666666666; 0.0; 0.8; 0.06666666666666667; 0.0 +1378004910; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 127225.06666666667; 0.0; 2.1333333333333333; 0.06666666666666667; 0.5333333333333333 +1378005210; 1; 2599.999343; 327.599917218; 12.6; 2097152.0; 534072.8; 34.2; 753.7333333333333; 149.46666666666667; 32.13333333333333 +1378005510; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 322958.93333333335; 0.0; 1.4; 0.0; 0.0 +1378005810; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 152391.73333333334; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378006110; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 127226.13333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1378006410; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 83884.0; 0.0; 0.8; 0.0; 0.0 +1378006711; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 117439.2; 12.733333333333333; 13.133333333333333; 0.3333333333333333; 0.2 +1378007011; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 153789.86666666667; 0.0; 1.3333333333333333; 0.0; 0.0 +1378007311; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 116041.06666666667; 0.0; 1.4666666666666666; 0.0; 0.0 +1378007611; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 123030.93333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1378007911; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 116041.06666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378008211; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1378008511; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 113244.8; 0.13333333333333333; 2.6666666666666665; 0.06666666666666667; 0.4666666666666667 +1378008811; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 199926.66666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1378009111; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 137012.26666666666; 0.0; 1.0; 0.0; 0.0 +1378009411; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 109050.4; 0.0; 1.1333333333333333; 0.0; 0.0 +1378009711; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 93670.93333333333; 0.0; 1.0; 0.0; 0.0 +1378010011; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 92272.8; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378010311; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 120235.46666666666; 0.0; 1.2; 0.0; 0.0 +1378010611; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 75495.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1378010911; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 121633.6; 0.0; 1.1333333333333333; 0.0; 0.0 +1378011211; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 75495.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1378011511; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 75495.2; 0.0; 1.2; 0.0; 0.0 +1378011811; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 121633.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1378012111; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 113243.46666666666; 0.13333333333333333; 13.533333333333333; 0.0; 0.4666666666666667 +1378012411; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 174761.06666666668; 0.0; 6.133333333333334; 0.2; 0.13333333333333333 +1378012711; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 156586.93333333332; 0.0; 1.9333333333333333; 0.0; 0.0 +1378013011; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 142604.8; 0.0; 1.2666666666666666; 0.0; 0.0 +1378013311; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 99263.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378013611; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 99263.46666666666; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378013912; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 99263.46666666666; 0.0; 1.0; 0.0; 0.0 +1378014211; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 104856.0; 0.0; 1.4; 0.0; 0.0 +1378014511; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 113244.8; 0.0; 1.1333333333333333; 0.0; 0.0 +1378014811; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 96467.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1378015111; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 109050.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1378015411; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 88078.4; 0.0; 1.1333333333333333; 0.0; 0.0 +1378015711; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 134216.0; 0.0; 2.2666666666666666; 0.06666666666666667; 0.4666666666666667 +1378016011; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 167771.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1378016311; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 106254.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378016611; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 121633.6; 0.0; 1.1333333333333333; 0.0; 0.0 +1378016911; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 100661.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1378017211; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 123031.73333333334; 0.0; 2.6; 0.06666666666666667; 0.13333333333333333 +1378017511; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 159381.6; 0.0; 1.8666666666666667; 0.0; 0.0 +1378017811; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 106254.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378018111; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 113244.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1378018411; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 109050.4; 0.0; 1.3333333333333333; 0.0; 0.0 +1378018711; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 117439.2; 0.0; 6.066666666666666; 0.2; 0.13333333333333333 +1378019011; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 92272.8; 0.0; 2.066666666666667; 0.0; 0.0 +1378019312; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 104856.0; 0.0; 2.2; 0.06666666666666667; 0.4666666666666667 +1378019612; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 163576.0; 0.0; 1.0; 0.0; 0.0 +1378019912; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 125828.0; 0.0; 1.1333333333333333; 0.0; 0.0 +1378020212; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 120235.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378020512; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1378020812; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 102059.73333333334; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378021112; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 113244.8; 0.0; 1.3333333333333333; 0.3333333333333333; 0.0 +1378021412; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 117439.2; 0.0; 1.3333333333333333; 0.06666666666666667; 0.0 +1378021712; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 104856.0; 0.0; 1.2666666666666666; 0.2; 0.0 +1378022012; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 141206.13333333333; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378022312; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 167770.4; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378022612; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 117439.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1378022912; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 95069.06666666667; 0.2; 2.4; 0.06666666666666667; 0.5333333333333333 +1378023212; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 142605.6; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1378023512; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 75495.2; 0.0; 1.0; 0.13333333333333333; 0.0 +1378023812; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 78291.46666666666; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378024112; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 121633.6; 0.0; 1.0; 0.0; 0.0 +1378024412; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 100661.6; 0.0; 0.8; 0.13333333333333333; 0.0 +1378024712; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 132818.66666666666; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378025012; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 134216.0; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378025312; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 142604.8; 0.0; 6.266666666666667; 0.3333333333333333; 0.2 +1378025612; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 192936.0; 0.0; 2.1333333333333333; 0.0; 0.0 +1378025912; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 99263.46666666666; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378026212; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 139809.33333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1378026512; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 149596.26666666666; 0.0; 2.3333333333333335; 0.0; 0.4666666666666667 +1378026812; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 171964.8; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1378027112; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 130022.4; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378027412; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 85282.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378027713; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 113244.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1378028013; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 102059.73333333334; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378028313; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 156586.13333333333; 0.0; 1.2; 0.0; 0.0 +1378028613; 1; 2599.999343; 69.33331581333334; 2.666666666666667; 2097152.0; 422225.06666666665; 161.0; 21.933333333333334; 0.06666666666666667; 0.4 +1378028913; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 603977.6; 0.0; 2.6; 0.06666666666666667; 0.0 +1378029213; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 226489.6; 31.8; 3.6; 0.0; 0.0 +1378029513; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 184548.0; 0.8; 2.6; 0.0; 0.0 +1378029813; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 130022.4; 0.0; 1.7333333333333334; 0.0; 0.0 +1378030113; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 137013.06666666668; 0.0; 2.3333333333333335; 1.4666666666666666; 0.5333333333333333 +1378030413; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 174761.33333333334; 0.0; 1.0; 0.0; 0.0 +1378030713; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378031013; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 159381.6; 0.0; 7.0; 0.26666666666666666; 0.13333333333333333 +1378031313; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 109050.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1378031613; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 100661.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1378031913; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 92272.8; 0.0; 0.8; 0.0; 0.0 +1378032213; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 75495.2; 0.0; 1.1333333333333333; 0.0; 0.0 +1378032513; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 96467.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1378032813; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 104856.0; 0.0; 1.1333333333333333; 0.0; 0.0 +1378033113; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 128624.26666666666; 0.0; 0.8; 0.0; 0.0 +1378033413; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 130022.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1378033713; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 121633.6; 0.06666666666666667; 2.7333333333333334; 0.06666666666666667; 0.4666666666666667 +1378034013; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 170566.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1378034313; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 93670.93333333333; 0.0; 1.0; 0.0; 0.0 +1378034613; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 79689.6; 0.06666666666666667; 1.4; 0.13333333333333333; 0.0 +1378034913; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 110448.53333333334; 0.06666666666666667; 1.4666666666666666; 0.06666666666666667; 0.0 +1378035214; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 90874.66666666667; 0.0; 1.0; 0.0; 0.0 +1378035514; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 103457.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378035814; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 131419.73333333334; 0.0; 1.2666666666666666; 0.06666666666666667; 0.0 +1378036114; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 111846.66666666667; 0.0; 1.2; 0.0; 0.0 +1378036414; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 111846.66666666667; 0.0; 1.2; 0.0; 0.0 +1378036714; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 97865.33333333333; 0.0; 7.266666666666667; 0.26666666666666666; 0.13333333333333333 +1378037014; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 113244.8; 0.0; 1.1333333333333333; 0.0; 0.0 +1378037314; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 104856.0; 0.06666666666666667; 2.4; 0.06666666666666667; 0.5333333333333333 +1378037614; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 144002.93333333332; 0.0; 0.8; 0.0; 0.0 +1378037914; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 125828.0; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1378038214; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 113244.8; 0.0; 1.0; 0.0; 0.0 +1378038514; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 113244.8; 0.0; 1.0666666666666667; 0.0; 0.0 +1378038814; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378039114; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 89476.53333333334; 0.0; 1.2; 0.0; 0.0 +1378039414; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 103457.86666666667; 0.0; 1.3333333333333333; 0.0; 0.0 +1378039714; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 75495.2; 0.0; 1.0; 0.06666666666666667; 0.0 +1378040014; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 60115.73333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378040314; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 99263.46666666666; 0.0; 0.8; 0.0; 0.0 +1378040614; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 114642.93333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1378040914; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 128623.73333333334; 0.0; 2.4; 0.13333333333333333; 0.5333333333333333 +1378041214; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 155188.0; 0.0; 1.0; 0.0; 0.0 +1378041514; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 125828.0; 0.0; 1.0; 0.0; 0.0 +1378041814; 1; 2599.999343; 5.199998686; 0.2; 2097152.0; 130022.4; 0.0; 1.2; 0.0; 0.0 +1378042114; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 124429.33333333333; 0.0; 7.4; 0.2; 0.13333333333333333 +1378042414; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 137012.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1378042715; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 113244.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1378043015; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 107652.26666666666; 0.0; 1.0; 0.0; 0.0 +1378043315; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 121633.6; 0.0; 1.4666666666666666; 0.0; 0.0 +1378043615; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 125828.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1378043915; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 88078.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1378044215; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 96467.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1378044515; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 124429.06666666667; 0.06666666666666667; 2.533333333333333; 0.13333333333333333; 0.4666666666666667 +1378044815; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 159382.13333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1378045115; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 125828.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1378045415; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 127226.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378045715; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 102059.73333333334; 0.0; 1.1333333333333333; 0.0; 0.0 +1378046015; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 113244.8; 0.06666666666666667; 1.3333333333333333; 0.06666666666666667; 0.13333333333333333 +1378046315; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 107652.26666666666; 0.0; 1.2; 0.0; 0.0 +1378046615; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 130022.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1378046915; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1378047215; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 149594.66666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378047515; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 95069.06666666667; 0.0; 1.8666666666666667; 0.0; 0.0 +1378047815; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 113244.8; 0.0; 0.8; 0.0; 0.0 +1378048115; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 176157.86666666667; 0.0; 8.333333333333334; 0.26666666666666666; 0.6 +1378048415; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 138410.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1378048715; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 103457.86666666667; 0.0; 1.0; 0.06666666666666667; 0.0 +1378049015; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 135614.93333333332; 0.0; 1.0666666666666667; 0.0; 0.0 +1378049315; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 127226.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378049615; 1; 2599.999343; 6.933331581333333; 0.26666666666666666; 2097152.0; 164974.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378049915; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 117439.2; 0.0; 0.8; 0.0; 0.0 +1378050215; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 88078.4; 0.0; 1.2666666666666666; 0.0; 0.0 +1378050515; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 90874.66666666667; 0.0; 1.4; 0.0; 0.0 +1378050815; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 75495.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1378051115; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 114642.93333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1378051415; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 164975.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1378051715; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 150992.8; 0.0; 2.3333333333333335; 0.06666666666666667; 0.5333333333333333 +1378052015; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 121633.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1378052315; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 138411.2; 0.0; 0.8; 0.0; 0.0 +1378052615; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 121633.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1378052915; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 145401.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378053215; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 113244.8; 0.0; 1.1333333333333333; 0.0; 0.0 +1378053515; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 113244.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1378053815; 1; 2599.999343; 6.933331581333333; 0.26666666666666666; 2097152.0; 103457.86666666667; 0.0; 0.8; 0.0; 0.0 +1378054115; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 130021.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1378054415; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 88078.4; 0.0; 1.0666666666666667; 0.0; 0.0 +1378054715; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 72698.93333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1378055015; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 125827.2; 0.0; 7.133333333333334; 0.2; 0.13333333333333333 +1378055315; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 167770.4; 0.0; 2.3333333333333335; 0.06666666666666667; 0.4666666666666667 +1378055615; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 144002.93333333332; 0.0; 12.0; 0.0; 0.0 +1378055916; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 121633.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1378056216; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1378056516; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 79689.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1378056816; 1; 2599.999343; 6.933331581333333; 0.26666666666666666; 2097152.0; 85282.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378057116; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 92272.8; 0.0; 1.0666666666666667; 0.0; 0.0 +1378057416; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 99263.46666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378057716; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 125828.0; 0.0; 1.1333333333333333; 0.0; 0.0 +1378058016; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 113244.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1378058316; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 88078.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1378058616; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 110448.53333333334; 0.0; 1.1333333333333333; 0.13333333333333333; 0.0 +1378058916; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 176159.2; 0.06666666666666667; 2.6; 0.06666666666666667; 0.5333333333333333 +1378059216; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 222296.0; 0.0; 0.8; 0.06666666666666667; 0.0 +1378059516; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 125828.0; 0.0; 0.8; 0.0; 0.0 +1378059816; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 99263.46666666666; 0.0; 1.0; 0.0; 0.0 +1378060116; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 121633.6; 0.0; 1.2; 0.0; 0.0 +1378060416; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 120235.46666666666; 0.0; 1.0; 0.0; 0.0 +1378060716; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 131420.53333333333; 0.06666666666666667; 7.133333333333334; 0.26666666666666666; 0.13333333333333333 +1378061016; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 160779.73333333334; 0.06666666666666667; 1.9333333333333333; 0.0; 0.0 +1378061316; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 134216.8; 0.0; 1.1333333333333333; 0.0; 0.0 +1378061616; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 107652.26666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1378061916; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 104856.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1378062216; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 113244.8; 1.4; 1.3333333333333333; 0.0; 0.0 +1378062516; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 127226.13333333333; 0.0; 2.4; 0.06666666666666667; 0.4666666666666667 +1378062816; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 109050.4; 0.0; 1.0666666666666667; 0.0; 0.0 +1378063116; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 130022.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1378063416; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 103457.86666666667; 0.0; 1.0; 0.0; 0.0 +1378063716; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 118837.33333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1378064017; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 163576.0; 0.0; 1.2666666666666666; 0.0; 0.0 +1378064317; 1; 2599.999343; 50.266653964666666; 1.9333333333333333; 2097152.0; 303386.4; 161.66666666666666; 14.466666666666667; 0.0; 0.2 +1378064617; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 373291.2; 0.0; 1.4; 0.0; 0.0 +1378064917; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 204120.53333333333; 31.8; 4.0; 0.0; 0.0 +1378065217; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 188741.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1378065517; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 134216.8; 0.0; 1.1333333333333333; 0.0; 0.0 +1378065817; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 117439.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1378066117; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 117439.2; 0.0; 2.4; 0.06666666666666667; 0.5333333333333333 +1378066417; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 150993.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1378066717; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 104856.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1378067017; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 117439.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1378067317; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 150993.6; 12.866666666666667; 13.066666666666666; 0.3333333333333333; 0.13333333333333333 +1378067617; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 184547.2; 0.0; 1.1333333333333333; 0.0; 0.0 +1378067917; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 104856.0; 0.0; 1.0; 0.0; 0.0 +1378068217; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 100661.6; 0.0; 1.2; 0.0; 0.0 +1378068517; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 107652.26666666666; 0.0; 0.8; 0.0; 0.0 +1378068817; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 125828.0; 0.0; 0.8; 0.0; 0.0 +1378069117; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 134216.8; 0.0; 0.8; 0.0; 0.0 +1378069417; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 102059.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1378069717; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 117439.2; 0.2; 3.2; 0.06666666666666667; 0.4666666666666667 +1378070017; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 148197.33333333334; 0.06666666666666667; 0.8; 0.0; 0.0 +1378070317; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1378070617; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 100661.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1378070917; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 130022.4; 0.0; 1.0; 0.0; 0.0 +1378071217; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 117439.2; 0.0; 0.8; 0.06666666666666667; 0.0 +1378071517; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 109049.6; 0.0; 1.2; 0.0; 0.0 +1378071817; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 95069.06666666667; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378072117; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 78291.46666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378072418; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 79689.6; 0.0; 1.0; 0.0; 0.0 +1378072718; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 118837.33333333333; 0.0; 6.8; 0.2; 0.13333333333333333 +1378073018; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 173362.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1378073318; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 144003.46666666667; 0.0; 2.4; 0.2; 0.4666666666666667 +1378073618; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 153789.06666666668; 0.0; 1.0666666666666667; 0.0; 0.0 +1378073918; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 127226.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378074218; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 104856.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1378074518; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 113244.8; 0.0; 0.8; 0.0; 0.0 +1378074818; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 124429.06666666667; 0.0; 1.4; 0.06666666666666667; 0.13333333333333333 +1378075118; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 123031.73333333334; 0.0; 1.0666666666666667; 0.0; 0.0 +1378075418; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 132818.66666666666; 0.0; 0.8; 0.0; 0.0 +1378075718; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 155188.0; 0.0; 0.8; 0.0; 0.0 +1378076018; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 109050.4; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378076318; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 116041.06666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378076618; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 86680.26666666666; 0.2; 1.4; 0.0; 0.0 +1378076918; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 139807.73333333334; 0.0; 2.2666666666666666; 0.06666666666666667; 0.5333333333333333 +1378077218; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 223694.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378077518; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 138410.93333333332; 0.0; 0.8666666666666667; 0.0; 0.0 +1378077818; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 92272.8; 0.0; 1.0666666666666667; 0.0; 0.0 +1378078118; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 104856.0; 0.0; 0.7333333333333333; 0.0; 0.0 +1378078418; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 141207.46666666667; 0.0; 7.4; 0.26666666666666666; 0.13333333333333333 +1378078718; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 132818.66666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378079018; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 148198.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378079318; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 90874.66666666667; 0.0; 1.4666666666666666; 0.0; 0.0 +1378079618; 1; 2599.999343; 6.933331581333333; 0.26666666666666666; 2097152.0; 106254.13333333333; 0.0; 1.0; 0.0; 0.0 +1378079918; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 117439.2; 0.0; 1.0; 0.0; 0.0 +1378080218; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 106254.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378080518; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 155188.0; 0.06666666666666667; 2.8666666666666667; 0.06666666666666667; 0.5333333333333333 +1378080818; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 149594.66666666666; 0.0; 1.1333333333333333; 0.0; 0.0 +1378081118; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 107652.26666666666; 0.0; 1.0; 0.0; 0.0 +1378081418; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 123031.73333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1378081718; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 103457.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378082018; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 120235.46666666666; 0.6; 1.7333333333333334; 0.0; 0.0 +1378082318; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 132818.66666666666; 0.0; 1.0; 0.0; 0.0 +1378082618; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 106254.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378082918; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 89476.53333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1378083218; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 107652.26666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1378083518; 1; 2599.999343; 51.99998686; 2.0; 2097152.0; 160779.73333333334; 161.0; 22.066666666666666; 0.2; 0.3333333333333333 +1378083818; 1; 2599.999343; 36.399990802; 1.4; 2097152.0; 598384.8; 0.0; 2.7333333333333334; 0.0; 0.0 +1378084118; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 282414.6666666667; 32.6; 5.066666666666666; 0.06666666666666667; 0.5333333333333333 +1378084418; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 209713.33333333334; 0.0; 2.6; 0.0; 0.0 +1378084718; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 153790.66666666666; 0.0; 1.8666666666666667; 0.0; 0.0 +1378085018; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 139808.53333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378085318; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 130022.4; 0.0; 7.066666666666666; 0.2; 0.13333333333333333 +1378085618; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 111846.66666666667; 0.0; 1.0; 0.0; 0.0 +1378085918; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 99263.46666666666; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1378086218; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 103457.86666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1378086518; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 114642.13333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1378086818; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 121633.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1378087118; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 141206.66666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378087418; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 120235.46666666666; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1378087718; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 167770.4; 0.0; 2.6666666666666665; 0.06666666666666667; 0.4666666666666667 +1378088018; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 192936.53333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1378088319; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 157985.06666666668; 0.0; 0.9333333333333333; 0.0; 0.0 +1378088619; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 150994.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1378088919; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 93670.93333333333; 0.0; 1.2; 0.0; 0.0 +1378089219; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 114642.93333333333; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378089519; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 86680.26666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378089819; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 81087.73333333334; 0.0; 1.0; 0.0; 0.0 +1378090119; 1; 2599.999343; 6.933331581333333; 0.26666666666666666; 2097152.0; 96467.2; 0.0; 1.0; 0.0; 0.0 +1378090419; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 142605.6; 0.0; 1.3333333333333333; 0.0; 0.0 +1378090719; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 90874.66666666667; 0.0; 6.8; 0.26666666666666666; 0.13333333333333333 +1378091019; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 123031.73333333334; 0.0; 1.1333333333333333; 0.0; 0.0 +1378091319; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 128624.26666666666; 0.06666666666666667; 2.6; 0.06666666666666667; 0.5333333333333333 +1378091619; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 170567.46666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1378091919; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 120235.46666666666; 0.0; 1.7333333333333334; 0.0; 0.0 +1378092219; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 71300.8; 0.0; 1.0; 0.13333333333333333; 0.0 +1378092519; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 106254.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378092819; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 90874.66666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378093119; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 90874.66666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1378093419; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 64310.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378093719; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 114642.93333333333; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1378094019; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 109050.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1378094319; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 85282.13333333333; 0.0; 0.8; 0.0; 0.0 +1378094619; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 120235.46666666666; 0.0; 1.2; 0.06666666666666667; 0.0 +1378094919; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 142605.33333333334; 0.06666666666666667; 2.4; 0.06666666666666667; 0.5333333333333333 +1378095219; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 153788.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1378095519; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 106254.13333333333; 0.0; 0.8; 0.06666666666666667; 0.0 +1378095819; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 104856.0; 0.0; 1.0; 0.0; 0.0 +1378096119; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 109050.4; 0.0; 0.9333333333333333; 0.2; 0.0 +1378096419; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 127226.13333333333; 0.0; 0.8; 0.0; 0.0 +1378096719; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 96467.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1378097019; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 99263.46666666666; 0.0; 1.2; 0.0; 0.0 +1378097319; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 65708.26666666666; 0.0; 1.3333333333333333; 0.06666666666666667; 0.0 +1378097619; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 89476.53333333334; 0.0; 1.0666666666666667; 0.0; 0.0 +1378097919; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 97865.33333333333; 0.0; 6.933333333333334; 0.26666666666666666; 0.13333333333333333 +1378098219; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 113244.8; 0.0; 0.8; 0.0; 0.0 +1378098519; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 141207.46666666667; 0.06666666666666667; 2.466666666666667; 0.06666666666666667; 0.4666666666666667 +1378098819; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 130021.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1378099119; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 83884.0; 0.0; 11.866666666666667; 0.0; 0.0 +1378099419; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1378099719; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 111846.66666666667; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378100019; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 128624.26666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1378100319; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 144003.73333333334; 0.0; 0.8; 0.0; 0.0 +1378100620; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 107652.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378100920; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 95069.06666666667; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378101220; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 104856.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1378101520; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 75495.2; 0.0; 1.2; 0.0; 0.0 +1378101820; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 110448.53333333334; 0.0; 1.0; 0.0; 0.0 +1378102120; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 99263.46666666666; 0.06666666666666667; 2.4; 0.06666666666666667; 0.4666666666666667 +1378102420; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 127225.33333333333; 0.0; 0.8; 0.0; 0.0 +1378102720; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 124429.86666666667; 0.0; 1.0; 0.0; 0.0 +1378103020; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.13333333333333333; 0.0 +1378103320; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 146800.0; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378103620; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 103457.86666666667; 0.06666666666666667; 2.6; 0.2; 0.13333333333333333 +1378103920; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 109050.4; 0.0; 1.7333333333333334; 0.0; 0.0 +1378104220; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 144002.93333333332; 0.0; 1.2666666666666666; 0.0; 0.0 +1378104520; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 132817.86666666667; 0.26666666666666666; 7.466666666666667; 0.26666666666666666; 0.13333333333333333 +1378104820; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 128624.26666666666; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1378105120; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 124429.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378105420; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 86680.26666666666; 0.0; 1.0; 0.0; 0.0 +1378105720; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 164974.4; 0.0; 2.3333333333333335; 0.13333333333333333; 0.5333333333333333 +1378106020; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 188742.4; 0.0; 1.0; 0.06666666666666667; 0.0 +1378106320; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 86680.26666666666; 0.0; 0.9333333333333333; 0.2; 0.0 +1378106620; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 85282.13333333333; 0.0; 0.9333333333333333; 0.2; 0.0 +1378106920; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 121633.6; 0.0; 1.3333333333333333; 0.0; 0.0 +1378107220; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 120235.46666666666; 0.0; 0.7333333333333333; 0.26666666666666666; 0.0 +1378107520; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 121633.6; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378107820; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 93670.93333333333; 0.0; 1.2666666666666666; 0.26666666666666666; 0.0 +1378108120; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 121633.6; 0.0; 1.4; 0.06666666666666667; 0.0 +1378108420; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 104856.0; 0.0; 1.4; 0.06666666666666667; 0.0 +1378108720; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 104856.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1378109020; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 118836.53333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1378109320; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 135613.86666666667; 0.0; 2.6; 0.06666666666666667; 0.5333333333333333 +1378109620; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 137012.53333333333; 0.0; 1.0; 0.0; 0.0 +1378109921; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 117439.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1378110221; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 128624.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378110521; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 128624.26666666666; 0.0; 7.133333333333334; 0.26666666666666666; 0.13333333333333333 +1378110820; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 95069.06666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1378111120; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 90874.66666666667; 0.0; 1.2; 0.0; 0.0 +1378111420; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 97865.33333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378111720; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 114642.93333333333; 0.0; 1.0; 0.0; 0.0 +1378112020; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 113244.8; 0.0; 1.0; 0.0; 0.0 +1378112320; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 102059.73333333334; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378112621; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 111846.66666666667; 0.0; 1.2666666666666666; 0.0; 0.0 +1378112921; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 139808.0; 0.0; 2.1333333333333333; 0.0; 0.4666666666666667 +1378113221; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 144002.93333333332; 0.0; 1.0; 0.0; 0.0 +1378113521; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 117439.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1378113821; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 107652.26666666666; 0.0; 1.0; 0.0; 0.0 +1378114121; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 130022.4; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378114421; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 132818.66666666666; 0.0; 1.0; 0.0; 0.0 +1378114721; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 106254.13333333333; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378115021; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 103457.86666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1378115321; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 118837.33333333333; 0.0; 1.4; 0.0; 0.0 +1378115621; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 121633.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1378115921; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 135614.13333333333; 0.0; 0.8; 0.0; 0.0 +1378116221; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 138410.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1378116521; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 128624.0; 0.0; 2.7333333333333334; 0.06666666666666667; 0.4666666666666667 +1378116821; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 170566.13333333333; 0.0; 7.0; 0.2; 0.13333333333333333 +1378117121; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 114642.93333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378117421; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 109050.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1378117721; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 132818.66666666666; 0.0; 1.0; 0.0; 0.0 +1378118021; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 111846.66666666667; 0.0; 1.2; 0.0; 0.0 +1378118321; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 137013.06666666668; 0.0; 0.8; 0.0; 0.0 +1378118621; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 134216.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1378118921; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 113244.8; 0.0; 1.0; 0.0; 0.0 +1378119221; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 104856.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1378119521; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 134215.2; 0.0; 1.2666666666666666; 0.0; 0.0 +1378119821; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 100661.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1378120121; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 141206.66666666666; 0.0; 2.4; 0.2; 0.5333333333333333 +1378120421; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 134216.8; 0.0; 1.0; 0.0; 0.0 +1378120721; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 124429.86666666667; 0.0; 1.0; 0.0; 0.0 +1378121021; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 93670.93333333333; 0.0; 0.8; 0.06666666666666667; 0.0 +1378121321; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 114642.93333333333; 0.0; 0.8; 0.0; 0.0 +1378121622; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 128624.26666666666; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1378121922; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 111846.66666666667; 0.0; 0.8; 0.0; 0.0 +1378122222; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 106254.13333333333; 0.0; 1.0; 0.0; 0.0 +1378122522; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 104856.0; 0.0; 1.3333333333333333; 0.0; 0.0 +1378122822; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 130022.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1378123122; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 148197.33333333334; 0.0; 0.8; 0.0; 0.0 +1378123422; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 167770.13333333333; 0.0; 7.2; 0.2; 0.13333333333333333 +1378123722; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 152392.0; 0.0; 2.2666666666666666; 0.06666666666666667; 0.5333333333333333 +1378124022; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 173362.93333333332; 0.0; 0.8666666666666667; 0.0; 0.0 +1378124322; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 139809.33333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1378124622; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 97865.33333333333; 0.0; 0.8; 0.06666666666666667; 0.0 +1378124922; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 118837.33333333333; 0.0; 1.2; 0.0; 0.0 +1378125222; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 86680.26666666666; 0.0; 0.6666666666666666; 0.06666666666666667; 0.0 +1378125522; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 106254.13333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1378125822; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 92272.8; 0.0; 0.7333333333333333; 0.0; 0.0 +1378126122; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 93670.93333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378126422; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 104856.0; 0.0; 1.0; 0.0; 0.0 +1378126722; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 109050.4; 0.0; 0.8; 0.0; 0.0 +1378127022; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 120235.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378127322; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 170567.2; 0.0; 2.2666666666666666; 0.0; 0.4666666666666667 +1378127622; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 150993.06666666668; 0.0; 1.1333333333333333; 0.0; 0.0 +1378127922; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 106254.13333333333; 0.0; 0.8; 0.0; 0.0 +1378128222; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 90874.66666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1378128522; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 103457.86666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1378128822; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 109050.4; 0.0; 0.6666666666666666; 0.0; 0.0 +1378129122; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 139809.33333333334; 0.0; 1.1333333333333333; 0.0; 0.0 +1378129422; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 138410.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1378129722; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 97865.33333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378130022; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 110448.53333333334; 0.0; 0.8; 0.0; 0.0 +1378130322; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 104856.0; 12.733333333333333; 13.066666666666666; 0.3333333333333333; 0.2 +1378130622; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 127225.33333333333; 0.06666666666666667; 1.5333333333333334; 0.0; 0.0 +1378130922; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 139808.53333333333; 0.13333333333333333; 2.7333333333333334; 0.06666666666666667; 0.4666666666666667 +1378131222; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 167770.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1378131522; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 106254.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378131822; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 114642.93333333333; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1378132122; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 118837.33333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378132422; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 92272.8; 0.06666666666666667; 1.3333333333333333; 0.06666666666666667; 0.06666666666666667 +1378132722; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 116041.06666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378133023; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 137013.06666666668; 0.0; 1.0; 0.0; 0.0 +1378133323; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 118837.33333333333; 0.0; 1.3333333333333333; 0.0; 0.0 +1378133623; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 95069.06666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378133923; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 127226.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378134223; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 144003.73333333334; 0.0; 1.0; 0.0; 0.0 +1378134523; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 169168.8; 0.0; 2.6; 0.06666666666666667; 0.5333333333333333 +1378134823; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 180353.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1378135123; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1378135423; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 97865.33333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378135723; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 123031.73333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1378136023; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 99263.46666666666; 0.0; 1.1333333333333333; 0.0; 0.0 +1378136323; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 113244.8; 0.0; 1.8; 0.0; 0.0 +1378136623; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 138411.2; 0.0; 7.133333333333334; 0.26666666666666666; 0.13333333333333333 +1378136923; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 113244.8; 0.0; 1.4; 0.0; 0.0 +1378137223; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 125828.0; 0.0; 1.0; 0.0; 0.0 +1378137523; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 113244.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1378137823; 1; 2599.999343; 51.99998686; 2.0; 2097152.0; 152391.46666666667; 161.0; 21.866666666666667; 0.13333333333333333; 0.4 +1378138123; 1; 2599.999343; 43.333322383333325; 1.6666666666666665; 2097152.0; 713029.6; 0.8; 4.333333333333333; 0.06666666666666667; 0.5333333333333333 +1378138423; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 360708.0; 31.8; 3.533333333333333; 0.13333333333333333; 0.0 +1378138723; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 222296.0; 0.0; 2.7333333333333334; 0.06666666666666667; 0.0 +1378139023; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 155188.0; 0.0; 1.8; 0.0; 0.0 +1378139323; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 134216.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1378139623; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 114642.93333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378139923; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 121633.6; 0.0; 1.0; 0.0; 0.0 +1378140223; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 102059.73333333334; 0.0; 1.2666666666666666; 0.0; 0.0 +1378140523; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 121633.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1378140824; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 83884.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1378141124; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 113244.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1378141424; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 142605.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1378141724; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 125828.0; 0.6; 2.4; 0.06666666666666667; 0.5333333333333333 +1378142024; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 155187.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1378142324; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 109050.4; 0.0; 1.0; 0.06666666666666667; 0.0 +1378142624; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 130022.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1378142924; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 138410.4; 0.0; 12.0; 0.0; 0.0 +1378143224; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 134216.53333333333; 0.0; 7.133333333333334; 0.7333333333333333; 0.13333333333333333 +1378143524; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 213906.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1378143824; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 117439.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1378144124; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 67106.4; 0.0; 1.0; 0.0; 0.0 +1378144424; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 67106.4; 0.0; 1.0666666666666667; 0.0; 0.0 +1378144724; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 79689.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1378145024; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 93670.93333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378145324; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 155187.2; 0.0; 2.4; 0.06666666666666667; 0.5333333333333333 +1378145624; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 138410.4; 0.0; 1.1333333333333333; 0.0; 0.0 +1378145924; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 117439.2; 0.0; 1.0; 0.0; 0.0 +1378146224; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 127226.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378146524; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 117439.2; 0.0; 0.7333333333333333; 0.0; 0.0 +1378146824; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 104856.0; 0.0; 0.8; 0.0; 0.0 +1378147124; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 88078.4; 0.0; 1.1333333333333333; 0.0; 0.0 +1378147424; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 79689.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1378147724; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 83884.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1378148024; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 113244.8; 0.0; 1.0; 0.0; 0.0 +1378148324; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 86680.26666666666; 0.0; 1.0; 0.0; 0.0 +1378148624; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 134216.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1378148924; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 163576.0; 0.0; 2.2666666666666666; 0.06666666666666667; 0.5333333333333333 +1378149224; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 152390.93333333332; 0.0; 0.8666666666666667; 0.0; 0.0 +1378149524; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 100661.6; 0.0; 6.933333333333334; 0.3333333333333333; 0.13333333333333333 +1378149824; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 83884.0; 0.0; 1.2666666666666666; 0.0; 0.0 +1378150124; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 123031.73333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1378150424; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 102059.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1378150724; 1; 2599.999343; 53.733319755333326; 2.0666666666666664; 2097152.0; 247462.4; 161.73333333333332; 14.333333333333334; 0.0; 0.2 +1378151024; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 364902.4; 0.0; 1.4666666666666666; 0.0; 0.0 +1378151324; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 167771.2; 31.8; 4.0; 0.0; 0.0 +1378151624; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 176159.2; 0.0; 0.8; 0.0; 0.0 +1378151924; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1378152224; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 100661.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1378152525; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 99263.46666666666; 0.0; 2.8666666666666667; 0.06666666666666667; 0.5333333333333333 +1378152825; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 153789.06666666668; 0.0; 1.0666666666666667; 0.0; 0.0 +1378153125; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 137012.26666666666; 0.0; 1.1333333333333333; 0.0; 0.0 +1378153425; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 118837.33333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378153725; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 109050.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1378154025; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 96467.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1378154325; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 134216.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1378154625; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 118837.33333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1378154925; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 125828.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1378155225; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 83884.0; 0.0; 7.266666666666667; 0.2; 0.13333333333333333 +1378155525; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 106254.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378155825; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 83884.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1378156125; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 153789.86666666667; 0.0; 2.2666666666666666; 0.06666666666666667; 0.4666666666666667 +1378156425; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 130021.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1378156725; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 134216.8; 0.0; 1.1333333333333333; 0.0; 0.0 +1378157025; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 113244.8; 0.0; 1.0666666666666667; 0.0; 0.0 +1378157325; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 96467.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1378157625; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 97865.33333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378157925; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 90874.66666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378158225; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 123031.73333333334; 0.0; 1.2; 0.0; 0.0 +1378158525; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 110448.53333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1378158825; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 116041.06666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378159125; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 99263.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378159425; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 132818.13333333333; 0.0; 2.066666666666667; 0.0; 0.0 +1378159725; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 166371.46666666667; 0.0; 2.466666666666667; 0.06666666666666667; 0.4666666666666667 +1378160025; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 145401.06666666668; 0.0; 1.0666666666666667; 0.0; 0.0 +1378160325; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 116041.06666666667; 0.0; 1.0; 0.0; 0.0 +1378160625; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 116041.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378160925; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 93670.93333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1378161225; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 125828.0; 0.06666666666666667; 1.8; 0.06666666666666667; 0.13333333333333333 +1378161525; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 125828.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1378161825; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 130022.4; 0.0; 1.0666666666666667; 0.0; 0.0 +1378162125; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 116041.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378162425; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 142604.8; 0.0; 7.2; 0.26666666666666666; 0.13333333333333333 +1378162725; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 128623.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378163025; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 171964.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378163325; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 170566.66666666666; 0.06666666666666667; 2.533333333333333; 0.06666666666666667; 0.5333333333333333 +1378163625; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 170566.4; 0.0; 1.2666666666666666; 0.0; 0.0 +1378163926; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 141206.66666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378164226; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 61513.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378164526; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 107652.26666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378164826; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 117439.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1378165126; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 107651.46666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1378165426; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 99263.46666666666; 0.0; 1.0; 0.0; 0.0 +1378165726; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 100661.6; 0.0; 1.1333333333333333; 0.0; 0.0 +1378166026; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 125828.0; 0.0; 1.0; 0.0; 0.0 +1378166326; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 85282.13333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1378166626; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 89476.53333333334; 0.0; 1.0; 0.0; 0.0 +1378166926; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 102059.73333333334; 0.0; 2.4; 0.06666666666666667; 0.5333333333333333 +1378167226; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 128624.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378167526; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 135614.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378167826; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 124429.33333333333; 0.0; 7.266666666666667; 0.2; 0.13333333333333333 +1378168126; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 135614.66666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378168426; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 120235.46666666666; 0.5333333333333333; 1.4666666666666666; 0.0; 0.0 +1378168726; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 117439.2; 0.0; 1.0; 0.0; 0.0 +1378169026; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 128624.26666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378169326; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 124429.86666666667; 0.0; 1.0; 0.0; 0.0 +1378169627; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 102059.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1378169927; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 121633.6; 0.0; 0.8; 0.0; 0.0 +1378170227; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 103457.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378170527; 1; 2599.999343; 24.266660534666663; 0.9333333333333332; 2097152.0; 131420.0; 0.0; 2.6; 0.06666666666666667; 0.5333333333333333 +1378170827; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 146798.93333333332; 0.0; 1.0; 0.0; 0.0 +1378171127; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 135614.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378171427; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 162178.66666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378171727; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 132818.66666666666; 0.0; 1.6; 0.0; 0.0 +1378172027; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 107652.26666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1378172327; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 95069.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378172627; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 106254.13333333333; 0.0; 1.0; 0.0; 0.0 +1378172927; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 114642.93333333333; 0.0; 0.8; 0.0; 0.0 +1378173227; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 102059.73333333334; 0.0; 1.0; 0.0; 0.0 +1378173527; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 118837.33333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1378173827; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 159381.86666666667; 0.0; 6.933333333333334; 0.2; 0.13333333333333333 +1378174127; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 137011.46666666667; 0.06666666666666667; 2.533333333333333; 0.06666666666666667; 0.4666666666666667 +1378174427; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 160780.26666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378174727; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 99263.46666666666; 0.0; 1.0; 0.0; 0.0 +1378175027; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 95069.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378175327; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 89476.53333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1378175627; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 92272.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1378175927; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 82485.86666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1378176227; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 78291.46666666666; 0.0; 1.1333333333333333; 0.0; 0.0 +1378176527; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 107652.26666666666; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378176827; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 103457.86666666667; 0.0; 0.8; 0.0; 0.0 +1378177127; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 79689.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1378177427; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 100661.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1378177727; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 114642.4; 0.06666666666666667; 2.533333333333333; 0.06666666666666667; 0.4666666666666667 +1378178027; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 184546.13333333333; 0.0; 1.0; 0.0; 0.0 +1378178327; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 127226.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378178627; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 109050.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1378178927; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 111846.66666666667; 0.0; 1.2; 0.0; 0.0 +1378179227; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 107652.26666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378179527; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 144003.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1378179828; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 106253.86666666667; 0.0; 1.0; 0.0; 0.0 +1378180128; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 90874.66666666667; 0.0; 7.8; 0.2; 0.13333333333333333 +1378180428; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 85282.13333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1378180728; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 95069.06666666667; 0.0; 1.6; 0.0; 0.0 +1378181028; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 118837.33333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378181328; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 110448.53333333334; 0.0; 2.466666666666667; 0.06666666666666667; 0.4666666666666667 +1378181628; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 138411.2; 0.0; 1.2; 0.0; 0.0 +1378181928; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 114642.93333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378182228; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 81087.73333333334; 0.0; 1.0; 0.0; 0.0 +1378182528; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 125828.0; 0.0; 1.1333333333333333; 0.0; 0.0 +1378182828; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 100661.6; 0.0; 1.0; 0.0; 0.0 +1378183128; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 99263.46666666666; 0.0; 1.0; 0.0; 0.0 +1378183428; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 121633.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1378183728; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 125828.0; 0.0; 1.0; 0.0; 0.0 +1378184028; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 104856.0; 0.0; 1.0; 0.0; 0.0 +1378184328; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 90874.66666666667; 0.0; 1.0; 0.0; 0.0 +1378184628; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 93670.93333333333; 0.0; 1.2; 0.0; 0.0 +1378184928; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 145401.33333333334; 0.0; 2.6; 0.06666666666666667; 0.5333333333333333 +1378185228; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 177556.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1378185528; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 97865.33333333333; 0.0; 0.8; 0.0; 0.0 +1378185828; 1; 2599.999343; 69.33331581333334; 2.666666666666667; 2097152.0; 619357.0666666667; 161.0; 22.0; 0.06666666666666667; 0.4 +1378186128; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 404049.3333333333; 0.0; 2.7333333333333334; 0.0; 0.0 +1378186428; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 234879.73333333334; 31.8; 14.466666666666667; 0.0; 0.0 +1378186728; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 190140.0; 0.0; 8.866666666666667; 0.2; 0.13333333333333333 +1378187028; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 178955.46666666667; 0.0; 1.6666666666666667; 0.0; 0.0 +1378187328; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 141207.46666666667; 0.0; 1.2; 0.0; 0.0 +1378187628; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 123031.73333333334; 0.0; 0.8; 0.0; 0.0 +1378187928; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 96467.2; 0.0; 0.8; 0.0; 0.0 +1378188228; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 157985.06666666668; 0.0; 0.8666666666666667; 0.0; 0.0 +1378188529; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 116041.06666666667; 0.4; 2.8; 0.06666666666666667; 0.5333333333333333 +1378188829; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 139808.53333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378189129; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 109050.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1378189429; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 113244.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1378189729; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 104856.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1378190029; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 146799.73333333334; 0.0; 2.3333333333333335; 0.06666666666666667; 0.06666666666666667 +1378190329; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 138410.66666666666; 0.0; 1.7333333333333334; 0.0; 0.0 +1378190629; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 123031.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1378190929; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 153789.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378191229; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1378191529; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 111846.66666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1378191829; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 134216.8; 0.0; 0.8; 0.0; 0.0 +1378192129; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 139808.26666666666; 0.06666666666666667; 2.4; 0.06666666666666667; 0.5333333333333333 +1378192429; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 145401.33333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1378192729; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 157984.26666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1378193029; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 117439.2; 0.0; 0.8; 0.0; 0.0 +1378193329; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 114642.93333333333; 0.0; 0.6666666666666666; 0.06666666666666667; 0.0 +1378193629; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 111846.66666666667; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378193929; 1; 2599.999343; 22.533327639333333; 0.8666666666666667; 2097152.0; 135614.13333333333; 12.733333333333333; 13.666666666666666; 0.3333333333333333; 0.13333333333333333 +1378194229; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 120235.46666666666; 0.0; 1.6; 0.13333333333333333; 0.0 +1378194529; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 117439.2; 0.0; 1.2; 0.06666666666666667; 0.0 +1378194829; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 111846.66666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1378195129; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 109050.4; 0.0; 0.8; 0.06666666666666667; 0.0 +1378195429; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 82485.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378195729; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 120235.46666666666; 0.06666666666666667; 2.1333333333333333; 0.06666666666666667; 0.4666666666666667 +1378196029; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 104856.0; 0.0; 1.0; 0.0; 0.0 +1378196329; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 145401.06666666668; 0.0; 0.8; 0.0; 0.0 +1378196629; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 111846.66666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1378196929; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 109050.4; 0.0; 1.0; 0.0; 0.0 +1378197229; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 109050.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1378197529; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 116041.06666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378197829; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 114642.93333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1378198129; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 128624.26666666666; 0.0; 1.0; 0.0; 0.0 +1378198429; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 106254.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378198730; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 83884.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1378199030; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 113244.8; 0.0; 1.3333333333333333; 0.0; 0.0 +1378199330; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 146800.0; 0.0; 2.466666666666667; 0.06666666666666667; 0.5333333333333333 +1378199630; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 116041.06666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1378199930; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 117439.2; 0.0; 0.7333333333333333; 0.0; 0.0 +1378200230; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 96467.2; 0.0; 7.0; 0.26666666666666666; 0.13333333333333333 +1378200530; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 128624.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378200830; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 123031.73333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1378201130; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 103457.86666666667; 0.0; 1.3333333333333333; 0.0; 0.0 +1378201430; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 86680.26666666666; 0.0; 0.6666666666666666; 0.06666666666666667; 0.0 +1378201730; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 120234.66666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378202030; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 109050.4; 0.0; 0.8; 0.0; 0.0 +1378202330; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 104856.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1378202630; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 110448.53333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1378202930; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 111846.4; 0.0; 2.3333333333333335; 0.06666666666666667; 0.5333333333333333 +1378203230; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 124429.33333333333; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378203530; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 114642.93333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378203830; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 137012.26666666666; 0.0; 1.1333333333333333; 0.0; 0.0 +1378204130; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 130022.4; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378204430; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 124429.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378204730; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 131419.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1378205030; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 106254.13333333333; 0.0; 1.2666666666666666; 0.0; 0.0 +1378205330; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 123031.73333333334; 0.0; 1.2; 0.0; 0.0 +1378205630; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 114642.93333333333; 0.0; 1.0; 0.0; 0.0 +1378205930; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 103457.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378206230; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 79689.6; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378206530; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 110448.26666666666; 0.0; 2.6666666666666665; 0.06666666666666667; 0.4666666666666667 +1378206831; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 141206.93333333332; 0.0; 7.133333333333334; 0.2; 0.13333333333333333 +1378207131; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 103457.86666666667; 1.3333333333333333; 6.133333333333334; 0.0; 0.0 +1378207431; 1; 2599.999343; 32.93332501133333; 1.2666666666666666; 2097152.0; 171964.8; 21.6; 7.2; 0.06666666666666667; 0.0 +1378207731; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 116041.06666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378208031; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 141207.46666666667; 0.0; 1.0666666666666667; 0.13333333333333333; 0.0 +1378208331; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 118837.33333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378208631; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1378208931; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 135614.93333333332; 0.0; 1.1333333333333333; 0.0; 0.0 +1378209231; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 109050.4; 0.0; 1.0666666666666667; 0.0; 0.0 +1378209531; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 132818.66666666666; 0.0; 1.1333333333333333; 0.0; 0.0 +1378209831; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 109050.4; 0.0; 1.0; 0.0; 0.0 +1378210131; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 150992.8; 0.06666666666666667; 2.6; 0.06666666666666667; 0.5333333333333333 +1378210431; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 174761.06666666668; 0.0; 1.0; 0.0; 0.0 +1378210731; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 121633.6; 0.0; 1.2; 0.13333333333333333; 0.0 +1378211031; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1378211331; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 95069.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378211631; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 130021.06666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378211931; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 146799.46666666667; 0.0; 1.0; 0.06666666666666667; 0.0 +1378212231; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 102059.73333333334; 0.0; 1.2666666666666666; 0.06666666666666667; 0.0 +1378212531; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 90874.66666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378212831; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 135614.13333333333; 0.06666666666666667; 7.133333333333334; 0.2; 0.13333333333333333 +1378213131; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 83884.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1378213431; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 96467.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1378213731; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 137012.0; 0.0; 2.3333333333333335; 0.06666666666666667; 0.4666666666666667 +1378214031; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 148197.6; 0.0; 1.0; 0.0; 0.0 +1378214331; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 117439.2; 0.0; 1.1333333333333333; 0.0; 0.0 +1378214631; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 124429.86666666667; 0.0; 1.0; 0.0; 0.0 +1378214931; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 83884.0; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1378215231; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 113244.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1378215531; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 106254.13333333333; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378215831; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 89476.53333333334; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1378216131; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 85282.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378216431; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 88078.4; 0.0; 1.0; 0.0; 0.0 +1378216731; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 107652.26666666666; 0.0; 0.9333333333333333; 0.2; 0.0 +1378217031; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 131420.26666666666; 0.0; 1.0; 0.0; 0.0 +1378217331; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 152392.0; 0.0; 2.4; 0.06666666666666667; 0.5333333333333333 +1378217631; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 162178.4; 0.0; 1.0666666666666667; 0.0; 0.0 +1378217932; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 123031.73333333334; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378218232; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 96467.2; 0.0; 0.8; 0.0; 0.0 +1378218532; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 93670.93333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378218832; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 120235.46666666666; 0.0; 1.1333333333333333; 0.06666666666666667; 0.13333333333333333 +1378219132; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 127226.13333333333; 0.0; 6.466666666666667; 0.2; 0.13333333333333333 +1378219432; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 123031.73333333334; 0.0; 1.6666666666666667; 0.0; 0.0 +1378219732; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 141207.46666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378220032; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 124429.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378220332; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 121633.6; 0.0; 1.0; 0.0; 0.0 +1378220632; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 113244.8; 0.0; 0.9333333333333333; 0.13333333333333333; 0.0 +1378220932; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 118836.53333333334; 0.0; 2.2; 0.06666666666666667; 0.5333333333333333 +1378221232; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 176159.2; 0.0; 1.0; 0.2; 0.0 +1378221532; 1; 2599.999343; 6.933331581333333; 0.26666666666666666; 2097152.0; 116041.06666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378221832; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 130022.4; 0.0; 1.2666666666666666; 0.0; 0.0 +1378222132; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 82485.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378222432; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 137013.06666666668; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378222732; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 88078.4; 0.0; 0.8666666666666667; 0.13333333333333333; 0.0 +1378223032; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 88078.4; 0.0; 1.0666666666666667; 0.0; 0.0 +1378223332; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 106254.13333333333; 0.0; 1.2666666666666666; 0.0; 0.0 +1378223632; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 127225.33333333333; 0.0; 1.0; 0.0; 0.0 +1378223932; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 117439.2; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378224232; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 109050.4; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378224532; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 121633.6; 0.26666666666666666; 2.8; 0.06666666666666667; 0.4666666666666667 +1378224832; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 125828.0; 0.0; 0.8; 0.4666666666666667; 0.0 +1378225132; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 109050.4; 0.0; 0.8666666666666667; 0.13333333333333333; 0.0 +1378225432; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 104856.0; 0.0; 1.6666666666666667; 0.06666666666666667; 0.0 +1378225732; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 109050.4; 0.0; 5.866666666666666; 0.26666666666666666; 0.13333333333333333 +1378226032; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 121633.6; 0.0; 2.4; 0.3333333333333333; 0.0 +1378226332; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 104856.0; 0.0; 1.0; 0.0; 0.0 +1378226632; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 99263.46666666666; 0.0; 1.0; 0.0; 0.0 +1378226932; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 100661.6; 0.0; 0.8; 0.0; 0.0 +1378227232; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 117439.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1378227533; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 76893.33333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378227833; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 110448.53333333334; 0.0; 1.0; 0.0; 0.0 +1378228133; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 116041.06666666667; 0.06666666666666667; 2.3333333333333335; 0.0; 0.4666666666666667 +1378228433; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 138410.4; 0.0; 1.0; 0.0; 0.0 +1378228733; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 120235.46666666666; 0.0; 1.4; 0.0; 0.0 +1378229033; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 114642.93333333333; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1378229333; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 106254.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378229633; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 121633.6; 0.0; 0.8; 0.0; 0.0 +1378229933; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 102059.73333333334; 0.0; 1.0666666666666667; 0.0; 0.0 +1378230233; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 121633.6; 0.0; 12.066666666666666; 0.0; 0.0 +1378230533; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 107652.26666666666; 0.0; 1.0; 0.0; 0.0 +1378230833; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 109050.4; 0.0; 1.0; 0.0; 0.0 +1378231133; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 106254.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378231433; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 117439.2; 0.0; 1.2666666666666666; 0.0; 0.0 +1378231733; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 139808.53333333333; 0.0; 2.466666666666667; 0.06666666666666667; 0.4666666666666667 +1378232033; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 164974.93333333332; 0.0; 0.9333333333333333; 0.0; 0.0 +1378232333; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 109050.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1378232633; 1; 2599.999343; 6.933331581333333; 0.26666666666666666; 2097152.0; 92272.8; 0.0; 1.1333333333333333; 0.0; 0.0 +1378232933; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 155188.0; 0.0; 7.333333333333333; 0.26666666666666666; 0.13333333333333333 +1378233233; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 102059.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1378233533; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 89476.53333333334; 0.0; 0.8; 0.0; 0.0 +1378233833; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 89476.53333333334; 0.0; 1.0666666666666667; 0.0; 0.0 +1378234133; 1; 2599.999343; 71.06664870866665; 2.733333333333333; 2097152.0; 789924.8; 161.06666666666666; 22.2; 0.06666666666666667; 0.4 +1378234433; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 331348.0; 0.0; 2.933333333333333; 0.0; 0.0 +1378234733; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 188741.6; 31.8; 3.466666666666667; 0.0; 0.0 +1378235033; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 150993.6; 0.8; 2.3333333333333335; 0.06666666666666667; 0.0 +1378235333; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 188741.6; 0.06666666666666667; 3.466666666666667; 0.13333333333333333; 0.5333333333333333 +1378235633; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 159382.4; 0.0; 1.2666666666666666; 0.0; 0.0 +1378235933; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 109050.4; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378236233; 1; 2599.999343; 6.933331581333333; 0.26666666666666666; 2097152.0; 97865.33333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378236533; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 130022.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1378236834; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 100661.6; 0.0; 1.0; 0.0; 0.0 +1378237134; 1; 2599.999343; 46.799988174; 1.8; 2097152.0; 208315.73333333334; 161.8; 14.8; 0.0; 0.13333333333333333 +1378237434; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 469760.0; 0.0; 1.3333333333333333; 0.0; 0.0 +1378237734; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 191538.4; 31.8; 4.066666666666666; 0.0; 0.0 +1378238034; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 213908.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1378238334; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 113244.8; 0.0; 1.1333333333333333; 0.0; 0.0 +1378238634; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 121633.6; 0.0; 7.0; 0.2; 0.13333333333333333 +1378238934; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 146798.4; 0.0; 2.066666666666667; 0.06666666666666667; 0.5333333333333333 +1378239234; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 163576.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1378239534; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 121633.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1378239834; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 125828.0; 0.0; 1.1333333333333333; 0.0; 0.0 +1378240134; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 92272.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1378240434; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 127226.13333333333; 0.0; 1.0; 0.0; 0.0 +1378240734; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 138411.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1378241034; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 117439.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1378241334; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 134216.0; 0.0; 1.0; 0.0; 0.0 +1378241634; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 83884.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1378241934; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 88078.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1378242234; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 109050.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1378242534; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 130021.6; 0.0; 2.6; 0.0; 0.5333333333333333 +1378242834; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 125828.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1378243134; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 113244.0; 0.0; 0.8; 0.0; 0.0 +1378243434; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 109050.4; 0.0; 0.8; 0.0; 0.0 +1378243734; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 118836.53333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1378244034; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 83884.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1378244334; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 97865.33333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378244634; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 120235.46666666666; 0.0; 7.2; 0.2; 0.13333333333333333 +1378244934; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 142604.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1378245234; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 128624.26666666666; 0.0; 1.1333333333333333; 0.0; 0.0 +1378245534; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 144002.93333333332; 0.0; 0.8; 0.0; 0.0 +1378245834; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 138411.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1378246134; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 110448.53333333334; 0.06666666666666667; 2.466666666666667; 0.06666666666666667; 0.4666666666666667 +1378246434; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 116041.06666666667; 0.0; 1.0; 0.0; 0.0 +1378246734; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 109050.4; 0.0; 1.1333333333333333; 0.0; 0.0 +1378247034; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 114642.93333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378247334; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 118837.33333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378247634; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 132817.86666666667; 0.06666666666666667; 1.5333333333333334; 0.0; 0.06666666666666667 +1378247935; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 121633.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1378248235; 1; 2599.999343; 6.933331581333333; 0.26666666666666666; 2097152.0; 138411.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1378248535; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 120235.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378248835; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 114642.93333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378249135; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 120235.46666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378249435; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 124429.86666666667; 0.0; 1.2666666666666666; 0.0; 0.0 +1378249735; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 139809.33333333334; 0.0; 2.2666666666666666; 0.06666666666666667; 0.5333333333333333 +1378250035; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 134216.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1378250335; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 113244.8; 0.0; 0.8; 0.0; 0.0 +1378250635; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 97865.33333333333; 0.0; 1.0; 0.0; 0.0 +1378250935; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 103457.86666666667; 0.0; 6.866666666666666; 0.2; 0.13333333333333333 +1378251235; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 148198.13333333333; 0.0; 2.066666666666667; 0.0; 0.0 +1378251536; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 89476.53333333334; 0.0; 1.2; 0.0; 0.0 +1378251836; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 110448.53333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1378252136; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 111846.66666666667; 0.0; 1.6; 0.0; 0.0 +1378252436; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 99263.46666666666; 0.0; 0.8; 0.0; 0.0 +1378252736; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 107652.26666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378253036; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 128624.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378253336; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 121633.6; 0.0; 2.2666666666666666; 0.06666666666666667; 0.5333333333333333 +1378253636; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 163576.0; 0.0; 1.2666666666666666; 0.0; 0.0 +1378253936; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 150994.4; 0.0; 1.1333333333333333; 0.0; 0.0 +1378254236; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 106254.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378254536; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 85282.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378254836; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 114642.93333333333; 0.5333333333333333; 1.4666666666666666; 0.0; 0.0 +1378255136; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 107652.26666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378255436; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 134216.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1378255736; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 114642.93333333333; 0.0; 1.0; 0.0; 0.0 +1378256036; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 117439.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1378256336; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 92272.8; 0.0; 1.2666666666666666; 0.0; 0.0 +1378256636; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 102059.73333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1378256936; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 176159.2; 0.2; 2.533333333333333; 0.0; 0.5333333333333333 +1378257236; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 150992.8; 0.06666666666666667; 1.0; 0.0; 0.0 +1378257536; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 83884.0; 0.0; 1.0; 0.0; 0.0 +1378257836; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 117439.2; 12.8; 13.0; 0.4; 0.13333333333333333 +1378258136; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 130022.4; 0.0; 1.0666666666666667; 0.0; 0.0 +1378258436; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 152391.73333333334; 0.0; 1.3333333333333333; 0.0; 0.0 +1378258736; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 114642.93333333333; 0.0; 1.0; 0.0; 0.0 +1378259036; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 113244.8; 0.0; 1.2; 0.0; 0.0 +1378259336; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 113244.8; 0.0; 1.0666666666666667; 0.0; 0.0 +1378259636; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 89476.53333333334; 0.0; 1.0; 0.0; 0.0 +1378259936; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 130022.4; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378260236; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 131419.73333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1378260537; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 157983.46666666667; 0.0; 2.6; 0.0; 0.5333333333333333 +1378260837; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 139808.53333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1378261137; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 166373.06666666668; 0.0; 0.9333333333333333; 0.0; 0.0 +1378261437; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 130022.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1378261737; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 121633.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1378262037; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 96467.2; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1378262337; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 96467.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1378262637; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 100661.6; 0.0; 1.0; 0.0; 0.0 +1378262937; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 117438.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1378263237; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 132817.86666666667; 0.0; 1.2; 0.0; 0.0 +1378263537; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 110447.73333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1378263837; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 110448.53333333334; 0.0; 0.8; 0.0; 0.0 +1378264137; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 138410.4; 0.0; 2.2; 0.0; 0.4666666666666667 +1378264437; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 148197.33333333334; 0.0; 6.733333333333333; 0.2; 0.13333333333333333 +1378264737; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 163577.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1378265037; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 130022.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1378265337; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 96467.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1378265637; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 111846.66666666667; 0.0; 1.0; 0.0; 0.0 +1378265937; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 109050.4; 0.0; 1.1333333333333333; 0.0; 0.0 +1378266237; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 104856.0; 0.0; 0.8; 0.0; 0.0 +1378266537; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 117439.2; 0.0; 1.0; 0.0; 0.0 +1378266837; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 92272.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1378267137; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 83884.0; 0.0; 0.8; 0.06666666666666667; 0.0 +1378267437; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 104856.0; 1.4666666666666666; 1.4; 0.0; 0.0 +1378267737; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 141206.66666666666; 0.0; 2.2666666666666666; 0.06666666666666667; 0.4666666666666667 +1378268037; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 113244.8; 0.0; 1.0; 0.0; 0.0 +1378268338; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 96467.2; 0.0; 0.8; 0.0; 0.0 +1378268638; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 138410.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1378268938; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 113244.8; 0.0; 0.8; 0.0; 0.0 +1378269238; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 113244.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1378269538; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 92272.8; 0.0; 0.6666666666666666; 0.0; 0.0 +1378269838; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 100661.6; 0.0; 1.7333333333333334; 0.0; 0.0 +1378270138; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 96467.2; 0.0; 1.2; 0.0; 0.0 +1378270438; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 146799.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1378270738; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 142604.8; 0.0; 0.6666666666666666; 0.0; 0.0 +1378271038; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 117439.2; 0.0; 0.7333333333333333; 0.0; 0.0 +1378271338; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 166372.26666666666; 0.06666666666666667; 2.6; 0.0; 0.5333333333333333 +1378271638; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 139809.33333333334; 0.0; 7.0; 0.26666666666666666; 0.2 +1378271938; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 146800.0; 0.0; 0.6666666666666666; 0.0; 0.0 +1378272238; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 111846.66666666667; 0.0; 0.8; 0.0; 0.0 +1378272538; 1; 2599.999343; 5.199998686; 0.2; 2097152.0; 121633.6; 0.0; 0.7333333333333333; 0.0; 0.0 +1378272838; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 130022.4; 0.0; 1.2666666666666666; 0.0; 0.0 +1378273138; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 135614.93333333332; 0.0; 0.6; 0.0; 0.0 +1378273438; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 117439.2; 0.0; 0.8; 0.0; 0.0 +1378273738; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 100661.6; 0.0; 11.666666666666666; 0.06666666666666667; 0.0 +1378274038; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 88078.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1378274338; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 121633.6; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378274638; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 102059.73333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1378274938; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 121633.6; 0.06666666666666667; 2.3333333333333335; 0.06666666666666667; 0.5333333333333333 +1378275238; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 155188.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1378275538; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 96467.2; 0.0; 1.0; 0.0; 0.0 +1378275838; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 113244.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1378276138; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 74097.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378276438; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 117439.2; 0.0; 2.4; 0.06666666666666667; 0.13333333333333333 +1378276738; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 144002.93333333332; 0.0; 1.5333333333333334; 0.0; 0.0 +1378277038; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 117439.2; 0.0; 1.4666666666666666; 0.06666666666666667; 0.0 +1378277338; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 104856.0; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378277638; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 99263.46666666666; 0.0; 6.933333333333334; 0.4666666666666667; 0.13333333333333333 +1378277938; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 90874.66666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378278238; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 130022.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1378278538; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 156586.93333333332; 0.0; 2.6; 0.0; 0.5333333333333333 +1378278838; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 156584.8; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378279138; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 114642.93333333333; 0.0; 0.9333333333333333; 0.26666666666666666; 0.0 +1378279438; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 124428.8; 0.0; 1.0; 0.0; 0.0 +1378279738; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 100661.6; 0.0; 1.2666666666666666; 0.0; 0.0 +1378280038; 1; 2599.999343; 32.93332501133333; 1.2666666666666666; 2097152.0; 164974.66666666666; 161.13333333333333; 20.4; 0.06666666666666667; 0.4 +1378280338; 1; 2599.999343; 53.733319755333326; 2.0666666666666664; 2097152.0; 759166.4; 0.0; 3.6666666666666665; 0.0; 0.0 +1378280638; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 314570.4; 31.8; 3.7333333333333334; 0.0; 0.0 +1378280938; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 204119.46666666667; 0.0; 2.8; 0.0; 0.0 +1378281238; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 183149.06666666668; 0.0; 2.066666666666667; 0.0; 0.0 +1378281538; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 148197.86666666667; 0.0; 1.0; 0.0; 0.0 +1378281838; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 110448.53333333334; 0.0; 1.0; 0.13333333333333333; 0.0 +1378282138; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 163575.73333333334; 0.06666666666666667; 2.533333333333333; 0.0; 0.5333333333333333 +1378282438; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 170566.66666666666; 0.0; 1.2; 0.0; 0.0 +1378282739; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 130022.4; 0.0; 1.0666666666666667; 0.0; 0.0 +1378283039; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 111846.66666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378283339; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 95069.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378283639; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 102059.73333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1378283939; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 121633.6; 0.0; 1.2666666666666666; 0.0; 0.0 +1378284239; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 118837.33333333333; 0.0; 1.0; 0.0; 0.0 +1378284539; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 135614.93333333332; 0.06666666666666667; 7.133333333333334; 0.2; 0.13333333333333333 +1378284839; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 116040.26666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378285139; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 95069.06666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378285439; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 96467.2; 0.0; 1.1333333333333333; 0.0; 0.0 +1378285739; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 107652.26666666666; 0.0; 2.4; 0.06666666666666667; 0.4666666666666667 +1378286039; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 142605.06666666668; 0.0; 1.0; 0.0; 0.0 +1378286339; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 139809.06666666668; 0.0; 1.0; 0.0; 0.0 +1378286639; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 116041.06666666667; 0.0; 1.2; 0.0; 0.0 +1378286939; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 107652.26666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378287239; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 103457.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378287539; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 104856.0; 0.0; 1.0; 0.0; 0.0 +1378287839; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 92272.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1378288139; 1; 2599.999343; 22.533327639333333; 0.8666666666666667; 2097152.0; 137013.06666666668; 20.4; 2.3333333333333335; 0.0; 0.06666666666666667 +1378288439; 1; 2599.999343; 29.466659220666667; 1.1333333333333333; 2097152.0; 135614.93333333332; 0.0; 3.933333333333333; 0.0; 0.0 +1378288739; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 127226.13333333333; 0.0; 2.466666666666667; 0.0; 0.0 +1378289039; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 152390.93333333332; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378289340; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 144003.73333333334; 0.06666666666666667; 2.7333333333333334; 0.0; 0.4666666666666667 +1378289640; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 166372.26666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378289940; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 99263.46666666666; 0.0; 6.866666666666666; 0.2; 0.13333333333333333 +1378290240; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 160780.53333333333; 0.0; 1.0; 0.0; 0.0 +1378290540; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 89476.53333333334; 0.0; 1.0; 0.0; 0.0 +1378290840; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 90874.66666666667; 0.0; 1.2666666666666666; 0.0; 0.0 +1378291140; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 96467.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1378291440; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 100661.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1378291740; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 120235.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378292040; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 118837.33333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378292340; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 117439.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1378292640; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 130021.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1378292940; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 121633.06666666667; 0.13333333333333333; 2.6666666666666665; 0.06666666666666667; 0.5333333333333333 +1378293240; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 174760.8; 0.0; 1.0; 0.0; 0.0 +1378293540; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 117439.2; 0.0; 1.1333333333333333; 0.0; 0.0 +1378293840; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 92272.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1378294140; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1378294440; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 102059.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1378294740; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 113244.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1378295040; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 117439.2; 0.0; 1.1333333333333333; 0.0; 0.0 +1378295340; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 132818.66666666666; 0.0; 1.2; 0.0; 0.0 +1378295640; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 110448.53333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1378295940; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 114642.93333333333; 0.0; 0.8; 0.0; 0.0 +1378296240; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 156585.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378296540; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 191538.13333333333; 0.06666666666666667; 2.466666666666667; 0.06666666666666667; 0.5333333333333333 +1378296840; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 194334.66666666666; 0.0; 6.733333333333333; 0.2; 0.13333333333333333 +1378297140; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 181751.73333333334; 0.0; 1.1333333333333333; 0.0; 0.0 +1378297441; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 118837.33333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378297741; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 100661.6; 0.0; 1.2666666666666666; 0.0; 0.0 +1378298041; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 79689.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1378298341; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 102059.73333333334; 0.0; 0.8; 0.0; 0.0 +1378298641; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 118837.33333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378298941; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 109049.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1378299241; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 83884.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1378299541; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 110448.53333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1378299841; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 160780.0; 0.0; 1.0; 0.0; 0.0 +1378300141; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 162177.86666666667; 0.0; 2.466666666666667; 0.06666666666666667; 0.4666666666666667 +1378300441; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 159382.4; 0.0; 1.2; 0.0; 0.0 +1378300741; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 121633.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1378301041; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 148198.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378301341; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 114642.93333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378301641; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 102059.73333333334; 0.9333333333333333; 1.7333333333333334; 0.0; 0.0 +1378301941; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 93670.93333333333; 0.0; 1.3333333333333333; 0.0; 0.0 +1378302241; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 117439.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1378302541; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 141207.46666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378302841; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 111846.66666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378303141; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 99263.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378303441; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 111846.66666666667; 0.0; 7.466666666666667; 0.2; 0.13333333333333333 +1378303741; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 160780.0; 0.0; 2.6666666666666665; 0.06666666666666667; 0.4666666666666667 +1378304041; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 174760.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1378304341; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 79689.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1378304642; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 81087.73333333334; 0.0; 1.3333333333333333; 0.0; 0.0 +1378304942; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 93670.93333333333; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378305242; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 177557.06666666668; 0.2; 1.2; 0.06666666666666667; 0.13333333333333333 +1378305542; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 117439.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1378305842; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 106254.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378306142; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 97865.33333333333; 0.0; 1.2; 0.0; 0.0 +1378306442; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 106254.13333333333; 0.0; 1.0; 0.0; 0.0 +1378306742; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 104856.0; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378307042; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 149595.46666666667; 0.0; 1.0; 0.06666666666666667; 0.0 +1378307342; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 138410.13333333333; 0.0; 2.533333333333333; 0.06666666666666667; 0.4666666666666667 +1378307641; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 150993.6; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378307941; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 116041.06666666667; 0.0; 0.8; 0.0; 0.0 +1378308241; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 102059.73333333334; 0.0; 1.0; 0.0; 0.0 +1378308541; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 97865.33333333333; 0.0; 0.8666666666666667; 0.13333333333333333; 0.0 +1378308841; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 135614.93333333332; 0.0; 1.2666666666666666; 0.06666666666666667; 0.0 +1378309141; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 141207.46666666667; 0.0; 1.0; 0.06666666666666667; 0.0 +1378309441; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 124429.86666666667; 0.0; 1.1333333333333333; 0.2; 0.0 +1378309741; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 148198.13333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1378310041; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 130022.4; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378310341; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 114642.93333333333; 0.0; 7.133333333333334; 0.4; 0.13333333333333333 +1378310641; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 121633.6; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378310941; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 123031.73333333334; 0.0; 2.466666666666667; 0.8666666666666667; 0.5333333333333333 +1378311241; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 127225.33333333333; 0.0; 1.0; 0.0; 0.0 +1378311541; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 118837.33333333333; 0.0; 1.2; 0.0; 0.0 +1378311842; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 121633.6; 0.0; 1.0; 0.0; 0.0 +1378312142; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 134216.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1378312442; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 109050.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1378312742; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 127226.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378313042; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 141207.46666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1378313342; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 124429.86666666667; 0.0; 1.0; 0.0; 0.0 +1378313642; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 128624.26666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378313942; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 104856.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1378314242; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 114642.93333333333; 0.0; 1.8666666666666667; 0.0; 0.0 +1378314542; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 150993.86666666667; 0.0; 2.466666666666667; 0.06666666666666667; 0.5333333333333333 +1378314842; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 174760.53333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378315142; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 127226.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378315442; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 86680.26666666666; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1378315742; 1; 2599.999343; 22.533327639333333; 0.8666666666666667; 2097152.0; 86680.26666666666; 12.733333333333333; 13.533333333333333; 0.4666666666666667; 0.2 +1378316042; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 118837.33333333333; 0.0; 1.4666666666666666; 0.06666666666666667; 0.0 +1378316342; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 93670.93333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1378316642; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 90874.66666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378316942; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 123031.73333333334; 0.0; 0.9333333333333333; 0.8666666666666667; 0.0 +1378317242; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 104855.2; 0.0; 11.866666666666667; 0.2; 0.0 +1378317542; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 85282.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378317842; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 92272.8; 0.0; 1.4666666666666666; 0.0; 0.0 +1378318142; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 130022.13333333333; 0.06666666666666667; 2.7333333333333334; 0.06666666666666667; 0.5333333333333333 +1378318442; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 138410.66666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1378318742; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 107652.26666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378319042; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 95069.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378319342; 1; 2599.999343; 6.933331581333333; 0.26666666666666666; 2097152.0; 124429.86666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1378319642; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 120235.46666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1378319942; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 148197.33333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1378320243; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 92272.8; 0.0; 1.0666666666666667; 0.0; 0.0 +1378320543; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 92272.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1378320843; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 107652.26666666666; 0.0; 1.0; 0.06666666666666667; 0.0 +1378321143; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 120235.46666666666; 0.0; 0.6; 0.0; 0.0 +1378321443; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 99263.46666666666; 0.0; 1.0; 0.0; 0.0 +1378321743; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 144003.73333333334; 0.06666666666666667; 2.466666666666667; 0.06666666666666667; 0.5333333333333333 +1378322043; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 178955.46666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1378322343; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 120235.46666666666; 0.0; 6.933333333333334; 0.2; 0.13333333333333333 +1378322643; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 148197.33333333334; 0.0; 1.2; 0.0; 0.0 +1378322943; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 102059.73333333334; 0.0; 0.8; 0.0; 0.0 +1378323243; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 97865.33333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1378323543; 1; 2599.999343; 39.866656592666665; 1.5333333333333334; 2097152.0; 160779.73333333334; 161.0; 14.4; 0.0; 0.2 +1378323843; 1; 2599.999343; 27.733326325333334; 1.0666666666666667; 2097152.0; 443195.73333333334; 0.0; 1.5333333333333334; 0.0; 0.0 +1378324143; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 184547.2; 31.8; 4.066666666666666; 0.0; 0.0 +1378324443; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 192936.26666666666; 0.0; 1.0; 0.0; 0.0 +1378324743; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 120234.66666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378325043; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 96467.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1378325343; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 121632.8; 0.06666666666666667; 2.4; 0.06666666666666667; 0.5333333333333333 +1378325643; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 123030.93333333333; 0.0; 1.2; 0.0; 0.0 +1378325943; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 113244.8; 0.0; 1.0; 0.0; 0.0 +1378326243; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 86680.26666666666; 0.0; 0.8; 0.0; 0.0 +1378326543; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 95069.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378326843; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 124429.86666666667; 0.0; 1.2; 0.0; 0.0 +1378327143; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 100661.6; 0.0; 1.0; 0.0; 0.0 +1378327443; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 93670.93333333333; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378327743; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 102059.73333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1378328043; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 123031.73333333334; 0.0; 1.1333333333333333; 0.0; 0.0 +1378328343; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 125828.0; 0.0; 1.3333333333333333; 0.0; 0.0 +1378328643; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 114642.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378328943; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 159381.6; 0.06666666666666667; 2.4; 0.06666666666666667; 0.5333333333333333 +1378329244; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 190139.46666666667; 0.0; 7.933333333333334; 0.2; 0.13333333333333333 +1378329544; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 153790.13333333333; 0.0; 1.2; 0.0; 0.0 +1378329844; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 97865.33333333333; 0.0; 1.0; 0.0; 0.0 +1378330144; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 82485.86666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1378330444; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 110448.53333333334; 0.0; 1.0; 0.0; 0.0 +1378330744; 1; 2599.999343; 8.666664476666668; 0.33333333333333337; 2097152.0; 149595.46666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378331044; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 111846.66666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1378331344; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 97865.33333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378331644; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 106254.13333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1378331944; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 100661.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1378332244; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 106254.13333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1378332544; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 127225.86666666667; 0.06666666666666667; 2.8; 0.06666666666666667; 0.5333333333333333 +1378332844; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 103457.33333333333; 0.0; 1.0; 0.0; 0.0 +1378333144; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 114642.93333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1378333444; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 131420.53333333333; 0.0; 0.8; 0.0; 0.0 +1378333744; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 106254.13333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1378334044; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 99263.46666666666; 0.0; 1.5333333333333334; 0.06666666666666667; 0.13333333333333333 +1378334344; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 132817.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378334644; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 110448.53333333334; 0.0; 7.2; 0.2; 0.13333333333333333 +1378334944; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 145401.06666666668; 0.0; 1.0666666666666667; 0.0; 0.0 +1378335244; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 169169.06666666668; 0.0; 1.1333333333333333; 0.0; 0.0 +1378335544; 1; 2599.999343; 65.86665002266666; 2.533333333333333; 2097152.0; 485139.4666666667; 161.0; 21.333333333333332; 0.06666666666666667; 0.4 +1378335844; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 588599.4666666667; 1.0666666666666667; 3.066666666666667; 0.0; 0.0 +1378336144; 1; 2599.999343; 24.266660534666663; 0.9333333333333332; 2097152.0; 306181.86666666664; 32.666666666666664; 5.6; 0.06666666666666667; 0.4666666666666667 +1378336444; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 236276.53333333333; 0.0; 2.466666666666667; 0.0; 0.0 +1378336744; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 169169.6; 0.0; 1.6666666666666667; 0.0; 0.0 +1378337045; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 110448.53333333334; 0.06666666666666667; 0.8666666666666667; 0.0; 0.0 +1378337345; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 95069.06666666667; 3.8; 1.4; 0.0; 0.0 +1378337645; 1; 2599.999343; 22.533327639333333; 0.8666666666666667; 2097152.0; 156586.13333333333; 0.4; 3.2666666666666666; 0.0; 0.0 +1378337945; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 128624.26666666666; 0.0; 1.1333333333333333; 0.0; 0.0 +1378338245; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 121633.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1378338545; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 111846.66666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1378338845; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 95069.06666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378339145; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 96467.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1378339445; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 118837.33333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378339745; 1; 2599.999343; 20.799994744; 0.8; 2097152.0; 118837.33333333333; 0.06666666666666667; 2.466666666666667; 0.06666666666666667; 0.5333333333333333 +1378340045; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 135614.93333333332; 0.0; 0.9333333333333333; 0.0; 0.0 +1378340345; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 128623.46666666666; 0.0; 0.8; 0.0; 0.0 +1378340645; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 81087.73333333334; 0.0; 1.0; 0.0; 0.0 +1378340945; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 72698.93333333333; 0.0; 0.6666666666666666; 0.0; 0.0 +1378341245; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 107652.26666666666; 0.6; 7.333333333333333; 0.2; 0.13333333333333333 +1378341545; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 127226.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378341845; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 106254.13333333333; 0.0; 1.0; 0.0; 0.0 +1378342145; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 75495.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1378342445; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 95069.06666666667; 0.0; 1.0; 0.0; 0.0 +1378342745; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1378343045; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 125828.0; 0.0; 0.6666666666666666; 0.0; 0.0 +1378343345; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 153789.86666666667; 0.0; 2.3333333333333335; 0.06666666666666667; 0.4666666666666667 +1378343645; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 152391.73333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1378343945; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 152391.73333333334; 0.0; 1.0; 0.0; 0.0 +1378344245; 1; 2599.999343; 17.333328953333336; 0.6666666666666667; 2097152.0; 121633.6; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378344545; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 110448.53333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1378344845; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 121633.6; 0.06666666666666667; 1.4666666666666666; 0.0; 0.0 +1378345145; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 95069.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378345445; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 110448.53333333334; 0.0; 0.8; 0.06666666666666667; 0.0 +1378345745; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 130022.4; 0.0; 1.0; 0.0; 0.0 +1378346045; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 117439.2; 0.0; 1.0; 0.0; 0.0 +1378346345; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 88078.4; 0.0; 1.2666666666666666; 0.0; 0.0 +1378346645; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 96467.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1378346945; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 137012.26666666666; 0.06666666666666667; 2.3333333333333335; 0.06666666666666667; 0.5333333333333333 +1378347245; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 163576.0; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1378347545; 1; 2599.999343; 19.066661848666666; 0.7333333333333333; 2097152.0; 120235.46666666666; 0.0; 7.466666666666667; 0.3333333333333333; 0.13333333333333333 +1378347845; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 123030.93333333333; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378348145; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 144002.93333333332; 0.0; 0.8666666666666667; 0.0; 0.0 +1378348445; 1; 2599.999343; 10.399997372; 0.4; 2097152.0; 142604.8; 0.0; 1.0666666666666667; 0.0; 0.0 +1378348745; 1; 2599.999343; 12.133330267333331; 0.4666666666666666; 2097152.0; 117439.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1378349045; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 134216.8; 0.0; 1.1333333333333333; 0.0; 0.0 +1378349345; 1; 2599.999343; 15.599996057999999; 0.6; 2097152.0; 90874.66666666667; 0.0; 1.0; 0.0; 0.0 +1378349645; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 144003.73333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1378349945; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 113244.8; 0.0; 1.0666666666666667; 0.0; 0.0 +1378350245; 1; 2599.999343; 13.866663162666667; 0.5333333333333333; 2097152.0; 109050.4; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1378350545; 1; 2599.999601; 25.99999601; 1.0; 2097152.0; 167772.0; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1378350845; 1; 2599.999601; 19.066663740666666; 0.7333333333333333; 2097152.0; 167772.0; 0.0; 1.1333333333333333; 0.2; 0.0 +1378351145; 1; 2599.999601; 5.199999202; 0.2; 2097152.0; 125828.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1378351445; 1; 2599.999601; 6.9333322693333335; 0.26666666666666666; 2097152.0; 124429.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378351745; 1; 2599.999601; 12.133331471333332; 0.4666666666666666; 2097152.0; 75495.2; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1378352046; 1; 2599.999601; 10.399998404; 0.4; 2097152.0; 103457.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1378352346; 1; 2599.999601; 6.9333322693333335; 0.26666666666666666; 2097152.0; 109049.86666666667; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1378352646; 1; 2599.999601; 10.399998404; 0.4; 2097152.0; 107652.26666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378352946; 1; 2599.999601; 3.4666661346666667; 0.13333333333333333; 2097152.0; 121633.6; 0.0; 1.4666666666666666; 0.0; 0.0 +1378353246; 1; 2599.999601; 8.666665336666668; 0.33333333333333337; 2097152.0; 97865.33333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1378353546; 1; 2599.999601; 5.199999202; 0.2; 2097152.0; 82485.86666666667; 0.0; 1.0; 0.0; 0.0 +1378353846; 1; 2599.999601; 8.666665336666668; 0.33333333333333337; 2097152.0; 95069.06666666667; 0.06666666666666667; 6.533333333333333; 0.26666666666666666; 0.13333333333333333 +1378354146; 1; 2599.999601; 13.866664538666667; 0.5333333333333333; 2097152.0; 180353.6; 0.0; 2.6666666666666665; 0.06666666666666667; 0.4666666666666667 +1378354446; 1; 2599.999601; 10.399998404; 0.4; 2097152.0; 125827.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1378354746; 1; 2599.999601; 0.0; 0.0; 2097152.0; 150993.6; 0.0; 1.0; 0.0; 0.0 +1378355046; 1; 2599.999601; 3.4666661346666667; 0.13333333333333333; 2097152.0; 148198.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378355346; 1; 2599.999601; 1.7333330673333334; 0.06666666666666667; 2097152.0; 130022.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1378355646; 1; 2599.999601; 1.7333330673333334; 0.06666666666666667; 2097152.0; 130022.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1378355946; 1; 2599.999601; 1.7333330673333334; 0.06666666666666667; 2097152.0; 117439.2; 0.0; 1.4; 0.0; 0.0 +1378356246; 1; 2599.999601; 0.0; 0.0; 2097152.0; 127225.33333333333; 0.0; 1.0; 0.0; 0.0 +1378356546; 1; 2599.999601; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1378356846; 1; 2599.999601; 3.4666661346666667; 0.13333333333333333; 2097152.0; 92272.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1378357146; 1; 2599.999601; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1378357446; 1; 2599.999601; 0.0; 0.0; 2097152.0; 114642.93333333333; 0.0; 1.0; 0.0; 0.0 +1378357746; 1; 2599.999601; 8.666665336666668; 0.33333333333333337; 2097152.0; 142605.6; 0.0; 2.3333333333333335; 0.06666666666666667; 0.4666666666666667 +1378358046; 1; 2599.999601; 5.199999202; 0.2; 2097152.0; 134216.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1378358346; 1; 2599.999601; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 1.0; 0.0; 0.0 +1378358646; 1; 2599.999601; 1.7333330673333334; 0.06666666666666667; 2097152.0; 109050.4; 0.0; 1.2666666666666666; 0.0; 0.0 +1378358947; 1; 2599.999601; 0.0; 0.0; 2097152.0; 116041.06666666667; 0.0; 1.6666666666666667; 0.0; 0.0 +1378359247; 1; 2599.999601; 1.7333330673333334; 0.06666666666666667; 2097152.0; 127226.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378359547; 1; 2599.999601; 3.4666661346666667; 0.13333333333333333; 2097152.0; 130022.4; 0.0; 1.0; 0.0; 0.0 +1378359847; 1; 2599.999601; 3.4666661346666667; 0.13333333333333333; 2097152.0; 104856.0; 0.0; 6.933333333333334; 0.26666666666666666; 0.13333333333333333 +1378360147; 1; 2599.999601; 1.7333330673333334; 0.06666666666666667; 2097152.0; 110448.53333333334; 0.0; 1.2666666666666666; 0.0; 0.0 +1378360447; 1; 2599.999601; 3.4666661346666667; 0.13333333333333333; 2097152.0; 93670.93333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378360747; 1; 2599.999601; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1378361047; 1; 2599.999601; 5.199999202; 0.2; 2097152.0; 123031.73333333334; 0.0; 11.866666666666667; 0.0; 0.0 +1378361347; 1; 2599.999601; 8.666665336666668; 0.33333333333333337; 2097152.0; 155186.93333333332; 0.0; 2.466666666666667; 0.0; 0.4666666666666667 +1378361647; 1; 2599.999601; 3.4666661346666667; 0.13333333333333333; 2097152.0; 171965.6; 0.0; 0.8666666666666667; 4.4; 0.0 +1378361947; 1; 2599.999601; 1.7333330673333334; 0.06666666666666667; 2097152.0; 162177.6; 0.0; 0.8666666666666667; 0.13333333333333333; 0.0 +1378362247; 1; 2599.999601; 0.0; 0.0; 2097152.0; 121632.8; 0.0; 1.2; 0.26666666666666666; 0.0 +1378362547; 1; 2599.999601; 0.0; 0.0; 2097152.0; 68504.53333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1378362847; 1; 2599.999601; 5.199999202; 0.2; 2097152.0; 173362.13333333333; 0.06666666666666667; 2.8666666666666667; 0.13333333333333333; 0.13333333333333333 +1378363147; 1; 2599.999601; 0.0; 0.0; 2097152.0; 184547.2; 0.0; 1.7333333333333334; 0.0; 0.0 +1378363447; 1; 2599.999601; 0.0; 0.0; 2097152.0; 120235.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378363747; 1; 2599.999601; 0.0; 0.0; 2097152.0; 125828.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1378364047; 1; 2599.999601; 0.0; 0.0; 2097152.0; 134216.0; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1378364347; 1; 2599.999601; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1378364647; 1; 2599.999601; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1378364947; 1; 2599.999601; 5.199999202; 0.2; 2097152.0; 149595.46666666667; 0.26666666666666666; 2.4; 0.06666666666666667; 0.5333333333333333 +1378365247; 1; 2599.999601; 1.7333330673333334; 0.06666666666666667; 2097152.0; 180352.8; 0.0; 1.0; 0.0; 0.0 +1378365547; 1; 2599.999601; 6.9333322693333335; 0.26666666666666666; 2097152.0; 134216.8; 0.0; 7.066666666666666; 0.26666666666666666; 0.13333333333333333 +1378365847; 1; 2599.999601; 1.7333330673333334; 0.06666666666666667; 2097152.0; 102059.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1378366147; 1; 2599.999601; 0.0; 0.0; 2097152.0; 130021.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1378366447; 1; 2599.999601; 0.0; 0.0; 2097152.0; 121633.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1378366747; 1; 2599.999601; 0.0; 0.0; 2097152.0; 120235.46666666666; 0.0; 1.1333333333333333; 0.0; 0.0 +1378367047; 1; 2599.999601; 0.0; 0.0; 2097152.0; 155188.0; 0.0; 1.7333333333333334; 0.0; 0.0 +1378367347; 1; 2599.999601; 1.7333330673333334; 0.06666666666666667; 2097152.0; 128624.26666666666; 0.0; 1.0; 0.2; 0.0 +1378367647; 1; 2599.999601; 0.0; 0.0; 2097152.0; 128623.46666666666; 0.0; 1.0; 0.0; 0.0 +1378367947; 1; 2599.999601; 3.4666661346666667; 0.13333333333333333; 2097152.0; 104856.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1378368247; 1; 2599.999601; 0.0; 0.0; 2097152.0; 130022.4; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1378368547; 1; 2599.999601; 5.199999202; 0.2; 2097152.0; 146799.2; 0.0; 2.3333333333333335; 0.06666666666666667; 0.4666666666666667 +1378368848; 1; 2599.999601; 0.0; 0.0; 2097152.0; 150992.8; 0.0; 1.0666666666666667; 0.0; 0.0 +1378369148; 1; 2599.999601; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1378369448; 1; 2599.999601; 0.0; 0.0; 2097152.0; 81087.73333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1378369748; 1; 2599.999601; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 1.2; 0.0; 0.0 +1378370048; 1; 2599.999601; 0.0; 0.0; 2097152.0; 118837.33333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378370348; 1; 2599.999601; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.8; 0.0; 0.0 +1378370648; 1; 2599.999601; 0.0; 0.0; 2097152.0; 110448.53333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1378370948; 1; 2599.999601; 0.0; 0.0; 2097152.0; 130022.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1378371248; 1; 2599.999601; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1378371548; 1; 2599.999601; 1.7333330673333334; 0.06666666666666667; 2097152.0; 113244.8; 0.0; 0.8; 0.0; 0.0 +1378371848; 1; 2599.999601; 0.0; 0.0; 2097152.0; 138411.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1378372148; 1; 2599.999601; 10.399998404; 0.4; 2097152.0; 141206.66666666666; 0.0; 8.2; 0.26666666666666666; 0.6666666666666666 +1378372448; 1; 2599.999601; 1.7333330673333334; 0.06666666666666667; 2097152.0; 155187.2; 0.0; 1.2; 0.0; 0.0 +1378372748; 1; 2599.999601; 0.0; 0.0; 2097152.0; 150994.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1378373047; 1; 2599.999601; 0.0; 0.0; 2097152.0; 150993.6; 0.0; 0.7333333333333333; 0.0; 0.0 +1378373347; 1; 2599.999601; 0.0; 0.0; 2097152.0; 121633.6; 0.0; 0.6666666666666666; 0.0; 0.0 +1378373647; 1; 2599.999601; 1.7333330673333334; 0.06666666666666667; 2097152.0; 109050.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1378373947; 1; 2599.999601; 1.7333330673333334; 0.06666666666666667; 2097152.0; 96467.2; 0.0; 1.2666666666666666; 0.0; 0.0 +1378374247; 1; 2599.999601; 8.666665336666668; 0.33333333333333337; 2097152.0; 85282.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378374547; 1; 2599.999601; 15.599997605999999; 0.6; 2097152.0; 96467.2; 0.0; 0.7333333333333333; 0.0; 0.0 +1378374847; 1; 2599.999601; 17.333330673333336; 0.6666666666666667; 2097152.0; 92272.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1378375147; 1; 2599.999601; 5.199999202; 0.2; 2097152.0; 117439.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1378375447; 1; 2599.999601; 0.0; 0.0; 2097152.0; 83884.0; 0.0; 0.8; 0.0; 0.0 +1378375747; 1; 2599.999601; 5.199999202; 0.2; 2097152.0; 117439.2; 0.0; 2.2666666666666666; 0.06666666666666667; 0.4666666666666667 +1378376047; 1; 2599.999601; 3.4666661346666667; 0.13333333333333333; 2097152.0; 125828.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1378376347; 1; 2599.999601; 1.7333330673333334; 0.06666666666666667; 2097152.0; 117439.2; 0.0; 0.8; 0.0; 0.0 +1378376648; 1; 2599.999601; 0.0; 0.0; 2097152.0; 82485.86666666667; 0.0; 1.0; 0.0; 0.0 +1378376948; 1; 2599.999601; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.8; 0.0; 0.0 +1378377248; 1; 2599.999601; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.8; 0.0; 0.0 +1378377548; 1; 2599.999601; 0.0; 0.0; 2097152.0; 86680.26666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1378377848; 1; 2599.999601; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 1.0; 0.0; 0.0 +1378378148; 1; 2599.999601; 0.0; 0.0; 2097152.0; 152391.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1378378448; 1; 2599.999601; 0.0; 0.0; 2097152.0; 148197.33333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1378378748; 1; 2599.999601; 0.0; 0.0; 2097152.0; 120235.46666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378379048; 1; 2599.999601; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 0.7333333333333333; 0.0; 0.0 +1378379348; 1; 2599.999601; 10.399998404; 0.4; 2097152.0; 142605.6; 13.0; 18.6; 0.3333333333333333; 0.6 +1378379648; 1; 2599.999601; 10.399998404; 0.4; 2097152.0; 130021.6; 0.0; 1.2; 0.0; 0.0 +1378379948; 1; 2599.999601; 13.866664538666667; 0.5333333333333333; 2097152.0; 125828.0; 0.0; 0.7333333333333333; 0.0; 0.0 +1378380248; 1; 2599.999601; 17.333330673333336; 0.6666666666666667; 2097152.0; 88078.4; 0.0; 0.8; 0.0; 0.0 +1378380548; 1; 2599.999601; 0.0; 0.0; 2097152.0; 109049.6; 0.0; 0.7333333333333333; 0.0; 0.0 +1378380848; 1; 2599.999601; 1.7333330673333334; 0.06666666666666667; 2097152.0; 54523.2; 0.0; 1.1333333333333333; 0.0; 0.0 +1378381148; 1; 2599.999601; 0.0; 0.0; 2097152.0; 82485.86666666667; 0.0; 0.8; 0.0; 0.0 +1378381448; 1; 2599.999601; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 1.4; 0.0; 0.0 +1378381748; 1; 2599.999601; 1.7333330673333334; 0.06666666666666667; 2097152.0; 134216.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1378382048; 1; 2599.999601; 13.866664538666667; 0.5333333333333333; 2097152.0; 134216.8; 0.0; 1.0666666666666667; 0.0; 0.0 +1378382348; 1; 2599.999601; 6.9333322693333335; 0.26666666666666666; 2097152.0; 104856.0; 0.0; 0.6666666666666666; 0.13333333333333333; 0.0 +1378382648; 1; 2599.999601; 17.333330673333336; 0.6666666666666667; 2097152.0; 109050.4; 0.0; 0.8; 0.0; 0.0 +1378382948; 1; 2599.999601; 15.599997605999999; 0.6; 2097152.0; 155187.2; 0.06666666666666667; 2.066666666666667; 0.06666666666666667; 0.4666666666666667 +1378383248; 1; 2599.999601; 0.0; 0.0; 2097152.0; 150992.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1378383548; 1; 2599.999601; 0.0; 0.0; 2097152.0; 141206.66666666666; 0.0; 1.1333333333333333; 0.0; 0.0 +1378383848; 1; 2599.999601; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1378384149; 1; 2599.999601; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378384449; 1; 2599.998989; 51.999979780000004; 2.0; 2097152.0; 83884.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1378384749; 1; 2599.998989; 22.53332457133334; 0.8666666666666667; 2097152.0; 96467.2; 0.0; 0.8; 0.0; 0.0 +1378385049; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 114642.93333333333; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1378385349; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 124429.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378385649; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 139809.33333333334; 0.0; 6.933333333333334; 0.26666666666666666; 0.2 +1378385949; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 121633.6; 0.0; 0.6666666666666666; 0.0; 0.0 +1378386249; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 120235.46666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378386549; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 124429.6; 0.0; 2.533333333333333; 0.13333333333333333; 0.5333333333333333 +1378386849; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 159381.86666666667; 0.0; 0.8; 0.0; 0.0 +1378387149; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 92272.8; 0.0; 0.6666666666666666; 0.13333333333333333; 0.0 +1378387449; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 103457.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378387749; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 88078.4; 0.0; 1.1333333333333333; 0.0; 0.0 +1378388049; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 110448.53333333334; 0.0; 0.8; 0.0; 0.0 +1378388349; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 118837.33333333333; 0.0; 0.6666666666666666; 0.0; 0.0 +1378388649; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 102059.73333333334; 0.0; 0.8; 0.0; 0.0 +1378388949; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 117439.2; 0.0; 1.0; 0.0; 0.0 +1378389249; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 103457.86666666667; 0.0; 0.8; 0.0; 0.0 +1378389549; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 109050.4; 0.0; 0.6666666666666666; 0.0; 0.0 +1378389849; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 114642.93333333333; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378390149; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 138410.4; 0.2; 2.3333333333333335; 0.06666666666666667; 0.5333333333333333 +1378390449; 1; 2599.998989; 74.53330435133334; 2.8666666666666667; 2097152.0; 250258.93333333332; 161.0; 21.8; 0.06666666666666667; 0.3333333333333333 +1378390749; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 717224.5333333333; 0.0; 2.533333333333333; 0.06666666666666667; 0.0 +1378391049; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 359310.13333333336; 31.8; 3.6; 0.0; 0.0 +1378391949; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 145401.86666666667; 0.0; 1.0; 0.0; 0.0 +1378392249; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 130022.4; 0.0; 6.4; 0.2; 0.13333333333333333 +1378392549; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 118837.33333333333; 0.0; 1.3333333333333333; 0.0; 0.0 +1378392849; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 111846.66666666667; 0.0; 0.8; 0.2; 0.0 +1378393149; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 79689.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1378393449; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 113244.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1378393749; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 181751.46666666667; 0.0; 2.2; 0.06666666666666667; 0.4666666666666667 +1378394050; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 149595.73333333334; 0.0; 0.6; 0.06666666666666667; 0.0 +1378394350; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 138410.4; 0.0; 0.8; 0.0; 0.0 +1378394650; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 145401.06666666668; 0.0; 1.2; 0.0; 0.0 +1378394950; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 123031.73333333334; 0.0; 0.8; 0.06666666666666667; 0.0 +1378395250; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 96467.2; 0.0; 0.6666666666666666; 0.0; 0.0 +1378395550; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 88078.4; 0.0; 0.8; 0.0; 0.0 +1378395850; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 118837.33333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1378396150; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 127226.13333333333; 0.0; 1.3333333333333333; 0.0; 0.0 +1378396450; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 127225.33333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378396750; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 130022.4; 0.0; 1.0666666666666667; 0.0; 0.0 +1378397050; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 142605.6; 0.0; 1.1333333333333333; 0.0; 0.0 +1378397350; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 131420.53333333333; 0.0; 2.7333333333333334; 0.13333333333333333; 0.5333333333333333 +1378397650; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 149595.46666666667; 0.0; 0.8; 0.0; 0.0 +1378397950; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 123031.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1378398250; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 125828.0; 0.0; 1.0; 0.0; 0.0 +1378398550; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 144003.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1378398850; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 125828.0; 0.0; 1.2666666666666666; 0.06666666666666667; 0.0 +1378399150; 1; 2599.998989; 20.799991912; 0.8; 2097152.0; 120234.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1378399450; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 116041.06666666667; 0.0; 7.0; 0.26666666666666666; 0.13333333333333333 +1378399750; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 113244.8; 0.0; 1.0; 0.13333333333333333; 0.0 +1378400050; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 114642.93333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1378400350; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 120235.46666666666; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378400650; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 134216.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1378400950; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 125827.2; 0.06666666666666667; 2.533333333333333; 0.0; 0.4666666666666667 +1378401250; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 150994.4; 0.0; 1.0; 0.0; 0.0 +1378401550; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 109050.4; 0.0; 1.2; 0.0; 0.0 +1378401850; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 113244.8; 0.0; 1.0; 0.0; 0.0 +1378402150; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 109050.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1378402450; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 88078.4; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378402750; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 109050.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1378403050; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 96467.2; 0.0; 1.2666666666666666; 0.0; 0.0 +1378403350; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 118837.33333333333; 0.0; 1.7333333333333334; 0.0; 0.0 +1378403650; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 134216.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1378403951; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 125828.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1378404251; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 117439.2; 0.0; 1.6; 0.0; 0.0 +1378404551; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 146798.4; 0.0; 13.0; 0.06666666666666667; 0.4666666666666667 +1378404851; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 178954.66666666666; 0.0; 7.066666666666666; 0.26666666666666666; 0.13333333333333333 +1378405151; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 163575.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1378405451; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 116041.06666666667; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378405750; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 138411.2; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1378406050; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 100661.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1378406350; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 124429.86666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1378406650; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 118837.33333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1378406950; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 100661.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1378407250; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 104856.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1378407550; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 96467.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1378407850; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 113244.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1378408150; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 164974.13333333333; 0.0; 2.3333333333333335; 0.0; 0.4666666666666667 +1378408450; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 146799.2; 0.0; 1.3333333333333333; 0.0; 0.0 +1378408751; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 109050.4; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378409051; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 79689.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1378409351; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 109050.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1378409651; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 89476.53333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1378409951; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 100661.6; 0.0; 1.1333333333333333; 0.0; 0.0 +1378410251; 1; 2599.998989; 51.999979780000004; 2.0; 2097152.0; 591395.2; 161.73333333333332; 14.2; 0.0; 0.2 +1378410551; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 272627.2; 0.0; 1.5333333333333334; 0.0; 0.0 +1378410851; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 159383.2; 31.8; 3.533333333333333; 0.0; 0.0 +1378411151; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 127226.13333333333; 0.0; 7.266666666666667; 0.26666666666666666; 0.13333333333333333 +1378411451; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 125828.0; 0.0; 0.8666666666666667; 4.466666666666667; 0.0 +1378411751; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 159382.4; 0.06666666666666667; 2.533333333333333; 0.06666666666666667; 0.4666666666666667 +1378412051; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 159382.4; 0.0; 0.8; 0.0; 0.0 +1378412351; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 125828.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1378412651; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1378412951; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 123031.73333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1378413251; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 159382.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1378413551; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 88078.4; 0.0; 0.9333333333333333; 0.13333333333333333; 0.0 +1378413851; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 130022.4; 0.0; 1.1333333333333333; 0.0; 0.0 +1378414151; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 124429.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378414451; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 113244.0; 0.0; 0.6666666666666666; 0.0; 0.0 +1378414751; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 109050.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1378415051; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 114642.93333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378415351; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 123031.73333333334; 0.13333333333333333; 2.8666666666666667; 0.06666666666666667; 0.4666666666666667 +1378415651; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 134216.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1378415951; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 120235.46666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1378416251; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 125827.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1378416551; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 103457.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378416851; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 125827.2; 0.0; 1.1333333333333333; 0.0; 0.0 +1378417152; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 85282.13333333333; 0.0; 1.2666666666666666; 0.0; 0.0 +1378417452; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 120235.46666666666; 0.0; 1.0; 0.0; 0.0 +1378417752; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 78291.46666666666; 0.0; 7.333333333333333; 0.26666666666666666; 0.2 +1378418052; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 113244.8; 0.0; 1.1333333333333333; 0.0; 0.0 +1378418352; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 71300.8; 0.0; 1.1333333333333333; 0.0; 0.0 +1378418652; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 88078.4; 0.0; 1.0; 0.0; 0.0 +1378418952; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 127226.13333333333; 0.06666666666666667; 2.1333333333333333; 0.0; 0.4666666666666667 +1378419252; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 138411.2; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378419552; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 118837.33333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1378419852; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 102059.73333333334; 0.0; 1.0; 0.0; 0.0 +1378420152; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 135614.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378420452; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 125827.2; 0.0; 1.6; 0.06666666666666667; 0.13333333333333333 +1378420752; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 106254.13333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1378421052; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 102059.73333333334; 0.0; 1.1333333333333333; 0.0; 0.0 +1378421352; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 121633.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1378421652; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 100661.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1378421952; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 74097.06666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378422252; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 96467.2; 0.0; 1.2; 0.0; 0.0 +1378422552; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 144002.93333333332; 0.06666666666666667; 2.6; 0.06666666666666667; 0.4666666666666667 +1378422852; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 135614.93333333332; 0.0; 1.0666666666666667; 0.0; 0.0 +1378423152; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 102059.73333333334; 0.0; 1.0; 0.0; 0.0 +1378423452; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 99263.46666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378423752; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 102059.73333333334; 0.0; 1.0666666666666667; 0.0; 0.0 +1378424052; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 134216.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1378424352; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 107652.26666666666; 0.0; 1.0; 0.0; 0.0 +1378424653; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 117439.2; 0.06666666666666667; 6.333333333333333; 0.2; 0.13333333333333333 +1378424953; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 132818.66666666666; 0.0; 2.1333333333333333; 0.0; 0.0 +1378425253; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 107652.26666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1378425553; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 83884.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1378425853; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 96467.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1378426153; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 110448.53333333334; 0.0; 2.3333333333333335; 0.06666666666666667; 0.4666666666666667 +1378426453; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 132817.86666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1378426753; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 90874.66666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1378427053; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 106254.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378427353; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 107652.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378427653; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 146799.2; 0.5333333333333333; 1.4; 0.0; 0.0 +1378427953; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 116041.06666666667; 0.0; 1.4; 0.0; 0.0 +1378428253; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1378428553; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 103457.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378428853; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 103457.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378429153; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 150994.4; 0.0; 1.0666666666666667; 0.0; 0.0 +1378429453; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 138410.4; 0.0; 1.0666666666666667; 0.0; 0.0 +1378429753; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 131420.53333333333; 0.0; 2.4; 0.06666666666666667; 0.4666666666666667 +1378430053; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 162178.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378430353; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 120235.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1378430653; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 117439.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1378430953; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 106254.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378431253; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 109050.4; 0.0; 6.933333333333334; 0.26666666666666666; 0.13333333333333333 +1378431553; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 106254.13333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1378431853; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 100661.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1378432153; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 104856.0; 0.0; 1.1333333333333333; 0.0; 0.0 +1378432453; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 110448.53333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1378432753; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 102059.73333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1378433054; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 97865.33333333333; 0.0; 0.8; 0.0; 0.0 +1378433354; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 118836.8; 0.0; 2.6; 0.06666666666666667; 0.5333333333333333 +1378433654; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 135614.66666666666; 0.0; 1.6666666666666667; 0.0; 0.0 +1378433954; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 167770.4; 0.0; 1.2; 0.0; 0.0 +1378434254; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 123030.93333333333; 0.0; 1.0; 0.0; 0.0 +1378434554; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 114642.93333333333; 0.0; 1.0; 0.0; 0.0 +1378434854; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 124429.86666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1378435154; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 102059.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1378435454; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 104856.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1378435754; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 107652.26666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378436054; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 132818.66666666666; 0.0; 1.2; 0.0; 0.0 +1378436354; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 134216.0; 0.0; 1.0; 0.0; 0.0 +1378436654; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 104856.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1378436954; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 157983.73333333334; 0.4; 2.1333333333333333; 0.06666666666666667; 0.4666666666666667 +1378437254; 1; 2599.998989; 22.53332457133334; 0.8666666666666667; 2097152.0; 208316.0; 12.733333333333333; 13.2; 0.3333333333333333; 0.13333333333333333 +1378437554; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 139808.53333333333; 0.0; 1.2666666666666666; 0.0; 0.0 +1378437854; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 109050.4; 0.0; 1.0; 0.0; 0.0 +1378438154; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 103457.86666666667; 0.0; 0.8; 0.0; 0.0 +1378438454; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 96467.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1378438754; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 109050.4; 0.0; 1.0666666666666667; 0.0; 0.0 +1378439053; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 81087.73333333334; 0.0; 1.1333333333333333; 0.0; 0.0 +1378439354; 1; 2599.998989; 69.33330637333334; 2.666666666666667; 2097152.0; 310376.8; 161.0; 22.266666666666666; 0.06666666666666667; 0.3333333333333333 +1378439654; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 666892.2666666667; 0.0; 2.466666666666667; 0.0; 0.0 +1378439954; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 279618.4; 31.8; 3.533333333333333; 0.0; 0.0 +1378440254; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 194334.66666666666; 0.8; 2.533333333333333; 0.0; 0.0 +1378440554; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 187343.46666666667; 0.0; 3.3333333333333335; 0.06666666666666667; 0.5333333333333333 +1378440854; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 195732.8; 0.0; 1.0; 0.0; 0.0 +1378441154; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 124429.86666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1378441454; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 88078.4; 0.0; 1.0; 0.0; 0.0 +1378441754; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 102059.73333333334; 0.0; 1.0; 0.0; 0.0 +1378442054; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 106254.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378442354; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 121632.8; 0.0; 1.0; 0.0; 0.0 +1378442654; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 102059.73333333334; 0.0; 1.2; 0.0; 0.0 +1378442954; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 113244.8; 0.0; 7.133333333333334; 0.2; 0.13333333333333333 +1378443254; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 153789.86666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1378443554; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 107652.26666666666; 0.0; 1.0; 0.0; 0.0 +1378443854; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 150993.33333333334; 0.0; 1.0; 0.0; 0.0 +1378444154; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 187343.73333333334; 0.06666666666666667; 2.7333333333333334; 0.06666666666666667; 0.5333333333333333 +1378444454; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 159382.13333333333; 0.0; 1.0; 0.0; 0.0 +1378444754; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 156586.13333333333; 0.0; 1.0; 0.0; 0.0 +1378445054; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 127226.13333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1378445354; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 103457.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378445654; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 127226.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378445954; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 107652.26666666666; 0.0; 1.2; 0.0; 0.0 +1378446254; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 114642.93333333333; 0.0; 1.2666666666666666; 0.0; 0.0 +1378446554; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 130022.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1378446855; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 142605.6; 0.0; 1.0; 0.0; 0.0 +1378447155; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 85282.13333333333; 0.0; 1.2; 0.0; 0.0 +1378447455; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 109050.4; 0.0; 1.1333333333333333; 0.0; 0.0 +1378447755; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 102059.73333333334; 0.06666666666666667; 3.466666666666667; 0.06666666666666667; 0.4666666666666667 +1378448055; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 144003.73333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1378448355; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 132817.86666666667; 0.0; 11.933333333333334; 0.0; 0.0 +1378448655; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 113244.8; 0.0; 1.2; 0.0; 0.0 +1378448955; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 89476.53333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1378449255; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 120235.46666666666; 0.0; 2.3333333333333335; 0.06666666666666667; 0.13333333333333333 +1378449555; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 164974.93333333332; 0.0; 1.7333333333333334; 0.0; 0.0 +1378449855; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 150993.33333333334; 0.0; 6.666666666666667; 0.2; 0.13333333333333333 +1378450155; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 117439.2; 0.0; 1.8666666666666667; 0.0; 0.0 +1378450455; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 109050.4; 0.0; 1.2; 0.0; 0.0 +1378450755; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 132818.66666666666; 0.0; 1.0; 0.0; 0.0 +1378451055; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 137012.53333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378451355; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 135614.66666666666; 0.0; 2.6; 0.06666666666666667; 0.5333333333333333 +1378451655; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 142604.8; 0.0; 1.0666666666666667; 0.0; 0.0 +1378451955; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 127226.13333333333; 0.0; 1.0; 0.06666666666666667; 0.0 +1378452255; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 90874.66666666667; 0.0; 1.0; 0.0; 0.0 +1378452555; 1; 2599.998989; 0.0; 0.0; 2097152.0; 130022.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1378452855; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 127225.06666666667; 0.0; 1.2; 0.0; 0.0 +1378453155; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 96467.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1378453455; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 97865.33333333333; 0.0; 1.2; 0.0; 0.0 +1378453755; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 135614.93333333332; 0.0; 1.1333333333333333; 0.0; 0.0 +1378454055; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 138411.2; 0.0; 1.3333333333333333; 0.0; 0.0 +1378454355; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 100661.6; 0.0; 1.0; 0.0; 0.0 +1378454655; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1378454955; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 113244.8; 0.0; 2.066666666666667; 0.06666666666666667; 0.4666666666666667 +1378455255; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 127226.13333333333; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378455555; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 120235.46666666666; 0.0; 1.1333333333333333; 0.0; 0.0 +1378455855; 1; 2599.998989; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1378456155; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 131420.53333333333; 0.0; 6.866666666666666; 0.26666666666666666; 0.13333333333333333 +1378456456; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 92272.8; 0.0; 0.8; 0.0; 0.0 +1378456756; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 102059.73333333334; 0.0; 1.0666666666666667; 0.0; 0.0 +1378457056; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 132818.66666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1378457356; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 111846.66666666667; 0.0; 0.8; 0.0; 0.0 +1378457656; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 138410.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378457956; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 107652.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378458256; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 138411.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1378458556; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 155186.93333333332; 0.06666666666666667; 2.4; 0.06666666666666667; 0.5333333333333333 +1378458856; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 157983.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1378459156; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 127225.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378459456; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 135614.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1378459756; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 109050.4; 0.0; 1.0666666666666667; 0.0; 0.0 +1378460056; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 88078.4; 0.0; 0.8; 0.0; 0.0 +1378460356; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 120235.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378460656; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 114642.93333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378460956; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 117439.2; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1378461256; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 116041.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378461556; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 99263.46666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378461856; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 88078.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1378462156; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 132818.4; 0.06666666666666667; 2.2; 0.06666666666666667; 0.5333333333333333 +1378462456; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 152391.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1378462756; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1378463056; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 99263.46666666666; 0.0; 6.933333333333334; 0.2; 0.13333333333333333 +1378463356; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 123031.73333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1378463657; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 103457.86666666667; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378463957; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 102059.73333333334; 0.0; 1.2666666666666666; 0.13333333333333333; 0.0 +1378464257; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 103457.86666666667; 0.0; 0.8; 0.06666666666666667; 0.0 +1378464557; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 83884.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1378464857; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 109050.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1378465157; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 95069.06666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1378465457; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 127226.13333333333; 0.0; 0.8; 0.13333333333333333; 0.0 +1378465757; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 142605.33333333334; 0.0; 2.1333333333333333; 0.0; 0.4666666666666667 +1378466057; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 176159.46666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378466357; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 135614.93333333332; 0.0; 1.0666666666666667; 0.0; 0.0 +1378466657; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 110448.53333333334; 0.0; 1.0666666666666667; 0.0; 0.0 +1378466957; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 123031.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1378467257; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 81087.73333333334; 0.0; 0.8666666666666667; 0.13333333333333333; 0.0 +1378467557; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 125827.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1378467857; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 114642.93333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1378468157; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 109050.4; 0.0; 1.3333333333333333; 0.0; 0.0 +1378468457; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 116041.06666666667; 0.0; 7.0; 0.2; 0.13333333333333333 +1378468757; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 120235.46666666666; 0.0; 1.0; 0.0; 0.0 +1378469057; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 110448.53333333334; 0.0; 1.1333333333333333; 0.0; 0.0 +1378469657; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 180352.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378470257; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 111846.66666666667; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378470557; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 102059.73333333334; 0.0; 0.9333333333333333; 0.2; 0.0 +1378470857; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 102059.73333333334; 0.0; 1.2; 0.06666666666666667; 0.0 +1378471157; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 92272.8; 0.0; 1.2; 0.06666666666666667; 0.0 +1378472057; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 95069.06666666667; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1378472657; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 118837.33333333333; 0.0; 1.2666666666666666; 0.13333333333333333; 0.0 +1378472957; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 137012.0; 0.06666666666666667; 2.6666666666666665; 0.13333333333333333; 0.4666666666666667 +1378473257; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 130021.06666666667; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1378473557; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 79689.6; 0.0; 1.2666666666666666; 0.2; 0.0 +1378473857; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 120235.46666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378474157; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 132818.66666666666; 0.0; 0.8666666666666667; 0.2; 0.0 +1378474457; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 97865.33333333333; 0.0; 1.0; 0.26666666666666666; 0.0 +1378474757; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 89476.53333333334; 0.0; 1.2; 0.13333333333333333; 0.0 +1378475057; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 89476.53333333334; 0.0; 1.0; 0.0; 0.0 +1378475358; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 99263.46666666666; 0.0; 7.266666666666667; 0.26666666666666666; 0.13333333333333333 +1378475658; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 88078.4; 0.0; 0.8666666666666667; 0.2; 0.0 +1378475958; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 88078.4; 0.0; 1.0; 0.13333333333333333; 0.0 +1378476258; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 121633.6; 0.0; 1.1333333333333333; 0.0; 0.0 +1378476558; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 160779.46666666667; 0.06666666666666667; 2.533333333333333; 0.2; 0.5333333333333333 +1378476858; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 162178.93333333332; 0.0; 0.9333333333333333; 0.0; 0.0 +1378477158; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 106254.13333333333; 0.0; 0.9333333333333333; 0.13333333333333333; 0.0 +1378477458; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 89476.53333333334; 0.0; 1.0; 0.06666666666666667; 0.0 +1378477758; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 124429.86666666667; 0.0; 1.1333333333333333; 0.13333333333333333; 0.0 +1378478058; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 131419.73333333334; 0.06666666666666667; 1.2; 0.06666666666666667; 0.13333333333333333 +1378478358; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 92272.8; 0.0; 1.2; 0.0; 0.0 +1378478658; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.2; 0.0 +1378478958; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 106253.33333333333; 0.0; 1.2; 0.06666666666666667; 0.0 +1378479258; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 88078.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1378479558; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 110448.53333333334; 0.0; 0.9333333333333333; 1.2; 0.0 +1378479858; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 114642.93333333333; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378480158; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 128623.2; 0.0; 2.2; 0.26666666666666666; 0.5333333333333333 +1378480458; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 118836.8; 0.0; 1.1333333333333333; 0.13333333333333333; 0.0 +1378480758; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 74097.06666666667; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378481058; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 124429.86666666667; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378481358; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 118837.33333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378481658; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 117439.2; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1378481958; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 97865.33333333333; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1378482258; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 93670.93333333333; 0.0; 2.466666666666667; 0.26666666666666666; 0.13333333333333333 +1378482558; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 142604.8; 0.0; 5.933333333333334; 0.0; 0.0 +1378482858; 1; 2599.998989; 0.0; 0.0; 2097152.0; 135614.93333333332; 0.0; 0.8666666666666667; 0.0; 0.0 +1378483158; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 103457.86666666667; 0.0; 1.0; 0.0; 0.0 +1378483459; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 116041.06666666667; 0.0; 0.8; 0.0; 0.0 +1378483759; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 113244.8; 0.06666666666666667; 2.466666666666667; 0.06666666666666667; 0.4666666666666667 +1378484059; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 110448.53333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1378484359; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 97865.33333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1378484659; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 109050.4; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1378484959; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 88078.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1378485259; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 130022.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1378485559; 1; 2599.998989; 64.13330839533334; 2.466666666666667; 2097152.0; 350922.4; 161.06666666666666; 22.0; 2.1333333333333333; 0.4 +1378485859; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 706038.4; 0.0; 2.8; 0.0; 0.0 +1378486159; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 290803.2; 31.8; 3.7333333333333334; 0.0; 0.0 +1378486459; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 198527.73333333334; 0.26666666666666666; 2.2666666666666666; 0.0; 0.0 +1378486759; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 124429.86666666667; 0.0; 1.6; 0.13333333333333333; 0.0 +1378487059; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 76893.33333333333; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378487359; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 128623.46666666666; 0.0; 2.2666666666666666; 0.06666666666666667; 0.4666666666666667 +1378487659; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 156586.4; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378487959; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 135613.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378488259; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 171964.8; 0.0; 6.866666666666666; 0.5333333333333333; 0.13333333333333333 +1378488559; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 131420.53333333333; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378488859; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 106254.13333333333; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1378489159; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 100661.6; 0.0; 1.0; 0.0; 0.0 +1378489459; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 120235.46666666666; 0.0; 1.0; 0.0; 0.0 +1378489759; 1; 2599.998989; 0.0; 0.0; 2097152.0; 163576.53333333333; 0.0; 0.9333333333333333; 0.2; 0.0 +1378490059; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 152390.66666666666; 0.0; 1.0; 0.2; 0.0 +1378490359; 1; 2599.998989; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.8; 0.13333333333333333; 0.0 +1378490659; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 114642.93333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378490959; 1; 2599.998989; 20.799991912; 0.8; 2097152.0; 118837.06666666667; 0.0; 2.2666666666666666; 0.06666666666666667; 0.5333333333333333 +1378491259; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 137012.53333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378491559; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 100661.6; 0.0; 1.2; 0.0; 0.0 +1378491859; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 92272.8; 0.0; 11.8; 0.0; 0.0 +1378492159; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 124429.06666666667; 0.0; 0.8; 0.0; 0.0 +1378492459; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 123031.73333333334; 0.0; 1.6666666666666667; 0.0; 0.0 +1378492759; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 100661.6; 0.0; 1.3333333333333333; 0.0; 0.0 +1378493059; 1; 2599.998989; 0.0; 0.0; 2097152.0; 68504.53333333334; 0.0; 1.0; 0.0; 0.0 +1378493360; 1; 2599.998989; 0.0; 0.0; 2097152.0; 106254.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378493660; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 128623.46666666666; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1378493960; 1; 2599.998989; 0.0; 0.0; 2097152.0; 125827.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1378494260; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 104856.0; 0.0; 7.4; 0.26666666666666666; 0.13333333333333333 +1378494560; 1; 2599.998989; 24.266657230666667; 0.9333333333333332; 2097152.0; 183149.6; 0.06666666666666667; 2.4; 0.0; 0.5333333333333333 +1378494860; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 164974.4; 0.0; 1.7333333333333334; 0.0; 0.0 +1378495160; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 99263.46666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378495460; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 104856.0; 0.0; 1.2; 0.0; 0.0 +1378495760; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 104856.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1378496060; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 116041.06666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378496360; 1; 2599.998989; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378496660; 1; 2599.998989; 41.599983824; 1.6; 2097152.0; 450186.93333333335; 161.8; 14.733333333333333; 0.0; 0.2 +1378496960; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 205519.46666666667; 0.0; 1.6; 0.0; 0.0 +1378497260; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 194333.33333333334; 31.8; 3.6; 0.0; 0.0 +1378497560; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 107652.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378497860; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 97865.33333333333; 0.0; 1.0; 0.0; 0.0 +1378498160; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 127225.06666666667; 0.0; 2.533333333333333; 0.0; 0.5333333333333333 +1378498460; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 156585.6; 0.0; 1.0; 0.0; 0.0 +1378498760; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 138411.2; 0.0; 1.0; 0.0; 0.0 +1378499060; 1; 2599.998989; 0.0; 0.0; 2097152.0; 106254.13333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1378499360; 1; 2599.998989; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378499660; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 162177.86666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1378499960; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 124429.86666666667; 12.733333333333333; 12.866666666666667; 0.4; 0.13333333333333333 +1378500260; 1; 2599.998989; 0.0; 0.0; 2097152.0; 121632.8; 0.0; 1.3333333333333333; 0.0; 0.0 +1378500560; 1; 2599.998989; 0.0; 0.0; 2097152.0; 82485.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378500860; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 83884.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1378501160; 1; 2599.998989; 0.0; 0.0; 2097152.0; 110448.53333333334; 0.0; 1.2666666666666666; 0.0; 0.0 +1378501460; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 104856.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1378501760; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 156585.86666666667; 0.0; 2.3333333333333335; 0.06666666666666667; 0.5333333333333333 +1378502060; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 208314.66666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378502360; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 139809.33333333334; 0.0; 1.0; 0.0; 0.0 +1378502660; 1; 2599.998989; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378502960; 1; 2599.998989; 0.0; 0.0; 2097152.0; 124429.86666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1378503260; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 109050.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1378503560; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 111846.66666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378503860; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 99263.46666666666; 0.0; 1.1333333333333333; 0.0; 0.0 +1378504160; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 106253.86666666667; 0.0; 0.8; 0.0; 0.0 +1378504460; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 124429.33333333333; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378504760; 1; 2599.998989; 0.0; 0.0; 2097152.0; 81087.73333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1378505060; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 117438.4; 0.0; 1.0; 0.0; 0.0 +1378505360; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 170566.4; 0.06666666666666667; 2.6; 0.06666666666666667; 0.4666666666666667 +1378505660; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 183149.33333333334; 0.0; 1.0666666666666667; 0.13333333333333333; 0.0 +1378505960; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 123031.73333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1378506260; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 128624.0; 0.06666666666666667; 7.0; 0.3333333333333333; 0.13333333333333333 +1378506560; 1; 2599.998989; 0.0; 0.0; 2097152.0; 127225.6; 0.0; 1.0666666666666667; 0.13333333333333333; 0.0 +1378506860; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 134216.0; 0.06666666666666667; 1.5333333333333334; 0.4666666666666667; 0.06666666666666667 +1378507160; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 132818.66666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378507460; 1; 2599.998989; 0.0; 0.0; 2097152.0; 118837.33333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1378507761; 1; 2599.998989; 0.0; 0.0; 2097152.0; 121633.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1378508061; 1; 2599.998989; 0.0; 0.0; 2097152.0; 118837.33333333333; 0.0; 1.2; 0.0; 0.0 +1378508361; 1; 2599.998989; 0.0; 0.0; 2097152.0; 125828.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1378508661; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 93670.93333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1378508961; 1; 2599.998989; 24.266657230666667; 0.9333333333333332; 2097152.0; 146799.2; 0.06666666666666667; 2.2; 0.0; 0.4666666666666667 +1378509261; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 134216.8; 0.0; 1.1333333333333333; 0.0; 0.0 +1378509561; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 148198.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378509861; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 85282.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378510161; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 121633.6; 0.0; 1.0; 0.0; 0.0 +1378510461; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 99263.46666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378510761; 1; 2599.998989; 20.799991912; 0.8; 2097152.0; 75495.2; 0.0; 1.1333333333333333; 0.0; 0.0 +1378511061; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 96467.2; 0.0; 1.2; 0.0; 0.0 +1378511361; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 92272.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1378511661; 1; 2599.998989; 20.799991912; 0.8; 2097152.0; 86680.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378511961; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 88078.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1378512261; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 138409.6; 0.0; 7.133333333333334; 0.2; 0.13333333333333333 +1378512561; 1; 2599.998989; 24.266657230666667; 0.9333333333333332; 2097152.0; 138410.4; 0.0; 2.466666666666667; 0.0; 0.4666666666666667 +1378512861; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 132818.66666666666; 0.0; 1.0; 0.0; 0.0 +1378513161; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 107652.26666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378513461; 1; 2599.998989; 20.799991912; 0.8; 2097152.0; 93670.93333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1378513761; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 139809.33333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1378514061; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 124429.06666666667; 0.5333333333333333; 1.3333333333333333; 0.13333333333333333; 0.0 +1378514361; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 144003.73333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1378514661; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 110448.53333333334; 0.0; 1.0; 0.4666666666666667; 0.0 +1378514961; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 124429.86666666667; 0.0; 1.2666666666666666; 0.0; 0.0 +1378515261; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 111846.66666666667; 0.0; 0.8; 0.0; 0.0 +1378515561; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 55921.333333333336; 0.0; 0.9333333333333333; 0.0; 0.0 +1378515861; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 92272.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1378516161; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 146798.93333333332; 0.0; 2.6; 0.0; 0.5333333333333333 +1378516461; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 167770.66666666666; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1378516761; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 135613.86666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1378517061; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 71300.8; 0.0; 1.0; 0.0; 0.0 +1378517361; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 72698.93333333333; 0.0; 1.0; 0.0; 0.0 +1378517661; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 107652.26666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1378517961; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 95069.06666666667; 0.0; 1.2; 0.0; 0.0 +1378518261; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 78291.46666666666; 0.2; 6.4; 0.2; 0.13333333333333333 +1378518561; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 163577.06666666668; 0.0; 1.4666666666666666; 0.0; 0.0 +1378518861; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 111846.66666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378519161; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 118837.33333333333; 0.0; 1.2; 0.06666666666666667; 0.0 +1378519461; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 109050.4; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1378519761; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 145401.06666666668; 0.0; 2.2666666666666666; 0.0; 0.4666666666666667 +1378520061; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 159381.06666666668; 0.0; 0.8; 0.0; 0.0 +1378520361; 1; 2599.998989; 0.0; 0.0; 2097152.0; 127226.13333333333; 0.0; 1.2; 0.0; 0.0 +1378520662; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 89476.53333333334; 0.0; 1.0666666666666667; 0.0; 0.0 +1378520962; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 148197.33333333334; 0.0; 0.9333333333333333; 10.266666666666667; 0.0 +1378521262; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 123031.73333333334; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378521562; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 106254.13333333333; 0.0; 1.2; 0.0; 0.0 +1378521862; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 83884.0; 0.0; 1.2666666666666666; 0.0; 0.0 +1378522162; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 92272.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1378522462; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 83884.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1378522762; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 88078.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1378523062; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 103457.86666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1378523362; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 124428.8; 0.0; 2.533333333333333; 0.0; 0.4666666666666667 +1378523662; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 155187.2; 0.0; 1.0; 0.0; 0.0 +1378523962; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 96467.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1378524262; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 79689.6; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378524562; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 104856.0; 0.0; 1.0666666666666667; 8.2; 0.0 +1378524862; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1378525162; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 120235.46666666666; 0.0; 6.733333333333333; 0.26666666666666666; 0.13333333333333333 +1378525462; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 65708.26666666666; 0.0; 1.4; 0.0; 0.0 +1378525762; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 139809.33333333334; 0.0; 1.0; 0.06666666666666667; 0.0 +1378526062; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 107652.26666666666; 0.0; 1.2666666666666666; 0.06666666666666667; 0.0 +1378526362; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 104856.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1378526662; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 74097.06666666667; 0.0; 1.0; 0.06666666666666667; 0.0 +1378527862; 1; 2599.998989; 16.714279215; 0.6428571428571429; 2097152.0; 107852.0; 0.0; 1.0714285714285714; 0.07692307692307693; 0.0 +1378528762; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 109050.4; 0.0; 1.0666666666666667; 0.4; 0.0 +1378529062; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 96467.2; 0.0; 1.0; 5.066666666666666; 0.0 +1378529362; 1; 2599.998989; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 0.8; 0.0; 0.0 +1378529662; 1; 2599.998989; 0.0; 0.0; 2097152.0; 125828.0; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1378529963; 1; 2599.998989; 0.0; 0.0; 2097152.0; 117438.4; 0.0; 0.6666666666666666; 0.0; 0.0 +1378530263; 1; 2599.998989; 0.0; 0.0; 2097152.0; 118837.33333333333; 0.0; 1.0; 0.06666666666666667; 0.0 +1378530563; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 135614.93333333332; 0.06666666666666667; 8.333333333333334; 0.26666666666666666; 0.6 +1378530863; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 159381.6; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378531163; 1; 2599.998989; 0.0; 0.0; 2097152.0; 138410.4; 0.0; 0.8; 0.0; 0.0 +1378531463; 1; 2599.998989; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1378531763; 1; 2599.998989; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.8; 0.0; 0.0 +1378532063; 1; 2599.998989; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.7333333333333333; 0.13333333333333333; 0.0 +1378532363; 1; 2599.998989; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1378532663; 1; 2599.998989; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.8; 0.06666666666666667; 0.0 +1378532963; 1; 2599.998989; 0.0; 0.0; 2097152.0; 120235.46666666666; 0.0; 1.1333333333333333; 0.0; 0.0 +1378533263; 1; 2599.998989; 60.66664307666668; 2.3333333333333335; 2097152.0; 406845.3333333333; 161.06666666666666; 21.533333333333335; 0.06666666666666667; 0.4 +1378533563; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 562034.4; 0.0; 2.533333333333333; 0.06666666666666667; 0.0 +1378533863; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 293599.2; 31.8; 4.333333333333333; 0.06666666666666667; 0.0 +1378534163; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 251656.0; 0.8; 3.933333333333333; 0.06666666666666667; 0.4666666666666667 +1378534463; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 167770.4; 0.0; 1.5333333333333334; 0.0; 0.0 +1378534763; 1; 2599.998989; 0.0; 0.0; 2097152.0; 144002.93333333332; 0.0; 0.9333333333333333; 0.0; 0.0 +1378535063; 1; 2599.998989; 0.0; 0.0; 2097152.0; 125828.0; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1378535363; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 100661.6; 0.0; 0.8; 0.0; 0.0 +1378535663; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 113244.8; 0.0; 13.4; 0.13333333333333333; 0.13333333333333333 +1378535963; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 134215.2; 0.0; 1.4666666666666666; 0.13333333333333333; 0.0 +1378536263; 1; 2599.998989; 0.0; 0.0; 2097152.0; 121632.8; 0.0; 0.7333333333333333; 0.13333333333333333; 0.0 +1378536563; 1; 2599.998989; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1378536863; 1; 2599.998989; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 1.5333333333333334; 0.0; 0.0 +1378537163; 1; 2599.998989; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1378537463; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 121633.6; 0.0; 7.0; 0.2; 0.13333333333333333 +1378537763; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 139808.53333333333; 0.0; 2.3333333333333335; 0.0; 0.4666666666666667 +1378538063; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 125827.2; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378538363; 1; 2599.998989; 0.0; 0.0; 2097152.0; 159383.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1378538663; 1; 2599.998989; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1378538963; 1; 2599.998989; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 1.0; 0.0; 0.0 +1378539263; 1; 2599.998989; 0.0; 0.0; 2097152.0; 130021.6; 0.0; 0.8666666666666667; 0.13333333333333333; 0.0 +1378539563; 1; 2599.998989; 0.0; 0.0; 2097152.0; 142605.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1378539863; 1; 2599.998989; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 2.066666666666667; 0.06666666666666667; 0.0 +1378540163; 1; 2599.998989; 0.0; 0.0; 2097152.0; 85282.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378540463; 1; 2599.998989; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1378540763; 1; 2599.998989; 0.0; 0.0; 2097152.0; 79689.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1378541063; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 118837.33333333333; 0.06666666666666667; 1.8666666666666667; 0.0; 0.0 +1378541363; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 125827.2; 0.13333333333333333; 2.533333333333333; 0.0; 0.4666666666666667 +1378541663; 1; 2599.998989; 0.0; 0.0; 2097152.0; 148197.33333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1378541963; 1; 2599.998989; 0.0; 0.0; 2097152.0; 138410.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1378542263; 1; 2599.998989; 0.0; 0.0; 2097152.0; 83884.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1378542563; 1; 2599.998989; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 1.1333333333333333; 0.0; 0.0 +1378542863; 1; 2599.998989; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378543163; 1; 2599.998989; 0.0; 0.0; 2097152.0; 86680.26666666666; 0.0; 1.0; 1.8666666666666667; 0.0 +1378543463; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 134216.8; 0.06666666666666667; 7.066666666666666; 0.26666666666666666; 0.13333333333333333 +1378543763; 1; 2599.998989; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1378544063; 1; 2599.998989; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1378544364; 1; 2599.998989; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1378544664; 1; 2599.998989; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 1.0666666666666667; 0.0; 0.0 +1378544964; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 146799.2; 0.0; 2.466666666666667; 0.0; 0.4666666666666667 +1378545264; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 149596.26666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1378545564; 1; 2599.998989; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1378545864; 1; 2599.998989; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 1.0; 0.0; 0.0 +1378546164; 1; 2599.998989; 0.0; 0.0; 2097152.0; 121633.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1378546464; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 121633.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1378546764; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 146798.93333333332; 0.0; 1.4; 0.0; 0.0 +1378547064; 1; 2599.998989; 0.0; 0.0; 2097152.0; 124429.86666666667; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1378547364; 1; 2599.998989; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378547664; 1; 2599.998989; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378547964; 1; 2599.998989; 0.0; 0.0; 2097152.0; 83884.0; 0.0; 1.0; 0.0; 0.0 +1378548264; 1; 2599.998989; 0.0; 0.0; 2097152.0; 137012.53333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1378548564; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 125828.0; 0.0; 2.466666666666667; 0.06666666666666667; 0.4666666666666667 +1378548864; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 134216.8; 0.0; 1.0; 0.0; 0.0 +1378549164; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 82485.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378549464; 1; 2599.998989; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 1.2; 0.06666666666666667; 0.0 +1378549764; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 138410.4; 0.0; 7.266666666666667; 0.2; 0.13333333333333333 +1378550064; 1; 2599.998989; 0.0; 0.0; 2097152.0; 131419.73333333334; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378550364; 1; 2599.998989; 0.0; 0.0; 2097152.0; 142605.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1378550664; 1; 2599.998989; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 1.0666666666666667; 0.0; 0.0 +1378550964; 1; 2599.998989; 0.0; 0.0; 2097152.0; 114642.93333333333; 0.0; 1.2666666666666666; 0.06666666666666667; 0.0 +1378551264; 1; 2599.998989; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1378551564; 1; 2599.998989; 0.0; 0.0; 2097152.0; 78291.46666666666; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378551864; 1; 2599.998989; 0.0; 0.0; 2097152.0; 89476.53333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1378552164; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 138409.86666666667; 0.0; 2.6; 0.06666666666666667; 0.4666666666666667 +1378552464; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 213907.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1378552764; 1; 2599.998989; 0.0; 0.0; 2097152.0; 155187.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1378553064; 1; 2599.998989; 0.0; 0.0; 2097152.0; 86680.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378553364; 1; 2599.998989; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 0.8666666666666667; 0.26666666666666666; 0.0 +1378553664; 1; 2599.998989; 0.0; 0.0; 2097152.0; 117438.4; 0.0; 1.2666666666666666; 0.06666666666666667; 0.0 +1378553964; 1; 2599.998989; 0.0; 0.0; 2097152.0; 125828.0; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378554264; 1; 2599.998989; 0.0; 0.0; 2097152.0; 120235.46666666666; 0.0; 1.1333333333333333; 0.0; 0.0 +1378554564; 1; 2599.998989; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 1.0; 0.06666666666666667; 0.0 +1378554865; 1; 2599.998989; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378555165; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 110448.53333333334; 0.0; 1.0666666666666667; 0.0; 0.0 +1378555465; 1; 2599.998989; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378555765; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 123031.2; 0.06666666666666667; 8.733333333333333; 0.26666666666666666; 0.6 +1378556065; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 124429.6; 0.0; 0.9333333333333333; 0.13333333333333333; 0.0 +1378556365; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 110448.53333333334; 0.0; 1.2; 0.0; 0.0 +1378556665; 1; 2599.998989; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378556965; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 123031.73333333334; 0.0; 1.0; 4.2; 0.0 +1378557265; 1; 2599.998989; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1378557565; 1; 2599.998989; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 0.8; 0.0; 0.0 +1378557865; 1; 2599.998989; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1378558165; 1; 2599.998989; 0.0; 0.0; 2097152.0; 132818.66666666666; 0.0; 1.0; 0.06666666666666667; 0.0 +1378558465; 1; 2599.998989; 0.0; 0.0; 2097152.0; 160779.46666666667; 0.0; 0.8; 0.0; 0.0 +1378558765; 1; 2599.998989; 0.0; 0.0; 2097152.0; 145401.06666666668; 0.0; 1.0; 0.0; 0.0 +1378559065; 1; 2599.998989; 0.0; 0.0; 2097152.0; 131419.73333333334; 0.0; 1.0; 0.0; 0.0 +1378559365; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 116040.53333333334; 0.0; 2.4; 0.0; 0.4666666666666667 +1378559665; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 148197.06666666668; 0.0; 0.8666666666666667; 0.0; 0.0 +1378559965; 1; 2599.998989; 0.0; 0.0; 2097152.0; 135614.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378560265; 1; 2599.998989; 0.0; 0.0; 2097152.0; 134216.8; 0.0; 0.8; 0.0; 0.0 +1378560565; 1; 2599.998989; 0.0; 0.0; 2097152.0; 86680.26666666666; 0.0; 1.2666666666666666; 0.0; 0.0 +1378560865; 1; 2599.998989; 0.0; 0.0; 2097152.0; 132818.66666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378561165; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 146800.0; 12.733333333333333; 13.133333333333333; 0.3333333333333333; 0.2 +1378561465; 1; 2599.998989; 0.0; 0.0; 2097152.0; 156585.33333333334; 0.0; 1.3333333333333333; 0.0; 0.0 +1378561765; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 104856.0; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378562065; 1; 2599.998989; 0.0; 0.0; 2097152.0; 71300.8; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1378562366; 1; 2599.998989; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378562666; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 118836.53333333334; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378562966; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 121633.6; 0.0; 2.466666666666667; 0.13333333333333333; 0.4666666666666667 +1378563266; 1; 2599.998989; 0.0; 0.0; 2097152.0; 124429.86666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1378563566; 1; 2599.998989; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1378563866; 1; 2599.998989; 0.0; 0.0; 2097152.0; 120235.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378564166; 1; 2599.998989; 0.0; 0.0; 2097152.0; 141207.46666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378564466; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 132817.86666666667; 0.0; 1.9333333333333333; 0.06666666666666667; 0.13333333333333333 +1378564766; 1; 2599.998989; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1378565066; 1; 2599.998989; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1378565366; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 104856.0; 0.0; 1.0; 0.0; 0.0 +1378565666; 1; 2599.998989; 0.0; 0.0; 2097152.0; 102059.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1378565966; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 104855.73333333334; 0.0; 1.1333333333333333; 0.0; 0.0 +1378566266; 1; 2599.998989; 0.0; 0.0; 2097152.0; 86680.26666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378566566; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 145401.06666666668; 0.06666666666666667; 8.266666666666667; 0.2; 0.6 +1378566866; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 173363.73333333334; 0.0; 1.0666666666666667; 0.0; 0.0 +1378567166; 1; 2599.998989; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 1.0; 0.0; 0.0 +1378567466; 1; 2599.998989; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 1.2; 0.0; 0.0 +1378567766; 1; 2599.998989; 0.0; 0.0; 2097152.0; 128624.26666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378568066; 1; 2599.998989; 0.0; 0.0; 2097152.0; 118837.33333333333; 0.0; 1.0; 0.0; 0.0 +1378568366; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 107652.26666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1378568666; 1; 2599.998989; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 1.1333333333333333; 0.0; 0.0 +1378568966; 1; 2599.998989; 0.0; 0.0; 2097152.0; 116041.06666666667; 0.0; 1.2; 0.0; 0.0 +1378569266; 1; 2599.998989; 0.0; 0.0; 2097152.0; 131420.53333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1378569566; 1; 2599.998989; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 1.1333333333333333; 0.0; 0.0 +1378569866; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 118837.33333333333; 0.0; 0.8; 0.0; 0.0 +1378570166; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 152391.2; 0.06666666666666667; 2.6666666666666665; 0.0; 0.4666666666666667 +1378570466; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 148197.6; 0.06666666666666667; 1.2; 0.0; 0.0 +1378570766; 1; 2599.998989; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 1.0; 0.0; 0.0 +1378571066; 1; 2599.998989; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8; 0.0; 0.0 +1378571366; 1; 2599.998989; 0.0; 0.0; 2097152.0; 95069.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378571666; 1; 2599.998989; 0.0; 0.0; 2097152.0; 71300.8; 0.0; 1.2; 0.13333333333333333; 0.0 +1378571966; 1; 2599.998989; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 1.2666666666666666; 0.06666666666666667; 0.0 +1378572266; 1; 2599.998989; 0.0; 0.0; 2097152.0; 69902.66666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378572566; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 93670.93333333333; 0.0; 7.133333333333334; 0.2; 0.13333333333333333 +1378572866; 1; 2599.998989; 0.0; 0.0; 2097152.0; 145401.06666666668; 0.0; 0.8666666666666667; 0.0; 0.0 +1378573166; 1; 2599.998989; 0.0; 0.0; 2097152.0; 81087.73333333334; 0.0; 1.2; 0.0; 0.0 +1378573466; 1; 2599.998989; 0.0; 0.0; 2097152.0; 82485.86666666667; 0.0; 1.0; 0.0; 0.0 +1378573766; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 141206.13333333333; 0.06666666666666667; 2.2666666666666666; 0.0; 0.4666666666666667 +1378574066; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 141206.4; 0.0; 1.0; 0.0; 0.0 +1378574367; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 103457.86666666667; 0.0; 1.4; 0.0; 0.0 +1378574667; 1; 2599.998989; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378574967; 1; 2599.998989; 0.0; 0.0; 2097152.0; 139809.33333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1378575267; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 134216.26666666666; 0.0; 1.0; 0.0; 0.0 +1378575567; 1; 2599.998989; 0.0; 0.0; 2097152.0; 107652.0; 0.0; 1.1333333333333333; 0.0; 0.0 +1378575867; 1; 2599.998989; 0.0; 0.0; 2097152.0; 124429.86666666667; 0.0; 1.2666666666666666; 0.0; 0.0 +1378576167; 1; 2599.998989; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378576467; 1; 2599.998989; 0.0; 0.0; 2097152.0; 71300.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1378576767; 1; 2599.998989; 0.0; 0.0; 2097152.0; 69902.66666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1378577067; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 82485.86666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1378577367; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 139808.53333333333; 0.0; 2.466666666666667; 0.06666666666666667; 0.4666666666666667 +1378577667; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 139809.33333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1378577967; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 124429.86666666667; 0.2; 7.0; 0.2; 0.13333333333333333 +1378578267; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 110448.53333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1378578567; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 111846.66666666667; 0.0; 1.2; 0.0; 0.0 +1378578867; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 116041.06666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378579167; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 107652.26666666666; 0.0; 11.933333333333334; 0.0; 0.0 +1378579467; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 102059.73333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1378579767; 1; 2599.998989; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1378580067; 1; 2599.998989; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1378580367; 1; 2599.998989; 0.0; 0.0; 2097152.0; 82485.86666666667; 0.0; 0.8; 0.0; 0.0 +1378580667; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 130022.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1378580967; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 128624.0; 0.0; 2.2666666666666666; 0.06666666666666667; 0.5333333333333333 +1378581267; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 170566.66666666666; 0.0; 1.8666666666666667; 0.0; 0.0 +1378581567; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 128624.26666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1378581867; 1; 2599.998989; 0.0; 0.0; 2097152.0; 131420.53333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378582167; 1; 2599.998989; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.7333333333333333; 0.0; 0.0 +1378582467; 1; 2599.998989; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.7333333333333333; 0.0; 0.0 +1378582767; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 134216.0; 0.0; 1.1333333333333333; 0.0; 0.0 +1378583067; 1; 2599.998989; 38.133318505333335; 1.4666666666666666; 2097152.0; 436206.4; 161.73333333333332; 14.8; 0.0; 0.2 +1378583368; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 304784.5333333333; 0.0; 1.2666666666666666; 0.0; 0.0 +1378583668; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 152390.93333333332; 31.8; 8.6; 0.2; 0.13333333333333333 +1378583968; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 188741.33333333334; 0.0; 2.3333333333333335; 0.0; 0.0 +1378584268; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 107652.26666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1378584568; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 128624.0; 0.0; 2.2666666666666666; 0.0; 0.4666666666666667 +1378584868; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 150993.06666666668; 0.0; 0.8; 0.0; 0.0 +1378585168; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 111846.66666666667; 0.0; 0.8; 0.06666666666666667; 0.0 +1378585468; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 95069.06666666667; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1378585768; 1; 2599.998989; 0.0; 0.0; 2097152.0; 114642.93333333333; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378586068; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 106254.13333333333; 0.0; 1.0; 0.06666666666666667; 0.0 +1378586368; 1; 2599.998989; 20.799991912; 0.8; 2097152.0; 67106.4; 0.0; 1.1333333333333333; 0.0; 0.0 +1378586668; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 107652.26666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1378586968; 1; 2599.998989; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 1.0; 0.0; 0.0 +1378587268; 1; 2599.998989; 0.0; 0.0; 2097152.0; 86680.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378587568; 1; 2599.998989; 0.0; 0.0; 2097152.0; 114642.93333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378587868; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 106254.13333333333; 0.0; 1.0; 0.0; 0.0 +1378588168; 1; 2599.998989; 25.999989890000002; 1.0; 2097152.0; 142604.53333333333; 0.0; 2.3333333333333335; 0.0; 0.5333333333333333 +1378588468; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 145401.33333333334; 0.0; 1.0666666666666667; 0.0; 0.0 +1378588768; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 128623.46666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1378589068; 1; 2599.998989; 0.0; 0.0; 2097152.0; 132818.66666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378589368; 1; 2599.998989; 0.0; 0.0; 2097152.0; 121633.6; 0.0; 0.8; 0.0; 0.0 +1378589668; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 139809.33333333334; 0.0; 1.0666666666666667; 0.0; 0.0 +1378589968; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 137012.8; 0.0; 7.133333333333334; 0.2; 0.13333333333333333 +1378590268; 1; 2599.998989; 67.599973714; 2.6; 2097152.0; 595589.6; 161.06666666666666; 22.133333333333333; 0.06666666666666667; 0.4 +1378590568; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 433409.6; 0.0; 2.4; 0.0; 0.0 +1378590868; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 239072.8; 31.8; 3.933333333333333; 0.0; 0.0 +1378591168; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 192936.26666666666; 0.8666666666666667; 2.533333333333333; 0.0; 0.0 +1378591468; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 132818.66666666666; 0.0; 1.5333333333333334; 0.0; 0.0 +1378591768; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 146799.2; 0.0; 2.3333333333333335; 0.06666666666666667; 0.4666666666666667 +1378592068; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 146800.0; 0.0; 0.6666666666666666; 0.0; 0.0 +1378592368; 1; 2599.998989; 0.0; 0.0; 2097152.0; 134216.0; 0.0; 1.2; 0.0; 0.0 +1378592668; 1; 2599.998989; 0.0; 0.0; 2097152.0; 116041.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378592968; 1; 2599.998989; 0.0; 0.0; 2097152.0; 116041.06666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378593269; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 134216.0; 0.0; 1.5333333333333334; 0.06666666666666667; 0.13333333333333333 +1378593569; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 114642.93333333333; 0.0; 1.0; 0.0; 0.0 +1378593869; 1; 2599.998989; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 1.0; 0.0; 0.0 +1378594169; 1; 2599.998989; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1378594469; 1; 2599.998989; 0.0; 0.0; 2097152.0; 124429.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378594769; 1; 2599.998989; 0.0; 0.0; 2097152.0; 132818.66666666666; 0.0; 0.8; 0.0; 0.0 +1378595069; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 137012.8; 0.0; 1.2; 0.0; 0.0 +1378595369; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 171964.8; 0.0; 2.3333333333333335; 0.0; 0.4666666666666667 +1378595669; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 195731.46666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1378595969; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 141207.46666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378596269; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 124429.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378596569; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 90874.66666666667; 0.0; 6.0; 0.2; 0.13333333333333333 +1378596869; 1; 2599.998989; 20.799991912; 0.8; 2097152.0; 127226.13333333333; 0.0; 2.2; 0.0; 0.0 +1378597169; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 120235.46666666666; 0.0; 1.0; 0.0; 0.0 +1378597469; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 117439.2; 0.0; 1.3333333333333333; 0.0; 0.0 +1378597769; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 130022.4; 0.0; 1.0; 0.0; 0.0 +1378598069; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 89476.53333333334; 0.0; 1.2; 0.0; 0.0 +1378598369; 1; 2599.998989; 20.799991912; 0.8; 2097152.0; 90874.66666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378598669; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 93670.93333333333; 0.0; 0.8; 0.0; 0.0 +1378598969; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 162176.8; 0.0; 2.1333333333333333; 0.0; 0.4666666666666667 +1378599269; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 153790.13333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1378599569; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 79689.6; 0.0; 0.6666666666666666; 0.0; 0.0 +1378599869; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 86680.26666666666; 0.0; 0.8; 0.0; 0.0 +1378600169; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 90874.66666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378600469; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 130022.4; 0.5333333333333333; 1.3333333333333333; 0.06666666666666667; 0.0 +1378600769; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 131420.53333333333; 0.0; 1.0; 0.0; 0.0 +1378601069; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 111846.66666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378601369; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 107652.26666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1378601669; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 99263.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378601970; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 86680.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378602269; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 130022.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1378602569; 1; 2599.998989; 20.799991912; 0.8; 2097152.0; 155188.0; 0.2; 2.7333333333333334; 0.06666666666666667; 0.4666666666666667 +1378602869; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 141207.46666666667; 0.0; 0.8; 0.0; 0.0 +1378603169; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 141206.93333333332; 0.0; 6.933333333333334; 0.26666666666666666; 0.13333333333333333 +1378603469; 1; 2599.998989; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1378603769; 1; 2599.998989; 0.0; 0.0; 2097152.0; 106254.13333333333; 0.0; 0.8; 0.06666666666666667; 0.0 +1378604069; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 137012.26666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1378604369; 1; 2599.998989; 0.0; 0.0; 2097152.0; 81087.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1378604669; 1; 2599.998989; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378604969; 1; 2599.998989; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 1.0; 0.0; 0.0 +1378605269; 1; 2599.998989; 0.0; 0.0; 2097152.0; 106254.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378605569; 1; 2599.998989; 0.0; 0.0; 2097152.0; 110448.53333333334; 0.0; 0.8; 0.0; 0.0 +1378605869; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 131420.53333333333; 0.0; 0.8; 0.0; 0.0 +1378606169; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 141207.46666666667; 0.06666666666666667; 2.3333333333333335; 0.0; 0.4666666666666667 +1378606469; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 134216.26666666666; 0.0; 0.8; 0.0; 0.0 +1378606769; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 123031.73333333334; 0.0; 0.8; 0.0; 0.0 +1378607069; 1; 2599.998989; 0.0; 0.0; 2097152.0; 153790.66666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1378607369; 1; 2599.998989; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1378607669; 1; 2599.998989; 0.0; 0.0; 2097152.0; 86680.26666666666; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1378607970; 1; 2599.998989; 0.0; 0.0; 2097152.0; 65708.26666666666; 0.0; 0.8; 0.0; 0.0 +1378608270; 1; 2599.998989; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 0.8; 0.0; 0.0 +1378608570; 1; 2599.998989; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8; 0.06666666666666667; 0.0 +1378608870; 1; 2599.998989; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1378609170; 1; 2599.998989; 0.0; 0.0; 2097152.0; 89476.53333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1378609470; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 121633.6; 0.8666666666666667; 1.3333333333333333; 0.06666666666666667; 0.0 +1378609770; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 138411.2; 0.0; 2.2; 0.0; 0.4666666666666667 +1378610070; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 155187.2; 0.0; 6.933333333333334; 0.2; 0.13333333333333333 +1378610370; 1; 2599.998989; 0.0; 0.0; 2097152.0; 130022.4; 0.0; 1.0666666666666667; 0.0; 0.0 +1378610670; 1; 2599.998989; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 0.8; 0.0; 0.0 +1378610970; 1; 2599.998989; 0.0; 0.0; 2097152.0; 95069.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378611270; 1; 2599.998989; 0.0; 0.0; 2097152.0; 118837.33333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378611570; 1; 2599.998989; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.8; 0.0; 0.0 +1378611870; 1; 2599.998989; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 1.6666666666666667; 0.0; 0.0 +1378612170; 1; 2599.998989; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 0.8; 0.0; 0.0 +1378612470; 1; 2599.998989; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 1.0; 0.06666666666666667; 0.0 +1378612770; 1; 2599.998989; 0.0; 0.0; 2097152.0; 110448.53333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1378613070; 1; 2599.998989; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1378613370; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 145401.86666666667; 0.06666666666666667; 2.533333333333333; 0.0; 0.4666666666666667 +1378613670; 1; 2599.998989; 0.0; 0.0; 2097152.0; 134216.0; 0.0; 1.1333333333333333; 0.0; 0.0 +1378613970; 1; 2599.998989; 0.0; 0.0; 2097152.0; 116041.06666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378614270; 1; 2599.998989; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1378614570; 1; 2599.998989; 0.0; 0.0; 2097152.0; 118836.53333333334; 0.0; 1.1333333333333333; 0.0; 0.0 +1378614870; 1; 2599.998989; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1378615170; 1; 2599.998989; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1378615470; 1; 2599.998989; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1378615770; 1; 2599.998989; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1378616070; 1; 2599.998989; 0.0; 0.0; 2097152.0; 138411.2; 0.0; 1.2; 0.0; 0.0 +1378616370; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 146799.2; 0.06666666666666667; 7.266666666666667; 0.2; 0.13333333333333333 +1378616670; 1; 2599.998989; 0.0; 0.0; 2097152.0; 134216.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1378616970; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 120235.46666666666; 0.0; 2.2; 0.0; 0.4666666666666667 +1378617270; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 167770.4; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1378617570; 1; 2599.998989; 0.0; 0.0; 2097152.0; 118837.33333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378617871; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 92272.0; 0.0; 0.8666666666666667; 7.6; 0.0 +1378618171; 1; 2599.998989; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 0.9333333333333333; 0.13333333333333333; 0.0 +1378618471; 1; 2599.998989; 0.0; 0.0; 2097152.0; 146799.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1378618771; 1; 2599.998989; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 1.2; 0.0; 0.0 +1378619071; 1; 2599.998989; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378619371; 1; 2599.998989; 0.0; 0.0; 2097152.0; 149596.26666666666; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378619671; 1; 2599.998989; 0.0; 0.0; 2097152.0; 146798.4; 0.0; 1.0; 0.0; 0.0 +1378619971; 1; 2599.998989; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 1.1333333333333333; 0.0; 0.0 +1378620271; 1; 2599.998989; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1378620571; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 134216.0; 0.0; 2.3333333333333335; 0.06666666666666667; 0.4666666666666667 +1378620871; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 213908.0; 0.0; 1.1333333333333333; 0.0; 0.0 +1378621171; 1; 2599.998989; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 1.0; 0.0; 0.0 +1378621471; 1; 2599.998989; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 1.1333333333333333; 0.0; 0.0 +1378621771; 1; 2599.998989; 0.0; 0.0; 2097152.0; 117438.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1378622071; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 130021.6; 0.0; 2.3333333333333335; 0.06666666666666667; 0.06666666666666667 +1378622371; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 113244.8; 0.0; 1.4666666666666666; 0.0; 0.0 +1378622671; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 100661.6; 0.0; 11.866666666666667; 0.06666666666666667; 0.0 +1378622971; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 117438.4; 12.733333333333333; 13.666666666666666; 0.26666666666666666; 0.13333333333333333 +1378623271; 1; 2599.998989; 0.0; 0.0; 2097152.0; 121633.6; 0.0; 1.2666666666666666; 0.06666666666666667; 0.0 +1378623571; 1; 2599.998989; 0.0; 0.0; 2097152.0; 121633.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1378623871; 1; 2599.998989; 0.0; 0.0; 2097152.0; 83884.0; 0.0; 1.0; 0.0; 0.0 +1378624171; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 96467.2; 0.0; 2.4; 0.06666666666666667; 0.4666666666666667 +1378624472; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 146800.0; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378624772; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 104856.0; 0.0; 1.0; 0.2; 0.0 +1378625072; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 92272.8; 0.0; 1.0; 0.0; 0.0 +1378625372; 1; 2599.998989; 0.0; 0.0; 2097152.0; 75495.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1378625672; 1; 2599.998989; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 1.2666666666666666; 0.0; 0.0 +1378625972; 1; 2599.998989; 0.0; 0.0; 2097152.0; 79689.6; 0.0; 1.7333333333333334; 0.0; 0.0 +1378626272; 1; 2599.998989; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 1.6; 0.0; 0.0 +1378626572; 1; 2599.998989; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1378626872; 1; 2599.998989; 0.0; 0.0; 2097152.0; 125828.0; 0.0; 1.0; 0.0; 0.0 +1378627172; 1; 2599.998989; 0.0; 0.0; 2097152.0; 118837.33333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378627472; 1; 2599.998989; 0.0; 0.0; 2097152.0; 116040.26666666666; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378627772; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 153789.6; 0.2; 2.4; 0.06666666666666667; 0.4666666666666667 +1378628072; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 184548.0; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378628372; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 138411.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1378628672; 1; 2599.998989; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1378628972; 1; 2599.998989; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 0.8; 0.0; 0.0 +1378629272; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 89476.53333333334; 0.0; 7.0; 0.26666666666666666; 0.13333333333333333 +1378629572; 1; 2599.998989; 0.0; 0.0; 2097152.0; 125827.2; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378629872; 1; 2599.998989; 0.0; 0.0; 2097152.0; 117438.4; 0.0; 1.2; 0.06666666666666667; 0.0 +1378630172; 1; 2599.998989; 0.0; 0.0; 2097152.0; 79689.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1378630472; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 95069.06666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378630772; 1; 2599.998989; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.7333333333333333; 0.0; 0.0 +1378631073; 1; 2599.998989; 0.0; 0.0; 2097152.0; 127226.13333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1378631373; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 134216.0; 0.0; 2.2666666666666666; 0.0; 0.4666666666666667 +1378631673; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 123031.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1378631973; 1; 2599.998989; 0.0; 0.0; 2097152.0; 78291.46666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378632273; 1; 2599.998989; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378632573; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 96467.2; 0.0; 1.2666666666666666; 0.0; 0.0 +1378632873; 1; 2599.998989; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1378633173; 1; 2599.998989; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 1.0; 0.06666666666666667; 0.0 +1378633473; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 76893.33333333333; 0.0; 0.8; 0.0; 0.0 +1378633773; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 120235.46666666666; 0.0; 1.0; 0.0; 0.0 +1378634073; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 117439.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1378634373; 1; 2599.998989; 0.0; 0.0; 2097152.0; 120235.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378634673; 1; 2599.998989; 0.0; 0.0; 2097152.0; 123031.73333333334; 0.0; 1.0; 0.0; 0.0 +1378634973; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 170566.4; 0.0; 8.4; 0.2; 0.6666666666666666 +1378635273; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 170566.4; 0.06666666666666667; 1.4666666666666666; 0.06666666666666667; 0.0 +1378635573; 1; 2599.998989; 0.0; 0.0; 2097152.0; 171964.53333333333; 0.0; 0.8; 0.06666666666666667; 0.0 +1378635873; 1; 2599.998989; 0.0; 0.0; 2097152.0; 137012.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1378636173; 1; 2599.998989; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1378636473; 1; 2599.998989; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1378636773; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 130022.4; 0.0; 1.2666666666666666; 0.06666666666666667; 0.0 +1378637073; 1; 2599.998989; 0.0; 0.0; 2097152.0; 118837.33333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378637373; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 88078.4; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378637673; 1; 2599.998989; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 0.8; 0.06666666666666667; 0.0 +1378637973; 1; 2599.998989; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1378638273; 1; 2599.998989; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378638573; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 127226.13333333333; 0.2; 2.533333333333333; 0.0; 0.4666666666666667 +1378638873; 1; 2599.998989; 0.0; 0.0; 2097152.0; 128624.26666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378639173; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 90874.66666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378639473; 1; 2599.998989; 0.0; 0.0; 2097152.0; 82485.86666666667; 0.0; 1.2666666666666666; 0.0; 0.0 +1378639773; 1; 2599.998989; 0.0; 0.0; 2097152.0; 124429.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378640073; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 139808.53333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378640373; 1; 2599.998989; 0.0; 0.0; 2097152.0; 123031.73333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1378640673; 1; 2599.998989; 0.0; 0.0; 2097152.0; 123031.73333333334; 0.0; 1.4; 0.0; 0.0 +1378640973; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 120234.66666666667; 0.06666666666666667; 7.133333333333334; 0.3333333333333333; 0.13333333333333333 +1378641273; 1; 2599.998989; 0.0; 0.0; 2097152.0; 166373.06666666668; 0.0; 1.0; 0.0; 0.0 +1378641573; 1; 2599.998989; 0.0; 0.0; 2097152.0; 123030.93333333333; 0.0; 1.0; 0.0; 0.0 +1378641873; 1; 2599.998989; 0.0; 0.0; 2097152.0; 118837.33333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378642173; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 167770.66666666666; 0.0; 2.7333333333333334; 0.0; 0.4666666666666667 +1378642473; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 183149.6; 0.0; 0.8; 0.0; 0.0 +1378642774; 1; 2599.998989; 0.0; 0.0; 2097152.0; 130022.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1378643074; 1; 2599.998989; 0.0; 0.0; 2097152.0; 110448.53333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1378643374; 1; 2599.998989; 20.799991912; 0.8; 2097152.0; 173362.93333333332; 161.0; 21.866666666666667; 0.06666666666666667; 0.4 +1378643674; 1; 2599.998989; 55.46664509866667; 2.1333333333333333; 2097152.0; 708835.7333333333; 0.0; 2.8666666666666667; 0.06666666666666667; 0.0 +1378643974; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 318765.3333333333; 31.8; 3.6666666666666665; 0.0; 0.0 +1378644274; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 183148.53333333333; 0.8; 2.2; 0.0; 0.0 +1378644574; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 150992.53333333333; 0.0; 1.6; 0.0; 0.0 +1378644874; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 120235.46666666666; 0.0; 1.0; 0.0; 0.0 +1378645174; 1; 2599.998989; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 1.0; 0.0; 0.0 +1378645474; 1; 2599.998989; 0.0; 0.0; 2097152.0; 152390.66666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378645774; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 153790.13333333333; 0.0; 2.3333333333333335; 0.0; 0.4666666666666667 +1378646074; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 162177.6; 0.0; 1.0; 0.0; 0.0 +1378646374; 1; 2599.998989; 0.0; 0.0; 2097152.0; 124429.86666666667; 0.0; 1.2; 0.0; 0.0 +1378646674; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 117439.2; 0.0; 7.266666666666667; 0.2; 0.13333333333333333 +1378646974; 1; 2599.998989; 0.0; 0.0; 2097152.0; 114642.93333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378647274; 1; 2599.998989; 0.0; 0.0; 2097152.0; 85282.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378647574; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 148197.33333333334; 1.0; 1.7333333333333334; 0.0; 0.0 +1378647874; 1; 2599.998989; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 1.2; 0.0; 0.0 +1378648174; 1; 2599.998989; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 1.0; 0.0; 0.0 +1378648474; 1; 2599.998989; 0.0; 0.0; 2097152.0; 106254.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378648774; 1; 2599.998989; 0.0; 0.0; 2097152.0; 81087.73333333334; 0.0; 1.0666666666666667; 0.0; 0.0 +1378649074; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 74097.06666666667; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1378649374; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 137012.26666666666; 0.06666666666666667; 2.533333333333333; 0.0; 0.4666666666666667 +1378649674; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 176159.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1378649974; 1; 2599.998989; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 1.0; 0.0; 0.0 +1378650274; 1; 2599.998989; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378650574; 1; 2599.998989; 0.0; 0.0; 2097152.0; 132818.66666666666; 0.0; 1.2; 0.0; 0.0 +1378650874; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 155188.8; 0.06666666666666667; 1.3333333333333333; 0.06666666666666667; 0.13333333333333333 +1378651174; 1; 2599.998989; 0.0; 0.0; 2097152.0; 131420.53333333333; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1378651474; 1; 2599.998989; 0.0; 0.0; 2097152.0; 116040.53333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1378651775; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 141207.46666666667; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378652075; 1; 2599.998989; 0.0; 0.0; 2097152.0; 130022.4; 0.0; 0.8; 0.0; 0.0 +1378652375; 1; 2599.998989; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1378652675; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 104856.0; 0.0; 6.8; 0.26666666666666666; 0.13333333333333333 +1378652975; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 149595.46666666667; 0.0; 2.2666666666666666; 0.0; 0.4666666666666667 +1378653275; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 145401.06666666668; 0.0; 1.1333333333333333; 0.0; 0.0 +1378653575; 1; 2599.998989; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1378653875; 1; 2599.998989; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1378654175; 1; 2599.998989; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1378654475; 1; 2599.998989; 0.0; 0.0; 2097152.0; 79689.6; 0.0; 0.8; 0.0; 0.0 +1378654775; 1; 2599.998989; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 1.1333333333333333; 0.0; 0.0 +1378655075; 1; 2599.998989; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 1.2666666666666666; 0.0; 0.0 +1378655375; 1; 2599.998989; 0.0; 0.0; 2097152.0; 116041.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378655675; 1; 2599.998989; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1378655975; 1; 2599.998989; 0.0; 0.0; 2097152.0; 146798.13333333333; 0.0; 1.0; 0.0; 0.0 +1378656275; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 102059.73333333334; 0.0; 0.8; 0.0; 0.0 +1378656575; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 117439.2; 0.0; 2.2; 0.0; 0.5333333333333333 +1378656875; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 127225.33333333333; 0.0; 0.8; 0.0; 0.0 +1378657175; 1; 2599.998989; 0.0; 0.0; 2097152.0; 121633.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1378657475; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 114642.93333333333; 0.0; 1.2; 0.0; 0.0 +1378657775; 1; 2599.998989; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1378658076; 1; 2599.998989; 0.0; 0.0; 2097152.0; 82485.86666666667; 0.0; 0.6666666666666666; 0.0; 0.0 +1378658376; 1; 2599.998989; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 1.0666666666666667; 0.0; 0.0 +1378658676; 1; 2599.998989; 0.0; 0.0; 2097152.0; 132818.66666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378658976; 1; 2599.998989; 0.0; 0.0; 2097152.0; 123030.93333333333; 0.0; 0.8; 0.0; 0.0 +1378659276; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 88078.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1378659576; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 89476.53333333334; 0.0; 6.933333333333334; 0.2; 0.13333333333333333 +1378659876; 1; 2599.998989; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378660176; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 148196.8; 0.0; 2.8; 0.0; 0.4666666666666667 +1378660476; 1; 2599.998989; 0.0; 0.0; 2097152.0; 152391.46666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1378660776; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 111846.66666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378661076; 1; 2599.998989; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1378661376; 1; 2599.998989; 0.0; 0.0; 2097152.0; 79689.6; 0.0; 0.7333333333333333; 0.0; 0.0 +1378661676; 1; 2599.998989; 0.0; 0.0; 2097152.0; 124429.86666666667; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378661976; 1; 2599.998989; 0.0; 0.0; 2097152.0; 95069.06666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1378662276; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 97865.33333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378662576; 1; 2599.998989; 0.0; 0.0; 2097152.0; 116041.06666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1378662876; 1; 2599.998989; 0.0; 0.0; 2097152.0; 68504.53333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1378663176; 1; 2599.998989; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 0.8; 0.0; 0.0 +1378663476; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 113244.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1378663776; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 148197.6; 0.06666666666666667; 2.533333333333333; 0.0; 0.4666666666666667 +1378664076; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 138410.13333333333; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378664376; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 95069.06666666667; 0.0; 1.2666666666666666; 0.06666666666666667; 0.0 +1378664676; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 114642.93333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378664976; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 106254.13333333333; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378665276; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 150994.4; 0.0; 6.733333333333333; 0.26666666666666666; 0.2 +1378665576; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 145401.86666666667; 0.0; 0.8; 0.0; 0.0 +1378665876; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 132817.6; 0.0; 1.1333333333333333; 0.0; 0.0 +1378666176; 1; 2599.998989; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.8; 0.0; 0.0 +1378666476; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 111846.66666666667; 0.0; 11.8; 0.0; 0.0 +1378666776; 1; 2599.998989; 0.0; 0.0; 2097152.0; 135614.66666666666; 0.0; 1.1333333333333333; 0.0; 0.0 +1378667076; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 120234.93333333333; 0.0; 1.2; 0.0; 0.0 +1378667377; 1; 2599.998989; 22.53332457133334; 0.8666666666666667; 2097152.0; 117439.2; 0.0; 2.3333333333333335; 0.0; 0.5333333333333333 +1378667677; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 102059.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1378667976; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 134216.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1378668276; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 111846.66666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378668576; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 92272.8; 0.0; 1.2; 0.0; 0.0 +1378668876; 1; 2599.998989; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378669176; 1; 2599.998989; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1378669476; 1; 2599.998989; 39.86665116466667; 1.5333333333333334; 2097152.0; 321561.6; 161.73333333333332; 14.733333333333333; 0.0; 0.2 +1378669776; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 339736.26666666666; 0.0; 1.8666666666666667; 0.0; 0.0 +1378670076; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 162178.66666666666; 31.8; 3.7333333333333334; 0.0; 0.0 +1378670376; 1; 2599.998989; 0.0; 0.0; 2097152.0; 134216.8; 0.0; 1.8; 0.0; 0.0 +1378670676; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 130021.6; 0.0; 7.0; 0.2; 0.13333333333333333 +1378670976; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 142604.53333333333; 0.06666666666666667; 2.7333333333333334; 0.0; 0.5333333333333333 +1378671276; 1; 2599.998989; 0.0; 0.0; 2097152.0; 125827.73333333334; 0.0; 1.3333333333333333; 0.0; 0.0 +1378671576; 1; 2599.998989; 0.0; 0.0; 2097152.0; 67106.4; 0.0; 1.1333333333333333; 0.0; 0.0 +1378671876; 1; 2599.998989; 0.0; 0.0; 2097152.0; 72698.93333333333; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378672176; 1; 2599.998989; 0.0; 0.0; 2097152.0; 110448.53333333334; 0.0; 0.8; 0.0; 0.0 +1378672477; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 121633.6; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378672777; 1; 2599.998989; 0.0; 0.0; 2097152.0; 85282.13333333333; 0.0; 1.2; 0.0; 0.0 +1378673077; 1; 2599.998989; 0.0; 0.0; 2097152.0; 74097.06666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1378673377; 1; 2599.998989; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378673677; 1; 2599.998989; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 1.0666666666666667; 0.0; 0.0 +1378673977; 1; 2599.998989; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 1.2666666666666666; 0.0; 0.0 +1378674277; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 113244.8; 0.0; 1.0; 0.0; 0.0 +1378674577; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 128624.26666666666; 0.13333333333333333; 2.533333333333333; 0.0; 0.4666666666666667 +1378674877; 1; 2599.998989; 0.0; 0.0; 2097152.0; 131420.53333333333; 0.0; 0.8; 0.0; 0.0 +1378675177; 1; 2599.998989; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1378675477; 1; 2599.998989; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 1.0; 0.0; 0.0 +1378675777; 1; 2599.998989; 0.0; 0.0; 2097152.0; 67106.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1378676077; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 86680.26666666666; 0.0; 0.8; 0.0; 0.0 +1378676377; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 85282.13333333333; 0.06666666666666667; 1.5333333333333334; 0.2; 0.13333333333333333 +1378676677; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 150993.33333333334; 0.0; 6.666666666666667; 0.0; 0.0 +1378676977; 1; 2599.998989; 0.0; 0.0; 2097152.0; 127225.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1378677277; 1; 2599.998989; 0.0; 0.0; 2097152.0; 146799.2; 0.0; 1.0; 0.0; 0.0 +1378677577; 1; 2599.998989; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1378677877; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 83884.0; 0.26666666666666666; 1.1333333333333333; 0.0; 0.0 +1378678177; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 138410.13333333333; 0.0; 2.533333333333333; 0.0; 0.5333333333333333 +1378678477; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 124428.53333333334; 0.0; 1.0; 0.0; 0.0 +1378678777; 1; 2599.998989; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1378679077; 1; 2599.998989; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.7333333333333333; 0.0; 0.0 +1378679377; 1; 2599.998989; 0.0; 0.0; 2097152.0; 145401.86666666667; 0.0; 0.8; 0.0; 0.0 +1378679677; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 130022.4; 0.0; 1.8; 0.06666666666666667; 0.13333333333333333 +1378679977; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 166373.06666666668; 0.0; 0.8; 0.0; 0.0 +1378680277; 1; 2599.998989; 0.0; 0.0; 2097152.0; 114642.93333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1378680577; 1; 2599.998989; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 1.0; 0.0; 0.0 +1378680877; 1; 2599.998989; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 1.3333333333333333; 0.06666666666666667; 0.0 +1378681178; 1; 2599.998989; 0.0; 0.0; 2097152.0; 118837.33333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378681478; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 118837.33333333333; 0.2; 1.0666666666666667; 0.0; 0.0 +1378681778; 1; 2599.998989; 20.799991912; 0.8; 2097152.0; 137013.06666666668; 0.0; 2.4; 0.0; 0.4666666666666667 +1378682078; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 130022.4; 0.0; 0.8; 0.0; 0.0 +1378682378; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 124429.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378682678; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 111846.13333333333; 12.733333333333333; 13.066666666666666; 0.3333333333333333; 0.13333333333333333 +1378682978; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 145400.53333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1378683278; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 125828.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1378683578; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 99263.46666666666; 0.0; 1.0; 0.0; 0.0 +1378683878; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 88078.4; 0.0; 1.3333333333333333; 0.0; 0.0 +1378684178; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 106254.13333333333; 0.0; 0.8; 0.0; 0.0 +1378684478; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 130022.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1378684778; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 92272.8; 0.0; 0.8; 0.0; 0.0 +1378685078; 1; 2599.998989; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1378685378; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 184547.73333333334; 0.06666666666666667; 2.466666666666667; 0.0; 0.5333333333333333 +1378685678; 1; 2599.998989; 0.0; 0.0; 2097152.0; 213908.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1378685978; 1; 2599.998989; 0.0; 0.0; 2097152.0; 138411.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1378686278; 1; 2599.998989; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378686578; 1; 2599.998989; 0.0; 0.0; 2097152.0; 124429.86666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1378686878; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 164974.4; 0.6; 1.2666666666666666; 0.0; 0.0 +1378687178; 1; 2599.998989; 0.0; 0.0; 2097152.0; 164973.33333333334; 0.0; 1.0666666666666667; 0.0; 0.0 +1378687478; 1; 2599.998989; 0.0; 0.0; 2097152.0; 127225.6; 0.0; 1.1333333333333333; 0.0; 0.0 +1378687778; 1; 2599.998989; 0.0; 0.0; 2097152.0; 124429.06666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1378688078; 1; 2599.998989; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 1.0; 0.0; 0.0 +1378688378; 1; 2599.998989; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1378688678; 1; 2599.998989; 0.0; 0.0; 2097152.0; 123031.73333333334; 0.0; 1.0666666666666667; 0.0; 0.0 +1378688978; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 149596.0; 0.0; 2.2666666666666666; 0.0; 0.4666666666666667 +1378689278; 1; 2599.998989; 0.0; 0.0; 2097152.0; 162177.86666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1378689578; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 113244.8; 0.0; 7.2; 0.2; 0.13333333333333333 +1378689878; 1; 2599.998989; 0.0; 0.0; 2097152.0; 135614.93333333332; 0.0; 1.0666666666666667; 0.0; 0.0 +1378690178; 1; 2599.998989; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 1.0; 0.0; 0.0 +1378690478; 1; 2599.998989; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1378690778; 1; 2599.998989; 0.0; 0.0; 2097152.0; 121633.6; 0.0; 1.2666666666666666; 0.0; 0.0 +1378691079; 1; 2599.998989; 0.0; 0.0; 2097152.0; 85282.13333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1378691379; 1; 2599.998989; 0.0; 0.0; 2097152.0; 79689.6; 0.0; 1.0; 0.0; 0.0 +1378691679; 1; 2599.998989; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1378691979; 1; 2599.998989; 0.0; 0.0; 2097152.0; 106254.13333333333; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1378692279; 1; 2599.998989; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 1.1333333333333333; 0.0; 0.0 +1378692579; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 131419.73333333334; 0.0; 2.466666666666667; 0.0; 0.4666666666666667 +1378692879; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 155188.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1378693179; 1; 2599.998989; 0.0; 0.0; 2097152.0; 148196.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1378693479; 1; 2599.998989; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1378693779; 1; 2599.998989; 0.0; 0.0; 2097152.0; 75495.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1378694079; 1; 2599.998989; 0.0; 0.0; 2097152.0; 78291.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378694379; 1; 2599.998989; 0.0; 0.0; 2097152.0; 78291.46666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378694679; 1; 2599.998989; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 1.2666666666666666; 0.0; 0.0 +1378694979; 1; 2599.998989; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 1.1333333333333333; 0.0; 0.0 +1378695279; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 131420.53333333333; 0.0; 6.866666666666666; 0.2; 0.13333333333333333 +1378695579; 1; 2599.998989; 0.0; 0.0; 2097152.0; 135614.93333333332; 0.0; 0.8666666666666667; 0.0; 0.0 +1378695879; 1; 2599.998989; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378696179; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 117439.2; 0.0; 2.8; 0.0; 0.4666666666666667 +1378696479; 1; 2599.998989; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1378696779; 1; 2599.998989; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1378697079; 1; 2599.998989; 64.13330839533334; 2.466666666666667; 2097152.0; 426419.2; 161.2; 21.733333333333334; 0.06666666666666667; 0.4 +1378697379; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 539665.0666666667; 0.0; 2.8; 0.0; 0.0 +1378697679; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 246063.46666666667; 31.8; 3.6666666666666665; 0.0; 0.0 +1378697979; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 159382.4; 0.8; 2.6666666666666665; 0.0; 0.0 +1378698279; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 124429.86666666667; 0.0; 1.8666666666666667; 0.0; 0.0 +1378698579; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 155187.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1378698879; 1; 2599.998989; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 1.1333333333333333; 0.0; 0.0 +1378699180; 1; 2599.998989; 0.0; 0.0; 2097152.0; 142605.6; 0.0; 1.0; 0.0; 0.0 +1378699480; 1; 2599.998989; 0.0; 0.0; 2097152.0; 125828.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1378699780; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 134216.53333333333; 6.8; 2.533333333333333; 0.0; 0.4666666666666667 +1378700080; 1; 2599.998989; 0.0; 0.0; 2097152.0; 118836.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1378700380; 1; 2599.998989; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1378700679; 1; 2599.998989; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378700979; 1; 2599.998989; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1378701279; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 142605.6; 0.0; 7.066666666666666; 0.26666666666666666; 0.2 +1378701579; 1; 2599.998989; 0.0; 0.0; 2097152.0; 114642.93333333333; 0.0; 1.2666666666666666; 0.0; 0.0 +1378701879; 1; 2599.998989; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1378702179; 1; 2599.998989; 0.0; 0.0; 2097152.0; 130021.6; 0.0; 1.0; 0.0; 0.0 +1378702479; 1; 2599.998989; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1378702779; 1; 2599.998989; 0.0; 0.0; 2097152.0; 123031.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1378703079; 1; 2599.998989; 0.0; 0.0; 2097152.0; 120235.46666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1378703379; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 183149.06666666668; 0.06666666666666667; 2.4; 0.0; 0.4666666666666667 +1378703680; 1; 2599.998989; 0.0; 0.0; 2097152.0; 160780.53333333333; 0.0; 1.0; 0.0; 0.0 +1378703980; 1; 2599.998989; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 1.0; 0.0; 0.0 +1378704280; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 111846.66666666667; 1.0; 2.0; 0.0; 0.0 +1378704580; 1; 2599.998989; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1378704880; 1; 2599.998989; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378705180; 1; 2599.998989; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378705480; 1; 2599.998989; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1378705780; 1; 2599.998989; 0.0; 0.0; 2097152.0; 139809.33333333334; 0.0; 1.0666666666666667; 0.0; 0.0 +1378706080; 1; 2599.998989; 0.0; 0.0; 2097152.0; 144002.93333333332; 0.0; 0.9333333333333333; 0.0; 0.0 +1378706380; 1; 2599.998989; 0.0; 0.0; 2097152.0; 106254.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378706680; 1; 2599.998989; 0.0; 0.0; 2097152.0; 150993.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1378706980; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 134216.0; 0.0; 1.9333333333333333; 0.0; 0.4666666666666667 +1378707280; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 116041.06666666667; 0.0; 6.333333333333333; 0.26666666666666666; 0.13333333333333333 +1378707580; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 148196.53333333333; 0.0; 1.5333333333333334; 0.0; 0.0 +1378707880; 1; 2599.998989; 0.0; 0.0; 2097152.0; 116041.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378708180; 1; 2599.998989; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378708480; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 116041.06666666667; 0.06666666666666667; 2.8; 0.06666666666666667; 0.06666666666666667 +1378708780; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 142604.53333333333; 0.0; 1.8; 0.0; 0.0 +1378709080; 1; 2599.998989; 0.0; 0.0; 2097152.0; 167770.4; 0.0; 1.0; 0.0; 0.0 +1378709380; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 135614.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378709680; 1; 2599.998989; 0.0; 0.0; 2097152.0; 128623.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378709980; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 81087.73333333334; 0.0; 11.933333333333334; 0.0; 0.0 +1378710280; 1; 2599.998989; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1378710580; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 135613.33333333334; 0.26666666666666666; 2.3333333333333335; 0.0; 0.4666666666666667 +1378710880; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 156585.6; 0.0; 1.0; 0.0; 0.0 +1378711180; 1; 2599.998989; 0.0; 0.0; 2097152.0; 128624.26666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378711481; 1; 2599.998989; 0.0; 0.0; 2097152.0; 82485.86666666667; 0.0; 1.0; 0.0; 0.0 +1378711781; 1; 2599.998989; 0.0; 0.0; 2097152.0; 135613.86666666667; 0.0; 0.6666666666666666; 0.0; 0.0 +1378712081; 1; 2599.998989; 0.0; 0.0; 2097152.0; 111846.13333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1378712381; 1; 2599.998989; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 1.1333333333333333; 0.0; 0.0 +1378712681; 1; 2599.998989; 0.0; 0.0; 2097152.0; 131420.53333333333; 0.0; 1.9333333333333333; 0.0; 0.0 +1378712981; 1; 2599.998989; 0.0; 0.0; 2097152.0; 120235.46666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378713281; 1; 2599.998989; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378713581; 1; 2599.998989; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378713881; 1; 2599.998989; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 1.0666666666666667; 0.13333333333333333; 0.0 +1378714181; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 174760.8; 0.0; 8.6; 0.2; 0.6666666666666666 +1378714481; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 155187.46666666667; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378714781; 1; 2599.998989; 0.0; 0.0; 2097152.0; 131419.73333333334; 0.0; 1.6666666666666667; 0.06666666666666667; 0.0 +1378715081; 1; 2599.998989; 0.0; 0.0; 2097152.0; 123030.93333333333; 0.0; 1.0; 0.0; 0.0 +1378715381; 1; 2599.998989; 0.0; 0.0; 2097152.0; 85282.13333333333; 0.0; 1.2; 0.0; 0.0 +1378715681; 1; 2599.998989; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1378715981; 1; 2599.998989; 0.0; 0.0; 2097152.0; 95069.06666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378716281; 1; 2599.998989; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378716581; 1; 2599.998989; 0.0; 0.0; 2097152.0; 89476.53333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1378716881; 1; 2599.998989; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 1.2; 0.0; 0.0 +1378717181; 1; 2599.998989; 0.0; 0.0; 2097152.0; 79689.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1378717481; 1; 2599.998989; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1378717782; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 128624.0; 0.0; 2.4; 0.0; 0.4666666666666667 +1378718082; 1; 2599.998989; 0.0; 0.0; 2097152.0; 152391.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1378718382; 1; 2599.998989; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1378718682; 1; 2599.998989; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378718982; 1; 2599.998989; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 1.0; 0.0; 0.0 +1378719282; 1; 2599.998989; 0.0; 0.0; 2097152.0; 85282.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378719582; 1; 2599.998989; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 1.2; 0.0; 0.0 +1378719882; 1; 2599.998989; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 1.0666666666666667; 0.0; 0.0 +1378720182; 1; 2599.998989; 0.0; 0.0; 2097152.0; 141207.46666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378720482; 1; 2599.998989; 0.0; 0.0; 2097152.0; 132818.66666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1378720782; 1; 2599.998989; 0.0; 0.0; 2097152.0; 79689.6; 0.0; 0.8; 0.0; 0.0 +1378721082; 1; 2599.998989; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1378721382; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 145400.8; 0.0; 8.733333333333333; 0.2; 0.6 +1378721682; 1; 2599.998989; 0.0; 0.0; 2097152.0; 153788.53333333333; 0.0; 0.8; 0.0; 0.0 +1378721982; 1; 2599.998989; 0.0; 0.0; 2097152.0; 116041.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378722282; 1; 2599.998989; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 1.0; 0.0; 0.0 +1378722582; 1; 2599.998989; 0.0; 0.0; 2097152.0; 106254.13333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1378722882; 1; 2599.998989; 0.0; 0.0; 2097152.0; 142604.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1378723182; 1; 2599.998989; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1378723482; 1; 2599.998989; 0.0; 0.0; 2097152.0; 89476.53333333334; 0.0; 0.8; 0.0; 0.0 +1378723782; 1; 2599.998989; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 1.3333333333333333; 0.0; 0.0 +1378724082; 1; 2599.998989; 0.0; 0.0; 2097152.0; 89476.53333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1378724382; 1; 2599.998989; 0.0; 0.0; 2097152.0; 68504.53333333334; 0.0; 1.0; 0.0; 0.0 +1378724682; 1; 2599.998989; 0.0; 0.0; 2097152.0; 86680.26666666666; 0.0; 0.7333333333333333; 0.0; 0.0 +1378724982; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 128624.26666666666; 0.0; 2.3333333333333335; 0.06666666666666667; 0.4666666666666667 +1378725282; 1; 2599.998989; 0.0; 0.0; 2097152.0; 145401.06666666668; 0.0; 0.8; 0.0; 0.0 +1378725582; 1; 2599.998989; 0.0; 0.0; 2097152.0; 150993.6; 0.0; 0.7333333333333333; 0.0; 0.0 +1378725882; 1; 2599.998989; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.7333333333333333; 0.0; 0.0 +1378726182; 1; 2599.998989; 0.0; 0.0; 2097152.0; 121633.6; 0.0; 0.8; 0.26666666666666666; 0.0 +1378726482; 1; 2599.998989; 0.0; 0.0; 2097152.0; 141207.46666666667; 0.0; 1.0; 0.0; 0.0 +1378726782; 1; 2599.998989; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.7333333333333333; 0.0; 0.0 +1378727082; 1; 2599.998989; 0.0; 0.0; 2097152.0; 110448.53333333334; 0.0; 1.4; 0.0; 0.0 +1378727382; 1; 2599.998989; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.8; 0.0; 0.0 +1378727682; 1; 2599.998989; 0.0; 0.0; 2097152.0; 89476.26666666666; 0.0; 0.8; 0.13333333333333333; 0.0 +1378727983; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 208314.93333333332; 0.0; 7.066666666666666; 0.2; 0.13333333333333333 +1378728283; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 125828.0; 0.0; 0.6666666666666666; 0.0; 0.0 +1378728583; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 156585.06666666668; 0.0; 2.3333333333333335; 0.0; 0.4666666666666667 +1378728883; 1; 2599.998989; 0.0; 0.0; 2097152.0; 153790.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378729183; 1; 2599.998989; 0.0; 0.0; 2097152.0; 125828.0; 0.0; 1.0; 0.0; 0.0 +1378729483; 1; 2599.998989; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.8; 0.0; 0.0 +1378729783; 1; 2599.998989; 0.0; 0.0; 2097152.0; 86680.26666666666; 0.0; 0.7333333333333333; 0.13333333333333333; 0.0 +1378730083; 1; 2599.998989; 0.0; 0.0; 2097152.0; 78291.46666666666; 0.0; 0.8; 0.0; 0.0 +1378730383; 1; 2599.998989; 0.0; 0.0; 2097152.0; 127225.06666666667; 0.0; 0.6666666666666666; 0.0; 0.0 +1378730683; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 128624.26666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1378730983; 1; 2599.998989; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.8; 0.0; 0.0 +1378731283; 1; 2599.998989; 0.0; 0.0; 2097152.0; 118837.33333333333; 0.0; 1.0; 0.0; 0.0 +1378731583; 1; 2599.998989; 0.0; 0.0; 2097152.0; 127226.13333333333; 0.0; 1.0; 0.0; 0.0 +1378731883; 1; 2599.998989; 0.0; 0.0; 2097152.0; 79689.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1378732183; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 162178.4; 0.06666666666666667; 2.466666666666667; 0.0; 0.4666666666666667 +1378732483; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 135613.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1378732783; 1; 2599.998989; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 0.8; 0.06666666666666667; 0.0 +1378733083; 1; 2599.998989; 0.0; 0.0; 2097152.0; 75495.2; 0.0; 0.7333333333333333; 0.0; 0.0 +1378733383; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 114642.93333333333; 0.0; 7.2; 0.26666666666666666; 0.2 +1378733683; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 103457.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378733983; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 93670.93333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378734283; 1; 2599.998989; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.8; 0.06666666666666667; 0.0 +1378734583; 1; 2599.998989; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.7333333333333333; 0.0; 0.0 +1378734883; 1; 2599.998989; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1378735183; 1; 2599.998989; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 0.6666666666666666; 0.06666666666666667; 0.0 +1378735483; 1; 2599.998989; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 0.6666666666666666; 0.3333333333333333; 0.0 +1378735783; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 180352.8; 0.13333333333333333; 2.6; 0.0; 0.4666666666666667 +1378736083; 1; 2599.998989; 0.0; 0.0; 2097152.0; 167770.4; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1378736383; 1; 2599.998989; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 1.0; 0.0; 0.0 +1378736683; 1; 2599.998989; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378736983; 1; 2599.998989; 0.0; 0.0; 2097152.0; 130021.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1378737283; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 109050.4; 0.0; 1.2666666666666666; 0.06666666666666667; 0.13333333333333333 +1378737583; 1; 2599.998989; 0.0; 0.0; 2097152.0; 120235.46666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1378737883; 1; 2599.998989; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 1.1333333333333333; 0.0; 0.0 +1378738183; 1; 2599.998989; 0.0; 0.0; 2097152.0; 116040.26666666666; 0.0; 1.0; 0.0; 0.0 +1378738483; 1; 2599.998989; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 1.0; 0.06666666666666667; 0.0 +1378738783; 1; 2599.998989; 0.0; 0.0; 2097152.0; 69902.66666666667; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378739083; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 104856.0; 0.0; 6.266666666666667; 0.2; 0.13333333333333333 +1378739383; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 181750.93333333332; 0.0; 2.8666666666666667; 0.06666666666666667; 0.4666666666666667 +1378739683; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 157983.46666666667; 0.0; 1.0; 0.0; 0.0 +1378739983; 1; 2599.998989; 0.0; 0.0; 2097152.0; 167771.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1378740283; 1; 2599.998989; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 1.1333333333333333; 0.6666666666666666; 0.0 +1378740583; 1; 2599.998989; 0.0; 0.0; 2097152.0; 79689.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1378740883; 1; 2599.998989; 0.0; 0.0; 2097152.0; 79689.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1378741183; 1; 2599.998989; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 1.0; 0.0; 0.0 +1378741483; 1; 2599.998989; 0.0; 0.0; 2097152.0; 138411.2; 0.0; 1.2666666666666666; 0.26666666666666666; 0.0 +1378741783; 1; 2599.998989; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 1.1333333333333333; 0.0; 0.0 +1378742084; 1; 2599.998989; 0.0; 0.0; 2097152.0; 123031.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1378742384; 1; 2599.998989; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1378742684; 1; 2599.998989; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1378742984; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 138410.4; 0.0; 2.466666666666667; 0.0; 0.4666666666666667 +1378743284; 1; 2599.998989; 0.0; 0.0; 2097152.0; 130021.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1378743584; 1; 2599.998989; 0.0; 0.0; 2097152.0; 83884.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1378743884; 1; 2599.998989; 0.0; 0.0; 2097152.0; 78291.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378744184; 1; 2599.998989; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1378744484; 1; 2599.998989; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 1.2; 0.0; 0.0 +1378744784; 1; 2599.998989; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1378745084; 1; 2599.998989; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.9333333333333333; 0.26666666666666666; 0.0 +1378745384; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 114642.93333333333; 12.733333333333333; 12.133333333333333; 0.4; 0.2 +1378745684; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 150993.6; 0.0; 2.4; 0.0; 0.0 +1378745984; 1; 2599.998989; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 1.4666666666666666; 0.0; 0.0 +1378746284; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 96467.2; 1.4666666666666666; 1.2; 0.0; 0.0 +1378746584; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 125828.0; 0.06666666666666667; 2.533333333333333; 0.06666666666666667; 0.4666666666666667 +1378746884; 1; 2599.998989; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1378747184; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 130022.4; 0.0; 1.1333333333333333; 0.0; 0.0 +1378747484; 1; 2599.998989; 0.0; 0.0; 2097152.0; 120235.46666666666; 0.0; 1.0; 0.0; 0.0 +1378747784; 1; 2599.998989; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 1.3333333333333333; 0.0; 0.0 +1378748084; 1; 2599.998989; 0.0; 0.0; 2097152.0; 116041.06666666667; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378748384; 1; 2599.998989; 0.0; 0.0; 2097152.0; 114642.93333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378748684; 1; 2599.998989; 0.0; 0.0; 2097152.0; 114642.93333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1378748984; 1; 2599.998989; 65.86664105466667; 2.533333333333333; 2097152.0; 468361.86666666664; 161.06666666666666; 21.733333333333334; 0.06666666666666667; 0.4 +1378749284; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 441797.6; 0.0; 2.8; 0.0; 0.0 +1378749584; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 248859.73333333334; 31.8; 3.7333333333333334; 0.0; 0.0 +1378749884; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 183149.06666666668; 0.8; 2.533333333333333; 0.0; 0.0 +1378750184; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 188742.4; 0.0; 3.466666666666667; 0.0; 0.4666666666666667 +1378750484; 1; 2599.998989; 0.0; 0.0; 2097152.0; 114642.93333333333; 0.0; 1.0; 0.0; 0.0 +1378750784; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 90874.66666666667; 0.0; 0.8; 0.0; 0.0 +1378751084; 1; 2599.998989; 0.0; 0.0; 2097152.0; 128624.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378751384; 1; 2599.998989; 0.0; 0.0; 2097152.0; 118837.33333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1378751684; 1; 2599.998989; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1378751984; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 160779.73333333334; 0.06666666666666667; 7.4; 0.2; 0.13333333333333333 +1378752284; 1; 2599.998989; 0.0; 0.0; 2097152.0; 110448.53333333334; 0.0; 1.0; 0.0; 0.0 +1378752585; 1; 2599.998989; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 1.0; 0.0; 0.0 +1378752885; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 97865.33333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1378753185; 1; 2599.998989; 0.0; 0.0; 2097152.0; 116041.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378753485; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 100661.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1378753785; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 144003.73333333334; 0.0; 13.0; 0.0; 0.4666666666666667 +1378754085; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 111846.66666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1378754385; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 109050.4; 0.0; 1.0666666666666667; 0.0; 0.0 +1378754685; 1; 2599.998989; 0.0; 0.0; 2097152.0; 125828.0; 0.0; 1.0; 0.0; 0.0 +1378754985; 1; 2599.998989; 0.0; 0.0; 2097152.0; 85282.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378755285; 1; 2599.998989; 0.0; 0.0; 2097152.0; 68504.53333333334; 0.0; 0.8; 0.0; 0.0 +1378755585; 1; 2599.998989; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 1.1333333333333333; 0.0; 0.0 +1378755885; 1; 2599.998989; 39.86665116466667; 1.5333333333333334; 2097152.0; 293599.2; 161.73333333333332; 14.8; 0.0; 0.2 +1378756185; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 378883.4666666667; 0.0; 1.4; 0.0; 0.0 +1378756485; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 155188.53333333333; 31.8; 3.533333333333333; 0.0; 0.0 +1378756785; 1; 2599.998989; 0.0; 0.0; 2097152.0; 152390.93333333332; 0.0; 1.2; 0.0; 0.0 +1378757085; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 96467.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1378757385; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 156584.8; 0.06666666666666667; 2.6666666666666665; 0.0; 0.4666666666666667 +1378757685; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 139809.06666666668; 0.0; 0.9333333333333333; 0.0; 0.0 +1378757985; 1; 2599.998989; 0.0; 0.0; 2097152.0; 121633.6; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1378758285; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 89476.53333333334; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1378758585; 1; 2599.998989; 0.0; 0.0; 2097152.0; 79689.6; 0.0; 0.9333333333333333; 1.2666666666666666; 0.0 +1378758885; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 111846.13333333333; 0.13333333333333333; 7.066666666666666; 0.2; 0.13333333333333333 +1378759185; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 111846.4; 0.0; 1.9333333333333333; 0.0; 0.0 +1378759485; 1; 2599.998989; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 1.0; 0.06666666666666667; 0.0 +1378759785; 1; 2599.998989; 0.0; 0.0; 2097152.0; 128624.26666666666; 0.0; 1.2; 0.0; 0.0 +1378760085; 1; 2599.998989; 0.0; 0.0; 2097152.0; 125828.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1378760385; 1; 2599.998989; 0.0; 0.0; 2097152.0; 118837.33333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378760685; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 135614.93333333332; 0.0; 0.9333333333333333; 0.0; 0.0 +1378760985; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 139808.0; 0.0; 2.6; 0.06666666666666667; 0.4666666666666667 +1378761285; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 135613.86666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1378761585; 1; 2599.998989; 0.0; 0.0; 2097152.0; 89476.53333333334; 0.0; 1.0; 0.0; 0.0 +1378761885; 1; 2599.998989; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 1.0; 0.0; 0.0 +1378762185; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 100661.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1378762485; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 104856.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1378762785; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 106254.13333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1378763086; 1; 2599.998989; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 1.0; 0.0; 0.0 +1378763386; 1; 2599.998989; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378763686; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 124429.86666666667; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378763986; 1; 2599.998989; 0.0; 0.0; 2097152.0; 110448.53333333334; 0.0; 1.3333333333333333; 0.0; 0.0 +1378764286; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 130022.4; 0.0; 1.0; 0.0; 0.0 +1378764586; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 123031.73333333334; 0.06666666666666667; 2.6; 0.0; 0.5333333333333333 +1378764886; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 89476.53333333334; 0.0; 7.066666666666666; 0.2; 0.13333333333333333 +1378765186; 1; 2599.998989; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 1.1333333333333333; 0.0; 0.0 +1378765486; 1; 2599.998989; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 1.0; 0.0; 0.0 +1378765786; 1; 2599.998989; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378766086; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 125827.46666666666; 0.06666666666666667; 1.6666666666666667; 0.06666666666666667; 0.13333333333333333 +1378766385; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 116040.8; 0.0; 1.0666666666666667; 0.0; 0.0 +1378766685; 1; 2599.998989; 0.0; 0.0; 2097152.0; 132818.66666666666; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1378766985; 1; 2599.998989; 0.0; 0.0; 2097152.0; 127226.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378767285; 1; 2599.998989; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1378767585; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 134216.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1378767885; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 104856.0; 0.0; 1.0; 0.0; 0.0 +1378768185; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 163576.8; 0.06666666666666667; 2.6666666666666665; 0.0; 0.4666666666666667 +1378768486; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 130022.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1378768786; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 125828.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1378769086; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 127226.13333333333; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378769386; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 85282.13333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1378769686; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 121633.6; 0.0; 0.8; 0.0; 0.0 +1378769986; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 138411.2; 0.0; 0.8; 0.0; 0.0 +1378770286; 1; 2599.998989; 20.799991912; 0.8; 2097152.0; 131419.73333333334; 0.0; 1.2; 0.0; 0.0 +1378770586; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 124429.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378770886; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 111846.66666666667; 0.0; 1.2; 0.0; 0.0 +1378771186; 1; 2599.998989; 20.799991912; 0.8; 2097152.0; 111846.66666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378771486; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 121633.6; 0.06666666666666667; 7.2; 0.26666666666666666; 0.13333333333333333 +1378771786; 1; 2599.998989; 22.53332457133334; 0.8666666666666667; 2097152.0; 156585.6; 0.0; 2.3333333333333335; 0.06666666666666667; 0.4666666666666667 +1378772086; 1; 2599.998989; 20.799991912; 0.8; 2097152.0; 169168.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1378772386; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 109050.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1378772686; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 128623.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378772986; 1; 2599.998989; 0.0; 0.0; 2097152.0; 121633.6; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378773286; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 113244.8; 0.5333333333333333; 1.3333333333333333; 0.0; 0.0 +1378773586; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 114642.93333333333; 0.0; 1.3333333333333333; 0.0; 0.0 +1378773886; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 100661.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1378774186; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 124429.86666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1378774486; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 97865.33333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378774786; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 78291.46666666666; 0.0; 1.0; 0.0; 0.0 +1378775086; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 96467.2; 0.0; 1.0; 0.0; 0.0 +1378775386; 1; 2599.998989; 25.999989890000002; 1.0; 2097152.0; 118837.33333333333; 0.0; 2.2; 0.0; 0.5333333333333333 +1378775686; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 146800.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1378775986; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 120235.46666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378776286; 1; 2599.998989; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1378776586; 1; 2599.998989; 0.0; 0.0; 2097152.0; 110448.53333333334; 0.0; 0.8; 0.0; 0.0 +1378776886; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 141207.46666666667; 0.0; 7.133333333333334; 0.2; 0.13333333333333333 +1378777186; 1; 2599.998989; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378777486; 1; 2599.998989; 0.0; 0.0; 2097152.0; 123031.73333333334; 0.0; 1.0666666666666667; 0.0; 0.0 +1378777787; 1; 2599.998989; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1378778087; 1; 2599.998989; 0.0; 0.0; 2097152.0; 79689.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1378778387; 1; 2599.998989; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1378778687; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 131420.53333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378778987; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 121633.06666666667; 0.0; 2.533333333333333; 0.0; 0.4666666666666667 +1378779287; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 135614.66666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378779587; 1; 2599.998989; 0.0; 0.0; 2097152.0; 123031.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1378779887; 1; 2599.998989; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378780187; 1; 2599.998989; 0.0; 0.0; 2097152.0; 116041.06666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378780487; 1; 2599.998989; 0.0; 0.0; 2097152.0; 118837.33333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1378780787; 1; 2599.998989; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378781087; 1; 2599.998989; 0.0; 0.0; 2097152.0; 95069.06666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1378781387; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 107652.26666666666; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1378781687; 1; 2599.998989; 0.0; 0.0; 2097152.0; 123031.73333333334; 0.0; 0.8; 0.0; 0.0 +1378781987; 1; 2599.998989; 0.0; 0.0; 2097152.0; 107652.26666666666; 0.0; 1.3333333333333333; 0.0; 0.0 +1378782287; 1; 2599.998989; 0.0; 0.0; 2097152.0; 110448.53333333334; 0.0; 1.0666666666666667; 0.0; 0.0 +1378782587; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 176158.66666666666; 0.06666666666666667; 2.6; 0.0; 0.4666666666666667 +1378782887; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 194334.13333333333; 0.0; 7.0; 0.2; 0.13333333333333333 +1378783187; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 153790.4; 0.0; 1.1333333333333333; 0.0; 0.0 +1378783487; 1; 2599.998989; 0.0; 0.0; 2097152.0; 83884.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1378783787; 1; 2599.998989; 0.0; 0.0; 2097152.0; 114642.93333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378784087; 1; 2599.998989; 0.0; 0.0; 2097152.0; 100661.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1378784387; 1; 2599.998989; 0.0; 0.0; 2097152.0; 81087.73333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1378784687; 1; 2599.998989; 0.0; 0.0; 2097152.0; 110448.53333333334; 0.0; 1.6; 0.0; 0.0 +1378784987; 1; 2599.998989; 0.0; 0.0; 2097152.0; 116041.06666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1378785287; 1; 2599.998989; 0.0; 0.0; 2097152.0; 139808.8; 0.0; 0.7333333333333333; 0.06666666666666667; 0.0 +1378785587; 1; 2599.998989; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1378785887; 1; 2599.998989; 0.0; 0.0; 2097152.0; 120235.46666666666; 0.0; 1.1333333333333333; 0.0; 0.0 +1378786187; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 145400.26666666666; 0.06666666666666667; 2.3333333333333335; 0.0; 0.4666666666666667 +1378786487; 1; 2599.998989; 0.0; 0.0; 2097152.0; 164973.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1378786787; 1; 2599.998989; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 1.0; 0.0; 0.0 +1378787088; 1; 2599.998989; 0.0; 0.0; 2097152.0; 124429.86666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1378787388; 1; 2599.998989; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 1.1333333333333333; 0.0; 0.0 +1378787688; 1; 2599.998989; 0.0; 0.0; 2097152.0; 86680.26666666666; 0.0; 0.8; 0.0; 0.0 +1378787988; 1; 2599.998989; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1378788288; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 102059.73333333334; 0.0; 7.2; 0.2; 0.13333333333333333 +1378788588; 1; 2599.998989; 0.0; 0.0; 2097152.0; 144002.93333333332; 0.0; 1.0; 0.0; 0.0 +1378788888; 1; 2599.998989; 0.0; 0.0; 2097152.0; 120235.46666666666; 0.0; 1.2; 0.0; 0.0 +1378789188; 1; 2599.998989; 0.0; 0.0; 2097152.0; 81087.73333333334; 0.0; 1.0666666666666667; 0.0; 0.0 +1378789488; 1; 2599.998989; 0.0; 0.0; 2097152.0; 86680.26666666666; 0.0; 1.0666666666666667; 0.0; 0.0 +1378789788; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 132817.86666666667; 0.0; 2.3333333333333335; 0.0; 0.4666666666666667 +1378790088; 1; 2599.998989; 0.0; 0.0; 2097152.0; 138411.2; 0.0; 1.0; 0.0; 0.0 +1378790388; 1; 2599.998989; 0.0; 0.0; 2097152.0; 146799.2; 0.0; 0.7333333333333333; 0.0; 0.0 +1378790688; 1; 2599.998989; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1378790988; 1; 2599.998989; 0.0; 0.0; 2097152.0; 89476.53333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1378791288; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 93670.93333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378791588; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 109050.4; 0.0; 1.2666666666666666; 0.0; 0.0 +1378791888; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 116041.06666666667; 0.0; 1.2666666666666666; 0.0; 0.0 +1378792188; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 127226.13333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1378792488; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 134216.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1378792788; 1; 2599.998989; 0.0; 0.0; 2097152.0; 137013.06666666668; 0.0; 1.0; 0.0; 0.0 +1378793088; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 107652.26666666666; 0.0; 1.2; 0.0; 0.0 +1378793388; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 104856.0; 0.06666666666666667; 2.6666666666666665; 0.0; 0.4666666666666667 +1378793688; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 104856.0; 0.0; 1.0; 0.0; 0.0 +1378793988; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 102059.73333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1378794288; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 127226.13333333333; 0.0; 1.4; 0.0; 0.0 +1378794588; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 113244.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1378794889; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 131420.53333333333; 0.06666666666666667; 8.4; 0.26666666666666666; 0.2 +1378795189; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 123031.73333333334; 0.0; 1.8; 0.0; 0.0 +1378795489; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 116041.06666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1378795789; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 111846.66666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1378796089; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 156586.93333333332; 0.0; 1.0; 0.0; 0.0 +1378796389; 1; 2599.998989; 0.0; 0.0; 2097152.0; 116041.06666666667; 0.0; 1.0; 0.0; 0.0 +1378796689; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 89476.53333333334; 0.0; 0.7333333333333333; 0.0; 0.0 +1378796989; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 137012.0; 0.06666666666666667; 2.6; 0.0; 0.4666666666666667 +1378797289; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 145400.53333333333; 0.0; 11.866666666666667; 0.0; 0.0 +1378797589; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 123031.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1378797889; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 106254.13333333333; 0.0; 0.6666666666666666; 0.0; 0.0 +1378798189; 1; 2599.998989; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 0.8; 0.0; 0.0 +1378798489; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 131420.53333333333; 0.0; 1.2666666666666666; 0.0; 0.0 +1378798789; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 89476.53333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1378799089; 1; 2599.998989; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 1.2666666666666666; 0.0; 0.0 +1378799388; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 82485.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378799688; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 125828.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1378799988; 1; 2599.998989; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1378800289; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 120235.2; 0.0; 0.8; 0.0; 0.0 +1378800589; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 150993.06666666668; 0.06666666666666667; 2.4; 0.0; 0.4666666666666667 +1378800889; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 150993.6; 0.0; 7.266666666666667; 0.2; 0.13333333333333333 +1378801189; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 135614.13333333333; 0.0; 1.2666666666666666; 0.0; 0.0 +1378801489; 1; 2599.998989; 72.799971692; 2.8; 2097152.0; 661300.2666666667; 161.2; 22.2; 0.06666666666666667; 0.4 +1378801789; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 500518.13333333336; 0.0; 18.933333333333334; 0.0; 0.0 +1378802089; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 237674.66666666666; 31.8; 3.6; 0.0; 0.0 +1378802389; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 148197.6; 5.066666666666666; 2.4; 0.0; 0.0 +1378802689; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 124429.86666666667; 0.0; 1.8; 0.0; 0.0 +1378802989; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 93670.93333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378803289; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 82485.86666666667; 0.0; 0.8; 0.0; 0.0 +1378803589; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 79689.6; 0.0; 0.8; 0.0; 0.0 +1378803889; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 67106.4; 0.0; 1.8666666666666667; 0.0; 0.0 +1378804189; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 148196.8; 3.8666666666666667; 2.533333333333333; 0.06666666666666667; 0.4666666666666667 +1378804489; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 174761.06666666668; 0.0; 0.7333333333333333; 0.0; 0.0 +1378804789; 1; 2599.998989; 0.0; 0.0; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378805089; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 95069.06666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378805389; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 69902.66666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1378805689; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 89476.53333333334; 0.0; 0.8; 0.0; 0.0 +1378805989; 1; 2599.998989; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1378806289; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 100661.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1378806589; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 117439.2; 0.0; 0.8; 0.0; 0.0 +1378806889; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 117439.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1378807189; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 149596.26666666666; 0.0; 1.0; 0.0; 0.0 +1378807489; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 164974.13333333333; 12.933333333333334; 13.266666666666667; 0.3333333333333333; 0.2 +1378807789; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 155186.93333333332; 0.0; 2.4; 0.13333333333333333; 0.4666666666666667 +1378808089; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 146799.46666666667; 0.0; 1.3333333333333333; 0.06666666666666667; 0.0 +1378808389; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1378808689; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 116041.06666666667; 1.0; 1.6; 0.06666666666666667; 0.0 +1378808990; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 164974.66666666666; 0.0; 1.0; 0.0; 0.0 +1378809290; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 137012.53333333333; 0.0; 1.0; 0.06666666666666667; 0.0 +1378809590; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 128624.26666666666; 0.0; 1.1333333333333333; 0.0; 0.0 +1378809890; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 110448.53333333334; 0.0; 1.0666666666666667; 0.0; 0.0 +1378810190; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 137013.06666666668; 0.0; 1.0; 0.0; 0.0 +1378810490; 1; 2599.998989; 0.0; 0.0; 2097152.0; 88078.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1378810790; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 102059.73333333334; 0.0; 1.1333333333333333; 0.0; 0.0 +1378811090; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 138410.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1378811390; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 127226.13333333333; 0.0; 2.4; 0.0; 0.5333333333333333 +1378811690; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 141206.93333333332; 0.0; 0.9333333333333333; 0.0; 0.0 +1378811990; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 110448.53333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1378812290; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 124429.86666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1378812590; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 85282.13333333333; 0.0; 1.0; 0.06666666666666667; 0.0 +1378812890; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 144003.46666666667; 0.0; 1.0; 0.0; 0.0 +1378813190; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 135614.13333333333; 0.0; 2.3333333333333335; 0.2; 0.13333333333333333 +1378813490; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 164974.4; 0.0; 6.2; 0.0; 0.0 +1378813790; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 130022.4; 0.0; 1.0666666666666667; 0.0; 0.0 +1378814090; 1; 2599.998989; 0.0; 0.0; 2097152.0; 106254.13333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1378814390; 1; 2599.998989; 0.0; 0.0; 2097152.0; 121633.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1378814690; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 120235.46666666666; 1.4; 1.2666666666666666; 0.0; 0.0 +1378814990; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 160779.46666666667; 0.0; 2.533333333333333; 0.06666666666666667; 0.5333333333333333 +1378815290; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 153789.06666666668; 0.0; 0.9333333333333333; 0.0; 0.0 +1378815590; 1; 2599.998989; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1378815890; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 95069.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378816190; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 83884.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1378816490; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 114642.93333333333; 0.0; 1.3333333333333333; 0.0; 0.0 +1378816790; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 117439.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1378817090; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 121633.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1378817390; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 109050.4; 0.0; 1.0; 0.0; 0.0 +1378817690; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 92272.8; 0.0; 1.0666666666666667; 0.0; 0.0 +1378817990; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 104856.0; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378818290; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 109050.4; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378818590; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 125828.0; 0.06666666666666667; 2.6; 0.0; 0.4666666666666667 +1378818890; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 134216.0; 0.0; 1.0; 0.0; 0.0 +1378819190; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 92272.8; 0.0; 1.2; 6.333333333333333; 0.0 +1378819490; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 104856.0; 0.06666666666666667; 7.066666666666666; 0.4; 0.13333333333333333 +1378819791; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 176159.2; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378820091; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 125827.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1378820391; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 93670.93333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378820691; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 104856.0; 0.0; 1.2; 0.13333333333333333; 0.0 +1378820991; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 102059.73333333334; 0.0; 1.0; 0.0; 0.0 +1378821291; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 121633.6; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378821591; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 121633.6; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378821891; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 104856.0; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1378822191; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 128624.26666666666; 0.0; 2.3333333333333335; 0.0; 0.5333333333333333 +1378822491; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 146799.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1378822791; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 155188.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1378823091; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 125828.0; 0.0; 0.8; 0.06666666666666667; 0.0 +1378823391; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 75495.2; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1378823691; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 125828.0; 0.0; 1.2; 0.06666666666666667; 0.13333333333333333 +1378823991; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 134216.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1378824291; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 83884.0; 0.0; 1.0666666666666667; 0.3333333333333333; 0.0 +1378824591; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 100661.6; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378824891; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 97865.33333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1378825191; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 155188.0; 0.0; 6.866666666666666; 0.26666666666666666; 0.13333333333333333 +1378825491; 1; 2599.998989; 0.0; 0.0; 2097152.0; 150993.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1378825791; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 171964.53333333333; 0.0; 2.0; 0.13333333333333333; 0.4666666666666667 +1378826091; 1; 2599.998989; 0.0; 0.0; 2097152.0; 138410.4; 0.06666666666666667; 1.4; 0.0; 0.0 +1378826391; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 97865.33333333333; 0.0; 0.8666666666666667; 0.13333333333333333; 0.0 +1378826691; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 113244.8; 0.0; 0.8; 0.0; 0.0 +1378826991; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1378827291; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 117439.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1378827591; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 110448.53333333334; 0.0; 1.2; 0.13333333333333333; 0.0 +1378827891; 1; 2599.998989; 0.0; 0.0; 2097152.0; 114642.93333333333; 0.0; 1.3333333333333333; 0.0; 0.0 +1378828191; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 145400.8; 0.0; 1.0; 0.0; 0.0 +1378828491; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 132818.66666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378828791; 1; 2599.998989; 0.0; 0.0; 2097152.0; 131420.53333333333; 0.0; 1.0; 0.2; 0.0 +1378829091; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 97865.33333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378829391; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 117439.2; 0.13333333333333333; 2.4; 0.06666666666666667; 0.4666666666666667 +1378829691; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 95069.06666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378829991; 1; 2599.998989; 0.0; 0.0; 2097152.0; 176159.2; 0.0; 0.8666666666666667; 0.13333333333333333; 0.0 +1378830291; 1; 2599.998989; 0.0; 0.0; 2097152.0; 150993.33333333334; 0.0; 1.1333333333333333; 0.0; 0.0 +1378830591; 1; 2599.998989; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378830892; 1; 2599.998989; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1378831192; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 103457.86666666667; 0.0; 0.9333333333333333; 0.13333333333333333; 0.0 +1378831492; 1; 2599.998989; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 1.0; 0.0; 0.0 +1378831791; 1; 2599.998989; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 1.0; 0.0; 0.0 +1378832091; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 134216.8; 0.06666666666666667; 7.066666666666666; 0.26666666666666666; 0.13333333333333333 +1378832391; 1; 2599.998989; 0.0; 0.0; 2097152.0; 132817.6; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378832691; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 113244.8; 0.0; 1.0; 0.06666666666666667; 0.0 +1378832991; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 96467.2; 0.0; 2.533333333333333; 0.0; 0.4666666666666667 +1378833291; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 117439.2; 0.0; 1.0; 0.0; 0.0 +1378833591; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 123031.73333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1378833891; 1; 2599.998989; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 0.8666666666666667; 0.13333333333333333; 0.0 +1378834191; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 120235.46666666666; 0.0; 0.9333333333333333; 0.26666666666666666; 0.0 +1378834492; 1; 2599.998989; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 1.1333333333333333; 0.0; 0.0 +1378834792; 1; 2599.998989; 0.0; 0.0; 2097152.0; 102059.73333333334; 0.0; 1.0; 0.0; 0.0 +1378835092; 1; 2599.998989; 0.0; 0.0; 2097152.0; 97865.33333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1378835392; 1; 2599.998989; 0.0; 0.0; 2097152.0; 120235.46666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378835692; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 124429.86666666667; 0.0; 1.2; 0.0; 0.0 +1378835992; 1; 2599.998989; 0.0; 0.0; 2097152.0; 159383.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1378836292; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 138411.2; 0.0; 1.6; 0.0; 0.0 +1378836592; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 180352.8; 0.0; 2.066666666666667; 0.0; 0.4666666666666667 +1378836892; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 159383.2; 0.0; 0.9333333333333333; 0.0; 0.0 +1378837192; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 138410.13333333333; 0.0; 1.3333333333333333; 0.0; 0.0 +1378837492; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 104856.0; 0.0; 7.133333333333334; 0.26666666666666666; 0.13333333333333333 +1378837792; 1; 2599.998989; 0.0; 0.0; 2097152.0; 121633.6; 0.0; 1.0; 0.0; 0.0 +1378838092; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 92272.8; 0.0; 0.8; 0.06666666666666667; 0.0 +1378838392; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 100661.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1378838692; 1; 2599.998989; 0.0; 0.0; 2097152.0; 90874.66666666667; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1378838992; 1; 2599.998989; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1378839292; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 92272.8; 0.0; 0.8666666666666667; 0.4666666666666667; 0.0 +1378839592; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 100661.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1378839892; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 90874.66666666667; 0.0; 1.2; 0.06666666666666667; 0.0 +1378840192; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 170566.93333333332; 0.0; 2.466666666666667; 0.0; 0.5333333333333333 +1378840492; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 123031.73333333334; 0.0; 1.0; 0.06666666666666667; 0.0 +1378840792; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 89476.53333333334; 0.0; 0.8; 0.0; 0.0 +1378841092; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 109050.4; 0.0; 11.8; 0.0; 0.0 +1378841392; 1; 2599.998989; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 1.2; 0.0; 0.0 +1378841692; 1; 2599.998989; 0.0; 0.0; 2097152.0; 81087.73333333334; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378841992; 1; 2599.998989; 0.0; 0.0; 2097152.0; 128624.26666666666; 0.0; 1.0; 0.06666666666666667; 0.0 +1378842293; 1; 2599.998989; 41.599983824; 1.6; 2097152.0; 251656.8; 161.93333333333334; 14.8; 1.4; 0.2 +1378842593; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 418030.4; 0.0; 1.6; 0.0; 0.0 +1378842893; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 198528.53333333333; 31.8; 3.7333333333333334; 0.0; 0.0 +1378843193; 1; 2599.998989; 0.0; 0.0; 2097152.0; 199927.46666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378843493; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 157984.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378843793; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 194334.13333333333; 0.0; 2.4; 0.0; 0.4666666666666667 +1378844093; 1; 2599.998989; 0.0; 0.0; 2097152.0; 171965.33333333334; 0.0; 1.0; 0.0; 0.0 +1378844393; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 127225.6; 0.0; 7.2; 0.4666666666666667; 0.13333333333333333 +1378844693; 1; 2599.998989; 0.0; 0.0; 2097152.0; 121633.33333333333; 0.0; 1.0; 0.0; 0.0 +1378844993; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 142605.06666666668; 0.0; 0.9333333333333333; 0.0; 0.0 +1378845293; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 134216.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1378845593; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 132818.66666666666; 0.0; 1.0; 0.06666666666666667; 0.0 +1378845893; 1; 2599.998989; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.2; 0.0 +1378846193; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 89476.53333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1378846493; 1; 2599.998989; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1378846793; 1; 2599.998989; 0.0; 0.0; 2097152.0; 113244.8; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1378847093; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 93670.93333333333; 0.0; 1.0; 0.0; 0.0 +1378847393; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 130021.6; 0.0; 2.466666666666667; 0.06666666666666667; 0.4666666666666667 +1378847693; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 135614.93333333332; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378847993; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 123031.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1378848293; 1; 2599.998989; 0.0; 0.0; 2097152.0; 99263.46666666666; 0.0; 1.8666666666666667; 0.0; 0.0 +1378848593; 1; 2599.998989; 0.0; 0.0; 2097152.0; 125828.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1378848893; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 117438.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1378849193; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 103457.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378849493; 1; 2599.998989; 0.0; 0.0; 2097152.0; 120235.46666666666; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378849793; 1; 2599.998989; 0.0; 0.0; 2097152.0; 139808.53333333333; 0.0; 1.2666666666666666; 0.0; 0.0 +1378850093; 1; 2599.998989; 0.0; 0.0; 2097152.0; 150993.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1378850393; 1; 2599.998989; 0.0; 0.0; 2097152.0; 116040.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378850693; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 82485.86666666667; 0.0; 1.5333333333333334; 0.2; 0.13333333333333333 +1378850993; 1; 2599.998989; 20.799991912; 0.8; 2097152.0; 146799.2; 0.0; 8.0; 0.06666666666666667; 0.4666666666666667 +1378851293; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 125828.0; 0.0; 1.0; 0.0; 0.0 +1378851593; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 118837.33333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378851893; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 134216.0; 0.0; 1.0; 0.0; 0.0 +1378852193; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 76893.33333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378852493; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 139808.53333333333; 0.06666666666666667; 1.8; 0.06666666666666667; 0.13333333333333333 +1378852794; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 113244.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1378853094; 1; 2599.998989; 22.53332457133334; 0.8666666666666667; 2097152.0; 248860.8; 169.66666666666666; 179.86666666666667; 0.0; 0.0 +1378853394; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 167770.93333333332; 0.0; 1.0; 0.0; 0.0 +1378853694; 1; 2599.998989; 69.33330637333334; 2.666666666666667; 2097152.0; 640328.5333333333; 161.06666666666666; 22.0; 0.0; 0.4 +1378853994; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 408243.73333333334; 0.0; 2.7333333333333334; 0.0; 0.0 +1378854294; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 233480.26666666666; 31.8; 3.6; 0.0; 0.0 +1378854594; 1; 2599.998989; 24.266657230666667; 0.9333333333333332; 2097152.0; 152391.73333333334; 13.333333333333334; 3.7333333333333334; 0.0; 0.4666666666666667 +1378854894; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 145401.06666666668; 0.0; 1.4666666666666666; 0.0; 0.0 +1378855194; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 123031.2; 0.0; 1.0666666666666667; 0.0; 0.0 +1378855494; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 103457.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1378855794; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 130022.4; 0.0; 0.8; 0.0; 0.0 +1378856094; 1; 2599.998989; 20.799991912; 0.8; 2097152.0; 131419.73333333334; 0.06666666666666667; 1.4; 0.0; 0.0 +1378856394; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 125828.0; 0.0; 1.2; 0.0; 0.0 +1378856694; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 144003.73333333334; 0.0; 7.666666666666667; 0.2; 0.13333333333333333 +1378856994; 1; 2599.998989; 20.799991912; 0.8; 2097152.0; 159382.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1378857294; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 97865.33333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1378857594; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 97865.33333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378857894; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 97865.33333333333; 0.0; 1.2666666666666666; 0.0; 0.0 +1378858194; 1; 2599.998989; 20.799991912; 0.8; 2097152.0; 157984.0; 0.06666666666666667; 2.466666666666667; 0.0; 0.4666666666666667 +1378858494; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 144002.93333333332; 0.0; 1.0; 0.06666666666666667; 0.0 +1378858794; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 93670.93333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378859094; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 124429.86666666667; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378859394; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 123031.73333333334; 0.0; 1.0; 0.0; 0.0 +1378859694; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 117439.2; 0.5333333333333333; 1.3333333333333333; 0.0; 0.0 +1378859994; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 90874.66666666667; 0.0; 1.0; 0.0; 0.0 +1378860294; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 93670.93333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378860594; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 111846.66666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1378860894; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 114642.93333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378861194; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 106254.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378861494; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 141206.93333333332; 0.0; 1.0; 0.0; 0.0 +1378861794; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 183149.86666666667; 0.0; 2.3333333333333335; 0.06666666666666667; 0.4666666666666667 +1378862094; 1; 2599.998989; 20.799991912; 0.8; 2097152.0; 152392.53333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1378862394; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 121633.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1378862694; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 89476.53333333334; 0.0; 1.0; 0.0; 0.0 +1378862994; 1; 2599.998989; 20.799991912; 0.8; 2097152.0; 124429.86666666667; 0.0; 7.066666666666666; 0.26666666666666666; 0.06666666666666667 +1378863294; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 155188.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1378863595; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 132818.66666666666; 0.0; 1.1333333333333333; 0.0; 0.0 +1378863895; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 117439.2; 0.0; 1.0; 0.0; 0.0 +1378864195; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 88078.4; 0.0; 0.8; 0.0; 0.0 +1378864494; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 116041.06666666667; 0.0; 0.8; 0.0; 0.0 +1378864794; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 128623.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1378865094; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 124429.86666666667; 0.0; 1.0; 0.0; 0.0 +1378865394; 1; 2599.998989; 22.53332457133334; 0.8666666666666667; 2097152.0; 114642.93333333333; 0.06666666666666667; 2.533333333333333; 0.06666666666666667; 0.4666666666666667 +1378865694; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 137013.06666666668; 0.0; 0.8666666666666667; 0.0; 0.0 +1378865994; 1; 2599.998989; 19.066659252666668; 0.7333333333333333; 2097152.0; 95069.06666666667; 0.0; 0.8; 0.0; 0.0 +1378866294; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 95069.06666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1378866594; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 103457.86666666667; 0.0; 0.7333333333333333; 0.0; 0.0 +1378866894; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 134216.8; 0.0; 0.6666666666666666; 0.0; 0.0 +1378867194; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 107652.26666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378867494; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 118837.33333333333; 0.0; 1.0; 0.0; 0.0 +1378867794; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 120235.46666666666; 0.0; 1.2; 0.0; 0.0 +1378868094; 1; 2599.998989; 15.599993934000002; 0.6; 2097152.0; 102059.73333333334; 0.0; 0.6666666666666666; 0.06666666666666667; 0.0 +1378868394; 1; 2599.998989; 0.0; 0.0; 2097152.0; 95069.06666666667; 0.0; 0.7333333333333333; 1.2666666666666666; 0.0 +1378868694; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 137012.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378868994; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 178955.2; 12.8; 14.2; 0.3333333333333333; 0.7333333333333333 +1378869294; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 201325.06666666668; 0.0; 1.2666666666666666; 0.0; 0.0 +1378869594; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 171964.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1378869894; 1; 2599.998989; 0.0; 0.0; 2097152.0; 131420.0; 0.0; 0.8; 0.0; 0.0 +1378870194; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 99263.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378870494; 1; 2599.998989; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 1.0; 0.0; 0.0 +1378870794; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 88078.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1378871094; 1; 2599.998989; 0.0; 0.0; 2097152.0; 95069.06666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1378871394; 1; 2599.998989; 0.0; 0.0; 2097152.0; 118837.33333333333; 0.0; 0.7333333333333333; 0.0; 0.0 +1378871695; 1; 2599.998989; 0.0; 0.0; 2097152.0; 124429.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378871995; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 88078.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1378872295; 1; 2599.998989; 0.0; 0.0; 2097152.0; 96467.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1378872595; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 157983.2; 0.0; 2.3333333333333335; 0.0; 0.4666666666666667 +1378872895; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 116041.06666666667; 0.0; 0.8; 0.0; 0.0 +1378873195; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 130022.4; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1378873495; 1; 2599.998989; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.6666666666666666; 0.06666666666666667; 0.0 +1378873795; 1; 2599.998989; 0.0; 0.0; 2097152.0; 79689.6; 0.0; 0.6666666666666666; 0.0; 0.0 +1378874095; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 107652.26666666666; 0.0; 0.6666666666666666; 0.0; 0.0 +1378874395; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 118837.33333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378874695; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 118837.33333333333; 0.0; 1.2666666666666666; 0.06666666666666667; 0.0 +1378874995; 1; 2599.998989; 0.0; 0.0; 2097152.0; 128624.26666666666; 0.0; 0.8; 0.06666666666666667; 0.0 +1378875295; 1; 2599.998989; 0.0; 0.0; 2097152.0; 124429.6; 0.0; 0.8; 0.0; 0.0 +1378875595; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 142604.8; 0.0; 7.2; 0.26666666666666666; 0.2 +1378875895; 1; 2599.998989; 0.0; 0.0; 2097152.0; 111846.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378876195; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 114642.93333333333; 0.06666666666666667; 2.2666666666666666; 0.0; 0.4666666666666667 +1378876495; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 157984.26666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378876795; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 131420.53333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378877095; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 127226.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378877395; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 127225.33333333333; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1378877695; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 124429.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378877995; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 100661.6; 0.0; 1.1333333333333333; 0.0; 0.0 +1378878295; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 134216.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1378878595; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 121633.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1378878895; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 118837.33333333333; 0.0; 1.0; 0.0; 0.0 +1378879195; 1; 2599.998989; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1378879495; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 106254.13333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378879795; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 157983.73333333334; 0.06666666666666667; 2.066666666666667; 0.0; 0.4666666666666667 +1378880095; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 139808.53333333333; 0.0; 1.1333333333333333; 0.0; 0.0 +1378880396; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 118837.33333333333; 0.0; 1.0; 0.0; 0.0 +1378880696; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 134216.53333333333; 0.0; 0.9333333333333333; 0.0; 0.0 +1378880996; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 148197.6; 0.0; 6.933333333333334; 0.26666666666666666; 0.2 +1378881296; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 139808.53333333333; 0.0; 2.4; 0.0; 0.06666666666666667 +1378881596; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 110448.53333333334; 0.0; 2.1333333333333333; 0.0; 0.0 +1378881896; 1; 2599.998989; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378882196; 1; 2599.998989; 0.0; 0.0; 2097152.0; 118837.33333333333; 0.0; 1.0; 0.0; 0.0 +1378882496; 1; 2599.998989; 0.0; 0.0; 2097152.0; 75495.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1378882796; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 95069.06666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1378883096; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 113244.8; 1.4666666666666666; 1.4; 0.0; 0.0 +1378883396; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 195732.26666666666; 0.0; 2.2666666666666666; 0.0; 0.5333333333333333 +1378883696; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 199926.93333333332; 0.0; 0.9333333333333333; 0.0; 0.0 +1378883996; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 104856.0; 0.0; 0.8666666666666667; 0.06666666666666667; 0.0 +1378884296; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 130022.4; 0.0; 1.1333333333333333; 0.06666666666666667; 0.0 +1378884596; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 109050.4; 0.0; 11.8; 1.7333333333333334; 0.0 +1378884896; 1; 2599.998989; 0.0; 0.0; 2097152.0; 128624.26666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378885196; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 106254.13333333333; 0.0; 0.9333333333333333; 0.06666666666666667; 0.0 +1378885496; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 92272.8; 0.0; 1.8; 0.0; 0.0 +1378885796; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 116041.06666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1378886096; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 134216.8; 0.0; 0.9333333333333333; 0.0; 0.0 +1378886396; 1; 2599.998989; 0.0; 0.0; 2097152.0; 109050.4; 0.0; 0.9333333333333333; 0.0; 0.0 +1378886696; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 124429.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378886996; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 157983.2; 0.13333333333333333; 9.266666666666667; 0.26666666666666666; 0.6 +1378887296; 1; 2599.998989; 12.133328615333333; 0.4666666666666666; 2097152.0; 178955.73333333334; 0.0; 0.9333333333333333; 0.0; 0.0 +1378887596; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 111846.66666666667; 0.0; 0.8666666666666667; 0.9333333333333333; 0.0 +1378887896; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 118837.33333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378888196; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 106254.13333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1378888496; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 106254.13333333333; 0.0; 1.2; 0.0; 0.0 +1378888796; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 113244.8; 0.0; 1.0; 0.0; 0.0 +1378889096; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 142604.0; 0.0; 0.8666666666666667; 0.0; 0.0 +1378889396; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 109049.86666666667; 0.0; 1.0; 0.0; 0.0 +1378889696; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 106254.13333333333; 0.0; 1.0666666666666667; 0.06666666666666667; 0.0 +1378889997; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 92272.8; 0.0; 0.8666666666666667; 0.13333333333333333; 0.0 +1378890297; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 137013.06666666668; 0.0; 1.0; 0.0; 0.0 +1378890597; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 156585.33333333334; 0.2; 2.4; 0.0; 0.4666666666666667 +1378890897; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 111846.66666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378891197; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 124429.86666666667; 0.0; 1.1333333333333333; 0.0; 0.0 +1378891497; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 107652.26666666666; 0.0; 0.8666666666666667; 2.0; 0.0 +1378891797; 1; 2599.998989; 0.0; 0.0; 2097152.0; 103457.86666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378892097; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 109050.4; 0.0; 0.8666666666666667; 0.0; 0.0 +1378892397; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 116041.06666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378892697; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 92272.8; 0.0; 8.066666666666666; 0.2; 0.13333333333333333 +1378892997; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 130021.6; 0.0; 0.9333333333333333; 0.0; 0.0 +1378893297; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 146799.2; 20.4; 2.1333333333333333; 0.0; 0.06666666666666667 +1378893597; 1; 2599.998989; 22.53332457133334; 0.8666666666666667; 2097152.0; 149596.26666666666; 0.0; 4.066666666666666; 0.0; 0.0 +1378893897; 1; 2599.998989; 13.866661274666667; 0.5333333333333333; 2097152.0; 183149.86666666667; 0.0; 2.8; 0.0; 0.0 +1378894197; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 148197.06666666668; 0.0; 2.066666666666667; 0.0; 0.4666666666666667 +1378894497; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 138411.2; 0.0; 0.8666666666666667; 0.0; 0.0 +1378894797; 1; 2599.998989; 0.0; 0.0; 2097152.0; 111846.66666666667; 0.0; 0.8666666666666667; 0.0; 0.0 +1378895097; 1; 2599.998989; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378895397; 1; 2599.998989; 0.0; 0.0; 2097152.0; 106254.13333333333; 0.0; 1.2666666666666666; 0.0; 0.0 +1378895697; 1; 2599.998989; 0.0; 0.0; 2097152.0; 123031.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1378895997; 1; 2599.998989; 0.0; 0.0; 2097152.0; 123031.73333333334; 0.0; 0.8666666666666667; 0.0; 0.0 +1378896297; 1; 2599.998989; 0.0; 0.0; 2097152.0; 137012.26666666666; 0.0; 0.9333333333333333; 0.0; 0.0 +1378896597; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 100661.6; 0.0; 1.0666666666666667; 0.0; 0.0 +1378896897; 1; 2599.998989; 0.0; 0.0; 2097152.0; 93670.93333333333; 0.0; 0.8; 0.0; 0.0 +1378897197; 1; 2599.998989; 0.0; 0.0; 2097152.0; 110448.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1378897497; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 116041.06666666667; 0.0; 0.9333333333333333; 0.0; 0.0 +1378897797; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 166372.26666666666; 0.0; 2.4; 0.0; 0.5333333333333333 +1378898097; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 173362.93333333332; 0.0; 1.2; 0.0; 0.0 +1378898397; 1; 2599.998989; 0.0; 0.0; 2097152.0; 124429.86666666667; 0.0; 0.8; 0.0; 0.0 +1378898697; 1; 2599.998989; 10.399995956; 0.4; 2097152.0; 92272.8; 0.0; 7.2; 0.2; 0.13333333333333333 +1378898997; 1; 2599.998989; 0.0; 0.0; 2097152.0; 120235.46666666666; 0.0; 0.8666666666666667; 0.0; 0.0 +1378899297; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 106254.13333333333; 0.0; 0.8666666666666667; 0.0; 0.0 +1378899597; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 110448.53333333334; 0.0; 1.2666666666666666; 0.0; 0.0 +1378899897; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 160780.0; 0.0; 1.2666666666666666; 0.06666666666666667; 0.0 +1378900197; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 132817.86666666667; 0.0; 1.0666666666666667; 1.4; 0.0 +1378900497; 1; 2599.998989; 0.0; 0.0; 2097152.0; 104856.0; 0.0; 0.7333333333333333; 0.0; 0.0 +1378900797; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 97865.33333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1378901097; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 90874.66666666667; 0.0; 0.8; 0.0; 0.0 +1378901397; 1; 2599.998989; 6.933330637333333; 0.26666666666666666; 2097152.0; 130022.4; 0.06666666666666667; 2.533333333333333; 0.0; 0.5333333333333333 +1378901697; 1; 2599.998989; 0.0; 0.0; 2097152.0; 184547.2; 0.0; 1.0; 0.0; 0.0 +1378901997; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 128624.26666666666; 0.0; 1.1333333333333333; 0.0; 0.0 +1378902297; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 114642.93333333333; 0.0; 1.2; 0.0; 0.0 +1378902598; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 109050.4; 0.0; 0.8; 0.0; 0.0 +1378902898; 1; 2599.998989; 0.0; 0.0; 2097152.0; 79689.6; 0.0; 0.8666666666666667; 0.0; 0.0 +1378903198; 1; 2599.998989; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 0.8666666666666667; 0.0; 0.0 +1378903498; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 125828.0; 0.0; 1.0666666666666667; 0.0; 0.0 +1378903798; 1; 2599.998989; 5.199997978; 0.2; 2097152.0; 95069.06666666667; 0.0; 1.0666666666666667; 0.0; 0.0 +1378904098; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 104856.0; 0.0; 0.8; 0.06666666666666667; 0.0 +1378904398; 1; 2599.998989; 0.0; 0.0; 2097152.0; 76893.33333333333; 0.0; 1.0666666666666667; 0.0; 0.0 +1378904698; 1; 2599.998989; 1.7333326593333334; 0.06666666666666667; 2097152.0; 104856.0; 0.0; 0.9333333333333333; 0.0; 0.0 +1378904998; 1; 2599.998989; 77.99996967000001; 3.0; 2097152.0; 450186.6666666667; 161.06666666666666; 107.6; 12.933333333333334; 1.2666666666666666 +1378905298; 1; 2599.998989; 17.333326593333336; 0.6666666666666667; 2097152.0; 633338.4; 0.0; 45.13333333333333; 0.2; 0.13333333333333333 +1378905598; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 297792.8; 31.8; 3.466666666666667; 0.0; 0.0 +1378905898; 1; 2599.998989; 8.666663296666668; 0.33333333333333337; 2097152.0; 162177.86666666667; 5.066666666666666; 2.2; 0.0; 0.0 +1378906198; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 142604.8; 0.0; 1.6; 0.0; 0.0 +1378906498; 1; 2599.998989; 3.4666653186666667; 0.13333333333333333; 2097152.0; 125827.2; 0.0; 1.3333333333333333; 0.0; 0.0 +1378906798; 1; 2599.998989; 0.0; 0.0; 2097152.0; 92272.8; 0.0; 1.0; 0.06666666666666667; 0.0 diff --git a/opendc-trace/opendc-trace-bitbrains/src/test/resources/vm.txt b/opendc-trace/opendc-trace-bitbrains/src/test/resources/vm.txt new file mode 100644 index 00000000..28bebb0c --- /dev/null +++ b/opendc-trace/opendc-trace-bitbrains/src/test/resources/vm.txt @@ -0,0 +1,2 @@ +1631911500 21.2 22.10 0.0 0.0 0.67 1.2 0.0 0.0 5 1 abc 1 0.01 1 10 0.0 0.0 2699 vm 4096 +1631911800 30.4 31.80 0.0 0.0 0.56 1.3 0.0 0.0 5 1 abc 1 0.02 1 10 0.0 0.0 2699 vm 4096 diff --git a/opendc-trace/opendc-trace-gwf/build.gradle.kts b/opendc-trace/opendc-trace-gwf/build.gradle.kts new file mode 100644 index 00000000..f3dfd6ef --- /dev/null +++ b/opendc-trace/opendc-trace-gwf/build.gradle.kts @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +description = "Support for GWF traces in OpenDC" + +/* Build configuration */ +plugins { + `kotlin-library-conventions` + `testing-conventions` + `jacoco-conventions` +} + +dependencies { + api(platform(projects.opendcPlatform)) + api(projects.opendcTrace.opendcTraceApi) + + implementation(libs.jackson.dataformat.csv) +} diff --git a/opendc-trace/opendc-trace-gwf/src/main/kotlin/org/opendc/trace/gwf/GwfTaskTableReader.kt b/opendc-trace/opendc-trace-gwf/src/main/kotlin/org/opendc/trace/gwf/GwfTaskTableReader.kt new file mode 100644 index 00000000..aa4c543b --- /dev/null +++ b/opendc-trace/opendc-trace-gwf/src/main/kotlin/org/opendc/trace/gwf/GwfTaskTableReader.kt @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.trace.gwf + +import com.fasterxml.jackson.core.JsonToken +import com.fasterxml.jackson.dataformat.csv.CsvParser +import com.fasterxml.jackson.dataformat.csv.CsvSchema +import org.opendc.trace.* +import java.time.Duration +import java.time.Instant +import java.util.regex.Pattern + +/** + * A [TableReader] implementation for the GWF format. + */ +internal class GwfTaskTableReader(private val parser: CsvParser) : TableReader { + init { + parser.schema = schema + } + + override fun nextRow(): Boolean { + // Reset the row state + reset() + + if (!nextStart()) { + return false + } + + while (true) { + val token = parser.nextValue() + + if (token == null || token == JsonToken.END_OBJECT) { + break + } + + when (parser.currentName) { + "WorkflowID" -> workflowId = parser.text + "JobID" -> jobId = parser.text + "SubmitTime" -> submitTime = Instant.ofEpochSecond(parser.longValue) + "RunTime" -> runtime = Duration.ofSeconds(parser.longValue) + "NProcs" -> nProcs = parser.intValue + "ReqNProcs" -> reqNProcs = parser.intValue + "Dependencies" -> parseParents(parser.valueAsString) + } + } + + return true + } + + override fun resolve(column: TableColumn<*>): Int = columns[column] ?: -1 + + override fun isNull(index: Int): Boolean { + check(index in 0..columns.size) { "Invalid column" } + return false + } + + override fun get(index: Int): Any? { + return when (index) { + COL_JOB_ID -> jobId + COL_WORKFLOW_ID -> workflowId + COL_SUBMIT_TIME -> submitTime + COL_RUNTIME -> runtime + COL_REQ_NPROC -> getInt(index) + COL_NPROC -> getInt(index) + COL_DEPS -> dependencies + else -> throw IllegalArgumentException("Invalid column") + } + } + + override fun getBoolean(index: Int): Boolean { + throw IllegalArgumentException("Invalid column") + } + + override fun getInt(index: Int): Int { + return when (index) { + COL_REQ_NPROC -> reqNProcs + COL_NPROC -> nProcs + else -> throw IllegalArgumentException("Invalid column") + } + } + + override fun getLong(index: Int): Long { + throw IllegalArgumentException("Invalid column") + } + + override fun getDouble(index: Int): Double { + throw IllegalArgumentException("Invalid column") + } + + override fun close() { + parser.close() + } + + /** + * The pattern used to parse the parents. + */ + private val pattern = Pattern.compile("\\s+") + + /** + * Parse the parents into a set of longs. + */ + private fun parseParents(value: String): Set<Long> { + val result = mutableSetOf<Long>() + val deps = value.split(pattern) + + for (dep in deps) { + if (dep.isBlank()) { + continue + } + + result.add(dep.toLong(10)) + } + + return result + } + + /** + * Advance the parser until the next object start. + */ + private fun nextStart(): Boolean { + var token = parser.nextValue() + + while (token != null && token != JsonToken.START_OBJECT) { + token = parser.nextValue() + } + + return token != null + } + + /** + * Reader state fields. + */ + private var workflowId: String? = null + private var jobId: String? = null + private var submitTime: Instant? = null + private var runtime: Duration? = null + private var nProcs = -1 + private var reqNProcs = -1 + private var dependencies = emptySet<Long>() + + /** + * Reset the state. + */ + private fun reset() { + workflowId = null + jobId = null + submitTime = null + runtime = null + nProcs = -1 + reqNProcs = -1 + dependencies = emptySet() + } + + private val COL_WORKFLOW_ID = 0 + private val COL_JOB_ID = 1 + private val COL_SUBMIT_TIME = 2 + private val COL_RUNTIME = 3 + private val COL_NPROC = 4 + private val COL_REQ_NPROC = 5 + private val COL_DEPS = 6 + + private val columns = mapOf( + TASK_ID to COL_JOB_ID, + TASK_WORKFLOW_ID to COL_WORKFLOW_ID, + TASK_SUBMIT_TIME to COL_SUBMIT_TIME, + TASK_RUNTIME to COL_RUNTIME, + TASK_ALLOC_NCPUS to COL_NPROC, + TASK_REQ_NCPUS to COL_REQ_NPROC, + TASK_PARENTS to COL_DEPS + ) + + companion object { + /** + * The [CsvSchema] that is used to parse the trace. + */ + private val schema = CsvSchema.builder() + .addColumn("WorkflowID", CsvSchema.ColumnType.NUMBER) + .addColumn("JobID", CsvSchema.ColumnType.NUMBER) + .addColumn("SubmitTime", CsvSchema.ColumnType.NUMBER) + .addColumn("RunTime", CsvSchema.ColumnType.NUMBER) + .addColumn("NProcs", CsvSchema.ColumnType.NUMBER) + .addColumn("ReqNProcs", CsvSchema.ColumnType.NUMBER) + .addColumn("Dependencies", CsvSchema.ColumnType.STRING) + .setAllowComments(true) + .setUseHeader(true) + .setColumnSeparator(',') + .build() + } +} diff --git a/opendc-trace/opendc-trace-gwf/src/main/kotlin/org/opendc/trace/gwf/GwfTraceFormat.kt b/opendc-trace/opendc-trace-gwf/src/main/kotlin/org/opendc/trace/gwf/GwfTraceFormat.kt new file mode 100644 index 00000000..d4287420 --- /dev/null +++ b/opendc-trace/opendc-trace-gwf/src/main/kotlin/org/opendc/trace/gwf/GwfTraceFormat.kt @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.trace.gwf + +import com.fasterxml.jackson.dataformat.csv.CsvFactory +import com.fasterxml.jackson.dataformat.csv.CsvParser +import org.opendc.trace.* +import org.opendc.trace.spi.TableDetails +import org.opendc.trace.spi.TraceFormat +import java.nio.file.Path + +/** + * A [TraceFormat] implementation for the GWF trace format. + */ +public class GwfTraceFormat : TraceFormat { + /** + * The name of this trace format. + */ + override val name: String = "gwf" + + /** + * The [CsvFactory] used to create the parser. + */ + private val factory = CsvFactory() + .enable(CsvParser.Feature.ALLOW_COMMENTS) + .enable(CsvParser.Feature.TRIM_SPACES) + + override fun create(path: Path) { + throw UnsupportedOperationException("Writing not supported for this format") + } + + override fun getTables(path: Path): List<String> = listOf(TABLE_TASKS) + + override fun getDetails(path: Path, table: String): TableDetails { + return when (table) { + TABLE_TASKS -> TableDetails( + listOf( + TASK_WORKFLOW_ID, + TASK_ID, + TASK_SUBMIT_TIME, + TASK_RUNTIME, + TASK_REQ_NCPUS, + TASK_ALLOC_NCPUS, + TASK_PARENTS, + ), + listOf(TASK_WORKFLOW_ID) + ) + else -> throw IllegalArgumentException("Table $table not supported") + } + } + + override fun newReader(path: Path, table: String): TableReader { + return when (table) { + TABLE_TASKS -> GwfTaskTableReader(factory.createParser(path.toFile())) + else -> throw IllegalArgumentException("Table $table not supported") + } + } + + override fun newWriter(path: Path, table: String): TableWriter { + throw UnsupportedOperationException("Writing not supported for this format") + } +} diff --git a/opendc-trace/opendc-trace-gwf/src/main/resources/META-INF/services/org.opendc.trace.spi.TraceFormat b/opendc-trace/opendc-trace-gwf/src/main/resources/META-INF/services/org.opendc.trace.spi.TraceFormat new file mode 100644 index 00000000..99a874c8 --- /dev/null +++ b/opendc-trace/opendc-trace-gwf/src/main/resources/META-INF/services/org.opendc.trace.spi.TraceFormat @@ -0,0 +1 @@ +org.opendc.trace.gwf.GwfTraceFormat diff --git a/opendc-trace/opendc-trace-gwf/src/test/kotlin/org/opendc/trace/gwf/GwfTraceFormatTest.kt b/opendc-trace/opendc-trace-gwf/src/test/kotlin/org/opendc/trace/gwf/GwfTraceFormatTest.kt new file mode 100644 index 00000000..7fe403b2 --- /dev/null +++ b/opendc-trace/opendc-trace-gwf/src/test/kotlin/org/opendc/trace/gwf/GwfTraceFormatTest.kt @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.trace.gwf + +import org.junit.jupiter.api.* +import org.junit.jupiter.api.Assertions.* +import org.opendc.trace.* +import java.nio.file.Paths +import java.time.Duration +import java.time.Instant + +/** + * Test suite for the [GwfTraceFormat] class. + */ +internal class GwfTraceFormatTest { + private val format = GwfTraceFormat() + + @Test + fun testTables() { + val path = Paths.get(checkNotNull(GwfTraceFormatTest::class.java.getResource("/trace.gwf")).toURI()) + + assertEquals(listOf(TABLE_TASKS), format.getTables(path)) + } + + @Test + fun testTableExists() { + val path = Paths.get(checkNotNull(GwfTraceFormatTest::class.java.getResource("/trace.gwf")).toURI()) + assertDoesNotThrow { format.getDetails(path, TABLE_TASKS) } + } + + @Test + fun testTableDoesNotExist() { + val path = Paths.get(checkNotNull(GwfTraceFormatTest::class.java.getResource("/trace.gwf")).toURI()) + + assertThrows<IllegalArgumentException> { format.getDetails(path, "test") } + } + + @Test + fun testTableReader() { + val path = Paths.get(checkNotNull(GwfTraceFormatTest::class.java.getResource("/trace.gwf")).toURI()) + val reader = format.newReader(path, TABLE_TASKS) + + assertAll( + { assertTrue(reader.nextRow()) }, + { assertEquals("0", reader.get(TASK_WORKFLOW_ID)) }, + { assertEquals("1", reader.get(TASK_ID)) }, + { assertEquals(Instant.ofEpochSecond(16), reader.get(TASK_SUBMIT_TIME)) }, + { assertEquals(Duration.ofSeconds(11), reader.get(TASK_RUNTIME)) }, + { assertEquals(emptySet<String>(), reader.get(TASK_PARENTS)) }, + ) + } +} diff --git a/opendc-trace/opendc-trace-gwf/src/test/resources/trace.gwf b/opendc-trace/opendc-trace-gwf/src/test/resources/trace.gwf new file mode 100644 index 00000000..2f99616d --- /dev/null +++ b/opendc-trace/opendc-trace-gwf/src/test/resources/trace.gwf @@ -0,0 +1,71 @@ +WorkflowID, JobID , SubmitTime, RunTime , NProcs , ReqNProcs , Dependencies +0 , 1 , 16 , 11 , 1 , 1 , +0 , 2 , 40 , 11 , 1 , 1 , 1 +0 , 3 , 40 , 11 , 1 , 1 , 1 +0 , 4 , 64 , 11 , 1 , 1 , 2 +0 , 5 , 63 , 11 , 1 , 1 , 3 +0 , 6 , 64 , 11 , 1 , 1 , 3 +0 , 7 , 87 , 11 , 1 , 1 , 4 5 6 +1 , 8 , 4 , 11 , 1 , 1 , +1 , 9 , 15 , 11 , 1 , 1 , 8 +1 , 10 , 15 , 11 , 1 , 1 , 8 +1 , 11 , 27 , 11 , 1 , 1 , 9 +1 , 12 , 27 , 11 , 1 , 1 , 10 +1 , 13 , 27 , 11 , 1 , 1 , 10 +1 , 14 , 38 , 11 , 1 , 1 , 12 11 13 +2 , 15 , 3 , 11 , 1 , 1 , +2 , 16 , 27 , 11 , 1 , 1 , 15 +2 , 17 , 27 , 11 , 1 , 1 , 15 +2 , 18 , 52 , 11 , 1 , 1 , 16 +2 , 19 , 51 , 11 , 1 , 1 , 17 +2 , 20 , 51 , 11 , 1 , 1 , 17 +2 , 21 , 75 , 11 , 1 , 1 , 20 18 19 +3 , 22 , 3 , 11 , 1 , 1 , +3 , 23 , 27 , 11 , 1 , 1 , 22 +3 , 24 , 27 , 11 , 1 , 1 , 22 +3 , 25 , 51 , 11 , 1 , 1 , 23 +3 , 26 , 50 , 11 , 1 , 1 , 24 +3 , 27 , 51 , 11 , 1 , 1 , 24 +3 , 28 , 75 , 11 , 1 , 1 , 25 27 26 +4 , 29 , 3 , 11 , 1 , 1 , +4 , 30 , 27 , 11 , 1 , 1 , 29 +4 , 31 , 27 , 11 , 1 , 1 , 29 +4 , 32 , 50 , 11 , 1 , 1 , 30 +4 , 33 , 50 , 11 , 1 , 1 , 31 +4 , 34 , 51 , 11 , 1 , 1 , 31 +4 , 35 , 74 , 11 , 1 , 1 , 33 32 34 +5 , 36 , 3 , 11 , 1 , 1 , +5 , 37 , 27 , 11 , 1 , 1 , 36 +5 , 38 , 26 , 11 , 1 , 1 , 36 +5 , 39 , 51 , 11 , 1 , 1 , 37 +5 , 40 , 50 , 11 , 1 , 1 , 38 +5 , 41 , 50 , 11 , 1 , 1 , 38 +5 , 42 , 74 , 11 , 1 , 1 , 39 40 41 +6 , 43 , 4 , 11 , 1 , 1 , +6 , 44 , 27 , 11 , 1 , 1 , 43 +6 , 45 , 27 , 11 , 1 , 1 , 43 +6 , 46 , 51 , 11 , 1 , 1 , 44 +6 , 47 , 51 , 11 , 1 , 1 , 45 +6 , 48 , 51 , 11 , 1 , 1 , 45 +6 , 49 , 75 , 11 , 1 , 1 , 46 47 48 +7 , 50 , 3 , 0 , 1 , 1 , +7 , 51 , 17 , 0 , 1 , 1 , 50 +7 , 52 , 17 , 0 , 1 , 1 , 50 +7 , 53 , 30 , 0 , 1 , 1 , 51 +7 , 54 , 30 , 0 , 1 , 1 , 52 +7 , 55 , 31 , 0 , 1 , 1 , 52 +7 , 56 , 44 , 0 , 1 , 1 , 55 54 53 +8 , 57 , 3 , 11 , 1 , 1 , +8 , 58 , 26 , 11 , 1 , 1 , 57 +8 , 59 , 27 , 11 , 1 , 1 , 57 +8 , 60 , 50 , 11 , 1 , 1 , 58 +8 , 61 , 51 , 11 , 1 , 1 , 59 +8 , 62 , 50 , 11 , 1 , 1 , 59 +8 , 63 , 74 , 11 , 1 , 1 , 62 61 60 +9 , 64 , 3 , 11 , 1 , 1 , +9 , 65 , 27 , 11 , 1 , 1 , 64 +9 , 66 , 27 , 11 , 1 , 1 , 64 +9 , 67 , 51 , 11 , 1 , 1 , 65 +9 , 68 , 50 , 11 , 1 , 1 , 66 +9 , 69 , 51 , 11 , 1 , 1 , 66 +9 , 70 , 74 , 11 , 1 , 1 , 68 69 67 diff --git a/opendc-trace/opendc-trace-opendc/build.gradle.kts b/opendc-trace/opendc-trace-opendc/build.gradle.kts new file mode 100644 index 00000000..b9c242a1 --- /dev/null +++ b/opendc-trace/opendc-trace-opendc/build.gradle.kts @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +description = "Support for OpenDC-specific trace formats" + +/* Build configuration */ +plugins { + `kotlin-library-conventions` + `testing-conventions` + `jacoco-conventions` +} + +dependencies { + api(platform(projects.opendcPlatform)) + api(projects.opendcTrace.opendcTraceApi) + + implementation(projects.opendcTrace.opendcTraceParquet) + + testRuntimeOnly(libs.slf4j.simple) +} diff --git a/opendc-trace/opendc-trace-opendc/src/main/kotlin/org/opendc/trace/opendc/OdcVmResourceStateTableReader.kt b/opendc-trace/opendc-trace-opendc/src/main/kotlin/org/opendc/trace/opendc/OdcVmResourceStateTableReader.kt new file mode 100644 index 00000000..b5043f82 --- /dev/null +++ b/opendc-trace/opendc-trace-opendc/src/main/kotlin/org/opendc/trace/opendc/OdcVmResourceStateTableReader.kt @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.trace.opendc + +import org.apache.avro.Schema +import org.apache.avro.generic.GenericRecord +import org.opendc.trace.* +import org.opendc.trace.util.parquet.LocalParquetReader +import java.time.Duration +import java.time.Instant + +/** + * A [TableReader] implementation for the OpenDC virtual machine trace format. + */ +internal class OdcVmResourceStateTableReader(private val reader: LocalParquetReader<GenericRecord>) : TableReader { + /** + * The current record. + */ + private var record: GenericRecord? = null + + /** + * A flag to indicate that the columns have been initialized. + */ + private var hasInitializedColumns = false + + override fun nextRow(): Boolean { + val record = reader.read() + this.record = record + + if (!hasInitializedColumns && record != null) { + initColumns(record.schema) + hasInitializedColumns = true + } + + return record != null + } + + override fun resolve(column: TableColumn<*>): Int = columns[column] ?: -1 + + override fun isNull(index: Int): Boolean { + check(index in 0..columns.size) { "Invalid column index" } + return get(index) == null + } + + override fun get(index: Int): Any? { + val record = checkNotNull(record) { "Reader in invalid state" } + + return when (index) { + COL_ID -> record[AVRO_COL_ID].toString() + COL_TIMESTAMP -> Instant.ofEpochMilli(record[AVRO_COL_TIMESTAMP] as Long) + COL_DURATION -> Duration.ofMillis(record[AVRO_COL_DURATION] as Long) + COL_CPU_COUNT -> getInt(index) + COL_CPU_USAGE -> getDouble(index) + else -> throw IllegalArgumentException("Invalid column") + } + } + + override fun getBoolean(index: Int): Boolean { + throw IllegalArgumentException("Invalid column") + } + + override fun getInt(index: Int): Int { + val record = checkNotNull(record) { "Reader in invalid state" } + return when (index) { + COL_CPU_COUNT -> record[AVRO_COL_CPU_COUNT] as Int + else -> throw IllegalArgumentException("Invalid column") + } + } + + override fun getLong(index: Int): Long { + throw IllegalArgumentException("Invalid column") + } + + override fun getDouble(index: Int): Double { + val record = checkNotNull(record) { "Reader in invalid state" } + return when (index) { + COL_CPU_USAGE -> (record[AVRO_COL_CPU_USAGE] as Number).toDouble() + else -> throw IllegalArgumentException("Invalid column") + } + } + + override fun close() { + reader.close() + } + + override fun toString(): String = "OdcVmResourceStateTableReader" + + /** + * Initialize the columns for the reader based on [schema]. + */ + private fun initColumns(schema: Schema) { + try { + AVRO_COL_ID = schema.getField("id").pos() + AVRO_COL_TIMESTAMP = (schema.getField("timestamp") ?: schema.getField("time")).pos() + AVRO_COL_DURATION = schema.getField("duration").pos() + AVRO_COL_CPU_COUNT = (schema.getField("cpu_count") ?: schema.getField("cores")).pos() + AVRO_COL_CPU_USAGE = (schema.getField("cpu_usage") ?: schema.getField("cpuUsage")).pos() + } catch (e: NullPointerException) { + // This happens when the field we are trying to access does not exist + throw IllegalArgumentException("Invalid schema", e) + } + } + + private var AVRO_COL_ID = -1 + private var AVRO_COL_TIMESTAMP = -1 + private var AVRO_COL_DURATION = -1 + private var AVRO_COL_CPU_COUNT = -1 + private var AVRO_COL_CPU_USAGE = -1 + + private val COL_ID = 0 + private val COL_TIMESTAMP = 1 + private val COL_DURATION = 2 + private val COL_CPU_COUNT = 3 + private val COL_CPU_USAGE = 4 + + private val columns = mapOf( + RESOURCE_ID to COL_ID, + RESOURCE_STATE_TIMESTAMP to COL_TIMESTAMP, + RESOURCE_STATE_DURATION to COL_DURATION, + RESOURCE_CPU_COUNT to COL_CPU_COUNT, + RESOURCE_STATE_CPU_USAGE to COL_CPU_USAGE, + ) +} diff --git a/opendc-trace/opendc-trace-opendc/src/main/kotlin/org/opendc/trace/opendc/OdcVmResourceStateTableWriter.kt b/opendc-trace/opendc-trace-opendc/src/main/kotlin/org/opendc/trace/opendc/OdcVmResourceStateTableWriter.kt new file mode 100644 index 00000000..15a8cb85 --- /dev/null +++ b/opendc-trace/opendc-trace-opendc/src/main/kotlin/org/opendc/trace/opendc/OdcVmResourceStateTableWriter.kt @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.trace.opendc + +import org.apache.avro.Schema +import org.apache.avro.generic.GenericRecord +import org.apache.avro.generic.GenericRecordBuilder +import org.apache.parquet.hadoop.ParquetWriter +import org.opendc.trace.* +import java.time.Duration +import java.time.Instant + +/** + * A [TableWriter] implementation for the OpenDC virtual machine trace format. + */ +internal class OdcVmResourceStateTableWriter( + private val writer: ParquetWriter<GenericRecord>, + private val schema: Schema +) : TableWriter { + /** + * The current builder for the record that is being written. + */ + private var builder: GenericRecordBuilder? = null + + /** + * The fields belonging to the resource state schema. + */ + private val fields = schema.fields + + override fun startRow() { + builder = GenericRecordBuilder(schema) + } + + override fun endRow() { + val builder = checkNotNull(builder) { "No active row" } + this.builder = null + + val record = builder.build() + val id = record[COL_ID] as String + val timestamp = record[COL_TIMESTAMP] as Long + + check(lastId != id || timestamp >= lastTimestamp) { "Records need to be ordered by (id, timestamp)" } + + writer.write(builder.build()) + + lastId = id + lastTimestamp = timestamp + } + + override fun resolve(column: TableColumn<*>): Int { + val schema = schema + return when (column) { + RESOURCE_ID -> schema.getField("id").pos() + RESOURCE_STATE_TIMESTAMP -> (schema.getField("timestamp") ?: schema.getField("time")).pos() + RESOURCE_STATE_DURATION -> schema.getField("duration").pos() + RESOURCE_CPU_COUNT -> (schema.getField("cpu_count") ?: schema.getField("cores")).pos() + RESOURCE_STATE_CPU_USAGE -> (schema.getField("cpu_usage") ?: schema.getField("cpuUsage")).pos() + else -> -1 + } + } + + override fun set(index: Int, value: Any) { + val builder = checkNotNull(builder) { "No active row" } + + builder.set( + fields[index], + when (index) { + COL_TIMESTAMP -> (value as Instant).toEpochMilli() + COL_DURATION -> (value as Duration).toMillis() + else -> value + } + ) + } + + override fun setBoolean(index: Int, value: Boolean) = set(index, value) + + override fun setInt(index: Int, value: Int) = set(index, value) + + override fun setLong(index: Int, value: Long) = set(index, value) + + override fun setDouble(index: Int, value: Double) = set(index, value) + + override fun flush() { + // Not available + } + + override fun close() { + writer.close() + } + + /** + * Last column values that are used to check for correct partitioning. + */ + private var lastId: String? = null + private var lastTimestamp: Long = Long.MIN_VALUE + + /** + * Columns with special behavior. + */ + private val COL_ID = resolve(RESOURCE_ID) + private val COL_TIMESTAMP = resolve(RESOURCE_STATE_TIMESTAMP) + private val COL_DURATION = resolve(RESOURCE_STATE_DURATION) +} diff --git a/opendc-trace/opendc-trace-opendc/src/main/kotlin/org/opendc/trace/opendc/OdcVmResourceTableReader.kt b/opendc-trace/opendc-trace-opendc/src/main/kotlin/org/opendc/trace/opendc/OdcVmResourceTableReader.kt new file mode 100644 index 00000000..d93929aa --- /dev/null +++ b/opendc-trace/opendc-trace-opendc/src/main/kotlin/org/opendc/trace/opendc/OdcVmResourceTableReader.kt @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.trace.opendc + +import org.apache.avro.Schema +import org.apache.avro.generic.GenericRecord +import org.opendc.trace.* +import org.opendc.trace.util.parquet.LocalParquetReader +import java.time.Instant + +/** + * A [TableReader] implementation for the resources table in the OpenDC virtual machine trace format. + */ +internal class OdcVmResourceTableReader(private val reader: LocalParquetReader<GenericRecord>) : TableReader { + /** + * The current record. + */ + private var record: GenericRecord? = null + + /** + * A flag to indicate that the columns have been initialized. + */ + private var hasInitializedColumns = false + + override fun nextRow(): Boolean { + val record = reader.read() + this.record = record + + if (!hasInitializedColumns && record != null) { + initColumns(record.schema) + hasInitializedColumns = true + } + + return record != null + } + + override fun resolve(column: TableColumn<*>): Int = columns[column] ?: -1 + + override fun isNull(index: Int): Boolean { + check(index in 0..columns.size) { "Invalid column index" } + return get(index) == null + } + + override fun get(index: Int): Any? { + val record = checkNotNull(record) { "Reader in invalid state" } + + return when (index) { + COL_ID -> record[AVRO_COL_ID].toString() + COL_START_TIME -> Instant.ofEpochMilli(record[AVRO_COL_START_TIME] as Long) + COL_STOP_TIME -> Instant.ofEpochMilli(record[AVRO_COL_STOP_TIME] as Long) + COL_CPU_COUNT -> getInt(index) + COL_MEM_CAPACITY -> getDouble(index) + else -> throw IllegalArgumentException("Invalid column") + } + } + + override fun getBoolean(index: Int): Boolean { + throw IllegalArgumentException("Invalid column") + } + + override fun getInt(index: Int): Int { + val record = checkNotNull(record) { "Reader in invalid state" } + + return when (index) { + COL_CPU_COUNT -> record[AVRO_COL_CPU_COUNT] as Int + else -> throw IllegalArgumentException("Invalid column") + } + } + + override fun getLong(index: Int): Long { + throw IllegalArgumentException("Invalid column") + } + + override fun getDouble(index: Int): Double { + val record = checkNotNull(record) { "Reader in invalid state" } + + return when (index) { + COL_MEM_CAPACITY -> (record[AVRO_COL_MEM_CAPACITY] as Number).toDouble() + else -> throw IllegalArgumentException("Invalid column") + } + } + + override fun close() { + reader.close() + } + + override fun toString(): String = "OdcVmResourceTableReader" + + /** + * Initialize the columns for the reader based on [schema]. + */ + private fun initColumns(schema: Schema) { + try { + AVRO_COL_ID = schema.getField("id").pos() + AVRO_COL_START_TIME = (schema.getField("start_time") ?: schema.getField("submissionTime")).pos() + AVRO_COL_STOP_TIME = (schema.getField("stop_time") ?: schema.getField("endTime")).pos() + AVRO_COL_CPU_COUNT = (schema.getField("cpu_count") ?: schema.getField("maxCores")).pos() + AVRO_COL_MEM_CAPACITY = (schema.getField("mem_capacity") ?: schema.getField("requiredMemory")).pos() + } catch (e: NullPointerException) { + // This happens when the field we are trying to access does not exist + throw IllegalArgumentException("Invalid schema") + } + } + + private var AVRO_COL_ID = -1 + private var AVRO_COL_START_TIME = -1 + private var AVRO_COL_STOP_TIME = -1 + private var AVRO_COL_CPU_COUNT = -1 + private var AVRO_COL_MEM_CAPACITY = -1 + + private val COL_ID = 0 + private val COL_START_TIME = 1 + private val COL_STOP_TIME = 2 + private val COL_CPU_COUNT = 3 + private val COL_MEM_CAPACITY = 4 + + private val columns = mapOf( + RESOURCE_ID to COL_ID, + RESOURCE_START_TIME to COL_START_TIME, + RESOURCE_STOP_TIME to COL_STOP_TIME, + RESOURCE_CPU_COUNT to COL_CPU_COUNT, + RESOURCE_MEM_CAPACITY to COL_MEM_CAPACITY, + ) +} diff --git a/opendc-trace/opendc-trace-opendc/src/main/kotlin/org/opendc/trace/opendc/OdcVmResourceTableWriter.kt b/opendc-trace/opendc-trace-opendc/src/main/kotlin/org/opendc/trace/opendc/OdcVmResourceTableWriter.kt new file mode 100644 index 00000000..9cc6ca7d --- /dev/null +++ b/opendc-trace/opendc-trace-opendc/src/main/kotlin/org/opendc/trace/opendc/OdcVmResourceTableWriter.kt @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.trace.opendc + +import org.apache.avro.Schema +import org.apache.avro.generic.GenericRecord +import org.apache.avro.generic.GenericRecordBuilder +import org.apache.parquet.hadoop.ParquetWriter +import org.opendc.trace.* +import java.time.Instant +import kotlin.math.roundToLong + +/** + * A [TableWriter] implementation for the OpenDC virtual machine trace format. + */ +internal class OdcVmResourceTableWriter( + private val writer: ParquetWriter<GenericRecord>, + private val schema: Schema +) : TableWriter { + /** + * The current builder for the record that is being written. + */ + private var builder: GenericRecordBuilder? = null + + /** + * The fields belonging to the resource schema. + */ + private val fields = schema.fields + + override fun startRow() { + builder = GenericRecordBuilder(schema) + } + + override fun endRow() { + val builder = checkNotNull(builder) { "No active row" } + this.builder = null + writer.write(builder.build()) + } + + override fun resolve(column: TableColumn<*>): Int { + val schema = schema + return when (column) { + RESOURCE_ID -> schema.getField("id").pos() + RESOURCE_START_TIME -> (schema.getField("start_time") ?: schema.getField("submissionTime")).pos() + RESOURCE_STOP_TIME -> (schema.getField("stop_time") ?: schema.getField("endTime")).pos() + RESOURCE_CPU_COUNT -> (schema.getField("cpu_count") ?: schema.getField("maxCores")).pos() + RESOURCE_MEM_CAPACITY -> (schema.getField("mem_capacity") ?: schema.getField("requiredMemory")).pos() + else -> -1 + } + } + + override fun set(index: Int, value: Any) { + val builder = checkNotNull(builder) { "No active row" } + builder.set( + fields[index], + when (index) { + COL_START_TIME, COL_STOP_TIME -> (value as Instant).toEpochMilli() + COL_MEM_CAPACITY -> (value as Double).roundToLong() + else -> value + } + ) + } + + override fun setBoolean(index: Int, value: Boolean) = set(index, value) + + override fun setInt(index: Int, value: Int) = set(index, value) + + override fun setLong(index: Int, value: Long) = set(index, value) + + override fun setDouble(index: Int, value: Double) = set(index, value) + + override fun flush() { + // Not available + } + + override fun close() { + writer.close() + } + + /** + * Columns with special behavior. + */ + private val COL_START_TIME = resolve(RESOURCE_START_TIME) + private val COL_STOP_TIME = resolve(RESOURCE_STOP_TIME) + private val COL_MEM_CAPACITY = resolve(RESOURCE_MEM_CAPACITY) +} diff --git a/opendc-trace/opendc-trace-opendc/src/main/kotlin/org/opendc/trace/opendc/OdcVmTraceFormat.kt b/opendc-trace/opendc-trace-opendc/src/main/kotlin/org/opendc/trace/opendc/OdcVmTraceFormat.kt new file mode 100644 index 00000000..9b32f8fd --- /dev/null +++ b/opendc-trace/opendc-trace-opendc/src/main/kotlin/org/opendc/trace/opendc/OdcVmTraceFormat.kt @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.trace.opendc + +import org.apache.avro.Schema +import org.apache.avro.SchemaBuilder +import org.apache.avro.generic.GenericRecord +import org.apache.parquet.avro.AvroParquetWriter +import org.apache.parquet.hadoop.ParquetFileWriter +import org.apache.parquet.hadoop.metadata.CompressionCodecName +import org.opendc.trace.* +import org.opendc.trace.spi.TableDetails +import org.opendc.trace.spi.TraceFormat +import org.opendc.trace.util.parquet.LocalOutputFile +import org.opendc.trace.util.parquet.LocalParquetReader +import org.opendc.trace.util.parquet.TIMESTAMP_SCHEMA +import java.nio.file.Files +import java.nio.file.Path + +/** + * A [TraceFormat] implementation of the OpenDC virtual machine trace format. + */ +public class OdcVmTraceFormat : TraceFormat { + /** + * The name of this trace format. + */ + override val name: String = "opendc-vm" + + override fun create(path: Path) { + // Construct directory containing the trace files + Files.createDirectory(path) + + val tables = getTables(path) + + for (table in tables) { + val writer = newWriter(path, table) + writer.close() + } + } + + override fun getTables(path: Path): List<String> = listOf(TABLE_RESOURCES, TABLE_RESOURCE_STATES) + + override fun getDetails(path: Path, table: String): TableDetails { + return when (table) { + TABLE_RESOURCES -> TableDetails( + listOf( + RESOURCE_ID, + RESOURCE_START_TIME, + RESOURCE_STOP_TIME, + RESOURCE_CPU_COUNT, + RESOURCE_MEM_CAPACITY, + ) + ) + TABLE_RESOURCE_STATES -> TableDetails( + listOf( + RESOURCE_ID, + RESOURCE_STATE_TIMESTAMP, + RESOURCE_STATE_DURATION, + RESOURCE_CPU_COUNT, + RESOURCE_STATE_CPU_USAGE, + ), + listOf(RESOURCE_ID, RESOURCE_STATE_TIMESTAMP) + ) + else -> throw IllegalArgumentException("Table $table not supported") + } + } + + override fun newReader(path: Path, table: String): TableReader { + return when (table) { + TABLE_RESOURCES -> { + val reader = LocalParquetReader<GenericRecord>(path.resolve("meta.parquet")) + OdcVmResourceTableReader(reader) + } + TABLE_RESOURCE_STATES -> { + val reader = LocalParquetReader<GenericRecord>(path.resolve("trace.parquet")) + OdcVmResourceStateTableReader(reader) + } + else -> throw IllegalArgumentException("Table $table not supported") + } + } + + override fun newWriter(path: Path, table: String): TableWriter { + return when (table) { + TABLE_RESOURCES -> { + val schema = RESOURCES_SCHEMA + val writer = AvroParquetWriter.builder<GenericRecord>(LocalOutputFile(path.resolve("meta.parquet"))) + .withSchema(schema) + .withCompressionCodec(CompressionCodecName.ZSTD) + .withWriteMode(ParquetFileWriter.Mode.OVERWRITE) + .build() + OdcVmResourceTableWriter(writer, schema) + } + TABLE_RESOURCE_STATES -> { + val schema = RESOURCE_STATES_SCHEMA + val writer = AvroParquetWriter.builder<GenericRecord>(LocalOutputFile(path.resolve("trace.parquet"))) + .withSchema(schema) + .withCompressionCodec(CompressionCodecName.ZSTD) + .withDictionaryEncoding("id", true) + .withBloomFilterEnabled("id", true) + .withWriteMode(ParquetFileWriter.Mode.OVERWRITE) + .build() + OdcVmResourceStateTableWriter(writer, schema) + } + else -> throw IllegalArgumentException("Table $table not supported") + } + } + + public companion object { + /** + * Schema for the resources table in the trace. + */ + @JvmStatic + public val RESOURCES_SCHEMA: Schema = SchemaBuilder + .record("resource") + .namespace("org.opendc.trace.opendc") + .fields() + .requiredString("id") + .name("start_time").type(TIMESTAMP_SCHEMA).noDefault() + .name("stop_time").type(TIMESTAMP_SCHEMA).noDefault() + .requiredInt("cpu_count") + .requiredLong("mem_capacity") + .endRecord() + + /** + * Schema for the resource states table in the trace. + */ + @JvmStatic + public val RESOURCE_STATES_SCHEMA: Schema = SchemaBuilder + .record("resource_state") + .namespace("org.opendc.trace.opendc") + .fields() + .requiredString("id") + .name("timestamp").type(TIMESTAMP_SCHEMA).noDefault() + .requiredLong("duration") + .requiredInt("cpu_count") + .requiredDouble("cpu_usage") + .endRecord() + } +} diff --git a/opendc-trace/opendc-trace-opendc/src/main/resources/META-INF/services/org.opendc.trace.spi.TraceFormat b/opendc-trace/opendc-trace-opendc/src/main/resources/META-INF/services/org.opendc.trace.spi.TraceFormat new file mode 100644 index 00000000..94094af4 --- /dev/null +++ b/opendc-trace/opendc-trace-opendc/src/main/resources/META-INF/services/org.opendc.trace.spi.TraceFormat @@ -0,0 +1 @@ +org.opendc.trace.opendc.OdcVmTraceFormat diff --git a/opendc-trace/opendc-trace-opendc/src/test/kotlin/org/opendc/trace/opendc/OdcVmTraceFormatTest.kt b/opendc-trace/opendc-trace-opendc/src/test/kotlin/org/opendc/trace/opendc/OdcVmTraceFormatTest.kt new file mode 100644 index 00000000..bfe0f881 --- /dev/null +++ b/opendc-trace/opendc-trace-opendc/src/test/kotlin/org/opendc/trace/opendc/OdcVmTraceFormatTest.kt @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.trace.opendc + +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertDoesNotThrow +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource +import org.opendc.trace.* +import java.nio.file.Paths + +/** + * Test suite for the [OdcVmTraceFormat] implementation. + */ +internal class OdcVmTraceFormatTest { + private val format = OdcVmTraceFormat() + + @Test + fun testTables() { + val path = Paths.get("src/test/resources/trace-v2.1") + + assertEquals(listOf(TABLE_RESOURCES, TABLE_RESOURCE_STATES), format.getTables(path)) + } + + @Test + fun testTableExists() { + val path = Paths.get("src/test/resources/trace-v2.1") + + assertDoesNotThrow { format.getDetails(path, TABLE_RESOURCE_STATES) } + } + + @Test + fun testTableDoesNotExist() { + val path = Paths.get("src/test/resources/trace-v2.1") + assertThrows<IllegalArgumentException> { format.getDetails(path, "test") } + } + + @ParameterizedTest + @ValueSource(strings = ["trace-v2.0", "trace-v2.1"]) + fun testResources(name: String) { + val path = Paths.get("src/test/resources/$name") + val reader = format.newReader(path, TABLE_RESOURCES) + + assertAll( + { assertTrue(reader.nextRow()) }, + { assertEquals("1019", reader.get(RESOURCE_ID)) }, + { assertTrue(reader.nextRow()) }, + { assertEquals("1023", reader.get(RESOURCE_ID)) }, + { assertTrue(reader.nextRow()) }, + { assertEquals("1052", reader.get(RESOURCE_ID)) }, + { assertTrue(reader.nextRow()) }, + { assertEquals("1073", reader.get(RESOURCE_ID)) }, + { assertFalse(reader.nextRow()) } + ) + + reader.close() + } + + @ParameterizedTest + @ValueSource(strings = ["trace-v2.0", "trace-v2.1"]) + fun testSmoke(name: String) { + val path = Paths.get("src/test/resources/$name") + val reader = format.newReader(path, TABLE_RESOURCE_STATES) + + assertAll( + { assertTrue(reader.nextRow()) }, + { assertEquals("1019", reader.get(RESOURCE_ID)) }, + { assertEquals(1376314846, reader.get(RESOURCE_STATE_TIMESTAMP).epochSecond) }, + { assertEquals(0.0, reader.getDouble(RESOURCE_STATE_CPU_USAGE), 0.01) } + ) + + reader.close() + } +} diff --git a/opendc-trace/opendc-trace-opendc/src/test/resources/trace-v2.0/meta.parquet b/opendc-trace/opendc-trace-opendc/src/test/resources/trace-v2.0/meta.parquet Binary files differnew file mode 100644 index 00000000..d6ff09d8 --- /dev/null +++ b/opendc-trace/opendc-trace-opendc/src/test/resources/trace-v2.0/meta.parquet diff --git a/opendc-trace/opendc-trace-opendc/src/test/resources/trace-v2.0/trace.parquet b/opendc-trace/opendc-trace-opendc/src/test/resources/trace-v2.0/trace.parquet Binary files differnew file mode 100644 index 00000000..5b6fa6b7 --- /dev/null +++ b/opendc-trace/opendc-trace-opendc/src/test/resources/trace-v2.0/trace.parquet diff --git a/opendc-trace/opendc-trace-opendc/src/test/resources/trace-v2.1/meta.parquet b/opendc-trace/opendc-trace-opendc/src/test/resources/trace-v2.1/meta.parquet Binary files differnew file mode 100644 index 00000000..d8184945 --- /dev/null +++ b/opendc-trace/opendc-trace-opendc/src/test/resources/trace-v2.1/meta.parquet diff --git a/opendc-trace/opendc-trace-opendc/src/test/resources/trace-v2.1/trace.parquet b/opendc-trace/opendc-trace-opendc/src/test/resources/trace-v2.1/trace.parquet Binary files differnew file mode 100644 index 00000000..00ab5835 --- /dev/null +++ b/opendc-trace/opendc-trace-opendc/src/test/resources/trace-v2.1/trace.parquet diff --git a/opendc-trace/opendc-trace-parquet/build.gradle.kts b/opendc-trace/opendc-trace-parquet/build.gradle.kts new file mode 100644 index 00000000..75378509 --- /dev/null +++ b/opendc-trace/opendc-trace-parquet/build.gradle.kts @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +description = "Parquet helpers for traces in OpenDC" + +/* Build configuration */ +plugins { + `kotlin-library-conventions` + `testing-conventions` + `jacoco-conventions` +} + +dependencies { + api(platform(projects.opendcPlatform)) + + /* This configuration is necessary for a slim dependency on Apache Parquet */ + api(libs.parquet) { + exclude(group = "org.apache.hadoop") + } + runtimeOnly(libs.hadoop.common) { + exclude(group = "org.slf4j", module = "slf4j-log4j12") + exclude(group = "log4j") + exclude(group = "org.apache.hadoop") + exclude(group = "org.apache.curator") + exclude(group = "org.apache.zookeeper") + exclude(group = "org.apache.kerby") + exclude(group = "org.apache.httpcomponents") + exclude(group = "org.apache.htrace") + exclude(group = "commons-cli") + exclude(group = "javax.servlet") + exclude(group = "org.eclipse.jetty") + exclude(group = "com.sun.jersey") + exclude(group = "com.jcraft") + exclude(group = "dnsjava") + } + runtimeOnly(libs.hadoop.mapreduce.client.core) { + isTransitive = false + } + + testRuntimeOnly(libs.slf4j.simple) +} diff --git a/opendc-trace/opendc-trace-parquet/src/main/kotlin/org/opendc/trace/util/parquet/AvroUtils.kt b/opendc-trace/opendc-trace-parquet/src/main/kotlin/org/opendc/trace/util/parquet/AvroUtils.kt new file mode 100644 index 00000000..086b900b --- /dev/null +++ b/opendc-trace/opendc-trace-parquet/src/main/kotlin/org/opendc/trace/util/parquet/AvroUtils.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +@file:JvmName("AvroUtils") +package org.opendc.trace.util.parquet + +import org.apache.avro.LogicalTypes +import org.apache.avro.Schema + +/** + * Schema for UUID type. + */ +public val UUID_SCHEMA: Schema = LogicalTypes.uuid().addToSchema(Schema.create(Schema.Type.STRING)) + +/** + * Schema for timestamp type. + */ +public val TIMESTAMP_SCHEMA: Schema = LogicalTypes.timestampMillis().addToSchema(Schema.create(Schema.Type.LONG)) + +/** + * Helper function to make a [Schema] field optional. + */ +public fun Schema.optional(): Schema { + return Schema.createUnion(Schema.create(Schema.Type.NULL), this) +} diff --git a/opendc-trace/opendc-trace-parquet/src/main/kotlin/org/opendc/trace/util/parquet/LocalInputFile.kt b/opendc-trace/opendc-trace-parquet/src/main/kotlin/org/opendc/trace/util/parquet/LocalInputFile.kt new file mode 100644 index 00000000..fd2e00cd --- /dev/null +++ b/opendc-trace/opendc-trace-parquet/src/main/kotlin/org/opendc/trace/util/parquet/LocalInputFile.kt @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.trace.util.parquet + +import org.apache.parquet.io.InputFile +import org.apache.parquet.io.SeekableInputStream +import java.io.EOFException +import java.io.File +import java.nio.ByteBuffer +import java.nio.channels.FileChannel +import java.nio.file.Path +import java.nio.file.StandardOpenOption + +/** + * An [InputFile] on the local filesystem. + */ +public class LocalInputFile(private val path: Path) : InputFile { + /** + * The [FileChannel] used for accessing the input path. + */ + private val channel = FileChannel.open(path, StandardOpenOption.READ) + + /** + * Construct a [LocalInputFile] for the specified [file]. + */ + public constructor(file: File) : this(file.toPath()) + + override fun getLength(): Long = channel.size() + + override fun newStream(): SeekableInputStream = object : SeekableInputStream() { + override fun read(buf: ByteBuffer): Int { + return channel.read(buf) + } + + override fun read(): Int { + val single = ByteBuffer.allocate(1) + var read: Int + + // ReadableByteChannel#read might read zero bytes so continue until we read at least one byte + do { + read = channel.read(single) + } while (read == 0) + + return if (read == -1) { + read + } else { + single.get(0).toInt() and 0xff + } + } + + override fun getPos(): Long { + return channel.position() + } + + override fun seek(newPos: Long) { + channel.position(newPos) + } + + override fun readFully(bytes: ByteArray) { + readFully(ByteBuffer.wrap(bytes)) + } + + override fun readFully(bytes: ByteArray, start: Int, len: Int) { + readFully(ByteBuffer.wrap(bytes, start, len)) + } + + override fun readFully(buf: ByteBuffer) { + var remainder = buf.remaining() + while (remainder > 0) { + val read = channel.read(buf) + remainder -= read + + if (read == -1 && remainder > 0) { + throw EOFException() + } + } + } + + override fun close() { + channel.close() + } + + override fun toString(): String = "NioSeekableInputStream" + } + + override fun toString(): String = "LocalInputFile[path=$path]" +} diff --git a/opendc-trace/opendc-trace-parquet/src/main/kotlin/org/opendc/trace/util/parquet/LocalOutputFile.kt b/opendc-trace/opendc-trace-parquet/src/main/kotlin/org/opendc/trace/util/parquet/LocalOutputFile.kt new file mode 100644 index 00000000..1b17ae5d --- /dev/null +++ b/opendc-trace/opendc-trace-parquet/src/main/kotlin/org/opendc/trace/util/parquet/LocalOutputFile.kt @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.trace.util.parquet + +import org.apache.parquet.io.OutputFile +import org.apache.parquet.io.PositionOutputStream +import java.io.File +import java.io.OutputStream +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.StandardOpenOption + +/** + * An [OutputFile] on the local filesystem. + */ +public class LocalOutputFile(private val path: Path) : OutputFile { + /** + * Construct a [LocalOutputFile] from the specified [file] + */ + public constructor(file: File) : this(file.toPath()) + + override fun create(blockSizeHint: Long): PositionOutputStream { + val output = Files.newOutputStream(path, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE) + return NioPositionOutputStream(output) + } + + override fun createOrOverwrite(blockSizeHint: Long): PositionOutputStream { + val output = Files.newOutputStream(path, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING) + return NioPositionOutputStream(output) + } + + override fun supportsBlockSize(): Boolean = false + + override fun defaultBlockSize(): Long = + throw UnsupportedOperationException("Local filesystem does not have default block size") + + override fun getPath(): String = path.toString() + + /** + * Implementation of [PositionOutputStream] for an [OutputStream]. + */ + private class NioPositionOutputStream(private val output: OutputStream) : PositionOutputStream() { + /** + * The current position in the file. + */ + private var _pos = 0L + + override fun getPos(): Long = _pos + + override fun write(b: Int) { + output.write(b) + _pos++ + } + + override fun write(b: ByteArray) { + output.write(b) + _pos += b.size + } + + override fun write(b: ByteArray, off: Int, len: Int) { + output.write(b, off, len) + _pos += len + } + + override fun flush() { + output.flush() + } + + override fun close() { + output.close() + } + + override fun toString(): String = "NioPositionOutputStream[output=$output]" + } +} diff --git a/opendc-trace/opendc-trace-parquet/src/main/kotlin/org/opendc/trace/util/parquet/LocalParquetReader.kt b/opendc-trace/opendc-trace-parquet/src/main/kotlin/org/opendc/trace/util/parquet/LocalParquetReader.kt new file mode 100644 index 00000000..ef9eaeb3 --- /dev/null +++ b/opendc-trace/opendc-trace-parquet/src/main/kotlin/org/opendc/trace/util/parquet/LocalParquetReader.kt @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.trace.util.parquet + +import org.apache.parquet.avro.AvroParquetReader +import org.apache.parquet.hadoop.ParquetReader +import org.apache.parquet.io.InputFile +import java.io.File +import java.io.IOException +import java.nio.file.Files +import java.nio.file.Path +import kotlin.io.path.isDirectory + +/** + * A helper class to read Parquet files. + * + * @param path The path to the Parquet file or directory to read. + */ +public class LocalParquetReader<out T>(path: Path) : AutoCloseable { + /** + * The input files to process. + */ + private val filesIterator = if (path.isDirectory()) + Files.list(path) + .filter { !it.isDirectory() } + .sorted() + .map { LocalInputFile(it) } + .iterator() + else + listOf(LocalInputFile(path)).iterator() + + /** + * The Parquet reader to use. + */ + private var reader: ParquetReader<T>? = null + + /** + * Construct a [LocalParquetReader] for the specified [file]. + */ + public constructor(file: File) : this(file.toPath()) + + /** + * Read a single entry in the Parquet file. + */ + public fun read(): T? { + return try { + val next = reader?.read() + if (next != null) { + next + } else { + initReader() + + if (reader == null) + null + else + read() + } + } catch (e: InterruptedException) { + throw IOException(e) + } + } + + /** + * Close the Parquet reader. + */ + override fun close() { + reader?.close() + } + + /** + * Initialize the next reader. + */ + private fun initReader() { + reader?.close() + + this.reader = if (filesIterator.hasNext()) { + createReader(filesIterator.next()) + } else { + null + } + } + + /** + * Create a Parquet reader for the specified file. + */ + private fun createReader(input: InputFile): ParquetReader<T> { + return AvroParquetReader + .builder<T>(input) + .disableCompatibility() + .build() + } +} diff --git a/opendc-trace/opendc-trace-parquet/src/test/kotlin/org/opendc/trace/util/parquet/ParquetTest.kt b/opendc-trace/opendc-trace-parquet/src/test/kotlin/org/opendc/trace/util/parquet/ParquetTest.kt new file mode 100644 index 00000000..8ef4d1fb --- /dev/null +++ b/opendc-trace/opendc-trace-parquet/src/test/kotlin/org/opendc/trace/util/parquet/ParquetTest.kt @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.trace.util.parquet + +import org.apache.avro.SchemaBuilder +import org.apache.avro.generic.GenericData +import org.apache.parquet.avro.AvroParquetReader +import org.apache.parquet.avro.AvroParquetWriter +import org.apache.parquet.hadoop.ParquetFileWriter +import org.junit.jupiter.api.* +import org.junit.jupiter.api.Assertions.assertEquals +import java.io.File +import java.nio.file.FileAlreadyExistsException +import java.nio.file.NoSuchFileException + +/** + * Test suite for the Parquet helper classes. + */ +internal class ParquetTest { + private val schema = SchemaBuilder + .record("test") + .namespace("org.opendc.format.util") + .fields() + .name("field").type().intType().noDefault() + .endRecord() + + private lateinit var file: File + + /** + * Setup the test + */ + @BeforeEach + fun setUp() { + file = File.createTempFile("opendc", "parquet") + } + + /** + * Tear down the test. + */ + @AfterEach + fun tearDown() { + file.delete() + } + + /** + * Initial test to verify whether the Parquet writer works. + */ + @Test + fun testSmoke() { + val n = 4 + val writer = AvroParquetWriter.builder<GenericData.Record>(LocalOutputFile(file)) + .withSchema(schema) + .withWriteMode(ParquetFileWriter.Mode.OVERWRITE) + .build() + + try { + repeat(n) { i -> + val record = GenericData.Record(schema) + record.put("field", i) + writer.write(record) + } + } finally { + writer.close() + } + + val reader = AvroParquetReader.builder<GenericData.Record>(LocalInputFile(file)) + .build() + + var counter = 0 + try { + while (true) { + val record = reader.read() ?: break + assertEquals(counter++, record.get("field")) + } + } finally { + reader.close() + } + + assertEquals(n, counter) + } + + /** + * Test if overwriting fails if not specified. + */ + @Test + fun testOverwrite() { + assertThrows<FileAlreadyExistsException> { + AvroParquetWriter.builder<GenericData.Record>(LocalOutputFile(file)) + .withSchema(schema) + .build() + } + } + + /** + * Test non-existent file. + */ + @Test + fun testNonExistent() { + file.delete() + assertThrows<NoSuchFileException> { + AvroParquetReader.builder<GenericData.Record>(LocalInputFile(file)) + .build() + } + } +} diff --git a/opendc-trace/opendc-trace-swf/build.gradle.kts b/opendc-trace/opendc-trace-swf/build.gradle.kts new file mode 100644 index 00000000..c9eaa78d --- /dev/null +++ b/opendc-trace/opendc-trace-swf/build.gradle.kts @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +description = "Support for Standard Workload Format (SWF) traces in OpenDC" + +/* Build configuration */ +plugins { + `kotlin-library-conventions` + `testing-conventions` + `jacoco-conventions` +} + +dependencies { + api(platform(projects.opendcPlatform)) + api(projects.opendcTrace.opendcTraceApi) +} diff --git a/opendc-trace/opendc-trace-swf/src/main/kotlin/org/opendc/trace/swf/SwfTaskTableReader.kt b/opendc-trace/opendc-trace-swf/src/main/kotlin/org/opendc/trace/swf/SwfTaskTableReader.kt new file mode 100644 index 00000000..2f6ea6ee --- /dev/null +++ b/opendc-trace/opendc-trace-swf/src/main/kotlin/org/opendc/trace/swf/SwfTaskTableReader.kt @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.trace.swf + +import org.opendc.trace.* +import java.io.BufferedReader +import java.time.Duration +import java.time.Instant + +/** + * A [TableReader] implementation for the SWF format. + */ +internal class SwfTaskTableReader(private val reader: BufferedReader) : TableReader { + /** + * The current row. + */ + private var fields = emptyList<String>() + + /** + * A [Regex] object to match whitespace. + */ + private val whitespace = "\\s+".toRegex() + + override fun nextRow(): Boolean { + var line: String + var num = 0 + + while (true) { + line = reader.readLine() ?: return false + num++ + + if (line.isBlank()) { + // Ignore empty lines + continue + } else if (line.startsWith(";")) { + // Ignore comments for now + continue + } + + break + } + + fields = line.trim().split(whitespace) + + if (fields.size < 18) { + throw IllegalArgumentException("Invalid format at line $line") + } + + return true + } + + override fun resolve(column: TableColumn<*>): Int = columns[column] ?: -1 + + override fun isNull(index: Int): Boolean { + require(index in columns.values) { "Invalid column index" } + return false + } + + override fun get(index: Int): Any? { + return when (index) { + COL_JOB_ID -> fields[index] + COL_SUBMIT_TIME -> Instant.ofEpochSecond(fields[index].toLong(10)) + COL_WAIT_TIME, COL_RUN_TIME -> Duration.ofSeconds(fields[index].toLong(10)) + COL_REQ_NCPUS, COL_ALLOC_NCPUS, COL_STATUS, COL_GROUP_ID, COL_USER_ID -> getInt(index) + COL_PARENT_JOB -> { + val parent = fields[index].toLong(10) + if (parent < 0) emptySet() else setOf(parent) + } + else -> throw IllegalArgumentException("Invalid column") + } + } + + override fun getBoolean(index: Int): Boolean { + throw IllegalArgumentException("Invalid column") + } + + override fun getInt(index: Int): Int { + return when (index) { + COL_REQ_NCPUS, COL_ALLOC_NCPUS, COL_STATUS, COL_GROUP_ID, COL_USER_ID -> fields[index].toInt(10) + else -> throw IllegalArgumentException("Invalid column") + } + } + + override fun getLong(index: Int): Long { + throw IllegalArgumentException("Invalid column") + } + + override fun getDouble(index: Int): Double { + throw IllegalArgumentException("Invalid column") + } + + override fun close() { + reader.close() + } + + /** + * Default column indices for the SWF format. + */ + private val COL_JOB_ID = 0 + private val COL_SUBMIT_TIME = 1 + private val COL_WAIT_TIME = 2 + private val COL_RUN_TIME = 3 + private val COL_ALLOC_NCPUS = 4 + private val COL_AVG_CPU_TIME = 5 + private val COL_USED_MEM = 6 + private val COL_REQ_NCPUS = 7 + private val COL_REQ_TIME = 8 + private val COL_REQ_MEM = 9 + private val COL_STATUS = 10 + private val COL_USER_ID = 11 + private val COL_GROUP_ID = 12 + private val COL_EXEC_NUM = 13 + private val COL_QUEUE_NUM = 14 + private val COL_PART_NUM = 15 + private val COL_PARENT_JOB = 16 + private val COL_PARENT_THINK_TIME = 17 + + private val columns = mapOf( + TASK_ID to COL_JOB_ID, + TASK_SUBMIT_TIME to COL_SUBMIT_TIME, + TASK_WAIT_TIME to COL_WAIT_TIME, + TASK_RUNTIME to COL_RUN_TIME, + TASK_ALLOC_NCPUS to COL_ALLOC_NCPUS, + TASK_REQ_NCPUS to COL_REQ_NCPUS, + TASK_STATUS to COL_STATUS, + TASK_USER_ID to COL_USER_ID, + TASK_GROUP_ID to COL_GROUP_ID, + TASK_PARENTS to COL_PARENT_JOB + ) +} diff --git a/opendc-trace/opendc-trace-swf/src/main/kotlin/org/opendc/trace/swf/SwfTraceFormat.kt b/opendc-trace/opendc-trace-swf/src/main/kotlin/org/opendc/trace/swf/SwfTraceFormat.kt new file mode 100644 index 00000000..1fd076d5 --- /dev/null +++ b/opendc-trace/opendc-trace-swf/src/main/kotlin/org/opendc/trace/swf/SwfTraceFormat.kt @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2020 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.trace.swf + +import org.opendc.trace.* +import org.opendc.trace.spi.TableDetails +import org.opendc.trace.spi.TraceFormat +import java.nio.file.Path +import kotlin.io.path.bufferedReader + +/** + * Support for the Standard Workload Format (SWF) in OpenDC. + * + * The standard is defined by the PWA, see here: https://www.cse.huji.ac.il/labs/parallel/workload/swf.html + */ +public class SwfTraceFormat : TraceFormat { + override val name: String = "swf" + + override fun create(path: Path) { + throw UnsupportedOperationException("Writing not supported for this format") + } + + override fun getTables(path: Path): List<String> = listOf(TABLE_TASKS) + + override fun getDetails(path: Path, table: String): TableDetails { + return when (table) { + TABLE_TASKS -> TableDetails( + listOf( + TASK_ID, + TASK_SUBMIT_TIME, + TASK_WAIT_TIME, + TASK_RUNTIME, + TASK_REQ_NCPUS, + TASK_ALLOC_NCPUS, + TASK_PARENTS, + TASK_STATUS, + TASK_GROUP_ID, + TASK_USER_ID + ), + emptyList() + ) + else -> throw IllegalArgumentException("Table $table not supported") + } + } + + override fun newReader(path: Path, table: String): TableReader { + return when (table) { + TABLE_TASKS -> SwfTaskTableReader(path.bufferedReader()) + else -> throw IllegalArgumentException("Table $table not supported") + } + } + + override fun newWriter(path: Path, table: String): TableWriter { + throw UnsupportedOperationException("Writing not supported for this format") + } +} diff --git a/opendc-trace/opendc-trace-swf/src/main/resources/META-INF/services/org.opendc.trace.spi.TraceFormat b/opendc-trace/opendc-trace-swf/src/main/resources/META-INF/services/org.opendc.trace.spi.TraceFormat new file mode 100644 index 00000000..6c6b0eb2 --- /dev/null +++ b/opendc-trace/opendc-trace-swf/src/main/resources/META-INF/services/org.opendc.trace.spi.TraceFormat @@ -0,0 +1 @@ +org.opendc.trace.swf.SwfTraceFormat diff --git a/opendc-trace/opendc-trace-swf/src/test/kotlin/org/opendc/trace/swf/SwfTraceFormatTest.kt b/opendc-trace/opendc-trace-swf/src/test/kotlin/org/opendc/trace/swf/SwfTraceFormatTest.kt new file mode 100644 index 00000000..4dcd43f6 --- /dev/null +++ b/opendc-trace/opendc-trace-swf/src/test/kotlin/org/opendc/trace/swf/SwfTraceFormatTest.kt @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.trace.swf + +import org.junit.jupiter.api.* +import org.junit.jupiter.api.Assertions.* +import org.opendc.trace.TABLE_TASKS +import org.opendc.trace.TASK_ALLOC_NCPUS +import org.opendc.trace.TASK_ID +import java.nio.file.Paths + +/** + * Test suite for the [SwfTraceFormat] class. + */ +internal class SwfTraceFormatTest { + private val format = SwfTraceFormat() + + @Test + fun testTables() { + val path = Paths.get(checkNotNull(SwfTraceFormatTest::class.java.getResource("/trace.swf")).toURI()) + + assertEquals(listOf(TABLE_TASKS), format.getTables(path)) + } + + @Test + fun testTableExists() { + val path = Paths.get(checkNotNull(SwfTraceFormatTest::class.java.getResource("/trace.swf")).toURI()) + assertDoesNotThrow { format.getDetails(path, TABLE_TASKS) } + } + + @Test + fun testTableDoesNotExist() { + val path = Paths.get(checkNotNull(SwfTraceFormatTest::class.java.getResource("/trace.swf")).toURI()) + + assertThrows<IllegalArgumentException> { format.getDetails(path, "test") } + } + + @Test + fun testReader() { + val path = Paths.get(checkNotNull(SwfTraceFormatTest::class.java.getResource("/trace.swf")).toURI()) + val reader = format.newReader(path, TABLE_TASKS) + + assertAll( + { assertTrue(reader.nextRow()) }, + { assertEquals("1", reader.get(TASK_ID)) }, + { assertEquals(306, reader.getInt(TASK_ALLOC_NCPUS)) }, + { assertTrue(reader.nextRow()) }, + { assertEquals("2", reader.get(TASK_ID)) }, + { assertEquals(17, reader.getInt(TASK_ALLOC_NCPUS)) }, + ) + + reader.close() + } +} diff --git a/opendc-format/src/test/resources/swf_trace.txt b/opendc-trace/opendc-trace-swf/src/test/resources/trace.swf index c3ecf890..c3ecf890 100644 --- a/opendc-format/src/test/resources/swf_trace.txt +++ b/opendc-trace/opendc-trace-swf/src/test/resources/trace.swf diff --git a/opendc-trace/opendc-trace-tools/build.gradle.kts b/opendc-trace/opendc-trace-tools/build.gradle.kts new file mode 100644 index 00000000..14a0fc7c --- /dev/null +++ b/opendc-trace/opendc-trace-tools/build.gradle.kts @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +description = "Tools for working with workload traces" + +/* Build configuration */ +plugins { + `kotlin-conventions` + application +} + +application { + mainClass.set("org.opendc.trace.tools.TraceConverter") +} + +dependencies { + api(platform(projects.opendcPlatform)) + + implementation(projects.opendcTrace.opendcTraceApi) + implementation(libs.kotlin.logging) + implementation(libs.clikt) + + runtimeOnly(projects.opendcTrace.opendcTraceOpendc) + runtimeOnly(projects.opendcTrace.opendcTraceBitbrains) + runtimeOnly(projects.opendcTrace.opendcTraceAzure) + runtimeOnly(libs.log4j.slf4j) +} diff --git a/opendc-trace/opendc-trace-tools/src/main/kotlin/org/opendc/trace/tools/TraceConverter.kt b/opendc-trace/opendc-trace-tools/src/main/kotlin/org/opendc/trace/tools/TraceConverter.kt new file mode 100644 index 00000000..6fad43be --- /dev/null +++ b/opendc-trace/opendc-trace-tools/src/main/kotlin/org/opendc/trace/tools/TraceConverter.kt @@ -0,0 +1,258 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +@file:JvmName("TraceConverter") +package org.opendc.trace.tools + +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.parameters.arguments.argument +import com.github.ajalt.clikt.parameters.groups.OptionGroup +import com.github.ajalt.clikt.parameters.groups.cooccurring +import com.github.ajalt.clikt.parameters.options.* +import com.github.ajalt.clikt.parameters.types.* +import mu.KotlinLogging +import org.opendc.trace.* +import java.io.File +import java.time.Duration +import java.time.Instant +import java.util.* +import kotlin.math.abs +import kotlin.math.max +import kotlin.math.min + +/** + * A script to convert a trace in text format into a Parquet trace. + */ +fun main(args: Array<String>): Unit = TraceConverterCli().main(args) + +/** + * Represents the command for converting traces + */ +internal class TraceConverterCli : CliktCommand(name = "trace-converter") { + /** + * The logger instance for the converter. + */ + private val logger = KotlinLogging.logger {} + + /** + * The directory where the trace should be stored. + */ + private val output by option("-O", "--output", help = "path to store the trace") + .file(canBeFile = false, mustExist = false) + .defaultLazy { File("output") } + + /** + * The directory where the input trace is located. + */ + private val input by argument("input", help = "path to the input trace") + .file(canBeFile = false) + + /** + * The input format of the trace. + */ + private val inputFormat by option("-f", "--input-format", help = "format of output trace") + .required() + + /** + * The format of the output trace. + */ + private val outputFormat by option("--output-format", help = "format of output trace") + .default("opendc-vm") + + /** + * The sampling options. + */ + private val samplingOptions by SamplingOptions().cooccurring() + + override fun run() { + val metaParquet = File(output, "meta.parquet") + val traceParquet = File(output, "trace.parquet") + + if (metaParquet.exists()) { + metaParquet.delete() + } + if (traceParquet.exists()) { + traceParquet.delete() + } + + val inputTrace = Trace.open(input, format = inputFormat) + val outputTrace = Trace.create(output, format = outputFormat) + + logger.info { "Building resources table" } + + val metaWriter = outputTrace.getTable(TABLE_RESOURCES)!!.newWriter() + + val selectedVms = metaWriter.use { convertResources(inputTrace, it) } + + if (selectedVms.isEmpty()) { + logger.warn { "No VMs selected" } + return + } + + logger.info { "Wrote ${selectedVms.size} rows" } + logger.info { "Building resource states table" } + + val writer = outputTrace.getTable(TABLE_RESOURCE_STATES)!!.newWriter() + + val statesCount = writer.use { convertResourceStates(inputTrace, it, selectedVms) } + logger.info { "Wrote $statesCount rows" } + } + + /** + * Convert the resources table for the trace. + */ + private fun convertResources(trace: Trace, writer: TableWriter): Set<String> { + val random = samplingOptions?.let { Random(it.seed) } + val samplingFraction = samplingOptions?.fraction ?: 1.0 + val reader = checkNotNull(trace.getTable(TABLE_RESOURCE_STATES)).newReader() + + var hasNextRow = reader.nextRow() + val selectedVms = mutableSetOf<String>() + + while (hasNextRow) { + var id: String + var numCpus = Int.MIN_VALUE + var memCapacity = Double.MIN_VALUE + var memUsage = Double.MIN_VALUE + var startTime = Long.MAX_VALUE + var stopTime = Long.MIN_VALUE + + do { + id = reader.get(RESOURCE_ID) + + val timestamp = reader.get(RESOURCE_STATE_TIMESTAMP).toEpochMilli() + startTime = min(startTime, timestamp) + stopTime = max(stopTime, timestamp) + + numCpus = max(numCpus, reader.getInt(RESOURCE_CPU_COUNT)) + + memCapacity = max(memCapacity, reader.getDouble(RESOURCE_MEM_CAPACITY)) + if (reader.hasColumn(RESOURCE_STATE_MEM_USAGE)) { + memUsage = max(memUsage, reader.getDouble(RESOURCE_STATE_MEM_USAGE)) + } + + hasNextRow = reader.nextRow() + } while (hasNextRow && id == reader.get(RESOURCE_ID)) + + // Sample only a fraction of the VMs + if (random != null && random.nextDouble() > samplingFraction) { + continue + } + + logger.info { "Selecting VM $id" } + selectedVms.add(id) + + writer.startRow() + writer.set(RESOURCE_ID, id) + writer.set(RESOURCE_START_TIME, Instant.ofEpochMilli(startTime)) + writer.set(RESOURCE_STOP_TIME, Instant.ofEpochMilli(stopTime)) + writer.setInt(RESOURCE_CPU_COUNT, numCpus) + writer.setDouble(RESOURCE_MEM_CAPACITY, max(memCapacity, memUsage)) + writer.endRow() + } + + return selectedVms + } + + /** + * Convert the resource states table for the trace. + */ + private fun convertResourceStates(trace: Trace, writer: TableWriter, selectedVms: Set<String>): Int { + val reader = checkNotNull(trace.getTable(TABLE_RESOURCE_STATES)).newReader() + + var hasNextRow = reader.nextRow() + var count = 0 + var lastId: String? = null + var lastTimestamp = 0L + + while (hasNextRow) { + val id = reader.get(RESOURCE_ID) + + if (id !in selectedVms) { + hasNextRow = reader.nextRow() + continue + } + + val cpuCount = reader.getInt(RESOURCE_CPU_COUNT) + val cpuUsage = reader.getDouble(RESOURCE_STATE_CPU_USAGE) + + val startTimestamp = reader.get(RESOURCE_STATE_TIMESTAMP).toEpochMilli() + var timestamp = startTimestamp + var duration: Long + + // Check whether the previous entry is from a different VM + if (id != lastId) { + lastTimestamp = timestamp - 5 * 60 * 1000L + } + + do { + timestamp = reader.get(RESOURCE_STATE_TIMESTAMP).toEpochMilli() + + duration = timestamp - lastTimestamp + hasNextRow = reader.nextRow() + + if (!hasNextRow) { + break + } + + val shouldContinue = id == reader.get(RESOURCE_ID) && + abs(cpuUsage - reader.getDouble(RESOURCE_STATE_CPU_USAGE)) < 0.01 && + cpuCount == reader.getInt(RESOURCE_CPU_COUNT) + } while (shouldContinue) + + writer.startRow() + writer.set(RESOURCE_ID, id) + writer.set(RESOURCE_STATE_TIMESTAMP, Instant.ofEpochMilli(startTimestamp)) + writer.set(RESOURCE_STATE_DURATION, Duration.ofMillis(duration)) + writer.setInt(RESOURCE_CPU_COUNT, cpuCount) + writer.setDouble(RESOURCE_STATE_CPU_USAGE, cpuUsage) + writer.endRow() + + count++ + + lastId = id + lastTimestamp = timestamp + } + + return count + } + + /** + * Options for sampling the workload trace. + */ + private class SamplingOptions : OptionGroup() { + /** + * The fraction of VMs to sample + */ + val fraction by option("--sampling-fraction", help = "fraction of the workload to sample") + .double() + .restrictTo(0.0001, 1.0) + .required() + + /** + * The seed for sampling the trace. + */ + val seed by option("--sampling-seed", help = "seed for sampling the workload") + .long() + .default(0) + } +} diff --git a/opendc-trace/opendc-trace-wfformat/build.gradle.kts b/opendc-trace/opendc-trace-wfformat/build.gradle.kts new file mode 100644 index 00000000..2d336d03 --- /dev/null +++ b/opendc-trace/opendc-trace-wfformat/build.gradle.kts @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +description = "Support for WfCommons workload traces in OpenDC" + +/* Build configuration */ +plugins { + `kotlin-library-conventions` + `testing-conventions` + `jacoco-conventions` +} + +dependencies { + api(platform(projects.opendcPlatform)) + api(projects.opendcTrace.opendcTraceApi) + + implementation(libs.jackson.core) +} diff --git a/opendc-trace/opendc-trace-wfformat/src/main/kotlin/org/opendc/trace/wfformat/WfFormatTaskTableReader.kt b/opendc-trace/opendc-trace-wfformat/src/main/kotlin/org/opendc/trace/wfformat/WfFormatTaskTableReader.kt new file mode 100644 index 00000000..7f378d80 --- /dev/null +++ b/opendc-trace/opendc-trace-wfformat/src/main/kotlin/org/opendc/trace/wfformat/WfFormatTaskTableReader.kt @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.trace.wfformat + +import com.fasterxml.jackson.core.JsonParseException +import com.fasterxml.jackson.core.JsonParser +import com.fasterxml.jackson.core.JsonToken +import org.opendc.trace.* +import java.time.Duration +import kotlin.math.roundToInt + +/** + * A [TableReader] implementation for the WfCommons workload trace format. + */ +internal class WfFormatTaskTableReader(private val parser: JsonParser) : TableReader { + /** + * The current nesting of the parser. + */ + private var level: ParserLevel = ParserLevel.TOP + + override fun nextRow(): Boolean { + reset() + + var hasJob = false + + while (!hasJob) { + when (level) { + ParserLevel.TOP -> { + val token = parser.nextToken() + + // Check whether the document is not empty and starts with an object + if (token == null) { + break + } else if (token != JsonToken.START_OBJECT) { + throw JsonParseException(parser, "Expected object", parser.currentLocation) + } else { + level = ParserLevel.TRACE + } + } + ParserLevel.TRACE -> { + // Seek for the workflow object in the file + if (!seekWorkflow()) { + break + } else if (!parser.isExpectedStartObjectToken) { + throw JsonParseException(parser, "Expected object", parser.currentLocation) + } else { + level = ParserLevel.WORKFLOW + } + } + ParserLevel.WORKFLOW -> { + // Seek for the jobs object in the file + level = if (!seekJobs()) { + ParserLevel.TRACE + } else if (!parser.isExpectedStartArrayToken) { + throw JsonParseException(parser, "Expected array", parser.currentLocation) + } else { + ParserLevel.JOB + } + } + ParserLevel.JOB -> { + when (parser.nextToken()) { + JsonToken.END_ARRAY -> level = ParserLevel.WORKFLOW + JsonToken.START_OBJECT -> { + parseJob() + hasJob = true + break + } + else -> throw JsonParseException(parser, "Unexpected token", parser.currentLocation) + } + } + } + } + + return hasJob + } + + override fun resolve(column: TableColumn<*>): Int = columns[column] ?: -1 + + override fun isNull(index: Int): Boolean { + check(index in 0..columns.size) { "Invalid column value" } + return false + } + + override fun get(index: Int): Any? { + return when (index) { + COL_ID -> id + COL_WORKFLOW_ID -> workflowId + COL_RUNTIME -> runtime + COL_PARENTS -> parents + COL_CHILDREN -> children + COL_NPROC -> getInt(index) + else -> throw IllegalArgumentException("Invalid column") + } + } + + override fun getBoolean(index: Int): Boolean { + throw IllegalArgumentException("Invalid column") + } + + override fun getInt(index: Int): Int { + return when (index) { + COL_NPROC -> cores + else -> throw IllegalArgumentException("Invalid column") + } + } + + override fun getLong(index: Int): Long { + throw IllegalArgumentException("Invalid column") + } + + override fun getDouble(index: Int): Double { + throw IllegalArgumentException("Invalid column") + } + + override fun close() { + parser.close() + } + + /** + * Parse the trace and seek until the workflow description. + */ + private fun seekWorkflow(): Boolean { + while (parser.nextValue() != JsonToken.END_OBJECT) { + when (parser.currentName) { + "name" -> workflowId = parser.text + "workflow" -> return true + else -> parser.skipChildren() + } + } + + return false + } + + /** + * Parse the workflow description in the file and seek until the first job. + */ + private fun seekJobs(): Boolean { + while (parser.nextValue() != JsonToken.END_OBJECT) { + when (parser.currentName) { + "jobs" -> return true + else -> parser.skipChildren() + } + } + + return false + } + + /** + * Parse a single job in the file. + */ + private fun parseJob() { + while (parser.nextValue() != JsonToken.END_OBJECT) { + when (parser.currentName) { + "name" -> id = parser.text + "parents" -> parents = parseIds() + "children" -> children = parseIds() + "runtime" -> runtime = Duration.ofSeconds(parser.numberValue.toLong()) + "cores" -> cores = parser.floatValue.roundToInt() + else -> parser.skipChildren() + } + } + } + + /** + * Parse the parents/children of a job. + */ + private fun parseIds(): Set<String> { + if (!parser.isExpectedStartArrayToken) { + throw JsonParseException(parser, "Expected array", parser.currentLocation) + } + + val ids = mutableSetOf<String>() + + while (parser.nextToken() != JsonToken.END_ARRAY) { + if (parser.currentToken != JsonToken.VALUE_STRING) { + throw JsonParseException(parser, "Expected token", parser.currentLocation) + } + + ids.add(parser.valueAsString) + } + + return ids + } + + private enum class ParserLevel { + TOP, TRACE, WORKFLOW, JOB + } + + /** + * State fields for the parser. + */ + private var id: String? = null + private var workflowId: String? = null + private var runtime: Duration? = null + private var parents: Set<String>? = null + private var children: Set<String>? = null + private var cores = -1 + + private fun reset() { + id = null + runtime = null + parents = null + children = null + cores = -1 + } + + private val COL_ID = 0 + private val COL_WORKFLOW_ID = 1 + private val COL_RUNTIME = 3 + private val COL_NPROC = 4 + private val COL_PARENTS = 5 + private val COL_CHILDREN = 6 + + private val columns = mapOf( + TASK_ID to COL_ID, + TASK_WORKFLOW_ID to COL_WORKFLOW_ID, + TASK_RUNTIME to COL_RUNTIME, + TASK_REQ_NCPUS to COL_NPROC, + TASK_PARENTS to COL_PARENTS, + TASK_CHILDREN to COL_CHILDREN, + ) +} diff --git a/opendc-trace/opendc-trace-wfformat/src/main/kotlin/org/opendc/trace/wfformat/WfFormatTraceFormat.kt b/opendc-trace/opendc-trace-wfformat/src/main/kotlin/org/opendc/trace/wfformat/WfFormatTraceFormat.kt new file mode 100644 index 00000000..c75e3cbb --- /dev/null +++ b/opendc-trace/opendc-trace-wfformat/src/main/kotlin/org/opendc/trace/wfformat/WfFormatTraceFormat.kt @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.trace.wfformat + +import com.fasterxml.jackson.core.JsonFactory +import org.opendc.trace.* +import org.opendc.trace.spi.TableDetails +import org.opendc.trace.spi.TraceFormat +import java.nio.file.Path + +/** + * A [TraceFormat] implementation for the WfCommons workload trace format. + */ +public class WfFormatTraceFormat : TraceFormat { + /** + * The [JsonFactory] that is used to created JSON parsers. + */ + private val factory = JsonFactory() + + override val name: String = "wfformat" + + override fun create(path: Path) { + throw UnsupportedOperationException("Writing not supported for this format") + } + + override fun getTables(path: Path): List<String> = listOf(TABLE_TASKS) + + override fun getDetails(path: Path, table: String): TableDetails { + return when (table) { + TABLE_TASKS -> TableDetails( + listOf( + TASK_ID, + TASK_WORKFLOW_ID, + TASK_RUNTIME, + TASK_REQ_NCPUS, + TASK_PARENTS, + TASK_CHILDREN + ), + emptyList() + ) + else -> throw IllegalArgumentException("Table $table not supported") + } + } + + override fun newReader(path: Path, table: String): TableReader { + return when (table) { + TABLE_TASKS -> WfFormatTaskTableReader(factory.createParser(path.toFile())) + else -> throw IllegalArgumentException("Table $table not supported") + } + } + + override fun newWriter(path: Path, table: String): TableWriter { + throw UnsupportedOperationException("Writing not supported for this format") + } +} diff --git a/opendc-trace/opendc-trace-wfformat/src/main/resources/META-INF/services/org.opendc.trace.spi.TraceFormat b/opendc-trace/opendc-trace-wfformat/src/main/resources/META-INF/services/org.opendc.trace.spi.TraceFormat new file mode 100644 index 00000000..ee3aa2f6 --- /dev/null +++ b/opendc-trace/opendc-trace-wfformat/src/main/resources/META-INF/services/org.opendc.trace.spi.TraceFormat @@ -0,0 +1 @@ +org.opendc.trace.wfformat.WfFormatTraceFormat diff --git a/opendc-trace/opendc-trace-wfformat/src/test/kotlin/org/opendc/trace/wfformat/WfFormatTaskTableReaderTest.kt b/opendc-trace/opendc-trace-wfformat/src/test/kotlin/org/opendc/trace/wfformat/WfFormatTaskTableReaderTest.kt new file mode 100644 index 00000000..b07f27ed --- /dev/null +++ b/opendc-trace/opendc-trace-wfformat/src/test/kotlin/org/opendc/trace/wfformat/WfFormatTaskTableReaderTest.kt @@ -0,0 +1,345 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.trace.wfformat + +import com.fasterxml.jackson.core.JsonFactory +import com.fasterxml.jackson.core.JsonParseException +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertDoesNotThrow +import org.junit.jupiter.api.assertThrows +import org.opendc.trace.TASK_ID +import org.opendc.trace.TASK_PARENTS + +/** + * Test suite for the [WfFormatTaskTableReader] class. + */ +internal class WfFormatTaskTableReaderTest { + /** + * The [JsonFactory] used to construct the parser. + */ + private val factory = JsonFactory() + + @Test + fun testEmptyInput() { + val content = "" + val parser = factory.createParser(content) + val reader = WfFormatTaskTableReader(parser) + + assertFalse(reader.nextRow()) + reader.close() + } + + @Test + fun testTopLevelArrayInput() { + val content = "[]" + val parser = factory.createParser(content) + val reader = WfFormatTaskTableReader(parser) + + assertThrows<JsonParseException> { + while (reader.nextRow()) { + continue + } + } + + reader.close() + } + + @Test + fun testNoWorkflow() { + val content = """ + { + "name": "eager-nextflow-chameleon" + } + """.trimIndent() + val parser = factory.createParser(content) + val reader = WfFormatTaskTableReader(parser) + + assertDoesNotThrow { + while (reader.nextRow()) { + continue + } + } + + reader.close() + } + + @Test + fun testWorkflowArrayType() { + val content = """ + { + "name": "eager-nextflow-chameleon", + "workflow": [] + } + """.trimIndent() + val parser = factory.createParser(content) + val reader = WfFormatTaskTableReader(parser) + + assertThrows<JsonParseException> { + while (reader.nextRow()) { + continue + } + } + + reader.close() + } + + @Test + fun testWorkflowNullType() { + val content = """ + { + "name": "eager-nextflow-chameleon", + "workflow": null + } + """.trimIndent() + val parser = factory.createParser(content) + val reader = WfFormatTaskTableReader(parser) + + assertThrows<JsonParseException> { + while (reader.nextRow()) { + continue + } + } + + reader.close() + } + + @Test + fun testNoJobs() { + val content = """ + { + "name": "eager-nextflow-chameleon", + "workflow": { + + } + } + """.trimIndent() + val parser = factory.createParser(content) + val reader = WfFormatTaskTableReader(parser) + + assertDoesNotThrow { reader.nextRow() } + + reader.close() + } + + @Test + fun testJobsObjectType() { + val content = """ + { + "name": "eager-nextflow-chameleon", + "workflow": { "jobs": {} } + } + """.trimIndent() + val parser = factory.createParser(content) + val reader = WfFormatTaskTableReader(parser) + + assertThrows<JsonParseException> { reader.nextRow() } + + reader.close() + } + + @Test + fun testJobsNullType() { + val content = """ + { + "name": "eager-nextflow-chameleon", + "workflow": { "jobs": null } + } + """.trimIndent() + val parser = factory.createParser(content) + val reader = WfFormatTaskTableReader(parser) + + assertThrows<JsonParseException> { reader.nextRow() } + + reader.close() + } + + @Test + fun testJobsInvalidChildType() { + val content = """ + { + "name": "eager-nextflow-chameleon", + "workflow": { + "jobs": [1] + } + } + """.trimIndent() + val parser = factory.createParser(content) + val reader = WfFormatTaskTableReader(parser) + + assertThrows<JsonParseException> { reader.nextRow() } + + reader.close() + } + + @Test + fun testJobsValidChildType() { + val content = """ + { + "name": "eager-nextflow-chameleon", + "workflow": { + "jobs": [ + { + "name": "test" + } + ] + } + } + """.trimIndent() + val parser = factory.createParser(content) + val reader = WfFormatTaskTableReader(parser) + + assertTrue(reader.nextRow()) + assertEquals("test", reader.get(TASK_ID)) + assertFalse(reader.nextRow()) + + reader.close() + } + + @Test + fun testJobsInvalidParents() { + val content = """ + { + "name": "eager-nextflow-chameleon", + "workflow": { + "jobs": [ + { + "name": "test", + "parents": 1, + } + ] + } + } + """.trimIndent() + val parser = factory.createParser(content) + val reader = WfFormatTaskTableReader(parser) + + assertThrows<JsonParseException> { reader.nextRow() } + + reader.close() + } + + @Test + fun testJobsInvalidParentsItem() { + val content = """ + { + "name": "eager-nextflow-chameleon", + "workflow": { + "jobs": [ + { + "name": "test", + "parents": [1], + } + ] + } + } + """.trimIndent() + val parser = factory.createParser(content) + val reader = WfFormatTaskTableReader(parser) + + assertThrows<JsonParseException> { reader.nextRow() } + + reader.close() + } + + @Test + fun testJobsValidParents() { + val content = """ + { + "name": "eager-nextflow-chameleon", + "workflow": { + "jobs": [ + { + "name": "test", + "parents": ["1"] + } + ] + } + } + """.trimIndent() + val parser = factory.createParser(content) + val reader = WfFormatTaskTableReader(parser) + + assertTrue(reader.nextRow()) + assertEquals(setOf("1"), reader.get(TASK_PARENTS)) + assertFalse(reader.nextRow()) + + reader.close() + } + + @Test + fun testJobsInvalidSecondEntry() { + val content = """ + { + "workflow": { + "jobs": [ + { + "name": "test", + "parents": ["1"] + }, + "test" + ] + } + } + """.trimIndent() + val parser = factory.createParser(content) + val reader = WfFormatTaskTableReader(parser) + + assertDoesNotThrow { reader.nextRow() } + assertThrows<JsonParseException> { reader.nextRow() } + + reader.close() + } + + @Test + fun testDuplicateJobsArray() { + val content = """ + { + "name": "eager-nextflow-chameleon", + "workflow": { + "jobs": [ + { + "name": "test", + "parents": ["1"] + } + ], + "jobs": [ + { + "name": "test2", + "parents": ["test"] + } + ] + } + } + """.trimIndent() + val parser = factory.createParser(content) + val reader = WfFormatTaskTableReader(parser) + + assertTrue(reader.nextRow()) + assertTrue(reader.nextRow()) + assertEquals("test2", reader.get(TASK_ID)) + assertFalse(reader.nextRow()) + + reader.close() + } +} diff --git a/opendc-trace/opendc-trace-wfformat/src/test/kotlin/org/opendc/trace/wfformat/WfFormatTraceFormatTest.kt b/opendc-trace/opendc-trace-wfformat/src/test/kotlin/org/opendc/trace/wfformat/WfFormatTraceFormatTest.kt new file mode 100644 index 00000000..217b175d --- /dev/null +++ b/opendc-trace/opendc-trace-wfformat/src/test/kotlin/org/opendc/trace/wfformat/WfFormatTraceFormatTest.kt @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.trace.wfformat + +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertDoesNotThrow +import org.junit.jupiter.api.assertThrows +import org.opendc.trace.* +import java.nio.file.Paths + +/** + * Test suite for the [WfFormatTraceFormat] class. + */ +class WfFormatTraceFormatTest { + private val format = WfFormatTraceFormat() + + @Test + fun testTables() { + val path = Paths.get("src/test/resources/trace.json") + + assertEquals(listOf(TABLE_TASKS), format.getTables(path)) + } + + @Test + fun testTableExists() { + val path = Paths.get("src/test/resources/trace.json") + Assertions.assertDoesNotThrow { format.getDetails(path, TABLE_TASKS) } + } + + @Test + fun testTableDoesNotExist() { + val path = Paths.get("src/test/resources/trace.json") + + assertThrows<IllegalArgumentException> { format.getDetails(path, "test") } + } + + /** + * Smoke test for parsing WfCommons traces. + */ + @Test + fun testTableReader() { + val path = Paths.get("src/test/resources/trace.json") + val reader = format.newReader(path, TABLE_TASKS) + + assertAll( + { assertTrue(reader.nextRow()) }, + { assertEquals("makebwaindex_mammoth_mt_krause.fasta", reader.get(TASK_ID)) }, + { assertEquals("eager-nextflow-chameleon", reader.get(TASK_WORKFLOW_ID)) }, + { assertEquals(172000, reader.get(TASK_RUNTIME).toMillis()) }, + { assertEquals(emptySet<String>(), reader.get(TASK_PARENTS)) }, + ) + + assertAll( + { assertTrue(reader.nextRow()) }, + { assertEquals("makeseqdict_mammoth_mt_krause.fasta", reader.get(TASK_ID)) }, + { assertEquals("eager-nextflow-chameleon", reader.get(TASK_WORKFLOW_ID)) }, + { assertEquals(175000, reader.get(TASK_RUNTIME).toMillis()) }, + { assertEquals(setOf("makebwaindex_mammoth_mt_krause.fasta"), reader.get(TASK_PARENTS)) }, + ) + + reader.close() + } + + /** + * Test full iteration of the table. + */ + @Test + fun testTableReaderFull() { + val path = Paths.get("src/test/resources/trace.json") + val reader = format.newReader(path, TABLE_TASKS) + + assertDoesNotThrow { + while (reader.nextRow()) { + // reader.get(TASK_ID) + } + reader.close() + } + } +} diff --git a/opendc-trace/opendc-trace-wfformat/src/test/resources/trace.json b/opendc-trace/opendc-trace-wfformat/src/test/resources/trace.json new file mode 100644 index 00000000..d21f024d --- /dev/null +++ b/opendc-trace/opendc-trace-wfformat/src/test/resources/trace.json @@ -0,0 +1,1342 @@ +{ + "name": "eager-nextflow-chameleon", + "description": "Instance generated with WfCommons - https://wfcommons.org", + "createdAt": "2021-09-06T03:43:31.762479", + "schemaVersion": "1.2", + "author": { + "name": "cc", + "email": "support@wfcommons.org" + }, + "wms": { + "name": "Nextflow", + "version": "21.04.3", + "url": "https://www.nextflow.io" + }, + "workflow": { + "executedAt": "20210906T034331+0000", + "makespan": 275, + "jobs": [ + { + "name": "makebwaindex_mammoth_mt_krause.fasta", + "type": "compute", + "runtime": 172.182, + "command": { + "program": "makebwaindex", + "arguments": [ + "bwa", + "index", + "Mammoth_MT_Krause.fasta", + "mkdir", + "BWAIndex", + "&&", + "mv", + "Mammoth_MT_Krause.fasta*", + "BWAIndex" + ] + }, + "parents": [], + "children": [ + "makeseqdict_mammoth_mt_krause.fasta" + ], + "files": [], + "cores": 1.0, + "id": "ID000001", + "category": "makebwaindex", + "avgCPU": 5.8, + "bytesRead": 124, + "bytesWritten": 126, + "memory": 4248 + }, + { + "name": "makeseqdict_mammoth_mt_krause.fasta", + "type": "compute", + "runtime": 175.427, + "command": { + "program": "makeseqdict", + "arguments": [ + "picard", + "-Xmx6144M", + "CreateSequenceDictionary", + "R=Mammoth_MT_Krause.fasta", + "O=\"Mammoth_MT_Krause.dict\"" + ] + }, + "parents": [ + "makebwaindex_mammoth_mt_krause.fasta" + ], + "children": [ + "makefastaindex_mammoth_mt_krause.fasta" + ], + "files": [], + "cores": 1.0, + "id": "ID000003", + "category": "makeseqdict", + "avgCPU": 83.5, + "bytesRead": 22728, + "bytesWritten": 1300, + "memory": 104416 + }, + { + "name": "makefastaindex_mammoth_mt_krause.fasta", + "type": "compute", + "runtime": 170.797, + "command": { + "program": "makefastaindex", + "arguments": [ + "samtools", + "faidx", + "Mammoth_MT_Krause.fasta" + ] + }, + "parents": [ + "makeseqdict_mammoth_mt_krause.fasta" + ], + "children": [ + "output_documentation" + ], + "files": [], + "cores": 1.0, + "id": "ID000002", + "category": "makefastaindex", + "avgCPU": 23.8, + "bytesRead": 66, + "bytesWritten": 4, + "memory": 6096 + }, + { + "name": "output_documentation", + "type": "compute", + "runtime": 173.479, + "command": { + "program": "output_documentation", + "arguments": [ + "markdown_to_html.py", + "output.md", + "-o", + "results_description.html" + ] + }, + "parents": [ + "makefastaindex_mammoth_mt_krause.fasta" + ], + "children": [ + "get_software_versions" + ], + "files": [], + "cores": 1.0, + "id": "ID000005", + "category": "output_documentation", + "avgCPU": 84.0, + "bytesRead": 8222, + "bytesWritten": 15165, + "memory": 11488 + }, + { + "name": "get_software_versions", + "type": "compute", + "runtime": 183.445, + "command": { + "program": "get_software_versions", + "arguments": [ + "echo", + "2.3.5", + "&>", + "v_pipeline.txt", + "echo", + "21.04.3", + "&>", + "v_nextflow.txt", + "fastqc", + "--version", + "&>", + "v_fastqc.txt", + "2>&1", + "||", + "true", + "AdapterRemoval", + "--version", + "&>", + "v_adapterremoval.txt", + "2>&1", + "||", + "true", + "fastp", + "--version", + "&>", + "v_fastp.txt", + "2>&1", + "||", + "true", + "bwa", + "&>", + "v_bwa.txt", + "2>&1", + "||", + "true", + "circulargenerator", + "--help", + "|", + "head", + "-n", + "1", + "&>", + "v_circulargenerator.txt", + "2>&1", + "||", + "true", + "samtools", + "--version", + "&>", + "v_samtools.txt", + "2>&1", + "||", + "true", + "dedup", + "-v", + "&>", + "v_dedup.txt", + "2>&1", + "||", + "true", + "##", + "bioconda", + "recipe", + "of", + "picard", + "is", + "incorrectly", + "set", + "up", + "and", + "extra", + "warning", + "made", + "with", + "stderr,", + "this", + "ugly", + "command", + "ensures", + "only", + "version", + "exported", + "(", + "exec", + "7>&1", + "picard", + "MarkDuplicates", + "--version", + "2>&1", + ">&7", + "|", + "grep", + "-v", + "/", + ">&2", + ")", + "2>", + "v_markduplicates.txt", + "||", + "true", + "qualimap", + "--version", + "&>", + "v_qualimap.txt", + "2>&1", + "||", + "true", + "preseq", + "&>", + "v_preseq.txt", + "2>&1", + "||", + "true", + "gatk", + "--version", + "2>&1", + "|", + "head", + "-n", + "1", + ">", + "v_gatk.txt", + "2>&1", + "||", + "true", + "gatk3", + "--version", + "2>&1", + ">", + "v_gatk3.txt", + "2>&1", + "||", + "true", + "freebayes", + "--version", + "&>", + "v_freebayes.txt", + "2>&1", + "||", + "true", + "bedtools", + "--version", + "&>", + "v_bedtools.txt", + "2>&1", + "||", + "true", + "damageprofiler", + "--version", + "&>", + "v_damageprofiler.txt", + "2>&1", + "||", + "true", + "bam", + "--version", + "&>", + "v_bamutil.txt", + "2>&1", + "||", + "true", + "pmdtools", + "--version", + "&>", + "v_pmdtools.txt", + "2>&1", + "||", + "true", + "angsd", + "-h", + "|&", + "head", + "-n", + "1", + "|", + "cut", + "-d", + "-f3-4", + "&>", + "v_angsd.txt", + "2>&1", + "||", + "true", + "multivcfanalyzer", + "--help", + "|", + "head", + "-n", + "1", + "&>", + "v_multivcfanalyzer.txt", + "2>&1", + "||", + "true", + "malt-run", + "--help", + "|&", + "tail", + "-n", + "3", + "|", + "head", + "-n", + "1", + "|", + "cut", + "-f", + "2", + "-d(", + "|", + "cut", + "-f", + "1", + "-d", + ",", + "&>", + "v_malt.txt", + "2>&1", + "||", + "true", + "MaltExtract", + "--help", + "|", + "head", + "-n", + "2", + "|", + "tail", + "-n", + "1", + "&>", + "v_maltextract.txt", + "2>&1", + "||", + "true", + "multiqc", + "--version", + "&>", + "v_multiqc.txt", + "2>&1", + "||", + "true", + "vcf2genome", + "-h", + "|&", + "head", + "-n", + "1", + "&>", + "v_vcf2genome.txt", + "||", + "true", + "mtnucratio", + "--help", + "&>", + "v_mtnucratiocalculator.txt", + "||", + "true", + "sexdeterrmine", + "--version", + "&>", + "v_sexdeterrmine.txt", + "||", + "true", + "kraken2", + "--version", + "|", + "head", + "-n", + "1", + "&>", + "v_kraken.txt", + "||", + "true", + "endorS.py", + "--version", + "&>", + "v_endorSpy.txt", + "||", + "true", + "pileupCaller", + "--version", + "&>", + "v_sequencetools.txt", + "2>&1", + "||", + "true", + "bowtie2", + "--version", + "|", + "grep", + "-a", + "bowtie2-.*", + "-fdebug", + ">", + "v_bowtie2.txt", + "||", + "true", + "eigenstrat_snp_coverage", + "--version", + "|", + "cut", + "-d", + "-f2", + ">v_eigenstrat_snp_coverage.txt", + "||", + "true", + "mapDamage", + "--version", + ">", + "v_mapdamage.txt", + "||", + "true", + "bbduk.sh", + "|", + "grep", + "Last", + "modified", + "|", + "cut", + "-d", + "-f", + "3-99", + ">", + "v_bbduk.txt", + "||", + "true", + "scrape_software_versions.py", + "&>", + "software_versions_mqc.yaml" + ] + }, + "parents": [ + "output_documentation" + ], + "children": [ + "fastqc_jk2782_l1", + "fastqc_jk2802_l2" + ], + "files": [], + "cores": 2.0, + "id": "ID000006", + "category": "get_software_versions", + "avgCPU": 147.8, + "bytesRead": 172760, + "bytesWritten": 1048, + "memory": 387324 + }, + { + "name": "fastqc_jk2782_l1", + "type": "compute", + "runtime": 175.205, + "command": { + "program": "fastqc", + "arguments": [ + "fastqc", + "-t", + "2", + "-q", + "JK2782_TGGCCGATCAACGA_L008_R1_001.fastq.gz.tengrand.fq.gz", + "JK2782_TGGCCGATCAACGA_L008_R2_001.fastq.gz.tengrand.fq.gz", + "rename", + "s/_fastqc.zip$/_raw_fastqc.zip/", + "*_fastqc.zip", + "rename", + "s/_fastqc.html$/_raw_fastqc.html/", + "*_fastqc.html" + ] + }, + "parents": [ + "get_software_versions" + ], + "children": [ + "adapter_removal_jk2782_l1", + "adapter_removal_jk2802_l2" + ], + "files": [], + "cores": 2.0, + "id": "ID000007", + "category": "fastqc", + "avgCPU": 161.8, + "bytesRead": 35981, + "bytesWritten": 3967, + "memory": 270124 + }, + { + "name": "adapter_removal_jk2782_l1", + "type": "compute", + "runtime": 172.643, + "command": { + "program": "adapter_removal", + "arguments": [ + "mkdir", + "-p", + "output", + "AdapterRemoval", + "--file1", + "JK2782_TGGCCGATCAACGA_L008_R1_001.fastq.gz.tengrand.fq.gz", + "--file2", + "JK2782_TGGCCGATCAACGA_L008_R2_001.fastq.gz.tengrand.fq.gz", + "--basename", + "JK2782_TGGCCGATCAACGA_L008_R1_001.fastq.gz.tengrand.fq_L1.pe", + "--gzip", + "--threads", + "2", + "--qualitymax", + "41", + "--collapse", + "--trimns", + "--trimqualities", + "--adapter1", + "AGATCGGAAGAGCACACGTCTGAACTCCAGTCAC", + "--adapter2", + "AGATCGGAAGAGCGTCGTGTAGGGAAAGAGTGTA", + "--minlength", + "30", + "--minquality", + "20", + "--minadapteroverlap", + "1", + "cat", + "*.collapsed.gz", + "*.collapsed.truncated.gz", + "*.singleton.truncated.gz", + "*.pair1.truncated.gz", + "*.pair2.truncated.gz", + ">", + "output/JK2782_TGGCCGATCAACGA_L008_R1_001.fastq.gz.tengrand.fq_L1.pe.combined.tmp.fq.gz", + "mv", + "*.settings", + "output/", + "##", + "Add", + "R_", + "and", + "L_", + "for", + "unmerged", + "reads", + "for", + "DeDup", + "compatibility", + "AdapterRemovalFixPrefix", + "-Xmx4g", + "output/JK2782_TGGCCGATCAACGA_L008_R1_001.fastq.gz.tengrand.fq_L1.pe.combined.tmp.fq.gz", + "|", + "pigz", + "-p", + "1", + ">", + "output/JK2782_TGGCCGATCAACGA_L008_R1_001.fastq.gz.tengrand.fq_L1.pe.combined.fq.gz" + ] + }, + "parents": [ + "fastqc_jk2782_l1", + "fastqc_jk2802_l2" + ], + "children": [ + "fastqc_after_clipping_jk2782_l1", + "fastqc_after_clipping_jk2802_l2" + ], + "files": [], + "cores": 2.0, + "id": "ID000008", + "category": "adapter_removal", + "avgCPU": 160.9, + "bytesRead": 17357, + "bytesWritten": 4405, + "memory": 79308 + }, + { + "name": "fastqc_jk2802_l2", + "type": "compute", + "runtime": 177.338, + "command": { + "program": "fastqc", + "arguments": [ + "fastqc", + "-q", + "JK2802_AGAATAACCTACCA_L008_R1_001.fastq.gz.tengrand.fq.gz", + "rename", + "s/_fastqc.zip$/_raw_fastqc.zip/", + "*_fastqc.zip", + "rename", + "s/_fastqc.html$/_raw_fastqc.html/", + "*_fastqc.html" + ] + }, + "parents": [ + "get_software_versions" + ], + "children": [ + "adapter_removal_jk2782_l1", + "adapter_removal_jk2802_l2" + ], + "files": [], + "cores": 2.0, + "id": "ID000009", + "category": "fastqc", + "avgCPU": 120.1, + "bytesRead": 24457, + "bytesWritten": 2181, + "memory": 181060 + }, + { + "name": "adapter_removal_jk2802_l2", + "type": "compute", + "runtime": 174.313, + "command": { + "program": "adapter_removal", + "arguments": [ + "mkdir", + "-p", + "output", + "AdapterRemoval", + "--file1", + "JK2802_AGAATAACCTACCA_L008_R1_001.fastq.gz.tengrand.fq.gz", + "--basename", + "JK2802_AGAATAACCTACCA_L008_R1_001.fastq.gz.tengrand.fq_L2.se", + "--gzip", + "--threads", + "2", + "--qualitymax", + "41", + "--trimns", + "--trimqualities", + "--adapter1", + "AGATCGGAAGAGCACACGTCTGAACTCCAGTCAC", + "--adapter2", + "AGATCGGAAGAGCGTCGTGTAGGGAAAGAGTGTA", + "--minlength", + "30", + "--minquality", + "20", + "--minadapteroverlap", + "1", + "mv", + "*.settings", + "*.se.truncated.gz", + "output/" + ] + }, + "parents": [ + "fastqc_jk2782_l1", + "fastqc_jk2802_l2" + ], + "children": [ + "fastqc_after_clipping_jk2782_l1", + "fastqc_after_clipping_jk2802_l2" + ], + "files": [], + "cores": 2.0, + "id": "ID000010", + "category": "adapter_removal", + "avgCPU": 106.5, + "bytesRead": 683, + "bytesWritten": 897, + "memory": 12136 + }, + { + "name": "fastqc_after_clipping_jk2782_l1", + "type": "compute", + "runtime": 15.371, + "command": { + "program": "fastqc_after_clipping", + "arguments": [ + "fastqc", + "-t", + "2", + "-q", + "JK2782_TGGCCGATCAACGA_L008_R1_001.fastq.gz.tengrand.fq_L1.pe.combined.fq.gz" + ] + }, + "parents": [ + "adapter_removal_jk2782_l1", + "adapter_removal_jk2802_l2" + ], + "children": [ + "bwa_jk2802", + "bwa_jk2782" + ], + "files": [], + "cores": 2.0, + "id": "ID000013", + "category": "fastqc_after_clipping", + "avgCPU": 133.3, + "bytesRead": 23788, + "bytesWritten": 1998, + "memory": 215020 + }, + { + "name": "fastqc_after_clipping_jk2802_l2", + "type": "compute", + "runtime": 15.272, + "command": { + "program": "fastqc_after_clipping", + "arguments": [ + "fastqc", + "-t", + "2", + "-q", + "JK2802_AGAATAACCTACCA_L008_R1_001.fastq.gz.tengrand.fq_L2.se.truncated.gz" + ] + }, + "parents": [ + "adapter_removal_jk2782_l1", + "adapter_removal_jk2802_l2" + ], + "children": [ + "bwa_jk2802", + "bwa_jk2782" + ], + "files": [], + "cores": 2.0, + "id": "ID000014", + "category": "fastqc_after_clipping", + "avgCPU": 124.1, + "bytesRead": 23882, + "bytesWritten": 2143, + "memory": 213064 + }, + { + "name": "bwa_jk2802", + "type": "compute", + "runtime": 9.566, + "command": { + "program": "bwa", + "arguments": [ + "bwa", + "aln", + "-t", + "2", + "BWAIndex/Mammoth_MT_Krause.fasta", + "JK2802_AGAATAACCTACCA_L008_R1_001.fastq.gz.tengrand.fq_L2.se.truncated.gz", + "-n", + "0.04", + "-l", + "1024", + "-k", + "2", + "-o", + "1", + "-f", + "JK2802.sai", + "bwa", + "samse", + "-r", + "\"@RGtID:ILLUMINA-JK2802tSM:JK2802tPL:illuminatPU:ILLUMINA-JK2802-SE\"", + "BWAIndex/Mammoth_MT_Krause.fasta", + "JK2802.sai", + "JK2802_AGAATAACCTACCA_L008_R1_001.fastq.gz.tengrand.fq_L2.se.truncated.gz", + "|", + "samtools", + "sort", + "-@", + "1", + "-O", + "bam", + "-", + ">", + "\"JK2802\"_\"SE\".mapped.bam", + "samtools", + "index", + "\"JK2802\"_\"SE\".mapped.bam" + ] + }, + "parents": [ + "fastqc_after_clipping_jk2782_l1", + "fastqc_after_clipping_jk2802_l2" + ], + "children": [ + "samtools_flagstat_jk2782", + "samtools_flagstat_jk2802" + ], + "files": [], + "cores": 2.0, + "id": "ID000016", + "category": "bwa", + "avgCPU": 15.7, + "bytesRead": 3774, + "bytesWritten": 3367, + "memory": 10628 + }, + { + "name": "bwa_jk2782", + "type": "compute", + "runtime": 9.652, + "command": { + "program": "bwa", + "arguments": [ + "bwa", + "aln", + "-t", + "2", + "BWAIndex/Mammoth_MT_Krause.fasta", + "JK2782_TGGCCGATCAACGA_L008_R1_001.fastq.gz.tengrand.fq_L1.pe.combined.fq.gz", + "-n", + "0.04", + "-l", + "1024", + "-k", + "2", + "-o", + "1", + "-f", + "JK2782.sai", + "bwa", + "samse", + "-r", + "\"@RGtID:ILLUMINA-JK2782tSM:JK2782tPL:illuminatPU:ILLUMINA-JK2782-PE\"", + "BWAIndex/Mammoth_MT_Krause.fasta", + "JK2782.sai", + "JK2782_TGGCCGATCAACGA_L008_R1_001.fastq.gz.tengrand.fq_L1.pe.combined.fq.gz", + "|", + "samtools", + "sort", + "-@", + "1", + "-O", + "bam", + "-", + ">", + "\"JK2782\"_\"PE\".mapped.bam", + "samtools", + "index", + "\"JK2782\"_\"PE\".mapped.bam" + ] + }, + "parents": [ + "fastqc_after_clipping_jk2782_l1", + "fastqc_after_clipping_jk2802_l2" + ], + "children": [ + "samtools_flagstat_jk2782", + "samtools_flagstat_jk2802" + ], + "files": [], + "cores": 2.0, + "id": "ID000015", + "category": "bwa", + "avgCPU": 69.8, + "bytesRead": 3705, + "bytesWritten": 3355, + "memory": 12876 + }, + { + "name": "samtools_flagstat_jk2782", + "type": "compute", + "runtime": 13.011, + "command": { + "program": "samtools_flagstat", + "arguments": [ + "samtools", + "flagstat", + "JK2782_PE.mapped.bam", + ">", + "JK2782_flagstat.stats" + ] + }, + "parents": [ + "bwa_jk2802", + "bwa_jk2782" + ], + "children": [ + "markduplicates_jk2782", + "markduplicates_jk2802" + ], + "files": [], + "cores": 1.0, + "id": "ID000026", + "category": "samtools_flagstat", + "avgCPU": 30.1, + "bytesRead": 478, + "bytesWritten": 5, + "memory": 6468 + }, + { + "name": "samtools_flagstat_jk2802", + "type": "compute", + "runtime": 13.129, + "command": { + "program": "samtools_flagstat", + "arguments": [ + "samtools", + "flagstat", + "JK2802_SE.mapped.bam", + ">", + "JK2802_flagstat.stats" + ] + }, + "parents": [ + "bwa_jk2802", + "bwa_jk2782" + ], + "children": [ + "markduplicates_jk2782", + "markduplicates_jk2802" + ], + "files": [], + "cores": 1.0, + "id": "ID000024", + "category": "samtools_flagstat", + "avgCPU": 118.5, + "bytesRead": 551, + "bytesWritten": 5 + }, + { + "name": "markduplicates_jk2782", + "type": "compute", + "runtime": 22.655, + "command": { + "program": "markduplicates", + "arguments": [ + "mv", + "JK2782_PE.mapped.bam", + "JK2782.bam", + "picard", + "-Xmx4096M", + "MarkDuplicates", + "INPUT=JK2782.bam", + "OUTPUT=JK2782_rmdup.bam", + "REMOVE_DUPLICATES=TRUE", + "AS=TRUE", + "METRICS_FILE=\"JK2782_rmdup.metrics\"", + "VALIDATION_STRINGENCY=SILENT", + "samtools", + "index", + "JK2782_rmdup.bam" + ] + }, + "parents": [ + "samtools_flagstat_jk2782", + "samtools_flagstat_jk2802" + ], + "children": [ + "preseq_jk2782", + "preseq_jk2802" + ], + "files": [], + "cores": 2.0, + "id": "ID000021", + "category": "markduplicates", + "avgCPU": 173.6, + "bytesRead": 24055, + "bytesWritten": 2319, + "memory": 1400048 + }, + { + "name": "markduplicates_jk2802", + "type": "compute", + "runtime": 21.545, + "command": { + "program": "markduplicates", + "arguments": [ + "mv", + "JK2802_SE.mapped.bam", + "JK2802.bam", + "picard", + "-Xmx4096M", + "MarkDuplicates", + "INPUT=JK2802.bam", + "OUTPUT=JK2802_rmdup.bam", + "REMOVE_DUPLICATES=TRUE", + "AS=TRUE", + "METRICS_FILE=\"JK2802_rmdup.metrics\"", + "VALIDATION_STRINGENCY=SILENT", + "samtools", + "index", + "JK2802_rmdup.bam" + ] + }, + "parents": [ + "samtools_flagstat_jk2782", + "samtools_flagstat_jk2802" + ], + "children": [ + "preseq_jk2782", + "preseq_jk2802" + ], + "files": [], + "cores": 2.0, + "id": "ID000020", + "category": "markduplicates", + "avgCPU": 182.6, + "bytesRead": 24242, + "bytesWritten": 2466, + "memory": 1404624 + }, + { + "name": "preseq_jk2782", + "type": "compute", + "runtime": 12.299, + "command": { + "program": "preseq", + "arguments": [ + "preseq", + "c_curve", + "-s", + "1000", + "-o", + "JK2782_PE.mapped.ccurve", + "-B", + "JK2782_PE.mapped.bam" + ] + }, + "parents": [ + "markduplicates_jk2782", + "markduplicates_jk2802" + ], + "children": [ + "endorspy_jk2782", + "endorspy_jk2802" + ], + "files": [], + "cores": 1.0, + "id": "ID000030", + "category": "preseq", + "avgCPU": 81.9, + "bytesRead": 473, + "bytesWritten": 4, + "memory": 12032 + }, + { + "name": "preseq_jk2802", + "type": "compute", + "runtime": 10.188, + "command": { + "program": "preseq", + "arguments": [ + "preseq", + "c_curve", + "-s", + "1000", + "-o", + "JK2802_SE.mapped.ccurve", + "-B", + "JK2802_SE.mapped.bam" + ] + }, + "parents": [ + "markduplicates_jk2782", + "markduplicates_jk2802" + ], + "children": [ + "endorspy_jk2782", + "endorspy_jk2802" + ], + "files": [], + "cores": 1.0, + "id": "ID000027", + "category": "preseq", + "avgCPU": 77.6, + "bytesRead": 548, + "bytesWritten": 4, + "memory": 11972 + }, + { + "name": "endorspy_jk2782", + "type": "compute", + "runtime": 7.537, + "command": { + "program": "endorspy", + "arguments": [ + "endorS.py", + "-o", + "json", + "-n", + "JK2782", + "JK2782_flagstat.stats" + ] + }, + "parents": [ + "preseq_jk2782", + "preseq_jk2802" + ], + "children": [ + "damageprofiler_jk2802", + "damageprofiler_jk2782" + ], + "files": [], + "cores": 1.0, + "id": "ID000031", + "category": "endorspy", + "avgCPU": 44.7, + "bytesRead": 623, + "bytesWritten": 4, + "memory": 12264 + }, + { + "name": "endorspy_jk2802", + "type": "compute", + "runtime": 8.0, + "command": { + "program": "endorspy", + "arguments": [ + "endorS.py", + "-o", + "json", + "-n", + "JK2802", + "JK2802_flagstat.stats" + ] + }, + "parents": [ + "preseq_jk2782", + "preseq_jk2802" + ], + "children": [ + "damageprofiler_jk2802", + "damageprofiler_jk2782" + ], + "files": [], + "cores": 1.0, + "id": "ID000032", + "category": "endorspy", + "avgCPU": 54.0, + "bytesRead": 623, + "bytesWritten": 4, + "memory": 12224 + }, + { + "name": "damageprofiler_jk2802", + "type": "compute", + "runtime": 18.596, + "command": { + "program": "damageprofiler", + "arguments": [ + "damageprofiler", + "-Xmx4g", + "-i", + "JK2802_rmdup.bam", + "-r", + "Mammoth_MT_Krause.fasta", + "-l", + "100", + "-t", + "15", + "-o", + ".", + "-yaxis_damageplot", + "0.30" + ] + }, + "parents": [ + "endorspy_jk2782", + "endorspy_jk2802" + ], + "children": [ + "qualimap_jk2802", + "qualimap_jk2782" + ], + "files": [], + "cores": 1.0, + "id": "ID000033", + "category": "damageprofiler", + "avgCPU": 88.6, + "bytesRead": 25744, + "bytesWritten": 391, + "memory": 242940 + }, + { + "name": "damageprofiler_jk2782", + "type": "compute", + "runtime": 16.736, + "command": { + "program": "damageprofiler", + "arguments": [ + "damageprofiler", + "-Xmx4g", + "-i", + "JK2782_rmdup.bam", + "-r", + "Mammoth_MT_Krause.fasta", + "-l", + "100", + "-t", + "15", + "-o", + ".", + "-yaxis_damageplot", + "0.30" + ] + }, + "parents": [ + "endorspy_jk2782", + "endorspy_jk2802" + ], + "children": [ + "qualimap_jk2802", + "qualimap_jk2782" + ], + "files": [], + "cores": 1.0, + "id": "ID000036", + "category": "damageprofiler", + "avgCPU": 88.3, + "bytesRead": 25661, + "bytesWritten": 327, + "memory": 198276 + }, + { + "name": "qualimap_jk2802", + "type": "compute", + "runtime": 15.368, + "command": { + "program": "qualimap", + "arguments": [ + "qualimap", + "bamqc", + "-bam", + "JK2802_rmdup.bam", + "-nt", + "2", + "-outdir", + ".", + "-outformat", + "\"HTML\"", + "--java-mem-size=4G" + ] + }, + "parents": [ + "damageprofiler_jk2802", + "damageprofiler_jk2782" + ], + "children": [ + "multiqc_1" + ], + "files": [], + "cores": 2.0, + "id": "ID000053", + "category": "qualimap", + "avgCPU": 177.7, + "bytesRead": 35038, + "bytesWritten": 1712, + "memory": 209440 + }, + { + "name": "qualimap_jk2782", + "type": "compute", + "runtime": 14.223, + "command": { + "program": "qualimap", + "arguments": [ + "qualimap", + "bamqc", + "-bam", + "JK2782_rmdup.bam", + "-nt", + "2", + "-outdir", + ".", + "-outformat", + "\"HTML\"", + "--java-mem-size=4G" + ] + }, + "parents": [ + "damageprofiler_jk2802", + "damageprofiler_jk2782" + ], + "children": [ + "multiqc_1" + ], + "files": [], + "cores": 2.0, + "id": "ID000054", + "category": "qualimap", + "avgCPU": 181.9, + "bytesRead": 34954, + "bytesWritten": 1937, + "memory": 232196 + }, + { + "name": "multiqc_1", + "type": "compute", + "runtime": 46.376, + "command": { + "program": "multiqc", + "arguments": [ + "multiqc", + "-f", + "multiqc_config.yaml", + "." + ] + }, + "parents": [ + "qualimap_jk2802", + "qualimap_jk2782" + ], + "children": [], + "files": [], + "cores": 1.0, + "id": "ID000056", + "category": "multiqc", + "avgCPU": 93.0, + "bytesRead": 1215169, + "bytesWritten": 22599, + "memory": 139496 + } + ] + } +} diff --git a/opendc-trace/opendc-trace-wtf/build.gradle.kts b/opendc-trace/opendc-trace-wtf/build.gradle.kts new file mode 100644 index 00000000..e4f0ab3a --- /dev/null +++ b/opendc-trace/opendc-trace-wtf/build.gradle.kts @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +description = "Support for Workflow Trace Format (WTF) traces in OpenDC" + +/* Build configuration */ +plugins { + `kotlin-library-conventions` + `testing-conventions` + `jacoco-conventions` +} + +dependencies { + api(platform(projects.opendcPlatform)) + api(projects.opendcTrace.opendcTraceApi) + + implementation(projects.opendcTrace.opendcTraceParquet) + + testRuntimeOnly(libs.slf4j.simple) +} diff --git a/opendc-trace/opendc-trace-wtf/src/main/kotlin/org/opendc/trace/wtf/WtfTaskTableReader.kt b/opendc-trace/opendc-trace-wtf/src/main/kotlin/org/opendc/trace/wtf/WtfTaskTableReader.kt new file mode 100644 index 00000000..45ec25dd --- /dev/null +++ b/opendc-trace/opendc-trace-wtf/src/main/kotlin/org/opendc/trace/wtf/WtfTaskTableReader.kt @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.trace.wtf + +import org.apache.avro.Schema +import org.apache.avro.generic.GenericRecord +import org.opendc.trace.* +import org.opendc.trace.util.parquet.LocalParquetReader +import java.time.Duration +import java.time.Instant + +/** + * A [TableReader] implementation for the WTF format. + */ +internal class WtfTaskTableReader(private val reader: LocalParquetReader<GenericRecord>) : TableReader { + /** + * The current record. + */ + private var record: GenericRecord? = null + + /** + * A flag to indicate that the columns have been initialized. + */ + private var hasInitializedColumns = false + + override fun nextRow(): Boolean { + val record = reader.read() + this.record = record + + if (!hasInitializedColumns && record != null) { + initColumns(record.schema) + hasInitializedColumns = true + } + + return record != null + } + + override fun resolve(column: TableColumn<*>): Int = columns[column] ?: -1 + + override fun isNull(index: Int): Boolean { + check(index in 0..columns.size) { "Invalid column index" } + return get(index) == null + } + + override fun get(index: Int): Any? { + val record = checkNotNull(record) { "Reader in invalid state" } + @Suppress("UNCHECKED_CAST") + return when (index) { + COL_ID -> (record[AVRO_COL_ID] as Long).toString() + COL_WORKFLOW_ID -> (record[AVRO_COL_WORKFLOW_ID] as Long).toString() + COL_SUBMIT_TIME -> Instant.ofEpochMilli(record[AVRO_COL_SUBMIT_TIME] as Long) + COL_WAIT_TIME -> Duration.ofMillis(record[AVRO_COL_WAIT_TIME] as Long) + COL_RUNTIME -> Duration.ofMillis(record[AVRO_COL_RUNTIME] as Long) + COL_REQ_NCPUS, COL_GROUP_ID, COL_USER_ID -> getInt(index) + COL_PARENTS -> (record[AVRO_COL_PARENTS] as ArrayList<GenericRecord>).map { it["item"].toString() }.toSet() + COL_CHILDREN -> (record[AVRO_COL_CHILDREN] as ArrayList<GenericRecord>).map { it["item"].toString() }.toSet() + else -> throw IllegalArgumentException("Invalid column") + } + } + + override fun getBoolean(index: Int): Boolean { + throw IllegalArgumentException("Invalid column") + } + + override fun getInt(index: Int): Int { + val record = checkNotNull(record) { "Reader in invalid state" } + + return when (index) { + COL_REQ_NCPUS -> (record[AVRO_COL_REQ_NCPUS] as Double).toInt() + COL_GROUP_ID -> record[AVRO_COL_GROUP_ID] as Int + COL_USER_ID -> record[AVRO_COL_USER_ID] as Int + else -> throw IllegalArgumentException("Invalid column") + } + } + + override fun getLong(index: Int): Long { + throw IllegalArgumentException("Invalid column") + } + + override fun getDouble(index: Int): Double { + throw IllegalArgumentException("Invalid column") + } + + override fun close() { + reader.close() + } + + /** + * Initialize the columns for the reader based on [schema]. + */ + private fun initColumns(schema: Schema) { + try { + AVRO_COL_ID = schema.getField("id").pos() + AVRO_COL_WORKFLOW_ID = schema.getField("workflow_id").pos() + AVRO_COL_SUBMIT_TIME = schema.getField("ts_submit").pos() + AVRO_COL_WAIT_TIME = schema.getField("wait_time").pos() + AVRO_COL_RUNTIME = schema.getField("runtime").pos() + AVRO_COL_REQ_NCPUS = schema.getField("resource_amount_requested").pos() + AVRO_COL_PARENTS = schema.getField("parents").pos() + AVRO_COL_CHILDREN = schema.getField("children").pos() + AVRO_COL_GROUP_ID = schema.getField("group_id").pos() + AVRO_COL_USER_ID = schema.getField("user_id").pos() + } catch (e: NullPointerException) { + // This happens when the field we are trying to access does not exist + throw IllegalArgumentException("Invalid schema", e) + } + } + + private var AVRO_COL_ID = -1 + private var AVRO_COL_WORKFLOW_ID = -1 + private var AVRO_COL_SUBMIT_TIME = -1 + private var AVRO_COL_WAIT_TIME = -1 + private var AVRO_COL_RUNTIME = -1 + private var AVRO_COL_REQ_NCPUS = -1 + private var AVRO_COL_PARENTS = -1 + private var AVRO_COL_CHILDREN = -1 + private var AVRO_COL_GROUP_ID = -1 + private var AVRO_COL_USER_ID = -1 + + private val COL_ID = 0 + private val COL_WORKFLOW_ID = 1 + private val COL_SUBMIT_TIME = 2 + private val COL_WAIT_TIME = 3 + private val COL_RUNTIME = 4 + private val COL_REQ_NCPUS = 5 + private val COL_PARENTS = 6 + private val COL_CHILDREN = 7 + private val COL_GROUP_ID = 8 + private val COL_USER_ID = 9 + + private val columns = mapOf( + TASK_ID to COL_ID, + TASK_WORKFLOW_ID to COL_WORKFLOW_ID, + TASK_SUBMIT_TIME to COL_SUBMIT_TIME, + TASK_WAIT_TIME to COL_WAIT_TIME, + TASK_RUNTIME to COL_RUNTIME, + TASK_REQ_NCPUS to COL_REQ_NCPUS, + TASK_PARENTS to COL_PARENTS, + TASK_CHILDREN to COL_CHILDREN, + TASK_GROUP_ID to COL_GROUP_ID, + TASK_USER_ID to COL_USER_ID, + ) +} diff --git a/opendc-trace/opendc-trace-wtf/src/main/kotlin/org/opendc/trace/wtf/WtfTraceFormat.kt b/opendc-trace/opendc-trace-wtf/src/main/kotlin/org/opendc/trace/wtf/WtfTraceFormat.kt new file mode 100644 index 00000000..ef88d295 --- /dev/null +++ b/opendc-trace/opendc-trace-wtf/src/main/kotlin/org/opendc/trace/wtf/WtfTraceFormat.kt @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.trace.wtf + +import org.apache.avro.generic.GenericRecord +import org.opendc.trace.* +import org.opendc.trace.spi.TableDetails +import org.opendc.trace.spi.TraceFormat +import org.opendc.trace.util.parquet.LocalParquetReader +import java.nio.file.Path + +/** + * A [TraceFormat] implementation for the Workflow Trace Format (WTF). + */ +public class WtfTraceFormat : TraceFormat { + override val name: String = "wtf" + + override fun create(path: Path) { + throw UnsupportedOperationException("Writing not supported for this format") + } + + override fun getTables(path: Path): List<String> = listOf(TABLE_TASKS) + + override fun getDetails(path: Path, table: String): TableDetails { + return when (table) { + TABLE_TASKS -> TableDetails( + listOf( + TASK_ID, + TASK_WORKFLOW_ID, + TASK_SUBMIT_TIME, + TASK_WAIT_TIME, + TASK_RUNTIME, + TASK_REQ_NCPUS, + TASK_PARENTS, + TASK_CHILDREN, + TASK_GROUP_ID, + TASK_USER_ID + ), + listOf(TASK_SUBMIT_TIME) + ) + else -> throw IllegalArgumentException("Table $table not supported") + } + } + + override fun newReader(path: Path, table: String): TableReader { + return when (table) { + TABLE_TASKS -> { + val reader = LocalParquetReader<GenericRecord>(path.resolve("tasks/schema-1.0")) + WtfTaskTableReader(reader) + } + else -> throw IllegalArgumentException("Table $table not supported") + } + } + + override fun newWriter(path: Path, table: String): TableWriter { + throw UnsupportedOperationException("Writing not supported for this format") + } +} diff --git a/opendc-trace/opendc-trace-wtf/src/main/resources/META-INF/services/org.opendc.trace.spi.TraceFormat b/opendc-trace/opendc-trace-wtf/src/main/resources/META-INF/services/org.opendc.trace.spi.TraceFormat new file mode 100644 index 00000000..32da52ff --- /dev/null +++ b/opendc-trace/opendc-trace-wtf/src/main/resources/META-INF/services/org.opendc.trace.spi.TraceFormat @@ -0,0 +1 @@ +org.opendc.trace.wtf.WtfTraceFormat diff --git a/opendc-trace/opendc-trace-wtf/src/test/kotlin/org/opendc/trace/wtf/WtfTraceFormatTest.kt b/opendc-trace/opendc-trace-wtf/src/test/kotlin/org/opendc/trace/wtf/WtfTraceFormatTest.kt new file mode 100644 index 00000000..09c3703a --- /dev/null +++ b/opendc-trace/opendc-trace-wtf/src/test/kotlin/org/opendc/trace/wtf/WtfTraceFormatTest.kt @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.trace.wtf + +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.opendc.trace.* +import java.nio.file.Paths +import java.time.Duration +import java.time.Instant + +/** + * Test suite for the [WtfTraceFormat] class. + */ +class WtfTraceFormatTest { + private val format = WtfTraceFormat() + + @Test + fun testTables() { + val path = Paths.get("src/test/resources/wtf-trace") + assertEquals(listOf(TABLE_TASKS), format.getTables(path)) + } + + @Test + fun testTableExists() { + val path = Paths.get("src/test/resources/wtf-trace") + assertDoesNotThrow { format.getDetails(path, TABLE_TASKS) } + } + + @Test + fun testTableDoesNotExist() { + val path = Paths.get("src/test/resources/wtf-trace") + + assertThrows<IllegalArgumentException> { format.getDetails(path, "test") } + } + + /** + * Smoke test for parsing WTF traces. + */ + @Test + fun testTableReader() { + val path = Paths.get("src/test/resources/wtf-trace") + val reader = format.newReader(path, TABLE_TASKS) + + assertAll( + { assertTrue(reader.nextRow()) }, + { assertEquals("362334516345962206", reader.get(TASK_ID)) }, + { assertEquals("1078341553348591493", reader.get(TASK_WORKFLOW_ID)) }, + { assertEquals(Instant.ofEpochMilli(245604), reader.get(TASK_SUBMIT_TIME)) }, + { assertEquals(Duration.ofMillis(8163), reader.get(TASK_RUNTIME)) }, + { assertEquals(setOf("584055316413447529", "133113685133695608", "1008582348422865408"), reader.get(TASK_PARENTS)) }, + ) + + assertAll( + { assertTrue(reader.nextRow()) }, + { assertEquals("502010169100446658", reader.get(TASK_ID)) }, + { assertEquals("1078341553348591493", reader.get(TASK_WORKFLOW_ID)) }, + { assertEquals(Instant.ofEpochMilli(251325), reader.get(TASK_SUBMIT_TIME)) }, + { assertEquals(Duration.ofMillis(8216), reader.get(TASK_RUNTIME)) }, + { assertEquals(setOf("584055316413447529", "133113685133695608", "1008582348422865408"), reader.get(TASK_PARENTS)) }, + ) + + reader.close() + } +} diff --git a/opendc-format/src/test/resources/wtf-trace/tasks/schema-1.0/part.0.parquet b/opendc-trace/opendc-trace-wtf/src/test/resources/wtf-trace/tasks/schema-1.0/part.0.parquet Binary files differindex d2044038..d2044038 100644 --- a/opendc-format/src/test/resources/wtf-trace/tasks/schema-1.0/part.0.parquet +++ b/opendc-trace/opendc-trace-wtf/src/test/resources/wtf-trace/tasks/schema-1.0/part.0.parquet diff --git a/opendc-utils/src/main/kotlin/org/opendc/utils/TimerScheduler.kt b/opendc-utils/src/main/kotlin/org/opendc/utils/TimerScheduler.kt index aa2f3367..d7da7f99 100644 --- a/opendc-utils/src/main/kotlin/org/opendc/utils/TimerScheduler.kt +++ b/opendc-utils/src/main/kotlin/org/opendc/utils/TimerScheduler.kt @@ -24,7 +24,6 @@ package org.opendc.utils import kotlinx.coroutines.* import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.channels.sendBlocking import kotlinx.coroutines.selects.select import java.time.Clock import java.util.* @@ -145,9 +144,9 @@ public class TimerScheduler<T>(context: CoroutineContext, private val clock: Clo queue.poll() if (queue.isNotEmpty()) { - channel.sendBlocking(peek.timestamp) + channel.trySend(peek.timestamp) } else { - channel.sendBlocking(null) + channel.trySend(null) } } } @@ -212,7 +211,7 @@ public class TimerScheduler<T>(context: CoroutineContext, private val clock: Clo // Check if we need to push the interruption forward // Note that we check by timer reference if (queue.peek() === timer) { - channel.offer(timer.timestamp) + channel.trySend(timer.timestamp) } timer diff --git a/opendc-web/opendc-web-api/.coveragerc b/opendc-web/opendc-web-api/.coveragerc new file mode 100644 index 00000000..55d99c2e --- /dev/null +++ b/opendc-web/opendc-web-api/.coveragerc @@ -0,0 +1,5 @@ +[run] +source = . +omit = + tests/* + conftest.py diff --git a/opendc-web/opendc-web-api/.gitignore b/opendc-web/opendc-web-api/.gitignore index b0390689..9f8dfc5c 100644 --- a/opendc-web/opendc-web-api/.gitignore +++ b/opendc-web/opendc-web-api/.gitignore @@ -15,4 +15,5 @@ config.json test.json .env* .coverage -.junit-report.xml +coverage.xml +junit-report.xml diff --git a/opendc-web/opendc-web-api/.pylintrc b/opendc-web/opendc-web-api/.pylintrc index 7fe24187..4dbb0b50 100644 --- a/opendc-web/opendc-web-api/.pylintrc +++ b/opendc-web/opendc-web-api/.pylintrc @@ -65,7 +65,8 @@ disable=duplicate-code, invalid-name, bare-except, too-few-public-methods, - fixme + fixme, + no-self-use # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option diff --git a/opendc-web/opendc-web-api/Dockerfile b/opendc-web/opendc-web-api/Dockerfile index 49702c90..505a69de 100644 --- a/opendc-web/opendc-web-api/Dockerfile +++ b/opendc-web/opendc-web-api/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.8 +FROM python:3.9-slim MAINTAINER OpenDC Maintainers <opendc@atlarge-research.com> # Ensure the STDOUT is not buffered by Python so that our logs become visible @@ -9,9 +9,15 @@ ENV PYTHONUNBUFFERED 1 COPY ./ /opendc # Fetch web server dependencies -RUN pip install -r /opendc/requirements.txt +RUN pip install -r /opendc/requirements.txt && pip install pyuwsgi + +# Create opendc user +RUN groupadd --gid 1000 opendc \ + && useradd --uid 1000 --gid opendc --shell /bin/bash --create-home opendc +RUN chown -R opendc:opendc /opendc +USER opendc # Set working directory WORKDIR /opendc -CMD ["python3", "main.py"] +CMD uwsgi -M --socket 0.0.0.0:80 --protocol=http --wsgi-file app.py --enable-threads --processes 2 --lazy-app diff --git a/opendc-web/opendc-web-api/README.md b/opendc-web/opendc-web-api/README.md index 4932f823..d1c469c1 100644 --- a/opendc-web/opendc-web-api/README.md +++ b/opendc-web/opendc-web-api/README.md @@ -9,15 +9,19 @@ <br> -The OpenDC web server is the bridge between OpenDC's frontend and database. It is built with Flask/SocketIO in Python and implements the OpenAPI-compliant [OpenDC API specification](../../opendc-api-spec.yml). +The OpenDC web server is the bridge between OpenDC's frontend and database. It is built with Flask/SocketIO in Python +and implements the OpenAPI-compliant [OpenDC API specification](../../opendc-api-spec.yml). -This document explains a high-level view of the web server architecture ([jump](#architecture)), and describes how to set up the web server for local development ([jump](#setup-for-local-development)). +This document explains a high-level view of the web server architecture ([jump](#architecture)), and describes how to +set up the web server for local development ([jump](#setup-for-local-development)). ## Architecture -The following diagram shows a high-level view of the architecture of the OpenDC web server. Squared-off colored boxes indicate packages (colors become more saturated as packages are nested); rounded-off boxes indicate individual components; dotted lines indicate control flow; and solid lines indicate data flow. +The following diagram shows a high-level view of the architecture of the OpenDC web server. Squared-off colored boxes +indicate packages (colors become more saturated as packages are nested); rounded-off boxes indicate individual +components; dotted lines indicate control flow; and solid lines indicate data flow. - + The OpenDC API is implemented by the `Main Server Loop`, which is the only component in the base package. @@ -25,74 +29,91 @@ The OpenDC API is implemented by the `Main Server Loop`, which is the only compo The `Util` package handles several miscellaneous tasks: -* `Database API`: Wraps database access functionality used by `Models` to read themselves from/write themselves into the database. +* `Database API`: Wraps database access functionality used by `Models` to read themselves from/write themselves into the + database. * `Exceptions`: Holds definitions for exceptions used throughout the web server. * `Parameter Checker`: Recursively checks whether required `Request` parameters are present and correctly typed. -* `REST`: Parses SocketIO and HTTP messages into `Request` objects, and calls the appropriate `API` endpoint to get a `Response` object to return to the `Main Server Loop`. +* `REST`: Parses HTTP messages into `Request` objects, and calls the appropriate `API` endpoint to get a `Response` + object to return to the `Main Server Loop`. ### API Package -The `API` package contains the logic for the HTTP methods in each API endpoint. Packages are structured to mirror the API: the code for the endpoint `GET api/projects`, for example, would be located at the `endpoint.py` inside the `projects` package (so at `api/projects/endpoint.py`). +The `API` package contains the logic for the HTTP methods in each API endpoint. Packages are structured to mirror the +API: the code for the endpoint `GET api/projects`, for example, would be located at the `endpoint.py` inside +the `projects` package (so at `api/projects/endpoint.py`). -An `endpoint.py` file contains methods for each HTTP method it supports, which takes a request as input (such as `def GET(request):`). Typically, such a method checks whether the parameters were passed correctly (using the `Parameter Checker`); fetches some model from the database; checks whether the data exists and is accessible by the user who made the request; possibly modifies this data and writes it back to the database; and returns a JSON representation of the model. +An `endpoint.py` file contains methods for each HTTP method it supports, which takes a request as input (such +as `def GET(request):`). Typically, such a method checks whether the parameters were passed correctly (using +the `Parameter Checker`); fetches some model from the database; checks whether the data exists and is accessible by the +user who made the request; possibly modifies this data and writes it back to the database; and returns a JSON +representation of the model. -The `REST` component dynamically imports the appropriate method from the appropriate `endpoint`, according to request it receives, and executes it. +The `REST` component dynamically imports the appropriate method from the appropriate `endpoint`, according to request it +receives, and executes it. ### Models Package -The `models` package contains the logic for mapping Python objects to their database representations. This involves an abstract `model` which has generic CRUD operations. Extensions of `model`, such as a `User` or `Project`, specify some more specific operations and their collection metadata. +The `models` package contains the logic for mapping Python objects to their database representations. This involves an +abstract `model` which has generic CRUD operations. Extensions of `model`, such as a `User` or `Project`, specify some +more specific operations and their collection metadata. `Endpoint`s import these `models` and use them to execute requests. ## Setup for Local Development -The following steps will guide you through setting up the OpenDC web server locally for development. To test individual endpoints, edit `static/index.html`. +The following steps will guide you through setting up the OpenDC web server locally for development. ### Local Setup #### Install requirements -Make sure you have Python 3.7+ installed (if not, get it [here](https://www.python.org/)), as well as pip (if not, get it [here](https://pip.pypa.io/en/stable/installing/)). Then run the following to install the requirements. +Make sure you have Python 3.7+ installed (if not, get it [here](https://www.python.org/)), as well as pip (if not, get +it [here](https://pip.pypa.io/en/stable/installing/)). Then run the following to install the requirements. ```bash pip install -r requirements.txt ``` -The web server also requires a running MongoDB instance. We recommend setting this up through docker, by running `docker-compose build` and `docker-compose up` in the [`mongodb` directory](../database) of the main OpenDC repository. +The web server also requires a running MongoDB instance. We recommend setting this up through docker, by +running `docker-compose build` and `docker-compose up` in the [`mongodb` directory](../../database) of the main OpenDC +repository. #### Get and configure the code -Clone OpenDC and follow the [instructions in the main repository](../) to set up a Google OAuth ID and environment variables. +Clone OpenDC and follow the [instructions from the deployment guide](../../docs/deploy.md) to set up an [Auth0](https://auth0.com) +application and environment variables. **Important:** Be sure to set up environment variables according to those instructions, in a `.env` file. -If you want to test REST calls manually, add your own `OAUTH_CLIENT_ID` in `content=` on line `2` in `api/static/index.html`. - #### Set up the database -You can selectively run only the database services from the standard OpenDC `docker-compose` setup (in the root directory): +You can selectively run only the database services from the standard OpenDC `docker-compose` setup (in the root +directory): ```bash docker-compose build mongo mongo-express docker-compose up mongo mongo-express ``` -This will set you up with a running MongoDB instance and a visual inspection tool running on [localhost:8082](http://localhost:8082), with which you can view and manipulate the database. Add the simulator images to the command lists above if you want to test simulation capabilities, as well. +This will set you up with a running MongoDB instance and a visual inspection tool running +on [localhost:8082](http://localhost:8082), with which you can view and manipulate the database. Add the simulator +images to the command lists above if you want to test simulation capabilities, as well. ### Local Development Run the server. ```bash -cd api -python main.py +python3 -m flask run --port 8081 ``` -When editing the web server code, restart the server (`CTRL` + `c` followed by `python main.py` in the console running the server) to see the result of your changes. +When editing the web server code, restart the server (`CTRL` + `c` followed by `python app.py` in the console running +the server) to see the result of your changes. #### Code Style -To format all files, run `format.sh` in this directory. The script uses `yapf` internally to format everything automatically. +To format all files, run `format.sh` in this directory. The script uses `yapf` internally to format everything +automatically. To check if code style is up to modern standards, run `check.sh` in this directory. The script uses `pylint` internally. diff --git a/opendc-web/opendc-web-api/app.py b/opendc-web/opendc-web-api/app.py new file mode 100755 index 00000000..36c80b7a --- /dev/null +++ b/opendc-web/opendc-web-api/app.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python3 +import mimetypes +import os + +from dotenv import load_dotenv +from flask import Flask, jsonify, redirect +from flask_compress import Compress +from flask_cors import CORS +from flask_restful import Api +from flask_swagger_ui import get_swaggerui_blueprint +from marshmallow import ValidationError + +from opendc.api.jobs import JobList, Job +from opendc.api.portfolios import Portfolio, PortfolioScenarios +from opendc.api.prefabs import Prefab, PrefabList +from opendc.api.projects import ProjectList, Project, ProjectTopologies, ProjectPortfolios +from opendc.api.scenarios import Scenario +from opendc.api.schedulers import SchedulerList +from opendc.api.topologies import Topology +from opendc.api.traces import TraceList, Trace +from opendc.auth import AuthError +from opendc.util import JSONEncoder + + +# Load environmental variables from dotenv file +load_dotenv() + + +def setup_sentry(): + """ + Setup the Sentry integration for Flask if a DSN is supplied via the environmental variables. + """ + if 'SENTRY_DSN' not in os.environ: + return + + import sentry_sdk + from sentry_sdk.integrations.flask import FlaskIntegration + + sentry_sdk.init( + integrations=[FlaskIntegration()], + traces_sample_rate=0.1 + ) + + +def setup_api(app): + """ + Setup the API interface. + """ + api = Api(app) + # Map to ('string', 'ObjectId') passing type and format + api.add_resource(ProjectList, '/projects/') + api.add_resource(Project, '/projects/<string:project_id>') + api.add_resource(ProjectTopologies, '/projects/<string:project_id>/topologies') + api.add_resource(ProjectPortfolios, '/projects/<string:project_id>/portfolios') + api.add_resource(Topology, '/topologies/<string:topology_id>') + api.add_resource(PrefabList, '/prefabs/') + api.add_resource(Prefab, '/prefabs/<string:prefab_id>') + api.add_resource(Portfolio, '/portfolios/<string:portfolio_id>') + api.add_resource(PortfolioScenarios, '/portfolios/<string:portfolio_id>/scenarios') + api.add_resource(Scenario, '/scenarios/<string:scenario_id>') + api.add_resource(TraceList, '/traces/') + api.add_resource(Trace, '/traces/<string:trace_id>') + api.add_resource(SchedulerList, '/schedulers/') + api.add_resource(JobList, '/jobs/') + api.add_resource(Job, '/jobs/<string:job_id>') + + @app.errorhandler(AuthError) + def handle_auth_error(ex): + response = jsonify(ex.error) + response.status_code = ex.status_code + return response + + @app.errorhandler(ValidationError) + def handle_validation_error(ex): + return {'message': 'Input validation failed', 'errors': ex.messages}, 400 + + return api + + +def setup_swagger(app): + """ + Setup Swagger UI + """ + SWAGGER_URL = '/docs' + API_URL = '../schema.yml' + + swaggerui_blueprint = get_swaggerui_blueprint( + SWAGGER_URL, + API_URL, + config={ + 'app_name': "OpenDC API v2" + }, + oauth_config={ + 'clientId': os.environ.get("AUTH0_DOCS_CLIENT_ID", ""), + 'additionalQueryStringParams': {'audience': os.environ.get("AUTH0_AUDIENCE", "https://api.opendc.org/v2/")}, + } + ) + app.register_blueprint(swaggerui_blueprint) + + +def create_app(testing=False): + app = Flask(__name__, static_url_path='/') + app.config['TESTING'] = testing + app.config['SECRET_KEY'] = os.environ['OPENDC_FLASK_SECRET'] + app.config['RESTFUL_JSON'] = {'cls': JSONEncoder} + app.json_encoder = JSONEncoder + + # Define YAML content type + mimetypes.add_type('text/yaml', '.yml') + + # Setup Sentry if DSN is specified + setup_sentry() + + # Set up CORS support + CORS(app) + + # Setup compression + compress = Compress() + compress.init_app(app) + + setup_api(app) + setup_swagger(app) + + @app.route('/') + def index(): + """ + Redirect the user to the API documentation if it accesses the API root. + """ + return redirect('docs/') + + return app + + +application = create_app(testing="OPENDC_FLASK_TESTING" in os.environ) + +if __name__ == '__main__': + application.run() diff --git a/opendc-web/opendc-web-api/conftest.py b/opendc-web/opendc-web-api/conftest.py index 1f4831b8..958a5894 100644 --- a/opendc-web/opendc-web-api/conftest.py +++ b/opendc-web/opendc-web-api/conftest.py @@ -1,15 +1,45 @@ """ Configuration file for all unit tests. """ + +from functools import wraps import pytest +from flask import _request_ctx_stack, g +from opendc.database import Database + + +def requires_auth_mock(f): + @wraps(f) + def decorated_function(*args, **kwargs): + _request_ctx_stack.top.current_user = {'sub': 'test'} + return f(*args, **kwargs) + return decorated_function + -from main import FLASK_CORE_APP +def requires_scope_mock(required_scope): + def decorator(f): + @wraps(f) + def decorated_function(*args, **kwargs): + return f(*args, **kwargs) + return decorated_function + return decorator @pytest.fixture def client(): """Returns a Flask API client to interact with.""" - FLASK_CORE_APP.config['TESTING'] = True - with FLASK_CORE_APP.test_client() as client: - yield client + # Disable authorization for test API endpoints + from opendc import exts + exts.requires_auth = requires_auth_mock + exts.requires_scope = requires_scope_mock + exts.has_scope = lambda x: False + + from app import create_app + + app = create_app(testing=True) + + with app.app_context(): + g.db = Database() + with app.test_client() as client: + yield client diff --git a/opendc-web/opendc-web-api/misc/artwork/opendc-web-server-component-diagram.png b/opendc-web/opendc-web-api/docs/component-diagram.png Binary files differindex 91b26006..91b26006 100644 --- a/opendc-web/opendc-web-api/misc/artwork/opendc-web-server-component-diagram.png +++ b/opendc-web/opendc-web-api/docs/component-diagram.png diff --git a/opendc-web/opendc-web-api/main.py b/opendc-web/opendc-web-api/main.py deleted file mode 100755 index 5c6dac31..00000000 --- a/opendc-web/opendc-web-api/main.py +++ /dev/null @@ -1,188 +0,0 @@ -#!/usr/bin/env python3 -import json -import os -import sys -import traceback -import urllib.request - -import flask_socketio -from dotenv import load_dotenv -from flask import Flask, request, jsonify -from flask_compress import Compress -from flask_cors import CORS -from oauth2client import client, crypt - -from opendc.models.user import User -from opendc.util import rest, path_parser, database -from opendc.util.exceptions import AuthorizationTokenError, RequestInitializationError -from opendc.util.json import JSONEncoder - -load_dotenv() - -TEST_MODE = "OPENDC_FLASK_TESTING" in os.environ - -# Setup Sentry if DSN is specified -if 'SENTRY_DSN' in os.environ: - import sentry_sdk - from sentry_sdk.integrations.flask import FlaskIntegration - - sentry_sdk.init( - integrations=[FlaskIntegration()], - traces_sample_rate=0.1 - ) - -# Set up database if not testing -if not TEST_MODE: - database.DB.initialize_database( - user=os.environ['OPENDC_DB_USERNAME'], - password=os.environ['OPENDC_DB_PASSWORD'], - database=os.environ['OPENDC_DB'], - host=os.environ.get('OPENDC_DB_HOST', 'localhost')) - -# Set up the core app -FLASK_CORE_APP = Flask(__name__) -FLASK_CORE_APP.testing = TEST_MODE -FLASK_CORE_APP.config['SECRET_KEY'] = os.environ['OPENDC_FLASK_SECRET'] -FLASK_CORE_APP.json_encoder = JSONEncoder - -# Set up CORS support -CORS(FLASK_CORE_APP) - -compress = Compress() -compress.init_app(FLASK_CORE_APP) - -SOCKET_IO_CORE = flask_socketio.SocketIO(FLASK_CORE_APP, cors_allowed_origins="*") - -API_VERSIONS = {'v2'} - - -@FLASK_CORE_APP.route('/tokensignin', methods=['POST']) -def sign_in(): - """Authenticate a user with Google sign in""" - - try: - token = request.form['idtoken'] - except KeyError: - return 'No idtoken provided', 401 - - try: - idinfo = client.verify_id_token(token, os.environ['OPENDC_OAUTH_CLIENT_ID']) - - if idinfo['aud'] != os.environ['OPENDC_OAUTH_CLIENT_ID']: - raise crypt.AppIdentityError('Unrecognized client.') - - if idinfo['iss'] not in ['accounts.google.com', 'https://accounts.google.com']: - raise crypt.AppIdentityError('Wrong issuer.') - except ValueError: - url = "https://www.googleapis.com/oauth2/v3/tokeninfo?id_token={}".format(token) - req = urllib.request.Request(url) - response = urllib.request.urlopen(url=req, timeout=30) - res = response.read() - idinfo = json.loads(res) - except crypt.AppIdentityError as e: - return 'Did not successfully authenticate' - - user = User.from_google_id(idinfo['sub']) - - data = {'isNewUser': user.obj is None} - - if user.obj is not None: - data['userId'] = user.get_id() - - return jsonify(**data) - - -@FLASK_CORE_APP.route('/<string:version>/<path:endpoint_path>', methods=['GET', 'POST', 'PUT', 'DELETE']) -def api_call(version, endpoint_path): - """Call an API endpoint directly over HTTP.""" - - # Check whether given version is valid - if version not in API_VERSIONS: - return jsonify(error='API version not found'), 404 - - # Get path and parameters - (path, path_parameters) = path_parser.parse(version, endpoint_path) - - query_parameters = request.args.to_dict() - for param in query_parameters: - try: - query_parameters[param] = int(query_parameters[param]) - except: - pass - - try: - body_parameters = json.loads(request.get_data()) - except: - body_parameters = {} - - # Create and call request - (req, response) = _process_message({ - 'id': 0, - 'method': request.method, - 'parameters': { - 'body': body_parameters, - 'path': path_parameters, - 'query': query_parameters - }, - 'path': path, - 'token': request.headers.get('auth-token') - }) - - print( - f'HTTP:\t{req.method} to `/{req.path}` resulted in {response.status["code"]}: {response.status["description"]}') - sys.stdout.flush() - - flask_response = jsonify(json.loads(response.to_JSON())) - flask_response.status_code = response.status['code'] - return flask_response - - -@SOCKET_IO_CORE.on('request') -def receive_message(message): - """"Receive a SocketIO request""" - (req, res) = _process_message(message) - - print(f'Socket: {req.method} to `/{req.path}` resulted in {res.status["code"]}: {res.status["description"]}') - sys.stdout.flush() - - flask_socketio.emit('response', res.to_JSON(), json=True) - - -def _process_message(message): - """Process a request message and return the response.""" - - try: - req = rest.Request(message) - res = req.process() - - return req, res - - except AuthorizationTokenError: - res = rest.Response(401, 'Authorization error') - res.id = message['id'] - - except RequestInitializationError as e: - res = rest.Response(400, str(e)) - res.id = message['id'] - - if not 'method' in message: - message['method'] = 'UNSPECIFIED' - if not 'path' in message: - message['path'] = 'UNSPECIFIED' - - except Exception: - res = rest.Response(500, 'Internal server error') - if 'id' in message: - res.id = message['id'] - traceback.print_exc() - - req = rest.Request() - req.method = message['method'] - req.path = message['path'] - - return req, res - - -if __name__ == '__main__': - print("Web server started on 8081") - SOCKET_IO_CORE.run(FLASK_CORE_APP, host='0.0.0.0', port=8081, use_reloader=False) diff --git a/opendc-web/opendc-web-api/opendc/api/jobs.py b/opendc-web/opendc-web-api/opendc/api/jobs.py new file mode 100644 index 00000000..6fb0522b --- /dev/null +++ b/opendc-web/opendc-web-api/opendc/api/jobs.py @@ -0,0 +1,105 @@ +# Copyright (c) 2021 AtLarge Research +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +from flask import request +from flask_restful import Resource +from marshmallow import fields, Schema, validate +from werkzeug.exceptions import BadRequest, Conflict + +from opendc.exts import requires_auth, requires_scope +from opendc.models.scenario import Scenario + + +def convert_to_job(scenario): + """Convert a scenario to a job. + """ + return JobSchema().dump({ + '_id': scenario['_id'], + 'scenarioId': scenario['_id'], + 'state': scenario['simulation']['state'], + 'heartbeat': scenario['simulation'].get('heartbeat', None), + 'results': scenario.get('results', {}) + }) + + +class JobSchema(Schema): + """ + Schema representing a simulation job. + """ + _id = fields.String(dump_only=True) + scenarioId = fields.String(dump_only=True) + state = fields.String(required=True, + validate=validate.OneOf(["QUEUED", "CLAIMED", "RUNNING", "FINISHED", "FAILED"])) + heartbeat = fields.DateTime() + results = fields.Dict() + + +class JobList(Resource): + """ + Resource representing the list of available jobs. + """ + method_decorators = [requires_auth, requires_scope('runner')] + + def get(self): + """Get all available jobs.""" + jobs = Scenario.get_jobs() + data = list(map(convert_to_job, jobs.obj)) + return {'data': data} + + +class Job(Resource): + """ + Resource representing a single job. + """ + method_decorators = [requires_auth, requires_scope('runner')] + + def get(self, job_id): + """Get the details of a single job.""" + job = Scenario.from_id(job_id) + job.check_exists() + data = convert_to_job(job.obj) + return {'data': data} + + def post(self, job_id): + """Update the details of a single job.""" + action = JobSchema(only=('state', 'results')).load(request.json) + + job = Scenario.from_id(job_id) + job.check_exists() + + old_state = job.obj['simulation']['state'] + new_state = action['state'] + + if old_state == new_state: + data = job.update_state(new_state) + elif (old_state, new_state) == ('QUEUED', 'CLAIMED'): + data = job.update_state('CLAIMED') + elif (old_state, new_state) == ('CLAIMED', 'RUNNING'): + data = job.update_state('RUNNING') + elif (old_state, new_state) == ('RUNNING', 'FINISHED'): + data = job.update_state('FINISHED', results=action.get('results', None)) + elif old_state in ('CLAIMED', 'RUNNING') and new_state == 'FAILED': + data = job.update_state('FAILED') + else: + raise BadRequest('Invalid state transition') + + if not data: + raise Conflict('State conflict') + + return {'data': convert_to_job(data)} diff --git a/opendc-web/opendc-web-api/opendc/api/portfolios.py b/opendc-web/opendc-web-api/opendc/api/portfolios.py new file mode 100644 index 00000000..4d8f54fd --- /dev/null +++ b/opendc-web/opendc-web-api/opendc/api/portfolios.py @@ -0,0 +1,153 @@ +# Copyright (c) 2021 AtLarge Research +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from flask import request +from flask_restful import Resource +from marshmallow import Schema, fields + +from opendc.exts import requires_auth, current_user, has_scope +from opendc.models.portfolio import Portfolio as PortfolioModel, PortfolioSchema +from opendc.models.project import Project +from opendc.models.scenario import ScenarioSchema, Scenario +from opendc.models.topology import Topology + + +class Portfolio(Resource): + """ + Resource representing a portfolio. + """ + method_decorators = [requires_auth] + + def get(self, portfolio_id): + """ + Get a portfolio by identifier. + """ + portfolio = PortfolioModel.from_id(portfolio_id) + + portfolio.check_exists() + + # Users with scope runner can access all portfolios + if not has_scope('runner'): + portfolio.check_user_access(current_user['sub'], False) + + data = PortfolioSchema().dump(portfolio.obj) + return {'data': data} + + def put(self, portfolio_id): + """ + Replace the portfolio. + """ + schema = Portfolio.PutSchema() + result = schema.load(request.json) + + portfolio = PortfolioModel.from_id(portfolio_id) + portfolio.check_exists() + portfolio.check_user_access(current_user['sub'], True) + + portfolio.set_property('name', result['portfolio']['name']) + portfolio.set_property('targets.enabledMetrics', result['portfolio']['targets']['enabledMetrics']) + portfolio.set_property('targets.repeatsPerScenario', result['portfolio']['targets']['repeatsPerScenario']) + + portfolio.update() + data = PortfolioSchema().dump(portfolio.obj) + return {'data': data} + + def delete(self, portfolio_id): + """ + Delete a portfolio. + """ + portfolio = PortfolioModel.from_id(portfolio_id) + + portfolio.check_exists() + portfolio.check_user_access(current_user['sub'], True) + + portfolio_id = portfolio.get_id() + + project = Project.from_id(portfolio.obj['projectId']) + project.check_exists() + if portfolio_id in project.obj['portfolioIds']: + project.obj['portfolioIds'].remove(portfolio_id) + project.update() + + old_object = portfolio.delete() + data = PortfolioSchema().dump(old_object) + return {'data': data} + + class PutSchema(Schema): + """ + Schema for the PUT operation on a portfolio. + """ + portfolio = fields.Nested(PortfolioSchema, required=True) + + +class PortfolioScenarios(Resource): + """ + Resource representing the scenarios of a portfolio. + """ + method_decorators = [requires_auth] + + def get(self, portfolio_id): + """ + Get all scenarios belonging to a portfolio. + """ + portfolio = PortfolioModel.from_id(portfolio_id) + + portfolio.check_exists() + portfolio.check_user_access(current_user['sub'], True) + + scenarios = Scenario.get_for_portfolio(portfolio_id) + + data = ScenarioSchema().dump(scenarios, many=True) + return {'data': data} + + def post(self, portfolio_id): + """ + Add a new scenario to this portfolio + """ + schema = PortfolioScenarios.PostSchema() + result = schema.load(request.json) + + portfolio = PortfolioModel.from_id(portfolio_id) + + portfolio.check_exists() + portfolio.check_user_access(current_user['sub'], True) + + scenario = Scenario(result['scenario']) + + topology = Topology.from_id(scenario.obj['topology']['topologyId']) + topology.check_exists() + topology.check_user_access(current_user['sub'], True) + + scenario.set_property('portfolioId', portfolio.get_id()) + scenario.set_property('simulation', {'state': 'QUEUED'}) + scenario.set_property('topology.topologyId', topology.get_id()) + + scenario.insert() + + portfolio.obj['scenarioIds'].append(scenario.get_id()) + portfolio.update() + data = ScenarioSchema().dump(scenario.obj) + return {'data': data} + + class PostSchema(Schema): + """ + Schema for the POST operation on a portfolio's scenarios. + """ + scenario = fields.Nested(ScenarioSchema, required=True) diff --git a/opendc-web/opendc-web-api/opendc/api/prefabs.py b/opendc-web/opendc-web-api/opendc/api/prefabs.py new file mode 100644 index 00000000..730546ba --- /dev/null +++ b/opendc-web/opendc-web-api/opendc/api/prefabs.py @@ -0,0 +1,123 @@ +# Copyright (c) 2021 AtLarge Research +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from datetime import datetime +from flask import request +from flask_restful import Resource +from marshmallow import Schema, fields + +from opendc.models.prefab import Prefab as PrefabModel, PrefabSchema +from opendc.exts import current_user, requires_auth, db + + +class PrefabList(Resource): + """ + Resource for the list of prefabs available to the user. + """ + method_decorators = [requires_auth] + + def get(self): + """ + Get the available prefabs for a user. + """ + user_id = current_user['sub'] + + own_prefabs = db.fetch_all({'authorId': user_id}, PrefabModel.collection_name) + public_prefabs = db.fetch_all({'visibility': 'public'}, PrefabModel.collection_name) + + authorizations = {"authorizations": []} + authorizations["authorizations"].append(own_prefabs) + authorizations["authorizations"].append(public_prefabs) + return {'data': authorizations} + + def post(self): + """ + Create a new prefab. + """ + schema = PrefabList.PostSchema() + result = schema.load(request.json) + + prefab = PrefabModel(result['prefab']) + prefab.set_property('datetimeCreated', datetime.now()) + prefab.set_property('datetimeLastEdited', datetime.now()) + + user_id = current_user['sub'] + prefab.set_property('authorId', user_id) + + prefab.insert() + data = PrefabSchema().dump(prefab.obj) + return {'data': data} + + class PostSchema(Schema): + """ + Schema for the POST operation on the prefab list. + """ + prefab = fields.Nested(PrefabSchema, required=True) + + +class Prefab(Resource): + """ + Resource representing a single prefab. + """ + method_decorators = [requires_auth] + + def get(self, prefab_id): + """Get this Prefab.""" + prefab = PrefabModel.from_id(prefab_id) + prefab.check_exists() + prefab.check_user_access(current_user['sub']) + data = PrefabSchema().dump(prefab.obj) + return {'data': data} + + def put(self, prefab_id): + """Update a prefab's name and/or contents.""" + + schema = Prefab.PutSchema() + result = schema.load(request.json) + + prefab = PrefabModel.from_id(prefab_id) + prefab.check_exists() + prefab.check_user_access(current_user['sub']) + + prefab.set_property('name', result['prefab']['name']) + prefab.set_property('rack', result['prefab']['rack']) + prefab.set_property('datetimeLastEdited', datetime.now()) + prefab.update() + + data = PrefabSchema().dump(prefab.obj) + return {'data': data} + + def delete(self, prefab_id): + """Delete this Prefab.""" + prefab = PrefabModel.from_id(prefab_id) + + prefab.check_exists() + prefab.check_user_access(current_user['sub']) + + old_object = prefab.delete() + + data = PrefabSchema().dump(old_object) + return {'data': data} + + class PutSchema(Schema): + """ + Schema for the PUT operation on a prefab. + """ + prefab = fields.Nested(PrefabSchema, required=True) diff --git a/opendc-web/opendc-web-api/opendc/api/projects.py b/opendc-web/opendc-web-api/opendc/api/projects.py new file mode 100644 index 00000000..2b47c12e --- /dev/null +++ b/opendc-web/opendc-web-api/opendc/api/projects.py @@ -0,0 +1,224 @@ +# Copyright (c) 2021 AtLarge Research +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from datetime import datetime +from flask import request +from flask_restful import Resource +from marshmallow import Schema, fields + +from opendc.models.portfolio import Portfolio, PortfolioSchema +from opendc.models.topology import Topology, TopologySchema +from opendc.models.project import Project as ProjectModel, ProjectSchema +from opendc.exts import current_user, requires_auth + + +class ProjectList(Resource): + """ + Resource representing the list of projects available to a user. + """ + method_decorators = [requires_auth] + + def get(self): + """Get the authorized projects of the user""" + user_id = current_user['sub'] + projects = ProjectModel.get_for_user(user_id) + data = ProjectSchema().dump(projects, many=True) + return {'data': data} + + def post(self): + """Create a new project, and return that new project.""" + user_id = current_user['sub'] + + schema = Project.PutSchema() + result = schema.load(request.json) + + topology = Topology({'name': 'Default topology', 'rooms': []}) + topology.insert() + + project = ProjectModel(result['project']) + project.set_property('datetimeCreated', datetime.now()) + project.set_property('datetimeLastEdited', datetime.now()) + project.set_property('topologyIds', [topology.get_id()]) + project.set_property('portfolioIds', []) + project.set_property('authorizations', [{'userId': user_id, 'level': 'OWN'}]) + project.insert() + + topology.set_property('projectId', project.get_id()) + topology.update() + + data = ProjectSchema().dump(project.obj) + return {'data': data} + + +class Project(Resource): + """ + Resource representing a single project. + """ + method_decorators = [requires_auth] + + def get(self, project_id): + """Get this Project.""" + project = ProjectModel.from_id(project_id) + + project.check_exists() + project.check_user_access(current_user['sub'], False) + + data = ProjectSchema().dump(project.obj) + return {'data': data} + + def put(self, project_id): + """Update a project's name.""" + schema = Project.PutSchema() + result = schema.load(request.json) + + project = ProjectModel.from_id(project_id) + + project.check_exists() + project.check_user_access(current_user['sub'], True) + + project.set_property('name', result['project']['name']) + project.set_property('datetimeLastEdited', datetime.now()) + project.update() + + data = ProjectSchema().dump(project.obj) + return {'data': data} + + def delete(self, project_id): + """Delete this Project.""" + project = ProjectModel.from_id(project_id) + + project.check_exists() + project.check_user_access(current_user['sub'], True) + + for topology_id in project.obj['topologyIds']: + topology = Topology.from_id(topology_id) + topology.delete() + + for portfolio_id in project.obj['portfolioIds']: + portfolio = Portfolio.from_id(portfolio_id) + portfolio.delete() + + old_object = project.delete() + data = ProjectSchema().dump(old_object) + return {'data': data} + + class PutSchema(Schema): + """ + Schema for the PUT operation on a project. + """ + project = fields.Nested(ProjectSchema, required=True) + + +class ProjectTopologies(Resource): + """ + Resource representing the topologies of a project. + """ + method_decorators = [requires_auth] + + def get(self, project_id): + """Get all topologies belonging to the project.""" + project = ProjectModel.from_id(project_id) + + project.check_exists() + project.check_user_access(current_user['sub'], True) + + topologies = Topology.get_for_project(project_id) + data = TopologySchema().dump(topologies, many=True) + + return {'data': data} + + def post(self, project_id): + """Add a new Topology to the specified project and return it""" + schema = ProjectTopologies.PutSchema() + result = schema.load(request.json) + + project = ProjectModel.from_id(project_id) + + project.check_exists() + project.check_user_access(current_user['sub'], True) + + topology = Topology({ + 'projectId': project.get_id(), + 'name': result['topology']['name'], + 'rooms': result['topology']['rooms'], + }) + + topology.insert() + + project.obj['topologyIds'].append(topology.get_id()) + project.set_property('datetimeLastEdited', datetime.now()) + project.update() + + data = TopologySchema().dump(topology.obj) + return {'data': data} + + class PutSchema(Schema): + """ + Schema for the PUT operation on a project topology. + """ + topology = fields.Nested(TopologySchema, required=True) + + +class ProjectPortfolios(Resource): + """ + Resource representing the portfolios of a project. + """ + method_decorators = [requires_auth] + + def get(self, project_id): + """Get all portfolios belonging to the project.""" + project = ProjectModel.from_id(project_id) + + project.check_exists() + project.check_user_access(current_user['sub'], True) + + portfolios = Portfolio.get_for_project(project_id) + data = PortfolioSchema().dump(portfolios, many=True) + + return {'data': data} + + def post(self, project_id): + """Add a new Portfolio for this Project.""" + schema = ProjectPortfolios.PutSchema() + result = schema.load(request.json) + + project = ProjectModel.from_id(project_id) + + project.check_exists() + project.check_user_access(current_user['sub'], True) + + portfolio = Portfolio(result['portfolio']) + + portfolio.set_property('projectId', project.get_id()) + portfolio.set_property('scenarioIds', []) + + portfolio.insert() + + project.obj['portfolioIds'].append(portfolio.get_id()) + project.update() + + data = PortfolioSchema().dump(portfolio.obj) + return {'data': data} + + class PutSchema(Schema): + """ + Schema for the PUT operation on a project portfolio. + """ + portfolio = fields.Nested(PortfolioSchema, required=True) diff --git a/opendc-web/opendc-web-api/opendc/api/scenarios.py b/opendc-web/opendc-web-api/opendc/api/scenarios.py new file mode 100644 index 00000000..eacb0b49 --- /dev/null +++ b/opendc-web/opendc-web-api/opendc/api/scenarios.py @@ -0,0 +1,86 @@ +# Copyright (c) 2021 AtLarge Research +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from flask import request +from flask_restful import Resource +from marshmallow import Schema, fields + +from opendc.models.scenario import Scenario as ScenarioModel, ScenarioSchema +from opendc.models.portfolio import Portfolio +from opendc.exts import current_user, requires_auth, has_scope + + +class Scenario(Resource): + """ + A Scenario resource. + """ + method_decorators = [requires_auth] + + def get(self, scenario_id): + """Get scenario by identifier.""" + scenario = ScenarioModel.from_id(scenario_id) + scenario.check_exists() + + # Users with scope runner can access all scenarios + if not has_scope('runner'): + scenario.check_user_access(current_user['sub'], False) + + data = ScenarioSchema().dump(scenario.obj) + return {'data': data} + + def put(self, scenario_id): + """Update this Scenarios name.""" + schema = Scenario.PutSchema() + result = schema.load(request.json) + + scenario = ScenarioModel.from_id(scenario_id) + + scenario.check_exists() + scenario.check_user_access(current_user['sub'], True) + + scenario.set_property('name', result['scenario']['name']) + + scenario.update() + data = ScenarioSchema().dump(scenario.obj) + return {'data': data} + + def delete(self, scenario_id): + """Delete this Scenario.""" + scenario = ScenarioModel.from_id(scenario_id) + scenario.check_exists() + scenario.check_user_access(current_user['sub'], True) + + scenario_id = scenario.get_id() + + portfolio = Portfolio.from_id(scenario.obj['portfolioId']) + portfolio.check_exists() + if scenario_id in portfolio.obj['scenarioIds']: + portfolio.obj['scenarioIds'].remove(scenario_id) + portfolio.update() + + old_object = scenario.delete() + data = ScenarioSchema().dump(old_object) + return {'data': data} + + class PutSchema(Schema): + """ + Schema for the put operation. + """ + scenario = fields.Nested(ScenarioSchema, required=True) diff --git a/opendc-web/opendc-web-api/opendc/api/schedulers.py b/opendc-web/opendc-web-api/opendc/api/schedulers.py new file mode 100644 index 00000000..b00d8c31 --- /dev/null +++ b/opendc-web/opendc-web-api/opendc/api/schedulers.py @@ -0,0 +1,46 @@ +# Copyright (c) 2021 AtLarge Research +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + + +from flask_restful import Resource +from opendc.exts import requires_auth + +SCHEDULERS = [ + 'mem', + 'mem-inv', + 'core-mem', + 'core-mem-inv', + 'active-servers', + 'active-servers-inv', + 'provisioned-cores', + 'provisioned-cores-inv', + 'random' +] + + +class SchedulerList(Resource): + """ + Resource for the list of schedulers to pick from. + """ + method_decorators = [requires_auth] + + def get(self): + """Get all available Traces.""" + return {'data': [{'name': name} for name in SCHEDULERS]} diff --git a/opendc-web/opendc-web-api/opendc/api/topologies.py b/opendc-web/opendc-web-api/opendc/api/topologies.py new file mode 100644 index 00000000..c0b2e7ee --- /dev/null +++ b/opendc-web/opendc-web-api/opendc/api/topologies.py @@ -0,0 +1,97 @@ +# Copyright (c) 2021 AtLarge Research +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from datetime import datetime + +from flask import request +from flask_restful import Resource +from marshmallow import Schema, fields + +from opendc.models.project import Project +from opendc.models.topology import Topology as TopologyModel, TopologySchema +from opendc.exts import current_user, requires_auth, has_scope + + +class Topology(Resource): + """ + Resource representing a single topology. + """ + method_decorators = [requires_auth] + + def get(self, topology_id): + """ + Get a single topology. + """ + topology = TopologyModel.from_id(topology_id) + topology.check_exists() + + # Users with scope runner can access all topologies + if not has_scope('runner'): + topology.check_user_access(current_user['sub'], False) + + data = TopologySchema().dump(topology.obj) + return {'data': data} + + def put(self, topology_id): + """ + Replace the topology. + """ + topology = TopologyModel.from_id(topology_id) + + schema = Topology.PutSchema() + result = schema.load(request.json) + + topology.check_exists() + topology.check_user_access(current_user['sub'], True) + + topology.set_property('name', result['topology']['name']) + topology.set_property('rooms', result['topology']['rooms']) + topology.set_property('datetimeLastEdited', datetime.now()) + + topology.update() + data = TopologySchema().dump(topology.obj) + return {'data': data} + + def delete(self, topology_id): + """ + Delete a topology. + """ + topology = TopologyModel.from_id(topology_id) + + topology.check_exists() + topology.check_user_access(current_user['sub'], True) + + topology_id = topology.get_id() + + project = Project.from_id(topology.obj['projectId']) + project.check_exists() + if topology_id in project.obj['topologyIds']: + project.obj['topologyIds'].remove(topology_id) + project.update() + + old_object = topology.delete() + data = TopologySchema().dump(old_object) + return {'data': data} + + class PutSchema(Schema): + """ + Schema for the PUT operation on a topology. + """ + topology = fields.Nested(TopologySchema, required=True) diff --git a/opendc-web/opendc-web-api/opendc/api/traces.py b/opendc-web/opendc-web-api/opendc/api/traces.py new file mode 100644 index 00000000..6be8c5e5 --- /dev/null +++ b/opendc-web/opendc-web-api/opendc/api/traces.py @@ -0,0 +1,51 @@ +# Copyright (c) 2021 AtLarge Research +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from flask_restful import Resource + +from opendc.exts import requires_auth +from opendc.models.trace import Trace as TraceModel, TraceSchema + + +class TraceList(Resource): + """ + Resource for the list of traces to pick from. + """ + method_decorators = [requires_auth] + + def get(self): + """Get all available Traces.""" + traces = TraceModel.get_all() + data = TraceSchema().dump(traces.obj, many=True) + return {'data': data} + + +class Trace(Resource): + """ + Resource representing a single trace. + """ + method_decorators = [requires_auth] + + def get(self, trace_id): + """Get trace information by identifier.""" + trace = TraceModel.from_id(trace_id) + trace.check_exists() + data = TraceSchema().dump(trace.obj) + return {'data': data} diff --git a/opendc-web/opendc-web-api/opendc/api/v2/__init__.py b/opendc-web/opendc-web-api/opendc/api/v2/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/__init__.py +++ /dev/null diff --git a/opendc-web/opendc-web-api/opendc/api/v2/paths.json b/opendc-web/opendc-web-api/opendc/api/v2/paths.json deleted file mode 100644 index 652be5bc..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/paths.json +++ /dev/null @@ -1,19 +0,0 @@ -[ - "/users", - "/users/{userId}", - "/projects", - "/projects/{projectId}", - "/projects/{projectId}/authorizations", - "/projects/{projectId}/topologies", - "/projects/{projectId}/portfolios", - "/topologies/{topologyId}", - "/portfolios/{portfolioId}", - "/portfolios/{portfolioId}/scenarios", - "/scenarios/{scenarioId}", - "/schedulers", - "/traces", - "/traces/{traceId}", - "/prefabs", - "/prefabs/{prefabId}", - "/prefabs/authorizations" -] diff --git a/opendc-web/opendc-web-api/opendc/api/v2/portfolios/__init__.py b/opendc-web/opendc-web-api/opendc/api/v2/portfolios/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/portfolios/__init__.py +++ /dev/null diff --git a/opendc-web/opendc-web-api/opendc/api/v2/portfolios/portfolioId/__init__.py b/opendc-web/opendc-web-api/opendc/api/v2/portfolios/portfolioId/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/portfolios/portfolioId/__init__.py +++ /dev/null diff --git a/opendc-web/opendc-web-api/opendc/api/v2/portfolios/portfolioId/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/portfolios/portfolioId/endpoint.py deleted file mode 100644 index 0ba61a13..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/portfolios/portfolioId/endpoint.py +++ /dev/null @@ -1,67 +0,0 @@ -from opendc.models.portfolio import Portfolio -from opendc.models.project import Project -from opendc.util.rest import Response - - -def GET(request): - """Get this Portfolio.""" - - request.check_required_parameters(path={'portfolioId': 'string'}) - - portfolio = Portfolio.from_id(request.params_path['portfolioId']) - - portfolio.check_exists() - portfolio.check_user_access(request.google_id, False) - - return Response(200, 'Successfully retrieved portfolio.', portfolio.obj) - - -def PUT(request): - """Update this Portfolio.""" - - request.check_required_parameters(path={'portfolioId': 'string'}, body={'portfolio': { - 'name': 'string', - 'targets': { - 'enabledMetrics': 'list', - 'repeatsPerScenario': 'int', - }, - }}) - - portfolio = Portfolio.from_id(request.params_path['portfolioId']) - - portfolio.check_exists() - portfolio.check_user_access(request.google_id, True) - - portfolio.set_property('name', - request.params_body['portfolio']['name']) - portfolio.set_property('targets.enabledMetrics', - request.params_body['portfolio']['targets']['enabledMetrics']) - portfolio.set_property('targets.repeatsPerScenario', - request.params_body['portfolio']['targets']['repeatsPerScenario']) - - portfolio.update() - - return Response(200, 'Successfully updated portfolio.', portfolio.obj) - - -def DELETE(request): - """Delete this Portfolio.""" - - request.check_required_parameters(path={'portfolioId': 'string'}) - - portfolio = Portfolio.from_id(request.params_path['portfolioId']) - - portfolio.check_exists() - portfolio.check_user_access(request.google_id, True) - - portfolio_id = portfolio.get_id() - - project = Project.from_id(portfolio.obj['projectId']) - project.check_exists() - if portfolio_id in project.obj['portfolioIds']: - project.obj['portfolioIds'].remove(portfolio_id) - project.update() - - old_object = portfolio.delete() - - return Response(200, 'Successfully deleted portfolio.', old_object) diff --git a/opendc-web/opendc-web-api/opendc/api/v2/portfolios/portfolioId/scenarios/__init__.py b/opendc-web/opendc-web-api/opendc/api/v2/portfolios/portfolioId/scenarios/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/portfolios/portfolioId/scenarios/__init__.py +++ /dev/null diff --git a/opendc-web/opendc-web-api/opendc/api/v2/portfolios/portfolioId/scenarios/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/portfolios/portfolioId/scenarios/endpoint.py deleted file mode 100644 index 2f042e06..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/portfolios/portfolioId/scenarios/endpoint.py +++ /dev/null @@ -1,49 +0,0 @@ -from opendc.models.portfolio import Portfolio -from opendc.models.scenario import Scenario -from opendc.models.topology import Topology -from opendc.util.rest import Response - - -def POST(request): - """Add a new Scenario for this Portfolio.""" - - request.check_required_parameters(path={'portfolioId': 'string'}, - body={ - 'scenario': { - 'name': 'string', - 'trace': { - 'traceId': 'string', - 'loadSamplingFraction': 'float', - }, - 'topology': { - 'topologyId': 'string', - }, - 'operational': { - 'failuresEnabled': 'bool', - 'performanceInterferenceEnabled': 'bool', - 'schedulerName': 'string', - }, - } - }) - - portfolio = Portfolio.from_id(request.params_path['portfolioId']) - - portfolio.check_exists() - portfolio.check_user_access(request.google_id, True) - - scenario = Scenario(request.params_body['scenario']) - - topology = Topology.from_id(scenario.obj['topology']['topologyId']) - topology.check_exists() - topology.check_user_access(request.google_id, True) - - scenario.set_property('portfolioId', portfolio.get_id()) - scenario.set_property('simulation', {'state': 'QUEUED'}) - scenario.set_property('topology.topologyId', topology.get_id()) - - scenario.insert() - - portfolio.obj['scenarioIds'].append(scenario.get_id()) - portfolio.update() - - return Response(200, 'Successfully added Scenario.', scenario.obj) diff --git a/opendc-web/opendc-web-api/opendc/api/v2/portfolios/portfolioId/scenarios/test_endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/portfolios/portfolioId/scenarios/test_endpoint.py deleted file mode 100644 index e5982b7f..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/portfolios/portfolioId/scenarios/test_endpoint.py +++ /dev/null @@ -1,125 +0,0 @@ -from opendc.util.database import DB - -test_id = 24 * '1' - - -def test_add_scenario_missing_parameter(client): - assert '400' in client.post('/v2/portfolios/1/scenarios').status - - -def test_add_scenario_non_existing_portfolio(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value=None) - assert '404' in client.post(f'/v2/portfolios/{test_id}/scenarios', - json={ - 'scenario': { - 'name': 'test', - 'trace': { - 'traceId': test_id, - 'loadSamplingFraction': 1.0, - }, - 'topology': { - 'topologyId': test_id, - }, - 'operational': { - 'failuresEnabled': True, - 'performanceInterferenceEnabled': False, - 'schedulerName': 'DEFAULT', - }, - } - }).status - - -def test_add_scenario_not_authorized(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'projectId': test_id, - 'portfolioId': test_id, - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'VIEW' - }] - }) - assert '403' in client.post(f'/v2/portfolios/{test_id}/scenarios', - json={ - 'scenario': { - 'name': 'test', - 'trace': { - 'traceId': test_id, - 'loadSamplingFraction': 1.0, - }, - 'topology': { - 'topologyId': test_id, - }, - 'operational': { - 'failuresEnabled': True, - 'performanceInterferenceEnabled': False, - 'schedulerName': 'DEFAULT', - }, - } - }).status - - -def test_add_scenario(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'projectId': test_id, - 'portfolioId': test_id, - 'portfolioIds': [test_id], - 'scenarioIds': [test_id], - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'EDIT' - }], - 'simulation': { - 'state': 'QUEUED', - }, - }) - mocker.patch.object(DB, - 'insert', - return_value={ - '_id': test_id, - 'name': 'test', - 'trace': { - 'traceId': test_id, - 'loadSamplingFraction': 1.0, - }, - 'topology': { - 'topologyId': test_id, - }, - 'operational': { - 'failuresEnabled': True, - 'performanceInterferenceEnabled': False, - 'schedulerName': 'DEFAULT', - }, - 'portfolioId': test_id, - 'simulationState': { - 'state': 'QUEUED', - }, - }) - mocker.patch.object(DB, 'update', return_value=None) - res = client.post( - f'/v2/portfolios/{test_id}/scenarios', - json={ - 'scenario': { - 'name': 'test', - 'trace': { - 'traceId': test_id, - 'loadSamplingFraction': 1.0, - }, - 'topology': { - 'topologyId': test_id, - }, - 'operational': { - 'failuresEnabled': True, - 'performanceInterferenceEnabled': False, - 'schedulerName': 'DEFAULT', - }, - } - }) - assert 'portfolioId' in res.json['content'] - assert 'simulation' in res.json['content'] - assert '200' in res.status diff --git a/opendc-web/opendc-web-api/opendc/api/v2/portfolios/portfolioId/test_endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/portfolios/portfolioId/test_endpoint.py deleted file mode 100644 index 52f71aa4..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/portfolios/portfolioId/test_endpoint.py +++ /dev/null @@ -1,152 +0,0 @@ -from opendc.util.database import DB - -test_id = 24 * '1' -test_id_2 = 24 * '2' - - -def test_get_portfolio_non_existing(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value=None) - assert '404' in client.get(f'/v2/portfolios/{test_id}').status - - -def test_get_portfolio_no_authorizations(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value={'projectId': test_id, 'authorizations': []}) - res = client.get(f'/v2/portfolios/{test_id}') - assert '403' in res.status - - -def test_get_portfolio_not_authorized(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - 'projectId': test_id, - '_id': test_id, - 'authorizations': [{ - 'projectId': test_id_2, - 'authorizationLevel': 'OWN' - }] - }) - res = client.get(f'/v2/portfolios/{test_id}') - assert '403' in res.status - - -def test_get_portfolio(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - 'projectId': test_id, - '_id': test_id, - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'EDIT' - }] - }) - res = client.get(f'/v2/portfolios/{test_id}') - assert '200' in res.status - - -def test_update_portfolio_missing_parameter(client): - assert '400' in client.put(f'/v2/portfolios/{test_id}').status - - -def test_update_portfolio_non_existing(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value=None) - assert '404' in client.put(f'/v2/portfolios/{test_id}', json={ - 'portfolio': { - 'name': 'test', - 'targets': { - 'enabledMetrics': ['test'], - 'repeatsPerScenario': 2 - } - } - }).status - - -def test_update_portfolio_not_authorized(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'projectId': test_id, - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'VIEW' - }] - }) - mocker.patch.object(DB, 'update', return_value={}) - assert '403' in client.put(f'/v2/portfolios/{test_id}', json={ - 'portfolio': { - 'name': 'test', - 'targets': { - 'enabledMetrics': ['test'], - 'repeatsPerScenario': 2 - } - } - }).status - - -def test_update_portfolio(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'projectId': test_id, - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'OWN' - }], - 'targets': { - 'enabledMetrics': [], - 'repeatsPerScenario': 1 - } - }) - mocker.patch.object(DB, 'update', return_value={}) - - res = client.put(f'/v2/portfolios/{test_id}', json={'portfolio': { - 'name': 'test', - 'targets': { - 'enabledMetrics': ['test'], - 'repeatsPerScenario': 2 - } - }}) - assert '200' in res.status - - -def test_delete_project_non_existing(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value=None) - assert '404' in client.delete(f'/v2/portfolios/{test_id}').status - - -def test_delete_project_different_user(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'projectId': test_id, - 'googleId': 'other_test', - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'VIEW' - }] - }) - mocker.patch.object(DB, 'delete_one', return_value=None) - assert '403' in client.delete(f'/v2/portfolios/{test_id}').status - - -def test_delete_project(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'projectId': test_id, - 'googleId': 'test', - 'portfolioIds': [test_id], - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'OWN' - }] - }) - mocker.patch.object(DB, 'delete_one', return_value={}) - mocker.patch.object(DB, 'update', return_value=None) - res = client.delete(f'/v2/portfolios/{test_id}') - assert '200' in res.status diff --git a/opendc-web/opendc-web-api/opendc/api/v2/prefabs/__init__.py b/opendc-web/opendc-web-api/opendc/api/v2/prefabs/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/prefabs/__init__.py +++ /dev/null diff --git a/opendc-web/opendc-web-api/opendc/api/v2/prefabs/authorizations/__init__.py b/opendc-web/opendc-web-api/opendc/api/v2/prefabs/authorizations/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/prefabs/authorizations/__init__.py +++ /dev/null diff --git a/opendc-web/opendc-web-api/opendc/api/v2/prefabs/authorizations/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/prefabs/authorizations/endpoint.py deleted file mode 100644 index 0d9ad5cd..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/prefabs/authorizations/endpoint.py +++ /dev/null @@ -1,22 +0,0 @@ -from opendc.models.prefab import Prefab -from opendc.util.database import DB -from opendc.models.user import User -from opendc.util.rest import Response - - -def GET(request): - """Return all prefabs the user is authorized to access""" - - user = User.from_google_id(request.google_id) - - user.check_exists() - - own_prefabs = DB.fetch_all({'authorId': user.get_id()}, Prefab.collection_name) - public_prefabs = DB.fetch_all({'visibility': 'public'}, Prefab.collection_name) - - authorizations = {"authorizations": []} - - authorizations["authorizations"].append(own_prefabs) - authorizations["authorizations"].append(public_prefabs) - - return Response(200, 'Successfully fetched authorizations.', authorizations) diff --git a/opendc-web/opendc-web-api/opendc/api/v2/prefabs/authorizations/test_endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/prefabs/authorizations/test_endpoint.py deleted file mode 100644 index 6d36d428..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/prefabs/authorizations/test_endpoint.py +++ /dev/null @@ -1,71 +0,0 @@ -from opendc.util.database import DB -from unittest.mock import Mock - -test_id = 24 * '1' - - -def test_get_authorizations(client, mocker): - DB.fetch_all = Mock() - mocker.patch.object(DB, 'fetch_one', return_value={'_id': test_id}) - DB.fetch_all.side_effect = [ - [{ - '_id': test_id, - 'datetimeCreated': '000', - 'datetimeLastEdited': '000', - 'authorId': test_id, - 'visibility' : 'private' - }, - { - '_id': '2' * 24, - 'datetimeCreated': '000', - 'datetimeLastEdited': '000', - 'authorId': test_id, - 'visibility' : 'private' - }, - { - '_id': '3' * 24, - 'datetimeCreated': '000', - 'datetimeLastEdited': '000', - 'authorId': test_id, - 'visibility' : 'public' - }, - { - '_id': '4' * 24, - 'datetimeCreated': '000', - 'datetimeLastEdited': '000', - 'authorId': test_id, - 'visibility' : 'public' - }], - [{ - '_id': '5' * 24, - 'datetimeCreated': '000', - 'datetimeLastEdited': '000', - 'authorId': '2' * 24, - 'visibility' : 'public' - }, - { - '_id': '6' * 24, - 'datetimeCreated': '000', - 'datetimeLastEdited': '000', - 'authorId': '2' * 24, - 'visibility' : 'public' - }, - { - '_id': '7' * 24, - 'datetimeCreated': '000', - 'datetimeLastEdited': '000', - 'authorId': '2' * 24, - 'visibility' : 'public' - }, - { - '_id': '8' * 24, - 'datetimeCreated': '000', - 'datetimeLastEdited': '000', - 'authorId': '2' * 24, - 'visibility' : 'public' - }] - ] - mocker.patch.object(DB, 'fetch_one', return_value={'_id': test_id}) - res = client.get('/v2/prefabs/authorizations') - assert '200' in res.status - diff --git a/opendc-web/opendc-web-api/opendc/api/v2/prefabs/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/prefabs/endpoint.py deleted file mode 100644 index 723a2f0d..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/prefabs/endpoint.py +++ /dev/null @@ -1,23 +0,0 @@ -from datetime import datetime - -from opendc.models.prefab import Prefab -from opendc.models.user import User -from opendc.util.database import Database -from opendc.util.rest import Response - - -def POST(request): - """Create a new prefab, and return that new prefab.""" - - request.check_required_parameters(body={'prefab': {'name': 'string'}}) - - prefab = Prefab(request.params_body['prefab']) - prefab.set_property('datetimeCreated', Database.datetime_to_string(datetime.now())) - prefab.set_property('datetimeLastEdited', Database.datetime_to_string(datetime.now())) - - user = User.from_google_id(request.google_id) - prefab.set_property('authorId', user.get_id()) - - prefab.insert() - - return Response(200, 'Successfully created prefab.', prefab.obj) diff --git a/opendc-web/opendc-web-api/opendc/api/v2/prefabs/prefabId/__init__.py b/opendc-web/opendc-web-api/opendc/api/v2/prefabs/prefabId/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/prefabs/prefabId/__init__.py +++ /dev/null diff --git a/opendc-web/opendc-web-api/opendc/api/v2/prefabs/prefabId/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/prefabs/prefabId/endpoint.py deleted file mode 100644 index 7b81f546..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/prefabs/prefabId/endpoint.py +++ /dev/null @@ -1,50 +0,0 @@ -from datetime import datetime - -from opendc.models.prefab import Prefab -from opendc.util.database import Database -from opendc.util.rest import Response - - -def GET(request): - """Get this Prefab.""" - - request.check_required_parameters(path={'prefabId': 'string'}) - - prefab = Prefab.from_id(request.params_path['prefabId']) - prefab.check_exists() - prefab.check_user_access(request.google_id) - - return Response(200, 'Successfully retrieved prefab', prefab.obj) - - -def PUT(request): - """Update a prefab's name and/or contents.""" - - request.check_required_parameters(body={'prefab': {'name': 'name'}}, path={'prefabId': 'string'}) - - prefab = Prefab.from_id(request.params_path['prefabId']) - - prefab.check_exists() - prefab.check_user_access(request.google_id) - - prefab.set_property('name', request.params_body['prefab']['name']) - prefab.set_property('rack', request.params_body['prefab']['rack']) - prefab.set_property('datetime_last_edited', Database.datetime_to_string(datetime.now())) - prefab.update() - - return Response(200, 'Successfully updated prefab.', prefab.obj) - - -def DELETE(request): - """Delete this Prefab.""" - - request.check_required_parameters(path={'prefabId': 'string'}) - - prefab = Prefab.from_id(request.params_path['prefabId']) - - prefab.check_exists() - prefab.check_user_access(request.google_id) - - old_object = prefab.delete() - - return Response(200, 'Successfully deleted prefab.', old_object) diff --git a/opendc-web/opendc-web-api/opendc/api/v2/prefabs/prefabId/test_endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/prefabs/prefabId/test_endpoint.py deleted file mode 100644 index 2daeb6bf..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/prefabs/prefabId/test_endpoint.py +++ /dev/null @@ -1,145 +0,0 @@ -from opendc.util.database import DB -from unittest.mock import Mock - -test_id = 24 * '1' -test_id_2 = 24 * '2' - - -def test_get_prefab_non_existing(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value=None) - assert '404' in client.get(f'/v2/prefabs/{test_id}').status - - -def test_get_private_prefab_not_authorized(client, mocker): - DB.fetch_one = Mock() - DB.fetch_one.side_effect = [{ - '_id': test_id, - 'name': 'test prefab', - 'authorId': test_id_2, - 'visibility': 'private', - 'rack': {} - }, - { - '_id': test_id - } - ] - res = client.get(f'/v2/prefabs/{test_id}') - assert '403' in res.status - - -def test_get_private_prefab(client, mocker): - DB.fetch_one = Mock() - DB.fetch_one.side_effect = [{ - '_id': test_id, - 'name': 'test prefab', - 'authorId': test_id, - 'visibility': 'private', - 'rack': {} - }, - { - '_id': test_id - } - ] - res = client.get(f'/v2/prefabs/{test_id}') - assert '200' in res.status - - -def test_get_public_prefab(client, mocker): - DB.fetch_one = Mock() - DB.fetch_one.side_effect = [{ - '_id': test_id, - 'name': 'test prefab', - 'authorId': test_id_2, - 'visibility': 'public', - 'rack': {} - }, - { - '_id': test_id - } - ] - res = client.get(f'/v2/prefabs/{test_id}') - assert '200' in res.status - - -def test_update_prefab_missing_parameter(client): - assert '400' in client.put(f'/v2/prefabs/{test_id}').status - - -def test_update_prefab_non_existing(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value=None) - assert '404' in client.put(f'/v2/prefabs/{test_id}', json={'prefab': {'name': 'S'}}).status - - -def test_update_prefab_not_authorized(client, mocker): - DB.fetch_one = Mock() - DB.fetch_one.side_effect = [{ - '_id': test_id, - 'name': 'test prefab', - 'authorId': test_id_2, - 'visibility': 'private', - 'rack': {} - }, - { - '_id': test_id - } - ] - mocker.patch.object(DB, 'update', return_value={}) - assert '403' in client.put(f'/v2/prefabs/{test_id}', json={'prefab': {'name': 'test prefab', 'rack': {}}}).status - - -def test_update_prefab(client, mocker): - DB.fetch_one = Mock() - DB.fetch_one.side_effect = [{ - '_id': test_id, - 'name': 'test prefab', - 'authorId': test_id, - 'visibility': 'private', - 'rack': {} - }, - { - '_id': test_id - } - ] - mocker.patch.object(DB, 'update', return_value={}) - res = client.put(f'/v2/prefabs/{test_id}', json={'prefab': {'name': 'test prefab', 'rack': {}}}) - assert '200' in res.status - - -def test_delete_prefab_non_existing(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value=None) - assert '404' in client.delete(f'/v2/prefabs/{test_id}').status - - -def test_delete_prefab_different_user(client, mocker): - DB.fetch_one = Mock() - DB.fetch_one.side_effect = [{ - '_id': test_id, - 'name': 'test prefab', - 'authorId': test_id_2, - 'visibility': 'private', - 'rack': {} - }, - { - '_id': test_id - } - ] - mocker.patch.object(DB, 'delete_one', return_value=None) - assert '403' in client.delete(f'/v2/prefabs/{test_id}').status - - -def test_delete_prefab(client, mocker): - DB.fetch_one = Mock() - DB.fetch_one.side_effect = [{ - '_id': test_id, - 'name': 'test prefab', - 'authorId': test_id, - 'visibility': 'private', - 'rack': {} - }, - { - '_id': test_id - } - ] - mocker.patch.object(DB, 'delete_one', return_value={'prefab': {'name': 'name'}}) - res = client.delete(f'/v2/prefabs/{test_id}') - assert '200' in res.status diff --git a/opendc-web/opendc-web-api/opendc/api/v2/prefabs/test_endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/prefabs/test_endpoint.py deleted file mode 100644 index 39a78c21..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/prefabs/test_endpoint.py +++ /dev/null @@ -1,24 +0,0 @@ -from opendc.util.database import DB - -test_id = 24 * '1' - - -def test_add_prefab_missing_parameter(client): - assert '400' in client.post('/v2/prefabs').status - - -def test_add_prefab(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value={'_id': test_id, 'authorizations': []}) - mocker.patch.object(DB, - 'insert', - return_value={ - '_id': test_id, - 'datetimeCreated': '000', - 'datetimeLastEdited': '000', - 'authorId': test_id - }) - res = client.post('/v2/prefabs', json={'prefab': {'name': 'test prefab'}}) - assert 'datetimeCreated' in res.json['content'] - assert 'datetimeLastEdited' in res.json['content'] - assert 'authorId' in res.json['content'] - assert '200' in res.status diff --git a/opendc-web/opendc-web-api/opendc/api/v2/projects/__init__.py b/opendc-web/opendc-web-api/opendc/api/v2/projects/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/projects/__init__.py +++ /dev/null diff --git a/opendc-web/opendc-web-api/opendc/api/v2/projects/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/projects/endpoint.py deleted file mode 100644 index bf031382..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/projects/endpoint.py +++ /dev/null @@ -1,32 +0,0 @@ -from datetime import datetime - -from opendc.models.project import Project -from opendc.models.topology import Topology -from opendc.models.user import User -from opendc.util.database import Database -from opendc.util.rest import Response - - -def POST(request): - """Create a new project, and return that new project.""" - - request.check_required_parameters(body={'project': {'name': 'string'}}) - - topology = Topology({'name': 'Default topology', 'rooms': []}) - topology.insert() - - project = Project(request.params_body['project']) - project.set_property('datetimeCreated', Database.datetime_to_string(datetime.now())) - project.set_property('datetimeLastEdited', Database.datetime_to_string(datetime.now())) - project.set_property('topologyIds', [topology.get_id()]) - project.set_property('portfolioIds', []) - project.insert() - - topology.set_property('projectId', project.get_id()) - topology.update() - - user = User.from_google_id(request.google_id) - user.obj['authorizations'].append({'projectId': project.get_id(), 'authorizationLevel': 'OWN'}) - user.update() - - return Response(200, 'Successfully created project.', project.obj) diff --git a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/__init__.py b/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/__init__.py +++ /dev/null diff --git a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/authorizations/__init__.py b/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/authorizations/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/authorizations/__init__.py +++ /dev/null diff --git a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/authorizations/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/authorizations/endpoint.py deleted file mode 100644 index 9f6a60ec..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/authorizations/endpoint.py +++ /dev/null @@ -1,17 +0,0 @@ -from opendc.models.project import Project -from opendc.util.rest import Response - - -def GET(request): - """Find all authorizations for a Project.""" - - request.check_required_parameters(path={'projectId': 'string'}) - - project = Project.from_id(request.params_path['projectId']) - - project.check_exists() - project.check_user_access(request.google_id, False) - - authorizations = project.get_all_authorizations() - - return Response(200, 'Successfully retrieved project authorizations', authorizations) diff --git a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/authorizations/test_endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/authorizations/test_endpoint.py deleted file mode 100644 index bebd6cff..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/authorizations/test_endpoint.py +++ /dev/null @@ -1,43 +0,0 @@ -from opendc.util.database import DB - -test_id = 24 * '1' -test_id_2 = 24 * '2' - - -def test_get_authorizations_non_existing(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value=None) - mocker.patch.object(DB, 'fetch_all', return_value=None) - assert '404' in client.get(f'/v2/projects/{test_id}/authorizations').status - - -def test_get_authorizations_not_authorized(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'name': 'test trace', - 'authorizations': [{ - 'projectId': test_id_2, - 'authorizationLevel': 'OWN' - }] - }) - mocker.patch.object(DB, 'fetch_all', return_value=[]) - res = client.get(f'/v2/projects/{test_id}/authorizations') - assert '403' in res.status - - -def test_get_authorizations(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'name': 'test trace', - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'OWN' - }] - }) - mocker.patch.object(DB, 'fetch_all', return_value=[]) - res = client.get(f'/v2/projects/{test_id}/authorizations') - assert len(res.json['content']) == 0 - assert '200' in res.status diff --git a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/endpoint.py deleted file mode 100644 index caac37ca..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/endpoint.py +++ /dev/null @@ -1,66 +0,0 @@ -from datetime import datetime - -from opendc.models.portfolio import Portfolio -from opendc.models.project import Project -from opendc.models.topology import Topology -from opendc.models.user import User -from opendc.util.database import Database -from opendc.util.rest import Response - - -def GET(request): - """Get this Project.""" - - request.check_required_parameters(path={'projectId': 'string'}) - - project = Project.from_id(request.params_path['projectId']) - - project.check_exists() - project.check_user_access(request.google_id, False) - - return Response(200, 'Successfully retrieved project', project.obj) - - -def PUT(request): - """Update a project's name.""" - - request.check_required_parameters(body={'project': {'name': 'name'}}, path={'projectId': 'string'}) - - project = Project.from_id(request.params_path['projectId']) - - project.check_exists() - project.check_user_access(request.google_id, True) - - project.set_property('name', request.params_body['project']['name']) - project.set_property('datetime_last_edited', Database.datetime_to_string(datetime.now())) - project.update() - - return Response(200, 'Successfully updated project.', project.obj) - - -def DELETE(request): - """Delete this Project.""" - - request.check_required_parameters(path={'projectId': 'string'}) - - project = Project.from_id(request.params_path['projectId']) - - project.check_exists() - project.check_user_access(request.google_id, True) - - for topology_id in project.obj['topologyIds']: - topology = Topology.from_id(topology_id) - topology.delete() - - for portfolio_id in project.obj['portfolioIds']: - portfolio = Portfolio.from_id(portfolio_id) - portfolio.delete() - - user = User.from_google_id(request.google_id) - user.obj['authorizations'] = list( - filter(lambda x: x['projectId'] != project.get_id(), user.obj['authorizations'])) - user.update() - - old_object = project.delete() - - return Response(200, 'Successfully deleted project.', old_object) diff --git a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/portfolios/__init__.py b/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/portfolios/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/portfolios/__init__.py +++ /dev/null diff --git a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/portfolios/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/portfolios/endpoint.py deleted file mode 100644 index 2cdb1194..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/portfolios/endpoint.py +++ /dev/null @@ -1,35 +0,0 @@ -from opendc.models.portfolio import Portfolio -from opendc.models.project import Project -from opendc.util.rest import Response - - -def POST(request): - """Add a new Portfolio for this Project.""" - - request.check_required_parameters(path={'projectId': 'string'}, - body={ - 'portfolio': { - 'name': 'string', - 'targets': { - 'enabledMetrics': 'list', - 'repeatsPerScenario': 'int', - }, - } - }) - - project = Project.from_id(request.params_path['projectId']) - - project.check_exists() - project.check_user_access(request.google_id, True) - - portfolio = Portfolio(request.params_body['portfolio']) - - portfolio.set_property('projectId', project.get_id()) - portfolio.set_property('scenarioIds', []) - - portfolio.insert() - - project.obj['portfolioIds'].append(portfolio.get_id()) - project.update() - - return Response(200, 'Successfully added Portfolio.', portfolio.obj) diff --git a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/portfolios/test_endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/portfolios/test_endpoint.py deleted file mode 100644 index 04c699b5..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/portfolios/test_endpoint.py +++ /dev/null @@ -1,85 +0,0 @@ -from opendc.util.database import DB - -test_id = 24 * '1' - - -def test_add_portfolio_missing_parameter(client): - assert '400' in client.post(f'/v2/projects/{test_id}/portfolios').status - - -def test_add_portfolio_non_existing_project(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value=None) - assert '404' in client.post(f'/v2/projects/{test_id}/portfolios', - json={ - 'portfolio': { - 'name': 'test', - 'targets': { - 'enabledMetrics': ['test'], - 'repeatsPerScenario': 2 - } - } - }).status - - -def test_add_portfolio_not_authorized(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'projectId': test_id, - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'VIEW' - }] - }) - assert '403' in client.post(f'/v2/projects/{test_id}/portfolios', - json={ - 'portfolio': { - 'name': 'test', - 'targets': { - 'enabledMetrics': ['test'], - 'repeatsPerScenario': 2 - } - } - }).status - - -def test_add_portfolio(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'projectId': test_id, - 'portfolioIds': [test_id], - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'EDIT' - }] - }) - mocker.patch.object(DB, - 'insert', - return_value={ - '_id': test_id, - 'name': 'test', - 'targets': { - 'enabledMetrics': ['test'], - 'repeatsPerScenario': 2 - }, - 'projectId': test_id, - 'scenarioIds': [], - }) - mocker.patch.object(DB, 'update', return_value=None) - res = client.post( - f'/v2/projects/{test_id}/portfolios', - json={ - 'portfolio': { - 'name': 'test', - 'targets': { - 'enabledMetrics': ['test'], - 'repeatsPerScenario': 2 - } - } - }) - assert 'projectId' in res.json['content'] - assert 'scenarioIds' in res.json['content'] - assert '200' in res.status diff --git a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/test_endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/test_endpoint.py deleted file mode 100644 index f9ffaf37..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/test_endpoint.py +++ /dev/null @@ -1,122 +0,0 @@ -from opendc.util.database import DB - -test_id = 24 * '1' -test_id_2 = 24 * '2' - - -def test_get_project_non_existing(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value=None) - assert '404' in client.get(f'/v2/projects/{test_id}').status - - -def test_get_project_no_authorizations(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value={'authorizations': []}) - res = client.get(f'/v2/projects/{test_id}') - assert '403' in res.status - - -def test_get_project_not_authorized(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'authorizations': [{ - 'projectId': test_id_2, - 'authorizationLevel': 'OWN' - }] - }) - res = client.get(f'/v2/projects/{test_id}') - assert '403' in res.status - - -def test_get_project(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'EDIT' - }] - }) - res = client.get(f'/v2/projects/{test_id}') - assert '200' in res.status - - -def test_update_project_missing_parameter(client): - assert '400' in client.put(f'/v2/projects/{test_id}').status - - -def test_update_project_non_existing(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value=None) - assert '404' in client.put(f'/v2/projects/{test_id}', json={'project': {'name': 'S'}}).status - - -def test_update_project_not_authorized(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'VIEW' - }] - }) - mocker.patch.object(DB, 'update', return_value={}) - assert '403' in client.put(f'/v2/projects/{test_id}', json={'project': {'name': 'S'}}).status - - -def test_update_project(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'OWN' - }] - }) - mocker.patch.object(DB, 'update', return_value={}) - - res = client.put(f'/v2/projects/{test_id}', json={'project': {'name': 'S'}}) - assert '200' in res.status - - -def test_delete_project_non_existing(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value=None) - assert '404' in client.delete(f'/v2/projects/{test_id}').status - - -def test_delete_project_different_user(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'googleId': 'other_test', - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'VIEW' - }], - 'topologyIds': [] - }) - mocker.patch.object(DB, 'delete_one', return_value=None) - assert '403' in client.delete(f'/v2/projects/{test_id}').status - - -def test_delete_project(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'googleId': 'test', - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'OWN' - }], - 'topologyIds': [], - 'portfolioIds': [], - }) - mocker.patch.object(DB, 'update', return_value=None) - mocker.patch.object(DB, 'delete_one', return_value={'googleId': 'test'}) - res = client.delete(f'/v2/projects/{test_id}') - assert '200' in res.status diff --git a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/topologies/__init__.py b/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/topologies/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/topologies/__init__.py +++ /dev/null diff --git a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/topologies/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/topologies/endpoint.py deleted file mode 100644 index 44a0d575..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/topologies/endpoint.py +++ /dev/null @@ -1,31 +0,0 @@ -from datetime import datetime - -from opendc.models.project import Project -from opendc.models.topology import Topology -from opendc.util.rest import Response -from opendc.util.database import Database - - -def POST(request): - """Add a new Topology to the specified project and return it""" - - request.check_required_parameters(path={'projectId': 'string'}, body={'topology': {'name': 'string'}}) - - project = Project.from_id(request.params_path['projectId']) - - project.check_exists() - project.check_user_access(request.google_id, True) - - topology = Topology({ - 'projectId': project.get_id(), - 'name': request.params_body['topology']['name'], - 'rooms': request.params_body['topology']['rooms'], - }) - - topology.insert() - - project.obj['topologyIds'].append(topology.get_id()) - project.set_property('datetimeLastEdited', Database.datetime_to_string(datetime.now())) - project.update() - - return Response(200, 'Successfully inserted topology.', topology.obj) diff --git a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/topologies/test_endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/topologies/test_endpoint.py deleted file mode 100644 index 71e88f00..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/topologies/test_endpoint.py +++ /dev/null @@ -1,52 +0,0 @@ -from opendc.util.database import DB - -test_id = 24 * '1' - - -def test_add_topology_missing_parameter(client): - assert '400' in client.post(f'/v2/projects/{test_id}/topologies').status - - -def test_add_topology(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'OWN' - }], - 'topologyIds': [] - }) - mocker.patch.object(DB, - 'insert', - return_value={ - '_id': test_id, - 'datetimeCreated': '000', - 'datetimeLastEdited': '000', - 'topologyIds': [] - }) - mocker.patch.object(DB, 'update', return_value={}) - res = client.post(f'/v2/projects/{test_id}/topologies', json={'topology': {'name': 'test project', 'rooms': []}}) - assert 'rooms' in res.json['content'] - assert '200' in res.status - - -def test_add_topology_not_authorized(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'projectId': test_id, - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'VIEW' - }] - }) - assert '403' in client.post(f'/v2/projects/{test_id}/topologies', - json={ - 'topology': { - 'name': 'test_topology', - 'rooms': {} - } - }).status diff --git a/opendc-web/opendc-web-api/opendc/api/v2/projects/test_endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/projects/test_endpoint.py deleted file mode 100644 index 9444b1e4..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/projects/test_endpoint.py +++ /dev/null @@ -1,25 +0,0 @@ -from opendc.util.database import DB - -test_id = 24 * '1' - - -def test_add_project_missing_parameter(client): - assert '400' in client.post('/v2/projects').status - - -def test_add_project(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value={'_id': test_id, 'authorizations': []}) - mocker.patch.object(DB, - 'insert', - return_value={ - '_id': test_id, - 'datetimeCreated': '000', - 'datetimeLastEdited': '000', - 'topologyIds': [] - }) - mocker.patch.object(DB, 'update', return_value={}) - res = client.post('/v2/projects', json={'project': {'name': 'test project'}}) - assert 'datetimeCreated' in res.json['content'] - assert 'datetimeLastEdited' in res.json['content'] - assert 'topologyIds' in res.json['content'] - assert '200' in res.status diff --git a/opendc-web/opendc-web-api/opendc/api/v2/scenarios/__init__.py b/opendc-web/opendc-web-api/opendc/api/v2/scenarios/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/scenarios/__init__.py +++ /dev/null diff --git a/opendc-web/opendc-web-api/opendc/api/v2/scenarios/scenarioId/__init__.py b/opendc-web/opendc-web-api/opendc/api/v2/scenarios/scenarioId/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/scenarios/scenarioId/__init__.py +++ /dev/null diff --git a/opendc-web/opendc-web-api/opendc/api/v2/scenarios/scenarioId/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/scenarios/scenarioId/endpoint.py deleted file mode 100644 index 88a74e9c..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/scenarios/scenarioId/endpoint.py +++ /dev/null @@ -1,59 +0,0 @@ -from opendc.models.scenario import Scenario -from opendc.models.portfolio import Portfolio -from opendc.util.rest import Response - - -def GET(request): - """Get this Scenario.""" - - request.check_required_parameters(path={'scenarioId': 'string'}) - - scenario = Scenario.from_id(request.params_path['scenarioId']) - - scenario.check_exists() - scenario.check_user_access(request.google_id, False) - - return Response(200, 'Successfully retrieved scenario.', scenario.obj) - - -def PUT(request): - """Update this Scenarios name.""" - - request.check_required_parameters(path={'scenarioId': 'string'}, body={'scenario': { - 'name': 'string', - }}) - - scenario = Scenario.from_id(request.params_path['scenarioId']) - - scenario.check_exists() - scenario.check_user_access(request.google_id, True) - - scenario.set_property('name', - request.params_body['scenario']['name']) - - scenario.update() - - return Response(200, 'Successfully updated scenario.', scenario.obj) - - -def DELETE(request): - """Delete this Scenario.""" - - request.check_required_parameters(path={'scenarioId': 'string'}) - - scenario = Scenario.from_id(request.params_path['scenarioId']) - - scenario.check_exists() - scenario.check_user_access(request.google_id, True) - - scenario_id = scenario.get_id() - - portfolio = Portfolio.from_id(scenario.obj['portfolioId']) - portfolio.check_exists() - if scenario_id in portfolio.obj['scenarioIds']: - portfolio.obj['scenarioIds'].remove(scenario_id) - portfolio.update() - - old_object = scenario.delete() - - return Response(200, 'Successfully deleted scenario.', old_object) diff --git a/opendc-web/opendc-web-api/opendc/api/v2/scenarios/scenarioId/test_endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/scenarios/scenarioId/test_endpoint.py deleted file mode 100644 index cd4bcdf8..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/scenarios/scenarioId/test_endpoint.py +++ /dev/null @@ -1,149 +0,0 @@ -from opendc.util.database import DB - -test_id = 24 * '1' -test_id_2 = 24 * '2' - - -def test_get_scenario_non_existing(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value=None) - assert '404' in client.get(f'/v2/scenarios/{test_id}').status - - -def test_get_scenario_no_authorizations(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value={ - 'portfolioId': '1', - 'authorizations': [] - }) - res = client.get(f'/v2/scenarios/{test_id}') - assert '403' in res.status - - -def test_get_scenario_not_authorized(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - 'projectId': test_id, - 'portfolioId': test_id, - '_id': test_id, - 'authorizations': [{ - 'projectId': test_id_2, - 'authorizationLevel': 'OWN' - }] - }) - res = client.get(f'/v2/scenarios/{test_id}') - assert '403' in res.status - - -def test_get_scenario(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - 'projectId': test_id, - 'portfolioId': test_id, - '_id': test_id, - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'EDIT' - }] - }) - res = client.get(f'/v2/scenarios/{test_id}') - assert '200' in res.status - - -def test_update_scenario_missing_parameter(client): - assert '400' in client.put(f'/v2/scenarios/{test_id}').status - - -def test_update_scenario_non_existing(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value=None) - assert '404' in client.put(f'/v2/scenarios/{test_id}', json={ - 'scenario': { - 'name': 'test', - } - }).status - - -def test_update_scenario_not_authorized(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'projectId': test_id, - 'portfolioId': test_id, - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'VIEW' - }] - }) - mocker.patch.object(DB, 'update', return_value={}) - assert '403' in client.put(f'/v2/scenarios/{test_id}', json={ - 'scenario': { - 'name': 'test', - } - }).status - - -def test_update_scenario(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'projectId': test_id, - 'portfolioId': test_id, - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'OWN' - }], - 'targets': { - 'enabledMetrics': [], - 'repeatsPerScenario': 1 - } - }) - mocker.patch.object(DB, 'update', return_value={}) - - res = client.put(f'/v2/scenarios/{test_id}', json={'scenario': { - 'name': 'test', - }}) - assert '200' in res.status - - -def test_delete_project_non_existing(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value=None) - assert '404' in client.delete(f'/v2/scenarios/{test_id}').status - - -def test_delete_project_different_user(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'projectId': test_id, - 'portfolioId': test_id, - 'googleId': 'other_test', - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'VIEW' - }] - }) - mocker.patch.object(DB, 'delete_one', return_value=None) - assert '403' in client.delete(f'/v2/scenarios/{test_id}').status - - -def test_delete_project(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'projectId': test_id, - 'portfolioId': test_id, - 'googleId': 'test', - 'scenarioIds': [test_id], - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'OWN' - }] - }) - mocker.patch.object(DB, 'delete_one', return_value={}) - mocker.patch.object(DB, 'update', return_value=None) - res = client.delete(f'/v2/scenarios/{test_id}') - assert '200' in res.status diff --git a/opendc-web/opendc-web-api/opendc/api/v2/schedulers/__init__.py b/opendc-web/opendc-web-api/opendc/api/v2/schedulers/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/schedulers/__init__.py +++ /dev/null diff --git a/opendc-web/opendc-web-api/opendc/api/v2/schedulers/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/schedulers/endpoint.py deleted file mode 100644 index f33159bf..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/schedulers/endpoint.py +++ /dev/null @@ -1,19 +0,0 @@ -from opendc.util.rest import Response - -SCHEDULERS = [ - 'mem', - 'mem-inv', - 'core-mem', - 'core-mem-inv', - 'active-servers', - 'active-servers-inv', - 'provisioned-cores', - 'provisioned-cores-inv', - 'random' -] - - -def GET(_): - """Get all available Schedulers.""" - - return Response(200, 'Successfully retrieved Schedulers.', [{'name': name} for name in SCHEDULERS]) diff --git a/opendc-web/opendc-web-api/opendc/api/v2/schedulers/test_endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/schedulers/test_endpoint.py deleted file mode 100644 index 4950ca4c..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/schedulers/test_endpoint.py +++ /dev/null @@ -1,2 +0,0 @@ -def test_get_schedulers(client): - assert '200' in client.get('/v2/schedulers').status diff --git a/opendc-web/opendc-web-api/opendc/api/v2/topologies/__init__.py b/opendc-web/opendc-web-api/opendc/api/v2/topologies/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/topologies/__init__.py +++ /dev/null diff --git a/opendc-web/opendc-web-api/opendc/api/v2/topologies/topologyId/__init__.py b/opendc-web/opendc-web-api/opendc/api/v2/topologies/topologyId/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/topologies/topologyId/__init__.py +++ /dev/null diff --git a/opendc-web/opendc-web-api/opendc/api/v2/topologies/topologyId/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/topologies/topologyId/endpoint.py deleted file mode 100644 index ea82b2e2..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/topologies/topologyId/endpoint.py +++ /dev/null @@ -1,58 +0,0 @@ -from datetime import datetime - -from opendc.util.database import Database -from opendc.models.project import Project -from opendc.models.topology import Topology -from opendc.util.rest import Response - - -def GET(request): - """Get this Topology.""" - - request.check_required_parameters(path={'topologyId': 'string'}) - - topology = Topology.from_id(request.params_path['topologyId']) - - topology.check_exists() - topology.check_user_access(request.google_id, False) - - return Response(200, 'Successfully retrieved topology.', topology.obj) - - -def PUT(request): - """Update this topology""" - request.check_required_parameters(path={'topologyId': 'string'}, body={'topology': {'name': 'string', 'rooms': {}}}) - topology = Topology.from_id(request.params_path['topologyId']) - - topology.check_exists() - topology.check_user_access(request.google_id, True) - - topology.set_property('name', request.params_body['topology']['name']) - topology.set_property('rooms', request.params_body['topology']['rooms']) - topology.set_property('datetimeLastEdited', Database.datetime_to_string(datetime.now())) - - topology.update() - - return Response(200, 'Successfully updated topology.', topology.obj) - - -def DELETE(request): - """Delete this topology""" - request.check_required_parameters(path={'topologyId': 'string'}) - - topology = Topology.from_id(request.params_path['topologyId']) - - topology.check_exists() - topology.check_user_access(request.google_id, True) - - topology_id = topology.get_id() - - project = Project.from_id(topology.obj['projectId']) - project.check_exists() - if topology_id in project.obj['topologyIds']: - project.obj['topologyIds'].remove(topology_id) - project.update() - - old_object = topology.delete() - - return Response(200, 'Successfully deleted topology.', old_object) diff --git a/opendc-web/opendc-web-api/opendc/api/v2/topologies/topologyId/test_endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/topologies/topologyId/test_endpoint.py deleted file mode 100644 index 4da0bc64..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/topologies/topologyId/test_endpoint.py +++ /dev/null @@ -1,119 +0,0 @@ -from opendc.util.database import DB - -test_id = 24 * '1' -test_id_2 = 24 * '2' - - -def test_get_topology(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'projectId': test_id, - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'EDIT' - }] - }) - res = client.get(f'/v2/topologies/{test_id}') - assert '200' in res.status - - -def test_get_topology_non_existing(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value=None) - assert '404' in client.get('/v2/topologies/1').status - - -def test_get_topology_not_authorized(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'projectId': test_id, - 'authorizations': [{ - 'projectId': test_id_2, - 'authorizationLevel': 'OWN' - }] - }) - res = client.get(f'/v2/topologies/{test_id}') - assert '403' in res.status - - -def test_get_topology_no_authorizations(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value={'projectId': test_id, 'authorizations': []}) - res = client.get(f'/v2/topologies/{test_id}') - assert '403' in res.status - - -def test_update_topology_missing_parameter(client): - assert '400' in client.put(f'/v2/topologies/{test_id}').status - - -def test_update_topology_non_existent(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value=None) - assert '404' in client.put(f'/v2/topologies/{test_id}', json={'topology': {'name': 'test_topology', 'rooms': {}}}).status - - -def test_update_topology_not_authorized(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'projectId': test_id, - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'VIEW' - }] - }) - mocker.patch.object(DB, 'update', return_value={}) - assert '403' in client.put(f'/v2/topologies/{test_id}', json={ - 'topology': { - 'name': 'updated_topology', - 'rooms': {} - } - }).status - - -def test_update_topology(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'projectId': test_id, - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'OWN' - }] - }) - mocker.patch.object(DB, 'update', return_value={}) - - assert '200' in client.put(f'/v2/topologies/{test_id}', json={ - 'topology': { - 'name': 'updated_topology', - 'rooms': {} - } - }).status - - -def test_delete_topology(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'projectId': test_id, - 'googleId': 'test', - 'topologyIds': [test_id], - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'OWN' - }] - }) - mocker.patch.object(DB, 'delete_one', return_value={}) - mocker.patch.object(DB, 'update', return_value=None) - res = client.delete(f'/v2/topologies/{test_id}') - assert '200' in res.status - - -def test_delete_nonexistent_topology(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value=None) - assert '404' in client.delete(f'/v2/topologies/{test_id}').status diff --git a/opendc-web/opendc-web-api/opendc/api/v2/traces/__init__.py b/opendc-web/opendc-web-api/opendc/api/v2/traces/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/traces/__init__.py +++ /dev/null diff --git a/opendc-web/opendc-web-api/opendc/api/v2/traces/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/traces/endpoint.py deleted file mode 100644 index ee699e02..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/traces/endpoint.py +++ /dev/null @@ -1,10 +0,0 @@ -from opendc.models.trace import Trace -from opendc.util.rest import Response - - -def GET(_): - """Get all available Traces.""" - - traces = Trace.get_all() - - return Response(200, 'Successfully retrieved Traces', traces.obj) diff --git a/opendc-web/opendc-web-api/opendc/api/v2/traces/test_endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/traces/test_endpoint.py deleted file mode 100644 index 36846bd9..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/traces/test_endpoint.py +++ /dev/null @@ -1,6 +0,0 @@ -from opendc.util.database import DB - - -def test_get_traces(client, mocker): - mocker.patch.object(DB, 'fetch_all', return_value=[]) - assert '200' in client.get('/v2/traces').status diff --git a/opendc-web/opendc-web-api/opendc/api/v2/traces/traceId/__init__.py b/opendc-web/opendc-web-api/opendc/api/v2/traces/traceId/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/traces/traceId/__init__.py +++ /dev/null diff --git a/opendc-web/opendc-web-api/opendc/api/v2/traces/traceId/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/traces/traceId/endpoint.py deleted file mode 100644 index 670f88d1..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/traces/traceId/endpoint.py +++ /dev/null @@ -1,14 +0,0 @@ -from opendc.models.trace import Trace -from opendc.util.rest import Response - - -def GET(request): - """Get this Trace.""" - - request.check_required_parameters(path={'traceId': 'string'}) - - trace = Trace.from_id(request.params_path['traceId']) - - trace.check_exists() - - return Response(200, 'Successfully retrieved trace.', trace.obj) diff --git a/opendc-web/opendc-web-api/opendc/api/v2/traces/traceId/test_endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/traces/traceId/test_endpoint.py deleted file mode 100644 index 0c51538b..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/traces/traceId/test_endpoint.py +++ /dev/null @@ -1,15 +0,0 @@ -from opendc.util.database import DB - -test_id = 24 * '1' - - -def test_get_trace_non_existing(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value=None) - assert '404' in client.get(f'/v2/traces/{test_id}').status - - -def test_get_trace(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value={'name': 'test trace'}) - res = client.get(f'/v2/traces/{test_id}') - assert 'name' in res.json['content'] - assert '200' in res.status diff --git a/opendc-web/opendc-web-api/opendc/api/v2/users/__init__.py b/opendc-web/opendc-web-api/opendc/api/v2/users/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/users/__init__.py +++ /dev/null diff --git a/opendc-web/opendc-web-api/opendc/api/v2/users/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/users/endpoint.py deleted file mode 100644 index 0dcf2463..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/users/endpoint.py +++ /dev/null @@ -1,30 +0,0 @@ -from opendc.models.user import User -from opendc.util.rest import Response - - -def GET(request): - """Search for a User using their email address.""" - - request.check_required_parameters(query={'email': 'string'}) - - user = User.from_email(request.params_query['email']) - - user.check_exists() - - return Response(200, 'Successfully retrieved user.', user.obj) - - -def POST(request): - """Add a new User.""" - - request.check_required_parameters(body={'user': {'email': 'string'}}) - - user = User(request.params_body['user']) - user.set_property('googleId', request.google_id) - user.set_property('authorizations', []) - - user.check_already_exists() - - user.insert() - - return Response(200, 'Successfully created user.', user.obj) diff --git a/opendc-web/opendc-web-api/opendc/api/v2/users/test_endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/users/test_endpoint.py deleted file mode 100644 index 13b63b20..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/users/test_endpoint.py +++ /dev/null @@ -1,34 +0,0 @@ -from opendc.util.database import DB - - -def test_get_user_by_email_missing_parameter(client): - assert '400' in client.get('/v2/users').status - - -def test_get_user_by_email_non_existing(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value=None) - assert '404' in client.get('/v2/users?email=test@test.com').status - - -def test_get_user_by_email(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value={'email': 'test@test.com'}) - res = client.get('/v2/users?email=test@test.com') - assert 'email' in res.json['content'] - assert '200' in res.status - - -def test_add_user_missing_parameter(client): - assert '400' in client.post('/v2/users').status - - -def test_add_user_existing(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value={'email': 'test@test.com'}) - assert '409' in client.post('/v2/users', json={'user': {'email': 'test@test.com'}}).status - - -def test_add_user(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value=None) - mocker.patch.object(DB, 'insert', return_value={'email': 'test@test.com'}) - res = client.post('/v2/users', json={'user': {'email': 'test@test.com'}}) - assert 'email' in res.json['content'] - assert '200' in res.status diff --git a/opendc-web/opendc-web-api/opendc/api/v2/users/userId/__init__.py b/opendc-web/opendc-web-api/opendc/api/v2/users/userId/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/users/userId/__init__.py +++ /dev/null diff --git a/opendc-web/opendc-web-api/opendc/api/v2/users/userId/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/users/userId/endpoint.py deleted file mode 100644 index be3462c0..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/users/userId/endpoint.py +++ /dev/null @@ -1,59 +0,0 @@ -from opendc.models.project import Project -from opendc.models.user import User -from opendc.util.rest import Response - - -def GET(request): - """Get this User.""" - - request.check_required_parameters(path={'userId': 'string'}) - - user = User.from_id(request.params_path['userId']) - - user.check_exists() - - return Response(200, 'Successfully retrieved user.', user.obj) - - -def PUT(request): - """Update this User's given name and/or family name.""" - - request.check_required_parameters(body={'user': { - 'givenName': 'string', - 'familyName': 'string' - }}, - path={'userId': 'string'}) - - user = User.from_id(request.params_path['userId']) - - user.check_exists() - user.check_correct_user(request.google_id) - - user.set_property('givenName', request.params_body['user']['givenName']) - user.set_property('familyName', request.params_body['user']['familyName']) - - user.update() - - return Response(200, 'Successfully updated user.', user.obj) - - -def DELETE(request): - """Delete this User.""" - - request.check_required_parameters(path={'userId': 'string'}) - - user = User.from_id(request.params_path['userId']) - - user.check_exists() - user.check_correct_user(request.google_id) - - for authorization in user.obj['authorizations']: - if authorization['authorizationLevel'] != 'OWN': - continue - - project = Project.from_id(authorization['projectId']) - project.delete() - - old_object = user.delete() - - return Response(200, 'Successfully deleted user.', old_object) diff --git a/opendc-web/opendc-web-api/opendc/api/v2/users/userId/test_endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/users/userId/test_endpoint.py deleted file mode 100644 index 4085642f..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/users/userId/test_endpoint.py +++ /dev/null @@ -1,56 +0,0 @@ -from opendc.util.database import DB - -test_id = 24 * '1' - - -def test_get_user_non_existing(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value=None) - assert '404' in client.get(f'/v2/users/{test_id}').status - - -def test_get_user(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value={'email': 'test@test.com'}) - res = client.get(f'/v2/users/{test_id}') - assert 'email' in res.json['content'] - assert '200' in res.status - - -def test_update_user_missing_parameter(client): - assert '400' in client.put(f'/v2/users/{test_id}').status - - -def test_update_user_non_existing(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value=None) - assert '404' in client.put(f'/v2/users/{test_id}', json={'user': {'givenName': 'A', 'familyName': 'B'}}).status - - -def test_update_user_different_user(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value={'_id': test_id, 'googleId': 'other_test'}) - assert '403' in client.put(f'/v2/users/{test_id}', json={'user': {'givenName': 'A', 'familyName': 'B'}}).status - - -def test_update_user(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value={'_id': test_id, 'googleId': 'test'}) - mocker.patch.object(DB, 'update', return_value={'givenName': 'A', 'familyName': 'B'}) - res = client.put(f'/v2/users/{test_id}', json={'user': {'givenName': 'A', 'familyName': 'B'}}) - assert 'givenName' in res.json['content'] - assert '200' in res.status - - -def test_delete_user_non_existing(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value=None) - assert '404' in client.delete(f'/v2/users/{test_id}').status - - -def test_delete_user_different_user(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value={'_id': test_id, 'googleId': 'other_test'}) - assert '403' in client.delete(f'/v2/users/{test_id}').status - - -def test_delete_user(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value={'_id': test_id, 'googleId': 'test', 'authorizations': []}) - mocker.patch.object(DB, 'delete_one', return_value={'googleId': 'test'}) - res = client.delete(f'/v2/users/{test_id}', ) - - assert 'googleId' in res.json['content'] - assert '200' in res.status diff --git a/opendc-web/opendc-web-api/opendc/auth.py b/opendc-web/opendc-web-api/opendc/auth.py new file mode 100644 index 00000000..d5da6ee5 --- /dev/null +++ b/opendc-web/opendc-web-api/opendc/auth.py @@ -0,0 +1,236 @@ +# Copyright (c) 2021 AtLarge Research +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import json +import time + +import urllib3 +from flask import request +from jose import jwt, JWTError + + +def get_token(): + """ + Obtain the Access Token from the Authorization Header + """ + auth = request.headers.get("Authorization", None) + if not auth: + raise AuthError({ + "code": "authorization_header_missing", + "description": "Authorization header is expected" + }, 401) + + parts = auth.split() + + if parts[0].lower() != "bearer": + raise AuthError({"code": "invalid_header", "description": "Authorization header must start with Bearer"}, 401) + if len(parts) == 1: + raise AuthError({"code": "invalid_header", "description": "Token not found"}, 401) + if len(parts) > 2: + raise AuthError({"code": "invalid_header", "description": "Authorization header must be" " Bearer token"}, 401) + + token = parts[1] + return token + + +class AuthError(Exception): + """ + This error is thrown when the request failed to authorize. + """ + def __init__(self, error, status_code): + Exception.__init__(self, error) + self.error = error + self.status_code = status_code + + +class AuthContext: + """ + This class handles the authorization of requests. + """ + def __init__(self, alg, issuer, audience): + self._alg = alg + self._issuer = issuer + self._audience = audience + + def validate(self, token): + """ + Validate the specified JWT token. + :param token: The authorization token specified by the user. + :return: The token payload on success, otherwise `AuthError`. + """ + try: + header = jwt.get_unverified_header(token) + except JWTError as e: + raise AuthError({"code": "invalid_token", "message": str(e)}, 401) + + alg = header.get('alg', None) + if alg != self._alg.algorithm: + raise AuthError( + { + "code": + "invalid_header", + "message": + f"Signature algorithm of {alg} is not supported. Expected the ID token " + f"to be signed with {self._alg.algorithm}" + }, 401) + + kid = header.get('kid', None) + try: + secret_or_certificate = self._alg.get_key(key_id=kid) + except TokenValidationError as e: + raise AuthError({"code": "invalid_header", "message": str(e)}, 401) + try: + payload = jwt.decode(token, + key=secret_or_certificate, + algorithms=[self._alg.algorithm], + audience=self._audience, + issuer=self._issuer) + return payload + except jwt.ExpiredSignatureError: + raise AuthError({"code": "token_expired", "message": "Token is expired"}, 401) + except jwt.JWTClaimsError: + raise AuthError( + { + "code": "invalid_claims", + "message": "Incorrect claims, please check the audience and issuer" + }, 401) + except Exception as e: + print(e) + raise AuthError({"code": "invalid_header", "message": "Unable to parse authentication token."}, 401) + + +class SymmetricJwtAlgorithm: + """Verifier for HMAC signatures, which rely on shared secrets. + Args: + shared_secret (str): The shared secret used to decode the token. + algorithm (str, optional): The expected signing algorithm. Defaults to "HS256". + """ + def __init__(self, shared_secret, algorithm="HS256"): + self.algorithm = algorithm + self._shared_secret = shared_secret + + # pylint: disable=W0613 + def get_key(self, key_id=None): + """ + Obtain the key for this algorithm. + :param key_id: The identifier of the key. + :return: The JWK key. + """ + return self._shared_secret + + +class AsymmetricJwtAlgorithm: + """Verifier for RSA signatures, which rely on public key certificates. + Args: + jwks_url (str): The url where the JWK set is located. + algorithm (str, optional): The expected signing algorithm. Defaults to "RS256". + """ + def __init__(self, jwks_url, algorithm="RS256"): + self.algorithm = algorithm + self._fetcher = JwksFetcher(jwks_url) + + def get_key(self, key_id=None): + """ + Obtain the key for this algorithm. + :param key_id: The identifier of the key. + :return: The JWK key. + """ + return self._fetcher.get_key(key_id) + + +class TokenValidationError(Exception): + """ + Error thrown when the token cannot be validated + """ + + +class JwksFetcher: + """Class that fetches and holds a JSON web key set. + This class makes use of an in-memory cache. For it to work properly, define this instance once and re-use it. + Args: + jwks_url (str): The url where the JWK set is located. + cache_ttl (str, optional): The lifetime of the JWK set cache in seconds. Defaults to 600 seconds. + """ + CACHE_TTL = 600 # 10 min cache lifetime + + def __init__(self, jwks_url, cache_ttl=CACHE_TTL): + self._jwks_url = jwks_url + self._http = urllib3.PoolManager() + self._cache_value = {} + self._cache_date = 0 + self._cache_ttl = cache_ttl + self._cache_is_fresh = False + + def _fetch_jwks(self, force=False): + """Attempts to obtain the JWK set from the cache, as long as it's still valid. + When not, it will perform a network request to the jwks_url to obtain a fresh result + and update the cache value with it. + Args: + force (bool, optional): whether to ignore the cache and force a network request or not. Defaults to False. + """ + has_expired = self._cache_date + self._cache_ttl < time.time() + + if not force and not has_expired: + # Return from cache + self._cache_is_fresh = False + return self._cache_value + + # Invalidate cache and fetch fresh data + self._cache_value = {} + response = self._http.request('GET', self._jwks_url) + + if response.status == 200: + # Update cache + jwks = json.loads(response.data.decode('utf-8')) + self._cache_value = self._parse_jwks(jwks) + self._cache_is_fresh = True + self._cache_date = time.time() + return self._cache_value + + @staticmethod + def _parse_jwks(jwks): + """Converts a JWK string representation into a binary certificate in PEM format. + """ + keys = {} + + for key in jwks['keys']: + keys[key["kid"]] = key + return keys + + def get_key(self, key_id): + """Obtains the JWK associated with the given key id. + Args: + key_id (str): The id of the key to fetch. + Returns: + the JWK associated with the given key id. + + Raises: + TokenValidationError: when a key with that id cannot be found + """ + keys = self._fetch_jwks() + + if keys and key_id in keys: + return keys[key_id] + + if not self._cache_is_fresh: + keys = self._fetch_jwks(force=True) + if keys and key_id in keys: + return keys[key_id] + raise TokenValidationError(f"RSA Public Key with ID {key_id} was not found.") diff --git a/opendc-web/opendc-web-api/opendc/database.py b/opendc-web/opendc-web-api/opendc/database.py new file mode 100644 index 00000000..dd6367f2 --- /dev/null +++ b/opendc-web/opendc-web-api/opendc/database.py @@ -0,0 +1,97 @@ +# Copyright (c) 2021 AtLarge Research +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import urllib.parse + +from pymongo import MongoClient, ReturnDocument + +DATETIME_STRING_FORMAT = '%Y-%m-%dT%H:%M:%S' +CONNECTION_POOL = None + + +class Database: + """Object holding functionality for database access.""" + def __init__(self, db=None): + """Initializes the database connection.""" + self.opendc_db = db + + @classmethod + def from_credentials(cls, user, password, database, host): + """ + Construct a database instance from the specified credentials. + :param user: The username to connect with. + :param password: The password to connect with. + :param database: The database name to connect to. + :param host: The host to connect to. + :return: The database instance. + """ + user = urllib.parse.quote_plus(user) + password = urllib.parse.quote_plus(password) + database = urllib.parse.quote_plus(database) + host = urllib.parse.quote_plus(host) + + client = MongoClient('mongodb://%s:%s@%s/default_db?authSource=%s' % (user, password, host, database)) + return cls(client.opendc) + + def fetch_one(self, query, collection): + """Uses existing mongo connection to return a single (the first) document in a collection matching the given + query as a JSON object. + + The query needs to be in json format, i.e.: `{'name': prefab_name}`. + """ + return getattr(self.opendc_db, collection).find_one(query) + + def fetch_all(self, query, collection): + """Uses existing mongo connection to return all documents matching a given query, as a list of JSON objects. + + The query needs to be in json format, i.e.: `{'name': prefab_name}`. + """ + cursor = getattr(self.opendc_db, collection).find(query) + return list(cursor) + + def insert(self, obj, collection): + """Updates an existing object.""" + bson = getattr(self.opendc_db, collection).insert(obj) + + return bson + + def update(self, _id, obj, collection): + """Updates an existing object.""" + return getattr(self.opendc_db, collection).update({'_id': _id}, obj) + + def fetch_and_update(self, query, update, collection): + """Updates an existing object.""" + return getattr(self.opendc_db, collection).find_one_and_update(query, + update, + return_document=ReturnDocument.AFTER) + + def delete_one(self, query, collection): + """Deletes one object matching the given query. + + The query needs to be in json format, i.e.: `{'name': prefab_name}`. + """ + getattr(self.opendc_db, collection).delete_one(query) + + def delete_all(self, query, collection): + """Deletes all objects matching the given query. + + The query needs to be in json format, i.e.: `{'name': prefab_name}`. + """ + getattr(self.opendc_db, collection).delete_many(query) diff --git a/opendc-web/opendc-web-api/opendc/exts.py b/opendc-web/opendc-web-api/opendc/exts.py new file mode 100644 index 00000000..3ee8babb --- /dev/null +++ b/opendc-web/opendc-web-api/opendc/exts.py @@ -0,0 +1,91 @@ +import os +from functools import wraps + +from flask import g, _request_ctx_stack +from jose import jwt +from werkzeug.local import LocalProxy + +from opendc.database import Database +from opendc.auth import AuthContext, AsymmetricJwtAlgorithm, get_token, AuthError + + +def get_db(): + """ + Return the configured database instance for the application. + """ + _db = getattr(g, 'db', None) + if _db is None: + _db = Database.from_credentials(user=os.environ['OPENDC_DB_USERNAME'], + password=os.environ['OPENDC_DB_PASSWORD'], + database=os.environ['OPENDC_DB'], + host=os.environ.get('OPENDC_DB_HOST', 'localhost')) + g.db = _db + return _db + + +db = LocalProxy(get_db) + + +def get_auth_context(): + """ + Return the configured auth context for the application. + """ + _auth_context = getattr(g, 'auth_context', None) + if _auth_context is None: + _auth_context = AuthContext( + alg=AsymmetricJwtAlgorithm(jwks_url=f"https://{os.environ['AUTH0_DOMAIN']}/.well-known/jwks.json"), + issuer=f"https://{os.environ['AUTH0_DOMAIN']}/", + audience=os.environ['AUTH0_AUDIENCE']) + g.auth_context = _auth_context + return _auth_context + + +auth_context = LocalProxy(get_auth_context) + + +def requires_auth(f): + """Decorator to determine if the Access Token is valid. + """ + @wraps(f) + def decorated(*args, **kwargs): + token = get_token() + payload = auth_context.validate(token) + _request_ctx_stack.top.current_user = payload + return f(*args, **kwargs) + + return decorated + + +current_user = LocalProxy(lambda: getattr(_request_ctx_stack.top, 'current_user', None)) + + +def has_scope(required_scope): + """Determines if the required scope is present in the Access Token + Args: + required_scope (str): The scope required to access the resource + """ + token = get_token() + unverified_claims = jwt.get_unverified_claims(token) + if unverified_claims.get("scope"): + token_scopes = unverified_claims["scope"].split() + for token_scope in token_scopes: + if token_scope == required_scope: + return True + return False + + +def requires_scope(required_scope): + """Determines if the required scope is present in the Access Token + Args: + required_scope (str): The scope required to access the resource + """ + def decorator(f): + @wraps(f) + def decorated(*args, **kwargs): + if not has_scope(required_scope): + raise AuthError({"code": "Unauthorized", "description": "You don't have access to this resource"}, 403) + return f(*args, **kwargs) + + return decorated + + return decorator diff --git a/opendc-web/opendc-web-api/opendc/models/model.py b/opendc-web/opendc-web-api/opendc/models/model.py index f9dfc9ad..28299453 100644 --- a/opendc-web/opendc-web-api/opendc/models/model.py +++ b/opendc-web/opendc-web-api/opendc/models/model.py @@ -1,8 +1,7 @@ from bson.objectid import ObjectId +from werkzeug.exceptions import NotFound -from opendc.util.database import DB -from opendc.util.exceptions import ClientError -from opendc.util.rest import Response +from opendc.exts import db class Model: @@ -15,15 +14,13 @@ class Model: """Fetches the document with given ID from the collection.""" if isinstance(_id, str) and len(_id) == 24: _id = ObjectId(_id) - elif not isinstance(_id, ObjectId): - return cls(None) - return cls(DB.fetch_one({'_id': _id}, cls.collection_name)) + return cls(db.fetch_one({'_id': _id}, cls.collection_name)) @classmethod def get_all(cls): """Fetches all documents from the collection.""" - return cls(DB.fetch_all({}, cls.collection_name)) + return cls(db.fetch_all({}, cls.collection_name)) def __init__(self, obj): self.obj = obj @@ -35,7 +32,7 @@ class Model: def check_exists(self): """Raises an error if the enclosed object does not exist.""" if self.obj is None: - raise ClientError(Response(404, 'Not found.')) + raise NotFound('Entity not found.') def set_property(self, key, value): """Sets the given property on the enclosed object, with support for simple nested access.""" @@ -48,11 +45,11 @@ class Model: def insert(self): """Inserts the enclosed object and generates a UUID for it.""" self.obj['_id'] = ObjectId() - DB.insert(self.obj, self.collection_name) + db.insert(self.obj, self.collection_name) def update(self): """Updates the enclosed object and updates the internal reference to the newly inserted object.""" - DB.update(self.get_id(), self.obj, self.collection_name) + db.update(self.get_id(), self.obj, self.collection_name) def delete(self): """Deletes the enclosed object in the database, if it existed.""" @@ -60,5 +57,5 @@ class Model: return None old_object = self.obj.copy() - DB.delete_one({'_id': self.get_id()}, self.collection_name) + db.delete_one({'_id': self.get_id()}, self.collection_name) return old_object diff --git a/opendc-web/opendc-web-api/opendc/models/portfolio.py b/opendc-web/opendc-web-api/opendc/models/portfolio.py index 32961b63..eb016947 100644 --- a/opendc-web/opendc-web-api/opendc/models/portfolio.py +++ b/opendc-web/opendc-web-api/opendc/models/portfolio.py @@ -1,7 +1,28 @@ +from bson import ObjectId +from marshmallow import Schema, fields + +from opendc.exts import db +from opendc.models.project import Project from opendc.models.model import Model -from opendc.models.user import User -from opendc.util.exceptions import ClientError -from opendc.util.rest import Response + + +class TargetSchema(Schema): + """ + Schema representing a target. + """ + enabledMetrics = fields.List(fields.String()) + repeatsPerScenario = fields.Integer(required=True) + + +class PortfolioSchema(Schema): + """ + Schema representing a portfolio. + """ + _id = fields.String(dump_only=True) + projectId = fields.String() + name = fields.String(required=True) + scenarioIds = fields.List(fields.String()) + targets = fields.Nested(TargetSchema) class Portfolio(Model): @@ -9,16 +30,18 @@ class Portfolio(Model): collection_name = 'portfolios' - def check_user_access(self, google_id, edit_access): - """Raises an error if the user with given [google_id] has insufficient access. + def check_user_access(self, user_id, edit_access): + """Raises an error if the user with given [user_id] has insufficient access. Checks access on the parent project. - :param google_id: The Google ID of the user. + :param user_id: The User ID of the user. :param edit_access: True when edit access should be checked, otherwise view access. """ - user = User.from_google_id(google_id) - authorizations = list( - filter(lambda x: str(x['projectId']) == str(self.obj['projectId']), user.obj['authorizations'])) - if len(authorizations) == 0 or (edit_access and authorizations[0]['authorizationLevel'] == 'VIEW'): - raise ClientError(Response(403, 'Forbidden from retrieving/editing portfolio.')) + project = Project.from_id(self.obj['projectId']) + project.check_user_access(user_id, edit_access) + + @classmethod + def get_for_project(cls, project_id): + """Get all portfolios for the specified project id.""" + return db.fetch_all({'projectId': ObjectId(project_id)}, cls.collection_name) diff --git a/opendc-web/opendc-web-api/opendc/models/prefab.py b/opendc-web/opendc-web-api/opendc/models/prefab.py index edf1d4c4..5e4b81dc 100644 --- a/opendc-web/opendc-web-api/opendc/models/prefab.py +++ b/opendc-web/opendc-web-api/opendc/models/prefab.py @@ -1,28 +1,31 @@ +from marshmallow import Schema, fields +from werkzeug.exceptions import Forbidden + +from opendc.models.topology import ObjectSchema from opendc.models.model import Model -from opendc.models.user import User -from opendc.util.exceptions import ClientError -from opendc.util.rest import Response + + +class PrefabSchema(Schema): + """ + Schema for a Prefab. + """ + _id = fields.String(dump_only=True) + authorId = fields.String(dump_only=True) + name = fields.String(required=True) + datetimeCreated = fields.DateTime() + datetimeLastEdited = fields.DateTime() + rack = fields.Nested(ObjectSchema) class Prefab(Model): - """Model representing a Project.""" + """Model representing a Prefab.""" collection_name = 'prefabs' - def check_user_access(self, google_id): - """Raises an error if the user with given [google_id] has insufficient access to view this prefab. + def check_user_access(self, user_id): + """Raises an error if the user with given [user_id] has insufficient access to view this prefab. - :param google_id: The Google ID of the user. + :param user_id: The user ID of the user. """ - user = User.from_google_id(google_id) - - # TODO(Jacob) add special handling for OpenDC-provided prefabs - - #try: - - print(self.obj) - if self.obj['authorId'] != user.get_id() and self.obj['visibility'] == "private": - raise ClientError(Response(403, "Forbidden from retrieving prefab.")) - #except KeyError: - # OpenDC-authored objects don't necessarily have an authorId - # return + if self.obj['authorId'] != user_id and self.obj['visibility'] == "private": + raise Forbidden("Forbidden from retrieving prefab.") diff --git a/opendc-web/opendc-web-api/opendc/models/project.py b/opendc-web/opendc-web-api/opendc/models/project.py index b57e9f77..f2b3b564 100644 --- a/opendc-web/opendc-web-api/opendc/models/project.py +++ b/opendc-web/opendc-web-api/opendc/models/project.py @@ -1,8 +1,29 @@ +from marshmallow import Schema, fields, validate +from werkzeug.exceptions import Forbidden + from opendc.models.model import Model -from opendc.models.user import User -from opendc.util.database import DB -from opendc.util.exceptions import ClientError -from opendc.util.rest import Response +from opendc.exts import db + + +class ProjectAuthorizations(Schema): + """ + Schema representing a project authorization. + """ + userId = fields.String(required=True) + level = fields.String(required=True, validate=validate.OneOf(["VIEW", "EDIT", "OWN"])) + + +class ProjectSchema(Schema): + """ + Schema representing a Project. + """ + _id = fields.String(dump_only=True) + name = fields.String(required=True) + datetimeCreated = fields.DateTime() + datetimeLastEdited = fields.DateTime() + topologyIds = fields.List(fields.String()) + portfolioIds = fields.List(fields.String()) + authorizations = fields.List(fields.Nested(ProjectAuthorizations)) class Project(Model): @@ -10,22 +31,18 @@ class Project(Model): collection_name = 'projects' - def check_user_access(self, google_id, edit_access): - """Raises an error if the user with given [google_id] has insufficient access. + def check_user_access(self, user_id, edit_access): + """Raises an error if the user with given [user_id] has insufficient access. - :param google_id: The Google ID of the user. + :param user_id: The User ID of the user. :param edit_access: True when edit access should be checked, otherwise view access. """ - user = User.from_google_id(google_id) - authorizations = list(filter(lambda x: str(x['projectId']) == str(self.get_id()), - user.obj['authorizations'])) - if len(authorizations) == 0 or (edit_access and authorizations[0]['authorizationLevel'] == 'VIEW'): - raise ClientError(Response(403, "Forbidden from retrieving project.")) - - def get_all_authorizations(self): - """Get all user IDs having access to this project.""" - return [ - str(user['_id']) for user in DB.fetch_all({'authorizations': { - 'projectId': self.obj['_id'] - }}, User.collection_name) - ] + for authorization in self.obj['authorizations']: + if user_id == authorization['userId'] and authorization['level'] != 'VIEW' or not edit_access: + return + raise Forbidden("Forbidden from retrieving project.") + + @classmethod + def get_for_user(cls, user_id): + """Get all projects for the specified user id.""" + return db.fetch_all({'authorizations.userId': user_id}, Project.collection_name) diff --git a/opendc-web/opendc-web-api/opendc/models/scenario.py b/opendc-web/opendc-web-api/opendc/models/scenario.py index 8d53e408..47771e06 100644 --- a/opendc-web/opendc-web-api/opendc/models/scenario.py +++ b/opendc-web/opendc-web-api/opendc/models/scenario.py @@ -1,8 +1,56 @@ +from datetime import datetime + +from bson import ObjectId +from marshmallow import Schema, fields + +from opendc.exts import db from opendc.models.model import Model from opendc.models.portfolio import Portfolio -from opendc.models.user import User -from opendc.util.exceptions import ClientError -from opendc.util.rest import Response + + +class SimulationSchema(Schema): + """ + Simulation details. + """ + state = fields.String() + + +class TraceSchema(Schema): + """ + Schema for specifying the trace of a scenario. + """ + traceId = fields.String() + loadSamplingFraction = fields.Float() + + +class TopologySchema(Schema): + """ + Schema for topology specification for a scenario. + """ + topologyId = fields.String() + + +class OperationalSchema(Schema): + """ + Schema for the operational phenomena for a scenario. + """ + failuresEnabled = fields.Boolean() + performanceInterferenceEnabled = fields.Boolean() + schedulerName = fields.String() + + +class ScenarioSchema(Schema): + """ + Schema representing a scenario. + """ + _id = fields.String(dump_only=True) + portfolioId = fields.String() + name = fields.String(required=True) + trace = fields.Nested(TraceSchema) + topology = fields.Nested(TopologySchema) + operational = fields.Nested(OperationalSchema) + simulation = fields.Nested(SimulationSchema, dump_only=True) + results = fields.Dict(dump_only=True) class Scenario(Model): @@ -10,17 +58,36 @@ class Scenario(Model): collection_name = 'scenarios' - def check_user_access(self, google_id, edit_access): - """Raises an error if the user with given [google_id] has insufficient access. + def check_user_access(self, user_id, edit_access): + """Raises an error if the user with given [user_id] has insufficient access. Checks access on the parent project. - :param google_id: The Google ID of the user. + :param user_id: The User ID of the user. :param edit_access: True when edit access should be checked, otherwise view access. """ portfolio = Portfolio.from_id(self.obj['portfolioId']) - user = User.from_google_id(google_id) - authorizations = list( - filter(lambda x: str(x['projectId']) == str(portfolio.obj['projectId']), user.obj['authorizations'])) - if len(authorizations) == 0 or (edit_access and authorizations[0]['authorizationLevel'] == 'VIEW'): - raise ClientError(Response(403, 'Forbidden from retrieving/editing scenario.')) + portfolio.check_user_access(user_id, edit_access) + + @classmethod + def get_jobs(cls): + """Obtain the scenarios that have been queued. + """ + return cls(db.fetch_all({'simulation.state': 'QUEUED'}, cls.collection_name)) + + @classmethod + def get_for_portfolio(cls, portfolio_id): + """Get all scenarios for the specified portfolio id.""" + return db.fetch_all({'portfolioId': ObjectId(portfolio_id)}, cls.collection_name) + + def update_state(self, new_state, results=None): + """Atomically update the state of the Scenario. + """ + update = {'$set': {'simulation.state': new_state, 'simulation.heartbeat': datetime.now()}} + if results: + update['$set']['results'] = results + return db.fetch_and_update( + query={'_id': self.obj['_id'], 'simulation.state': self.obj['simulation']['state']}, + update=update, + collection=self.collection_name + ) diff --git a/opendc-web/opendc-web-api/opendc/models/topology.py b/opendc-web/opendc-web-api/opendc/models/topology.py index cb4c4bab..44994818 100644 --- a/opendc-web/opendc-web-api/opendc/models/topology.py +++ b/opendc-web/opendc-web-api/opendc/models/topology.py @@ -1,7 +1,89 @@ +from bson import ObjectId +from marshmallow import Schema, fields + +from opendc.exts import db +from opendc.models.project import Project from opendc.models.model import Model -from opendc.models.user import User -from opendc.util.exceptions import ClientError -from opendc.util.rest import Response + + +class MemorySchema(Schema): + """ + Schema representing a memory unit. + """ + _id = fields.String() + name = fields.String() + speedMbPerS = fields.Integer() + sizeMb = fields.Integer() + energyConsumptionW = fields.Integer() + + +class PuSchema(Schema): + """ + Schema representing a processing unit. + """ + _id = fields.String() + name = fields.String() + clockRateMhz = fields.Integer() + numberOfCores = fields.Integer() + energyConsumptionW = fields.Integer() + + +class MachineSchema(Schema): + """ + Schema representing a machine. + """ + _id = fields.String() + position = fields.Integer() + cpus = fields.List(fields.Nested(PuSchema)) + gpus = fields.List(fields.Nested(PuSchema)) + memories = fields.List(fields.Nested(MemorySchema)) + storages = fields.List(fields.Nested(MemorySchema)) + rackId = fields.String() + + +class ObjectSchema(Schema): + """ + Schema representing a room object. + """ + _id = fields.String() + name = fields.String() + capacity = fields.Integer() + powerCapacityW = fields.Integer() + machines = fields.List(fields.Nested(MachineSchema)) + tileId = fields.String() + + +class TileSchema(Schema): + """ + Schema representing a room tile. + """ + _id = fields.String() + topologyId = fields.String() + positionX = fields.Integer() + positionY = fields.Integer() + rack = fields.Nested(ObjectSchema) + roomId = fields.String() + + +class RoomSchema(Schema): + """ + Schema representing a room. + """ + _id = fields.String() + name = fields.String(required=True) + topologyId = fields.String() + tiles = fields.List(fields.Nested(TileSchema), required=True) + + +class TopologySchema(Schema): + """ + Schema representing a datacenter topology. + """ + _id = fields.String(dump_only=True) + projectId = fields.String() + name = fields.String(required=True) + rooms = fields.List(fields.Nested(RoomSchema), required=True) + datetimeLastEdited = fields.DateTime() class Topology(Model): @@ -9,19 +91,18 @@ class Topology(Model): collection_name = 'topologies' - def check_user_access(self, google_id, edit_access): - """Raises an error if the user with given [google_id] has insufficient access. + def check_user_access(self, user_id, edit_access): + """Raises an error if the user with given [user_id] has insufficient access. Checks access on the parent project. - :param google_id: The Google ID of the user. + :param user_id: The User ID of the user. :param edit_access: True when edit access should be checked, otherwise view access. """ - user = User.from_google_id(google_id) - if 'projectId' not in self.obj: - raise ClientError(Response(400, 'Missing projectId in topology.')) - - authorizations = list( - filter(lambda x: str(x['projectId']) == str(self.obj['projectId']), user.obj['authorizations'])) - if len(authorizations) == 0 or (edit_access and authorizations[0]['authorizationLevel'] == 'VIEW'): - raise ClientError(Response(403, 'Forbidden from retrieving topology.')) + project = Project.from_id(self.obj['projectId']) + project.check_user_access(user_id, edit_access) + + @classmethod + def get_for_project(cls, project_id): + """Get all topologies for the specified project id.""" + return db.fetch_all({'projectId': ObjectId(project_id)}, cls.collection_name) diff --git a/opendc-web/opendc-web-api/opendc/models/trace.py b/opendc-web/opendc-web-api/opendc/models/trace.py index 2f6e4926..69287f29 100644 --- a/opendc-web/opendc-web-api/opendc/models/trace.py +++ b/opendc-web/opendc-web-api/opendc/models/trace.py @@ -1,6 +1,15 @@ +from marshmallow import Schema, fields + from opendc.models.model import Model +class TraceSchema(Schema): + """Schema for a Trace.""" + _id = fields.String(dump_only=True) + name = fields.String() + type = fields.String() + + class Trace(Model): """Model representing a Trace.""" diff --git a/opendc-web/opendc-web-api/opendc/models/user.py b/opendc-web/opendc-web-api/opendc/models/user.py deleted file mode 100644 index 8e8ff945..00000000 --- a/opendc-web/opendc-web-api/opendc/models/user.py +++ /dev/null @@ -1,36 +0,0 @@ -from opendc.models.model import Model -from opendc.util.database import DB -from opendc.util.exceptions import ClientError -from opendc.util.rest import Response - - -class User(Model): - """Model representing a User.""" - - collection_name = 'users' - - @classmethod - def from_email(cls, email): - """Fetches the user with given email from the collection.""" - return User(DB.fetch_one({'email': email}, User.collection_name)) - - @classmethod - def from_google_id(cls, google_id): - """Fetches the user with given Google ID from the collection.""" - return User(DB.fetch_one({'googleId': google_id}, User.collection_name)) - - def check_correct_user(self, request_google_id): - """Raises an error if a user tries to modify another user. - - :param request_google_id: - """ - if request_google_id is not None and self.obj['googleId'] != request_google_id: - raise ClientError(Response(403, f'Forbidden from editing user with ID {self.obj["_id"]}.')) - - def check_already_exists(self): - """Checks if the user already exists in the database.""" - - existing_user = DB.fetch_one({'googleId': self.obj['googleId']}, self.collection_name) - - if existing_user is not None: - raise ClientError(Response(409, 'User already exists.')) diff --git a/opendc-web/opendc-web-api/opendc/util.py b/opendc-web/opendc-web-api/opendc/util.py new file mode 100644 index 00000000..e7dc07a4 --- /dev/null +++ b/opendc-web/opendc-web-api/opendc/util.py @@ -0,0 +1,32 @@ +# Copyright (c) 2021 AtLarge Research +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import flask +from bson.objectid import ObjectId + + +class JSONEncoder(flask.json.JSONEncoder): + """ + A customized JSON encoder to handle unsupported types. + """ + def default(self, o): + if isinstance(o, ObjectId): + return str(o) + return flask.json.JSONEncoder.default(self, o) diff --git a/opendc-web/opendc-web-api/opendc/util/__init__.py b/opendc-web/opendc-web-api/opendc/util/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/opendc-web/opendc-web-api/opendc/util/__init__.py +++ /dev/null diff --git a/opendc-web/opendc-web-api/opendc/util/database.py b/opendc-web/opendc-web-api/opendc/util/database.py deleted file mode 100644 index dd26533d..00000000 --- a/opendc-web/opendc-web-api/opendc/util/database.py +++ /dev/null @@ -1,77 +0,0 @@ -import urllib.parse -from datetime import datetime - -from pymongo import MongoClient - -DATETIME_STRING_FORMAT = '%Y-%m-%dT%H:%M:%S' -CONNECTION_POOL = None - - -class Database: - """Object holding functionality for database access.""" - def __init__(self): - self.opendc_db = None - - def initialize_database(self, user, password, database, host): - """Initializes the database connection.""" - - user = urllib.parse.quote_plus(user) - password = urllib.parse.quote_plus(password) - database = urllib.parse.quote_plus(database) - host = urllib.parse.quote_plus(host) - - client = MongoClient('mongodb://%s:%s@%s/default_db?authSource=%s' % (user, password, host, database)) - self.opendc_db = client.opendc - - def fetch_one(self, query, collection): - """Uses existing mongo connection to return a single (the first) document in a collection matching the given - query as a JSON object. - - The query needs to be in json format, i.e.: `{'name': prefab_name}`. - """ - return getattr(self.opendc_db, collection).find_one(query) - - def fetch_all(self, query, collection): - """Uses existing mongo connection to return all documents matching a given query, as a list of JSON objects. - - The query needs to be in json format, i.e.: `{'name': prefab_name}`. - """ - cursor = getattr(self.opendc_db, collection).find(query) - return list(cursor) - - def insert(self, obj, collection): - """Updates an existing object.""" - bson = getattr(self.opendc_db, collection).insert(obj) - - return bson - - def update(self, _id, obj, collection): - """Updates an existing object.""" - return getattr(self.opendc_db, collection).update({'_id': _id}, obj) - - def delete_one(self, query, collection): - """Deletes one object matching the given query. - - The query needs to be in json format, i.e.: `{'name': prefab_name}`. - """ - getattr(self.opendc_db, collection).delete_one(query) - - def delete_all(self, query, collection): - """Deletes all objects matching the given query. - - The query needs to be in json format, i.e.: `{'name': prefab_name}`. - """ - getattr(self.opendc_db, collection).delete_many(query) - - @staticmethod - def datetime_to_string(datetime_to_convert): - """Return a database-compatible string representation of the given datetime object.""" - return datetime_to_convert.strftime(DATETIME_STRING_FORMAT) - - @staticmethod - def string_to_datetime(string_to_convert): - """Return a datetime corresponding to the given string representation.""" - return datetime.strptime(string_to_convert, DATETIME_STRING_FORMAT) - - -DB = Database() diff --git a/opendc-web/opendc-web-api/opendc/util/exceptions.py b/opendc-web/opendc-web-api/opendc/util/exceptions.py deleted file mode 100644 index 7724a407..00000000 --- a/opendc-web/opendc-web-api/opendc/util/exceptions.py +++ /dev/null @@ -1,64 +0,0 @@ -class RequestInitializationError(Exception): - """Raised when a Request cannot successfully be initialized""" - - -class UnimplementedEndpointError(RequestInitializationError): - """Raised when a Request path does not point to a module.""" - - -class MissingRequestParameterError(RequestInitializationError): - """Raised when a Request does not contain one or more required parameters.""" - - -class UnsupportedMethodError(RequestInitializationError): - """Raised when a Request does not use a supported REST method. - - The method must be in all-caps, supported by REST, and implemented by the module. - """ - - -class AuthorizationTokenError(RequestInitializationError): - """Raised when an authorization token is not correctly verified.""" - - -class ForeignKeyError(Exception): - """Raised when a foreign key constraint is not met.""" - - -class RowNotFoundError(Exception): - """Raised when a database row is not found.""" - def __init__(self, table_name): - super(RowNotFoundError, self).__init__('Row in `{}` table not found.'.format(table_name)) - - self.table_name = table_name - - -class ParameterError(Exception): - """Raised when a parameter is either missing or incorrectly typed.""" - - -class IncorrectParameterError(ParameterError): - """Raised when a parameter is of the wrong type.""" - def __init__(self, parameter_name, parameter_location): - super(IncorrectParameterError, - self).__init__('Incorrectly typed `{}` {} parameter.'.format(parameter_name, parameter_location)) - - self.parameter_name = parameter_name - self.parameter_location = parameter_location - - -class MissingParameterError(ParameterError): - """Raised when a parameter is missing.""" - def __init__(self, parameter_name, parameter_location): - super(MissingParameterError, - self).__init__('Missing required `{}` {} parameter.'.format(parameter_name, parameter_location)) - - self.parameter_name = parameter_name - self.parameter_location = parameter_location - - -class ClientError(Exception): - """Raised when a 4xx response is to be returned.""" - def __init__(self, response): - super(ClientError, self).__init__(str(response)) - self.response = response diff --git a/opendc-web/opendc-web-api/opendc/util/json.py b/opendc-web/opendc-web-api/opendc/util/json.py deleted file mode 100644 index 2ef4f965..00000000 --- a/opendc-web/opendc-web-api/opendc/util/json.py +++ /dev/null @@ -1,12 +0,0 @@ -import flask -from bson.objectid import ObjectId - - -class JSONEncoder(flask.json.JSONEncoder): - """ - A customized JSON encoder to handle unsupported types. - """ - def default(self, o): - if isinstance(o, ObjectId): - return str(o) - return flask.json.JSONEncoder.default(self, o) diff --git a/opendc-web/opendc-web-api/opendc/util/parameter_checker.py b/opendc-web/opendc-web-api/opendc/util/parameter_checker.py deleted file mode 100644 index 14dd1dc0..00000000 --- a/opendc-web/opendc-web-api/opendc/util/parameter_checker.py +++ /dev/null @@ -1,85 +0,0 @@ -from opendc.util import exceptions -from opendc.util.database import Database - - -def _missing_parameter(params_required, params_actual, parent=''): - """Recursively search for the first missing parameter.""" - - for param_name in params_required: - - if param_name not in params_actual: - return '{}.{}'.format(parent, param_name) - - param_required = params_required.get(param_name) - param_actual = params_actual.get(param_name) - - if isinstance(param_required, dict): - - param_missing = _missing_parameter(param_required, param_actual, param_name) - - if param_missing is not None: - return '{}.{}'.format(parent, param_missing) - - return None - - -def _incorrect_parameter(params_required, params_actual, parent=''): - """Recursively make sure each parameter is of the correct type.""" - - for param_name in params_required: - - param_required = params_required.get(param_name) - param_actual = params_actual.get(param_name) - - if isinstance(param_required, dict): - - param_incorrect = _incorrect_parameter(param_required, param_actual, param_name) - - if param_incorrect is not None: - return '{}.{}'.format(parent, param_incorrect) - - else: - - if param_required == 'datetime': - try: - Database.string_to_datetime(param_actual) - except: - return '{}.{}'.format(parent, param_name) - - type_pairs = [ - ('int', (int,)), - ('float', (float, int)), - ('bool', (bool,)), - ('string', (str, int)), - ('list', (list,)), - ] - - for str_type, actual_types in type_pairs: - if param_required == str_type and all(not isinstance(param_actual, t) - for t in actual_types): - return '{}.{}'.format(parent, param_name) - - return None - - -def _format_parameter(parameter): - """Format the output of a parameter check.""" - - parts = parameter.split('.') - inner = ['["{}"]'.format(x) for x in parts[2:]] - return parts[1] + ''.join(inner) - - -def check(request, **kwargs): - """Check if all required parameters are there.""" - - for location, params_required in kwargs.items(): - params_actual = getattr(request, 'params_{}'.format(location)) - - missing_parameter = _missing_parameter(params_required, params_actual) - if missing_parameter is not None: - raise exceptions.MissingParameterError(_format_parameter(missing_parameter), location) - - incorrect_parameter = _incorrect_parameter(params_required, params_actual) - if incorrect_parameter is not None: - raise exceptions.IncorrectParameterError(_format_parameter(incorrect_parameter), location) diff --git a/opendc-web/opendc-web-api/opendc/util/path_parser.py b/opendc-web/opendc-web-api/opendc/util/path_parser.py deleted file mode 100644 index c8452f20..00000000 --- a/opendc-web/opendc-web-api/opendc/util/path_parser.py +++ /dev/null @@ -1,36 +0,0 @@ -import json -import os - - -def parse(version, endpoint_path): - """Map an HTTP endpoint path to an API path""" - - # Get possible paths - with open(os.path.join(os.path.dirname(__file__), '..', 'api', '{}', 'paths.json').format(version)) as paths_file: - paths = json.load(paths_file) - - # Find API path that matches endpoint_path - endpoint_path_parts = endpoint_path.strip('/').split('/') - paths_parts = [x.strip('/').split('/') for x in paths if len(x.strip('/').split('/')) == len(endpoint_path_parts)] - path = None - - for path_parts in paths_parts: - found = True - for (endpoint_part, part) in zip(endpoint_path_parts, path_parts): - if not part.startswith('{') and endpoint_part != part: - found = False - break - if found: - path = path_parts - - if path is None: - return None - - # Extract path parameters - parameters = {} - - for (name, value) in zip(path, endpoint_path_parts): - if name.startswith('{'): - parameters[name.strip('{}')] = value - - return '{}/{}'.format(version, '/'.join(path)), parameters diff --git a/opendc-web/opendc-web-api/opendc/util/rest.py b/opendc-web/opendc-web-api/opendc/util/rest.py deleted file mode 100644 index c9e98295..00000000 --- a/opendc-web/opendc-web-api/opendc/util/rest.py +++ /dev/null @@ -1,141 +0,0 @@ -import importlib -import json -import os - -from oauth2client import client, crypt - -from opendc.util import exceptions, parameter_checker -from opendc.util.exceptions import ClientError - - -class Request: - """WebSocket message to REST request mapping.""" - def __init__(self, message=None): - """"Initialize a Request from a socket message.""" - - # Get the Request parameters from the message - - if message is None: - return - - try: - self.message = message - - self.id = message['id'] - - self.path = message['path'] - self.method = message['method'] - - self.params_body = message['parameters']['body'] - self.params_path = message['parameters']['path'] - self.params_query = message['parameters']['query'] - - self.token = message['token'] - - except KeyError as exception: - raise exceptions.MissingRequestParameterError(exception) - - # Parse the path and import the appropriate module - - try: - self.path = message['path'].strip('/') - - module_base = 'opendc.api.{}.endpoint' - module_path = self.path.replace('{', '').replace('}', '').replace('/', '.') - - self.module = importlib.import_module(module_base.format(module_path)) - except ImportError as e: - print(e) - raise exceptions.UnimplementedEndpointError('Unimplemented endpoint: {}.'.format(self.path)) - - # Check the method - - if self.method not in ['POST', 'GET', 'PUT', 'PATCH', 'DELETE']: - raise exceptions.UnsupportedMethodError('Non-rest method: {}'.format(self.method)) - - if not hasattr(self.module, self.method): - raise exceptions.UnsupportedMethodError('Unimplemented method at endpoint {}: {}'.format( - self.path, self.method)) - - # Verify the user - - if "OPENDC_FLASK_TESTING" in os.environ: - self.google_id = 'test' - return - - try: - self.google_id = self._verify_token(self.token) - except crypt.AppIdentityError as e: - raise exceptions.AuthorizationTokenError(e) - - def check_required_parameters(self, **kwargs): - """Raise an error if a parameter is missing or of the wrong type.""" - - try: - parameter_checker.check(self, **kwargs) - except exceptions.ParameterError as e: - raise ClientError(Response(400, str(e))) - - def process(self): - """Process the Request and return a Response.""" - - method = getattr(self.module, self.method) - - try: - response = method(self) - except ClientError as e: - e.response.id = self.id - return e.response - - response.id = self.id - - return response - - def to_JSON(self): - """Return a JSON representation of this Request""" - - self.message['id'] = 0 - self.message['token'] = None - - return json.dumps(self.message) - - @staticmethod - def _verify_token(token): - """Return the ID of the signed-in user. - - Or throw an Exception if the token is invalid. - """ - - try: - id_info = client.verify_id_token(token, os.environ['OPENDC_OAUTH_CLIENT_ID']) - except Exception as e: - print(e) - raise crypt.AppIdentityError('Exception caught trying to verify ID token: {}'.format(e)) - - if id_info['aud'] != os.environ['OPENDC_OAUTH_CLIENT_ID']: - raise crypt.AppIdentityError('Unrecognized client.') - - if id_info['iss'] not in ['accounts.google.com', 'https://accounts.google.com']: - raise crypt.AppIdentityError('Wrong issuer.') - - return id_info['sub'] - - -class Response: - """Response to websocket mapping""" - def __init__(self, status_code, status_description, content=None): - """Initialize a new Response.""" - - self.id = 0 - self.status = {'code': status_code, 'description': status_description} - self.content = content - - def to_JSON(self): - """"Return a JSON representation of this Response""" - - data = {'id': self.id, 'status': self.status} - - if self.content is not None: - data['content'] = self.content - - return json.dumps(data, default=str) diff --git a/opendc-web/opendc-web-api/requirements.txt b/opendc-web/opendc-web-api/requirements.txt index 146f1717..45eae12b 100644 --- a/opendc-web/opendc-web-api/requirements.txt +++ b/opendc-web/opendc-web-api/requirements.txt @@ -9,6 +9,8 @@ Flask==1.1.2 Flask-Compress==1.5.0 Flask-Cors==3.0.9 Flask-SocketIO==4.3.1 +flask-swagger-ui==3.36.0 +Flask-Restful==0.3.8 greenlet==0.4.17 httplib2==0.19.0 isort==4.3.21 @@ -16,6 +18,7 @@ itsdangerous==1.1.0 Jinja2==2.11.3 lazy-object-proxy==1.4.3 MarkupSafe==1.1.1 +marshmallow==3.12.1 mccabe==0.6.1 monotonic==1.5 more-itertools==8.6.0 @@ -33,13 +36,12 @@ pytest-cov==2.11.1 pytest-env==0.6.2 pytest-mock==3.2.0 python-dotenv==0.14.0 -python-engineio==3.13.2 -python-socketio==4.6.0 +python-jose==3.2.0 rsa==4.7 sentry-sdk==0.19.2 six==1.15.0 toml==0.10.2 -urllib3==1.26.4 +urllib3==1.26.5 wcwidth==0.2.5 Werkzeug==1.0.1 wrapt==1.12.1 diff --git a/opendc-web/opendc-web-api/static/index.html b/opendc-web/opendc-web-api/static/index.html deleted file mode 100644 index ac78cbfb..00000000 --- a/opendc-web/opendc-web-api/static/index.html +++ /dev/null @@ -1,22 +0,0 @@ -<script src="https://apis.google.com/js/platform.js" async defer></script> -<meta name="google-signin-client_id" content="561588943542-fq7065hk47qdf3lfsc50ebll4spi6u76.apps.googleusercontent.com"> -<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/socket.io/1.3.6/socket.io.min.js"></script> -<script> - function onSignIn(googleUser) { - document.getElementById('token').innerText = googleUser.getAuthResponse().id_token; - } -</script> -<script> - function signOut() { - var auth2 = gapi.auth2.getAuthInstance(); - auth2.signOut().then(function () { - console.log('User signed out.'); - }); - } -</script> - -<div class="g-signin2" data-onsuccess="onSignIn"></div> -<a href="#" onclick="signOut();">Sign out</a> - -<p>Your auth token:</p> -<p id="token" style="word-wrap:break-word;">Loading...</p>
\ No newline at end of file diff --git a/opendc-web/opendc-web-api/static/schema.yml b/opendc-web/opendc-web-api/static/schema.yml new file mode 100644 index 00000000..56cf58e7 --- /dev/null +++ b/opendc-web/opendc-web-api/static/schema.yml @@ -0,0 +1,1631 @@ +openapi: 3.0.0 +info: + version: 2.1.0 + title: OpenDC REST API v2 + description: OpenDC is an open-source datacenter simulator for education, featuring + real-time online collaboration, diverse simulation models, and detailed + performance feedback statistics. + license: + name: MIT + url: https://spdx.org/licenses/MIT + contact: + name: Support + url: https://opendc.org +servers: + - url: https://api.opendc.org/v2 +externalDocs: + description: OpenDC REST API v2 + url: https://api.opendc.com/v2/docs/ +security: + - auth0: + - openid +paths: + /projects: + get: + tags: + - projects + description: List Projects of the active user + responses: + "200": + description: Successfully + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + type: array + items: + $ref: "#/components/schemas/Project" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + post: + tags: + - projects + description: Add a Project. + requestBody: + content: + application/json: + schema: + properties: + name: + type: string + description: The new Project. + required: true + responses: + "200": + description: Successfully added Project. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Project" + "400": + description: Missing or incorrectly typed parameter. + content: + "application/json": + schema: + $ref: "#/components/schemas/Invalid" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "/projects/{projectId}": + get: + tags: + - projects + description: Get this Project. + parameters: + - name: projectId + in: path + description: Project's ID. + required: true + schema: + type: string + responses: + "200": + description: Successfully retrieved Project. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Project" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "403": + description: Forbidden from retrieving Project. + content: + "application/json": + schema: + $ref: "#/components/schemas/Forbidden" + "404": + description: Project not found + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + put: + tags: + - projects + description: Update this Project. + parameters: + - name: projectId + in: path + description: Project's ID. + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + properties: + project: + $ref: "#/components/schemas/Project" + description: Project's new properties. + required: true + responses: + "200": + description: Successfully updated Project. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Project" + "400": + description: Missing or incorrectly typed parameter. + content: + "application/json": + schema: + $ref: "#/components/schemas/Invalid" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "403": + description: Forbidden from updating Project. + content: + "application/json": + schema: + $ref: "#/components/schemas/Forbidden" + "404": + description: Project not found. + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + delete: + tags: + - projects + description: Delete this project. + parameters: + - name: projectId + in: path + description: Project's ID. + required: true + schema: + type: string + responses: + "200": + description: Successfully deleted Project. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Project" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "403": + description: Forbidden from deleting Project. + content: + "application/json": + schema: + $ref: "#/components/schemas/Forbidden" + "404": + description: Project not found. + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + "/projects/{projectId}/topologies": + get: + tags: + - projects + description: Get Project Topologies. + parameters: + - name: projectId + in: path + description: Project's ID. + required: true + schema: + type: string + responses: + "200": + description: Successfully retrieved Project Topologies. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + type: array + items: + $ref: "#/components/schemas/Topology" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "403": + description: Forbidden from retrieving Project. + content: + "application/json": + schema: + $ref: "#/components/schemas/Forbidden" + "404": + description: Project not found. + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + post: + tags: + - projects + description: Add a Topology. + parameters: + - name: projectId + in: path + description: Project's ID. + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + properties: + topology: + $ref: "#/components/schemas/Topology" + description: The new Topology. + required: true + responses: + "200": + description: Successfully added Topology. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Topology" + "400": + description: Missing or incorrectly typed parameter. + content: + "application/json": + schema: + $ref: "#/components/schemas/Invalid" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "404": + description: Project not found. + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + "/projects/{projectId}/portfolios": + get: + tags: + - projects + description: Get Project Portfolios. + parameters: + - name: projectId + in: path + description: Project's ID. + required: true + schema: + type: string + responses: + "200": + description: Successfully retrieved Project Portfolios. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + type: array + items: + $ref: "#/components/schemas/Portfolio" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "403": + description: Forbidden from retrieving Project. + content: + "application/json": + schema: + $ref: "#/components/schemas/Forbidden" + "404": + description: Project not found. + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + post: + tags: + - projects + description: Add a Portfolio. + parameters: + - name: projectId + in: path + description: Project's ID. + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + properties: + topology: + $ref: "#/components/schemas/Portfolio" + description: The new Portfolio. + required: true + responses: + "200": + description: Successfully added Portfolio. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Portfolio" + "400": + description: Missing or incorrectly typed parameter. + content: + "application/json": + schema: + $ref: "#/components/schemas/Invalid" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "404": + description: Project not found. + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + "/topologies/{topologyId}": + get: + tags: + - topologies + description: Get this Topology. + parameters: + - name: topologyId + in: path + description: Topology's ID. + required: true + schema: + type: string + responses: + "200": + description: Successfully retrieved Topology. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Topology" + "400": + description: Missing or incorrectly typed parameter. + content: + "application/json": + schema: + $ref: "#/components/schemas/Invalid" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "403": + description: Forbidden from retrieving Topology. + content: + "application/json": + schema: + $ref: "#/components/schemas/Forbidden" + "404": + description: Topology not found. + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + put: + tags: + - topologies + description: Update this Topology's name. + parameters: + - name: topologyId + in: path + description: Topology's ID. + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + properties: + topology: + $ref: "#/components/schemas/Topology" + description: Topology's new properties. + required: true + responses: + "200": + description: Successfully updated Topology. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Topology" + "400": + description: Missing or incorrectly typed parameter. + content: + "application/json": + schema: + $ref: "#/components/schemas/Invalid" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "403": + description: Forbidden from retrieving Project. + content: + "application/json": + schema: + $ref: "#/components/schemas/Forbidden" + "404": + description: Project not found. + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + delete: + tags: + - topologies + description: Delete this Topology. + parameters: + - name: topologyId + in: path + description: Topology's ID. + required: true + schema: + type: string + responses: + "200": + description: Successfully deleted Topology. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Topology" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "403": + description: Forbidden from deleting Topology. + content: + "application/json": + schema: + $ref: "#/components/schemas/Forbidden" + "404": + description: Topology not found. + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + "/portfolios/{portfolioId}": + get: + tags: + - portfolios + description: Get this Portfolio. + parameters: + - name: portfolioId + in: path + description: Portfolio's ID. + required: true + schema: + type: string + responses: + "200": + description: Successfully retrieved Portfolio. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Portfolio" + "400": + description: Missing or incorrectly typed parameter. + content: + "application/json": + schema: + $ref: "#/components/schemas/Invalid" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "403": + description: Forbidden from retrieving Portfolio. + content: + "application/json": + schema: + $ref: "#/components/schemas/Forbidden" + "404": + description: Portfolio not found. + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + put: + tags: + - portfolios + description: Update this Portfolio. + parameters: + - name: portfolioId + in: path + description: Portfolio's ID. + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/Portfolio" + description: Portfolio's new properties. + required: true + responses: + "200": + description: Successfully updated Portfolio. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Portfolio" + "400": + description: Missing or incorrectly typed parameter. + content: + "application/json": + schema: + $ref: "#/components/schemas/Invalid" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "403": + description: Forbidden from retrieving Portfolio. + content: + "application/json": + schema: + $ref: "#/components/schemas/Forbidden" + "404": + description: Portfolio not found. + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + delete: + tags: + - portfolios + description: Delete this Portfolio. + parameters: + - name: portfolioId + in: path + description: Portfolio's ID. + required: true + schema: + type: string + responses: + "200": + description: Successfully deleted Portfolio. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Portfolio" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "403": + description: Forbidden from retrieving Portfolio. + content: + "application/json": + schema: + $ref: "#/components/schemas/Forbidden" + "404": + description: Portfolio not found. + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + "/portfolios/{portfolioId}/scenarios": + get: + tags: + - portfolios + description: Get Portfolio Scenarios. + parameters: + - name: portfolioId + in: path + description: Portfolio's ID. + required: true + schema: + type: string + responses: + "200": + description: Successfully retrieved Portfolio Scenarios. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + type: array + items: + $ref: "#/components/schemas/Scenario" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "403": + description: Forbidden from retrieving Portfolio. + content: + "application/json": + schema: + $ref: "#/components/schemas/Forbidden" + "404": + description: Portfolio not found. + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + post: + tags: + - portfolios + description: Add a Scenario. + parameters: + - name: portfolioId + in: path + description: Portfolio's ID. + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + properties: + topology: + $ref: "#/components/schemas/Scenario" + description: The new Scenario. + required: true + responses: + "200": + description: Successfully added Scenario. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Scenario" + "400": + description: Missing or incorrectly typed parameter. + content: + "application/json": + schema: + $ref: "#/components/schemas/Invalid" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "404": + description: Portfolio not found. + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + "/scenarios/{scenarioId}": + get: + tags: + - scenarios + description: Get this Scenario. + parameters: + - name: scenarioId + in: path + description: Scenario's ID. + required: true + schema: + type: string + responses: + "200": + description: Successfully retrieved Scenario. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Scenario" + "400": + description: Missing or incorrectly typed parameter. + content: + "application/json": + schema: + $ref: "#/components/schemas/Invalid" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "403": + description: Forbidden from retrieving Scenario. + content: + "application/json": + schema: + $ref: "#/components/schemas/Forbidden" + "404": + description: Scenario not found. + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + put: + tags: + - scenarios + description: Update this Scenario's name (other properties are read-only). + parameters: + - name: scenarioId + in: path + description: Scenario's ID. + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/Scenario" + description: Scenario with new name. + required: true + responses: + "200": + description: Successfully updated Scenario. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Scenario" + "400": + description: Missing or incorrectly typed parameter. + content: + "application/json": + schema: + $ref: "#/components/schemas/Invalid" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "403": + description: Forbidden from retrieving Scenario. + content: + "application/json": + schema: + $ref: "#/components/schemas/Forbidden" + "404": + description: Scenario not found. + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + delete: + tags: + - scenarios + description: Delete this Scenario. + parameters: + - name: scenarioId + in: path + description: Scenario's ID. + required: true + schema: + type: string + responses: + "200": + description: Successfully deleted Scenario. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Scenario" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "403": + description: Forbidden from retrieving Scenario. + content: + "application/json": + schema: + $ref: "#/components/schemas/Forbidden" + "404": + description: Scenario not found. + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + /schedulers: + get: + tags: + - simulation + description: Get all available Schedulers + responses: + "200": + description: Successfully retrieved Schedulers. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + type: array + items: + $ref: "#/components/schemas/Scheduler" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + /traces: + get: + tags: + - simulation + description: Get all available Traces + responses: + "200": + description: Successfully retrieved Traces. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + type: array + items: + type: object + properties: + _id: + type: string + name: + type: string + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "/traces/{traceId}": + get: + tags: + - simulation + description: Get this Trace. + parameters: + - name: traceId + in: path + description: Trace's ID. + required: true + schema: + type: string + responses: + "200": + description: Successfully retrieved Trace. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Trace" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "404": + description: Trace not found + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + /prefabs: + get: + tags: + - prefabs + description: Get all Prefabs the user has rights to view. + responses: + "200": + description: Successfully retrieved prefabs the user is authorized on. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + type: array + items: + $ref: "#/components/schemas/Prefab" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + post: + tags: + - prefabs + description: Add a Prefab. + requestBody: + content: + application/json: + schema: + properties: + name: + type: string + description: The new Prefab. + required: true + responses: + "200": + description: Successfully added Prefab. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Prefab" + "400": + description: Missing or incorrectly typed parameter. + content: + "application/json": + schema: + $ref: "#/components/schemas/Invalid" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "/prefabs/{prefabId}": + get: + tags: + - prefabs + description: Get this Prefab. + parameters: + - name: prefabId + in: path + description: Prefab's ID. + required: true + schema: + type: string + responses: + "200": + description: Successfully retrieved Prefab. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Prefab" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "403": + description: Forbidden from retrieving Prefab. + content: + "application/json": + schema: + $ref: "#/components/schemas/Forbidden" + "404": + description: Prefab not found. + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + put: + tags: + - prefabs + description: Update this Prefab. + parameters: + - name: prefabId + in: path + description: Prefab's ID. + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + properties: + prefab: + $ref: "#/components/schemas/Prefab" + description: Prefab's new properties. + required: true + responses: + "200": + description: Successfully updated Prefab. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Prefab" + "400": + description: Missing or incorrectly typed parameter. + content: + "application/json": + schema: + $ref: "#/components/schemas/Invalid" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "403": + description: Forbidden from retrieving Prefab. + content: + "application/json": + schema: + $ref: "#/components/schemas/Forbidden" + "404": + description: Prefab not found. + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + delete: + tags: + - prefabs + description: Delete this prefab. + parameters: + - name: prefabId + in: path + description: Prefab's ID. + required: true + schema: + type: string + responses: + "200": + description: Successfully deleted Prefab. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Prefab" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "404": + description: Prefab not found. + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + /jobs: + get: + tags: + - jobs + description: Get all available jobs to run. + responses: + "200": + description: Successfully retrieved available jobs. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + type: array + items: + $ref: "#/components/schemas/Job" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "/jobs/{jobId}": + get: + tags: + - jobs + description: Get this Job. + parameters: + - name: jobId + in: path + description: Job's ID. + required: true + schema: + type: string + responses: + "200": + description: Successfully retrieved Job. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Job" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "403": + description: Forbidden from retrieving Job. + content: + "application/json": + schema: + $ref: "#/components/schemas/Forbidden" + "404": + description: Job not found. + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + post: + tags: + - jobs + description: Update this Job. + parameters: + - name: jobId + in: path + description: Job's ID. + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + properties: + job: + $ref: "#/components/schemas/Job" + description: Job's new properties. + required: true + responses: + "200": + description: Successfully updated Job. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Job" + "400": + description: Missing or incorrectly typed parameter. + content: + "application/json": + schema: + $ref: "#/components/schemas/Invalid" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "403": + description: Forbidden from retrieving Job. + content: + "application/json": + schema: + $ref: "#/components/schemas/Forbidden" + "404": + description: Job not found. + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + "409": + description: State conflict. + content: + "application/json": + schema: + $ref: "#/components/schemas/Invalid" +components: + securitySchemes: + auth0: + type: oauth2 + x-token-validation-url: https://opendc.eu.auth0.com/userinfo + flows: + authorizationCode: + authorizationUrl: https://opendc.eu.auth0.com/authorize + tokenUrl: https://opendc.eu.auth0.com/oauth/token + scopes: + openid: Grants access to user_id + runner: Grants access to runner jobs + schemas: + Unauthorized: + type: object + required: + - message + properties: + message: + type: string + Invalid: + type: object + required: + - message + - errors + properties: + message: + type: string + errors: + type: array + items: + type: string + Forbidden: + type: object + required: + - message + properties: + message: + type: string + NotFound: + type: object + required: + - message + properties: + message: + type: string + Scheduler: + type: object + properties: + name: + type: string + Project: + type: object + properties: + _id: + type: string + name: + type: string + datetimeCreated: + type: string + format: dateTime + datetimeLastEdited: + type: string + format: dateTime + topologyIds: + type: array + items: + type: string + portfolioIds: + type: array + items: + type: string + authorizations: + type: array + items: + type: object + properties: + userId: + type: string + level: + type: string + enum: ['OWN', 'EDIT', 'VIEW'] + Topology: + type: object + properties: + _id: + type: string + projectId: + type: string + name: + type: string + rooms: + type: array + items: + type: object + properties: + _id: + type: string + name: + type: string + tiles: + type: array + items: + type: object + properties: + _id: + type: string + positionX: + type: integer + positionY: + type: integer + object: + type: object + properties: + _id: + type: string + name: + type: string + capacity: + type: integer + powerCapacityW: + type: integer + machines: + type: array + items: + type: object + properties: + _id: + type: string + position: + type: integer + cpus: + type: array + items: + type: object + properties: + _id: + type: string + name: + type: string + clockRateMhz: + type: integer + numberOfCores: + type: integer + energyConsumptionW: + type: integer + gpus: + type: array + items: + type: object + properties: + _id: + type: string + name: + type: string + clockRateMhz: + type: integer + numberOfCores: + type: integer + energyConsumptionW: + type: integer + memories: + type: array + items: + type: object + properties: + _id: + type: string + name: + type: string + speedMbPerS: + type: integer + sizeMb: + type: integer + energyConsumptionW: + type: integer + storages: + type: array + items: + type: object + properties: + _id: + type: string + name: + type: string + speedMbPerS: + type: integer + sizeMb: + type: integer + energyConsumptionW: + type: integer + Portfolio: + type: object + properties: + _id: + type: string + projectId: + type: string + name: + type: string + scenarioIds: + type: array + items: + type: string + targets: + type: object + properties: + enabledMetrics: + type: array + items: + type: string + repeatsPerScenario: + type: integer + Scenario: + type: object + properties: + _id: + type: string + portfolioId: + type: string + name: + type: string + trace: + type: object + properties: + traceId: + type: string + loadSamplingFraction: + type: number + topology: + type: object + properties: + topologyId: + type: string + operational: + type: object + properties: + failuresEnabled: + type: boolean + performanceInterferenceEnabled: + type: boolean + schedulerName: + type: string + Job: + type: object + properties: + _id: + type: string + scenarioId: + type: string + state: + type: string + heartbeat: + type: string + results: + type: object + Trace: + type: object + properties: + _id: + type: string + name: + type: string + path: + type: string + type: + type: string + Prefab: + type: object + properties: + _id: + type: string + name: + type: string + datetimeCreated: + type: string + format: dateTime + datetimeLastEdited: + type: string + format: dateTime diff --git a/opendc-web/opendc-web-api/tests/api/test_jobs.py b/opendc-web/opendc-web-api/tests/api/test_jobs.py new file mode 100644 index 00000000..2efe6933 --- /dev/null +++ b/opendc-web/opendc-web-api/tests/api/test_jobs.py @@ -0,0 +1,139 @@ +# Copyright (c) 2021 AtLarge Research +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# +from datetime import datetime + +from opendc.exts import db + +test_id = 24 * '1' +test_id_2 = 24 * '2' + + +def test_get_jobs(client, mocker): + mocker.patch.object(db, 'fetch_all', return_value=[ + {'_id': 'a', 'scenarioId': 'x', 'portfolioId': 'y', 'simulation': {'state': 'QUEUED'}} + ]) + res = client.get('/jobs/') + assert '200' in res.status + + +def test_get_job_non_existing(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value=None) + assert '404' in client.get(f'/jobs/{test_id}').status + + +def test_get_job(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value={ + '_id': 'a', 'scenarioId': 'x', 'portfolioId': 'y', 'simulation': {'state': 'QUEUED'} + }) + res = client.get(f'/jobs/{test_id}') + assert '200' in res.status + + +def test_update_job_nop(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value={ + '_id': test_id, 'scenarioId': 'x', 'portfolioId': 'y', 'simulation': {'state': 'QUEUED'} + }) + update_mock = mocker.patch.object(db, 'fetch_and_update', return_value={ + '_id': test_id, 'scenarioId': 'x', 'portfolioId': 'y', + 'simulation': {'state': 'QUEUED', 'heartbeat': datetime.now()} + }) + res = client.post(f'/jobs/{test_id}', json={'state': 'QUEUED'}) + assert '200' in res.status + update_mock.assert_called_once() + + +def test_update_job_invalid_state(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value={ + '_id': test_id, 'scenarioId': 'x', 'portfolioId': 'y', 'simulation': {'state': 'QUEUED'} + }) + res = client.post(f'/jobs/{test_id}', json={'state': 'FINISHED'}) + assert '400' in res.status + + +def test_update_job_claim(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value={ + '_id': test_id, 'scenarioId': 'x', 'portfolioId': 'y', 'simulation': {'state': 'QUEUED'} + }) + update_mock = mocker.patch.object(db, 'fetch_and_update', return_value={ + '_id': test_id, 'scenarioId': 'x', 'portfolioId': 'y', + 'simulation': {'state': 'CLAIMED', 'heartbeat': datetime.now()} + }) + res = client.post(f'/jobs/{test_id}', json={'state': 'CLAIMED'}) + assert '200' in res.status + update_mock.assert_called_once() + + +def test_update_job_conflict(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value={ + '_id': test_id, 'scenarioId': 'x', 'portfolioId': 'y', 'simulation': {'state': 'QUEUED'} + }) + update_mock = mocker.patch.object(db, 'fetch_and_update', return_value=None) + res = client.post(f'/jobs/{test_id}', json={'state': 'CLAIMED'}) + assert '409' in res.status + update_mock.assert_called_once() + + +def test_update_job_run(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value={ + '_id': test_id, 'scenarioId': 'x', 'portfolioId': 'y', 'simulation': {'state': 'CLAIMED'} + }) + update_mock = mocker.patch.object(db, 'fetch_and_update', return_value={ + '_id': test_id, 'scenarioId': 'x', 'portfolioId': 'y', + 'simulation': {'state': 'RUNNING', 'heartbeat': datetime.now()} + }) + res = client.post(f'/jobs/{test_id}', json={'state': 'RUNNING'}) + assert '200' in res.status + update_mock.assert_called_once() + + +def test_update_job_finished(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value={ + '_id': test_id, 'scenarioId': 'x', 'portfolioId': 'y', 'simulation': {'state': 'RUNNING'} + }) + update_mock = mocker.patch.object(db, 'fetch_and_update', return_value={ + '_id': test_id, 'scenarioId': 'x', 'portfolioId': 'y', + 'simulation': {'state': 'FINISHED', 'heartbeat': datetime.now()} + }) + res = client.post(f'/jobs/{test_id}', json={'state': 'FINISHED'}) + assert '200' in res.status + update_mock.assert_called_once() + + +def test_update_job_failed(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value={ + '_id': test_id, 'scenarioId': 'x', 'portfolioId': 'y', 'simulation': {'state': 'RUNNING'} + }) + update_mock = mocker.patch.object(db, 'fetch_and_update', return_value={ + '_id': test_id, 'scenarioId': 'x', 'portfolioId': 'y', + 'simulation': {'state': 'FAILED', 'heartbeat': datetime.now()} + }) + res = client.post(f'/jobs/{test_id}', json={'state': 'FAILED'}) + assert '200' in res.status + update_mock.assert_called_once() diff --git a/opendc-web/opendc-web-api/tests/api/test_portfolios.py b/opendc-web/opendc-web-api/tests/api/test_portfolios.py new file mode 100644 index 00000000..196fcb1c --- /dev/null +++ b/opendc-web/opendc-web-api/tests/api/test_portfolios.py @@ -0,0 +1,340 @@ +# Copyright (c) 2021 AtLarge Research +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from opendc.exts import db + +test_id = 24 * '1' +test_id_2 = 24 * '2' + + +def test_get_portfolio_non_existing(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value=None) + assert '404' in client.get(f'/portfolios/{test_id}').status + + +def test_get_portfolio_no_authorizations(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value={'projectId': test_id, 'authorizations': []}) + res = client.get(f'/portfolios/{test_id}') + assert '403' in res.status + + +def test_get_portfolio_not_authorized(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + 'projectId': test_id, + '_id': test_id, + 'authorizations': [] + }) + res = client.get(f'/portfolios/{test_id}') + assert '403' in res.status + + +def test_get_portfolio(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + 'projectId': test_id, + '_id': test_id, + 'authorizations': [{ + 'userId': 'test', + 'level': 'EDIT' + }] + }) + res = client.get(f'/portfolios/{test_id}') + assert '200' in res.status + + +def test_update_portfolio_missing_parameter(client): + assert '400' in client.put(f'/portfolios/{test_id}').status + + +def test_update_portfolio_non_existing(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value=None) + assert '404' in client.put(f'/portfolios/{test_id}', json={ + 'portfolio': { + 'name': 'test', + 'targets': { + 'enabledMetrics': ['test'], + 'repeatsPerScenario': 2 + } + } + }).status + + +def test_update_portfolio_not_authorized(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'projectId': test_id, + 'authorizations': [{ + 'userId': 'test', + 'level': 'VIEW' + }] + }) + mocker.patch.object(db, 'update', return_value={}) + assert '403' in client.put(f'/portfolios/{test_id}', json={ + 'portfolio': { + 'name': 'test', + 'targets': { + 'enabledMetrics': ['test'], + 'repeatsPerScenario': 2 + } + } + }).status + + +def test_update_portfolio(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'projectId': test_id, + 'authorizations': [{ + 'userId': 'test', + 'level': 'OWN' + }], + 'targets': { + 'enabledMetrics': [], + 'repeatsPerScenario': 1 + } + }) + mocker.patch.object(db, 'update', return_value={}) + + res = client.put(f'/portfolios/{test_id}', json={'portfolio': { + 'name': 'test', + 'targets': { + 'enabledMetrics': ['test'], + 'repeatsPerScenario': 2 + } + }}) + assert '200' in res.status + + +def test_delete_project_non_existing(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value=None) + assert '404' in client.delete(f'/portfolios/{test_id}').status + + +def test_delete_project_different_user(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'projectId': test_id, + 'googleId': 'other_test', + 'authorizations': [{ + 'userId': 'test', + 'level': 'VIEW' + }] + }) + mocker.patch.object(db, 'delete_one', return_value=None) + assert '403' in client.delete(f'/portfolios/{test_id}').status + + +def test_delete_project(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'projectId': test_id, + 'googleId': 'test', + 'portfolioIds': [test_id], + 'authorizations': [{ + 'userId': 'test', + 'level': 'OWN' + }] + }) + mocker.patch.object(db, 'delete_one', return_value={}) + mocker.patch.object(db, 'update', return_value=None) + res = client.delete(f'/portfolios/{test_id}') + assert '200' in res.status + + +def test_add_topology_missing_parameter(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'projectId': test_id, + 'googleId': 'test', + 'portfolioIds': [test_id], + 'authorizations': [{ + 'userId': 'test', + 'level': 'OWN' + }] + }) + assert '400' in client.post(f'/projects/{test_id}/topologies').status + + +def test_add_topology(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'authorizations': [{ + 'userId': 'test', + 'level': 'OWN' + }], + 'topologyIds': [] + }) + mocker.patch.object(db, + 'insert', + return_value={ + '_id': test_id, + 'datetimeCreated': '000', + 'datetimeLastEdited': '000', + 'topologyIds': [] + }) + mocker.patch.object(db, 'update', return_value={}) + res = client.post(f'/projects/{test_id}/topologies', json={'topology': {'name': 'test project', 'rooms': []}}) + assert 'rooms' in res.json['data'] + assert '200' in res.status + + +def test_add_topology_not_authorized(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'projectId': test_id, + 'authorizations': [{ + 'userId': 'test', + 'level': 'VIEW' + }] + }) + assert '403' in client.post(f'/projects/{test_id}/topologies', + json={ + 'topology': { + 'name': 'test_topology', + 'rooms': [] + } + }).status + + +def test_add_portfolio_missing_parameter(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'projectId': test_id, + 'googleId': 'test', + 'portfolioIds': [test_id], + 'authorizations': [{ + 'userId': 'test', + 'level': 'OWN' + }] + }) + assert '400' in client.post(f'/projects/{test_id}/portfolios').status + + +def test_add_portfolio_non_existing_project(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value=None) + assert '404' in client.post(f'/projects/{test_id}/portfolios', + json={ + 'portfolio': { + 'name': 'test', + 'targets': { + 'enabledMetrics': ['test'], + 'repeatsPerScenario': 2 + } + } + }).status + + +def test_add_portfolio_not_authorized(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'projectId': test_id, + 'authorizations': [{ + 'userId': 'test', + 'level': 'VIEW' + }] + }) + assert '403' in client.post(f'/projects/{test_id}/portfolios', + json={ + 'portfolio': { + 'name': 'test', + 'targets': { + 'enabledMetrics': ['test'], + 'repeatsPerScenario': 2 + } + } + }).status + + +def test_add_portfolio(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'projectId': test_id, + 'portfolioIds': [test_id], + 'authorizations': [{ + 'userId': 'test', + 'level': 'EDIT' + }] + }) + mocker.patch.object(db, + 'insert', + return_value={ + '_id': test_id, + 'name': 'test', + 'targets': { + 'enabledMetrics': ['test'], + 'repeatsPerScenario': 2 + }, + 'projectId': test_id, + 'scenarioIds': [], + }) + mocker.patch.object(db, 'update', return_value=None) + res = client.post( + f'/projects/{test_id}/portfolios', + json={ + 'portfolio': { + 'name': 'test', + 'targets': { + 'enabledMetrics': ['test'], + 'repeatsPerScenario': 2 + } + } + }) + assert 'projectId' in res.json['data'] + assert 'scenarioIds' in res.json['data'] + assert '200' in res.status + + +def test_get_portfolio_scenarios(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + 'projectId': test_id, + '_id': test_id, + 'authorizations': [{ + 'userId': 'test', + 'level': 'EDIT' + }] + }) + mocker.patch.object(db, 'fetch_all', return_value=[{'_id': test_id}]) + res = client.get(f'/portfolios/{test_id}/scenarios') + assert '200' in res.status diff --git a/opendc-web/opendc-web-api/tests/api/test_prefabs.py b/opendc-web/opendc-web-api/tests/api/test_prefabs.py new file mode 100644 index 00000000..ea3d92d6 --- /dev/null +++ b/opendc-web/opendc-web-api/tests/api/test_prefabs.py @@ -0,0 +1,252 @@ +# Copyright (c) 2021 AtLarge Research +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from unittest.mock import Mock +from opendc.exts import db + +test_id = 24 * '1' +test_id_2 = 24 * '2' + + +def test_add_prefab_missing_parameter(client): + assert '400' in client.post('/prefabs/').status + + +def test_add_prefab(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value={'_id': test_id, 'authorizations': []}) + mocker.patch.object(db, + 'insert', + return_value={ + '_id': test_id, + 'datetimeCreated': '000', + 'datetimeLastEdited': '000', + 'authorId': test_id + }) + res = client.post('/prefabs/', json={'prefab': {'name': 'test prefab'}}) + assert 'datetimeCreated' in res.json['data'] + assert 'datetimeLastEdited' in res.json['data'] + assert 'authorId' in res.json['data'] + assert '200' in res.status + + +def test_get_prefabs(client, mocker): + db.fetch_all = Mock() + mocker.patch.object(db, 'fetch_one', return_value={'_id': test_id}) + db.fetch_all.side_effect = [ + [{ + '_id': test_id, + 'datetimeCreated': '000', + 'datetimeLastEdited': '000', + 'authorId': test_id, + 'visibility' : 'private' + }, + { + '_id': '2' * 24, + 'datetimeCreated': '000', + 'datetimeLastEdited': '000', + 'authorId': test_id, + 'visibility' : 'private' + }, + { + '_id': '3' * 24, + 'datetimeCreated': '000', + 'datetimeLastEdited': '000', + 'authorId': test_id, + 'visibility' : 'public' + }, + { + '_id': '4' * 24, + 'datetimeCreated': '000', + 'datetimeLastEdited': '000', + 'authorId': test_id, + 'visibility' : 'public' + }], + [{ + '_id': '5' * 24, + 'datetimeCreated': '000', + 'datetimeLastEdited': '000', + 'authorId': '2' * 24, + 'visibility' : 'public' + }, + { + '_id': '6' * 24, + 'datetimeCreated': '000', + 'datetimeLastEdited': '000', + 'authorId': '2' * 24, + 'visibility' : 'public' + }, + { + '_id': '7' * 24, + 'datetimeCreated': '000', + 'datetimeLastEdited': '000', + 'authorId': '2' * 24, + 'visibility' : 'public' + }, + { + '_id': '8' * 24, + 'datetimeCreated': '000', + 'datetimeLastEdited': '000', + 'authorId': '2' * 24, + 'visibility' : 'public' + }] + ] + mocker.patch.object(db, 'fetch_one', return_value={'_id': test_id}) + res = client.get('/prefabs/') + assert '200' in res.status + + +def test_get_prefab_non_existing(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value=None) + assert '404' in client.get(f'/prefabs/{test_id}').status + + +def test_get_private_prefab_not_authorized(client, mocker): + db.fetch_one = Mock() + db.fetch_one.side_effect = [{ + '_id': test_id, + 'name': 'test prefab', + 'authorId': test_id_2, + 'visibility': 'private', + 'rack': {} + }, + { + '_id': test_id + } + ] + res = client.get(f'/prefabs/{test_id}') + assert '403' in res.status + + +def test_get_private_prefab(client, mocker): + db.fetch_one = Mock() + db.fetch_one.side_effect = [{ + '_id': test_id, + 'name': 'test prefab', + 'authorId': 'test', + 'visibility': 'private', + 'rack': {} + }, + { + '_id': test_id + } + ] + res = client.get(f'/prefabs/{test_id}') + assert '200' in res.status + + +def test_get_public_prefab(client, mocker): + db.fetch_one = Mock() + db.fetch_one.side_effect = [{ + '_id': test_id, + 'name': 'test prefab', + 'authorId': test_id_2, + 'visibility': 'public', + 'rack': {} + }, + { + '_id': test_id + } + ] + res = client.get(f'/prefabs/{test_id}') + assert '200' in res.status + + +def test_update_prefab_missing_parameter(client): + assert '400' in client.put(f'/prefabs/{test_id}').status + + +def test_update_prefab_non_existing(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value=None) + assert '404' in client.put(f'/prefabs/{test_id}', json={'prefab': {'name': 'S'}}).status + + +def test_update_prefab_not_authorized(client, mocker): + db.fetch_one = Mock() + db.fetch_one.side_effect = [{ + '_id': test_id, + 'name': 'test prefab', + 'authorId': test_id_2, + 'visibility': 'private', + 'rack': {} + }, + { + '_id': test_id + } + ] + mocker.patch.object(db, 'update', return_value={}) + assert '403' in client.put(f'/prefabs/{test_id}', json={'prefab': {'name': 'test prefab', 'rack': {}}}).status + + +def test_update_prefab(client, mocker): + db.fetch_one = Mock() + db.fetch_one.side_effect = [{ + '_id': test_id, + 'name': 'test prefab', + 'authorId': 'test', + 'visibility': 'private', + 'rack': {} + }, + { + '_id': test_id + } + ] + mocker.patch.object(db, 'update', return_value={}) + res = client.put(f'/prefabs/{test_id}', json={'prefab': {'name': 'test prefab', 'rack': {}}}) + assert '200' in res.status + + +def test_delete_prefab_non_existing(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value=None) + assert '404' in client.delete(f'/prefabs/{test_id}').status + + +def test_delete_prefab_different_user(client, mocker): + db.fetch_one = Mock() + db.fetch_one.side_effect = [{ + '_id': test_id, + 'name': 'test prefab', + 'authorId': test_id_2, + 'visibility': 'private', + 'rack': {} + }, + { + '_id': test_id + } + ] + mocker.patch.object(db, 'delete_one', return_value=None) + assert '403' in client.delete(f'/prefabs/{test_id}').status + + +def test_delete_prefab(client, mocker): + db.fetch_one = Mock() + db.fetch_one.side_effect = [{ + '_id': test_id, + 'name': 'test prefab', + 'authorId': 'test', + 'visibility': 'private', + 'rack': {} + }, + { + '_id': test_id + } + ] + mocker.patch.object(db, 'delete_one', return_value={'prefab': {'name': 'name'}}) + res = client.delete(f'/prefabs/{test_id}') + assert '200' in res.status diff --git a/opendc-web/opendc-web-api/tests/api/test_projects.py b/opendc-web/opendc-web-api/tests/api/test_projects.py new file mode 100644 index 00000000..1cfe4c52 --- /dev/null +++ b/opendc-web/opendc-web-api/tests/api/test_projects.py @@ -0,0 +1,197 @@ +# Copyright (c) 2021 AtLarge Research +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from opendc.exts import db + +test_id = 24 * '1' + + +def test_get_user_projects(client, mocker): + mocker.patch.object(db, 'fetch_all', return_value={'_id': test_id, 'authorizations': [{'userId': 'test', + 'level': 'OWN'}]}) + res = client.get('/projects/') + assert '200' in res.status + + +def test_get_user_topologies(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'authorizations': [{ + 'userId': 'test', + 'level': 'EDIT' + }] + }) + mocker.patch.object(db, 'fetch_all', return_value=[{'_id': test_id}]) + res = client.get(f'/projects/{test_id}/topologies') + assert '200' in res.status + + +def test_get_user_portfolios(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'authorizations': [{ + 'userId': 'test', + 'level': 'EDIT' + }] + }) + mocker.patch.object(db, 'fetch_all', return_value=[{'_id': test_id}]) + res = client.get(f'/projects/{test_id}/portfolios') + assert '200' in res.status + + +def test_add_project_missing_parameter(client): + assert '400' in client.post('/projects/').status + + +def test_add_project(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value={'_id': test_id, 'authorizations': []}) + mocker.patch.object(db, + 'insert', + return_value={ + '_id': test_id, + 'datetimeCreated': '000', + 'datetimeLastEdited': '000', + 'topologyIds': [] + }) + mocker.patch.object(db, 'update', return_value={}) + res = client.post('/projects/', json={'project': {'name': 'test project'}}) + assert 'datetimeCreated' in res.json['data'] + assert 'datetimeLastEdited' in res.json['data'] + assert 'topologyIds' in res.json['data'] + assert '200' in res.status + + +def test_get_project_non_existing(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value=None) + assert '404' in client.get(f'/projects/{test_id}').status + + +def test_get_project_no_authorizations(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value={'authorizations': []}) + res = client.get(f'/projects/{test_id}') + assert '403' in res.status + + +def test_get_project_not_authorized(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'authorizations': [] + }) + res = client.get(f'/projects/{test_id}') + assert '403' in res.status + + +def test_get_project(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'authorizations': [{ + 'userId': 'test', + 'level': 'EDIT' + }] + }) + res = client.get(f'/projects/{test_id}') + assert '200' in res.status + + +def test_update_project_missing_parameter(client): + assert '400' in client.put(f'/projects/{test_id}').status + + +def test_update_project_non_existing(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value=None) + assert '404' in client.put(f'/projects/{test_id}', json={'project': {'name': 'S'}}).status + + +def test_update_project_not_authorized(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'authorizations': [{ + 'userId': 'test', + 'level': 'VIEW' + }] + }) + mocker.patch.object(db, 'update', return_value={}) + assert '403' in client.put(f'/projects/{test_id}', json={'project': {'name': 'S'}}).status + + +def test_update_project(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'authorizations': [{ + 'userId': 'test', + 'level': 'OWN' + }] + }) + mocker.patch.object(db, 'update', return_value={}) + + res = client.put(f'/projects/{test_id}', json={'project': {'name': 'S'}}) + assert '200' in res.status + + +def test_delete_project_non_existing(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value=None) + assert '404' in client.delete(f'/projects/{test_id}').status + + +def test_delete_project_different_user(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'googleId': 'other_test', + 'authorizations': [{ + 'userId': 'test', + 'level': 'VIEW' + }], + 'topologyIds': [] + }) + mocker.patch.object(db, 'delete_one', return_value=None) + assert '403' in client.delete(f'/projects/{test_id}').status + + +def test_delete_project(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'googleId': 'test', + 'authorizations': [{ + 'userId': 'test', + 'level': 'OWN' + }], + 'topologyIds': [], + 'portfolioIds': [], + }) + mocker.patch.object(db, 'update', return_value=None) + mocker.patch.object(db, 'delete_one', return_value={'googleId': 'test'}) + res = client.delete(f'/projects/{test_id}') + assert '200' in res.status diff --git a/opendc-web/opendc-web-api/tests/api/test_scenarios.py b/opendc-web/opendc-web-api/tests/api/test_scenarios.py new file mode 100644 index 00000000..bdd5c4a3 --- /dev/null +++ b/opendc-web/opendc-web-api/tests/api/test_scenarios.py @@ -0,0 +1,135 @@ +# Copyright (c) 2021 AtLarge Research +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from opendc.exts import db + +test_id = 24 * '1' +test_id_2 = 24 * '2' + + +def test_get_scenario_non_existing(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value=None) + assert '404' in client.get(f'/scenarios/{test_id}').status + + +def test_get_scenario_no_authorizations(client, mocker): + m = mocker.MagicMock() + m.side_effect = ({'portfolioId': test_id}, {'projectId': test_id}, {'authorizations': []}) + mocker.patch.object(db, 'fetch_one', m) + res = client.get(f'/scenarios/{test_id}') + assert '403' in res.status + + +def test_get_scenario(client, mocker): + mocker.patch.object(db, + 'fetch_one', + side_effect=[ + {'portfolioId': test_id}, + {'projectId': test_id}, + {'authorizations': + [{'userId': 'test', 'level': 'OWN'}] + }]) + res = client.get(f'/scenarios/{test_id}') + assert '200' in res.status + + +def test_update_scenario_missing_parameter(client): + assert '400' in client.put(f'/scenarios/{test_id}').status + + +def test_update_scenario_non_existing(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value=None) + assert '404' in client.put(f'/scenarios/{test_id}', json={ + 'scenario': { + 'name': 'test', + } + }).status + + +def test_update_scenario_not_authorized(client, mocker): + mocker.patch.object(db, + 'fetch_one', + side_effect=[ + {'portfolioId': test_id}, + {'projectId': test_id}, + {'authorizations': + [{'userId': 'test', 'level': 'VIEW'}] + }]) + mocker.patch.object(db, 'update', return_value={}) + assert '403' in client.put(f'/scenarios/{test_id}', json={ + 'scenario': { + 'name': 'test', + } + }).status + + +def test_update_scenario(client, mocker): + mocker.patch.object(db, + 'fetch_one', + side_effect=[ + {'_id': test_id, 'portfolioId': test_id}, + {'projectId': test_id}, + {'authorizations': + [{'userId': 'test', 'level': 'OWN'}] + }]) + mocker.patch.object(db, 'update', return_value={}) + + res = client.put(f'/scenarios/{test_id}', json={'scenario': { + 'name': 'test', + }}) + assert '200' in res.status + + +def test_delete_project_non_existing(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value=None) + assert '404' in client.delete(f'/scenarios/{test_id}').status + + +def test_delete_project_different_user(client, mocker): + mocker.patch.object(db, + 'fetch_one', + side_effect=[ + {'_id': test_id, 'portfolioId': test_id}, + {'projectId': test_id}, + {'authorizations': + [{'userId': 'test', 'level': 'VIEW'}] + }]) + mocker.patch.object(db, 'delete_one', return_value=None) + assert '403' in client.delete(f'/scenarios/{test_id}').status + + +def test_delete_project(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'projectId': test_id, + 'portfolioId': test_id, + 'googleId': 'test', + 'scenarioIds': [test_id], + 'authorizations': [{ + 'userId': 'test', + 'level': 'OWN' + }] + }) + mocker.patch.object(db, 'delete_one', return_value={}) + mocker.patch.object(db, 'update', return_value=None) + res = client.delete(f'/scenarios/{test_id}') + assert '200' in res.status diff --git a/opendc-web/opendc-web-api/tests/api/test_schedulers.py b/opendc-web/opendc-web-api/tests/api/test_schedulers.py new file mode 100644 index 00000000..5d9e6995 --- /dev/null +++ b/opendc-web/opendc-web-api/tests/api/test_schedulers.py @@ -0,0 +1,22 @@ +# Copyright (c) 2021 AtLarge Research +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +def test_get_schedulers(client): + assert '200' in client.get('/schedulers/').status diff --git a/opendc-web/opendc-web-api/tests/api/test_topologies.py b/opendc-web/opendc-web-api/tests/api/test_topologies.py new file mode 100644 index 00000000..6e7c54ef --- /dev/null +++ b/opendc-web/opendc-web-api/tests/api/test_topologies.py @@ -0,0 +1,140 @@ +# Copyright (c) 2021 AtLarge Research +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from opendc.exts import db + +test_id = 24 * '1' +test_id_2 = 24 * '2' + + +def test_get_topology(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'projectId': test_id, + 'authorizations': [{ + 'userId': 'test', + 'level': 'EDIT' + }] + }) + res = client.get(f'/topologies/{test_id}') + assert '200' in res.status + + +def test_get_topology_non_existing(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value=None) + assert '404' in client.get('/topologies/1').status + + +def test_get_topology_not_authorized(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'projectId': test_id, + 'authorizations': [] + }) + res = client.get(f'/topologies/{test_id}') + assert '403' in res.status + + +def test_get_topology_no_authorizations(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value={'projectId': test_id, 'authorizations': []}) + res = client.get(f'/topologies/{test_id}') + assert '403' in res.status + + +def test_update_topology_missing_parameter(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'projectId': test_id, + 'authorizations': [] + }) + assert '400' in client.put(f'/topologies/{test_id}').status + + +def test_update_topology_non_existent(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value=None) + assert '404' in client.put(f'/topologies/{test_id}', json={'topology': {'name': 'test_topology', 'rooms': []}}).status + + +def test_update_topology_not_authorized(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'projectId': test_id, + 'authorizations': [] + }) + mocker.patch.object(db, 'update', return_value={}) + assert '403' in client.put(f'/topologies/{test_id}', json={ + 'topology': { + 'name': 'updated_topology', + 'rooms': [] + } + }).status + + +def test_update_topology(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'projectId': test_id, + 'authorizations': [{ + 'userId': 'test', + 'level': 'OWN' + }] + }) + mocker.patch.object(db, 'update', return_value={}) + + assert '200' in client.put(f'/topologies/{test_id}', json={ + 'topology': { + 'name': 'updated_topology', + 'rooms': [] + } + }).status + + +def test_delete_topology(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'projectId': test_id, + 'googleId': 'test', + 'topologyIds': [test_id], + 'authorizations': [{ + 'userId': 'test', + 'level': 'OWN' + }] + }) + mocker.patch.object(db, 'delete_one', return_value={}) + mocker.patch.object(db, 'update', return_value=None) + res = client.delete(f'/topologies/{test_id}') + assert '200' in res.status + + +def test_delete_nonexistent_topology(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value=None) + assert '404' in client.delete(f'/topologies/{test_id}').status diff --git a/opendc-web/opendc-web-api/tests/api/test_traces.py b/opendc-web/opendc-web-api/tests/api/test_traces.py new file mode 100644 index 00000000..0b252c2f --- /dev/null +++ b/opendc-web/opendc-web-api/tests/api/test_traces.py @@ -0,0 +1,40 @@ +# Copyright (c) 2021 AtLarge Research +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from opendc.exts import db + +test_id = 24 * '1' + + +def test_get_traces(client, mocker): + mocker.patch.object(db, 'fetch_all', return_value=[]) + assert '200' in client.get('/traces/').status + + +def test_get_trace_non_existing(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value=None) + assert '404' in client.get(f'/traces/{test_id}').status + + +def test_get_trace(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value={'name': 'test trace'}) + res = client.get(f'/traces/{test_id}') + assert 'name' in res.json['data'] + assert '200' in res.status diff --git a/opendc-web/opendc-web-runner/build.gradle.kts b/opendc-web/opendc-web-runner/build.gradle.kts index b7eb223c..810f512f 100644 --- a/opendc-web/opendc-web-runner/build.gradle.kts +++ b/opendc-web/opendc-web-runner/build.gradle.kts @@ -25,26 +25,34 @@ description = "Experiment runner for OpenDC" /* Build configuration */ plugins { `kotlin-conventions` + `testing-conventions` application } application { - mainClass.set("org.opendc.runner.web.MainKt") + mainClass.set("org.opendc.web.runner.MainKt") } dependencies { api(platform(projects.opendcPlatform)) implementation(projects.opendcCompute.opendcComputeSimulator) - implementation(projects.opendcFormat) - implementation(projects.opendcExperiments.opendcExperimentsCapelin) + implementation(projects.opendcCompute.opendcComputeWorkload) implementation(projects.opendcSimulator.opendcSimulatorCore) implementation(projects.opendcTelemetry.opendcTelemetrySdk) + implementation(projects.opendcTelemetry.opendcTelemetryCompute) + implementation(projects.opendcTrace.opendcTraceApi) implementation(libs.kotlin.logging) implementation(libs.clikt) - implementation(libs.jackson.module.kotlin) implementation(libs.sentry.log4j2) - implementation(libs.mongodb) + implementation(libs.ktor.client.cio) + implementation(libs.ktor.client.auth) + implementation(libs.ktor.client.jackson) + implementation(libs.jackson.datatype.jsr310) + implementation(kotlin("reflect")) + runtimeOnly(projects.opendcTrace.opendcTraceOpendc) runtimeOnly(libs.log4j.slf4j) + + testImplementation(libs.ktor.client.mock) } diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/runner/web/Main.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/runner/web/Main.kt deleted file mode 100644 index 09f7de35..00000000 --- a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/runner/web/Main.kt +++ /dev/null @@ -1,380 +0,0 @@ -/* - * Copyright (c) 2020 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.runner.web - -import com.github.ajalt.clikt.core.CliktCommand -import com.github.ajalt.clikt.parameters.options.* -import com.github.ajalt.clikt.parameters.types.file -import com.github.ajalt.clikt.parameters.types.int -import com.github.ajalt.clikt.parameters.types.long -import com.mongodb.MongoClientSettings -import com.mongodb.MongoCredential -import com.mongodb.ServerAddress -import com.mongodb.client.MongoClients -import com.mongodb.client.MongoDatabase -import com.mongodb.client.model.Filters -import io.opentelemetry.api.metrics.MeterProvider -import io.opentelemetry.sdk.metrics.SdkMeterProvider -import io.opentelemetry.sdk.metrics.export.MetricProducer -import kotlinx.coroutines.* -import kotlinx.coroutines.channels.Channel -import mu.KotlinLogging -import org.bson.Document -import org.bson.types.ObjectId -import org.opendc.compute.service.scheduler.FilterScheduler -import org.opendc.compute.service.scheduler.filters.ComputeCapabilitiesFilter -import org.opendc.compute.service.scheduler.filters.ComputeFilter -import org.opendc.compute.service.scheduler.weights.* -import org.opendc.experiments.capelin.* -import org.opendc.experiments.capelin.model.Workload -import org.opendc.experiments.capelin.trace.Sc20ParquetTraceReader -import org.opendc.experiments.capelin.trace.Sc20RawParquetTraceReader -import org.opendc.format.environment.EnvironmentReader -import org.opendc.format.trace.sc20.Sc20PerformanceInterferenceReader -import org.opendc.simulator.core.runBlockingSimulation -import org.opendc.telemetry.sdk.toOtelClock -import java.io.File -import kotlin.random.Random - -private val logger = KotlinLogging.logger {} - -/** - * Represents the CLI command for starting the OpenDC web runner. - */ -@OptIn(ExperimentalCoroutinesApi::class) -public class RunnerCli : CliktCommand(name = "runner") { - /** - * The name of the database to use. - */ - private val mongoDb by option( - "--mongo-db", - help = "name of the database to use", - envvar = "OPENDC_DB" - ) - .default("opendc") - - /** - * The database host to connect to. - */ - private val mongoHost by option( - "--mongo-host", - help = "database host to connect to", - envvar = "OPENDC_DB_HOST" - ) - .default("localhost") - - /** - * The database port to connect to. - */ - private val mongoPort by option( - "--mongo-port", - help = "database port to connect to", - envvar = "OPENDC_DB_PORT" - ) - .int() - .default(27017) - - /** - * The database user to connect with. - */ - private val mongoUser by option( - "--mongo-user", - help = "database user to connect with", - envvar = "OPENDC_DB_USER" - ) - .default("opendc") - - /** - * The database password to connect with. - */ - private val mongoPassword by option( - "--mongo-password", - help = "database password to connect with", - envvar = "OPENDC_DB_PASSWORD" - ) - .convert { it.toCharArray() } - .required() - - /** - * The path to the traces directory. - */ - private val tracePath by option( - "--traces", - help = "path to the directory containing the traces", - envvar = "OPENDC_TRACES" - ) - .file(canBeFile = false) - .defaultLazy { File("traces/") } - - /** - * The maximum duration of a single experiment run. - */ - private val runTimeout by option( - "--run-timeout", - help = "maximum duration of experiment in seconds", - envvar = "OPENDC_RUN_TIMEOUT" - ) - .long() - .default(60 * 3) // Experiment may run for a maximum of three minutes - - /** - * Connect to the user-specified database. - */ - private fun createDatabase(): MongoDatabase { - val credential = MongoCredential.createScramSha1Credential( - mongoUser, - mongoDb, - mongoPassword - ) - - val settings = MongoClientSettings.builder() - .credential(credential) - .applyToClusterSettings { it.hosts(listOf(ServerAddress(mongoHost, mongoPort))) } - .build() - val client = MongoClients.create(settings) - return client.getDatabase(mongoDb) - } - - /** - * Run a single scenario. - */ - private suspend fun runScenario(portfolio: Document, scenario: Document, topologyParser: TopologyParser): List<WebExperimentMonitor.Result> { - val id = scenario.getObjectId("_id") - - logger.info { "Constructing performance interference model" } - - val traceDir = File( - tracePath, - scenario.getEmbedded(listOf("trace", "traceId"), String::class.java) - ) - val traceReader = Sc20RawParquetTraceReader(traceDir) - val performanceInterferenceReader = let { - val path = File(traceDir, "performance-interference-model.json") - val operational = scenario.get("operational", Document::class.java) - val enabled = operational.getBoolean("performanceInterferenceEnabled") - - if (!enabled || !path.exists()) { - return@let null - } - - path.inputStream().use { Sc20PerformanceInterferenceReader(it) } - } - - val targets = portfolio.get("targets", Document::class.java) - val topologyId = scenario.getEmbedded(listOf("topology", "topologyId"), ObjectId::class.java) - val environment = topologyParser.read(topologyId) - - val results = (0 until targets.getInteger("repeatsPerScenario")).map { - logger.info { "Starting repeat $it" } - withTimeout(runTimeout * 1000) { - runRepeat(scenario, it, environment, traceReader, performanceInterferenceReader) - } - } - - logger.info { "Finished simulation for scenario $id" } - - return results - } - - /** - * Run a single repeat. - */ - private suspend fun runRepeat( - scenario: Document, - repeat: Int, - environment: EnvironmentReader, - traceReader: Sc20RawParquetTraceReader, - performanceInterferenceReader: Sc20PerformanceInterferenceReader? - ): WebExperimentMonitor.Result { - val monitor = WebExperimentMonitor() - - try { - runBlockingSimulation { - val seed = repeat - val traceDocument = scenario.get("trace", Document::class.java) - val workloadName = traceDocument.getString("traceId") - val workloadFraction = traceDocument.get("loadSamplingFraction", Number::class.java).toDouble() - - val seeder = Random(seed) - - val chan = Channel<Unit>(Channel.CONFLATED) - - val meterProvider: MeterProvider = SdkMeterProvider - .builder() - .setClock(clock.toOtelClock()) - .build() - val metricProducer = meterProvider as MetricProducer - - val operational = scenario.get("operational", Document::class.java) - val allocationPolicy = - when (val policyName = operational.getString("schedulerName")) { - "mem" -> FilterScheduler( - filters = listOf(ComputeFilter(), ComputeCapabilitiesFilter()), - weighers = listOf(MemoryWeigher() to -1.0) - ) - "mem-inv" -> FilterScheduler( - filters = listOf(ComputeFilter(), ComputeCapabilitiesFilter()), - weighers = listOf(MemoryWeigher() to 1.0) - ) - "core-mem" -> FilterScheduler( - filters = listOf(ComputeFilter(), ComputeCapabilitiesFilter()), - weighers = listOf(CoreMemoryWeigher() to -1.0) - ) - "core-mem-inv" -> FilterScheduler( - filters = listOf(ComputeFilter(), ComputeCapabilitiesFilter()), - weighers = listOf(CoreMemoryWeigher() to 1.0) - ) - "active-servers" -> FilterScheduler( - filters = listOf(ComputeFilter(), ComputeCapabilitiesFilter()), - weighers = listOf(ProvisionedCoresWeigher() to -1.0) - ) - "active-servers-inv" -> FilterScheduler( - filters = listOf(ComputeFilter(), ComputeCapabilitiesFilter()), - weighers = listOf(InstanceCountWeigher() to 1.0) - ) - "provisioned-cores" -> FilterScheduler( - filters = listOf(ComputeFilter(), ComputeCapabilitiesFilter()), - weighers = listOf(ProvisionedCoresWeigher() to -1.0) - ) - "provisioned-cores-inv" -> FilterScheduler( - filters = listOf(ComputeFilter(), ComputeCapabilitiesFilter()), - weighers = listOf(ProvisionedCoresWeigher() to 1.0) - ) - "random" -> FilterScheduler( - filters = listOf(ComputeFilter(), ComputeCapabilitiesFilter()), - weighers = listOf(RandomWeigher(java.util.Random(seeder.nextLong())) to 1.0) - ) - else -> throw IllegalArgumentException("Unknown policy $policyName") - } - - val performanceInterferenceModel = performanceInterferenceReader?.construct(seeder) ?: emptyMap() - val trace = Sc20ParquetTraceReader( - listOf(traceReader), - performanceInterferenceModel, - Workload(workloadName, workloadFraction), - seed - ) - val failureFrequency = if (operational.getBoolean("failuresEnabled", false)) 24.0 * 7 else 0.0 - - withComputeService(clock, meterProvider, environment, allocationPolicy) { scheduler -> - val failureDomain = if (failureFrequency > 0) { - logger.debug { "ENABLING failures" } - createFailureDomain( - this, - clock, - seeder.nextInt(), - failureFrequency, - scheduler, - chan - ) - } else { - null - } - - withMonitor(monitor, clock, meterProvider as MetricProducer, scheduler) { - processTrace( - clock, - trace, - scheduler, - chan, - monitor - ) - } - - failureDomain?.cancel() - } - - val monitorResults = collectMetrics(metricProducer) - logger.debug { "Finish SUBMIT=${monitorResults.submittedVms} FAIL=${monitorResults.unscheduledVms} QUEUE=${monitorResults.queuedVms} RUNNING=${monitorResults.runningVms}" } - } - } catch (cause: Throwable) { - logger.warn(cause) { "Experiment failed" } - } - - return monitor.getResult() - } - - private val POLL_INTERVAL = 5000L // ms = 5 s - private val HEARTBEAT_INTERVAL = 60000L // ms = 1 min - - override fun run(): Unit = runBlocking(Dispatchers.Default) { - logger.info { "Starting OpenDC web runner" } - logger.info { "Connecting to MongoDB instance" } - val database = createDatabase() - val manager = ScenarioManager(database.getCollection("scenarios")) - val portfolios = database.getCollection("portfolios") - val topologies = database.getCollection("topologies") - val topologyParser = TopologyParser(topologies) - - logger.info { "Watching for queued scenarios" } - - while (true) { - val scenario = manager.findNext() - - if (scenario == null) { - delay(POLL_INTERVAL) - continue - } - - val id = scenario.getObjectId("_id") - - logger.info { "Found queued scenario $id: attempting to claim" } - - if (!manager.claim(id)) { - logger.info { "Failed to claim scenario" } - continue - } - - coroutineScope { - // Launch heartbeat process - val heartbeat = launch { - while (true) { - delay(HEARTBEAT_INTERVAL) - manager.heartbeat(id) - } - } - - try { - val portfolio = portfolios.find(Filters.eq("_id", scenario.getObjectId("portfolioId"))).first()!! - val results = runScenario(portfolio, scenario, topologyParser) - - logger.info { "Writing results to database" } - - manager.finish(id, results) - - logger.info { "Successfully finished scenario $id" } - } catch (e: Exception) { - logger.error(e) { "Scenario failed to finish" } - manager.fail(id) - } finally { - heartbeat.cancel() - } - } - } - } -} - -/** - * Main entry point of the runner. - */ -public fun main(args: Array<String>): Unit = RunnerCli().main(args) diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/runner/web/ScenarioManager.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/runner/web/ScenarioManager.kt deleted file mode 100644 index a3907051..00000000 --- a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/runner/web/ScenarioManager.kt +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (c) 2020 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.runner.web - -import com.mongodb.client.MongoCollection -import com.mongodb.client.model.Filters -import com.mongodb.client.model.Updates -import org.bson.Document -import org.bson.types.ObjectId -import java.time.Instant - -/** - * Manages the queue of scenarios that need to be processed. - */ -public class ScenarioManager(private val collection: MongoCollection<Document>) { - /** - * Find the next scenario that the simulator needs to process. - */ - public fun findNext(): Document? { - return collection - .find(Filters.eq("simulation.state", "QUEUED")) - .first() - } - - /** - * Claim the scenario in the database with the specified id. - */ - public fun claim(id: ObjectId): Boolean { - val res = collection.findOneAndUpdate( - Filters.and( - Filters.eq("_id", id), - Filters.eq("simulation.state", "QUEUED") - ), - Updates.combine( - Updates.set("simulation.state", "RUNNING"), - Updates.set("simulation.heartbeat", Instant.now()) - ) - ) - return res != null - } - - /** - * Update the heartbeat of the specified scenario. - */ - public fun heartbeat(id: ObjectId) { - collection.findOneAndUpdate( - Filters.and( - Filters.eq("_id", id), - Filters.eq("simulation.state", "RUNNING") - ), - Updates.set("simulation.heartbeat", Instant.now()) - ) - } - - /** - * Mark the scenario as failed. - */ - public fun fail(id: ObjectId) { - collection.findOneAndUpdate( - Filters.eq("_id", id), - Updates.combine( - Updates.set("simulation.state", "FAILED"), - Updates.set("simulation.heartbeat", Instant.now()) - ) - ) - } - - /** - * Persist the specified results. - */ - public fun finish(id: ObjectId, results: List<WebExperimentMonitor.Result>) { - collection.findOneAndUpdate( - Filters.eq("_id", id), - Updates.combine( - Updates.set("simulation.state", "FINISHED"), - Updates.unset("simulation.time"), - Updates.set("results.total_requested_burst", results.map { it.totalRequestedBurst }), - Updates.set("results.total_granted_burst", results.map { it.totalGrantedBurst }), - Updates.set("results.total_overcommitted_burst", results.map { it.totalOvercommittedBurst }), - Updates.set("results.total_interfered_burst", results.map { it.totalInterferedBurst }), - Updates.set("results.mean_cpu_usage", results.map { it.meanCpuUsage }), - Updates.set("results.mean_cpu_demand", results.map { it.meanCpuDemand }), - Updates.set("results.mean_num_deployed_images", results.map { it.meanNumDeployedImages }), - Updates.set("results.max_num_deployed_images", results.map { it.maxNumDeployedImages }), - Updates.set("results.total_power_draw", results.map { it.totalPowerDraw }), - Updates.set("results.total_failure_slices", results.map { it.totalFailureSlices }), - Updates.set("results.total_failure_vm_slices", results.map { it.totalFailureVmSlices }), - Updates.set("results.total_vms_submitted", results.map { it.totalVmsSubmitted }), - Updates.set("results.total_vms_queued", results.map { it.totalVmsQueued }), - Updates.set("results.total_vms_finished", results.map { it.totalVmsFinished }), - Updates.set("results.total_vms_failed", results.map { it.totalVmsFailed }) - ) - ) - } -} diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/runner/web/TopologyParser.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/runner/web/TopologyParser.kt deleted file mode 100644 index 2dd63340..00000000 --- a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/runner/web/TopologyParser.kt +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (c) 2020 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.runner.web - -import com.mongodb.client.AggregateIterable -import com.mongodb.client.MongoCollection -import com.mongodb.client.model.Aggregates -import com.mongodb.client.model.Field -import com.mongodb.client.model.Filters -import com.mongodb.client.model.Projections -import org.bson.Document -import org.bson.types.ObjectId -import org.opendc.format.environment.EnvironmentReader -import org.opendc.format.environment.MachineDef -import org.opendc.simulator.compute.SimMachineModel -import org.opendc.simulator.compute.model.MemoryUnit -import org.opendc.simulator.compute.model.ProcessingNode -import org.opendc.simulator.compute.model.ProcessingUnit -import org.opendc.simulator.compute.power.LinearPowerModel -import java.util.* - -/** - * A helper class that converts the MongoDB topology into an OpenDC environment. - */ -public class TopologyParser(private val collection: MongoCollection<Document>) { - - /** - * Parse the topology from the specified [id]. - */ - public fun read(id: ObjectId): EnvironmentReader { - val nodes = mutableListOf<MachineDef>() - val random = Random(0) - - for (machine in fetchMachines(id)) { - val clusterId = machine.get("rack_id").toString() - val position = machine.getInteger("position") - - val processors = machine.getList("cpus", Document::class.java).flatMap { cpu -> - val cores = cpu.getInteger("numberOfCores") - val speed = cpu.get("clockRateMhz", Number::class.java).toDouble() - // TODO Remove hardcoding of vendor - val node = ProcessingNode("Intel", "amd64", cpu.getString("name"), cores) - List(cores) { coreId -> - ProcessingUnit(node, coreId, speed) - } - } - val memoryUnits = machine.getList("memories", Document::class.java).map { memory -> - MemoryUnit( - "Samsung", - memory.getString("name"), - memory.get("speedMbPerS", Number::class.java).toDouble(), - memory.get("sizeMb", Number::class.java).toLong() - ) - } - - val energyConsumptionW = machine.getList("cpus", Document::class.java).sumBy { it.getInteger("energyConsumptionW") }.toDouble() - - nodes.add( - MachineDef( - UUID(random.nextLong(), random.nextLong()), - "node-$clusterId-$position", - mapOf("cluster" to clusterId), - SimMachineModel(processors, memoryUnits), - LinearPowerModel(2 * energyConsumptionW, energyConsumptionW * 0.5) - ) - ) - } - - return object : EnvironmentReader { - override fun read(): List<MachineDef> = nodes - override fun close() {} - } - } - - /** - * Fetch the metadata of the topology. - */ - private fun fetchName(id: ObjectId): String { - return collection.aggregate( - listOf( - Aggregates.match(Filters.eq("_id", id)), - Aggregates.project(Projections.include("name")) - ) - ) - .first()!! - .getString("name") - } - - /** - * Fetch a topology from the database with the specified [id]. - */ - private fun fetchMachines(id: ObjectId): AggregateIterable<Document> { - return collection.aggregate( - listOf( - Aggregates.match(Filters.eq("_id", id)), - Aggregates.project(Projections.fields(Document("racks", "\$rooms.tiles.rack"))), - Aggregates.unwind("\$racks"), - Aggregates.unwind("\$racks"), - Aggregates.replaceRoot("\$racks"), - Aggregates.addFields(Field("machines.rack_id", "\$_id")), - Aggregates.unwind("\$machines"), - Aggregates.replaceRoot("\$machines") - ) - ) - } -} diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/runner/web/WebExperimentMonitor.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/runner/web/WebExperimentMonitor.kt deleted file mode 100644 index c913f82f..00000000 --- a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/runner/web/WebExperimentMonitor.kt +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright (c) 2020 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.runner.web - -import mu.KotlinLogging -import org.opendc.compute.api.Server -import org.opendc.compute.api.ServerState -import org.opendc.compute.service.driver.Host -import org.opendc.compute.service.driver.HostState -import org.opendc.experiments.capelin.monitor.ExperimentMonitor -import org.opendc.experiments.capelin.telemetry.HostEvent -import kotlin.math.max - -/** - * An [ExperimentMonitor] that tracks the aggregate metrics for each repeat. - */ -public class WebExperimentMonitor : ExperimentMonitor { - private val logger = KotlinLogging.logger {} - - override fun reportVmStateChange(time: Long, server: Server, newState: ServerState) {} - - override fun reportHostStateChange(time: Long, host: Host, newState: HostState) { - logger.debug { "Host ${host.uid} changed state $newState [$time]" } - } - - override fun reportHostSlice( - time: Long, - requestedBurst: Long, - grantedBurst: Long, - overcommissionedBurst: Long, - interferedBurst: Long, - cpuUsage: Double, - cpuDemand: Double, - powerDraw: Double, - numberOfDeployedImages: Int, - host: Host, - ) { - processHostEvent( - HostEvent( - time, - 5 * 60 * 1000L, - host, - numberOfDeployedImages, - requestedBurst, - grantedBurst, - overcommissionedBurst, - interferedBurst, - cpuUsage, - cpuDemand, - powerDraw, - host.model.cpuCount - ) - ) - } - - private var hostAggregateMetrics: AggregateHostMetrics = AggregateHostMetrics() - private val hostMetrics: MutableMap<Host, HostMetrics> = mutableMapOf() - - private fun processHostEvent(event: HostEvent) { - val slices = event.duration / SLICE_LENGTH - - hostAggregateMetrics = AggregateHostMetrics( - hostAggregateMetrics.totalRequestedBurst + event.requestedBurst, - hostAggregateMetrics.totalGrantedBurst + event.grantedBurst, - hostAggregateMetrics.totalOvercommittedBurst + event.overcommissionedBurst, - hostAggregateMetrics.totalInterferedBurst + event.interferedBurst, - hostAggregateMetrics.totalPowerDraw + (event.duration * event.powerDraw) / 3600, - hostAggregateMetrics.totalFailureSlices + if (event.host.state != HostState.UP) slices else 0, - hostAggregateMetrics.totalFailureVmSlices + if (event.host.state != HostState.UP) event.vmCount * slices else 0 - ) - - hostMetrics.compute(event.host) { _, prev -> - HostMetrics( - (event.cpuUsage.takeIf { event.host.state == HostState.UP } ?: 0.0) + (prev?.cpuUsage ?: 0.0), - (event.cpuDemand.takeIf { event.host.state == HostState.UP } ?: 0.0) + (prev?.cpuDemand ?: 0.0), - event.vmCount + (prev?.vmCount ?: 0), - 1 + (prev?.count ?: 0) - ) - } - } - - private val SLICE_LENGTH: Long = 5 * 60 * 1000 - - public data class AggregateHostMetrics( - val totalRequestedBurst: Long = 0, - val totalGrantedBurst: Long = 0, - val totalOvercommittedBurst: Long = 0, - val totalInterferedBurst: Long = 0, - val totalPowerDraw: Double = 0.0, - val totalFailureSlices: Long = 0, - val totalFailureVmSlices: Long = 0, - ) - - public data class HostMetrics( - val cpuUsage: Double, - val cpuDemand: Double, - val vmCount: Long, - val count: Long - ) - - private var provisionerMetrics: AggregateProvisionerMetrics = AggregateProvisionerMetrics() - - override fun reportProvisionerMetrics( - time: Long, - totalHostCount: Int, - availableHostCount: Int, - totalVmCount: Int, - activeVmCount: Int, - inactiveVmCount: Int, - waitingVmCount: Int, - failedVmCount: Int - ) { - provisionerMetrics = AggregateProvisionerMetrics( - max(totalVmCount, provisionerMetrics.vmTotalCount), - max(waitingVmCount, provisionerMetrics.vmWaitingCount), - max(activeVmCount, provisionerMetrics.vmActiveCount), - max(inactiveVmCount, provisionerMetrics.vmInactiveCount), - max(failedVmCount, provisionerMetrics.vmFailedCount), - ) - } - - public data class AggregateProvisionerMetrics( - val vmTotalCount: Int = 0, - val vmWaitingCount: Int = 0, - val vmActiveCount: Int = 0, - val vmInactiveCount: Int = 0, - val vmFailedCount: Int = 0 - ) - - override fun close() {} - - public fun getResult(): Result { - return Result( - hostAggregateMetrics.totalRequestedBurst, - hostAggregateMetrics.totalGrantedBurst, - hostAggregateMetrics.totalOvercommittedBurst, - hostAggregateMetrics.totalInterferedBurst, - hostMetrics.map { it.value.cpuUsage / it.value.count }.average(), - hostMetrics.map { it.value.cpuDemand / it.value.count }.average(), - hostMetrics.map { it.value.vmCount.toDouble() / it.value.count }.average(), - hostMetrics.map { it.value.vmCount.toDouble() / it.value.count }.maxOrNull() ?: 0.0, - hostAggregateMetrics.totalPowerDraw, - hostAggregateMetrics.totalFailureSlices, - hostAggregateMetrics.totalFailureVmSlices, - provisionerMetrics.vmTotalCount, - provisionerMetrics.vmWaitingCount, - provisionerMetrics.vmInactiveCount, - provisionerMetrics.vmFailedCount, - ) - } - - public data class Result( - public val totalRequestedBurst: Long, - public val totalGrantedBurst: Long, - public val totalOvercommittedBurst: Long, - public val totalInterferedBurst: Long, - public val meanCpuUsage: Double, - public val meanCpuDemand: Double, - public val meanNumDeployedImages: Double, - public val maxNumDeployedImages: Double, - public val totalPowerDraw: Double, - public val totalFailureSlices: Long, - public val totalFailureVmSlices: Long, - public val totalVmsSubmitted: Int, - public val totalVmsQueued: Int, - public val totalVmsFinished: Int, - public val totalVmsFailed: Int - ) -} diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/ApiClient.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/ApiClient.kt new file mode 100644 index 00000000..9f2656c4 --- /dev/null +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/ApiClient.kt @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.client + +import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule +import io.ktor.client.* +import io.ktor.client.features.auth.* +import io.ktor.client.features.auth.providers.* +import io.ktor.client.features.json.* +import io.ktor.client.request.* +import io.ktor.client.statement.* +import io.ktor.http.* +import org.opendc.web.client.model.* +import java.net.URI + +/** + * Client implementation for the OpenDC REST API (version 2). + * + * @param baseUrl The base url of the API. + * @param auth The authentication configuration for the client. + * @param client The HTTP client to use. + */ +public class ApiClient( + private val baseUrl: URI, + private val auth: AuthConfiguration, + private val audience: String = "https://api.opendc.org/v2/", + client: HttpClient = HttpClient {} +) : AutoCloseable { + /** + * The Ktor [HttpClient] that is used to communicate with the REST API. + */ + private val client = client.config { + install(JsonFeature) { + serializer = JacksonSerializer { + registerModule(JavaTimeModule()) + } + } + install(Auth) { + bearer { + loadTokens { requestToken() } + refreshTokens { requestToken() } + } + } + expectSuccess = false + } + + /** + * Retrieve the topology with the specified [id]. + */ + public suspend fun getPortfolio(id: String): Portfolio? { + val url = URLBuilder(Url(baseUrl)) + .path("portfolios", id) + .build() + return when (val result = client.get<ApiResult<Portfolio>>(url)) { + is ApiResult.Success -> result.data + else -> null + } + } + + /** + * Retrieve the scenario with the specified [id]. + */ + public suspend fun getScenario(id: String): Scenario? { + val url = URLBuilder(Url(baseUrl)) + .path("scenarios", id) + .build() + return when (val result = client.get<ApiResult<Scenario>>(url)) { + is ApiResult.Success -> result.data + else -> null + } + } + + /** + * Retrieve the topology with the specified [id]. + */ + public suspend fun getTopology(id: String): Topology? { + val url = URLBuilder(Url(baseUrl)) + .path("topologies", id) + .build() + return when (val result = client.get<ApiResult<Topology>>(url)) { + is ApiResult.Success -> result.data + else -> null + } + } + + /** + * Retrieve the available jobs. + */ + public suspend fun getJobs(): List<Job> { + val url = URLBuilder(Url(baseUrl)) + .path("jobs") + .build() + return when (val result = client.get<ApiResult<List<Job>>>(url)) { + is ApiResult.Success -> result.data + else -> emptyList() + } + } + + /** + * Update the specified job. + * + * @param id The identifier of the job. + * @param state The new state of the job. + * @param results The results of the job. + */ + public suspend fun updateJob(id: String, state: SimulationState, results: Map<String, Any> = emptyMap()): Boolean { + val url = URLBuilder(Url(baseUrl)) + .path("jobs", id) + .build() + + data class Request( + val state: SimulationState, + val results: Map<String, Any> + ) + + val res = client.post<HttpResponse> { + url(url) + contentType(ContentType.Application.Json) + body = Request(state, results) + } + return res.status.isSuccess() + } + + /** + * Request the auth token for the API. + */ + private suspend fun requestToken(): BearerTokens { + data class Request( + val audience: String, + @JsonProperty("grant_type") + val grantType: String, + @JsonProperty("client_id") + val clientId: String, + @JsonProperty("client_secret") + val clientSecret: String + ) + + data class Response( + @JsonProperty("access_token") + val accessToken: String, + @JsonProperty("token_type") + val tokenType: String, + val scope: String = "", + @JsonProperty("expires_in") + val expiresIn: Long + ) + + val result = client.post<Response> { + url(Url("https://${auth.domain}/oauth/token")) + contentType(ContentType.Application.Json) + body = Request(audience, "client_credentials", auth.clientId, auth.clientSecret) + } + + return BearerTokens(result.accessToken, "") + } + + override fun close() = client.close() +} diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/ApiResult.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/ApiResult.kt new file mode 100644 index 00000000..a3df01c5 --- /dev/null +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/ApiResult.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.client + +import com.fasterxml.jackson.annotation.JsonSubTypes +import com.fasterxml.jackson.annotation.JsonTypeInfo + +/** + * Generic response model for the OpenDC API. + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION) +@JsonSubTypes(JsonSubTypes.Type(ApiResult.Success::class), JsonSubTypes.Type(ApiResult.Failure::class)) +public sealed class ApiResult<out T> { + /** + * A response indicating everything is okay. + */ + public data class Success<out T>(val data: T) : ApiResult<T>() + + /** + * A response indicating a failure. + */ + public data class Failure<out T>(val message: String, val errors: List<String> = emptyList()) : ApiResult<T>() +} diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/AuthConfiguration.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/AuthConfiguration.kt new file mode 100644 index 00000000..5dbf2f59 --- /dev/null +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/AuthConfiguration.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.client + +/** + * The authentication configuration for the API client. + */ +public data class AuthConfiguration( + val domain: String, + val clientId: String, + val clientSecret: String +) diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/Job.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/Job.kt new file mode 100644 index 00000000..eeb65e49 --- /dev/null +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/Job.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.client.model + +import com.fasterxml.jackson.annotation.JsonProperty +import java.time.LocalDateTime + +/** + * A description of a simulation job. + */ +public data class Job( + @JsonProperty("_id") + val id: String, + val scenarioId: String, + val state: SimulationState, + val heartbeat: LocalDateTime, + val results: Map<String, Any> +) diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/Machine.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/Machine.kt new file mode 100644 index 00000000..86d2d46f --- /dev/null +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/Machine.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.client.model + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties +import com.fasterxml.jackson.annotation.JsonProperty + +/** + * A machine in a rack. + */ +@JsonIgnoreProperties("id_legacy") +public data class Machine( + @JsonProperty("_id") + val id: String, + val position: Int, + val cpus: List<ProcessingUnit> = emptyList(), + val gpus: List<ProcessingUnit> = emptyList(), + @JsonProperty("memories") + val memory: List<MemoryUnit> = emptyList(), + @JsonProperty("storages") + val storage: List<MemoryUnit> = emptyList(), + val rackId: String? = null +) diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/MemoryUnit.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/MemoryUnit.kt new file mode 100644 index 00000000..11e794e8 --- /dev/null +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/MemoryUnit.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.client.model + +import com.fasterxml.jackson.annotation.JsonProperty + +/** + * A memory unit in a system. + */ +public data class MemoryUnit( + @JsonProperty("_id") + val id: String, + val name: String, + val speedMbPerS: Double, + val sizeMb: Double, + val energyConsumptionW: Double +) diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/OperationalPhenomena.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/OperationalPhenomena.kt new file mode 100644 index 00000000..ef5b4902 --- /dev/null +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/OperationalPhenomena.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.client.model + +/** + * Object describing the enabled operational phenomena for a scenario. + */ +public data class OperationalPhenomena( + val failuresEnabled: Boolean, + val performanceInterferenceEnabled: Boolean, + val schedulerName: String +) diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/Portfolio.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/Portfolio.kt new file mode 100644 index 00000000..6904920b --- /dev/null +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/Portfolio.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.client.model + +import com.fasterxml.jackson.annotation.JsonProperty + +/** + * A portfolio in OpenDC. + */ +public data class Portfolio( + @JsonProperty("_id") + val id: String, + val projectId: String, + val name: String, + @JsonProperty("scenarioIds") + val scenarios: Set<String>, + val targets: PortfolioTargets +) diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/PortfolioTargets.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/PortfolioTargets.kt new file mode 100644 index 00000000..07c11c19 --- /dev/null +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/PortfolioTargets.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.client.model + +/** + * The targets of a portfolio. + */ +public data class PortfolioTargets(val enabledMetrics: Set<String>, val repeatsPerScenario: Int) diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/ProcessingUnit.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/ProcessingUnit.kt new file mode 100644 index 00000000..449b5c43 --- /dev/null +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/ProcessingUnit.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.client.model + +import com.fasterxml.jackson.annotation.JsonProperty + +/** + * A CPU model. + */ +public data class ProcessingUnit( + @JsonProperty("_id") + val id: String, + val name: String, + val clockRateMhz: Double, + val numberOfCores: Int, + val energyConsumptionW: Double +) diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/Rack.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/Rack.kt new file mode 100644 index 00000000..a0464388 --- /dev/null +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/Rack.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.client.model + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties +import com.fasterxml.jackson.annotation.JsonProperty + +/** + * A rack in a datacenter. + */ +@JsonIgnoreProperties("id_legacy") +public class Rack( + @JsonProperty("_id") + val id: String, + val name: String, + val capacity: Int, + val powerCapacityW: Double, + val machines: List<Machine> +) diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/Room.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/Room.kt new file mode 100644 index 00000000..f1b8f946 --- /dev/null +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/Room.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.client.model + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties +import com.fasterxml.jackson.annotation.JsonProperty + +/** + * A room in a datacenter. + */ +@JsonIgnoreProperties("id_legacy") +public data class Room( + @JsonProperty("_id") + val id: String, + val name: String, + val tiles: Set<RoomTile>, + val topologyId: String? = null, +) diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/RoomTile.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/RoomTile.kt new file mode 100644 index 00000000..0b956262 --- /dev/null +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/RoomTile.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.client.model + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties +import com.fasterxml.jackson.annotation.JsonProperty + +/** + * A room tile. + */ +@JsonIgnoreProperties("id_legacy") +public data class RoomTile( + @JsonProperty("_id") + val id: String, + val positionX: Double, + val positionY: Double, + val rack: Rack? = null, + val roomId: String? = null, +) diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/Scenario.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/Scenario.kt new file mode 100644 index 00000000..851ff980 --- /dev/null +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/Scenario.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.client.model + +import com.fasterxml.jackson.annotation.JsonProperty + +/** + * A simulation scenario. + */ +public data class Scenario( + @JsonProperty("_id") + val id: String, + val portfolioId: String, + val name: String, + val trace: ScenarioTrace, + val topology: ScenarioTopology, + @JsonProperty("operational") + val operationalPhenomena: OperationalPhenomena +) diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/ScenarioTopology.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/ScenarioTopology.kt new file mode 100644 index 00000000..2b90f7ef --- /dev/null +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/ScenarioTopology.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.client.model + +/** + * The topology details for a scenario. + */ +public data class ScenarioTopology(val topologyId: String) diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/ScenarioTrace.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/ScenarioTrace.kt new file mode 100644 index 00000000..adff6d97 --- /dev/null +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/ScenarioTrace.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.client.model + +/** + * The trace details of a scenario. + */ +public data class ScenarioTrace(val traceId: String, val loadSamplingFraction: Double) diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/SimulationState.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/SimulationState.kt new file mode 100644 index 00000000..2eadd747 --- /dev/null +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/SimulationState.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.client.model + +/** + * The state of a simulation job. + */ +public enum class SimulationState { + QUEUED, CLAIMED, RUNNING, FINISHED, FAILED +} diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/Topology.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/Topology.kt new file mode 100644 index 00000000..b59aba42 --- /dev/null +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/Topology.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.client.model + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties +import com.fasterxml.jackson.annotation.JsonProperty + +/** + * Model for an OpenDC topology. + */ +@JsonIgnoreProperties("id_legacy", "datacenter_id_legacy", "datetimeLastUpdated", "datetimeLastEdited") +public data class Topology( + @JsonProperty("_id") + val id: String, + val projectId: String, + val name: String, + val rooms: Set<Room>, +) diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/Main.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/Main.kt new file mode 100644 index 00000000..59308e11 --- /dev/null +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/Main.kt @@ -0,0 +1,348 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.runner + +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.parameters.options.* +import com.github.ajalt.clikt.parameters.types.file +import com.github.ajalt.clikt.parameters.types.long +import kotlinx.coroutines.* +import mu.KotlinLogging +import org.opendc.compute.workload.* +import org.opendc.compute.workload.topology.HostSpec +import org.opendc.compute.workload.topology.Topology +import org.opendc.compute.workload.topology.apply +import org.opendc.compute.workload.util.PerformanceInterferenceReader +import org.opendc.simulator.compute.kernel.interference.VmInterferenceModel +import org.opendc.simulator.compute.model.MachineModel +import org.opendc.simulator.compute.model.MemoryUnit +import org.opendc.simulator.compute.model.ProcessingNode +import org.opendc.simulator.compute.model.ProcessingUnit +import org.opendc.simulator.compute.power.LinearPowerModel +import org.opendc.simulator.compute.power.SimplePowerDriver +import org.opendc.simulator.core.runBlockingSimulation +import org.opendc.telemetry.compute.collectServiceMetrics +import org.opendc.telemetry.sdk.metrics.export.CoroutineMetricReader +import org.opendc.web.client.ApiClient +import org.opendc.web.client.AuthConfiguration +import org.opendc.web.client.model.Scenario +import java.io.File +import java.net.URI +import java.time.Duration +import java.util.* +import org.opendc.web.client.model.Portfolio as ClientPortfolio +import org.opendc.web.client.model.Topology as ClientTopology + +private val logger = KotlinLogging.logger {} + +/** + * Represents the CLI command for starting the OpenDC web runner. + */ +class RunnerCli : CliktCommand(name = "runner") { + /** + * The URL to the OpenDC API. + */ + private val apiUrl by option( + "--api-url", + help = "url to the OpenDC API", + envvar = "OPENDC_API_URL" + ) + .convert { URI(it) } + .default(URI("https://api.opendc.org/v2")) + + /** + * The auth domain to use. + */ + private val authDomain by option( + "--auth-domain", + help = "auth domain of the OpenDC API", + envvar = "AUTH0_DOMAIN" + ) + .required() + + /** + * The auth client ID to use. + */ + private val authClientId by option( + "--auth-id", + help = "auth client id of the OpenDC API", + envvar = "AUTH0_CLIENT_ID" + ) + .required() + + /** + * The auth client secret to use. + */ + private val authClientSecret by option( + "--auth-secret", + help = "auth client secret of the OpenDC API", + envvar = "AUTH0_CLIENT_SECRET" + ) + .required() + + /** + * The path to the traces directory. + */ + private val tracePath by option( + "--traces", + help = "path to the directory containing the traces", + envvar = "OPENDC_TRACES" + ) + .file(canBeFile = false) + .defaultLazy { File("traces/") } + + /** + * The maximum duration of a single experiment run. + */ + private val runTimeout by option( + "--run-timeout", + help = "maximum duration of experiment in seconds", + envvar = "OPENDC_RUN_TIMEOUT" + ) + .long() + .default(60L * 3) // Experiment may run for a maximum of three minutes + + /** + * Converge a single scenario. + */ + private suspend fun runScenario(portfolio: ClientPortfolio, scenario: Scenario, topology: Topology): List<WebComputeMetricExporter.Result> { + val id = scenario.id + + logger.info { "Constructing performance interference model" } + + val workloadLoader = ComputeWorkloadLoader(tracePath) + val interferenceGroups = let { + val path = tracePath.resolve(scenario.trace.traceId).resolve("performance-interference-model.json") + val operational = scenario.operationalPhenomena + val enabled = operational.performanceInterferenceEnabled + + if (!enabled || !path.exists()) { + return@let null + } + + PerformanceInterferenceReader().read(path.inputStream()) + } + + val targets = portfolio.targets + val results = (0 until targets.repeatsPerScenario).map { repeat -> + logger.info { "Starting repeat $repeat" } + withTimeout(runTimeout * 1000) { + val interferenceModel = interferenceGroups?.let { VmInterferenceModel(it, Random(repeat.toLong())) } + runRepeat(scenario, repeat, topology, workloadLoader, interferenceModel) + } + } + + logger.info { "Finished simulation for scenario $id" } + + return results + } + + /** + * Converge a single repeat. + */ + private suspend fun runRepeat( + scenario: Scenario, + repeat: Int, + topology: Topology, + workloadLoader: ComputeWorkloadLoader, + interferenceModel: VmInterferenceModel? + ): WebComputeMetricExporter.Result { + val exporter = WebComputeMetricExporter() + + try { + runBlockingSimulation { + val workloadName = scenario.trace.traceId + val workloadFraction = scenario.trace.loadSamplingFraction + + val seeder = Random(repeat.toLong()) + + val operational = scenario.operationalPhenomena + val computeScheduler = createComputeScheduler(operational.schedulerName, seeder) + val workload = trace(workloadName).sampleByLoad(workloadFraction) + + val failureModel = + if (operational.failuresEnabled) + grid5000(Duration.ofDays(7)) + else + null + + val simulator = ComputeWorkloadRunner( + coroutineContext, + clock, + computeScheduler, + failureModel, + interferenceModel.takeIf { operational.performanceInterferenceEnabled } + ) + + val metricReader = CoroutineMetricReader(this, simulator.producers, exporter, exportInterval = Duration.ofHours(1)) + + try { + // Instantiate the topology onto the simulator + simulator.apply(topology) + // Converge workload trace + simulator.run(workload.resolve(workloadLoader, seeder), seeder.nextLong()) + } finally { + simulator.close() + metricReader.close() + } + + val serviceMetrics = collectServiceMetrics(simulator.producers[0]) + logger.debug { + "Scheduler " + + "Success=${serviceMetrics.attemptsSuccess} " + + "Failure=${serviceMetrics.attemptsFailure} " + + "Error=${serviceMetrics.attemptsError} " + + "Pending=${serviceMetrics.serversPending} " + + "Active=${serviceMetrics.serversActive}" + } + } + } catch (cause: Throwable) { + logger.warn(cause) { "Experiment failed" } + } + + return exporter.getResult() + } + + private val POLL_INTERVAL = 30000L // ms = 30 s + private val HEARTBEAT_INTERVAL = 60000L // ms = 1 min + + override fun run(): Unit = runBlocking(Dispatchers.Default) { + logger.info { "Starting OpenDC web runner" } + + val client = ApiClient(baseUrl = apiUrl, AuthConfiguration(authDomain, authClientId, authClientSecret)) + val manager = ScenarioManager(client) + + logger.info { "Watching for queued scenarios" } + + while (true) { + val scenario = manager.findNext() + + if (scenario == null) { + delay(POLL_INTERVAL) + continue + } + + val id = scenario.id + + logger.info { "Found queued scenario $id: attempting to claim" } + + if (!manager.claim(id)) { + logger.info { "Failed to claim scenario" } + continue + } + + coroutineScope { + // Launch heartbeat process + val heartbeat = launch { + while (true) { + manager.heartbeat(id) + delay(HEARTBEAT_INTERVAL) + } + } + + try { + val scenarioModel = client.getScenario(id)!! + val portfolio = client.getPortfolio(scenarioModel.portfolioId)!! + val environment = convert(client.getTopology(scenarioModel.topology.topologyId)!!) + val results = runScenario(portfolio, scenarioModel, environment) + + logger.info { "Writing results to database" } + + manager.finish(id, results) + + logger.info { "Successfully finished scenario $id" } + } catch (e: Exception) { + logger.error(e) { "Scenario failed to finish" } + manager.fail(id) + } finally { + heartbeat.cancel() + } + } + } + } + + /** + * Convert the specified [topology] into an [Topology] understood by OpenDC. + */ + private fun convert(topology: ClientTopology): Topology { + return object : Topology { + + override fun resolve(): List<HostSpec> { + val res = mutableListOf<HostSpec>() + val random = Random(0) + + val machines = topology.rooms.asSequence() + .flatMap { room -> + room.tiles.flatMap { tile -> + tile.rack?.machines?.map { machine -> tile.rack to machine } ?: emptyList() + } + } + for ((rack, machine) in machines) { + val clusterId = rack.id + val position = machine.position + + val processors = machine.cpus.flatMap { cpu -> + val cores = cpu.numberOfCores + val speed = cpu.clockRateMhz + // TODO Remove hard coding of vendor + val node = ProcessingNode("Intel", "amd64", cpu.name, cores) + List(cores) { coreId -> + ProcessingUnit(node, coreId, speed) + } + } + val memoryUnits = machine.memory.map { memory -> + MemoryUnit( + "Samsung", + memory.name, + memory.speedMbPerS, + memory.sizeMb.toLong() + ) + } + + val energyConsumptionW = machine.cpus.sumOf { it.energyConsumptionW } + val powerModel = LinearPowerModel(2 * energyConsumptionW, energyConsumptionW * 0.5) + val powerDriver = SimplePowerDriver(powerModel) + + val spec = HostSpec( + UUID(random.nextLong(), random.nextLong()), + "node-$clusterId-$position", + mapOf("cluster" to clusterId), + MachineModel(processors, memoryUnits), + powerDriver + ) + + res += spec + } + + return res + } + + override fun toString(): String = "WebRunnerTopologyFactory" + } + } +} + +/** + * Main entry point of the runner. + */ +fun main(args: Array<String>): Unit = RunnerCli().main(args) diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/ScenarioManager.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/ScenarioManager.kt new file mode 100644 index 00000000..1ee835a6 --- /dev/null +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/ScenarioManager.kt @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.runner + +import org.opendc.web.client.ApiClient +import org.opendc.web.client.model.Job +import org.opendc.web.client.model.SimulationState + +/** + * Manages the queue of scenarios that need to be processed. + */ +public class ScenarioManager(private val client: ApiClient) { + /** + * Find the next job that the simulator needs to process. + */ + public suspend fun findNext(): Job? { + return client.getJobs().firstOrNull() + } + + /** + * Claim the simulation job with the specified id. + */ + public suspend fun claim(id: String): Boolean { + return client.updateJob(id, SimulationState.CLAIMED) + } + + /** + * Update the heartbeat of the specified scenario. + */ + public suspend fun heartbeat(id: String) { + client.updateJob(id, SimulationState.RUNNING) + } + + /** + * Mark the scenario as failed. + */ + public suspend fun fail(id: String) { + client.updateJob(id, SimulationState.FAILED) + } + + /** + * Persist the specified results. + */ + public suspend fun finish(id: String, results: List<WebComputeMetricExporter.Result>) { + client.updateJob( + id, SimulationState.FINISHED, + mapOf( + "total_requested_burst" to results.map { it.totalActiveTime + it.totalIdleTime }, + "total_granted_burst" to results.map { it.totalActiveTime }, + "total_overcommitted_burst" to results.map { it.totalStealTime }, + "total_interfered_burst" to results.map { it.totalLostTime }, + "mean_cpu_usage" to results.map { it.meanCpuUsage }, + "mean_cpu_demand" to results.map { it.meanCpuDemand }, + "mean_num_deployed_images" to results.map { it.meanNumDeployedImages }, + "max_num_deployed_images" to results.map { it.maxNumDeployedImages }, + "total_power_draw" to results.map { it.totalPowerDraw }, + "total_failure_slices" to results.map { it.totalFailureSlices }, + "total_failure_vm_slices" to results.map { it.totalFailureVmSlices }, + "total_vms_submitted" to results.map { it.totalVmsSubmitted }, + "total_vms_queued" to results.map { it.totalVmsQueued }, + "total_vms_finished" to results.map { it.totalVmsFinished }, + "total_vms_failed" to results.map { it.totalVmsFailed } + ) + ) + } +} diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/WebComputeMetricExporter.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/WebComputeMetricExporter.kt new file mode 100644 index 00000000..7913660d --- /dev/null +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/WebComputeMetricExporter.kt @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.runner + +import org.opendc.telemetry.compute.ComputeMetricExporter +import org.opendc.telemetry.compute.ComputeMonitor +import org.opendc.telemetry.compute.table.HostData +import org.opendc.telemetry.compute.table.ServiceData +import kotlin.math.max +import kotlin.math.roundToLong + +/** + * A [ComputeMonitor] that tracks the aggregate metrics for each repeat. + */ +class WebComputeMetricExporter : ComputeMetricExporter() { + override fun record(data: HostData) { + val slices = data.downtime / SLICE_LENGTH + + hostAggregateMetrics = AggregateHostMetrics( + hostAggregateMetrics.totalActiveTime + data.cpuActiveTime, + hostAggregateMetrics.totalIdleTime + data.cpuIdleTime, + hostAggregateMetrics.totalStealTime + data.cpuStealTime, + hostAggregateMetrics.totalLostTime + data.cpuLostTime, + hostAggregateMetrics.totalPowerDraw + data.powerTotal, + hostAggregateMetrics.totalFailureSlices + slices, + hostAggregateMetrics.totalFailureVmSlices + data.guestsRunning * slices + ) + + hostMetrics.compute(data.host.id) { _, prev -> + HostMetrics( + data.cpuUsage + (prev?.cpuUsage ?: 0.0), + data.cpuDemand + (prev?.cpuDemand ?: 0.0), + data.guestsRunning + (prev?.instanceCount ?: 0), + 1 + (prev?.count ?: 0) + ) + } + } + + private var hostAggregateMetrics: AggregateHostMetrics = AggregateHostMetrics() + private val hostMetrics: MutableMap<String, HostMetrics> = mutableMapOf() + private val SLICE_LENGTH: Long = 5 * 60L + + data class AggregateHostMetrics( + val totalActiveTime: Long = 0L, + val totalIdleTime: Long = 0L, + val totalStealTime: Long = 0L, + val totalLostTime: Long = 0L, + val totalPowerDraw: Double = 0.0, + val totalFailureSlices: Double = 0.0, + val totalFailureVmSlices: Double = 0.0, + ) + + data class HostMetrics( + val cpuUsage: Double, + val cpuDemand: Double, + val instanceCount: Long, + val count: Long + ) + + private var serviceMetrics: AggregateServiceMetrics = AggregateServiceMetrics() + + override fun record(data: ServiceData) { + serviceMetrics = AggregateServiceMetrics( + max(data.attemptsSuccess, serviceMetrics.vmTotalCount), + max(data.serversPending, serviceMetrics.vmWaitingCount), + max(data.serversActive, serviceMetrics.vmActiveCount), + max(0, serviceMetrics.vmInactiveCount), + max(data.attemptsFailure, serviceMetrics.vmFailedCount), + ) + } + + data class AggregateServiceMetrics( + val vmTotalCount: Int = 0, + val vmWaitingCount: Int = 0, + val vmActiveCount: Int = 0, + val vmInactiveCount: Int = 0, + val vmFailedCount: Int = 0 + ) + + fun getResult(): Result { + return Result( + hostAggregateMetrics.totalActiveTime, + hostAggregateMetrics.totalIdleTime, + hostAggregateMetrics.totalStealTime, + hostAggregateMetrics.totalLostTime, + hostMetrics.map { it.value.cpuUsage / it.value.count }.average(), + hostMetrics.map { it.value.cpuDemand / it.value.count }.average(), + hostMetrics.map { it.value.instanceCount.toDouble() / it.value.count }.average(), + hostMetrics.map { it.value.instanceCount.toDouble() / it.value.count }.maxOrNull() ?: 0.0, + hostAggregateMetrics.totalPowerDraw, + hostAggregateMetrics.totalFailureSlices.roundToLong(), + hostAggregateMetrics.totalFailureVmSlices.roundToLong(), + serviceMetrics.vmTotalCount, + serviceMetrics.vmWaitingCount, + serviceMetrics.vmInactiveCount, + serviceMetrics.vmFailedCount, + ) + } + + data class Result( + val totalActiveTime: Long, + val totalIdleTime: Long, + val totalStealTime: Long, + val totalLostTime: Long, + val meanCpuUsage: Double, + val meanCpuDemand: Double, + val meanNumDeployedImages: Double, + val maxNumDeployedImages: Double, + val totalPowerDraw: Double, + val totalFailureSlices: Long, + val totalFailureVmSlices: Long, + val totalVmsSubmitted: Int, + val totalVmsQueued: Int, + val totalVmsFinished: Int, + val totalVmsFailed: Int + ) +} diff --git a/opendc-web/opendc-web-runner/src/main/resources/log4j2.xml b/opendc-web/opendc-web-runner/src/main/resources/log4j2.xml index 503bc5dc..ad99cc00 100644 --- a/opendc-web/opendc-web-runner/src/main/resources/log4j2.xml +++ b/opendc-web/opendc-web-runner/src/main/resources/log4j2.xml @@ -36,7 +36,7 @@ <AppenderRef ref="Console"/> <AppenderRef ref="Sentry"/> </Logger> - <Logger name="org.opendc.runner" level="info" additivity="false"> + <Logger name="org.opendc.web.runner" level="info" additivity="false"> <AppenderRef ref="Console"/> <AppenderRef ref="Sentry"/> </Logger> diff --git a/opendc-web/opendc-web-runner/src/test/kotlin/org/opendc/web/client/ApiClientTest.kt b/opendc-web/opendc-web-runner/src/test/kotlin/org/opendc/web/client/ApiClientTest.kt new file mode 100644 index 00000000..3a0730a6 --- /dev/null +++ b/opendc-web/opendc-web-runner/src/test/kotlin/org/opendc/web/client/ApiClientTest.kt @@ -0,0 +1,264 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.client + +import io.ktor.client.* +import io.ktor.client.engine.mock.* +import io.ktor.http.* +import kotlinx.coroutines.runBlocking +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Test +import java.net.URI + +/** + * Test suite for the [ApiClient] class. + */ +class ApiClientTest { + /** + * The Ktor [HttpClient] instance. + */ + private val ktor = HttpClient(MockEngine) { + engine { + addHandler { request -> + when (request.url.fullPath) { + "/oauth/token" -> { + val responseHeaders = headersOf("Content-Type" to listOf(ContentType.Application.Json.toString())) + respond( + """ + { + "access_token": "eyJz93a...k4laUWw", + "token_type": "Bearer", + "expires_in": 86400 + } + """.trimIndent(), + headers = responseHeaders + ) + } + "/portfolios/5fda5daa97dca438e7cb0a4c" -> { + val responseHeaders = headersOf("Content-Type" to listOf(ContentType.Application.Json.toString())) + respond( + """ + { + "data": { + "_id": "string", + "projectId": "string", + "name": "string", + "scenarioIds": [ + "string" + ], + "targets": { + "enabledMetrics": [ + "string" + ], + "repeatsPerScenario": 0 + } + } + } + """.trimIndent(), + headers = responseHeaders + ) + } + "/portfolios/x" -> { + val responseHeaders = headersOf("Content-Type" to listOf(ContentType.Application.Json.toString())) + respond( + """ + { + "message": "Not Found" + } + """.trimIndent(), + headers = responseHeaders, status = HttpStatusCode.NotFound + ) + } + "/scenarios/5fda5db297dca438e7cb0a4d" -> { + val responseHeaders = headersOf("Content-Type" to listOf(ContentType.Application.Json.toString())) + respond( + """ + { + "data": { + "_id": "string", + "portfolioId": "string", + "name": "string", + "trace": { + "traceId": "string", + "loadSamplingFraction": 0 + }, + "topology": { + "topologyId": "string" + }, + "operational": { + "failuresEnabled": true, + "performanceInterferenceEnabled": true, + "schedulerName": "string" + } + } + } + """.trimIndent(), + headers = responseHeaders + ) + } + "/scenarios/x" -> { + val responseHeaders = headersOf("Content-Type" to listOf(ContentType.Application.Json.toString())) + respond( + """ + { + "message": "Not Found" + } + """.trimIndent(), + headers = responseHeaders, status = HttpStatusCode.NotFound + ) + } + "/topologies/5f9825a6cf6e4c24e380b86f" -> { + val responseHeaders = headersOf("Content-Type" to listOf(ContentType.Application.Json.toString())) + respond( + """ + { + "data": { + "_id": "string", + "projectId": "string", + "name": "string", + "rooms": [ + { + "_id": "string", + "name": "string", + "tiles": [ + { + "_id": "string", + "positionX": 0, + "positionY": 0, + "rack": { + "_id": "string", + "name": "string", + "capacity": 0, + "powerCapacityW": 0, + "machines": [ + { + "_id": "string", + "position": 0, + "cpus": [ + { + "_id": "string", + "name": "string", + "clockRateMhz": 0, + "numberOfCores": 0 + } + ], + "gpus": [ + { + "_id": "string", + "name": "string", + "clockRateMhz": 0, + "numberOfCores": 0 + } + ], + "memories": [ + { + "_id": "string", + "name": "string", + "speedMbPerS": 0, + "sizeMb": 0 + } + ], + "storages": [ + { + "_id": "string", + "name": "string", + "speedMbPerS": 0, + "sizeMb": 0 + } + ] + } + ] + } + } + ] + } + ] + } + } + """.trimIndent(), + headers = responseHeaders + ) + } + "/topologies/x" -> { + val responseHeaders = + headersOf("Content-Type" to listOf(ContentType.Application.Json.toString())) + respond( + """ + { + "message": "Not Found" + } + """.trimIndent(), + headers = responseHeaders, status = HttpStatusCode.NotFound + ) + } + else -> error("Unhandled ${request.url}") + } + } + } + } + + private val auth = AuthConfiguration("auth.opendc.org", "a", "b") + + @Test + fun testPortfolioExists(): Unit = runBlocking { + val client = ApiClient(URI("http://localhost:8081"), auth, client = ktor) + val portfolio = client.getPortfolio("5fda5daa97dca438e7cb0a4c") + assertNotNull(portfolio) + } + + @Test + fun testPortfolioDoesNotExists(): Unit = runBlocking { + val client = ApiClient(URI("http://localhost:8081"), auth, client = ktor) + val portfolio = client.getPortfolio("x") + assertNull(portfolio) + } + + @Test + fun testScenarioExists(): Unit = runBlocking { + val client = ApiClient(URI("http://localhost:8081"), auth, client = ktor) + val scenario = client.getScenario("5fda5db297dca438e7cb0a4d") + assertNotNull(scenario) + } + + @Test + fun testScenarioDoesNotExists(): Unit = runBlocking { + val client = ApiClient(URI("http://localhost:8081"), auth, client = ktor) + val scenario = client.getScenario("x") + assertNull(scenario) + } + + @Test + fun testTopologyExists(): Unit = runBlocking { + val client = ApiClient(URI("http://localhost:8081"), auth, client = ktor) + val topology = client.getTopology("5f9825a6cf6e4c24e380b86f") + assertNotNull(topology) + } + + @Test + fun testTopologyDoesNotExists(): Unit = runBlocking { + val client = ApiClient(URI("http://localhost:8081"), auth, client = ktor) + val topology = client.getTopology("x") + assertNull(topology) + } +} diff --git a/opendc-web/opendc-web-ui/.eslintrc b/opendc-web/opendc-web-ui/.eslintrc new file mode 100644 index 00000000..1446fa02 --- /dev/null +++ b/opendc-web/opendc-web-ui/.eslintrc @@ -0,0 +1,16 @@ +{ + "extends": ["next", "eslint:recommended"], + "env": { + "browser": true, + "node": true, + "es6": true + }, + "overrides": [ + { + "files": ["src/**/*.test.js"], + "env": { + "jest": true + } + } + ] +} diff --git a/opendc-web/opendc-web-ui/.gitignore b/opendc-web/opendc-web-ui/.gitignore index 4fa931fe..3340b9ee 100644 --- a/opendc-web/opendc-web-ui/.gitignore +++ b/opendc-web/opendc-web-ui/.gitignore @@ -22,7 +22,9 @@ yarn-error.log* /.idea # Environment variables -.env +.env.local # Sass output *.css + +/.next diff --git a/opendc-web/opendc-web-ui/Dockerfile b/opendc-web/opendc-web-ui/Dockerfile index 86a18a03..15a92068 100644 --- a/opendc-web/opendc-web-ui/Dockerfile +++ b/opendc-web/opendc-web-ui/Dockerfile @@ -1,22 +1,28 @@ -FROM node:15 AS staging +FROM node:16 AS staging MAINTAINER OpenDC Maintainers <opendc@atlarge-research.com> # Copy package details COPY ./package.json ./yarn.lock /opendc/ -RUN cd /opendc && yarn +RUN cd /opendc && yarn install --frozen-lockfile # Build frontend -FROM node:15 AS build +FROM node:16 AS build COPY ./ /opendc COPY --from=staging /opendc/node_modules /opendc/node_modules RUN cd /opendc/ \ - && export REACT_APP_OAUTH_CLIENT_ID="\\\$REACT_APP_OAUTH_CLIENT_ID" \ + # Environmental variables that will be substituted during image runtime + && export NEXT_PUBLIC_API_BASE_URL="%%NEXT_PUBLIC_API_BASE_URL%%" \ + NEXT_PUBLIC_SENTRY_DSN="%%NEXT_PUBLIC_SENTRY_DSN%%" \ + NEXT_PUBLIC_AUTH0_DOMAIN="%%NEXT_PUBLIC_AUTH0_DOMAIN%%" \ + NEXT_PUBLIC_AUTH0_CLIENT_ID="%%NEXT_PUBLIC_AUTH0_CLIENT_ID%%" \ + NEXT_PUBLIC_AUTH0_AUDIENCE="%%NEXT_PUBLIC_AUTH0_AUDIENCE%%" \ && yarn build \ - && mv build/index.html build/index.html.template + && yarn cache clean --all \ + && mv .next .next.template -# Setup nginx to serve the frontend -FROM nginx:1.20 -COPY --from=build /opendc/scripts/envsubst-html.sh /docker-entrypoint.d/00-envsubst-html.sh -COPY --from=build /opendc/build /usr/share/nginx/html -COPY nginx.conf /etc/nginx/conf.d/default.conf + +FROM node:16-slim +COPY --from=build /opendc /opendc +WORKDIR /opendc +CMD ./scripts/envsubst.sh; yarn start diff --git a/opendc-web/opendc-web-ui/README.md b/opendc-web/opendc-web-ui/README.md index f3a58e7a..d562f2a4 100644 --- a/opendc-web/opendc-web-ui/README.md +++ b/opendc-web/opendc-web-ui/README.md @@ -7,16 +7,19 @@ Collaborative Datacenter Simulation and Exploration for Everybody </p> -The user-facing component of the OpenDC stack, allowing users to build and interact with their own (virtual) datacenters. Built in *React.js* and *Redux*, with the help of `create-react-app`. - +The user-facing component of the OpenDC stack, allowing users to build and interact with their own (virtual) +datacenters. Built in *React.js* and *Redux*, with the help of [Next.js](https://nextjs.org/). ## Get Up and Running -Looking for the full OpenDC stack? Check out [the main OpenDC repo](https://github.com/atlarge-research/opendc) for instructions on how to set up a Docker container with all of OpenDC, without the hassle of running each of the components manually. +Looking for the full OpenDC stack? Check out the [deployment guide](../../docs/deploy.md) for instructions on +how to set up a Docker container with all of OpenDC, without the hassle of running each of the components manually. ### Installation -To get started, you'll need the [Node.js environment](https://nodejs.org) and the [Yarn package manager](https://yarnpkg.com). Once you have those installed, run the following command from the root directory of this repo: +To get started, you'll need the [Node.js environment](https://nodejs.org) and +the [Yarn package manager](https://yarnpkg.com). Once you have those installed, run the following command from the root +directory of this repo: ```bash yarn @@ -24,17 +27,22 @@ yarn ### Running the development server -First, you need to have a Google OAuth client ID set up. Check the [documentation of the main OpenDC repo](https://github.com/atlarge-research/opendc) if you're not sure how to do this. Once you have such an ID, you need to set it as environment variable `REACT_APP_OAUTH_CLIENT_ID`. One way of doing this is to create an `.env` file with content `REACT_APP_OAUTH_CLIENT_ID=YOUR_ID` (`YOUR_ID` without quotes), in the root directory of this repo. +First, you need to set up an [Auth0](https://auth0.com) application. Check +the [documentation in the deployment guide](../../docs/deploy.md) if you're not sure how to do this. Once you have such +an ID, you need to set it as environment variable `NEXT_PUBLIC_AUTH0_CLIENT_ID` and `NEXT_PUBLIC_AUTH0_DOMAIN` +One way of doing this is to create an `.env.local` file with content `NEXT_PUBLIC_AUTH0_CLIENT_ID=YOUR_ID` and +`NEXT_PUBLIC_AUTH0_DOMAIN=YOUR_AUTH0_DOMAIN` in the root directory of this repo. Once you've set this variable, start the OpenDC `docker-compose` setup. See the root README for instructions on this. - + Now, you're ready to start the development server: ```bash -yarn start +yarn dev ``` -This will start a development server running on [`localhost:3000`](http://localhost:3000), watching for changes you make to the code and rebuilding automatically when you save changes. +This will start a development server running on [`localhost:3000`](http://localhost:3000), watching for changes you make +to the code and rebuilding automatically when you save changes. To compile everything for camera-ready deployment, use the following command: @@ -42,47 +50,68 @@ To compile everything for camera-ready deployment, use the following command: yarn build ``` +You can run the production server using Next.js as follows: + +```bash +yarn start +``` ## Architecture -The codebase follows a standard React.js structure, with static assets being contained in the `public` folder, while dynamic components and their styles are contained in `src`. The app uses client-side routing (with `react-router`), meaning that the only HTML file needing to be served is a `index.html` file. +The codebase follows a standard React.js structure, with static assets being contained in the `public` folder, while +dynamic components and their styles are contained in `src`. ### Pages -All pages are represented by a component in the `src/pages` directory. There are components for the following pages: +All pages are represented by a component in the `src/pages` directory, following +the [Next.js conventions](https://nextjs.org/docs/routing/introduction) for routing. There are components for the +following pages: -**Home.js** - Entry page (`/`) +**index.js** - Entry page (`/`) -**Projects.js** - Overview of projects of the user (`/projects`) +**projects/index.js** - Overview of projects of the user (`/projects`) -**App.js** - Main application, with datacenter construction and simulation UI (`/projects/:projectId` and `/projects/:projectId/portfolios/:portfolioId`) +**projects/[project]/index.js** - Main application, with datacenter construction and simulation UI (`/projects/:projectId` +and `/projects/:projectId/portfolios/:portfolioId`) -**Profile.js** - Profile of the current user (`/profile`) +**profile.js** - Profile of the current user (`/profile`) -**NotFound.js** - 404 page to appear when the route is invalid (`/*`) +**404.js** - 404 page to appear when the route is invalid (`/*`) ### Components & Containers -The building blocks of the UI are divided into so-called *components* and *containers* ([as encouraged](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0) by the author of Redux). *Components* are considered 'pure', rendered as a function of input properties. *Containers*, on the other hand, are wrappers around *components*, injecting state through the properties of the components they wrap. +The building blocks of the UI are divided into so-called *components* and * +containers* ([as encouraged](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0) by the author of +Redux). *Components* are considered 'pure', rendered as a function of input properties. *Containers*, on the other hand, +are wrappers around *components*, injecting state through the properties of the components they wrap. -Even the canvas (the main component of the app) is built using React components, with the help of the `react-konva` module. To illustrate: A rectangular object on the canvas is defined in a way that is not very different from how we define a standard `div` element on the splashpage. +Even the canvas (the main component of the app) is built using React components, with the help of the `react-konva` +module. To illustrate: A rectangular object on the canvas is defined in a way that is not very different from how we +define a standard `div` element on the splashpage. ### State Management -Almost all state is kept in a central Redux store. State is kept there in an immutable form, only to be modified through actions being dispatched. These actions are contained in the `src/actions` folder, and the reducers (managing how state is updated according to dispatched actions) are located in `src/reducers`. If you're not familiar with the Redux approach to state management, have a look at their [official documentation](https://redux.js.org/). +Almost all state is kept in a central Redux store. State is kept there in an immutable form, only to be modified through +actions being dispatched. These actions are contained in the `src/actions` folder, and the reducers (managing how state +is updated according to dispatched actions) are located in `src/reducers`. If you're not familiar with the Redux +approach to state management, have a look at their [official documentation](https://redux.js.org/). ### API Interaction -The web-app needs to pull data in from the API of a backend running on a server. The functions that call routes are located in `src/api`. The actual logic responsible for calling these functions is contained in `src/sagas`. These API fetch procedures are written with the help of `redux-saga`. The [official documentation](https://redux-saga.js.org/) of `redux-saga` can be a helpful aid in understanding that part of the codebase. - +The web-app needs to pull data in from the API of a backend running on a server. The functions that call routes are +located in `src/api`. The actual logic responsible for calling these functions is contained in `src/sagas`. These API +fetch procedures are written with the help of `redux-saga`. The [official documentation](https://redux-saga.js.org/) +of `redux-saga` can be a helpful aid in understanding that part of the codebase. ## Tests -Files containing tests can be recognized by the `.test.js` suffix. They are usually located right next to the source code they are testing, to make discovery easier. +Files containing tests can be recognized by the `.test.js` suffix. They are usually located right next to the source +code they are testing, to make discovery easier. ### Running all tests -The following command runs all tests in the codebase. On top of this, it also watches the code for changes and reruns the tests whenever any file is saved. +The following command runs all tests in the codebase. On top of this, it also watches the code for changes and reruns +the tests whenever any file is saved. ```bash yarn test diff --git a/opendc-web/opendc-web-ui/next.config.js b/opendc-web/opendc-web-ui/next.config.js new file mode 100644 index 00000000..1dfe4156 --- /dev/null +++ b/opendc-web/opendc-web-ui/next.config.js @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +// PatternFly 4 uses global CSS imports in its distribution files. Therefore, +// we need to transpile the modules before we can use them. +const withTM = require('next-transpile-modules')([ + '@patternfly/react-core', + '@patternfly/react-styles', + '@patternfly/react-table', + '@patternfly/react-tokens', +]) + +module.exports = withTM({ + reactStrictMode: true, + experimental: { + eslint: true, + }, + async redirects() { + return [ + { + source: '/', + destination: '/projects', + permanent: true, + }, + ] + }, + webpack: (config, options) => { + if (options.dev) { + config.optimization.splitChunks = { + cacheGroups: { + vendor: { + test: /[\\/]node_modules[\\/]/, + name: 'transpiled-modules', + chunks: 'all', + }, + }, + } + } + return config + } +}) diff --git a/opendc-web/opendc-web-ui/nginx.conf b/opendc-web/opendc-web-ui/nginx.conf deleted file mode 100644 index 1b4e3a73..00000000 --- a/opendc-web/opendc-web-ui/nginx.conf +++ /dev/null @@ -1,15 +0,0 @@ -server { - listen 80; - server_name opendc.org; - - location / { - root /usr/share/nginx/html; - index index.html index.htm; - try_files $uri $uri/ /index.html; - } - - error_page 500 502 503 504 /50x.html; - location = /50x.html { - root /usr/share/nginx/html; - } -} diff --git a/opendc-web/opendc-web-ui/package.json b/opendc-web/opendc-web-ui/package.json index c64a3c11..427e3cbd 100644 --- a/opendc-web/opendc-web-ui/package.json +++ b/opendc-web/opendc-web-ui/package.json @@ -10,46 +10,54 @@ ], "homepage": "http://opendc.org", "bugs": { - "url": "https://github.com/atlarge-research/opendc-frontend/issues", + "url": "https://github.com/atlarge-research/opendc/issues", "email": "opendc@atlarge-research.com" }, - "author": "Georgios Andreadis <g.andreadis@atlarge-research.com> (https://gandreadis.com/)", + "author": "OpenDC Maintainers <opendc@atlarge-research.com>", "license": "MIT", "private": true, - "proxy": "http://localhost:8081", "dependencies": { - "@sentry/react": "^5.27.3", - "@sentry/tracing": "^5.27.3", - "approximate-number": "~2.0.0", - "bootstrap": "4.5.3", - "classnames": "~2.2.5", - "husky": "~4.2.5", - "konva": "~6.0.0", - "lint-staged": "~10.2.2", - "mathjs": "~7.1.0", - "prettier": "~2.0.5", - "prop-types": "~15.7.2", - "react": "~16.13.1", - "react-document-title": "~2.0.3", - "react-dom": "~16.13.1", - "react-fontawesome": "~1.7.1", - "react-google-login": "~5.1.14", - "react-konva": "~16.13.0-2", - "react-redux": "~7.2.0", - "react-router-dom": "~5.1.2", - "react-scripts": "~3.4.1", - "react-shortcuts": "~2.1.0", - "reactstrap": "^8.6.0", - "recharts": "~1.8.5", - "redux": "~4.0.5", - "redux-localstorage": "~0.4.1", + "@auth0/auth0-react": "^1.7.0", + "@fortawesome/fontawesome-svg-core": "^1.2.35", + "@fortawesome/free-brands-svg-icons": "^5.15.3", + "@fortawesome/free-solid-svg-icons": "^5.15.3", + "@fortawesome/react-fontawesome": "^0.1.14", + "@patternfly/react-core": "^4.152.4", + "@patternfly/react-icons": "^4.11.14", + "@patternfly/react-table": "^4.29.58", + "@sentry/react": "^5.30.0", + "@sentry/tracing": "^5.30.0", + "approximate-number": "^2.1.0", + "classnames": "^2.3.1", + "husky": "^4.3.8", + "immer": "^9.0.6", + "konva": "^7.2.5", + "lint-staged": "^10.5.4", + "mathjs": "^7.6.0", + "next": "^11.1.2", + "next-transpile-modules": "^8.0.0", + "normalizr": "^3.6.1", + "prettier": "^2.3.2", + "prop-types": "^15.7.2", + "react": "^17.0.2", + "react-dom": "^17.0.2", + "react-hotkeys": "^2.0.0", + "react-konva": "^17.0.2-5", + "react-query": "^3.22.0", + "react-redux": "^7.2.5", + "recharts": "^2.1.2", + "redux": "^4.1.1", "redux-logger": "~3.0.6", "redux-saga": "~1.1.3", "redux-thunk": "~2.3.0", - "sass": "^1.32.12", - "socket.io-client": "~2.3.0", - "svgsaver": "~0.9.0", - "uuidv4": "~6.1.1" + "sass": "^1.39.0", + "svgsaver": "^0.9.0", + "use-resize-observer": "^8.0.0", + "uuidv4": "^6.2.12" + }, + "devDependencies": { + "eslint": "^7.32.0", + "eslint-config-next": "^11.1.2" }, "lint-staged": { "src/**/*.{js,jsx,json}": [ @@ -60,10 +68,10 @@ "scripts": { "format": "prettier --write src", "precommit": "lint-staged", - "start": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test --env=jsdom", - "eject": "react-scripts eject" + "dev": "next dev", + "build": "next build", + "start": "next start", + "export": "next export -o build" }, "browserslist": { "production": [ diff --git a/opendc-web/opendc-web-ui/public/index.html b/opendc-web/opendc-web-ui/public/index.html deleted file mode 100644 index 19b80e29..00000000 --- a/opendc-web/opendc-web-ui/public/index.html +++ /dev/null @@ -1,70 +0,0 @@ -<!doctype html> -<html lang="en"> -<head> - <meta charset="utf-8"> - <title>OpenDC</title> - - <!-- Standard meta tags --> - <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> - <meta name="theme-color" content="#00A6D6"> - <meta name="description" content="Collaborative Datacenter Simulation and Exploration for Everybody"> - <meta name="author" content="@Large Research"> - <meta name="keywords" content="OpenDC, Datacenter, Simulation, Simulator, Collaborative, Distributed, Cluster"> - <link rel="manifest" href="/manifest.json"> - <link rel="shortcut icon" href="/favicon.ico"> - - <!-- Twitter Card data --> - <meta name="twitter:card" content="summary"> - <meta name="twitter:site" content="@LargeResearch"> - <meta name="twitter:title" content="OpenDC"> - <meta name="twitter:description" content="Collaborative Datacenter Simulation and Exploration for Everybody"> - <meta name="twitter:creator" content="@LargeResearch"> - <meta name="twitter:image" content="http://opendc.org/img/logo.png"> - - <!-- OpenGraph meta tags --> - <meta property="og:title" content="OpenDC"> - <meta property="og:site_name" content="OpenDC"> - <meta property="og:type" content="website"> - <meta property="og:image" content="http://opendc.org/img/logo.png"> - <meta property="og:url" content="http://opendc.org/"> - <meta property="og:description" - content="OpenDC provides collaborative online datacenter modeling, diverse and effective datacenter - simulation, and exploratory datacenter performance feedback."> - <meta property="og:locale" content="en_US"> - - <!-- Google meta tags --> - <meta name="google-signin-client_id" content="%REACT_APP_OAUTH_CLIENT_ID%"> - <meta name="google-site-verification" content="YIR4LkQTv6WmOdWv8MkeiUKni-0Yu3WHylLp4VvUMig"/> - - <!-- CDN dependencies --> - <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" rel="stylesheet"> - <script src="https://use.fontawesome.com/ece66a2e7c.js"></script> - - <!-- Google Analytics --> - <script async src="https://www.googletagmanager.com/gtag/js?id=UA-84285092-3"></script> - <script> - window.dataLayer = window.dataLayer || [] - - function gtag() { - dataLayer.push(arguments) - } - - gtag('js', new Date()) - gtag('config', 'UA-84285092-3') - </script> - <script> - /* Dynamic app variables */ - window.config_overrides = { - REACT_APP_API_BASE_URL: "$REACT_APP_API_BASE_URL", - REACT_APP_OAUTH_CLIENT_ID: "$REACT_APP_OAUTH_CLIENT_ID", - REACT_APP_SENTRY_DSN: "$REACT_APP_SENTRY_DSN", - }; - </script> -</head> -<body> -<noscript> - You need to enable JavaScript to run this app. -</noscript> -<div id="root"></div> -</body> -</html> diff --git a/opendc-web/opendc-web-ui/scripts/envsubst-html.sh b/opendc-web/opendc-web-ui/scripts/envsubst-html.sh deleted file mode 100755 index 8ca12e8a..00000000 --- a/opendc-web/opendc-web-ui/scripts/envsubst-html.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/sh - -set -e - -# We can use simple version of envsubst execution as -# envsubst < /usr/share/nginx/html/index.html.template > /usr/share/nginx/html/index.html -# but it replaces everything that looks like environment variable substitution -# so it affects `default values` approach. -# we need to replace only provided environment variables. - -auto_envsubst() { - template_path="/usr/share/nginx/html/index.html.template" - output_path="/usr/share/nginx/html/index.html" - defined_envs=$(printf '${%s} ' $(env | cut -d= -f1)) - envsubst "$defined_envs" < "$template_path" > "$output_path" -} - -auto_envsubst -exit 0 diff --git a/opendc-web/opendc-web-ui/scripts/envsubst.sh b/opendc-web/opendc-web-ui/scripts/envsubst.sh new file mode 100755 index 00000000..d7ae9ecb --- /dev/null +++ b/opendc-web/opendc-web-ui/scripts/envsubst.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +set -e + +auto_envsubst() { + input_path="/opendc/.next.template" + output_path="/opendc/.next" + + cp -r "$input_path" "$output_path" + find "$output_path" -type f -name '*.js' -exec perl -pi -e 's/%%(NEXT_PUBLIC_[_A-Z0-9]+)%%/$ENV{$1}/g' {} \; +} + +auto_envsubst +exit 0 diff --git a/opendc-web/opendc-web-ui/src/actions/auth.js b/opendc-web/opendc-web-ui/src/actions/auth.js deleted file mode 100644 index 38c1a782..00000000 --- a/opendc-web/opendc-web-ui/src/actions/auth.js +++ /dev/null @@ -1,23 +0,0 @@ -export const LOG_IN = 'LOG_IN' -export const LOG_IN_SUCCEEDED = 'LOG_IN_SUCCEEDED' -export const LOG_OUT = 'LOG_OUT' - -export function logIn(payload) { - return { - type: LOG_IN, - payload, - } -} - -export function logInSucceeded(payload) { - return { - type: LOG_IN_SUCCEEDED, - payload, - } -} - -export function logOut() { - return { - type: LOG_OUT, - } -} diff --git a/opendc-web/opendc-web-ui/src/actions/interaction-level.js b/opendc-web/opendc-web-ui/src/actions/interaction-level.js deleted file mode 100644 index ff6b1fa3..00000000 --- a/opendc-web/opendc-web-ui/src/actions/interaction-level.js +++ /dev/null @@ -1,50 +0,0 @@ -export const GO_FROM_BUILDING_TO_ROOM = 'GO_FROM_BUILDING_TO_ROOM' -export const GO_FROM_ROOM_TO_RACK = 'GO_FROM_ROOM_TO_RACK' -export const GO_FROM_RACK_TO_MACHINE = 'GO_FROM_RACK_TO_MACHINE' -export const GO_DOWN_ONE_INTERACTION_LEVEL = 'GO_DOWN_ONE_INTERACTION_LEVEL' - -export function goFromBuildingToRoom(roomId) { - return (dispatch, getState) => { - const { interactionLevel } = getState() - if (interactionLevel.mode !== 'BUILDING') { - return - } - - dispatch({ - type: GO_FROM_BUILDING_TO_ROOM, - roomId, - }) - } -} - -export function goFromRoomToRack(tileId) { - return (dispatch, getState) => { - const { interactionLevel } = getState() - if (interactionLevel.mode !== 'ROOM') { - return - } - dispatch({ - type: GO_FROM_ROOM_TO_RACK, - tileId, - }) - } -} - -export function goFromRackToMachine(position) { - return (dispatch, getState) => { - const { interactionLevel } = getState() - if (interactionLevel.mode !== 'RACK') { - return - } - dispatch({ - type: GO_FROM_RACK_TO_MACHINE, - position, - }) - } -} - -export function goDownOneInteractionLevel() { - return { - type: GO_DOWN_ONE_INTERACTION_LEVEL, - } -} diff --git a/opendc-web/opendc-web-ui/src/actions/map.js b/opendc-web/opendc-web-ui/src/actions/map.js deleted file mode 100644 index 0d49d849..00000000 --- a/opendc-web/opendc-web-ui/src/actions/map.js +++ /dev/null @@ -1,82 +0,0 @@ -import { - MAP_MAX_SCALE, - MAP_MIN_SCALE, - MAP_SCALE_PER_EVENT, - MAP_SIZE_IN_PIXELS, -} from '../components/app/map/MapConstants' - -export const SET_MAP_POSITION = 'SET_MAP_POSITION' -export const SET_MAP_DIMENSIONS = 'SET_MAP_DIMENSIONS' -export const SET_MAP_SCALE = 'SET_MAP_SCALE' - -export function setMapPosition(x, y) { - return { - type: SET_MAP_POSITION, - x, - y, - } -} - -export function setMapDimensions(width, height) { - return { - type: SET_MAP_DIMENSIONS, - width, - height, - } -} - -export function setMapScale(scale) { - return { - type: SET_MAP_SCALE, - scale, - } -} - -export function zoomInOnCenter(zoomIn) { - return (dispatch, getState) => { - const state = getState() - - dispatch(zoomInOnPosition(zoomIn, state.map.dimensions.width / 2, state.map.dimensions.height / 2)) - } -} - -export function zoomInOnPosition(zoomIn, x, y) { - return (dispatch, getState) => { - const state = getState() - - const centerPoint = { - x: x / state.map.scale - state.map.position.x / state.map.scale, - y: y / state.map.scale - state.map.position.y / state.map.scale, - } - const newScale = zoomIn ? state.map.scale * MAP_SCALE_PER_EVENT : state.map.scale / MAP_SCALE_PER_EVENT - const boundedScale = Math.min(Math.max(MAP_MIN_SCALE, newScale), MAP_MAX_SCALE) - - const newX = -(centerPoint.x - x / boundedScale) * boundedScale - const newY = -(centerPoint.y - y / boundedScale) * boundedScale - - dispatch(setMapPositionWithBoundsCheck(newX, newY)) - dispatch(setMapScale(boundedScale)) - } -} - -export function setMapPositionWithBoundsCheck(x, y) { - return (dispatch, getState) => { - const state = getState() - - const scaledMapSize = MAP_SIZE_IN_PIXELS * state.map.scale - const updatedX = - x > 0 - ? 0 - : x < -scaledMapSize + state.map.dimensions.width - ? -scaledMapSize + state.map.dimensions.width - : x - const updatedY = - y > 0 - ? 0 - : y < -scaledMapSize + state.map.dimensions.height - ? -scaledMapSize + state.map.dimensions.height - : y - - dispatch(setMapPosition(updatedX, updatedY)) - } -} diff --git a/opendc-web/opendc-web-ui/src/actions/modals/portfolios.js b/opendc-web/opendc-web-ui/src/actions/modals/portfolios.js deleted file mode 100644 index f6dce2e3..00000000 --- a/opendc-web/opendc-web-ui/src/actions/modals/portfolios.js +++ /dev/null @@ -1,14 +0,0 @@ -export const OPEN_NEW_PORTFOLIO_MODAL = 'OPEN_NEW_PORTFOLIO_MODAL' -export const CLOSE_NEW_PORTFOLIO_MODAL = 'CLOSE_PORTFOLIO_MODAL' - -export function openNewPortfolioModal() { - return { - type: OPEN_NEW_PORTFOLIO_MODAL, - } -} - -export function closeNewPortfolioModal() { - return { - type: CLOSE_NEW_PORTFOLIO_MODAL, - } -} diff --git a/opendc-web/opendc-web-ui/src/actions/modals/prefabs.js b/opendc-web/opendc-web-ui/src/actions/modals/prefabs.js deleted file mode 100644 index 826565d2..00000000 --- a/opendc-web/opendc-web-ui/src/actions/modals/prefabs.js +++ /dev/null @@ -1,14 +0,0 @@ -export const OPEN_NEW_PREFAB_MODAL = 'OPEN_NEW_PREFAB_MODAL' -export const CLOSE_NEW_PREFAB_MODAL = 'CLOSE_PREFAB_MODAL' - -export function openNewPrefabModal() { - return { - type: OPEN_NEW_PREFAB_MODAL, - } -} - -export function closeNewPrefabModal() { - return { - type: CLOSE_NEW_PREFAB_MODAL, - } -} diff --git a/opendc-web/opendc-web-ui/src/actions/modals/profile.js b/opendc-web/opendc-web-ui/src/actions/modals/profile.js deleted file mode 100644 index 39c72c03..00000000 --- a/opendc-web/opendc-web-ui/src/actions/modals/profile.js +++ /dev/null @@ -1,14 +0,0 @@ -export const OPEN_DELETE_PROFILE_MODAL = 'OPEN_DELETE_PROFILE_MODAL' -export const CLOSE_DELETE_PROFILE_MODAL = 'CLOSE_DELETE_PROFILE_MODAL' - -export function openDeleteProfileModal() { - return { - type: OPEN_DELETE_PROFILE_MODAL, - } -} - -export function closeDeleteProfileModal() { - return { - type: CLOSE_DELETE_PROFILE_MODAL, - } -} diff --git a/opendc-web/opendc-web-ui/src/actions/modals/projects.js b/opendc-web/opendc-web-ui/src/actions/modals/projects.js deleted file mode 100644 index d1043cbb..00000000 --- a/opendc-web/opendc-web-ui/src/actions/modals/projects.js +++ /dev/null @@ -1,14 +0,0 @@ -export const OPEN_NEW_PROJECT_MODAL = 'OPEN_NEW_PROJECT_MODAL' -export const CLOSE_NEW_PROJECT_MODAL = 'CLOSE_PROJECT_MODAL' - -export function openNewProjectModal() { - return { - type: OPEN_NEW_PROJECT_MODAL, - } -} - -export function closeNewProjectModal() { - return { - type: CLOSE_NEW_PROJECT_MODAL, - } -} diff --git a/opendc-web/opendc-web-ui/src/actions/modals/scenarios.js b/opendc-web/opendc-web-ui/src/actions/modals/scenarios.js deleted file mode 100644 index b71cb27b..00000000 --- a/opendc-web/opendc-web-ui/src/actions/modals/scenarios.js +++ /dev/null @@ -1,14 +0,0 @@ -export const OPEN_NEW_SCENARIO_MODAL = 'OPEN_NEW_SCENARIO_MODAL' -export const CLOSE_NEW_SCENARIO_MODAL = 'CLOSE_SCENARIO_MODAL' - -export function openNewScenarioModal() { - return { - type: OPEN_NEW_SCENARIO_MODAL, - } -} - -export function closeNewScenarioModal() { - return { - type: CLOSE_NEW_SCENARIO_MODAL, - } -} diff --git a/opendc-web/opendc-web-ui/src/actions/modals/topology.js b/opendc-web/opendc-web-ui/src/actions/modals/topology.js deleted file mode 100644 index b5fecac1..00000000 --- a/opendc-web/opendc-web-ui/src/actions/modals/topology.js +++ /dev/null @@ -1,84 +0,0 @@ -export const OPEN_NEW_TOPOLOGY_MODAL = 'OPEN_NEW_TOPOLOGY_MODAL' -export const CLOSE_NEW_TOPOLOGY_MODAL = 'CLOSE_NEW_TOPOLOGY_MODAL' -export const OPEN_EDIT_ROOM_NAME_MODAL = 'OPEN_EDIT_ROOM_NAME_MODAL' -export const CLOSE_EDIT_ROOM_NAME_MODAL = 'CLOSE_EDIT_ROOM_NAME_MODAL' -export const OPEN_DELETE_ROOM_MODAL = 'OPEN_DELETE_ROOM_MODAL' -export const CLOSE_DELETE_ROOM_MODAL = 'CLOSE_DELETE_ROOM_MODAL' -export const OPEN_EDIT_RACK_NAME_MODAL = 'OPEN_EDIT_RACK_NAME_MODAL' -export const CLOSE_EDIT_RACK_NAME_MODAL = 'CLOSE_EDIT_RACK_NAME_MODAL' -export const OPEN_DELETE_RACK_MODAL = 'OPEN_DELETE_RACK_MODAL' -export const CLOSE_DELETE_RACK_MODAL = 'CLOSE_DELETE_RACK_MODAL' -export const OPEN_DELETE_MACHINE_MODAL = 'OPEN_DELETE_MACHINE_MODAL' -export const CLOSE_DELETE_MACHINE_MODAL = 'CLOSE_DELETE_MACHINE_MODAL' - -export function openNewTopologyModal() { - return { - type: OPEN_NEW_TOPOLOGY_MODAL, - } -} - -export function closeNewTopologyModal() { - return { - type: CLOSE_NEW_TOPOLOGY_MODAL, - } -} - -export function openEditRoomNameModal() { - return { - type: OPEN_EDIT_ROOM_NAME_MODAL, - } -} - -export function closeEditRoomNameModal() { - return { - type: CLOSE_EDIT_ROOM_NAME_MODAL, - } -} - -export function openDeleteRoomModal() { - return { - type: OPEN_DELETE_ROOM_MODAL, - } -} - -export function closeDeleteRoomModal() { - return { - type: CLOSE_DELETE_ROOM_MODAL, - } -} - -export function openEditRackNameModal() { - return { - type: OPEN_EDIT_RACK_NAME_MODAL, - } -} - -export function closeEditRackNameModal() { - return { - type: CLOSE_EDIT_RACK_NAME_MODAL, - } -} - -export function openDeleteRackModal() { - return { - type: OPEN_DELETE_RACK_MODAL, - } -} - -export function closeDeleteRackModal() { - return { - type: CLOSE_DELETE_RACK_MODAL, - } -} - -export function openDeleteMachineModal() { - return { - type: OPEN_DELETE_MACHINE_MODAL, - } -} - -export function closeDeleteMachineModal() { - return { - type: CLOSE_DELETE_MACHINE_MODAL, - } -} diff --git a/opendc-web/opendc-web-ui/src/actions/objects.js b/opendc-web/opendc-web-ui/src/actions/objects.js deleted file mode 100644 index 7b648b18..00000000 --- a/opendc-web/opendc-web-ui/src/actions/objects.js +++ /dev/null @@ -1,41 +0,0 @@ -export const ADD_TO_STORE = 'ADD_TO_STORE' -export const ADD_PROP_TO_STORE_OBJECT = 'ADD_PROP_TO_STORE_OBJECT' -export const ADD_ID_TO_STORE_OBJECT_LIST_PROP = 'ADD_ID_TO_STORE_OBJECT_LIST_PROP' -export const REMOVE_ID_FROM_STORE_OBJECT_LIST_PROP = 'REMOVE_ID_FROM_STORE_OBJECT_LIST_PROP' - -export function addToStore(objectType, object) { - return { - type: ADD_TO_STORE, - objectType, - object, - } -} - -export function addPropToStoreObject(objectType, objectId, propObject) { - return { - type: ADD_PROP_TO_STORE_OBJECT, - objectType, - objectId, - propObject, - } -} - -export function addIdToStoreObjectListProp(objectType, objectId, propName, id) { - return { - type: ADD_ID_TO_STORE_OBJECT_LIST_PROP, - objectType, - objectId, - propName, - id, - } -} - -export function removeIdFromStoreObjectListProp(objectType, objectId, propName, id) { - return { - type: REMOVE_ID_FROM_STORE_OBJECT_LIST_PROP, - objectType, - objectId, - propName, - id, - } -} diff --git a/opendc-web/opendc-web-ui/src/actions/portfolios.js b/opendc-web/opendc-web-ui/src/actions/portfolios.js deleted file mode 100644 index d37886d8..00000000 --- a/opendc-web/opendc-web-ui/src/actions/portfolios.js +++ /dev/null @@ -1,41 +0,0 @@ -export const ADD_PORTFOLIO = 'ADD_PORTFOLIO' -export const UPDATE_PORTFOLIO = 'UPDATE_PORTFOLIO' -export const DELETE_PORTFOLIO = 'DELETE_PORTFOLIO' -export const OPEN_PORTFOLIO_SUCCEEDED = 'OPEN_PORTFOLIO_SUCCEEDED' -export const SET_CURRENT_PORTFOLIO = 'SET_CURRENT_PORTFOLIO' - -export function addPortfolio(portfolio) { - return { - type: ADD_PORTFOLIO, - portfolio, - } -} - -export function updatePortfolio(portfolio) { - return { - type: UPDATE_PORTFOLIO, - portfolio, - } -} - -export function deletePortfolio(id) { - return { - type: DELETE_PORTFOLIO, - id, - } -} - -export function openPortfolioSucceeded(projectId, portfolioId) { - return { - type: OPEN_PORTFOLIO_SUCCEEDED, - projectId, - portfolioId, - } -} - -export function setCurrentPortfolio(portfolioId) { - return { - type: SET_CURRENT_PORTFOLIO, - portfolioId, - } -} diff --git a/opendc-web/opendc-web-ui/src/actions/prefabs.js b/opendc-web/opendc-web-ui/src/actions/prefabs.js deleted file mode 100644 index c112feed..00000000 --- a/opendc-web/opendc-web-ui/src/actions/prefabs.js +++ /dev/null @@ -1,32 +0,0 @@ -export const ADD_PREFAB = 'ADD_PREFAB' -export const DELETE_PREFAB = 'DELETE_PREFAB' -export const DELETE_PREFAB_SUCCEEDED = 'DELETE_PREFAB_SUCCEEDED' -export const OPEN_PREFAB_SUCCEEDED = 'OPEN_PREFAB_SUCCEEDED' - -export function addPrefab(name) { - return { - type: ADD_PREFAB, - name, - } -} - -export function deletePrefab(id) { - return { - type: DELETE_PREFAB, - id, - } -} - -export function deletePrefabSucceeded(id) { - return { - type: DELETE_PREFAB_SUCCEEDED, - id, - } -} - -export function openPrefabSucceeded(id) { - return { - type: OPEN_PREFAB_SUCCEEDED, - id, - } -} diff --git a/opendc-web/opendc-web-ui/src/actions/projects.js b/opendc-web/opendc-web-ui/src/actions/projects.js deleted file mode 100644 index add0f242..00000000 --- a/opendc-web/opendc-web-ui/src/actions/projects.js +++ /dev/null @@ -1,52 +0,0 @@ -export const SET_AUTH_VISIBILITY_FILTER = 'SET_AUTH_VISIBILITY_FILTER' -export const ADD_PROJECT = 'ADD_PROJECT' -export const ADD_PROJECT_SUCCEEDED = 'ADD_PROJECT_SUCCEEDED' -export const DELETE_PROJECT = 'DELETE_PROJECT' -export const DELETE_PROJECT_SUCCEEDED = 'DELETE_PROJECT_SUCCEEDED' -export const OPEN_PROJECT_SUCCEEDED = 'OPEN_PROJECT_SUCCEEDED' - -export function setAuthVisibilityFilter(filter) { - return { - type: SET_AUTH_VISIBILITY_FILTER, - filter, - } -} - -export function addProject(name) { - return (dispatch, getState) => { - const { auth } = getState() - dispatch({ - type: ADD_PROJECT, - name, - userId: auth.userId, - }) - } -} - -export function addProjectSucceeded(authorization) { - return { - type: ADD_PROJECT_SUCCEEDED, - authorization, - } -} - -export function deleteProject(id) { - return { - type: DELETE_PROJECT, - id, - } -} - -export function deleteProjectSucceeded(id) { - return { - type: DELETE_PROJECT_SUCCEEDED, - id, - } -} - -export function openProjectSucceeded(id) { - return { - type: OPEN_PROJECT_SUCCEEDED, - id, - } -} diff --git a/opendc-web/opendc-web-ui/src/actions/scenarios.js b/opendc-web/opendc-web-ui/src/actions/scenarios.js deleted file mode 100644 index c8a90762..00000000 --- a/opendc-web/opendc-web-ui/src/actions/scenarios.js +++ /dev/null @@ -1,43 +0,0 @@ -export const ADD_SCENARIO = 'ADD_SCENARIO' -export const UPDATE_SCENARIO = 'UPDATE_SCENARIO' -export const DELETE_SCENARIO = 'DELETE_SCENARIO' -export const OPEN_SCENARIO_SUCCEEDED = 'OPEN_SCENARIO_SUCCEEDED' -export const SET_CURRENT_SCENARIO = 'SET_CURRENT_SCENARIO' - -export function addScenario(scenario) { - return { - type: ADD_SCENARIO, - scenario, - } -} - -export function updateScenario(scenario) { - return { - type: UPDATE_SCENARIO, - scenario, - } -} - -export function deleteScenario(id) { - return { - type: DELETE_SCENARIO, - id, - } -} - -export function openScenarioSucceeded(projectId, portfolioId, scenarioId) { - return { - type: OPEN_SCENARIO_SUCCEEDED, - projectId, - portfolioId, - scenarioId, - } -} - -export function setCurrentScenario(portfolioId, scenarioId) { - return { - type: SET_CURRENT_SCENARIO, - portfolioId, - scenarioId, - } -} diff --git a/opendc-web/opendc-web-ui/src/actions/topologies.js b/opendc-web/opendc-web-ui/src/actions/topologies.js deleted file mode 100644 index dcce3b7d..00000000 --- a/opendc-web/opendc-web-ui/src/actions/topologies.js +++ /dev/null @@ -1,17 +0,0 @@ -export const ADD_TOPOLOGY = 'ADD_TOPOLOGY' -export const DELETE_TOPOLOGY = 'DELETE_TOPOLOGY' - -export function addTopology(name, duplicateId) { - return { - type: ADD_TOPOLOGY, - name, - duplicateId, - } -} - -export function deleteTopology(id) { - return { - type: DELETE_TOPOLOGY, - id, - } -} diff --git a/opendc-web/opendc-web-ui/src/actions/topology/building.js b/opendc-web/opendc-web-ui/src/actions/topology/building.js deleted file mode 100644 index 72deda6f..00000000 --- a/opendc-web/opendc-web-ui/src/actions/topology/building.js +++ /dev/null @@ -1,105 +0,0 @@ -export const SET_CURRENT_TOPOLOGY = 'SET_CURRENT_TOPOLOGY' -export const START_NEW_ROOM_CONSTRUCTION = 'START_NEW_ROOM_CONSTRUCTION' -export const START_NEW_ROOM_CONSTRUCTION_SUCCEEDED = 'START_NEW_ROOM_CONSTRUCTION_SUCCEEDED' -export const FINISH_NEW_ROOM_CONSTRUCTION = 'FINISH_NEW_ROOM_CONSTRUCTION' -export const CANCEL_NEW_ROOM_CONSTRUCTION = 'CANCEL_NEW_ROOM_CONSTRUCTION' -export const CANCEL_NEW_ROOM_CONSTRUCTION_SUCCEEDED = 'CANCEL_NEW_ROOM_CONSTRUCTION_SUCCEEDED' -export const START_ROOM_EDIT = 'START_ROOM_EDIT' -export const FINISH_ROOM_EDIT = 'FINISH_ROOM_EDIT' -export const ADD_TILE = 'ADD_TILE' -export const DELETE_TILE = 'DELETE_TILE' - -export function setCurrentTopology(topologyId) { - return { - type: SET_CURRENT_TOPOLOGY, - topologyId, - } -} - -export function startNewRoomConstruction() { - return { - type: START_NEW_ROOM_CONSTRUCTION, - } -} - -export function startNewRoomConstructionSucceeded(roomId) { - return { - type: START_NEW_ROOM_CONSTRUCTION_SUCCEEDED, - roomId, - } -} - -export function finishNewRoomConstruction() { - return (dispatch, getState) => { - const { objects, construction } = getState() - if (objects.room[construction.currentRoomInConstruction].tileIds.length === 0) { - dispatch(cancelNewRoomConstruction()) - return - } - - dispatch({ - type: FINISH_NEW_ROOM_CONSTRUCTION, - }) - } -} - -export function cancelNewRoomConstruction() { - return { - type: CANCEL_NEW_ROOM_CONSTRUCTION, - } -} - -export function cancelNewRoomConstructionSucceeded() { - return { - type: CANCEL_NEW_ROOM_CONSTRUCTION_SUCCEEDED, - } -} - -export function startRoomEdit() { - return (dispatch, getState) => { - const { interactionLevel } = getState() - dispatch({ - type: START_ROOM_EDIT, - roomId: interactionLevel.roomId, - }) - } -} - -export function finishRoomEdit() { - return { - type: FINISH_ROOM_EDIT, - } -} - -export function toggleTileAtLocation(positionX, positionY) { - return (dispatch, getState) => { - const { objects, construction } = getState() - - const tileIds = objects.room[construction.currentRoomInConstruction].tileIds - for (let index in tileIds) { - if ( - objects.tile[tileIds[index]].positionX === positionX && - objects.tile[tileIds[index]].positionY === positionY - ) { - dispatch(deleteTile(tileIds[index])) - return - } - } - dispatch(addTile(positionX, positionY)) - } -} - -export function addTile(positionX, positionY) { - return { - type: ADD_TILE, - positionX, - positionY, - } -} - -export function deleteTile(tileId) { - return { - type: DELETE_TILE, - tileId, - } -} diff --git a/opendc-web/opendc-web-ui/src/actions/topology/machine.js b/opendc-web/opendc-web-ui/src/actions/topology/machine.js deleted file mode 100644 index 17ccce5d..00000000 --- a/opendc-web/opendc-web-ui/src/actions/topology/machine.js +++ /dev/null @@ -1,25 +0,0 @@ -export const DELETE_MACHINE = 'DELETE_MACHINE' -export const ADD_UNIT = 'ADD_UNIT' -export const DELETE_UNIT = 'DELETE_UNIT' - -export function deleteMachine() { - return { - type: DELETE_MACHINE, - } -} - -export function addUnit(unitType, id) { - return { - type: ADD_UNIT, - unitType, - id, - } -} - -export function deleteUnit(unitType, index) { - return { - type: DELETE_UNIT, - unitType, - index, - } -} diff --git a/opendc-web/opendc-web-ui/src/actions/topology/rack.js b/opendc-web/opendc-web-ui/src/actions/topology/rack.js deleted file mode 100644 index b117402e..00000000 --- a/opendc-web/opendc-web-ui/src/actions/topology/rack.js +++ /dev/null @@ -1,23 +0,0 @@ -export const EDIT_RACK_NAME = 'EDIT_RACK_NAME' -export const DELETE_RACK = 'DELETE_RACK' -export const ADD_MACHINE = 'ADD_MACHINE' - -export function editRackName(name) { - return { - type: EDIT_RACK_NAME, - name, - } -} - -export function deleteRack() { - return { - type: DELETE_RACK, - } -} - -export function addMachine(position) { - return { - type: ADD_MACHINE, - position, - } -} diff --git a/opendc-web/opendc-web-ui/src/actions/topology/room.js b/opendc-web/opendc-web-ui/src/actions/topology/room.js deleted file mode 100644 index 52cba680..00000000 --- a/opendc-web/opendc-web-ui/src/actions/topology/room.js +++ /dev/null @@ -1,48 +0,0 @@ -import { findTileWithPosition } from '../../util/tile-calculations' - -export const EDIT_ROOM_NAME = 'EDIT_ROOM_NAME' -export const DELETE_ROOM = 'DELETE_ROOM' -export const START_RACK_CONSTRUCTION = 'START_RACK_CONSTRUCTION' -export const STOP_RACK_CONSTRUCTION = 'STOP_RACK_CONSTRUCTION' -export const ADD_RACK_TO_TILE = 'ADD_RACK_TO_TILE' - -export function editRoomName(name) { - return { - type: EDIT_ROOM_NAME, - name, - } -} - -export function startRackConstruction() { - return { - type: START_RACK_CONSTRUCTION, - } -} - -export function stopRackConstruction() { - return { - type: STOP_RACK_CONSTRUCTION, - } -} - -export function addRackToTile(positionX, positionY) { - return (dispatch, getState) => { - const { objects, interactionLevel } = getState() - const currentRoom = objects.room[interactionLevel.roomId] - const tiles = currentRoom.tileIds.map((tileId) => objects.tile[tileId]) - const tile = findTileWithPosition(tiles, positionX, positionY) - - if (tile !== null) { - dispatch({ - type: ADD_RACK_TO_TILE, - tileId: tile._id, - }) - } - } -} - -export function deleteRoom() { - return { - type: DELETE_ROOM, - } -} diff --git a/opendc-web/opendc-web-ui/src/actions/users.js b/opendc-web/opendc-web-ui/src/actions/users.js deleted file mode 100644 index 4868ac34..00000000 --- a/opendc-web/opendc-web-ui/src/actions/users.js +++ /dev/null @@ -1,37 +0,0 @@ -export const FETCH_AUTHORIZATIONS_OF_CURRENT_USER = 'FETCH_AUTHORIZATIONS_OF_CURRENT_USER' -export const FETCH_AUTHORIZATIONS_OF_CURRENT_USER_SUCCEEDED = 'FETCH_AUTHORIZATIONS_OF_CURRENT_USER_SUCCEEDED' -export const DELETE_CURRENT_USER = 'DELETE_CURRENT_USER' -export const DELETE_CURRENT_USER_SUCCEEDED = 'DELETE_CURRENT_USER_SUCCEEDED' - -export function fetchAuthorizationsOfCurrentUser() { - return (dispatch, getState) => { - const { auth } = getState() - dispatch({ - type: FETCH_AUTHORIZATIONS_OF_CURRENT_USER, - userId: auth.userId, - }) - } -} - -export function fetchAuthorizationsOfCurrentUserSucceeded(authorizationsOfCurrentUser) { - return { - type: FETCH_AUTHORIZATIONS_OF_CURRENT_USER_SUCCEEDED, - authorizationsOfCurrentUser, - } -} - -export function deleteCurrentUser() { - return (dispatch, getState) => { - const { auth } = getState() - dispatch({ - type: DELETE_CURRENT_USER, - userId: auth.userId, - }) - } -} - -export function deleteCurrentUserSucceeded() { - return { - type: DELETE_CURRENT_USER_SUCCEEDED, - } -} diff --git a/opendc-web/opendc-web-ui/src/api/index.js b/opendc-web/opendc-web-ui/src/api/index.js index cefcb2c5..680d49ce 100644 --- a/opendc-web/opendc-web-ui/src/api/index.js +++ b/opendc-web/opendc-web-ui/src/api/index.js @@ -1,13 +1,51 @@ -import { sendSocketRequest } from './socket' - -export function sendRequest(request) { - return new Promise((resolve, reject) => { - sendSocketRequest(request, (response) => { - if (response.status.code === 200) { - resolve(response.content) - } else { - reject(response) - } - }) +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +const apiUrl = process.env.NEXT_PUBLIC_API_BASE_URL + +/** + * Send the specified request to the OpenDC API. + * + * @param auth The authentication context. + * @param path Relative path for the API. + * @param method The method to use for the request. + * @param body The body of the request. + */ +export async function request(auth, path, method = 'GET', body) { + const { getAccessTokenSilently } = auth + const token = await getAccessTokenSilently() + const response = await fetch(`${apiUrl}/${path}`, { + method: method, + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + body: body && JSON.stringify(body), }) + const json = await response.json() + + if (!response.ok) { + throw response.message + } + + return json.data } diff --git a/opendc-web/opendc-web-ui/src/api/portfolios.js b/opendc-web/opendc-web-ui/src/api/portfolios.js new file mode 100644 index 00000000..82ac0ced --- /dev/null +++ b/opendc-web/opendc-web-ui/src/api/portfolios.js @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { request } from './index' + +export function fetchPortfolio(auth, portfolioId) { + return request(auth, `portfolios/${portfolioId}`) +} + +export function fetchPortfoliosOfProject(auth, projectId) { + return request(auth, `projects/${projectId}/portfolios`) +} + +export function addPortfolio(auth, portfolio) { + return request(auth, `projects/${portfolio.projectId}/portfolios`, 'POST', { portfolio }) +} + +export function updatePortfolio(auth, portfolioId, portfolio) { + return request(auth, `portfolios/${portfolioId}`, 'PUT', { portfolio }) +} + +export function deletePortfolio(auth, portfolioId) { + return request(auth, `portfolios/${portfolioId}`, 'DELETE') +} diff --git a/opendc-web/opendc-web-ui/src/api/prefabs.js b/opendc-web/opendc-web-ui/src/api/prefabs.js new file mode 100644 index 00000000..eb9aa23c --- /dev/null +++ b/opendc-web/opendc-web-ui/src/api/prefabs.js @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { request } from './index' + +export function getPrefab(auth, prefabId) { + return request(auth, `prefabs/${prefabId}`) +} + +export function addPrefab(auth, prefab) { + return request(auth, 'prefabs/', 'POST', { prefab }) +} + +export function updatePrefab(auth, prefab) { + return request(auth, `prefabs/${prefab._id}`, 'PUT', { prefab }) +} + +export function deletePrefab(auth, prefabId) { + return request(auth, `prefabs/${prefabId}`, 'DELETE') +} diff --git a/opendc-web/opendc-web-ui/src/api/projects.js b/opendc-web/opendc-web-ui/src/api/projects.js new file mode 100644 index 00000000..4123b371 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/api/projects.js @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { request } from './index' + +export function fetchProjects(auth) { + return request(auth, `projects/`) +} + +export function fetchProject(auth, projectId) { + return request(auth, `projects/${projectId}`) +} + +export function addProject(auth, project) { + return request(auth, 'projects/', 'POST', { project }) +} + +export function updateProject(auth, project) { + return request(auth, `projects/${project._id}`, 'PUT', { project }) +} + +export function deleteProject(auth, projectId) { + return request(auth, `projects/${projectId}`, 'DELETE') +} diff --git a/opendc-web/opendc-web-ui/src/api/routes/portfolios.js b/opendc-web/opendc-web-ui/src/api/routes/portfolios.js deleted file mode 100644 index 7c9ea02a..00000000 --- a/opendc-web/opendc-web-ui/src/api/routes/portfolios.js +++ /dev/null @@ -1,42 +0,0 @@ -import { deleteById, getById } from './util' -import { sendRequest } from '../index' - -export function addPortfolio(projectId, portfolio) { - return sendRequest({ - path: '/projects/{projectId}/portfolios', - method: 'POST', - parameters: { - body: { - portfolio, - }, - path: { - projectId, - }, - query: {}, - }, - }) -} - -export function getPortfolio(portfolioId) { - return getById('/portfolios/{portfolioId}', { portfolioId }) -} - -export function updatePortfolio(portfolioId, portfolio) { - return sendRequest({ - path: '/portfolios/{projectId}', - method: 'POST', - parameters: { - body: { - portfolio, - }, - path: { - portfolioId, - }, - query: {}, - }, - }) -} - -export function deletePortfolio(portfolioId) { - return deleteById('/portfolios/{portfolioId}', { portfolioId }) -} diff --git a/opendc-web/opendc-web-ui/src/api/routes/prefabs.js b/opendc-web/opendc-web-ui/src/api/routes/prefabs.js deleted file mode 100644 index 8a1debfa..00000000 --- a/opendc-web/opendc-web-ui/src/api/routes/prefabs.js +++ /dev/null @@ -1,40 +0,0 @@ -import { sendRequest } from '../index' -import { deleteById, getById } from './util' - -export function getPrefab(prefabId) { - return getById('/prefabs/{prefabId}', { prefabId }) -} - -export function addPrefab(prefab) { - return sendRequest({ - path: '/prefabs', - method: 'POST', - parameters: { - body: { - prefab, - }, - path: {}, - query: {}, - }, - }) -} - -export function updatePrefab(prefab) { - return sendRequest({ - path: '/prefabs/{prefabId}', - method: 'PUT', - parameters: { - body: { - prefab, - }, - path: { - prefabId: prefab._id, - }, - query: {}, - }, - }) -} - -export function deletePrefab(prefabId) { - return deleteById('/prefabs/{prefabId}', { prefabId }) -} diff --git a/opendc-web/opendc-web-ui/src/api/routes/projects.js b/opendc-web/opendc-web-ui/src/api/routes/projects.js deleted file mode 100644 index 4109079c..00000000 --- a/opendc-web/opendc-web-ui/src/api/routes/projects.js +++ /dev/null @@ -1,40 +0,0 @@ -import { sendRequest } from '../index' -import { deleteById, getById } from './util' - -export function getProject(projectId) { - return getById('/projects/{projectId}', { projectId }) -} - -export function addProject(project) { - return sendRequest({ - path: '/projects', - method: 'POST', - parameters: { - body: { - project, - }, - path: {}, - query: {}, - }, - }) -} - -export function updateProject(project) { - return sendRequest({ - path: '/projects/{projectId}', - method: 'PUT', - parameters: { - body: { - project, - }, - path: { - projectId: project._id, - }, - query: {}, - }, - }) -} - -export function deleteProject(projectId) { - return deleteById('/projects/{projectId}', { projectId }) -} diff --git a/opendc-web/opendc-web-ui/src/api/routes/scenarios.js b/opendc-web/opendc-web-ui/src/api/routes/scenarios.js deleted file mode 100644 index ab2e8b86..00000000 --- a/opendc-web/opendc-web-ui/src/api/routes/scenarios.js +++ /dev/null @@ -1,42 +0,0 @@ -import { deleteById, getById } from './util' -import { sendRequest } from '../index' - -export function addScenario(portfolioId, scenario) { - return sendRequest({ - path: '/portfolios/{portfolioId}/scenarios', - method: 'POST', - parameters: { - body: { - scenario, - }, - path: { - portfolioId, - }, - query: {}, - }, - }) -} - -export function getScenario(scenarioId) { - return getById('/scenarios/{scenarioId}', { scenarioId }) -} - -export function updateScenario(scenarioId, scenario) { - return sendRequest({ - path: '/scenarios/{projectId}', - method: 'POST', - parameters: { - body: { - scenario, - }, - path: { - scenarioId, - }, - query: {}, - }, - }) -} - -export function deleteScenario(scenarioId) { - return deleteById('/scenarios/{scenarioId}', { scenarioId }) -} diff --git a/opendc-web/opendc-web-ui/src/api/routes/schedulers.js b/opendc-web/opendc-web-ui/src/api/routes/schedulers.js deleted file mode 100644 index 4481fb2a..00000000 --- a/opendc-web/opendc-web-ui/src/api/routes/schedulers.js +++ /dev/null @@ -1,5 +0,0 @@ -import { getAll } from './util' - -export function getAllSchedulers() { - return getAll('/schedulers') -} diff --git a/opendc-web/opendc-web-ui/src/api/routes/token-signin.js b/opendc-web/opendc-web-ui/src/api/routes/token-signin.js deleted file mode 100644 index ced5d2e0..00000000 --- a/opendc-web/opendc-web-ui/src/api/routes/token-signin.js +++ /dev/null @@ -1,12 +0,0 @@ -import config from '../../config' - -export function performTokenSignIn(token) { - const apiUrl = config['API_BASE_URL'] - - return fetch(`${apiUrl}/tokensignin`, { - method: 'POST', - body: new URLSearchParams({ - idtoken: token, - }), - }).then((res) => res.json()) -} diff --git a/opendc-web/opendc-web-ui/src/api/routes/topologies.js b/opendc-web/opendc-web-ui/src/api/routes/topologies.js deleted file mode 100644 index a8f0d6b1..00000000 --- a/opendc-web/opendc-web-ui/src/api/routes/topologies.js +++ /dev/null @@ -1,42 +0,0 @@ -import { deleteById, getById } from './util' -import { sendRequest } from '../index' - -export function addTopology(topology) { - return sendRequest({ - path: '/projects/{projectId}/topologies', - method: 'POST', - parameters: { - body: { - topology, - }, - path: { - projectId: topology.projectId, - }, - query: {}, - }, - }) -} - -export function getTopology(topologyId) { - return getById('/topologies/{topologyId}', { topologyId }) -} - -export function updateTopology(topology) { - return sendRequest({ - path: '/topologies/{topologyId}', - method: 'PUT', - parameters: { - body: { - topology, - }, - path: { - topologyId: topology._id, - }, - query: {}, - }, - }) -} - -export function deleteTopology(topologyId) { - return deleteById('/topologies/{topologyId}', { topologyId }) -} diff --git a/opendc-web/opendc-web-ui/src/api/routes/traces.js b/opendc-web/opendc-web-ui/src/api/routes/traces.js deleted file mode 100644 index 67895a87..00000000 --- a/opendc-web/opendc-web-ui/src/api/routes/traces.js +++ /dev/null @@ -1,5 +0,0 @@ -import { getAll } from './util' - -export function getAllTraces() { - return getAll('/traces') -} diff --git a/opendc-web/opendc-web-ui/src/api/routes/users.js b/opendc-web/opendc-web-ui/src/api/routes/users.js deleted file mode 100644 index 3028f3f7..00000000 --- a/opendc-web/opendc-web-ui/src/api/routes/users.js +++ /dev/null @@ -1,48 +0,0 @@ -import { sendRequest } from '../index' -import { deleteById } from './util' - -export function getUserByEmail(email) { - return sendRequest({ - path: '/users', - method: 'GET', - parameters: { - body: {}, - path: {}, - query: { - email, - }, - }, - }) -} - -export function addUser(user) { - return sendRequest({ - path: '/users', - method: 'POST', - parameters: { - body: { - user, - }, - path: {}, - query: {}, - }, - }) -} - -export function getUser(userId) { - return sendRequest({ - path: '/users/{userId}', - method: 'GET', - parameters: { - body: {}, - path: { - userId, - }, - query: {}, - }, - }) -} - -export function deleteUser(userId) { - return deleteById('/users/{userId}', { userId }) -} diff --git a/opendc-web/opendc-web-ui/src/api/routes/util.js b/opendc-web/opendc-web-ui/src/api/routes/util.js deleted file mode 100644 index 67e7173b..00000000 --- a/opendc-web/opendc-web-ui/src/api/routes/util.js +++ /dev/null @@ -1,37 +0,0 @@ -import { sendRequest } from '../index' - -export function getAll(path) { - return sendRequest({ - path, - method: 'GET', - parameters: { - body: {}, - path: {}, - query: {}, - }, - }) -} - -export function getById(path, pathObject) { - return sendRequest({ - path, - method: 'GET', - parameters: { - body: {}, - path: pathObject, - query: {}, - }, - }) -} - -export function deleteById(path, pathObject) { - return sendRequest({ - path, - method: 'DELETE', - parameters: { - body: {}, - path: pathObject, - query: {}, - }, - }) -} diff --git a/opendc-web/opendc-web-ui/src/api/scenarios.js b/opendc-web/opendc-web-ui/src/api/scenarios.js new file mode 100644 index 00000000..88516caa --- /dev/null +++ b/opendc-web/opendc-web-ui/src/api/scenarios.js @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { request } from './index' + +export function fetchScenario(auth, scenarioId) { + return request(auth, `scenarios/${scenarioId}`) +} + +export function fetchScenariosOfPortfolio(auth, portfolioId) { + return request(auth, `portfolios/${portfolioId}/scenarios`) +} + +export function addScenario(auth, scenario) { + return request(auth, `portfolios/${scenario.portfolioId}/scenarios`, 'POST', { scenario }) +} + +export function updateScenario(auth, scenarioId, scenario) { + return request(auth, `scenarios/${scenarioId}`, 'PUT', { scenario }) +} + +export function deleteScenario(auth, scenarioId) { + return request(auth, `scenarios/${scenarioId}`, 'DELETE') +} diff --git a/opendc-web/opendc-web-ui/src/api/schedulers.js b/opendc-web/opendc-web-ui/src/api/schedulers.js new file mode 100644 index 00000000..0b8b8153 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/api/schedulers.js @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { request } from './index' + +export function fetchSchedulers(auth) { + return request(auth, 'schedulers/') +} diff --git a/opendc-web/opendc-web-ui/src/api/socket.js b/opendc-web/opendc-web-ui/src/api/socket.js deleted file mode 100644 index 87facda8..00000000 --- a/opendc-web/opendc-web-ui/src/api/socket.js +++ /dev/null @@ -1,50 +0,0 @@ -import io from 'socket.io-client' -import { getAuthToken } from '../auth/index' -import config from '../config' - -let socket -let requestIdCounter = 0 -const callbacks = {} - -export function setupSocketConnection(onConnect) { - const apiUrl = - config['API_BASE_URL'] || `${window.location.protocol}//${window.location.hostname}:${window.location.port}` - - socket = io.connect(apiUrl) - socket.on('connect', onConnect) - socket.on('response', onSocketResponse) -} - -export function sendSocketRequest(request, callback) { - if (!socket.connected) { - console.error('Attempted to send request over unconnected socket') - return - } - - const newId = requestIdCounter++ - callbacks[newId] = callback - - request.id = newId - request.token = getAuthToken() - - if (!request.isRootRoute) { - request.path = '/v2' + request.path - } - - socket.emit('request', request) - - if (process.env.NODE_ENV !== 'production') { - console.log('Sent socket request:', request) - } -} - -function onSocketResponse(json) { - const response = JSON.parse(json) - - if (process.env.NODE_ENV !== 'production') { - console.log('Received socket response:', response) - } - - callbacks[response.id](response) - delete callbacks[response.id] -} diff --git a/opendc-web/opendc-web-ui/src/api/topologies.js b/opendc-web/opendc-web-ui/src/api/topologies.js new file mode 100644 index 00000000..bd4e3bc4 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/api/topologies.js @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { request } from './index' + +export function fetchTopology(auth, topologyId) { + return request(auth, `topologies/${topologyId}`) +} + +export function fetchTopologiesOfProject(auth, projectId) { + return request(auth, `projects/${projectId}/topologies`) +} + +export function addTopology(auth, topology) { + return request(auth, `projects/${topology.projectId}/topologies`, 'POST', { topology }) +} + +export function updateTopology(auth, topology) { + // eslint-disable-next-line no-unused-vars + const { _id, ...data } = topology + return request(auth, `topologies/${topology._id}`, 'PUT', { topology: data }) +} + +export function deleteTopology(auth, topologyId) { + return request(auth, `topologies/${topologyId}`, 'DELETE') +} diff --git a/opendc-web/opendc-web-ui/src/api/traces.js b/opendc-web/opendc-web-ui/src/api/traces.js new file mode 100644 index 00000000..fd637ac3 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/api/traces.js @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { request } from './index' + +export function fetchTraces(auth) { + return request(auth, 'traces/') +} diff --git a/opendc-web/opendc-web-ui/src/auth.js b/opendc-web/opendc-web-ui/src/auth.js new file mode 100644 index 00000000..e670476c --- /dev/null +++ b/opendc-web/opendc-web-ui/src/auth.js @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import { Auth0Provider, useAuth0 } from '@auth0/auth0-react' +import { useEffect } from 'react' + +/** + * Obtain the authentication context. + */ +export function useAuth() { + return useAuth0() +} + +/** + * Force the user to be authenticated or redirect to the homepage. + */ +export function useRequireAuth() { + const auth = useAuth() + const { loginWithRedirect, isLoading, isAuthenticated } = auth + + useEffect(() => { + if (!isLoading && !isAuthenticated) { + loginWithRedirect() + } + }, [loginWithRedirect, isLoading, isAuthenticated]) + + return auth +} + +/** + * AuthProvider which provides an authentication context. + */ +export function AuthProvider({ children }) { + return ( + <Auth0Provider + domain={process.env.NEXT_PUBLIC_AUTH0_DOMAIN} + clientId={process.env.NEXT_PUBLIC_AUTH0_CLIENT_ID} + redirectUri={global.window && global.window.location.origin} + audience={process.env.NEXT_PUBLIC_AUTH0_AUDIENCE} + > + {children} + </Auth0Provider> + ) +} + +AuthProvider.propTypes = { + children: PropTypes.node, +} diff --git a/opendc-web/opendc-web-ui/src/auth/index.js b/opendc-web/opendc-web-ui/src/auth/index.js deleted file mode 100644 index b5953990..00000000 --- a/opendc-web/opendc-web-ui/src/auth/index.js +++ /dev/null @@ -1,57 +0,0 @@ -import { LOG_IN_SUCCEEDED, LOG_OUT } from '../actions/auth' -import { DELETE_CURRENT_USER_SUCCEEDED } from '../actions/users' - -const getAuthObject = () => { - const authItem = localStorage.getItem('auth') - if (!authItem || authItem === '{}') { - return undefined - } - return JSON.parse(authItem) -} - -export const userIsLoggedIn = () => { - const authObj = getAuthObject() - - if (!authObj || !authObj.googleId) { - return false - } - - const currentTime = new Date().getTime() - return parseInt(authObj.expiresAt, 10) - currentTime > 0 -} - -export const getAuthToken = () => { - const authObj = getAuthObject() - if (!authObj) { - return undefined - } - - return authObj.authToken -} - -export const saveAuthLocalStorage = (payload) => { - localStorage.setItem('auth', JSON.stringify(payload)) -} - -export const clearAuthLocalStorage = () => { - localStorage.setItem('auth', '') -} - -export const authRedirectMiddleware = (store) => (next) => (action) => { - switch (action.type) { - case LOG_IN_SUCCEEDED: - saveAuthLocalStorage(action.payload) - window.location.href = '/projects' - break - case LOG_OUT: - case DELETE_CURRENT_USER_SUCCEEDED: - clearAuthLocalStorage() - window.location.href = '/' - break - default: - next(action) - return - } - - next(action) -} diff --git a/opendc-web/opendc-web-ui/src/components/AppHeader.js b/opendc-web/opendc-web-ui/src/components/AppHeader.js new file mode 100644 index 00000000..b33212c4 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/AppHeader.js @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { PageHeader } from '@patternfly/react-core' +import React from 'react' +import Image from 'next/image' +import AppHeaderTools from './AppHeaderTools' +import { AppNavigation } from './AppNavigation' +import AppLogo from './AppLogo' + +export function AppHeader() { + const logo = <Image src="/img/logo.png" layout="fixed" width={30} height={30} alt="OpenDC" /> + + return ( + <PageHeader + logo={logo} + logoProps={{ href: '/' }} + logoComponent={AppLogo} + headerTools={<AppHeaderTools />} + topNav={<AppNavigation />} + /> + ) +} + +AppHeader.propTypes = {} diff --git a/opendc-web/opendc-web-ui/src/components/AppHeaderTools.js b/opendc-web/opendc-web-ui/src/components/AppHeaderTools.js new file mode 100644 index 00000000..02e5d265 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/AppHeaderTools.js @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { + Avatar, + Button, + ButtonVariant, + Dropdown, + DropdownGroup, + DropdownItem, + DropdownToggle, + KebabToggle, + PageHeaderTools, + PageHeaderToolsGroup, + PageHeaderToolsItem, + Skeleton, +} from '@patternfly/react-core' +import { useState } from 'react' +import { useAuth } from '../auth' +import { GithubIcon, HelpIcon } from '@patternfly/react-icons' + +function AppHeaderTools() { + const auth = useAuth() + + const [isKebabDropdownOpen, setKebabDropdownOpen] = useState(false) + const kebabDropdownItems = [ + <DropdownItem + key={0} + component={ + <a href="https://opendc.org" target="_blank" rel="noreferrer"> + <HelpIcon /> Help + </a> + } + />, + ] + + const [isDropdownOpen, setDropdownOpen] = useState(false) + const userDropdownItems = [ + <DropdownGroup key="group 2"> + <DropdownItem key="group 2 logout" onClick={() => auth.logout({ returnTo: window.location.origin })}> + Logout + </DropdownItem> + </DropdownGroup>, + ] + + return ( + <PageHeaderTools> + <PageHeaderToolsGroup visibility={{ default: 'hidden', lg: 'visible' }}> + <PageHeaderToolsItem> + <Button + component="a" + href="https://github.com/atlarge-research/opendc" + target="_blank" + aria-label="Source code" + variant={ButtonVariant.plain} + > + <GithubIcon /> + </Button> + </PageHeaderToolsItem> + <PageHeaderToolsItem> + <Button + component="a" + href="https://opendc.org/" + target="_blank" + aria-label="Help actions" + variant={ButtonVariant.plain} + > + <HelpIcon /> + </Button> + </PageHeaderToolsItem> + </PageHeaderToolsGroup> + <PageHeaderToolsGroup> + <PageHeaderToolsItem visibility={{ lg: 'hidden' }}> + <Dropdown + isPlain + position="right" + toggle={<KebabToggle onToggle={() => setKebabDropdownOpen(!isKebabDropdownOpen)} />} + isOpen={isKebabDropdownOpen} + dropdownItems={kebabDropdownItems} + /> + </PageHeaderToolsItem> + <PageHeaderToolsItem visibility={{ default: 'hidden', md: 'visible' }}> + <Dropdown + isPlain + position="right" + isOpen={isDropdownOpen} + toggle={ + <DropdownToggle onToggle={() => setDropdownOpen(!isDropdownOpen)}> + {auth?.user?.name ?? ( + <Skeleton + fontSize="xs" + width="150px" + className="pf-u-display-inline-flex" + screenreaderText="Loading username" + /> + )} + </DropdownToggle> + } + dropdownItems={userDropdownItems} + /> + </PageHeaderToolsItem> + </PageHeaderToolsGroup> + {auth?.user?.picture ? ( + <Avatar src={auth.user.picture} alt="Avatar image" /> + ) : ( + <Skeleton className="pf-c-avatar" shape="circle" width="2.25rem" screenreaderText="Loading avatar" /> + )} + </PageHeaderTools> + ) +} + +AppHeaderTools.propTypes = {} + +export default AppHeaderTools diff --git a/opendc-web/opendc-web-ui/src/components/AppLogo.js b/opendc-web/opendc-web-ui/src/components/AppLogo.js new file mode 100644 index 00000000..92663295 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/AppLogo.js @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import Link from 'next/link' +import { appLogo } from './AppLogo.module.scss' + +function AppLogo({ href, children, className, ...props }) { + return ( + <> + <Link href={href}> + <a {...props} className={`${className ?? ''} ${appLogo}`}> + {children} + <span>OpenDC</span> + </a> + </Link> + </> + ) +} + +AppLogo.propTypes = { + href: PropTypes.string.isRequired, + children: PropTypes.node, + className: PropTypes.string, +} + +export default AppLogo diff --git a/opendc-web/opendc-web-ui/src/components/AppLogo.module.scss b/opendc-web/opendc-web-ui/src/components/AppLogo.module.scss new file mode 100644 index 00000000..3d228cb6 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/AppLogo.module.scss @@ -0,0 +1,33 @@ +/*! + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +.appLogo { + span { + margin-left: 4px; + color: #fff; + } + + &:hover, + &:focus { + text-decoration: none; + } +} diff --git a/opendc-web/opendc-web-ui/src/components/AppNavigation.js b/opendc-web/opendc-web-ui/src/components/AppNavigation.js new file mode 100644 index 00000000..178c3ec0 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/AppNavigation.js @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { Nav, NavItem, NavList } from '@patternfly/react-core' +import { useRouter } from 'next/router' +import NavItemLink from './util/NavItemLink' +import { useProject } from '../data/project' + +export function AppNavigation() { + const { pathname, query } = useRouter() + const { project: projectId } = query + const { data: project } = useProject(projectId) + + const nextTopologyId = project?.topologyIds?.[0] + const nextPortfolioId = project?.portfolioIds?.[0] + + return ( + <Nav variant="horizontal"> + <NavList> + <NavItem + id="projects" + to="/projects" + itemId={0} + component={NavItemLink} + isActive={pathname === '/projects' || pathname === '/projects/[project]'} + > + Projects + </NavItem> + {pathname.startsWith('/projects/[project]') && ( + <> + <NavItem + id="topologies" + to={nextTopologyId ? `/projects/${projectId}/topologies/${nextTopologyId}` : '/projects'} + itemId={1} + component={NavItemLink} + isActive={pathname === '/projects/[project]/topologies/[topology]'} + > + Topologies + </NavItem> + <NavItem + id="portfolios" + to={nextPortfolioId ? `/projects/${projectId}/portfolios/${nextPortfolioId}` : '/projects'} + itemId={2} + component={NavItemLink} + isActive={pathname === '/projects/[project]/portfolios/[portfolio]'} + > + Portfolios + </NavItem> + </> + )} + </NavList> + </Nav> + ) +} + +AppNavigation.propTypes = {} diff --git a/opendc-web/opendc-web-ui/src/components/AppPage.js b/opendc-web/opendc-web-ui/src/components/AppPage.js new file mode 100644 index 00000000..25afaf9a --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/AppPage.js @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import { AppHeader } from './AppHeader' +import React from 'react' +import { Page, PageGroup, PageBreadcrumb } from '@patternfly/react-core' + +export function AppPage({ children, breadcrumb, contextSelectors }) { + return ( + <Page header={<AppHeader />}> + <PageGroup> + {contextSelectors} + {breadcrumb && <PageBreadcrumb>{breadcrumb}</PageBreadcrumb>} + </PageGroup> + {children} + </Page> + ) +} + +AppPage.propTypes = { + breadcrumb: PropTypes.node, + contextSelectors: PropTypes.node, + children: PropTypes.node, +} diff --git a/opendc-web/opendc-web-ui/src/components/app/map/LoadingScreen.js b/opendc-web/opendc-web-ui/src/components/app/map/LoadingScreen.js deleted file mode 100644 index 7efea9b0..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/LoadingScreen.js +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react' -import FontAwesome from 'react-fontawesome' - -const LoadingScreen = () => ( - <div className="display-4"> - <FontAwesome name="refresh" className="mr-4" spin /> - Loading your project... - </div> -) - -export default LoadingScreen diff --git a/opendc-web/opendc-web-ui/src/components/app/map/MapConstants.js b/opendc-web/opendc-web-ui/src/components/app/map/MapConstants.js deleted file mode 100644 index d6ea1f84..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/MapConstants.js +++ /dev/null @@ -1,28 +0,0 @@ -export const MAP_SIZE = 50 -export const TILE_SIZE_IN_PIXELS = 100 -export const TILE_SIZE_IN_METERS = 0.5 -export const MAP_SIZE_IN_PIXELS = MAP_SIZE * TILE_SIZE_IN_PIXELS - -export const OBJECT_MARGIN_IN_PIXELS = TILE_SIZE_IN_PIXELS / 5 -export const TILE_PLUS_MARGIN_IN_PIXELS = TILE_SIZE_IN_PIXELS / 3 -export const OBJECT_SIZE_IN_PIXELS = TILE_SIZE_IN_PIXELS - OBJECT_MARGIN_IN_PIXELS * 2 - -export const GRID_LINE_WIDTH_IN_PIXELS = 2 -export const WALL_WIDTH_IN_PIXELS = TILE_SIZE_IN_PIXELS / 8 -export const OBJECT_BORDER_WIDTH_IN_PIXELS = TILE_SIZE_IN_PIXELS / 12 -export const TILE_PLUS_WIDTH_IN_PIXELS = TILE_SIZE_IN_PIXELS / 10 - -export const SIDEBAR_WIDTH = 350 -export const VIEWPORT_PADDING = 50 - -export const RACK_FILL_ICON_WIDTH = OBJECT_SIZE_IN_PIXELS / 3 -export const RACK_FILL_ICON_OPACITY = 0.8 - -export const MAP_MOVE_PIXELS_PER_EVENT = 20 -export const MAP_SCALE_PER_EVENT = 1.1 -export const MAP_MIN_SCALE = 0.5 -export const MAP_MAX_SCALE = 1.5 - -export const MAX_NUM_UNITS_PER_MACHINE = 6 -export const DEFAULT_RACK_SLOT_CAPACITY = 42 -export const DEFAULT_RACK_POWER_CAPACITY = 10000 diff --git a/opendc-web/opendc-web-ui/src/components/app/map/MapStageComponent.js b/opendc-web/opendc-web-ui/src/components/app/map/MapStageComponent.js deleted file mode 100644 index 2cd0ed6e..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/MapStageComponent.js +++ /dev/null @@ -1,103 +0,0 @@ -import React from 'react' -import { Stage } from 'react-konva' -import { Shortcuts } from 'react-shortcuts' -import MapLayer from '../../../containers/app/map/layers/MapLayer' -import ObjectHoverLayer from '../../../containers/app/map/layers/ObjectHoverLayer' -import RoomHoverLayer from '../../../containers/app/map/layers/RoomHoverLayer' -import { NAVBAR_HEIGHT } from '../../navigation/Navbar' -import { MAP_MOVE_PIXELS_PER_EVENT } from './MapConstants' -import { Provider } from 'react-redux' -import { store } from '../../../store/configure-store' - -class MapStageComponent extends React.Component { - state = { - mouseX: 0, - mouseY: 0, - } - - constructor(props) { - super(props) - - this.updateDimensions = this.updateDimensions.bind(this) - this.updateScale = this.updateScale.bind(this) - } - - componentDidMount() { - this.updateDimensions() - - window.addEventListener('resize', this.updateDimensions) - window.addEventListener('wheel', this.updateScale) - - window['exportCanvasToImage'] = () => { - const download = document.createElement('a') - download.href = this.stage.getStage().toDataURL() - download.download = 'opendc-canvas-export-' + Date.now() + '.png' - download.click() - } - } - - componentWillUnmount() { - window.removeEventListener('resize', this.updateDimensions) - window.removeEventListener('wheel', this.updateScale) - } - - updateDimensions() { - this.props.setMapDimensions(window.innerWidth, window.innerHeight - NAVBAR_HEIGHT) - } - - updateScale(e) { - e.preventDefault() - this.props.zoomInOnPosition(e.deltaY < 0, this.state.mouseX, this.state.mouseY) - } - - updateMousePosition() { - const mousePos = this.stage.getStage().getPointerPosition() - this.setState({ mouseX: mousePos.x, mouseY: mousePos.y }) - } - - handleShortcuts(action) { - switch (action) { - case 'MOVE_LEFT': - this.moveWithDelta(MAP_MOVE_PIXELS_PER_EVENT, 0) - break - case 'MOVE_RIGHT': - this.moveWithDelta(-MAP_MOVE_PIXELS_PER_EVENT, 0) - break - case 'MOVE_UP': - this.moveWithDelta(0, MAP_MOVE_PIXELS_PER_EVENT) - break - case 'MOVE_DOWN': - this.moveWithDelta(0, -MAP_MOVE_PIXELS_PER_EVENT) - break - default: - break - } - } - - moveWithDelta(deltaX, deltaY) { - this.props.setMapPositionWithBoundsCheck(this.props.mapPosition.x + deltaX, this.props.mapPosition.y + deltaY) - } - - render() { - return ( - <Shortcuts name="MAP" handler={this.handleShortcuts.bind(this)} targetNodeSelector="body"> - <Stage - ref={(stage) => { - this.stage = stage - }} - width={this.props.mapDimensions.width} - height={this.props.mapDimensions.height} - onMouseMove={this.updateMousePosition.bind(this)} - > - <Provider store={store}> - <MapLayer /> - <RoomHoverLayer mouseX={this.state.mouseX} mouseY={this.state.mouseY} /> - <ObjectHoverLayer mouseX={this.state.mouseX} mouseY={this.state.mouseY} /> - </Provider> - </Stage> - </Shortcuts> - ) - } -} - -export default MapStageComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/map/controls/ExportCanvasComponent.js b/opendc-web/opendc-web-ui/src/components/app/map/controls/ExportCanvasComponent.js deleted file mode 100644 index 8487f47b..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/controls/ExportCanvasComponent.js +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react' - -const ExportCanvasComponent = () => ( - <button - className="btn btn-success btn-circle btn-sm" - title="Export Canvas to PNG Image" - onClick={() => window['exportCanvasToImage']()} - > - <span className="fa fa-camera" /> - </button> -) - -export default ExportCanvasComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicatorComponent.js b/opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicatorComponent.js deleted file mode 100644 index 7cbb45c0..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicatorComponent.js +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react' -import { TILE_SIZE_IN_METERS, TILE_SIZE_IN_PIXELS } from '../MapConstants' -import './ScaleIndicatorComponent.sass' - -const ScaleIndicatorComponent = ({ scale }) => ( - <div className="scale-indicator" style={{ width: TILE_SIZE_IN_PIXELS * scale }}> - {TILE_SIZE_IN_METERS}m - </div> -) - -export default ScaleIndicatorComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicatorComponent.sass b/opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicatorComponent.sass deleted file mode 100644 index 03a72c99..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicatorComponent.sass +++ /dev/null @@ -1,9 +0,0 @@ -.scale-indicator - position: absolute - right: 10px - bottom: 10px - z-index: 50 - - border: solid 2px #212529 - border-top: none - border-left: none diff --git a/opendc-web/opendc-web-ui/src/components/app/map/controls/ToolPanelComponent.js b/opendc-web/opendc-web-ui/src/components/app/map/controls/ToolPanelComponent.js deleted file mode 100644 index f372734d..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/controls/ToolPanelComponent.js +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react' -import ZoomControlContainer from '../../../../containers/app/map/controls/ZoomControlContainer' -import ExportCanvasComponent from './ExportCanvasComponent' -import './ToolPanelComponent.sass' - -const ToolPanelComponent = () => ( - <div className="tool-panel"> - <ZoomControlContainer /> - <ExportCanvasComponent /> - </div> -) - -export default ToolPanelComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/map/controls/ToolPanelComponent.sass b/opendc-web/opendc-web-ui/src/components/app/map/controls/ToolPanelComponent.sass deleted file mode 100644 index 8b27d24a..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/controls/ToolPanelComponent.sass +++ /dev/null @@ -1,5 +0,0 @@ -.tool-panel - position: absolute - left: 10px - bottom: 10px - z-index: 50 diff --git a/opendc-web/opendc-web-ui/src/components/app/map/controls/ZoomControlComponent.js b/opendc-web/opendc-web-ui/src/components/app/map/controls/ZoomControlComponent.js deleted file mode 100644 index 65944bea..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/controls/ZoomControlComponent.js +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react' - -const ZoomControlComponent = ({ zoomInOnCenter }) => { - return ( - <span> - <button - className="btn btn-default btn-circle btn-sm mr-1" - title="Zoom in" - onClick={() => zoomInOnCenter(true)} - > - <span className="fa fa-plus" /> - </button> - <button - className="btn btn-default btn-circle btn-sm mr-1" - title="Zoom out" - onClick={() => zoomInOnCenter(false)} - > - <span className="fa fa-minus" /> - </button> - </span> - ) -} - -export default ZoomControlComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/map/elements/Backdrop.js b/opendc-web/opendc-web-ui/src/components/app/map/elements/Backdrop.js deleted file mode 100644 index 8ccfe584..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/elements/Backdrop.js +++ /dev/null @@ -1,8 +0,0 @@ -import React from 'react' -import { Rect } from 'react-konva' -import { BACKDROP_COLOR } from '../../../../util/colors' -import { MAP_SIZE_IN_PIXELS } from '../MapConstants' - -const Backdrop = () => <Rect x={0} y={0} width={MAP_SIZE_IN_PIXELS} height={MAP_SIZE_IN_PIXELS} fill={BACKDROP_COLOR} /> - -export default Backdrop diff --git a/opendc-web/opendc-web-ui/src/components/app/map/elements/GrayLayer.js b/opendc-web/opendc-web-ui/src/components/app/map/elements/GrayLayer.js deleted file mode 100644 index c54a34ad..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/elements/GrayLayer.js +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react' -import { Rect } from 'react-konva' -import { GRAYED_OUT_AREA_COLOR } from '../../../../util/colors' -import { MAP_SIZE_IN_PIXELS } from '../MapConstants' - -const GrayLayer = ({ onClick }) => ( - <Rect - x={0} - y={0} - width={MAP_SIZE_IN_PIXELS} - height={MAP_SIZE_IN_PIXELS} - fill={GRAYED_OUT_AREA_COLOR} - onClick={onClick} - /> -) - -export default GrayLayer diff --git a/opendc-web/opendc-web-ui/src/components/app/map/elements/HoverTile.js b/opendc-web/opendc-web-ui/src/components/app/map/elements/HoverTile.js deleted file mode 100644 index 912229c4..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/elements/HoverTile.js +++ /dev/null @@ -1,27 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { Rect } from 'react-konva' -import { ROOM_HOVER_INVALID_COLOR, ROOM_HOVER_VALID_COLOR } from '../../../../util/colors' -import { TILE_SIZE_IN_PIXELS } from '../MapConstants' - -const HoverTile = ({ pixelX, pixelY, isValid, scale, onClick }) => ( - <Rect - x={pixelX} - y={pixelY} - scaleX={scale} - scaleY={scale} - width={TILE_SIZE_IN_PIXELS} - height={TILE_SIZE_IN_PIXELS} - fill={isValid ? ROOM_HOVER_VALID_COLOR : ROOM_HOVER_INVALID_COLOR} - onClick={onClick} - /> -) - -HoverTile.propTypes = { - pixelX: PropTypes.number.isRequired, - pixelY: PropTypes.number.isRequired, - isValid: PropTypes.bool.isRequired, - onClick: PropTypes.func.isRequired, -} - -export default HoverTile diff --git a/opendc-web/opendc-web-ui/src/components/app/map/elements/ImageComponent.js b/opendc-web/opendc-web-ui/src/components/app/map/elements/ImageComponent.js deleted file mode 100644 index 2b5c569f..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/elements/ImageComponent.js +++ /dev/null @@ -1,48 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { Image } from 'react-konva' - -class ImageComponent extends React.Component { - static imageCaches = {} - static propTypes = { - src: PropTypes.string.isRequired, - x: PropTypes.number.isRequired, - y: PropTypes.number.isRequired, - width: PropTypes.number.isRequired, - height: PropTypes.number.isRequired, - opacity: PropTypes.number.isRequired, - } - - state = { - image: null, - } - - componentDidMount() { - if (ImageComponent.imageCaches[this.props.src]) { - this.setState({ image: ImageComponent.imageCaches[this.props.src] }) - return - } - - const image = new window.Image() - image.src = this.props.src - image.onload = () => { - this.setState({ image }) - ImageComponent.imageCaches[this.props.src] = image - } - } - - render() { - return ( - <Image - image={this.state.image} - x={this.props.x} - y={this.props.y} - width={this.props.width} - height={this.props.height} - opacity={this.props.opacity} - /> - ) - } -} - -export default ImageComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/map/elements/RackFillBar.js b/opendc-web/opendc-web-ui/src/components/app/map/elements/RackFillBar.js deleted file mode 100644 index 8c573a6f..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/elements/RackFillBar.js +++ /dev/null @@ -1,68 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { Group, Rect } from 'react-konva' -import { - RACK_ENERGY_BAR_BACKGROUND_COLOR, - RACK_ENERGY_BAR_FILL_COLOR, - RACK_SPACE_BAR_BACKGROUND_COLOR, - RACK_SPACE_BAR_FILL_COLOR, -} from '../../../../util/colors' -import { - OBJECT_BORDER_WIDTH_IN_PIXELS, - OBJECT_MARGIN_IN_PIXELS, - RACK_FILL_ICON_OPACITY, - RACK_FILL_ICON_WIDTH, - TILE_SIZE_IN_PIXELS, -} from '../MapConstants' -import ImageComponent from './ImageComponent' - -const RackFillBar = ({ positionX, positionY, type, fillFraction }) => { - const halfOfObjectBorderWidth = OBJECT_BORDER_WIDTH_IN_PIXELS / 2 - const x = - positionX * TILE_SIZE_IN_PIXELS + - OBJECT_MARGIN_IN_PIXELS + - (type === 'space' ? halfOfObjectBorderWidth : 0.5 * (TILE_SIZE_IN_PIXELS - 2 * OBJECT_MARGIN_IN_PIXELS)) - const startY = positionY * TILE_SIZE_IN_PIXELS + OBJECT_MARGIN_IN_PIXELS + halfOfObjectBorderWidth - const width = 0.5 * (TILE_SIZE_IN_PIXELS - OBJECT_MARGIN_IN_PIXELS * 2) - halfOfObjectBorderWidth - const fullHeight = TILE_SIZE_IN_PIXELS - OBJECT_MARGIN_IN_PIXELS * 2 - OBJECT_BORDER_WIDTH_IN_PIXELS - - const fractionHeight = fillFraction * fullHeight - const fractionY = - (positionY + 1) * TILE_SIZE_IN_PIXELS - OBJECT_MARGIN_IN_PIXELS - halfOfObjectBorderWidth - fractionHeight - - return ( - <Group> - <Rect - x={x} - y={startY} - width={width} - height={fullHeight} - fill={type === 'space' ? RACK_SPACE_BAR_BACKGROUND_COLOR : RACK_ENERGY_BAR_BACKGROUND_COLOR} - /> - <Rect - x={x} - y={fractionY} - width={width} - height={fractionHeight} - fill={type === 'space' ? RACK_SPACE_BAR_FILL_COLOR : RACK_ENERGY_BAR_FILL_COLOR} - /> - <ImageComponent - src={'/img/topology/rack-' + type + '-icon.png'} - x={x + width * 0.5 - RACK_FILL_ICON_WIDTH * 0.5} - y={startY + fullHeight * 0.5 - RACK_FILL_ICON_WIDTH * 0.5} - width={RACK_FILL_ICON_WIDTH} - height={RACK_FILL_ICON_WIDTH} - opacity={RACK_FILL_ICON_OPACITY} - /> - </Group> - ) -} - -RackFillBar.propTypes = { - positionX: PropTypes.number.isRequired, - positionY: PropTypes.number.isRequired, - type: PropTypes.string.isRequired, - fillFraction: PropTypes.number.isRequired, -} - -export default RackFillBar diff --git a/opendc-web/opendc-web-ui/src/components/app/map/elements/RoomTile.js b/opendc-web/opendc-web-ui/src/components/app/map/elements/RoomTile.js deleted file mode 100644 index 43bf918e..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/elements/RoomTile.js +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react' -import { Rect } from 'react-konva' -import Shapes from '../../../../shapes/index' -import { TILE_SIZE_IN_PIXELS } from '../MapConstants' - -const RoomTile = ({ tile, color }) => ( - <Rect - x={tile.positionX * TILE_SIZE_IN_PIXELS} - y={tile.positionY * TILE_SIZE_IN_PIXELS} - width={TILE_SIZE_IN_PIXELS} - height={TILE_SIZE_IN_PIXELS} - fill={color} - /> -) - -RoomTile.propTypes = { - tile: Shapes.Tile, -} - -export default RoomTile diff --git a/opendc-web/opendc-web-ui/src/components/app/map/elements/TileObject.js b/opendc-web/opendc-web-ui/src/components/app/map/elements/TileObject.js deleted file mode 100644 index 9e87cc82..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/elements/TileObject.js +++ /dev/null @@ -1,25 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { Rect } from 'react-konva' -import { OBJECT_BORDER_COLOR } from '../../../../util/colors' -import { OBJECT_BORDER_WIDTH_IN_PIXELS, OBJECT_MARGIN_IN_PIXELS, TILE_SIZE_IN_PIXELS } from '../MapConstants' - -const TileObject = ({ positionX, positionY, color }) => ( - <Rect - x={positionX * TILE_SIZE_IN_PIXELS + OBJECT_MARGIN_IN_PIXELS} - y={positionY * TILE_SIZE_IN_PIXELS + OBJECT_MARGIN_IN_PIXELS} - width={TILE_SIZE_IN_PIXELS - OBJECT_MARGIN_IN_PIXELS * 2} - height={TILE_SIZE_IN_PIXELS - OBJECT_MARGIN_IN_PIXELS * 2} - fill={color} - stroke={OBJECT_BORDER_COLOR} - strokeWidth={OBJECT_BORDER_WIDTH_IN_PIXELS} - /> -) - -TileObject.propTypes = { - positionX: PropTypes.number.isRequired, - positionY: PropTypes.number.isRequired, - color: PropTypes.string.isRequired, -} - -export default TileObject diff --git a/opendc-web/opendc-web-ui/src/components/app/map/elements/TilePlusIcon.js b/opendc-web/opendc-web-ui/src/components/app/map/elements/TilePlusIcon.js deleted file mode 100644 index be3a00a8..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/elements/TilePlusIcon.js +++ /dev/null @@ -1,44 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { Group, Line } from 'react-konva' -import { TILE_PLUS_COLOR } from '../../../../util/colors' -import { TILE_PLUS_MARGIN_IN_PIXELS, TILE_PLUS_WIDTH_IN_PIXELS, TILE_SIZE_IN_PIXELS } from '../MapConstants' - -const TilePlusIcon = ({ pixelX, pixelY, mapScale }) => { - const linePoints = [ - [ - pixelX + 0.5 * TILE_SIZE_IN_PIXELS * mapScale, - pixelY + TILE_PLUS_MARGIN_IN_PIXELS * mapScale, - pixelX + 0.5 * TILE_SIZE_IN_PIXELS * mapScale, - pixelY + TILE_SIZE_IN_PIXELS * mapScale - TILE_PLUS_MARGIN_IN_PIXELS * mapScale, - ], - [ - pixelX + TILE_PLUS_MARGIN_IN_PIXELS * mapScale, - pixelY + 0.5 * TILE_SIZE_IN_PIXELS * mapScale, - pixelX + TILE_SIZE_IN_PIXELS * mapScale - TILE_PLUS_MARGIN_IN_PIXELS * mapScale, - pixelY + 0.5 * TILE_SIZE_IN_PIXELS * mapScale, - ], - ] - return ( - <Group> - {linePoints.map((points, index) => ( - <Line - key={index} - points={points} - lineCap="round" - stroke={TILE_PLUS_COLOR} - strokeWidth={TILE_PLUS_WIDTH_IN_PIXELS * mapScale} - listening={false} - /> - ))} - </Group> - ) -} - -TilePlusIcon.propTypes = { - pixelX: PropTypes.number, - pixelY: PropTypes.number, - mapScale: PropTypes.number, -} - -export default TilePlusIcon diff --git a/opendc-web/opendc-web-ui/src/components/app/map/elements/WallSegment.js b/opendc-web/opendc-web-ui/src/components/app/map/elements/WallSegment.js deleted file mode 100644 index 8aa2aebf..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/elements/WallSegment.js +++ /dev/null @@ -1,32 +0,0 @@ -import React from 'react' -import { Line } from 'react-konva' -import Shapes from '../../../../shapes/index' -import { WALL_COLOR } from '../../../../util/colors' -import { TILE_SIZE_IN_PIXELS, WALL_WIDTH_IN_PIXELS } from '../MapConstants' - -const WallSegment = ({ wallSegment }) => { - let points - if (wallSegment.isHorizontal) { - points = [ - wallSegment.startPosX * TILE_SIZE_IN_PIXELS, - wallSegment.startPosY * TILE_SIZE_IN_PIXELS, - (wallSegment.startPosX + wallSegment.length) * TILE_SIZE_IN_PIXELS, - wallSegment.startPosY * TILE_SIZE_IN_PIXELS, - ] - } else { - points = [ - wallSegment.startPosX * TILE_SIZE_IN_PIXELS, - wallSegment.startPosY * TILE_SIZE_IN_PIXELS, - wallSegment.startPosX * TILE_SIZE_IN_PIXELS, - (wallSegment.startPosY + wallSegment.length) * TILE_SIZE_IN_PIXELS, - ] - } - - return <Line points={points} lineCap="round" stroke={WALL_COLOR} strokeWidth={WALL_WIDTH_IN_PIXELS} /> -} - -WallSegment.propTypes = { - wallSegment: Shapes.WallSegment, -} - -export default WallSegment diff --git a/opendc-web/opendc-web-ui/src/components/app/map/groups/GridGroup.js b/opendc-web/opendc-web-ui/src/components/app/map/groups/GridGroup.js deleted file mode 100644 index ebc00244..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/groups/GridGroup.js +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react' -import { Group, Line } from 'react-konva' -import { GRID_COLOR } from '../../../../util/colors' -import { GRID_LINE_WIDTH_IN_PIXELS, MAP_SIZE, MAP_SIZE_IN_PIXELS, TILE_SIZE_IN_PIXELS } from '../MapConstants' - -const MAP_COORDINATE_ENTRIES = Array.from(new Array(MAP_SIZE), (x, i) => i) -const HORIZONTAL_POINT_PAIRS = MAP_COORDINATE_ENTRIES.map((index) => [ - 0, - index * TILE_SIZE_IN_PIXELS, - MAP_SIZE_IN_PIXELS, - index * TILE_SIZE_IN_PIXELS, -]) -const VERTICAL_POINT_PAIRS = MAP_COORDINATE_ENTRIES.map((index) => [ - index * TILE_SIZE_IN_PIXELS, - 0, - index * TILE_SIZE_IN_PIXELS, - MAP_SIZE_IN_PIXELS, -]) - -const GridGroup = () => ( - <Group> - {HORIZONTAL_POINT_PAIRS.concat(VERTICAL_POINT_PAIRS).map((points, index) => ( - <Line - key={index} - points={points} - stroke={GRID_COLOR} - strokeWidth={GRID_LINE_WIDTH_IN_PIXELS} - listening={false} - /> - ))} - </Group> -) - -export default GridGroup diff --git a/opendc-web/opendc-web-ui/src/components/app/map/groups/RackGroup.js b/opendc-web/opendc-web-ui/src/components/app/map/groups/RackGroup.js deleted file mode 100644 index eb6dc24a..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/groups/RackGroup.js +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react' -import { Group } from 'react-konva' -import RackEnergyFillContainer from '../../../../containers/app/map/RackEnergyFillContainer' -import RackSpaceFillContainer from '../../../../containers/app/map/RackSpaceFillContainer' -import Shapes from '../../../../shapes/index' -import { RACK_BACKGROUND_COLOR } from '../../../../util/colors' -import TileObject from '../elements/TileObject' - -const RackGroup = ({ tile }) => { - return ( - <Group> - <TileObject positionX={tile.positionX} positionY={tile.positionY} color={RACK_BACKGROUND_COLOR} /> - <Group> - <RackSpaceFillContainer tileId={tile._id} positionX={tile.positionX} positionY={tile.positionY} /> - <RackEnergyFillContainer tileId={tile._id} positionX={tile.positionX} positionY={tile.positionY} /> - </Group> - </Group> - ) -} - -RackGroup.propTypes = { - tile: Shapes.Tile, -} - -export default RackGroup diff --git a/opendc-web/opendc-web-ui/src/components/app/map/groups/RoomGroup.js b/opendc-web/opendc-web-ui/src/components/app/map/groups/RoomGroup.js deleted file mode 100644 index 1fd54687..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/groups/RoomGroup.js +++ /dev/null @@ -1,48 +0,0 @@ -import React from 'react' -import { Group } from 'react-konva' -import GrayContainer from '../../../../containers/app/map/GrayContainer' -import TileContainer from '../../../../containers/app/map/TileContainer' -import WallContainer from '../../../../containers/app/map/WallContainer' -import Shapes from '../../../../shapes/index' - -const RoomGroup = ({ room, interactionLevel, currentRoomInConstruction, onClick }) => { - if (currentRoomInConstruction === room._id) { - return ( - <Group onClick={onClick}> - {room.tileIds.map((tileId) => ( - <TileContainer key={tileId} tileId={tileId} newTile={true} /> - ))} - </Group> - ) - } - - return ( - <Group onClick={onClick}> - {(() => { - if ( - (interactionLevel.mode === 'RACK' || interactionLevel.mode === 'MACHINE') && - interactionLevel.roomId === room._id - ) { - return [ - room.tileIds - .filter((tileId) => tileId !== interactionLevel.tileId) - .map((tileId) => <TileContainer key={tileId} tileId={tileId} />), - <GrayContainer key={-1} />, - room.tileIds - .filter((tileId) => tileId === interactionLevel.tileId) - .map((tileId) => <TileContainer key={tileId} tileId={tileId} />), - ] - } else { - return room.tileIds.map((tileId) => <TileContainer key={tileId} tileId={tileId} />) - } - })()} - <WallContainer roomId={room._id} /> - </Group> - ) -} - -RoomGroup.propTypes = { - room: Shapes.Room, -} - -export default RoomGroup diff --git a/opendc-web/opendc-web-ui/src/components/app/map/groups/TileGroup.js b/opendc-web/opendc-web-ui/src/components/app/map/groups/TileGroup.js deleted file mode 100644 index 1e106823..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/groups/TileGroup.js +++ /dev/null @@ -1,35 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { Group } from 'react-konva' -import RackContainer from '../../../../containers/app/map/RackContainer' -import Shapes from '../../../../shapes/index' -import { ROOM_DEFAULT_COLOR, ROOM_IN_CONSTRUCTION_COLOR } from '../../../../util/colors' -import RoomTile from '../elements/RoomTile' - -const TileGroup = ({ tile, newTile, roomLoad, onClick }) => { - let tileObject - if (tile.rackId) { - tileObject = <RackContainer tile={tile} /> - } else { - tileObject = null - } - - let color = ROOM_DEFAULT_COLOR - if (newTile) { - color = ROOM_IN_CONSTRUCTION_COLOR - } - - return ( - <Group onClick={() => onClick(tile)}> - <RoomTile tile={tile} color={color} /> - {tileObject} - </Group> - ) -} - -TileGroup.propTypes = { - tile: Shapes.Tile, - newTile: PropTypes.bool, -} - -export default TileGroup diff --git a/opendc-web/opendc-web-ui/src/components/app/map/groups/TopologyGroup.js b/opendc-web/opendc-web-ui/src/components/app/map/groups/TopologyGroup.js deleted file mode 100644 index 6096fc8b..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/groups/TopologyGroup.js +++ /dev/null @@ -1,44 +0,0 @@ -import React from 'react' -import { Group } from 'react-konva' -import GrayContainer from '../../../../containers/app/map/GrayContainer' -import RoomContainer from '../../../../containers/app/map/RoomContainer' -import Shapes from '../../../../shapes/index' - -const TopologyGroup = ({ topology, interactionLevel }) => { - if (!topology) { - return <Group /> - } - - if (interactionLevel.mode === 'BUILDING') { - return ( - <Group> - {topology.roomIds.map((roomId) => ( - <RoomContainer key={roomId} roomId={roomId} /> - ))} - </Group> - ) - } - - return ( - <Group> - {topology.roomIds - .filter((roomId) => roomId !== interactionLevel.roomId) - .map((roomId) => ( - <RoomContainer key={roomId} roomId={roomId} /> - ))} - {interactionLevel.mode === 'ROOM' ? <GrayContainer /> : null} - {topology.roomIds - .filter((roomId) => roomId === interactionLevel.roomId) - .map((roomId) => ( - <RoomContainer key={roomId} roomId={roomId} /> - ))} - </Group> - ) -} - -TopologyGroup.propTypes = { - topology: Shapes.Topology, - interactionLevel: Shapes.InteractionLevel, -} - -export default TopologyGroup diff --git a/opendc-web/opendc-web-ui/src/components/app/map/groups/WallGroup.js b/opendc-web/opendc-web-ui/src/components/app/map/groups/WallGroup.js deleted file mode 100644 index 7b0f5ca0..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/groups/WallGroup.js +++ /dev/null @@ -1,22 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { Group } from 'react-konva' -import Shapes from '../../../../shapes/index' -import { deriveWallLocations } from '../../../../util/tile-calculations' -import WallSegment from '../elements/WallSegment' - -const WallGroup = ({ tiles }) => { - return ( - <Group> - {deriveWallLocations(tiles).map((wallSegment, index) => ( - <WallSegment key={index} wallSegment={wallSegment} /> - ))} - </Group> - ) -} - -WallGroup.propTypes = { - tiles: PropTypes.arrayOf(Shapes.Tile).isRequired, -} - -export default WallGroup diff --git a/opendc-web/opendc-web-ui/src/components/app/map/layers/HoverLayerComponent.js b/opendc-web/opendc-web-ui/src/components/app/map/layers/HoverLayerComponent.js deleted file mode 100644 index bead87de..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/layers/HoverLayerComponent.js +++ /dev/null @@ -1,75 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { Layer } from 'react-konva' -import HoverTile from '../elements/HoverTile' -import { TILE_SIZE_IN_PIXELS } from '../MapConstants' - -class HoverLayerComponent extends React.Component { - static propTypes = { - mouseX: PropTypes.number.isRequired, - mouseY: PropTypes.number.isRequired, - mapPosition: PropTypes.object.isRequired, - mapScale: PropTypes.number.isRequired, - isEnabled: PropTypes.func.isRequired, - onClick: PropTypes.func.isRequired, - } - - state = { - positionX: -1, - positionY: -1, - validity: false, - } - - componentDidUpdate() { - if (!this.props.isEnabled()) { - return - } - - const positionX = Math.floor( - (this.props.mouseX - this.props.mapPosition.x) / (this.props.mapScale * TILE_SIZE_IN_PIXELS) - ) - const positionY = Math.floor( - (this.props.mouseY - this.props.mapPosition.y) / (this.props.mapScale * TILE_SIZE_IN_PIXELS) - ) - - if (positionX !== this.state.positionX || positionY !== this.state.positionY) { - this.setState({ - positionX, - positionY, - validity: this.props.isValid(positionX, positionY), - }) - } - } - - render() { - if (!this.props.isEnabled()) { - return <Layer /> - } - - const pixelX = this.props.mapScale * this.state.positionX * TILE_SIZE_IN_PIXELS + this.props.mapPosition.x - const pixelY = this.props.mapScale * this.state.positionY * TILE_SIZE_IN_PIXELS + this.props.mapPosition.y - - return ( - <Layer opacity={0.6}> - <HoverTile - pixelX={pixelX} - pixelY={pixelY} - scale={this.props.mapScale} - isValid={this.state.validity} - onClick={() => - this.state.validity ? this.props.onClick(this.state.positionX, this.state.positionY) : undefined - } - /> - {this.props.children - ? React.cloneElement(this.props.children, { - pixelX, - pixelY, - scale: this.props.mapScale, - }) - : undefined} - </Layer> - ) - } -} - -export default HoverLayerComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/map/layers/MapLayerComponent.js b/opendc-web/opendc-web-ui/src/components/app/map/layers/MapLayerComponent.js deleted file mode 100644 index 8ee14c9c..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/layers/MapLayerComponent.js +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react' -import { Group, Layer } from 'react-konva' -import TopologyContainer from '../../../../containers/app/map/TopologyContainer' -import Backdrop from '../elements/Backdrop' -import GridGroup from '../groups/GridGroup' - -const MapLayerComponent = ({ mapPosition, mapScale }) => ( - <Layer> - <Group x={mapPosition.x} y={mapPosition.y} scaleX={mapScale} scaleY={mapScale}> - <Backdrop /> - <TopologyContainer /> - <GridGroup /> - </Group> - </Layer> -) - -export default MapLayerComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/map/layers/ObjectHoverLayerComponent.js b/opendc-web/opendc-web-ui/src/components/app/map/layers/ObjectHoverLayerComponent.js deleted file mode 100644 index 661fc255..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/layers/ObjectHoverLayerComponent.js +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react' -import TilePlusIcon from '../elements/TilePlusIcon' -import HoverLayerComponent from './HoverLayerComponent' - -const ObjectHoverLayerComponent = (props) => ( - <HoverLayerComponent {...props}> - <TilePlusIcon {...props} /> - </HoverLayerComponent> -) - -export default ObjectHoverLayerComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/map/layers/RoomHoverLayerComponent.js b/opendc-web/opendc-web-ui/src/components/app/map/layers/RoomHoverLayerComponent.js deleted file mode 100644 index 887e2891..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/layers/RoomHoverLayerComponent.js +++ /dev/null @@ -1,6 +0,0 @@ -import React from 'react' -import HoverLayerComponent from './HoverLayerComponent' - -const RoomHoverLayerComponent = (props) => <HoverLayerComponent {...props} /> - -export default RoomHoverLayerComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/results/PortfolioResultsComponent.js b/opendc-web/opendc-web-ui/src/components/app/results/PortfolioResultsComponent.js deleted file mode 100644 index c0b16fee..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/results/PortfolioResultsComponent.js +++ /dev/null @@ -1,94 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import { Bar, CartesianGrid, ComposedChart, ErrorBar, ResponsiveContainer, Scatter, Tooltip, XAxis, YAxis } from 'recharts' -import { AVAILABLE_METRICS, METRIC_NAMES_SHORT, METRIC_UNITS } from '../../../util/available-metrics' -import { mean, std } from 'mathjs' -import Shapes from '../../../shapes/index' -import approx from 'approximate-number' - -const PortfolioResultsComponent = ({ portfolio, scenarios }) => { - if (!portfolio) { - return <div>Loading...</div> - } - - const nonFinishedScenarios = scenarios.filter((s) => s.simulation.state !== 'FINISHED') - - if (nonFinishedScenarios.length > 0) { - if (nonFinishedScenarios.every((s) => s.simulation.state === 'QUEUED' || s.simulation.state === 'RUNNING')) { - return ( - <div> - <h1>Simulation running...</h1> - <p>{nonFinishedScenarios.length} of the scenarios are still being simulated</p> - </div> - ) - } - if (nonFinishedScenarios.some((s) => s.simulation.state === 'FAILED')) { - return ( - <div> - <h1>Simulation failed.</h1> - <p> - Try again by creating a new scenario. Please contact the OpenDC team for support, if issues - persist. - </p> - </div> - ) - } - } - - const dataPerMetric = {} - - AVAILABLE_METRICS.forEach((metric) => { - dataPerMetric[metric] = scenarios.map((scenario) => ({ - name: scenario.name, - value: mean(scenario.results[metric]), - errorX: std(scenario.results[metric]), - })) - }) - - return ( - <div className="full-height" style={{ overflowY: 'scroll', overflowX: 'hidden' }}> - <h2>Portfolio: {portfolio.name}</h2> - <p>Repeats per Scenario: {portfolio.targets.repeatsPerScenario}</p> - <div className="row"> - {AVAILABLE_METRICS.map((metric) => ( - <div className="col-6 mb-2" key={metric}> - <h4>{METRIC_NAMES_SHORT[metric]}</h4> - <ResponsiveContainer aspect={16 / 9} width="100%"> - <ComposedChart - data={dataPerMetric[metric]} - margin={{ left: 35, bottom: 15 }} - layout="vertical" - > - <CartesianGrid strokeDasharray="3 3" /> - <XAxis - tickFormatter={(tick) => approx(tick)} - label={{ value: METRIC_UNITS[metric], position: 'bottom', offset: 0 }} - type="number" - /> - <YAxis dataKey="name" type="category" /> - <Bar dataKey="value" fill="#3399FF" isAnimationActive={false} /> - <Scatter dataKey="value" opacity={0} isAnimationActive={false}> - <ErrorBar - dataKey="errorX" - width={10} - strokeWidth={3} - stroke="#FF6600" - direction="x" - /> - </Scatter> - <Tooltip/> - </ComposedChart> - </ResponsiveContainer> - </div> - ))} - </div> - </div> - ) -} - -PortfolioResultsComponent.propTypes = { - portfolio: Shapes.Portfolio, - scenarios: PropTypes.arrayOf(Shapes.Scenario), -} - -export default PortfolioResultsComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/Sidebar.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/Sidebar.js deleted file mode 100644 index f7368f54..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/Sidebar.js +++ /dev/null @@ -1,53 +0,0 @@ -import PropTypes from 'prop-types' -import classNames from 'classnames' -import React from 'react' -import './Sidebar.sass' - -class Sidebar extends React.Component { - static propTypes = { - isRight: PropTypes.bool.isRequired, - collapsible: PropTypes.bool, - } - - static defaultProps = { - collapsible: true, - } - - state = { - collapsed: false, - } - - render() { - const collapseButton = ( - <div - className={classNames('sidebar-collapse-button', { - 'sidebar-collapse-button-right': this.props.isRight, - })} - onClick={() => this.setState({ collapsed: !this.state.collapsed })} - > - {(this.state.collapsed && this.props.isRight) || (!this.state.collapsed && !this.props.isRight) ? ( - <span className="fa fa-angle-left" title={this.props.isRight ? 'Expand' : 'Collapse'} /> - ) : ( - <span className="fa fa-angle-right" title={this.props.isRight ? 'Collapse' : 'Expand'} /> - )} - </div> - ) - - if (this.state.collapsed) { - return collapseButton - } - return ( - <div - className={classNames('sidebar p-3 h-100', { - 'sidebar-right': this.props.isRight, - })} - onWheel={(e) => e.stopPropagation()} - > - {this.props.children} - {this.props.collapsible && collapseButton} - </div> - ) - } -} - -export default Sidebar diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/Sidebar.sass b/opendc-web/opendc-web-ui/src/components/app/sidebars/Sidebar.sass deleted file mode 100644 index b8e15716..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/Sidebar.sass +++ /dev/null @@ -1,50 +0,0 @@ -@import ../../../style-globals/_variables.sass -@import ../../../style-globals/_mixins.sass - -.sidebar-collapse-button - position: absolute - left: 5px - top: 5px - padding: 5px 7px - - background: white - border: solid 1px $gray-semi-light - z-index: 99 - - +clickable - +border-radius(5px) - +transition(background, 200ms) - - &.sidebar-collapse-button-right - left: auto - right: 5px - top: 5px - - &:hover - background: #eeeeee - -.sidebar - position: absolute - top: 0 - left: 0 - width: $side-bar-width - - z-index: 100 - background: white - - border-right: $gray-semi-dark 1px solid - - .sidebar-collapse-button - left: auto - right: -25px - -.sidebar-right - left: auto - right: 0 - - border-left: $gray-semi-dark 1px solid - border-right: none - - .sidebar-collapse-button-right - left: -25px - right: auto diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/project/PortfolioListComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/project/PortfolioListComponent.js deleted file mode 100644 index b000b9e2..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/project/PortfolioListComponent.js +++ /dev/null @@ -1,66 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import Shapes from '../../../../shapes' -import { Link } from 'react-router-dom' -import FontAwesome from 'react-fontawesome' -import ScenarioListContainer from '../../../../containers/app/sidebars/project/ScenarioListContainer' - -class PortfolioListComponent extends React.Component { - static propTypes = { - portfolios: PropTypes.arrayOf(Shapes.Portfolio), - currentProjectId: PropTypes.string.isRequired, - currentPortfolioId: PropTypes.string, - onNewPortfolio: PropTypes.func.isRequired, - onChoosePortfolio: PropTypes.func.isRequired, - onDeletePortfolio: PropTypes.func.isRequired, - } - - onDelete(id) { - this.props.onDeletePortfolio(id) - } - - render() { - return ( - <div className="pb-3"> - <h2> - Portfolios - <button - className="btn btn-outline-primary float-right" - onClick={this.props.onNewPortfolio.bind(this)} - > - <FontAwesome name="plus" /> - </button> - </h2> - - {this.props.portfolios.map((portfolio, idx) => ( - <div key={portfolio._id}> - <div className="row mb-1"> - <div - className={ - 'col-7 align-self-center ' + - (portfolio._id === this.props.currentPortfolioId ? 'font-weight-bold' : '') - } - > - {portfolio.name} - </div> - <div className="col-5 text-right"> - <Link - className="btn btn-outline-primary mr-1 fa fa-play" - to={`/projects/${this.props.currentProjectId}/portfolios/${portfolio._id}`} - onClick={() => this.props.onChoosePortfolio(portfolio._id)} - /> - <span - className="btn btn-outline-danger fa fa-trash" - onClick={() => this.onDelete(portfolio._id)} - /> - </div> - </div> - <ScenarioListContainer portfolioId={portfolio._id} /> - </div> - ))} - </div> - ) - } -} - -export default PortfolioListComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/project/ProjectSidebarComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/project/ProjectSidebarComponent.js deleted file mode 100644 index 4789315e..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/project/ProjectSidebarComponent.js +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react' -import Sidebar from '../Sidebar' -import TopologyListContainer from '../../../../containers/app/sidebars/project/TopologyListContainer' -import PortfolioListContainer from '../../../../containers/app/sidebars/project/PortfolioListContainer' - -const ProjectSidebarComponent = ({ collapsible }) => ( - <Sidebar isRight={false} collapsible={collapsible}> - <div className="h-100 overflow-auto container-fluid"> - <TopologyListContainer /> - <PortfolioListContainer /> - </div> - </Sidebar> -) - -export default ProjectSidebarComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/project/ScenarioListComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/project/ScenarioListComponent.js deleted file mode 100644 index e775a663..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/project/ScenarioListComponent.js +++ /dev/null @@ -1,62 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import Shapes from '../../../../shapes' -import { Link } from 'react-router-dom' -import FontAwesome from 'react-fontawesome' - -class ScenarioListComponent extends React.Component { - static propTypes = { - scenarios: PropTypes.arrayOf(Shapes.Scenario), - portfolioId: PropTypes.string, - currentProjectId: PropTypes.string.isRequired, - currentScenarioId: PropTypes.string, - onNewScenario: PropTypes.func.isRequired, - onChooseScenario: PropTypes.func.isRequired, - onDeleteScenario: PropTypes.func.isRequired, - } - - onDelete(id) { - this.props.onDeleteScenario(id) - } - - render() { - return ( - <> - {this.props.scenarios.map((scenario, idx) => ( - <div key={scenario._id} className="row mb-1"> - <div - className={ - 'col-7 pl-5 align-self-center ' + - (scenario._id === this.props.currentScenarioId ? 'font-weight-bold' : '') - } - > - {scenario.name} - </div> - <div className="col-5 text-right"> - <Link - className="btn btn-outline-primary mr-1 fa fa-play disabled" - to={`/projects/${this.props.currentProjectId}/portfolios/${scenario.portfolioId}/scenarios/${scenario._id}`} - onClick={() => this.props.onChooseScenario(scenario.portfolioId, scenario._id)} - /> - <span - className={'btn btn-outline-danger fa fa-trash ' + (idx === 0 ? 'disabled' : '')} - onClick={() => (idx !== 0 ? this.onDelete(scenario._id) : undefined)} - /> - </div> - </div> - ))} - <div className="pl-4 mb-2"> - <div - className="btn btn-outline-primary" - onClick={() => this.props.onNewScenario(this.props.portfolioId)} - > - <FontAwesome name="plus" className="mr-1" /> - New scenario - </div> - </div> - </> - ) - } -} - -export default ScenarioListComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/project/TopologyListComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/project/TopologyListComponent.js deleted file mode 100644 index 2f42f7e4..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/project/TopologyListComponent.js +++ /dev/null @@ -1,60 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import Shapes from '../../../../shapes' -import FontAwesome from 'react-fontawesome' - -class TopologyListComponent extends React.Component { - static propTypes = { - topologies: PropTypes.arrayOf(Shapes.Topology), - currentTopologyId: PropTypes.string, - onChooseTopology: PropTypes.func.isRequired, - onNewTopology: PropTypes.func.isRequired, - onDeleteTopology: PropTypes.func.isRequired, - } - - onChoose(id) { - this.props.onChooseTopology(id) - } - - onDelete(id) { - this.props.onDeleteTopology(id) - } - - render() { - return ( - <div className="pb-3"> - <h2> - Topologies - <button className="btn btn-outline-primary float-right" onClick={this.props.onNewTopology}> - <FontAwesome name="plus" /> - </button> - </h2> - - {this.props.topologies.map((topology, idx) => ( - <div key={topology._id} className="row mb-1"> - <div - className={ - 'col-7 align-self-center ' + - (topology._id === this.props.currentTopologyId ? 'font-weight-bold' : '') - } - > - {topology.name} - </div> - <div className="col-5 text-right"> - <span - className="btn btn-outline-primary mr-1 fa fa-play" - onClick={() => this.onChoose(topology._id)} - /> - <span - className={'btn btn-outline-danger fa fa-trash ' + (idx === 0 ? 'disabled' : '')} - onClick={() => (idx !== 0 ? this.onDelete(topology._id) : undefined)} - /> - </div> - </div> - ))} - </div> - ) - } -} - -export default TopologyListComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/NameComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/NameComponent.js deleted file mode 100644 index 5fb0dc55..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/NameComponent.js +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react' -import FontAwesome from 'react-fontawesome' - -const NameComponent = ({ name, onEdit }) => ( - <h2> - {name} - <button className="btn btn-outline-secondary float-right" onClick={onEdit}> - <FontAwesome name="pencil" /> - </button> - </h2> -) - -export default NameComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/TopologySidebarComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/TopologySidebarComponent.js deleted file mode 100644 index f5eee36b..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/TopologySidebarComponent.js +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react' -import BuildingSidebarContainer from '../../../../containers/app/sidebars/topology/building/BuildingSidebarContainer' -import MachineSidebarContainer from '../../../../containers/app/sidebars/topology/machine/MachineSidebarContainer' -import RackSidebarContainer from '../../../../containers/app/sidebars/topology/rack/RackSidebarContainer' -import RoomSidebarContainer from '../../../../containers/app/sidebars/topology/room/RoomSidebarContainer' -import Sidebar from '../Sidebar' - -const TopologySidebarComponent = ({ interactionLevel }) => { - let sidebarContent - - switch (interactionLevel.mode) { - case 'BUILDING': - sidebarContent = <BuildingSidebarContainer /> - break - case 'ROOM': - sidebarContent = <RoomSidebarContainer /> - break - case 'RACK': - sidebarContent = <RackSidebarContainer /> - break - case 'MACHINE': - sidebarContent = <MachineSidebarContainer /> - break - default: - sidebarContent = 'Missing Content' - } - - return <Sidebar isRight={true}>{sidebarContent}</Sidebar> -} - -export default TopologySidebarComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/BuildingSidebarComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/BuildingSidebarComponent.js deleted file mode 100644 index eea62f84..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/BuildingSidebarComponent.js +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react' -import NewRoomConstructionContainer from '../../../../../containers/app/sidebars/topology/building/NewRoomConstructionContainer' - -const BuildingSidebarComponent = () => { - return ( - <div> - <h2>Building</h2> - <NewRoomConstructionContainer /> - </div> - ) -} - -export default BuildingSidebarComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/NewRoomConstructionComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/NewRoomConstructionComponent.js deleted file mode 100644 index fd552c1e..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/NewRoomConstructionComponent.js +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react' - -const NewRoomConstructionComponent = ({ onStart, onFinish, onCancel, currentRoomInConstruction }) => { - if (currentRoomInConstruction === '-1') { - return ( - <div className="btn btn-outline-primary btn-block" onClick={onStart}> - <span className="fa fa-plus mr-2" /> - Construct a new room - </div> - ) - } - return ( - <div> - <div className="btn btn-primary btn-block" onClick={onFinish}> - <span className="fa fa-check mr-2" /> - Finalize new room - </div> - <div className="btn btn-default btn-block" onClick={onCancel}> - <span className="fa fa-times mr-2" /> - Cancel construction - </div> - </div> - ) -} - -export default NewRoomConstructionComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/BackToRackComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/BackToRackComponent.js deleted file mode 100644 index 70d522b2..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/BackToRackComponent.js +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react' - -const BackToRackComponent = ({ onClick }) => ( - <div className="btn btn-secondary btn-block" onClick={onClick}> - <span className="fa fa-angle-left mr-2" /> - Back to rack - </div> -) - -export default BackToRackComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/DeleteMachineComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/DeleteMachineComponent.js deleted file mode 100644 index 37820316..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/DeleteMachineComponent.js +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react' - -const DeleteMachineComponent = ({ onClick }) => ( - <div className="btn btn-outline-danger btn-block" onClick={onClick}> - <span className="fa fa-trash mr-2" /> - Delete this machine - </div> -) - -export default DeleteMachineComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineNameComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineNameComponent.js deleted file mode 100644 index 992383c4..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineNameComponent.js +++ /dev/null @@ -1,5 +0,0 @@ -import React from 'react' - -const MachineNameComponent = ({ position }) => <h2>Machine at slot {position}</h2> - -export default MachineNameComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineSidebarComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineSidebarComponent.js deleted file mode 100644 index 7c78cf9e..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineSidebarComponent.js +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react' -import BackToRackContainer from '../../../../../containers/app/sidebars/topology/machine/BackToRackContainer' -import DeleteMachineContainer from '../../../../../containers/app/sidebars/topology/machine/DeleteMachineContainer' -import MachineNameContainer from '../../../../../containers/app/sidebars/topology/machine/MachineNameContainer' -import UnitTabsContainer from '../../../../../containers/app/sidebars/topology/machine/UnitTabsContainer' - -const MachineSidebarComponent = ({ machineId }) => { - return ( - <div className="h-100 overflow-auto"> - <MachineNameContainer /> - <BackToRackContainer /> - <DeleteMachineContainer /> - <UnitTabsContainer /> - </div> - ) -} - -export default MachineSidebarComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitAddComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitAddComponent.js deleted file mode 100644 index 4e9dbc7e..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitAddComponent.js +++ /dev/null @@ -1,35 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' - -class UnitAddComponent extends React.Component { - static propTypes = { - units: PropTypes.array.isRequired, - onAdd: PropTypes.func.isRequired, - } - - render() { - return ( - <div className="form-inline"> - <div className="form-group w-100"> - <select className="form-control w-70 mr-1" ref={(unitSelect) => (this.unitSelect = unitSelect)}> - {this.props.units.map((unit) => ( - <option value={unit._id} key={unit._id}> - {unit.name} - </option> - ))} - </select> - <button - type="submit" - className="btn btn-outline-primary" - onClick={() => this.props.onAdd(this.unitSelect.value)} - > - <span className="fa fa-plus mr-2" /> - Add - </button> - </div> - </div> - ) - } -} - -export default UnitAddComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitComponent.js deleted file mode 100644 index de55e506..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitComponent.js +++ /dev/null @@ -1,52 +0,0 @@ -import React from 'react' -import { UncontrolledPopover, PopoverHeader, PopoverBody, Button } from 'reactstrap' - -function UnitComponent({ index, unitType, unit, onDelete }) { - let unitInfo - if (unitType === 'cpu' || unitType === 'gpu') { - unitInfo = ( - <> - <strong>Clockrate: </strong> - <code>{unit.clockRateMhz}</code> - <br /> - <strong>Num. Cores: </strong> - <code>{unit.numberOfCores}</code> - <br /> - <strong>Energy Cons.: </strong> - <code>{unit.energyConsumptionW} W</code> - <br /> - </> - ) - } else if (unitType === 'memory' || unitType === 'storage') { - unitInfo = ( - <> - <strong>Speed:</strong> - <code>{unit.speedMbPerS} Mb/s</code> - <br /> - <strong>Size:</strong> - <code>{unit.sizeMb} MB</code> - <br /> - <strong>Energy Cons.:</strong> - <code>{unit.energyConsumptionW} W</code> - <br /> - </> - ) - } - - return ( - <li className="d-flex list-group-item justify-content-between align-items-center"> - <span style={{ maxWidth: '60%' }}>{unit.name}</span> - <span> - <Button outline={true} color="info" className="mr-1 fa fa-info-circle" id={`unit-${index}`} /> - <UncontrolledPopover trigger="focus" placement="left" target={`unit-${index}`}> - <PopoverHeader>Unit Information</PopoverHeader> - <PopoverBody>{unitInfo}</PopoverBody> - </UncontrolledPopover> - - <span className="btn btn-outline-danger fa fa-trash" onClick={onDelete} /> - </span> - </li> - ) -} - -export default UnitComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitListComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitListComponent.js deleted file mode 100644 index 2ade0f6a..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitListComponent.js +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react' -import UnitContainer from '../../../../../containers/app/sidebars/topology/machine/UnitContainer' - -const UnitListComponent = ({ unitType, unitIds }) => ( - <ul className="list-group mt-1"> - {unitIds.length !== 0 ? ( - unitIds.map((unitId, index) => ( - <UnitContainer unitType={unitType} unitId={unitId} index={index} key={index} /> - )) - ) : ( - <div className="alert alert-info"> - <span> - <strong>No units...</strong> Add some with the menu above! - </span> - </div> - )} - </ul> -) - -export default UnitListComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitTabsComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitTabsComponent.js deleted file mode 100644 index 6599fefd..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitTabsComponent.js +++ /dev/null @@ -1,78 +0,0 @@ -import React, { useState } from 'react' -import { Nav, NavItem, NavLink, TabContent, TabPane } from 'reactstrap' -import UnitAddContainer from '../../../../../containers/app/sidebars/topology/machine/UnitAddContainer' -import UnitListContainer from '../../../../../containers/app/sidebars/topology/machine/UnitListContainer' - -const UnitTabsComponent = () => { - const [activeTab, setActiveTab] = useState('cpu-units') - const toggle = (tab) => { - if (activeTab !== tab) setActiveTab(tab) - } - - return ( - <div> - <Nav tabs> - <NavItem> - <NavLink - className={activeTab === 'cpu-units' ? 'active' : ''} - onClick={() => { - toggle('cpu-units') - }} - > - CPU - </NavLink> - </NavItem> - <NavItem> - <NavLink - className={activeTab === 'gpu-units' ? 'active' : ''} - onClick={() => { - toggle('gpu-units') - }} - > - GPU - </NavLink> - </NavItem> - <NavItem> - <NavLink - className={activeTab === 'memory-units' ? 'active' : ''} - onClick={() => { - toggle('memory-units') - }} - > - Memory - </NavLink> - </NavItem> - <NavItem> - <NavLink - className={activeTab === 'storage-units' ? 'active' : ''} - onClick={() => { - toggle('storage-units') - }} - > - Stor. - </NavLink> - </NavItem> - </Nav> - <TabContent activeTab={activeTab}> - <TabPane tabId="cpu-units"> - <UnitAddContainer unitType="cpu" /> - <UnitListContainer unitType="cpu" /> - </TabPane> - <TabPane tabId="gpu-units"> - <UnitAddContainer unitType="gpu" /> - <UnitListContainer unitType="gpu" /> - </TabPane> - <TabPane tabId="memory-units"> - <UnitAddContainer unitType="memory" /> - <UnitListContainer unitType="memory" /> - </TabPane> - <TabPane tabId="storage-units"> - <UnitAddContainer unitType="storage" /> - <UnitListContainer unitType="storage" /> - </TabPane> - </TabContent> - </div> - ) -} - -export default UnitTabsComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/AddPrefabComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/AddPrefabComponent.js deleted file mode 100644 index 75418f9d..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/AddPrefabComponent.js +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react' - -const AddPrefabComponent = ({ onClick }) => ( - <div className="btn btn-primary btn-block" onClick={onClick}> - <span className="fa fa-floppy-o mr-2" /> - Save this rack to a prefab - </div> -) - -export default AddPrefabComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/BackToRoomComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/BackToRoomComponent.js deleted file mode 100644 index c14775bf..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/BackToRoomComponent.js +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react' - -const BackToRoomComponent = ({ onClick }) => ( - <div className="btn btn-secondary btn-block mb-2" onClick={onClick}> - <span className="fa fa-angle-left mr-2" /> - Back to room - </div> -) - -export default BackToRoomComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/DeleteRackComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/DeleteRackComponent.js deleted file mode 100644 index 23b0daac..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/DeleteRackComponent.js +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react' - -const DeleteRackComponent = ({ onClick }) => ( - <div className="btn btn-outline-danger btn-block" onClick={onClick}> - <span className="fa fa-trash mr-2" /> - Delete this rack - </div> -) - -export default DeleteRackComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/EmptySlotComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/EmptySlotComponent.js deleted file mode 100644 index d7e30f1d..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/EmptySlotComponent.js +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react' - -const EmptySlotComponent = ({ position, onAdd }) => ( - <li className="list-group-item d-flex justify-content-between align-items-center"> - <span className="badge badge-default badge-info mr-1 disabled">{position}</span> - <button className="btn btn-outline-primary" onClick={onAdd}> - <span className="fa fa-plus mr-2" /> - Add machine - </button> - </li> -) - -export default EmptySlotComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineComponent.js deleted file mode 100644 index caa3dc04..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineComponent.js +++ /dev/null @@ -1,43 +0,0 @@ -import React from 'react' -import Shapes from '../../../../../shapes' - -const UnitIcon = ({ id, type }) => ( - <div> - <img - src={'/img/topology/' + id + '-icon.png'} - alt={'Machine contains ' + type + ' units'} - className="img-fluid ml-1" - style={{ maxHeight: '35px' }} - /> - </div> -) - -const MachineComponent = ({ position, machine, onClick }) => { - const hasNoUnits = - machine.cpuIds.length + machine.gpuIds.length + machine.memoryIds.length + machine.storageIds.length === 0 - - return ( - <li - className="d-flex list-group-item list-group-item-action justify-content-between align-items-center" - onClick={onClick} - style={{ backgroundColor: 'white' }} - > - <span className="badge badge-default badge-info mr-1">{position}</span> - <div className="d-inline-flex"> - {machine.cpuIds.length > 0 ? <UnitIcon id="cpu" type="CPU" /> : undefined} - {machine.gpuIds.length > 0 ? <UnitIcon id="gpu" type="GPU" /> : undefined} - {machine.memoryIds.length > 0 ? <UnitIcon id="memory" type="memory" /> : undefined} - {machine.storageIds.length > 0 ? <UnitIcon id="storage" type="storage" /> : undefined} - {hasNoUnits ? ( - <span className="badge badge-default badge-warning">Machine with no units</span> - ) : undefined} - </div> - </li> - ) -} - -MachineComponent.propTypes = { - machine: Shapes.Machine, -} - -export default MachineComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListComponent.js deleted file mode 100644 index 12be26bd..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListComponent.js +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react' -import EmptySlotContainer from '../../../../../containers/app/sidebars/topology/rack/EmptySlotContainer' -import MachineContainer from '../../../../../containers/app/sidebars/topology/rack/MachineContainer' -import './MachineListComponent.sass' - -const MachineListComponent = ({ machineIds }) => { - return ( - <ul className="list-group machine-list"> - {machineIds.map((machineId, index) => { - if (machineId === null) { - return <EmptySlotContainer key={index} position={index + 1} /> - } else { - return <MachineContainer key={index} position={index + 1} machineId={machineId} /> - } - })} - </ul> - ) -} - -export default MachineListComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListComponent.sass b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListComponent.sass deleted file mode 100644 index 11b82c93..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListComponent.sass +++ /dev/null @@ -1,2 +0,0 @@ -.machine-list li - min-height: 64px diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackNameComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackNameComponent.js deleted file mode 100644 index b701909a..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackNameComponent.js +++ /dev/null @@ -1,6 +0,0 @@ -import React from 'react' -import NameComponent from '../NameComponent' - -const RackNameComponent = ({ rackName, onEdit }) => <NameComponent name={rackName} onEdit={onEdit} /> - -export default RackNameComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.js deleted file mode 100644 index ca41bf57..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.js +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react' -import BackToRoomContainer from '../../../../../containers/app/sidebars/topology/rack/BackToRoomContainer' -import DeleteRackContainer from '../../../../../containers/app/sidebars/topology/rack/DeleteRackContainer' -import MachineListContainer from '../../../../../containers/app/sidebars/topology/rack/MachineListContainer' -import RackNameContainer from '../../../../../containers/app/sidebars/topology/rack/RackNameContainer' -import './RackSidebarComponent.sass' -import AddPrefabContainer from '../../../../../containers/app/sidebars/topology/rack/AddPrefabContainer' - -const RackSidebarComponent = () => { - return ( - <div className="rack-sidebar-container flex-column"> - <div className="rack-sidebar-header-container"> - <RackNameContainer /> - <BackToRoomContainer /> - <AddPrefabContainer /> - <DeleteRackContainer /> - </div> - <div className="machine-list-container mt-2"> - <MachineListContainer /> - </div> - </div> - ) -} - -export default RackSidebarComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.sass b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.sass deleted file mode 100644 index 29fec02a..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.sass +++ /dev/null @@ -1,11 +0,0 @@ -.rack-sidebar-container - display: flex - height: 100% - max-height: 100% - -.rack-sidebar-header-container - flex: 0 - -.machine-list-container - flex: 1 - overflow-y: scroll diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/BackToBuildingComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/BackToBuildingComponent.js deleted file mode 100644 index 64c0a1f6..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/BackToBuildingComponent.js +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react' - -const BackToBuildingComponent = ({ onClick }) => ( - <div className="btn btn-secondary btn-block mb-2" onClick={onClick}> - <span className="fa fa-angle-left mr-2" /> - Back to building - </div> -) - -export default BackToBuildingComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/DeleteRoomComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/DeleteRoomComponent.js deleted file mode 100644 index 78417359..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/DeleteRoomComponent.js +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react' - -const DeleteRoomComponent = ({ onClick }) => ( - <div className="btn btn-outline-danger btn-block" onClick={onClick}> - <span className="fa fa-trash mr-2" /> - Delete this room - </div> -) - -export default DeleteRoomComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/EditRoomComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/EditRoomComponent.js deleted file mode 100644 index 857a646f..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/EditRoomComponent.js +++ /dev/null @@ -1,22 +0,0 @@ -import classNames from 'classnames' -import React from 'react' - -const EditRoomComponent = ({ onEdit, onFinish, isEditing, isInRackConstructionMode }) => - isEditing ? ( - <div className="btn btn-info btn-block" onClick={onFinish}> - <span className="fa fa-check mr-2" /> - Finish editing room - </div> - ) : ( - <div - className={classNames('btn btn-outline-info btn-block', { - disabled: isInRackConstructionMode, - })} - onClick={() => (isInRackConstructionMode ? undefined : onEdit())} - > - <span className="fa fa-pencil mr-2" /> - Edit the tiles of this room - </div> - ) - -export default EditRoomComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RackConstructionComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RackConstructionComponent.js deleted file mode 100644 index 44566f61..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RackConstructionComponent.js +++ /dev/null @@ -1,27 +0,0 @@ -import classNames from 'classnames' -import React from 'react' - -const RackConstructionComponent = ({ onStart, onStop, inRackConstructionMode, isEditingRoom }) => { - if (inRackConstructionMode) { - return ( - <div className="btn btn-primary btn-block" onClick={onStop}> - <span className="fa fa-times mr-2" /> - Stop rack construction - </div> - ) - } - - return ( - <div - className={classNames('btn btn-outline-primary btn-block', { - disabled: isEditingRoom, - })} - onClick={() => (isEditingRoom ? undefined : onStart())} - > - <span className="fa fa-plus mr-2" /> - Start rack construction - </div> - ) -} - -export default RackConstructionComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomNameComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomNameComponent.js deleted file mode 100644 index d637828e..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomNameComponent.js +++ /dev/null @@ -1,6 +0,0 @@ -import React from 'react' -import NameComponent from '../NameComponent' - -const RoomNameComponent = ({ roomName, onEdit }) => <NameComponent name={roomName} onEdit={onEdit} /> - -export default RoomNameComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomSidebarComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomSidebarComponent.js deleted file mode 100644 index 1bc6533e..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomSidebarComponent.js +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react' -import BackToBuildingContainer from '../../../../../containers/app/sidebars/topology/room/BackToBuildingContainer' -import DeleteRoomContainer from '../../../../../containers/app/sidebars/topology/room/DeleteRoomContainer' -import EditRoomContainer from '../../../../../containers/app/sidebars/topology/room/EditRoomContainer' -import RackConstructionContainer from '../../../../../containers/app/sidebars/topology/room/RackConstructionContainer' -import RoomNameContainer from '../../../../../containers/app/sidebars/topology/room/RoomNameContainer' - -const RoomSidebarComponent = () => { - return ( - <div> - <RoomNameContainer /> - <BackToBuildingContainer /> - <RackConstructionContainer /> - <EditRoomContainer /> - <DeleteRoomContainer /> - </div> - ) -} - -export default RoomSidebarComponent diff --git a/opendc-web/opendc-web-ui/src/components/context/ContextSelectionSection.js b/opendc-web/opendc-web-ui/src/components/context/ContextSelectionSection.js new file mode 100644 index 00000000..5d3a6441 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/context/ContextSelectionSection.js @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import { contextSelectionSection } from './ContextSelectionSection.module.scss' + +function ContextSelectionSection({ children }) { + return <section className={contextSelectionSection}>{children}</section> +} + +ContextSelectionSection.propTypes = { + children: PropTypes.node, +} + +export default ContextSelectionSection diff --git a/opendc-web/opendc-web-ui/src/components/context/ContextSelectionSection.module.scss b/opendc-web/opendc-web-ui/src/components/context/ContextSelectionSection.module.scss new file mode 100644 index 00000000..0e902af0 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/context/ContextSelectionSection.module.scss @@ -0,0 +1,28 @@ +/*! + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +.contextSelectionSection { + padding-left: var(--pf-c-page__main-breadcrumb--PaddingLeft); + flex-shrink: 0; + border-bottom: var(--pf-global--BorderWidth--sm) solid var(--pf-global--BorderColor--100); + background-color: var(--pf-c-page__main-breadcrumb--BackgroundColor); +} diff --git a/opendc-web/opendc-web-ui/src/components/context/ContextSelector.js b/opendc-web/opendc-web-ui/src/components/context/ContextSelector.js new file mode 100644 index 00000000..3712cfa0 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/context/ContextSelector.js @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import { ContextSelector as PFContextSelector, ContextSelectorItem } from '@patternfly/react-core' +import { useMemo, useState, useReducer } from 'react' +import { contextSelector } from './ContextSelector.module.scss' + +function ContextSelector({ activeItem, items, onSelect, label }) { + const [isOpen, toggle] = useReducer((isOpen) => !isOpen, false) + const [searchValue, setSearchValue] = useState('') + + const filteredItems = useMemo( + () => items.filter(({ name }) => name.toLowerCase().indexOf(searchValue.toLowerCase()) !== -1) || items, + [items, searchValue] + ) + + return ( + <PFContextSelector + menuAppendTo={global.document?.body} + className={contextSelector} + toggleText={activeItem ? `${label}: ${activeItem.name}` : label} + onSearchInputChange={(value) => setSearchValue(value)} + searchInputValue={searchValue} + isOpen={isOpen} + onToggle={toggle} + onSelect={(event) => { + const targetId = event.target.value + const target = items.find((item) => item._id === targetId) + + toggle() + onSelect(target) + }} + > + {filteredItems.map((item) => ( + <ContextSelectorItem key={item._id} value={item._id}> + {item.name} + </ContextSelectorItem> + ))} + </PFContextSelector> + ) +} + +const Item = PropTypes.shape({ + _id: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, +}) + +ContextSelector.propTypes = { + activeItem: Item, + items: PropTypes.arrayOf(Item).isRequired, + onSelect: PropTypes.func.isRequired, + label: PropTypes.string, +} + +export default ContextSelector diff --git a/opendc-web/opendc-web-ui/src/components/context/ContextSelector.module.scss b/opendc-web/opendc-web-ui/src/components/context/ContextSelector.module.scss new file mode 100644 index 00000000..fefba41f --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/context/ContextSelector.module.scss @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +.contextSelector { + width: auto; + margin-right: 20px; + + --pf-c-context-selector__toggle--PaddingTop: var(--pf-global--spacer--sm); + --pf-c-context-selector__toggle--PaddingRight: 0; + --pf-c-context-selector__toggle--PaddingBottom: var(--pf-global--spacer--sm); + --pf-c-context-selector__toggle--PaddingLeft: 0; + --pf-c-context-selector__toggle--BorderWidth: 0; + --pf-c-context-selector__toggle-text--FontSize: var(--pf-global--FontSize--sm); + + & :global(.pf-c-context-selector__toggle) { + &:active, + &:focus-within, + &:global(.pf-m-active) { + --pf-c-context-selector__toggle--after--BorderBottomWidth: 0; + } + } + + &:global(.pf-m-expanded) > :global(.pf-c-context-selector__toggle) { + --pf-c-context-selector__toggle--after--BorderBottomWidth: 0; + } +} diff --git a/opendc-web/opendc-web-ui/src/components/context/PortfolioSelector.js b/opendc-web/opendc-web-ui/src/components/context/PortfolioSelector.js new file mode 100644 index 00000000..694681ac --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/context/PortfolioSelector.js @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { useRouter } from 'next/router' +import { useMemo } from 'react' +import { useProjectPortfolios } from '../../data/project' +import ContextSelector from './ContextSelector' + +function PortfolioSelector() { + const router = useRouter() + const { project, portfolio: activePortfolioId } = router.query + const { data: portfolios = [] } = useProjectPortfolios(project) + const activePortfolio = useMemo(() => portfolios.find((portfolio) => portfolio._id === activePortfolioId), [ + activePortfolioId, + portfolios, + ]) + + return ( + <ContextSelector + label="Portfolio" + activeItem={activePortfolio} + items={portfolios} + onSelect={(portfolio) => router.push(`/projects/${portfolio.projectId}/portfolios/${portfolio._id}`)} + /> + ) +} + +export default PortfolioSelector diff --git a/opendc-web/opendc-web-ui/src/components/context/ProjectSelector.js b/opendc-web/opendc-web-ui/src/components/context/ProjectSelector.js new file mode 100644 index 00000000..753632ab --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/context/ProjectSelector.js @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import { useRouter } from 'next/router' +import { useMemo } from 'react' +import { useProjects } from '../../data/project' +import ContextSelector from './ContextSelector' + +function ProjectSelector({ projectId }) { + const router = useRouter() + const { data: projects = [] } = useProjects() + const activeProject = useMemo(() => projects.find((project) => project._id === projectId), [projectId, projects]) + + return ( + <ContextSelector + label="Project" + activeItem={activeProject} + items={projects} + onSelect={(project) => router.push(`/projects/${project._id}`)} + /> + ) +} + +ProjectSelector.propTypes = { + projectId: PropTypes.string, +} + +export default ProjectSelector diff --git a/opendc-web/opendc-web-ui/src/components/context/TopologySelector.js b/opendc-web/opendc-web-ui/src/components/context/TopologySelector.js new file mode 100644 index 00000000..d5e51c6c --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/context/TopologySelector.js @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import { useRouter } from 'next/router' +import { useMemo } from 'react' +import { useProjectTopologies } from '../../data/topology' +import ContextSelector from './ContextSelector' + +function TopologySelector({ projectId, topologyId }) { + const router = useRouter() + const { data: topologies = [] } = useProjectTopologies(projectId) + const activeTopology = useMemo(() => topologies.find((topology) => topology._id === topologyId), [ + topologyId, + topologies, + ]) + + return ( + <ContextSelector + label="Topology" + activeItem={activeTopology} + items={topologies} + onSelect={(topology) => router.push(`/projects/${topology.projectId}/topologies/${topology._id}`)} + /> + ) +} + +TopologySelector.propTypes = { + projectId: PropTypes.string, + topologyId: PropTypes.string, +} + +export default TopologySelector diff --git a/opendc-web/opendc-web-ui/src/components/home/ContactSection.js b/opendc-web/opendc-web-ui/src/components/home/ContactSection.js deleted file mode 100644 index d5c6e55f..00000000 --- a/opendc-web/opendc-web-ui/src/components/home/ContactSection.js +++ /dev/null @@ -1,55 +0,0 @@ -import React from 'react' -import FontAwesome from 'react-fontawesome' -import './ContactSection.sass' -import ContentSection from './ContentSection' - -const ContactSection = () => ( - <ContentSection name="contact" title="Contact"> - <div className="row justify-content-center"> - <div className="col-4"> - <a href="https://github.com/atlarge-research/opendc"> - <FontAwesome name="github" size="3x" className="mb-2" /> - <div className="w-100" /> - atlarge-research/opendc - </a> - </div> - <div className="col-4"> - <a href="mailto:opendc@atlarge-research.com"> - <FontAwesome name="envelope" size="3x" className="mb-2" /> - <div className="w-100" /> - opendc@atlarge-research.com - </a> - </div> - </div> - <div className="row"> - <div className="col text-center"> - <img src="img/tudelft-icon.png" className="img-fluid tudelft-icon" alt="TU Delft" /> - </div> - </div> - <div className="row"> - <div className="col text-center"> - A project by the - <a href="http://atlarge.science" target="_blank" rel="noopener noreferrer"> - <strong>@Large Research Group</strong> - </a> - . - </div> - </div> - <div className="row"> - <div className="col text-center disclaimer mt-5 small"> - <FontAwesome name="exclamation-triangle" size="2x" className="mr-2" /> - <br /> - <strong>Disclaimer: </strong> - OpenDC is an experimental tool. Your data may get lost, overwritten, or otherwise become unavailable. - <br /> - The OpenDC authors should in no way be liable in the event this happens (see our{' '} - <strong> - <a href="https://github.com/atlarge-research/opendc/blob/master/LICENSE.txt">license</a> - </strong> - ). Sorry for the inconvenience. - </div> - </div> - </ContentSection> -) - -export default ContactSection diff --git a/opendc-web/opendc-web-ui/src/components/home/ContactSection.sass b/opendc-web/opendc-web-ui/src/components/home/ContactSection.sass deleted file mode 100644 index 997f8d98..00000000 --- a/opendc-web/opendc-web-ui/src/components/home/ContactSection.sass +++ /dev/null @@ -1,15 +0,0 @@ -.contact-section - background-color: #444 - color: #ddd - - a - color: #ddd - - a:hover - color: #fff - - .tudelft-icon - height: 100px - - .disclaimer - color: #cccccc diff --git a/opendc-web/opendc-web-ui/src/components/home/ContentSection.js b/opendc-web/opendc-web-ui/src/components/home/ContentSection.js deleted file mode 100644 index 9d4832d9..00000000 --- a/opendc-web/opendc-web-ui/src/components/home/ContentSection.js +++ /dev/null @@ -1,19 +0,0 @@ -import classNames from 'classnames' -import PropTypes from 'prop-types' -import React from 'react' -import './ContentSection.sass' - -const ContentSection = ({ name, title, children }) => ( - <div id={name} className={classNames(name + '-section', 'content-section')}> - <div className="container"> - <h1>{title}</h1> - {children} - </div> - </div> -) - -ContentSection.propTypes = { - name: PropTypes.string.isRequired, -} - -export default ContentSection diff --git a/opendc-web/opendc-web-ui/src/components/home/ContentSection.sass b/opendc-web/opendc-web-ui/src/components/home/ContentSection.sass deleted file mode 100644 index a4c8bd66..00000000 --- a/opendc-web/opendc-web-ui/src/components/home/ContentSection.sass +++ /dev/null @@ -1,9 +0,0 @@ -@import ../../style-globals/_variables.sass - -.content-section - padding-top: 50px - padding-bottom: 150px - text-align: center - - h1 - margin-bottom: 30px diff --git a/opendc-web/opendc-web-ui/src/components/home/IntroSection.js b/opendc-web/opendc-web-ui/src/components/home/IntroSection.js deleted file mode 100644 index a799272a..00000000 --- a/opendc-web/opendc-web-ui/src/components/home/IntroSection.js +++ /dev/null @@ -1,40 +0,0 @@ -import React from 'react' - -const IntroSection = () => ( - <section id="intro" className="intro-section"> - <div className="container pt-5 pb-3"> - <div className="row justify-content-center"> - <div className="col-xl-4 col-lg-4 col-md-4 col-sm-8 col-8"> - <h4>The datacenter (DC) industry...</h4> - <ul> - <li>Is worth over $15 bn, and growing</li> - <li>Has many hard-to-grasp concepts</li> - <li>Needs to become accessible to many</li> - </ul> - </div> - <div className="col-xl-4 col-lg-4 col-md-4 col-sm-8 col-8"> - <img - src="img/datacenter-drawing.png" - className="col-12 img-fluid" - alt="Schematic top-down view of a datacenter" - /> - <p className="col-12 figure-caption text-center"> - <a href="http://www.dolphinhosts.co.uk/wp-content/uploads/2013/07/data-centers.gif"> - Image source - </a> - </p> - </div> - <div className="col-xl-4 col-lg-4 col-md-4 col-sm-8 col-8"> - <h4>OpenDC provides...</h4> - <ul> - <li>Collaborative online DC modeling</li> - <li>Diverse and effective DC simulation</li> - <li>Exploratory DC performance feedback</li> - </ul> - </div> - </div> - </div> - </section> -) - -export default IntroSection diff --git a/opendc-web/opendc-web-ui/src/components/home/JumbotronHeader.js b/opendc-web/opendc-web-ui/src/components/home/JumbotronHeader.js deleted file mode 100644 index 6a9ea00c..00000000 --- a/opendc-web/opendc-web-ui/src/components/home/JumbotronHeader.js +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react' -import { Container, Jumbotron, Button } from 'reactstrap' -import './JumbotronHeader.sass' - -const JumbotronHeader = () => ( - <section className="jumbotron-header"> - <Container> - <Jumbotron className="text-center"> - <h1> - Open<span className="dc">DC</span> - </h1> - <p className="lead">Collaborative Datacenter Simulation and Exploration for Everybody</p> - <img src="img/logo.png" className="img-responsive mt-3" alt="OpenDC" /> - <p className="lead mt-5"> - <Button - tag="a" - target="_blank" - href="https://atlarge-research.com/pdfs/ccgrid21-opendc-paper.pdf" - color="warning" - > - Read about <strong>OpenDC 2.0</strong> <i className="fa fa-external-link" /> - </Button> - </p> - </Jumbotron> - </Container> - </section> -) - -export default JumbotronHeader diff --git a/opendc-web/opendc-web-ui/src/components/home/JumbotronHeader.sass b/opendc-web/opendc-web-ui/src/components/home/JumbotronHeader.sass deleted file mode 100644 index 1b6a89fd..00000000 --- a/opendc-web/opendc-web-ui/src/components/home/JumbotronHeader.sass +++ /dev/null @@ -1,24 +0,0 @@ -.jumbotron-header - background: #00A6D6 - -.jumbotron - background-color: inherit - margin-bottom: 0 - - padding-top: 120px - padding-bottom: 120px - - img - max-width: 110px - - h1 - color: #fff - font-size: 4.5em - - .dc - color: #fff - font-weight: bold - - .lead - color: #fff - font-size: 1.4em diff --git a/opendc-web/opendc-web-ui/src/components/home/ModelingSection.js b/opendc-web/opendc-web-ui/src/components/home/ModelingSection.js deleted file mode 100644 index 643dca65..00000000 --- a/opendc-web/opendc-web-ui/src/components/home/ModelingSection.js +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react' -import ScreenshotSection from './ScreenshotSection' - -const ModelingSection = () => ( - <ScreenshotSection - name="modeling" - title="Datacenter Modeling" - imageUrl="/img/screenshot-construction.png" - caption="Building a datacenter in OpenDC" - imageIsRight={true} - > - <h3>Collaboratively...</h3> - <ul> - <li>Model DC layout, and room locations and types</li> - <li>Place racks in rooms and nodes in racks</li> - <li>Add real-world CPU, GPU, memory, storage and network units to each node</li> - <li>Select from diverse scheduling policies</li> - </ul> - </ScreenshotSection> -) - -export default ModelingSection diff --git a/opendc-web/opendc-web-ui/src/components/home/ScreenshotSection.js b/opendc-web/opendc-web-ui/src/components/home/ScreenshotSection.js deleted file mode 100644 index 263590d5..00000000 --- a/opendc-web/opendc-web-ui/src/components/home/ScreenshotSection.js +++ /dev/null @@ -1,29 +0,0 @@ -import classNames from 'classnames' -import React from 'react' -import { Row, Col } from 'reactstrap' -import ContentSection from './ContentSection' -import './ScreenshotSection.sass' - -const ScreenshotSection = ({ name, title, imageUrl, caption, imageIsRight, children }) => ( - <ContentSection name={name} title={title}> - <Row> - <Col - xl="5" - lg="5" - md="5" - sm="!2" - className={classNames('text-left my-auto', { - 'order-1': !imageIsRight, - })} - > - {children} - </Col> - <Col xl="7" lg="7" md="7" sm="12"> - <img src={imageUrl} className="col-12 screenshot" alt={caption} /> - <Row className="text-muted justify-content-center">{caption}</Row> - </Col> - </Row> - </ContentSection> -) - -export default ScreenshotSection diff --git a/opendc-web/opendc-web-ui/src/components/home/ScreenshotSection.sass b/opendc-web/opendc-web-ui/src/components/home/ScreenshotSection.sass deleted file mode 100644 index 6b1a6ec4..00000000 --- a/opendc-web/opendc-web-ui/src/components/home/ScreenshotSection.sass +++ /dev/null @@ -1,4 +0,0 @@ -.screenshot - padding-left: 0 - padding-right: 0 - margin-bottom: 5px diff --git a/opendc-web/opendc-web-ui/src/components/home/SimulationSection.js b/opendc-web/opendc-web-ui/src/components/home/SimulationSection.js deleted file mode 100644 index 8e98717a..00000000 --- a/opendc-web/opendc-web-ui/src/components/home/SimulationSection.js +++ /dev/null @@ -1,50 +0,0 @@ -import React from 'react' -import { Col, Row } from 'reactstrap' -import ContentSection from './ContentSection' - -const SimulationSection = () => { - return ( - <ContentSection name="project" title="Datecenter Simulation"> - <Row> - <Col xl="5" lg="5" md="5" sm="2" className="text-left my-auto order-1"> - <h3>Working with OpenDC:</h3> - <ul> - <li>Seamlessly switch between construction and simulation modes</li> - <li> - Choose one of several predefined workloads (Business Critical, Workflows, Machine Learning, - Serverless, etc.) - </li> - <li>Compare datacenter topologies using automated plots and visual summaries</li> - </ul> - </Col> - <Col xl="7" lg="7" md="7" sm="12"> - <img - src="/img/screenshot-simulation.png" - className="col-12 screenshot" - alt="Running an experiment in OpenDC" - /> - <Row className="text-muted justify-content-center">Running an experiment in OpenDC</Row> - </Col> - </Row> - <Row className="mt-5"> - <Col xl="5" lg="5" md="5" sm="2" className="text-left my-auto"> - <h3>OpenDC's Simulator:</h3> - <ul> - <li>Includes a detailed operational model of modern datacenters</li> - <li> - Support for emerging datacenter technologies around <em>cloud computing</em>,{' '} - <em>serverless computing</em>, <em>big data</em>, and <em>machine learning</em> - </li> - </ul> - </Col> - - <Col xl="7" lg="7" md="7" sm="12"> - <img src="/img/opendc-architecture.png" className="col-12 screenshot" alt="OpenDC's Architecture" /> - <Row className="text-muted justify-content-center">OpenDC's Architecture</Row> - </Col> - </Row> - </ContentSection> - ) -} - -export default SimulationSection diff --git a/opendc-web/opendc-web-ui/src/components/home/StakeholderSection.js b/opendc-web/opendc-web-ui/src/components/home/StakeholderSection.js deleted file mode 100644 index e5ed9683..00000000 --- a/opendc-web/opendc-web-ui/src/components/home/StakeholderSection.js +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react' -import ContentSection from './ContentSection' - -const Stakeholder = ({ name, title, subtitle }) => ( - <div className="col-xl-4 col-lg-4 col-md-4 col-sm-6 col-6"> - <img - src={'img/stakeholders/' + name + '.png'} - className="col-xl-3 col-lg-4 col-md-4 col-sm-4 col-4 img-fluid" - alt={title} - /> - <div className="text-center mt-2"> - <h4>{title}</h4> - <p>{subtitle}</p> - </div> - </div> -) - -const StakeholderSection = () => ( - <ContentSection name="stakeholders" title="Stakeholders"> - <div className="row justify-content-center"> - <Stakeholder name="Manager" title="Managers" subtitle="Seeing is deciding" /> - <Stakeholder name="Sales" title="Sales" subtitle="Demo concepts" /> - <Stakeholder name="Developer" title="DevOps" subtitle="Develop & tune" /> - <Stakeholder name="Researcher" title="Researchers" subtitle="Understand & design" /> - <Stakeholder name="Student" title="Students" subtitle="Grasp complex concepts" /> - </div> - </ContentSection> -) - -export default StakeholderSection diff --git a/opendc-web/opendc-web-ui/src/components/home/TeamSection.js b/opendc-web/opendc-web-ui/src/components/home/TeamSection.js deleted file mode 100644 index 1ee1cbf5..00000000 --- a/opendc-web/opendc-web-ui/src/components/home/TeamSection.js +++ /dev/null @@ -1,65 +0,0 @@ -import React from 'react' -import { Row, Col } from 'reactstrap' -import ContentSection from './ContentSection' - -const TeamLead = ({ photoId, name, description }) => ( - <Col xl="3" lg="3" md="4" sm="6" className="justify-content-center"> - <Col - tag="img" - src={'img/portraits/' + photoId + '.png'} - xl="10" - lg="10" - md="10" - sm="8" - col="5" - className="mb-2 mt-2" - alt={name} - /> - <Col> - <h4>{name}</h4> - <div className="team-member-description">{description}</div> - </Col> - </Col> -) - -const TeamMember = ({ photoId, name }) => ( - <Col xl="2" lg="2" md="3" sm="4" className="justify-content-center"> - <Col - tag="img" - src={'img/portraits/' + photoId + '.png'} - xl="10" - lg="10" - md="10" - sm="8" - col="5" - className="mb-2 mt-2" - alt={name} - /> - <Col> - <h5>{name}</h5> - </Col> - </Col> -) - -const TeamSection = () => ( - <ContentSection name="team" title="OpenDC Team"> - <Row className="justify-content-center"> - <TeamLead photoId="aiosup" name="Prof. dr. ir. Alexandru Iosup" description="Project Lead" /> - <TeamLead photoId="fmastenbroek" name="Fabian Mastenbroek" description="Technology Lead" /> - <TeamLead photoId="gandreadis" name="Georgios Andreadis" description="Former Technology Lead (2018-2020)" /> - <TeamLead photoId="vvanbeek" name="Vincent van Beek" description="Former Technology Lead (2017-2018)" /> - </Row> - <Row className="justify-content-center mt-5"> - <TeamMember photoId="loverweel" name="Leon Overweel" /> - <TeamMember photoId="lfdversluis" name="Laurens Versluis" /> - <TeamMember photoId="evaneyk" name="Erwin van Eyk" /> - <TeamMember photoId="sjounaid" name="Soufiane Jounaid" /> - <TeamMember photoId="wlai" name="Wenchen Lai" /> - <TeamMember photoId="hhe" name="Hongyu He" /> - <TeamMember photoId="jburley" name="Jacob Burley" /> - <TeamMember photoId="jbosch" name="Jaro Bosch" /> - </Row> - </ContentSection> -) - -export default TeamSection diff --git a/opendc-web/opendc-web-ui/src/components/home/TechnologiesSection.js b/opendc-web/opendc-web-ui/src/components/home/TechnologiesSection.js deleted file mode 100644 index c6013c71..00000000 --- a/opendc-web/opendc-web-ui/src/components/home/TechnologiesSection.js +++ /dev/null @@ -1,40 +0,0 @@ -import React from 'react' -import FontAwesome from 'react-fontawesome' -import ContentSection from './ContentSection' - -const TechnologiesSection = () => ( - <ContentSection name="technologies" title="Technologies"> - <ul className="list-group text-left"> - <li className="d-flex list-group-item justify-content-between align-items-center list-group-item-primary"> - <span style={{ minWidth: 100 }}> - <FontAwesome name="window-maximize" className="mr-2" /> - <strong className="">Browser</strong> - </span> - <span className="text-right">JavaScript, React, Redux, Konva</span> - </li> - <li className="d-flex list-group-item justify-content-between align-items-center list-group-item-warning"> - <span style={{ minWidth: 100 }}> - <FontAwesome name="television" className="mr-2" /> - <strong>Server</strong> - </span> - <span className="text-right">Python, Flask, FlaskSocketIO, OpenAPI</span> - </li> - <li className="d-flex list-group-item justify-content-between align-items-center list-group-item-success"> - <span style={{ minWidth: 100 }}> - <FontAwesome name="database" className="mr-2" /> - <strong>Database</strong> - </span> - <span className="text-right">MongoDB</span> - </li> - <li className="d-flex list-group-item justify-content-between align-items-center list-group-item-danger"> - <span style={{ minWidth: 100 }}> - <FontAwesome name="cogs" className="mr-2" /> - <strong>Simulator</strong> - </span> - <span className="text-right">Kotlin</span> - </li> - </ul> - </ContentSection> -) - -export default TechnologiesSection diff --git a/opendc-web/opendc-web-ui/src/components/modals/ConfirmationModal.js b/opendc-web/opendc-web-ui/src/components/modals/ConfirmationModal.js deleted file mode 100644 index 589047dc..00000000 --- a/opendc-web/opendc-web-ui/src/components/modals/ConfirmationModal.js +++ /dev/null @@ -1,37 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import Modal from './Modal' - -class ConfirmationModal extends React.Component { - static propTypes = { - title: PropTypes.string.isRequired, - message: PropTypes.string.isRequired, - show: PropTypes.bool.isRequired, - callback: PropTypes.func.isRequired, - } - - onConfirm() { - this.props.callback(true) - } - - onCancel() { - this.props.callback(false) - } - - render() { - return ( - <Modal - title={this.props.title} - show={this.props.show} - onSubmit={this.onConfirm.bind(this)} - onCancel={this.onCancel.bind(this)} - submitButtonType="danger" - submitButtonText="Confirm" - > - {this.props.message} - </Modal> - ) - } -} - -export default ConfirmationModal diff --git a/opendc-web/opendc-web-ui/src/components/modals/Modal.js b/opendc-web/opendc-web-ui/src/components/modals/Modal.js deleted file mode 100644 index 21b7f119..00000000 --- a/opendc-web/opendc-web-ui/src/components/modals/Modal.js +++ /dev/null @@ -1,53 +0,0 @@ -import React, { useState, useEffect } from 'react' -import PropTypes from 'prop-types' -import { Modal as RModal, ModalHeader, ModalBody, ModalFooter, Button } from 'reactstrap' - -function Modal({ children, title, show, onSubmit, onCancel, submitButtonType, submitButtonText }) { - const [modal, setModal] = useState(show) - - useEffect(() => setModal(show), [show]) - - const toggle = () => setModal(!modal) - const cancel = () => { - if (onCancel() !== false) { - toggle() - } - } - const submit = () => { - if (onSubmit() !== false) { - toggle() - } - } - - return ( - <RModal isOpen={modal} toggle={cancel}> - <ModalHeader toggle={cancel}>{title}</ModalHeader> - <ModalBody>{children}</ModalBody> - <ModalFooter> - <Button color="secondary" onClick={cancel}> - Close - </Button> - <Button color={submitButtonType} onClick={submit}> - {submitButtonText} - </Button> - </ModalFooter> - </RModal> - ) -} - -Modal.propTypes = { - title: PropTypes.string.isRequired, - show: PropTypes.bool.isRequired, - onSubmit: PropTypes.func.isRequired, - onCancel: PropTypes.func.isRequired, - submitButtonType: PropTypes.string, - submitButtonText: PropTypes.string, -} - -Modal.defaultProps = { - submitButtonType: 'primary', - submitButtonText: 'Save', - show: false, -} - -export default Modal diff --git a/opendc-web/opendc-web-ui/src/components/modals/TextInputModal.js b/opendc-web/opendc-web-ui/src/components/modals/TextInputModal.js deleted file mode 100644 index d0918c7e..00000000 --- a/opendc-web/opendc-web-ui/src/components/modals/TextInputModal.js +++ /dev/null @@ -1,54 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import Modal from './Modal' - -class TextInputModal extends React.Component { - static propTypes = { - title: PropTypes.string.isRequired, - label: PropTypes.string.isRequired, - show: PropTypes.bool.isRequired, - callback: PropTypes.func.isRequired, - initialValue: PropTypes.string, - } - - componentDidUpdate() { - if (this.props.initialValue && this.textInput) { - this.textInput.value = this.props.initialValue - } - } - - onSubmit() { - this.props.callback(this.textInput.value) - this.textInput.value = '' - } - - onCancel() { - this.props.callback(undefined) - this.textInput.value = '' - } - - render() { - return ( - <Modal - title={this.props.title} - show={this.props.show} - onSubmit={this.onSubmit.bind(this)} - onCancel={this.onCancel.bind(this)} - > - <form - onSubmit={(e) => { - e.preventDefault() - this.onSubmit() - }} - > - <div className="form-group"> - <label className="form-control-label">{this.props.label}</label> - <input type="text" className="form-control" ref={(textInput) => (this.textInput = textInput)} /> - </div> - </form> - </Modal> - ) - } -} - -export default TextInputModal diff --git a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewPortfolioModalComponent.js b/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewPortfolioModalComponent.js deleted file mode 100644 index 3c6b8724..00000000 --- a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewPortfolioModalComponent.js +++ /dev/null @@ -1,78 +0,0 @@ -import PropTypes from 'prop-types' -import React, { useRef } from 'react' -import { Form, FormGroup, Input, Label } from 'reactstrap' -import Modal from '../Modal' -import { AVAILABLE_METRICS, METRIC_NAMES } from '../../../util/available-metrics' - -const NewPortfolioModalComponent = ({ show, callback }) => { - const form = useRef(null) - const textInput = useRef(null) - const repeatsInput = useRef(null) - const metricCheckboxes = useRef({}) - - const onSubmit = () => { - if (form.current.reportValidity()) { - callback(textInput.current.value, { - enabledMetrics: AVAILABLE_METRICS.filter((metric) => metricCheckboxes.current[metric].checked), - repeatsPerScenario: parseInt(repeatsInput.current.value), - }) - - return true - } else { - return false - } - } - const onCancel = () => callback(undefined) - - return ( - <Modal title="New Portfolio" show={show} onSubmit={onSubmit} onCancel={onCancel}> - <Form - onSubmit={(e) => { - e.preventDefault() - this.onSubmit() - }} - innerRef={form} - > - <FormGroup> - <Label for="name">Name</Label> - <Input name="name" type="text" required innerRef={textInput} placeholder="My Portfolio" /> - </FormGroup> - <h4>Targets</h4> - <h5>Metrics</h5> - <FormGroup> - {AVAILABLE_METRICS.map((metric) => ( - <FormGroup check key={metric}> - <Label for={metric} check> - <Input - name={metric} - type="checkbox" - innerRef={(ref) => (metricCheckboxes.current[metric] = ref)} - /> - {METRIC_NAMES[metric]} - </Label> - </FormGroup> - ))} - </FormGroup> - <FormGroup> - <Label for="repeats">Repeats per scenario</Label> - <Input - name="repeats" - type="number" - required - innerRef={repeatsInput} - defaultValue="1" - min="1" - step="1" - /> - </FormGroup> - </Form> - </Modal> - ) -} - -NewPortfolioModalComponent.propTypes = { - show: PropTypes.bool.isRequired, - callback: PropTypes.func.isRequired, -} - -export default NewPortfolioModalComponent diff --git a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewScenarioModalComponent.js b/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewScenarioModalComponent.js deleted file mode 100644 index 01a5719c..00000000 --- a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewScenarioModalComponent.js +++ /dev/null @@ -1,144 +0,0 @@ -import PropTypes from 'prop-types' -import React, { useRef } from 'react' -import { Form, FormGroup, Input, Label } from 'reactstrap' -import Shapes from '../../../shapes' -import Modal from '../Modal' - -const NewScenarioModalComponent = ({ - show, - callback, - currentPortfolioId, - currentPortfolioScenarioIds, - traces, - topologies, - schedulers, -}) => { - const form = useRef(null) - const textInput = useRef(null) - const traceSelect = useRef(null) - const traceLoadInput = useRef(null) - const topologySelect = useRef(null) - const failuresCheckbox = useRef(null) - const performanceInterferenceCheckbox = useRef(null) - const schedulerSelect = useRef(null) - - const onSubmit = () => { - if (!form.current.reportValidity()) { - return false - } - callback( - textInput.current.value, - currentPortfolioId, - { - traceId: traceSelect.current.value, - loadSamplingFraction: parseFloat(traceLoadInput.current.value), - }, - { - topologyId: topologySelect.current.value, - }, - { - failuresEnabled: failuresCheckbox.current.checked, - performanceInterferenceEnabled: performanceInterferenceCheckbox.current.checked, - schedulerName: schedulerSelect.current.value, - } - ) - return true - } - const onCancel = () => { - callback(undefined) - } - - return ( - <Modal title="New Scenario" show={show} onSubmit={onSubmit} onCancel={onCancel}> - <Form - onSubmit={(e) => { - e.preventDefault() - onSubmit() - }} - innerRef={form} - > - <FormGroup> - <Label for="name">Name</Label> - <Input - name="name" - type="text" - required - disabled={currentPortfolioScenarioIds.length === 0} - defaultValue={currentPortfolioScenarioIds.length === 0 ? 'Base scenario' : ''} - innerRef={textInput} - /> - </FormGroup> - <h4>Trace</h4> - <FormGroup> - <Label for="trace">Trace</Label> - <Input name="trace" type="select" innerRef={traceSelect}> - {traces.map((trace) => ( - <option value={trace._id} key={trace._id}> - {trace.name} - </option> - ))} - </Input> - </FormGroup> - <FormGroup> - <Label for="trace-load">Load sampling fraction</Label> - <Input - name="trace-load" - type="number" - innerRef={traceLoadInput} - required - defaultValue="1" - min="0" - max="1" - step="0.1" - /> - </FormGroup> - <h4>Topology</h4> - <div className="form-group"> - <Label for="topology">Topology</Label> - <Input name="topology" type="select" innerRef={topologySelect}> - {topologies.map((topology) => ( - <option value={topology._id} key={topology._id}> - {topology.name} - </option> - ))} - </Input> - </div> - <h4>Operational Phenomena</h4> - <FormGroup check> - <Label check for="failures"> - <Input type="checkbox" name="failures" innerRef={failuresCheckbox} />{' '} - <span className="ml-2">Enable failures</span> - </Label> - </FormGroup> - <FormGroup check> - <Label check for="perf-interference"> - <Input type="checkbox" name="perf-interference" innerRef={performanceInterferenceCheckbox} />{' '} - <span className="ml-2">Enable performance interference</span> - </Label> - </FormGroup> - <FormGroup> - <Label for="scheduler">Scheduler</Label> - <Input name="scheduler" type="select" innerRef={schedulerSelect}> - {schedulers.map((scheduler) => ( - <option value={scheduler.name} key={scheduler.name}> - {scheduler.name} - </option> - ))} - </Input> - </FormGroup> - </Form> - </Modal> - ) -} - -NewScenarioModalComponent.propTypes = { - show: PropTypes.bool.isRequired, - currentPortfolioId: PropTypes.string.isRequired, - currentPortfolioScenarioIds: PropTypes.arrayOf(PropTypes.string), - traces: PropTypes.arrayOf(Shapes.Trace), - topologies: PropTypes.arrayOf(Shapes.Topology), - schedulers: PropTypes.arrayOf(Shapes.Scheduler), - callback: PropTypes.func.isRequired, -} - -export default NewScenarioModalComponent diff --git a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewTopologyModalComponent.js b/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewTopologyModalComponent.js deleted file mode 100644 index 9fee8831..00000000 --- a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewTopologyModalComponent.js +++ /dev/null @@ -1,71 +0,0 @@ -import PropTypes from 'prop-types' -import { Form, FormGroup, Input, Label } from 'reactstrap' -import React, { useRef } from 'react' -import Shapes from '../../../shapes' -import Modal from '../Modal' - -const NewTopologyModalComponent = ({ show, onCreateTopology, onDuplicateTopology, onCancel, topologies }) => { - const form = useRef(null) - const textInput = useRef(null) - const originTopology = useRef(null) - - const onCreate = () => { - onCreateTopology(textInput.current.value) - } - - const onDuplicate = () => { - onDuplicateTopology(textInput.current.value, originTopology.current.value) - } - - const onSubmit = () => { - if (!form.current.reportValidity()) { - return false - } else if (originTopology.current.selectedIndex === 0) { - onCreate() - } else { - onDuplicate() - } - - return true - } - - return ( - <Modal title="New Topology" show={show} onSubmit={onSubmit} onCancel={onCancel}> - <Form - onSubmit={(e) => { - e.preventDefault() - onSubmit() - }} - innerRef={form} - > - <FormGroup> - <Label for="name">Name</Label> - <Input name="name" type="text" required innerRef={textInput} /> - </FormGroup> - <FormGroup> - <Label for="origin">Topology to duplicate</Label> - <Input name="origin" type="select" innerRef={originTopology}> - <option value={-1} key={-1}> - None - start from scratch - </option> - {topologies.map((topology) => ( - <option value={topology._id} key={topology._id}> - {topology.name} - </option> - ))} - </Input> - </FormGroup> - </Form> - </Modal> - ) -} - -NewTopologyModalComponent.propTypes = { - show: PropTypes.bool.isRequired, - topologies: PropTypes.arrayOf(Shapes.Topology), - onCreateTopology: PropTypes.func.isRequired, - onDuplicateTopology: PropTypes.func.isRequired, - onCancel: PropTypes.func.isRequired, -} - -export default NewTopologyModalComponent diff --git a/opendc-web/opendc-web-ui/src/components/navigation/AppNavbarComponent.js b/opendc-web/opendc-web-ui/src/components/navigation/AppNavbarComponent.js deleted file mode 100644 index c5de3d0b..00000000 --- a/opendc-web/opendc-web-ui/src/components/navigation/AppNavbarComponent.js +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react' -import FontAwesome from 'react-fontawesome' -import { Link } from 'react-router-dom' -import { NavLink } from 'reactstrap' -import Navbar, { NavItem } from './Navbar' -import './Navbar.sass' - -const AppNavbarComponent = ({ project, fullWidth }) => ( - <Navbar fullWidth={fullWidth}> - <NavItem route="/projects"> - <NavLink tag={Link} title="My Projects" to="/projects"> - <FontAwesome name="list" className="mr-2" /> - My Projects - </NavLink> - </NavItem> - {project ? ( - <NavItem> - <NavLink tag={Link} title="Current Project" to={`/projects/${project._id}`}> - <span>{project.name}</span> - </NavLink> - </NavItem> - ) : undefined} - </Navbar> -) - -export default AppNavbarComponent diff --git a/opendc-web/opendc-web-ui/src/components/navigation/HomeNavbar.js b/opendc-web/opendc-web-ui/src/components/navigation/HomeNavbar.js deleted file mode 100644 index 08d222ea..00000000 --- a/opendc-web/opendc-web-ui/src/components/navigation/HomeNavbar.js +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react' -import { NavItem, NavLink } from 'reactstrap' -import Navbar from './Navbar' -import './Navbar.sass' - -const ScrollNavItem = ({ id, name }) => ( - <NavItem> - <NavLink href={id}>{name}</NavLink> - </NavItem> -) - -const HomeNavbar = () => ( - <Navbar fullWidth={false}> - <ScrollNavItem id="#stakeholders" name="Stakeholders" /> - <ScrollNavItem id="#modeling" name="Modeling" /> - <ScrollNavItem id="#project" name="Project" /> - <ScrollNavItem id="#technologies" name="Technologies" /> - <ScrollNavItem id="#team" name="Team" /> - <ScrollNavItem id="#contact" name="Contact" /> - </Navbar> -) - -export default HomeNavbar diff --git a/opendc-web/opendc-web-ui/src/components/navigation/LogoutButton.js b/opendc-web/opendc-web-ui/src/components/navigation/LogoutButton.js deleted file mode 100644 index 78b02b44..00000000 --- a/opendc-web/opendc-web-ui/src/components/navigation/LogoutButton.js +++ /dev/null @@ -1,17 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import FontAwesome from 'react-fontawesome' -import { Link } from 'react-router-dom' -import { NavLink } from 'reactstrap' - -const LogoutButton = ({ onLogout }) => ( - <NavLink tag={Link} className="logout" title="Sign out" to="#" onClick={onLogout}> - <FontAwesome name="power-off" size="lg" /> - </NavLink> -) - -LogoutButton.propTypes = { - onLogout: PropTypes.func.isRequired, -} - -export default LogoutButton diff --git a/opendc-web/opendc-web-ui/src/components/navigation/Navbar.js b/opendc-web/opendc-web-ui/src/components/navigation/Navbar.js deleted file mode 100644 index 55f98900..00000000 --- a/opendc-web/opendc-web-ui/src/components/navigation/Navbar.js +++ /dev/null @@ -1,92 +0,0 @@ -import React, { useState } from 'react' -import { Link, useLocation } from 'react-router-dom' -import { - Navbar as RNavbar, - NavItem as RNavItem, - NavLink, - NavbarBrand, - NavbarToggler, - Collapse, - Nav, - Container, -} from 'reactstrap' -import { userIsLoggedIn } from '../../auth/index' -import Login from '../../containers/auth/Login' -import Logout from '../../containers/auth/Logout' -import ProfileName from '../../containers/auth/ProfileName' -import './Navbar.sass' - -export const NAVBAR_HEIGHT = 60 - -const GitHubLink = () => ( - <a - href="https://github.com/atlarge-research/opendc" - className="ml-2 mr-3 text-dark" - style={{ position: 'relative', top: 7 }} - > - <span className="fa fa-github fa-2x" /> - </a> -) - -export const NavItem = ({ route, children }) => { - const location = useLocation() - return <RNavItem active={location.pathname === route}>{children}</RNavItem> -} - -export const LoggedInSection = () => { - const location = useLocation() - return ( - <Nav navbar className="auth-links"> - {userIsLoggedIn() ? ( - [ - location.pathname === '/' ? ( - <NavItem route="/projects" key="projects"> - <NavLink tag={Link} title="My Projects" to="/projects"> - My Projects - </NavLink> - </NavItem> - ) : ( - <NavItem route="/profile" key="profile"> - <NavLink tag={Link} title="My Profile" to="/profile"> - <ProfileName /> - </NavLink> - </NavItem> - ), - <NavItem route="logout" key="logout"> - <Logout /> - </NavItem>, - ] - ) : ( - <NavItem route="login"> - <GitHubLink /> - <Login visible={true} /> - </NavItem> - )} - </Nav> - ) -} - -const Navbar = ({ fullWidth, children }) => { - const [isOpen, setIsOpen] = useState(false) - const toggle = () => setIsOpen(!isOpen) - - return ( - <RNavbar fixed="top" color="light" light expand="lg" id="navbar"> - <Container fluid={fullWidth}> - <NavbarToggler onClick={toggle} /> - <NavbarBrand tag={Link} to="/" title="OpenDC" className="opendc-brand"> - <img src="/img/logo.png" alt="OpenDC" /> - </NavbarBrand> - - <Collapse isOpen={isOpen} navbar> - <Nav className="mr-auto" navbar> - {children} - </Nav> - <LoggedInSection /> - </Collapse> - </Container> - </RNavbar> - ) -} - -export default Navbar diff --git a/opendc-web/opendc-web-ui/src/components/navigation/Navbar.sass b/opendc-web/opendc-web-ui/src/components/navigation/Navbar.sass deleted file mode 100644 index c9d2aad2..00000000 --- a/opendc-web/opendc-web-ui/src/components/navigation/Navbar.sass +++ /dev/null @@ -1,30 +0,0 @@ -@import ../../style-globals/_mixins.sass -@import ../../style-globals/_variables.sass - -.navbar - border-top: $blue 3px solid - border-bottom: $gray-semi-dark 1px solid - color: $gray-very-dark - background: #fafafb - -.opendc-brand - display: inline-block - color: $gray-very-dark - - +transition(background, $transition-length) - - img - position: relative - bottom: 3px - display: inline-block - width: 30px - -.login - height: 40px - background: $blue - border: none - padding-top: 10px - +clickable - - &:hover - background: $blue-dark diff --git a/opendc-web/opendc-web-ui/src/components/not-found/BlinkingCursor.js b/opendc-web/opendc-web-ui/src/components/not-found/BlinkingCursor.js deleted file mode 100644 index dbdba212..00000000 --- a/opendc-web/opendc-web-ui/src/components/not-found/BlinkingCursor.js +++ /dev/null @@ -1,6 +0,0 @@ -import React from 'react' -import './BlinkingCursor.sass' - -const BlinkingCursor = () => <span className="blinking-cursor">_</span> - -export default BlinkingCursor diff --git a/opendc-web/opendc-web-ui/src/components/not-found/BlinkingCursor.sass b/opendc-web/opendc-web-ui/src/components/not-found/BlinkingCursor.sass deleted file mode 100644 index ad91df85..00000000 --- a/opendc-web/opendc-web-ui/src/components/not-found/BlinkingCursor.sass +++ /dev/null @@ -1,35 +0,0 @@ -.blinking-cursor - -webkit-animation: 1s blink step-end infinite - -moz-animation: 1s blink step-end infinite - -o-animation: 1s blink step-end infinite - animation: 1s blink step-end infinite - -@keyframes blink - from, to - color: #eeeeee - 50% - color: #333333 - -@-moz-keyframes blink - from, to - color: #eeeeee - 50% - color: #333333 - -@-webkit-keyframes blink - from, to - color: #eeeeee - 50% - color: #333333 - -@-ms-keyframes blink - from, to - color: #eeeeee - 50% - color: #333333 - -@-o-keyframes blink - from, to - color: #eeeeee - 50% - color: #333333 diff --git a/opendc-web/opendc-web-ui/src/components/not-found/CodeBlock.js b/opendc-web/opendc-web-ui/src/components/not-found/CodeBlock.js deleted file mode 100644 index bcc522c9..00000000 --- a/opendc-web/opendc-web-ui/src/components/not-found/CodeBlock.js +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react' -import './CodeBlock.sass' - -const CodeBlock = () => { - const textBlock = - ' oo oooo oo <br/>' + - ' oo oo oo oo <br/>' + - ' oo oo oo oo <br/>' + - ' oooooo oo oo oooooo <br/>' + - ' oo oo oo oo <br/>' + - ' oo oooo oo <br/>' - const charList = textBlock.split('') - - // Binary representation of the string "OpenDC!" ;) - const binaryString = '01001111011100000110010101101110010001000100001100100001' - - let binaryIndex = 0 - for (let i = 0; i < charList.length; i++) { - if (charList[i] === 'o') { - charList[i] = binaryString[binaryIndex] - binaryIndex++ - } - } - - return <div className="code-block" dangerouslySetInnerHTML={{ __html: textBlock }} /> -} - -export default CodeBlock diff --git a/opendc-web/opendc-web-ui/src/components/not-found/CodeBlock.sass b/opendc-web/opendc-web-ui/src/components/not-found/CodeBlock.sass deleted file mode 100644 index e452f917..00000000 --- a/opendc-web/opendc-web-ui/src/components/not-found/CodeBlock.sass +++ /dev/null @@ -1,3 +0,0 @@ -.code-block - white-space: pre-wrap - margin-top: 60px diff --git a/opendc-web/opendc-web-ui/src/components/not-found/TerminalWindow.js b/opendc-web/opendc-web-ui/src/components/not-found/TerminalWindow.js deleted file mode 100644 index a25e558a..00000000 --- a/opendc-web/opendc-web-ui/src/components/not-found/TerminalWindow.js +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react' -import { Link } from 'react-router-dom' -import BlinkingCursor from './BlinkingCursor' -import CodeBlock from './CodeBlock' -import './TerminalWindow.sass' - -const TerminalWindow = () => ( - <div className="terminal-window"> - <div className="terminal-header">Terminal -- bash</div> - <div className="terminal-body"> - <div className="segfault"> - $ status - <br /> - opendc[4264]: segfault at 0000051497be459d1 err 12 in libopendc.9.0.4 - <br /> - opendc[4269]: segfault at 000004234855fc2db err 3 in libopendc.9.0.4 - <br /> - opendc[4270]: STDERR Page does not exist - <br /> - </div> - <CodeBlock /> - <div className="sub-title"> - Got lost? - <BlinkingCursor /> - </div> - <Link to="/" className="home-btn"> - <span className="fa fa-home" /> GET ME BACK TO OPENDC - </Link> - </div> - </div> -) - -export default TerminalWindow diff --git a/opendc-web/opendc-web-ui/src/components/not-found/TerminalWindow.sass b/opendc-web/opendc-web-ui/src/components/not-found/TerminalWindow.sass deleted file mode 100644 index 7f05335a..00000000 --- a/opendc-web/opendc-web-ui/src/components/not-found/TerminalWindow.sass +++ /dev/null @@ -1,70 +0,0 @@ -.terminal-window - width: 600px - height: 400px - display: block - - position: absolute - top: 0 - bottom: 0 - left: 0 - right: 0 - - margin: auto - - -webkit-user-select: none - -moz-user-select: none - -ms-user-select: none - user-select: none - cursor: default - - overflow: hidden - - box-shadow: 5px 5px 20px #444444 - -.terminal-header - font-family: monospace - background: #cccccc - color: #444444 - height: 30px - line-height: 30px - padding-left: 10px - - border-top-left-radius: 7px - border-top-right-radius: 7px - -.terminal-body - font-family: monospace - text-align: center - background-color: #333333 - color: #eeeeee - padding: 10px - - height: 100% - -.segfault - text-align: left - -.sub-title - margin-top: 20px - -.home-btn - margin-top: 10px - padding: 5px - display: inline-block - border: 1px solid #eeeeee - color: #eeeeee - text-decoration: none - cursor: pointer - - -webkit-transition: all 200ms - -moz-transition: all 200ms - -o-transition: all 200ms - transition: all 200ms - -.home-btn:hover - background: #eeeeee - color: #333333 - -.home-btn:active - background: #333333 - color: #eeeeee diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/NewScenario.js b/opendc-web/opendc-web-ui/src/components/portfolios/NewScenario.js new file mode 100644 index 00000000..856282a7 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/portfolios/NewScenario.js @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import { PlusIcon } from '@patternfly/react-icons' +import { Button } from '@patternfly/react-core' +import { useState } from 'react' +import { useMutation } from 'react-query' +import NewScenarioModal from './NewScenarioModal' + +function NewScenario({ portfolioId }) { + const [isVisible, setVisible] = useState(false) + const { mutate: addScenario } = useMutation('addScenario') + + const onSubmit = (name, portfolioId, trace, topology, operational) => { + addScenario({ + portfolioId, + name, + trace, + topology, + operational, + }) + setVisible(false) + } + + return ( + <> + <Button icon={<PlusIcon />} isSmall onClick={() => setVisible(true)}> + New Scenario + </Button> + <NewScenarioModal + portfolioId={portfolioId} + isOpen={isVisible} + onSubmit={onSubmit} + onCancel={() => setVisible(false)} + /> + </> + ) +} + +NewScenario.propTypes = { + portfolioId: PropTypes.string, +} + +export default NewScenario diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/NewScenarioModal.js b/opendc-web/opendc-web-ui/src/components/portfolios/NewScenarioModal.js new file mode 100644 index 00000000..7f620c8c --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/portfolios/NewScenarioModal.js @@ -0,0 +1,159 @@ +import PropTypes from 'prop-types' +import React, { useRef, useState } from 'react' +import Modal from '../util/modals/Modal' +import { + Checkbox, + Form, + FormGroup, + FormSection, + FormSelect, + FormSelectOption, + NumberInput, + TextInput, +} from '@patternfly/react-core' +import { useSchedulers, useTraces } from '../../data/experiments' +import { useProjectTopologies } from '../../data/topology' +import { usePortfolio } from '../../data/project' + +const NewScenarioModal = ({ portfolioId, isOpen, onSubmit: onSubmitUpstream, onCancel: onCancelUpstream }) => { + const { data: portfolio } = usePortfolio(portfolioId) + const { data: topologies = [] } = useProjectTopologies(portfolio?.projectId) + const { data: traces = [] } = useTraces() + const { data: schedulers = [] } = useSchedulers() + + // eslint-disable-next-line no-unused-vars + const [isSubmitted, setSubmitted] = useState(false) + const [traceLoad, setTraceLoad] = useState(100) + const [trace, setTrace] = useState(undefined) + const [topology, setTopology] = useState(undefined) + const [scheduler, setScheduler] = useState(undefined) + const [failuresEnabled, setFailuresEnabled] = useState(false) + const [opPhenEnabled, setOpPhenEnabled] = useState(false) + const nameInput = useRef(null) + + const resetState = () => { + setSubmitted(false) + setTraceLoad(100) + setTrace(undefined) + setTopology(undefined) + setScheduler(undefined) + setFailuresEnabled(false) + setOpPhenEnabled(false) + nameInput.current.value = '' + } + + const onSubmit = (event) => { + setSubmitted(true) + + if (event) { + event.preventDefault() + } + + const name = nameInput.current.value + + onSubmitUpstream( + name, + portfolio._id, + { + traceId: trace || traces[0]._id, + loadSamplingFraction: traceLoad / 100, + }, + { + topologyId: topology || topologies[0]._id, + }, + { + failuresEnabled, + performanceInterferenceEnabled: opPhenEnabled, + schedulerName: scheduler || schedulers[0].name, + } + ) + + resetState() + return true + } + const onCancel = () => { + onCancelUpstream() + resetState() + } + + return ( + <Modal title="New Scenario" isOpen={isOpen} onSubmit={onSubmit} onCancel={onCancel}> + <Form onSubmit={onSubmit}> + <FormGroup label="Name" fieldId="name" isRequired> + <TextInput + id="name" + name="name" + type="text" + isDisabled={portfolio?.scenarioIds?.length === 0} + defaultValue={portfolio?.scenarioIds?.length === 0 ? 'Base scenario' : ''} + ref={nameInput} + /> + </FormGroup> + <FormSection title="Workload"> + <FormGroup label="Trace" fieldId="trace" isRequired> + <FormSelect id="trace" name="trace" value={trace} onChange={setTrace}> + {traces.map((trace) => ( + <FormSelectOption value={trace._id} key={trace._id} label={trace.name} /> + ))} + </FormSelect> + </FormGroup> + <FormGroup label="Load Sampling Fraction" fieldId="trace-load" isRequired> + <NumberInput + name="trace-load" + type="number" + min={0} + max={100} + value={traceLoad} + onMinus={() => setTraceLoad((load) => load - 1)} + onPlus={() => setTraceLoad((load) => load + 1)} + onChange={(e) => setTraceLoad(Number(e.target.value))} + unit="%" + /> + </FormGroup> + </FormSection> + <FormSection title="Topology"> + <FormGroup label="Topology" fieldId="topology" isRequired> + <FormSelect id="topology" name="topology" value={topology} onChange={setTopology}> + {topologies.map((topology) => ( + <FormSelectOption value={topology._id} key={topology._id} label={topology.name} /> + ))} + </FormSelect> + </FormGroup> + + <FormGroup label="Scheduler" fieldId="scheduler" isRequired> + <FormSelect id="scheduler" name="scheduler" value={scheduler} onChange={setScheduler}> + {schedulers.map((scheduler) => ( + <FormSelectOption value={scheduler.name} key={scheduler.name} label={scheduler.name} /> + ))} + </FormSelect> + </FormGroup> + </FormSection> + <FormSection title="Operational Phenomena"> + <Checkbox + label="Failures" + id="failures" + name="failures" + isChecked={failuresEnabled} + onChange={() => setFailuresEnabled((e) => !e)} + /> + <Checkbox + label="Performance Interference" + id="perf-interference" + name="perf-interference" + isChecked={opPhenEnabled} + onChange={() => setOpPhenEnabled((e) => !e)} + /> + </FormSection> + </Form> + </Modal> + ) +} + +NewScenarioModal.propTypes = { + portfolioId: PropTypes.string, + isOpen: PropTypes.bool.isRequired, + onSubmit: PropTypes.func.isRequired, + onCancel: PropTypes.func.isRequired, +} + +export default NewScenarioModal diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioOverview.js b/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioOverview.js new file mode 100644 index 00000000..580b0a29 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioOverview.js @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import { + Card, + CardActions, + CardBody, + CardHeader, + CardTitle, + Chip, + ChipGroup, + DescriptionList, + DescriptionListDescription, + DescriptionListGroup, + DescriptionListTerm, + Grid, + GridItem, + Skeleton, +} from '@patternfly/react-core' +import React from 'react' +import { usePortfolio } from '../../data/project' +import { METRIC_NAMES } from '../../util/available-metrics' +import NewScenario from './NewScenario' +import ScenarioTable from './ScenarioTable' + +function PortfolioOverview({ portfolioId }) { + const { data: portfolio } = usePortfolio(portfolioId) + + return ( + <Grid hasGutter> + <GridItem md={2}> + <Card> + <CardTitle>Details</CardTitle> + <CardBody> + <DescriptionList> + <DescriptionListGroup> + <DescriptionListTerm>Name</DescriptionListTerm> + <DescriptionListDescription> + {portfolio?.name ?? <Skeleton screenreaderText="Loading portfolio" />} + </DescriptionListDescription> + </DescriptionListGroup> + <DescriptionListGroup> + <DescriptionListTerm>Scenarios</DescriptionListTerm> + <DescriptionListDescription> + {portfolio?.scenarioIds.length ?? <Skeleton screenreaderText="Loading portfolio" />} + </DescriptionListDescription> + </DescriptionListGroup> + <DescriptionListGroup> + <DescriptionListTerm>Metrics</DescriptionListTerm> + <DescriptionListDescription> + {portfolio?.targets?.enabledMetrics ? ( + portfolio.targets.enabledMetrics.length > 0 ? ( + <ChipGroup> + {portfolio.targets.enabledMetrics.map((metric) => ( + <Chip isReadOnly key={metric}> + {METRIC_NAMES[metric]} + </Chip> + ))} + </ChipGroup> + ) : ( + 'No metrics enabled' + ) + ) : ( + <Skeleton screenreaderText="Loading portfolio" /> + )} + </DescriptionListDescription> + </DescriptionListGroup> + <DescriptionListGroup> + <DescriptionListTerm>Repeats per Scenario</DescriptionListTerm> + <DescriptionListDescription> + {portfolio?.targets?.repeatsPerScenario ?? ( + <Skeleton screenreaderText="Loading portfolio" /> + )} + </DescriptionListDescription> + </DescriptionListGroup> + </DescriptionList> + </CardBody> + </Card> + </GridItem> + <GridItem md={6}> + <Card> + <CardHeader> + <CardActions> + <NewScenario portfolioId={portfolioId} /> + </CardActions> + <CardTitle>Scenarios</CardTitle> + </CardHeader> + <CardBody> + <ScenarioTable portfolioId={portfolioId} /> + </CardBody> + </Card> + </GridItem> + </Grid> + ) +} + +PortfolioOverview.propTypes = { + portfolioId: PropTypes.string, +} + +export default PortfolioOverview diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResultInfo.js b/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResultInfo.js new file mode 100644 index 00000000..dbfa928f --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResultInfo.js @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import { Tooltip } from '@patternfly/react-core' +import { OutlinedQuestionCircleIcon } from '@patternfly/react-icons' +import { METRIC_DESCRIPTIONS } from '../../util/available-metrics' + +function PortfolioResultInfo({ metric }) { + return ( + <Tooltip position="top" content={<div>{METRIC_DESCRIPTIONS[metric]}</div>}> + <OutlinedQuestionCircleIcon title="Metric information" /> + </Tooltip> + ) +} + +PortfolioResultInfo.propTypes = { + metric: PropTypes.string.isRequired, +} + +export default PortfolioResultInfo diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResults.js b/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResults.js new file mode 100644 index 00000000..00023d9e --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResults.js @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React from 'react' +import PropTypes from 'prop-types' +import { Bar, CartesianGrid, ComposedChart, ErrorBar, ResponsiveContainer, Scatter, XAxis, YAxis } from 'recharts' +import { AVAILABLE_METRICS, METRIC_NAMES, METRIC_UNITS } from '../../util/available-metrics' +import { mean, std } from 'mathjs' +import approx from 'approximate-number' +import { + Bullseye, + Card, + CardActions, + CardBody, + CardHeader, + CardTitle, + EmptyState, + EmptyStateBody, + EmptyStateIcon, + Grid, + GridItem, + Spinner, + Title, +} from '@patternfly/react-core' +import { ErrorCircleOIcon, CubesIcon } from '@patternfly/react-icons' +import { usePortfolioScenarios } from '../../data/project' +import PortfolioResultInfo from './PortfolioResultInfo' +import NewScenario from './NewScenario' + +const PortfolioResults = ({ portfolioId }) => { + const { status, data: scenarios = [] } = usePortfolioScenarios(portfolioId) + + if (status === 'loading') { + return ( + <Bullseye> + <EmptyState> + <EmptyStateIcon variant="container" component={Spinner} /> + <Title size="lg" headingLevel="h4"> + Loading Results + </Title> + </EmptyState> + </Bullseye> + ) + } else if (status === 'error') { + return ( + <Bullseye> + <EmptyState> + <EmptyStateIcon variant="container" component={ErrorCircleOIcon} /> + <Title size="lg" headingLevel="h4"> + Unable to connect + </Title> + <EmptyStateBody> + There was an error retrieving data. Check your connection and try again. + </EmptyStateBody> + </EmptyState> + </Bullseye> + ) + } else if (scenarios.length === 0) { + return ( + <Bullseye> + <EmptyState> + <EmptyStateIcon variant="container" component={CubesIcon} /> + <Title size="lg" headingLevel="h4"> + No results + </Title> + <EmptyStateBody> + No results are currently available for this portfolio. Run a scenario to obtain simulation + results. + </EmptyStateBody> + <NewScenario portfolioId={portfolioId} /> + </EmptyState> + </Bullseye> + ) + } + + const dataPerMetric = {} + + AVAILABLE_METRICS.forEach((metric) => { + dataPerMetric[metric] = scenarios + .filter((scenario) => scenario.results) + .map((scenario) => ({ + name: scenario.name, + value: mean(scenario.results[metric]), + errorX: std(scenario.results[metric]), + })) + }) + + return ( + <Grid hasGutter> + {AVAILABLE_METRICS.map((metric) => ( + <GridItem xl={6} lg={12} key={metric}> + <Card> + <CardHeader> + <CardActions> + <PortfolioResultInfo metric={metric} /> + </CardActions> + <CardTitle>{METRIC_NAMES[metric]}</CardTitle> + </CardHeader> + <CardBody> + <ResponsiveContainer aspect={16 / 9} width="100%"> + <ComposedChart + data={dataPerMetric[metric]} + margin={{ left: 35, bottom: 15 }} + layout="vertical" + > + <CartesianGrid strokeDasharray="3 3" /> + <XAxis + tickFormatter={(tick) => approx(tick)} + label={{ value: METRIC_UNITS[metric], position: 'bottom', offset: 0 }} + type="number" + /> + <YAxis dataKey="name" type="category" /> + <Bar dataKey="value" fill="#3399FF" isAnimationActive={false} /> + <Scatter dataKey="value" opacity={0} isAnimationActive={false}> + <ErrorBar + dataKey="errorX" + width={10} + strokeWidth={3} + stroke="#FF6600" + direction="x" + /> + </Scatter> + </ComposedChart> + </ResponsiveContainer> + </CardBody> + </Card> + </GridItem> + ))} + </Grid> + ) +} + +PortfolioResults.propTypes = { + portfolioId: PropTypes.string, +} + +export default PortfolioResults diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioState.js b/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioState.js new file mode 100644 index 00000000..66691580 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioState.js @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import { ClockIcon, CheckCircleIcon, ErrorCircleOIcon } from '@patternfly/react-icons' + +function ScenarioState({ state }) { + switch (state) { + case 'CLAIMED': + case 'QUEUED': + return ( + <span> + <ClockIcon color="blue" /> Queued + </span> + ) + case 'RUNNING': + return ( + <span> + <ClockIcon color="green" /> Running + </span> + ) + case 'FINISHED': + return ( + <span> + <CheckCircleIcon color="green" /> Finished + </span> + ) + case 'FAILED': + return ( + <span> + <ErrorCircleOIcon color="red" /> Failed + </span> + ) + } + + return 'Unknown' +} + +ScenarioState.propTypes = { + state: PropTypes.oneOf(['QUEUED', 'CLAIMED', 'RUNNING', 'FINISHED', 'FAILED']), +} + +export default ScenarioState diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioTable.js b/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioTable.js new file mode 100644 index 00000000..9966e3ba --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioTable.js @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import Link from 'next/link' +import { Table, TableBody, TableHeader } from '@patternfly/react-table' +import React from 'react' +import TableEmptyState from '../util/TableEmptyState' +import ScenarioState from './ScenarioState' +import { usePortfolio, usePortfolioScenarios } from '../../data/project' +import { useProjectTopologies } from '../../data/topology' +import { useMutation } from 'react-query' + +const ScenarioTable = ({ portfolioId }) => { + const { data: portfolio } = usePortfolio(portfolioId) + const { status, data: scenarios = [] } = usePortfolioScenarios(portfolioId) + const { data: topologies } = useProjectTopologies(portfolio?.projectId, { + select: (topologies) => new Map(topologies.map((topology) => [topology._id, topology])), + }) + + const { mutate: deleteScenario } = useMutation('deleteScenario') + + const columns = ['Name', 'Topology', 'Trace', 'State'] + const rows = + scenarios.length > 0 + ? scenarios.map((scenario) => { + const topology = topologies?.get(scenario.topology.topologyId) + + return [ + scenario.name, + { + title: topology ? ( + <Link href={`/projects/${topology.projectId}/topologies/${topology._id}`}> + <a>{topology.name}</a> + </Link> + ) : ( + 'Unknown Topology' + ), + }, + scenario.trace.traceId, + { title: <ScenarioState state={scenario.simulation.state} /> }, + ] + }) + : [ + { + heightAuto: true, + cells: [ + { + props: { colSpan: 4 }, + title: ( + <TableEmptyState + status={status} + loadingTitle="Loading Scenarios" + emptyTitle="No scenarios" + emptyText="You have not created any scenario for this portfolio yet. Click the New Scenario button to create one." + /> + ), + }, + ], + }, + ] + + const actionResolver = (_, { rowIndex }) => [ + { + title: 'Delete Scenario', + onClick: (_, rowId) => deleteScenario(scenarios[rowId]._id), + isDisabled: rowIndex === 0, + }, + ] + + return ( + <Table + aria-label="Scenario List" + variant="compact" + cells={columns} + rows={rows} + actionResolver={scenarios.length > 0 ? actionResolver : undefined} + > + <TableHeader /> + <TableBody /> + </Table> + ) +} + +ScenarioTable.propTypes = { + portfolioId: PropTypes.string, +} + +export default ScenarioTable diff --git a/opendc-web/opendc-web-ui/src/components/projects/FilterButton.js b/opendc-web/opendc-web-ui/src/components/projects/FilterButton.js deleted file mode 100644 index 664f9b46..00000000 --- a/opendc-web/opendc-web-ui/src/components/projects/FilterButton.js +++ /dev/null @@ -1,24 +0,0 @@ -import classNames from 'classnames' -import PropTypes from 'prop-types' -import React from 'react' - -const FilterButton = ({ active, children, onClick }) => ( - <button - className={classNames('btn btn-secondary', { active: active })} - onClick={() => { - if (!active) { - onClick() - } - }} - > - {children} - </button> -) - -FilterButton.propTypes = { - active: PropTypes.bool.isRequired, - children: PropTypes.node.isRequired, - onClick: PropTypes.func.isRequired, -} - -export default FilterButton diff --git a/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.js b/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.js index 2b9795d0..285217e9 100644 --- a/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.js +++ b/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.js @@ -1,13 +1,26 @@ import React from 'react' -import FilterLink from '../../containers/projects/FilterLink' -import './FilterPanel.sass' - -const FilterPanel = () => ( - <div className="btn-group filter-panel mb-2"> - <FilterLink filter="SHOW_ALL">All Projects</FilterLink> - <FilterLink filter="SHOW_OWN">My Projects</FilterLink> - <FilterLink filter="SHOW_SHARED">Shared with me</FilterLink> - </div> +import PropTypes from 'prop-types' +import { ToggleGroup, ToggleGroupItem } from '@patternfly/react-core' +import { filterPanel } from './FilterPanel.module.scss' + +export const FILTERS = { SHOW_ALL: 'All Projects', SHOW_OWN: 'My Projects', SHOW_SHARED: 'Shared with me' } + +const FilterPanel = ({ onSelect, activeFilter = 'SHOW_ALL' }) => ( + <ToggleGroup className={`${filterPanel} mb-2`}> + {Object.keys(FILTERS).map((filter) => ( + <ToggleGroupItem + key={filter} + onChange={() => activeFilter === filter || onSelect(filter)} + isSelected={activeFilter === filter} + text={FILTERS[filter]} + /> + ))} + </ToggleGroup> ) +FilterPanel.propTypes = { + onSelect: PropTypes.func.isRequired, + activeFilter: PropTypes.string, +} + export default FilterPanel diff --git a/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.module.scss b/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.module.scss new file mode 100644 index 00000000..79cdf81a --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.module.scss @@ -0,0 +1,7 @@ +.filterPanel { + display: flex; + + button { + flex: 1 !important; + } +} diff --git a/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.sass b/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.sass deleted file mode 100644 index f71cf6c8..00000000 --- a/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.sass +++ /dev/null @@ -1,5 +0,0 @@ -.filter-panel - display: flex - - button - flex: 1 !important diff --git a/opendc-web/opendc-web-ui/src/components/projects/NewPortfolio.js b/opendc-web/opendc-web-ui/src/components/projects/NewPortfolio.js new file mode 100644 index 00000000..87ea059d --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/projects/NewPortfolio.js @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import { PlusIcon } from '@patternfly/react-icons' +import { Button } from '@patternfly/react-core' +import { useState } from 'react' +import { useMutation } from 'react-query' +import NewPortfolioModal from './NewPortfolioModal' + +function NewPortfolio({ projectId }) { + const [isVisible, setVisible] = useState(false) + const { mutate: addPortfolio } = useMutation('addPortfolio') + + const onSubmit = (name, targets) => { + addPortfolio({ projectId, name, targets }) + setVisible(false) + } + + return ( + <> + <Button icon={<PlusIcon />} isSmall onClick={() => setVisible(true)}> + New Portfolio + </Button> + <NewPortfolioModal isOpen={isVisible} onSubmit={onSubmit} onCancel={() => setVisible(false)} /> + </> + ) +} + +NewPortfolio.propTypes = { + projectId: PropTypes.string, +} + +export default NewPortfolio diff --git a/opendc-web/opendc-web-ui/src/components/projects/NewPortfolioModal.js b/opendc-web/opendc-web-ui/src/components/projects/NewPortfolioModal.js new file mode 100644 index 00000000..4276d7d4 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/projects/NewPortfolioModal.js @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import React, { useRef, useState } from 'react' +import { + Form, + FormGroup, + FormSection, + NumberInput, + Select, + SelectGroup, + SelectOption, + SelectVariant, + TextInput, +} from '@patternfly/react-core' +import Modal from '../util/modals/Modal' +import { METRIC_GROUPS, METRIC_NAMES } from '../../util/available-metrics' + +const NewPortfolioModal = ({ isOpen, onSubmit: onSubmitUpstream, onCancel: onUpstreamCancel }) => { + const nameInput = useRef(null) + const [repeats, setRepeats] = useState(1) + const [isSelectOpen, setSelectOpen] = useState(false) + const [selectedMetrics, setSelectedMetrics] = useState([]) + + const [isSubmitted, setSubmitted] = useState(false) + const [errors, setErrors] = useState({}) + + const clearState = () => { + setSubmitted(false) + setErrors({}) + nameInput.current.value = '' + setRepeats(1) + setSelectOpen(false) + setSelectedMetrics([]) + } + + const onSubmit = (event) => { + setSubmitted(true) + + if (event) { + event.preventDefault() + } + + const name = nameInput.current.value + + if (!name) { + setErrors({ name: true }) + return false + } else { + onSubmitUpstream(name, { enabledMetrics: selectedMetrics, repeatsPerScenario: repeats }) + } + + clearState() + return false + } + const onCancel = () => { + onUpstreamCancel() + clearState() + } + + const onSelect = (event, selection) => { + if (selectedMetrics.includes(selection)) { + setSelectedMetrics((metrics) => metrics.filter((item) => item !== selection)) + } else { + setSelectedMetrics((metrics) => [...metrics, selection]) + } + } + + return ( + <Modal title="New Portfolio" isOpen={isOpen} onSubmit={onSubmit} onCancel={onCancel}> + <Form onSubmit={onSubmit}> + <FormSection> + <FormGroup + label="Name" + fieldId="name" + isRequired + validated={isSubmitted && errors.name ? 'error' : 'default'} + helperTextInvalid="This field cannot be empty" + > + <TextInput + name="name" + id="name" + type="text" + isRequired + ref={nameInput} + placeholder="My Portfolio" + /> + </FormGroup> + </FormSection> + <FormSection title="Targets" titleElement="h4"> + <FormGroup label="Metrics" fieldId="metrics"> + <Select + variant={SelectVariant.typeaheadMulti} + typeAheadAriaLabel="Select a metric" + onToggle={() => setSelectOpen(!isSelectOpen)} + onSelect={onSelect} + onClear={() => setSelectedMetrics([])} + selections={selectedMetrics} + isOpen={isSelectOpen} + placeholderText="Select a metric" + menuAppendTo="parent" + maxHeight="300px" + chipGroupProps={{ numChips: 1 }} + isGrouped + > + {Object.entries(METRIC_GROUPS).map(([group, metrics]) => ( + <SelectGroup label={group} key={group}> + {metrics.map((metric) => ( + <SelectOption key={metric} value={metric}> + {METRIC_NAMES[metric]} + </SelectOption> + ))} + </SelectGroup> + ))} + </Select> + </FormGroup> + <FormGroup label="Repeats per Scenario" fieldId="repeats"> + <NumberInput + id="repeats" + inputName="repeats" + type="number" + value={repeats} + onChange={(e) => setRepeats(Number(e.target.value))} + onPlus={() => setRepeats((r) => r + 1)} + onMinus={() => setRepeats((r) => r - 1)} + min={1} + /> + </FormGroup> + </FormSection> + </Form> + </Modal> + ) +} + +NewPortfolioModal.propTypes = { + isOpen: PropTypes.bool.isRequired, + onSubmit: PropTypes.func.isRequired, + onCancel: PropTypes.func.isRequired, +} + +export default NewPortfolioModal diff --git a/opendc-web/opendc-web-ui/src/components/projects/NewProject.js b/opendc-web/opendc-web-ui/src/components/projects/NewProject.js new file mode 100644 index 00000000..984264dc --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/projects/NewProject.js @@ -0,0 +1,39 @@ +import React, { useState } from 'react' +import { Button } from '@patternfly/react-core' +import { useMutation } from 'react-query' +import { PlusIcon } from '@patternfly/react-icons' +import { buttonContainer } from './NewProject.module.scss' +import TextInputModal from '../util/modals/TextInputModal' + +/** + * A container for creating a new project. + */ +const NewProject = () => { + const [isVisible, setVisible] = useState(false) + const { mutate: addProject } = useMutation('addProject') + + const onSubmit = (name) => { + if (name) { + addProject({ name }) + } + setVisible(false) + } + + return ( + <> + <div className={buttonContainer}> + <Button + icon={<PlusIcon />} + color="primary" + className="pf-u-float-right" + onClick={() => setVisible(true)} + > + New Project + </Button> + </div> + <TextInputModal title="New Project" label="Project name" isOpen={isVisible} callback={onSubmit} /> + </> + ) +} + +export default NewProject diff --git a/opendc-web/opendc-web-ui/src/components/projects/NewProject.module.scss b/opendc-web/opendc-web-ui/src/components/projects/NewProject.module.scss new file mode 100644 index 00000000..5a0e74fc --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/projects/NewProject.module.scss @@ -0,0 +1,26 @@ +/*! + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +.buttonContainer { + flex: 0 1 auto; + padding: 20px 0; +} diff --git a/opendc-web/opendc-web-ui/src/components/projects/NewProjectButtonComponent.js b/opendc-web/opendc-web-ui/src/components/projects/NewProjectButtonComponent.js deleted file mode 100644 index 312671c6..00000000 --- a/opendc-web/opendc-web-ui/src/components/projects/NewProjectButtonComponent.js +++ /dev/null @@ -1,17 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' - -const NewProjectButtonComponent = ({ onClick }) => ( - <div className="bottom-btn-container"> - <div className="btn btn-primary float-right" onClick={onClick}> - <span className="fa fa-plus mr-2" /> - New Project - </div> - </div> -) - -NewProjectButtonComponent.propTypes = { - onClick: PropTypes.func.isRequired, -} - -export default NewProjectButtonComponent diff --git a/opendc-web/opendc-web-ui/src/components/projects/NewTopology.js b/opendc-web/opendc-web-ui/src/components/projects/NewTopology.js new file mode 100644 index 00000000..bf59e020 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/projects/NewTopology.js @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import { PlusIcon } from '@patternfly/react-icons' +import { Button } from '@patternfly/react-core' +import { useState } from 'react' +import { useDispatch } from 'react-redux' +import { addTopology } from '../../redux/actions/topologies' +import NewTopologyModal from './NewTopologyModal' + +function NewTopology({ projectId }) { + const [isVisible, setVisible] = useState(false) + const dispatch = useDispatch() + + const onSubmit = (name, duplicateId) => { + dispatch(addTopology(projectId, name, duplicateId)) + setVisible(false) + } + return ( + <> + <Button icon={<PlusIcon />} isSmall onClick={() => setVisible(true)}> + New Topology + </Button> + <NewTopologyModal + projectId={projectId} + isOpen={isVisible} + onSubmit={onSubmit} + onCancel={() => setVisible(false)} + /> + </> + ) +} + +NewTopology.propTypes = { + projectId: PropTypes.string, +} + +export default NewTopology diff --git a/opendc-web/opendc-web-ui/src/components/projects/NewTopologyModal.js b/opendc-web/opendc-web-ui/src/components/projects/NewTopologyModal.js new file mode 100644 index 00000000..a495f73e --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/projects/NewTopologyModal.js @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import React, { useRef, useState } from 'react' +import { Form, FormGroup, FormSelect, FormSelectOption, TextInput } from '@patternfly/react-core' +import { useProjectTopologies } from '../../data/topology' +import Modal from '../util/modals/Modal' + +const NewTopologyModal = ({ projectId, isOpen, onSubmit: onSubmitUpstream, onCancel: onCancelUpstream }) => { + const nameInput = useRef(null) + const [isSubmitted, setSubmitted] = useState(false) + const [originTopology, setOriginTopology] = useState(-1) + const [errors, setErrors] = useState({}) + + const { data: topologies = [] } = useProjectTopologies(projectId) + + const clearState = () => { + nameInput.current.value = '' + setSubmitted(false) + setOriginTopology(-1) + setErrors({}) + } + + const onSubmit = (event) => { + setSubmitted(true) + + if (event) { + event.preventDefault() + } + + const name = nameInput.current.value + + if (!name) { + setErrors({ name: true }) + return false + } else if (originTopology === -1) { + onSubmitUpstream(name) + } else { + onSubmitUpstream(name, originTopology) + } + + clearState() + return true + } + + const onCancel = () => { + onCancelUpstream() + clearState() + } + + return ( + <Modal title="New Topology" isOpen={isOpen} onSubmit={onSubmit} onCancel={onCancel}> + <Form onSubmit={onSubmit}> + <FormGroup + label="Name" + fieldId="name" + isRequired + validated={isSubmitted && errors.name ? 'error' : 'default'} + helperTextInvalid="This field cannot be empty" + > + <TextInput id="name" name="name" type="text" isRequired ref={nameInput} /> + </FormGroup> + <FormGroup label="Topology to duplicate" fieldId="origin" isRequired> + <FormSelect id="origin" name="origin" value={originTopology} onChange={setOriginTopology}> + <FormSelectOption value={-1} key={-1} label="None - start from scratch" /> + {topologies.map((topology) => ( + <FormSelectOption value={topology._id} key={topology._id} label={topology.name} /> + ))} + </FormSelect> + </FormGroup> + </Form> + </Modal> + ) +} + +NewTopologyModal.propTypes = { + projectId: PropTypes.string, + isOpen: PropTypes.bool.isRequired, + onSubmit: PropTypes.func.isRequired, + onCancel: PropTypes.func.isRequired, +} + +export default NewTopologyModal diff --git a/opendc-web/opendc-web-ui/src/components/projects/PortfolioTable.js b/opendc-web/opendc-web-ui/src/components/projects/PortfolioTable.js new file mode 100644 index 00000000..45e399ed --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/projects/PortfolioTable.js @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import Link from 'next/link' +import { Table, TableBody, TableHeader } from '@patternfly/react-table' +import React from 'react' +import TableEmptyState from '../util/TableEmptyState' +import { useProjectPortfolios } from '../../data/project' +import { useMutation } from 'react-query' + +const PortfolioTable = ({ projectId }) => { + const { status, data: portfolios = [] } = useProjectPortfolios(projectId) + const { mutate: deletePortfolio } = useMutation('deletePortfolio') + + const columns = ['Name', 'Scenarios', 'Metrics', 'Repeats'] + const rows = + portfolios.length > 0 + ? portfolios.map((portfolio) => [ + { + title: ( + <Link href={`/projects/${portfolio.projectId}/portfolios/${portfolio._id}`}> + {portfolio.name} + </Link> + ), + }, + + portfolio.scenarioIds.length === 1 ? '1 scenario' : `${portfolio.scenarioIds.length} scenarios`, + + portfolio.targets.enabledMetrics.length === 1 + ? '1 metric' + : `${portfolio.targets.enabledMetrics.length} metrics`, + portfolio.targets.repeatsPerScenario === 1 + ? '1 repeat' + : `${portfolio.targets.repeatsPerScenario} repeats`, + ]) + : [ + { + heightAuto: true, + cells: [ + { + props: { colSpan: 4 }, + title: ( + <TableEmptyState + status={status} + loadingTitle="Loading portfolios" + emptyTitle="No portfolios" + emptyText="You have not created any portfolio for this project yet. Click the New Portfolio button to create one." + /> + ), + }, + ], + }, + ] + + const actions = + portfolios.length > 0 + ? [ + { + title: 'Delete Portfolio', + onClick: (_, rowId) => deletePortfolio(portfolios[rowId]._id), + }, + ] + : [] + + return ( + <Table aria-label="Portfolio List" variant="compact" cells={columns} rows={rows} actions={actions}> + <TableHeader /> + <TableBody /> + </Table> + ) +} + +PortfolioTable.propTypes = { + projectId: PropTypes.string, +} + +export default PortfolioTable diff --git a/opendc-web/opendc-web-ui/src/components/projects/ProjectActionButtons.js b/opendc-web/opendc-web-ui/src/components/projects/ProjectActionButtons.js deleted file mode 100644 index 1c76cc7f..00000000 --- a/opendc-web/opendc-web-ui/src/components/projects/ProjectActionButtons.js +++ /dev/null @@ -1,29 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { Link } from 'react-router-dom' - -const ProjectActionButtons = ({ projectId, onViewUsers, onDelete }) => ( - <td className="text-right"> - <Link to={'/projects/' + projectId} className="btn btn-outline-primary btn-sm mr-2" title="Open this project"> - <span className="fa fa-play" /> - </Link> - <div - className="btn btn-outline-success btn-sm disabled mr-2" - title="View and edit collaborators (not supported currently)" - onClick={() => onViewUsers(projectId)} - > - <span className="fa fa-users" /> - </div> - <div className="btn btn-outline-danger btn-sm" title="Delete this project" onClick={() => onDelete(projectId)}> - <span className="fa fa-trash" /> - </div> - </td> -) - -ProjectActionButtons.propTypes = { - projectId: PropTypes.string.isRequired, - onViewUsers: PropTypes.func, - onDelete: PropTypes.func, -} - -export default ProjectActionButtons diff --git a/opendc-web/opendc-web-ui/src/components/projects/ProjectAuthList.js b/opendc-web/opendc-web-ui/src/components/projects/ProjectAuthList.js deleted file mode 100644 index 8eb4f93b..00000000 --- a/opendc-web/opendc-web-ui/src/components/projects/ProjectAuthList.js +++ /dev/null @@ -1,39 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import Shapes from '../../shapes/index' -import ProjectAuthRow from './ProjectAuthRow' - -const ProjectAuthList = ({ authorizations }) => { - return ( - <div className="vertically-expanding-container"> - {authorizations.length === 0 ? ( - <div className="alert alert-info"> - <span className="info-icon fa fa-question-circle mr-2" /> - <strong>No projects here yet...</strong> Add some with the 'New Project' button! - </div> - ) : ( - <table className="table table-striped"> - <thead> - <tr> - <th>Project name</th> - <th>Last edited</th> - <th>Access rights</th> - <th /> - </tr> - </thead> - <tbody> - {authorizations.map((authorization) => ( - <ProjectAuthRow projectAuth={authorization} key={authorization.project._id} /> - ))} - </tbody> - </table> - )} - </div> - ) -} - -ProjectAuthList.propTypes = { - authorizations: PropTypes.arrayOf(Shapes.Authorization).isRequired, -} - -export default ProjectAuthList diff --git a/opendc-web/opendc-web-ui/src/components/projects/ProjectAuthRow.js b/opendc-web/opendc-web-ui/src/components/projects/ProjectAuthRow.js deleted file mode 100644 index 3f904061..00000000 --- a/opendc-web/opendc-web-ui/src/components/projects/ProjectAuthRow.js +++ /dev/null @@ -1,24 +0,0 @@ -import classNames from 'classnames' -import React from 'react' -import ProjectActions from '../../containers/projects/ProjectActions' -import Shapes from '../../shapes/index' -import { AUTH_DESCRIPTION_MAP, AUTH_ICON_MAP } from '../../util/authorizations' -import { parseAndFormatDateTime } from '../../util/date-time' - -const ProjectAuthRow = ({ projectAuth }) => ( - <tr> - <td className="pt-3">{projectAuth.project.name}</td> - <td className="pt-3">{parseAndFormatDateTime(projectAuth.project.datetimeLastEdited)}</td> - <td className="pt-3"> - <span className={classNames('fa', 'fa-' + AUTH_ICON_MAP[projectAuth.authorizationLevel], 'mr-2')} /> - {AUTH_DESCRIPTION_MAP[projectAuth.authorizationLevel]} - </td> - <ProjectActions projectId={projectAuth.project._id} /> - </tr> -) - -ProjectAuthRow.propTypes = { - projectAuth: Shapes.Authorization.isRequired, -} - -export default ProjectAuthRow diff --git a/opendc-web/opendc-web-ui/src/components/projects/ProjectOverview.js b/opendc-web/opendc-web-ui/src/components/projects/ProjectOverview.js new file mode 100644 index 00000000..65b8f5a0 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/projects/ProjectOverview.js @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import { + Card, + CardActions, + CardBody, + CardHeader, + CardTitle, + DescriptionList, + DescriptionListDescription, + DescriptionListGroup, + DescriptionListTerm, + Grid, + GridItem, + Skeleton, +} from '@patternfly/react-core' +import NewTopology from './NewTopology' +import TopologyTable from './TopologyTable' +import NewPortfolio from './NewPortfolio' +import PortfolioTable from './PortfolioTable' +import { useProject } from '../../data/project' + +function ProjectOverview({ projectId }) { + const { data: project } = useProject(projectId) + + return ( + <Grid hasGutter> + <GridItem md={2}> + <Card> + <CardTitle>Details</CardTitle> + <CardBody> + <DescriptionList> + <DescriptionListGroup> + <DescriptionListTerm>Name</DescriptionListTerm> + <DescriptionListDescription> + {project?.name ?? <Skeleton screenreaderText="Loading project" />} + </DescriptionListDescription> + </DescriptionListGroup> + </DescriptionList> + </CardBody> + </Card> + </GridItem> + <GridItem md={5}> + <Card> + <CardHeader> + <CardActions> + <NewTopology projectId={projectId} /> + </CardActions> + <CardTitle>Topologies</CardTitle> + </CardHeader> + <CardBody> + <TopologyTable projectId={projectId} /> + </CardBody> + </Card> + </GridItem> + <GridItem md={5}> + <Card> + <CardHeader> + <CardActions> + <NewPortfolio projectId={projectId} /> + </CardActions> + <CardTitle>Portfolios</CardTitle> + </CardHeader> + <CardBody> + <PortfolioTable projectId={projectId} /> + </CardBody> + </Card> + </GridItem> + </Grid> + ) +} + +ProjectOverview.propTypes = { + projectId: PropTypes.string, +} + +export default ProjectOverview diff --git a/opendc-web/opendc-web-ui/src/components/projects/ProjectTable.js b/opendc-web/opendc-web-ui/src/components/projects/ProjectTable.js new file mode 100644 index 00000000..a7290259 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/projects/ProjectTable.js @@ -0,0 +1,76 @@ +import PropTypes from 'prop-types' +import React from 'react' +import Link from 'next/link' +import { Project, Status } from '../../shapes' +import { Table, TableBody, TableHeader } from '@patternfly/react-table' +import { parseAndFormatDateTime } from '../../util/date-time' +import { AUTH_DESCRIPTION_MAP, AUTH_ICON_MAP } from '../../util/authorizations' +import { useAuth } from '../../auth' +import TableEmptyState from '../util/TableEmptyState' + +const ProjectTable = ({ status, projects, onDelete, isFiltering }) => { + const { user } = useAuth() + const columns = ['Project name', 'Last edited', 'Access Rights'] + const rows = + projects.length > 0 + ? projects.map((project) => { + const { level } = project.authorizations.find((auth) => auth.userId === user.sub) + const Icon = AUTH_ICON_MAP[level] + return [ + { + title: <Link href={`/projects/${project._id}`}>{project.name}</Link>, + }, + parseAndFormatDateTime(project.datetimeLastEdited), + { + title: ( + <> + <Icon className="pf-u-mr-md" key="auth" /> {AUTH_DESCRIPTION_MAP[level]} + </> + ), + }, + ] + }) + : [ + { + heightAuto: true, + cells: [ + { + props: { colSpan: 3 }, + title: ( + <TableEmptyState + status={status} + loadingTitle="Loading Projects" + isFiltering={isFiltering} + /> + ), + }, + ], + }, + ] + + const actions = + projects.length > 0 + ? [ + { + title: 'Delete Project', + onClick: (_, rowId) => onDelete(projects[rowId]), + }, + ] + : [] + + return ( + <Table aria-label="Project List" variant="compact" cells={columns} rows={rows} actions={actions}> + <TableHeader /> + <TableBody /> + </Table> + ) +} + +ProjectTable.propTypes = { + status: Status.isRequired, + isFiltering: PropTypes.bool, + projects: PropTypes.arrayOf(Project).isRequired, + onDelete: PropTypes.func, +} + +export default ProjectTable diff --git a/opendc-web/opendc-web-ui/src/components/projects/TopologyTable.js b/opendc-web/opendc-web-ui/src/components/projects/TopologyTable.js new file mode 100644 index 00000000..80099ece --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/projects/TopologyTable.js @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import Link from 'next/link' +import { Table, TableBody, TableHeader } from '@patternfly/react-table' +import React from 'react' +import TableEmptyState from '../util/TableEmptyState' +import { parseAndFormatDateTime } from '../../util/date-time' +import { useMutation } from 'react-query' +import { useProjectTopologies } from '../../data/topology' + +const TopologyTable = ({ projectId }) => { + const { status, data: topologies = [] } = useProjectTopologies(projectId) + const { mutate: deleteTopology } = useMutation('deleteTopology') + + const columns = ['Name', 'Rooms', 'Last Edited'] + const rows = + topologies.length > 0 + ? topologies.map((topology) => [ + { + title: ( + <Link href={`/projects/${topology.projectId}/topologies/${topology._id}`}> + {topology.name} + </Link> + ), + }, + topology.rooms.length === 1 ? '1 room' : `${topology.rooms.length} rooms`, + parseAndFormatDateTime(topology.datetimeLastEdited), + ]) + : [ + { + heightAuto: true, + cells: [ + { + props: { colSpan: 3 }, + title: ( + <TableEmptyState + status={status} + loadingTitle="Loading topologies" + emptyTitle="No topologies" + emptyText="You have not created any topology for this project yet. Click the New Topology button to create one." + /> + ), + }, + ], + }, + ] + + const actionResolver = (_, { rowIndex }) => [ + { + title: 'Delete Topology', + onClick: (_, rowId) => deleteTopology(topologies[rowId]._id), + isDisabled: rowIndex === 0, + }, + ] + + return ( + <Table + aria-label="Topology List" + variant="compact" + cells={columns} + rows={rows} + actionResolver={topologies.length > 0 ? actionResolver : () => []} + > + <TableHeader /> + <TableBody /> + </Table> + ) +} + +TopologyTable.propTypes = { + projectId: PropTypes.string, +} + +export default TopologyTable diff --git a/opendc-web/opendc-web-ui/src/components/topologies/RoomTable.js b/opendc-web/opendc-web-ui/src/components/topologies/RoomTable.js new file mode 100644 index 00000000..9bf369e9 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/RoomTable.js @@ -0,0 +1,69 @@ +import { Button } from '@patternfly/react-core' +import PropTypes from 'prop-types' +import React from 'react' +import { useDispatch } from 'react-redux' +import { useTopology } from '../../data/topology' +import { Table, TableBody, TableHeader } from '@patternfly/react-table' +import { deleteRoom } from '../../redux/actions/topology/room' +import TableEmptyState from '../util/TableEmptyState' + +function RoomTable({ topologyId, onSelect }) { + const dispatch = useDispatch() + const { status, data: topology } = useTopology(topologyId) + + const onDelete = (room) => dispatch(deleteRoom(room._id)) + + const columns = ['Name', 'Tiles', 'Racks'] + const rows = + topology?.rooms.length > 0 + ? topology.rooms.map((room) => { + const tileCount = room.tiles.length + const rackCount = room.tiles.filter((tile) => tile.rack).length + return [ + { + title: ( + <Button variant="link" isInline onClick={() => onSelect(room)}> + {room.name} + </Button> + ), + }, + tileCount === 1 ? '1 tile' : `${tileCount} tiles`, + rackCount === 1 ? '1 rack' : `${rackCount} racks`, + ] + }) + : [ + { + heightAuto: true, + cells: [ + { + props: { colSpan: 3 }, + title: <TableEmptyState status={status} loadingTitle="Loading Rooms" />, + }, + ], + }, + ] + + const actions = + topology?.rooms.length > 0 + ? [ + { + title: 'Delete room', + onClick: (_, rowId) => onDelete(topology.rooms[rowId]), + }, + ] + : [] + + return ( + <Table aria-label="Room list" variant="compact" cells={columns} rows={rows} actions={actions}> + <TableHeader /> + <TableBody /> + </Table> + ) +} + +RoomTable.propTypes = { + topologyId: PropTypes.string, + onSelect: PropTypes.func, +} + +export default RoomTable diff --git a/opendc-web/opendc-web-ui/src/components/topologies/TopologyMap.js b/opendc-web/opendc-web-ui/src/components/topologies/TopologyMap.js new file mode 100644 index 00000000..2f27749f --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/TopologyMap.js @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React, { useState } from 'react' +import { + Bullseye, + Drawer, + DrawerContent, + DrawerContentBody, + EmptyState, + EmptyStateIcon, + Spinner, + Title, +} from '@patternfly/react-core' +import { configure, HotKeys } from 'react-hotkeys' +import { KeymapConfiguration } from '../../hotkeys' +import MapStage from './map/MapStage' +import Collapse from './map/controls/Collapse' +import { useSelector } from 'react-redux' +import TopologySidebar from './sidebar/TopologySidebar' + +function TopologyMap() { + const topologyIsLoading = useSelector((state) => !state.topology.root) + const interactionLevel = useSelector((state) => state.interactionLevel) + + const [isExpanded, setExpanded] = useState(true) + const panelContent = <TopologySidebar interactionLevel={interactionLevel} onClose={() => setExpanded(false)} /> + + // Make sure that holding down a key will generate repeated events + configure({ + ignoreRepeatedEventsWhenKeyHeldDown: false, + }) + + return topologyIsLoading ? ( + <Bullseye> + <EmptyState> + <EmptyStateIcon variant="container" component={Spinner} /> + <Title size="lg" headingLevel="h4"> + Loading Topology + </Title> + </EmptyState> + </Bullseye> + ) : ( + <HotKeys keyMap={KeymapConfiguration} allowChanges={true} className="full-height"> + <Drawer isExpanded={isExpanded}> + <DrawerContent panelContent={panelContent}> + <DrawerContentBody> + <MapStage /> + <Collapse onClick={() => setExpanded(true)} /> + </DrawerContentBody> + </DrawerContent> + </Drawer> + </HotKeys> + ) +} + +export default TopologyMap diff --git a/opendc-web/opendc-web-ui/src/components/topologies/TopologyOverview.js b/opendc-web/opendc-web-ui/src/components/topologies/TopologyOverview.js new file mode 100644 index 00000000..213a4868 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/TopologyOverview.js @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import { + Card, + CardBody, + CardTitle, + DescriptionList, + DescriptionListDescription, + DescriptionListGroup, + DescriptionListTerm, + Grid, + GridItem, + Skeleton, +} from '@patternfly/react-core' +import React from 'react' +import { useTopology } from '../../data/topology' +import { parseAndFormatDateTime } from '../../util/date-time' +import RoomTable from './RoomTable' + +function TopologyOverview({ topologyId, onSelect }) { + const { data: topology } = useTopology(topologyId) + return ( + <Grid hasGutter> + <GridItem md={2}> + <Card> + <CardTitle>Details</CardTitle> + <CardBody> + <DescriptionList> + <DescriptionListGroup> + <DescriptionListTerm>Name</DescriptionListTerm> + <DescriptionListDescription> + {topology?.name ?? <Skeleton screenreaderText="Loading topology" />} + </DescriptionListDescription> + </DescriptionListGroup> + <DescriptionListGroup> + <DescriptionListTerm>Last edited</DescriptionListTerm> + <DescriptionListDescription> + {topology ? ( + parseAndFormatDateTime(topology.datetimeLastEdited) + ) : ( + <Skeleton screenreaderText="Loading topology" /> + )} + </DescriptionListDescription> + </DescriptionListGroup> + </DescriptionList> + </CardBody> + </Card> + </GridItem> + <GridItem md={5}> + <Card> + <CardTitle>Rooms</CardTitle> + <CardBody> + <RoomTable topologyId={topologyId} onSelect={(room) => onSelect('room', room)} /> + </CardBody> + </Card> + </GridItem> + </Grid> + ) +} + +TopologyOverview.propTypes = { + topologyId: PropTypes.string, + onSelect: PropTypes.func, +} + +export default TopologyOverview diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/GrayContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/GrayContainer.js new file mode 100644 index 00000000..ccf637e5 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/GrayContainer.js @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React from 'react' +import { useDispatch } from 'react-redux' +import { goDownOneInteractionLevel } from '../../../redux/actions/interaction-level' +import GrayLayer from './elements/GrayLayer' + +function GrayContainer() { + const dispatch = useDispatch() + const onClick = () => dispatch(goDownOneInteractionLevel()) + return <GrayLayer onClick={onClick} /> +} + +export default GrayContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/MapConstants.js b/opendc-web/opendc-web-ui/src/components/topologies/map/MapConstants.js new file mode 100644 index 00000000..4c3b2757 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/MapConstants.js @@ -0,0 +1,25 @@ +export const MAP_SIZE = 50 +export const TILE_SIZE_IN_PIXELS = 100 +export const TILE_SIZE_IN_METERS = 0.5 +export const MAP_SIZE_IN_PIXELS = MAP_SIZE * TILE_SIZE_IN_PIXELS + +export const OBJECT_MARGIN_IN_PIXELS = TILE_SIZE_IN_PIXELS / 5 +export const TILE_PLUS_MARGIN_IN_PIXELS = TILE_SIZE_IN_PIXELS / 3 +export const OBJECT_SIZE_IN_PIXELS = TILE_SIZE_IN_PIXELS - OBJECT_MARGIN_IN_PIXELS * 2 + +export const GRID_LINE_WIDTH_IN_PIXELS = 2 +export const WALL_WIDTH_IN_PIXELS = TILE_SIZE_IN_PIXELS / 16 +export const OBJECT_BORDER_WIDTH_IN_PIXELS = TILE_SIZE_IN_PIXELS / 16 +export const TILE_PLUS_WIDTH_IN_PIXELS = TILE_SIZE_IN_PIXELS / 10 + +export const RACK_FILL_ICON_WIDTH = OBJECT_SIZE_IN_PIXELS / 3 +export const RACK_FILL_ICON_OPACITY = 0.8 + +export const MAP_MOVE_PIXELS_PER_EVENT = 20 +export const MAP_SCALE_PER_EVENT = 1.1 +export const MAP_MIN_SCALE = 0.5 +export const MAP_MAX_SCALE = 1.5 + +export const MAX_NUM_UNITS_PER_MACHINE = 6 +export const DEFAULT_RACK_SLOT_CAPACITY = 42 +export const DEFAULT_RACK_POWER_CAPACITY = 10000 diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/MapStage.js b/opendc-web/opendc-web-ui/src/components/topologies/map/MapStage.js new file mode 100644 index 00000000..d8735cf1 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/MapStage.js @@ -0,0 +1,83 @@ +import React, { useRef, useState, useContext } from 'react' +import { HotKeys } from 'react-hotkeys' +import { Stage } from 'react-konva' +import { MAP_MAX_SCALE, MAP_MIN_SCALE, MAP_MOVE_PIXELS_PER_EVENT, MAP_SCALE_PER_EVENT } from './MapConstants' +import { ReactReduxContext } from 'react-redux' +import useResizeObserver from 'use-resize-observer' +import { mapContainer } from './MapStage.module.scss' +import MapLayer from './layers/MapLayer' +import RoomHoverLayer from './layers/RoomHoverLayer' +import ObjectHoverLayer from './layers/ObjectHoverLayer' +import ScaleIndicator from './controls/ScaleIndicator' +import Toolbar from './controls/Toolbar' + +function MapStage() { + const reduxContext = useContext(ReactReduxContext) + const { ref, width = 100, height = 100 } = useResizeObserver() + const stageRef = useRef(null) + const [[x, y], setPos] = useState([0, 0]) + const [scale, setScale] = useState(1) + + const clampScale = (target) => Math.min(Math.max(target, MAP_MIN_SCALE), MAP_MAX_SCALE) + const moveWithDelta = (deltaX, deltaY) => setPos(([x, y]) => [x + deltaX, y + deltaY]) + + const onZoom = (e) => { + e.evt.preventDefault() + + const stage = stageRef.current.getStage() + const oldScale = scale + + const pointer = stage.getPointerPosition() + const mousePointTo = { + x: (pointer.x - x) / oldScale, + y: (pointer.y - y) / oldScale, + } + + const newScale = clampScale(e.evt.deltaY > 0 ? oldScale * MAP_SCALE_PER_EVENT : oldScale / MAP_SCALE_PER_EVENT) + + setScale(newScale) + setPos([pointer.x - mousePointTo.x * newScale, pointer.y - mousePointTo.y * newScale]) + } + const onZoomButton = (zoomIn) => + setScale((scale) => clampScale(zoomIn ? scale * MAP_SCALE_PER_EVENT : scale / MAP_SCALE_PER_EVENT)) + const onDragEnd = (e) => setPos([e.target.x(), e.target.y()]) + const onExport = () => { + const download = document.createElement('a') + download.href = stageRef.current.getStage().toDataURL() + download.download = 'opendc-canvas-export-' + Date.now() + '.png' + download.click() + } + + const handlers = { + MOVE_LEFT: () => moveWithDelta(MAP_MOVE_PIXELS_PER_EVENT, 0), + MOVE_RIGHT: () => moveWithDelta(-MAP_MOVE_PIXELS_PER_EVENT, 0), + MOVE_UP: () => moveWithDelta(0, MAP_MOVE_PIXELS_PER_EVENT), + MOVE_DOWN: () => moveWithDelta(0, -MAP_MOVE_PIXELS_PER_EVENT), + } + + return ( + <HotKeys handlers={handlers} allowChanges={true} innerRef={ref} className={mapContainer}> + <Stage + ref={stageRef} + onWheel={onZoom} + onDragEnd={onDragEnd} + draggable + width={width} + height={height} + scale={{ x: scale, y: scale }} + x={x} + y={y} + > + <ReactReduxContext.Provider value={reduxContext}> + <MapLayer /> + <RoomHoverLayer /> + <ObjectHoverLayer /> + </ReactReduxContext.Provider> + </Stage> + <ScaleIndicator scale={scale} /> + <Toolbar onZoom={onZoomButton} onExport={onExport} /> + </HotKeys> + ) +} + +export default MapStage diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/MapStage.module.scss b/opendc-web/opendc-web-ui/src/components/topologies/map/MapStage.module.scss new file mode 100644 index 00000000..d879b4c8 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/MapStage.module.scss @@ -0,0 +1,31 @@ +/*! + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +.mapContainer { + background-color: var(--pf-global--Color--light-200); + position: relative; + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; +} diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/RackContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/RackContainer.js new file mode 100644 index 00000000..14449a91 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/RackContainer.js @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React from 'react' +import { useSelector } from 'react-redux' +import { Tile } from '../../../shapes' +import RackGroup from './groups/RackGroup' + +function RackContainer({ tile }) { + const interactionLevel = useSelector((state) => state.interactionLevel) + return <RackGroup interactionLevel={interactionLevel} tile={tile} /> +} + +RackContainer.propTypes = { + tile: Tile, +} + +export default RackContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/RackEnergyFillContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/RackEnergyFillContainer.js new file mode 100644 index 00000000..be1f3e45 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/RackEnergyFillContainer.js @@ -0,0 +1,34 @@ +import React from 'react' +import PropTypes from 'prop-types' +import { useSelector } from 'react-redux' +import RackFillBar from './elements/RackFillBar' + +function RackSpaceFillContainer({ tileId, ...props }) { + const fillFraction = useSelector((state) => { + let energyConsumptionTotal = 0 + const rack = state.topology.racks[state.topology.tiles[tileId].rack] + const machineIds = rack.machines + machineIds.forEach((machineId) => { + if (machineId !== null) { + const machine = state.topology.machines[machineId] + machine.cpus.forEach((id) => (energyConsumptionTotal += state.topology.cpus[id].energyConsumptionW)) + machine.gpus.forEach((id) => (energyConsumptionTotal += state.topology.gpus[id].energyConsumptionW)) + machine.memories.forEach( + (id) => (energyConsumptionTotal += state.topology.memories[id].energyConsumptionW) + ) + machine.storages.forEach( + (id) => (energyConsumptionTotal += state.topology.storages[id].energyConsumptionW) + ) + } + }) + + return Math.min(1, energyConsumptionTotal / rack.powerCapacityW) + }) + return <RackFillBar {...props} type="energy" fillFraction={fillFraction} /> +} + +RackSpaceFillContainer.propTypes = { + tileId: PropTypes.string.isRequired, +} + +export default RackSpaceFillContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/RackSpaceFillContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/RackSpaceFillContainer.js new file mode 100644 index 00000000..0c15d54b --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/RackSpaceFillContainer.js @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React from 'react' +import PropTypes from 'prop-types' +import { useSelector } from 'react-redux' +import RackFillBar from './elements/RackFillBar' + +function RackSpaceFillContainer({ tileId, ...props }) { + const rack = useSelector((state) => state.topology.racks[state.topology.tiles[tileId].rack]) + return <RackFillBar {...props} type="space" fillFraction={rack.machines.length / rack.capacity} /> +} + +RackSpaceFillContainer.propTypes = { + tileId: PropTypes.string.isRequired, +} + +export default RackSpaceFillContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/RoomContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/RoomContainer.js new file mode 100644 index 00000000..65189891 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/RoomContainer.js @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import React from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { goFromBuildingToRoom } from '../../../redux/actions/interaction-level' +import RoomGroup from './groups/RoomGroup' + +function RoomContainer({ roomId, ...props }) { + const state = useSelector((state) => { + return { + interactionLevel: state.interactionLevel, + currentRoomInConstruction: state.construction.currentRoomInConstruction, + room: state.topology.rooms[roomId], + } + }) + const dispatch = useDispatch() + return <RoomGroup {...props} {...state} onClick={() => dispatch(goFromBuildingToRoom(roomId))} /> +} + +RoomContainer.propTypes = { + roomId: PropTypes.string, +} + +export default RoomContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/TileContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/TileContainer.js new file mode 100644 index 00000000..411a5ca7 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/TileContainer.js @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React from 'react' +import PropTypes from 'prop-types' +import { useDispatch, useSelector } from 'react-redux' +import { goFromRoomToRack } from '../../../redux/actions/interaction-level' +import TileGroup from './groups/TileGroup' + +function TileContainer({ tileId, ...props }) { + const interactionLevel = useSelector((state) => state.interactionLevel) + const tile = useSelector((state) => state.topology.tiles[tileId]) + + const dispatch = useDispatch() + const onClick = (tile) => { + if (tile.rack) { + dispatch(goFromRoomToRack(tile._id)) + } + } + return <TileGroup {...props} onClick={onClick} tile={tile} interactionLevel={interactionLevel} /> +} + +TileContainer.propTypes = { + tileId: PropTypes.string.isRequired, +} + +export default TileContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/TopologyContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/TopologyContainer.js new file mode 100644 index 00000000..cc0d46b3 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/TopologyContainer.js @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React from 'react' +import { useSelector } from 'react-redux' +import TopologyGroup from './groups/TopologyGroup' + +function TopologyContainer() { + const topology = useSelector((state) => state.topology.root) + const interactionLevel = useSelector((state) => state.interactionLevel) + + return <TopologyGroup topology={topology} interactionLevel={interactionLevel} /> +} + +export default TopologyContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/WallContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/WallContainer.js new file mode 100644 index 00000000..143f70c2 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/WallContainer.js @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React from 'react' +import PropTypes from 'prop-types' +import { useSelector } from 'react-redux' +import WallGroup from './groups/WallGroup' + +function WallContainer({ roomId, ...props }) { + const tiles = useSelector((state) => { + return state.topology.rooms[roomId].tiles.map((tileId) => state.topology.tiles[tileId]) + }) + return <WallGroup {...props} tiles={tiles} /> +} + +WallContainer.propTypes = { + roomId: PropTypes.string.isRequired, +} + +export default WallContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Collapse.js b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Collapse.js new file mode 100644 index 00000000..f54b7c84 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Collapse.js @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import { ChevronLeftIcon } from '@patternfly/react-icons' +import { collapseContainer } from './Collapse.module.scss' +import { Button } from '@patternfly/react-core' + +function Collapse({ onClick }) { + return ( + <div className={collapseContainer}> + <Button variant="tertiary" onClick={onClick}> + <ChevronLeftIcon /> + </Button> + </div> + ) +} + +Collapse.propTypes = { + onClick: PropTypes.func, +} + +export default Collapse diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Collapse.module.scss b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Collapse.module.scss new file mode 100644 index 00000000..0c1fac94 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Collapse.module.scss @@ -0,0 +1,55 @@ +/*! + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +.collapseContainer { + position: absolute; + right: var(--pf-global--spacer--xs); + top: 0; + bottom: 10%; + margin: auto 0; + height: 50px; + + button:global(.pf-m-tertiary) { + height: 100%; + padding: 2px; + + margin-right: var(--pf-global--spacer--xs); + margin-top: var(--pf-global--spacer--xs); + background-color: var(--pf-global--BackgroundColor--100); + border: none; + border-radius: var(--pf-global--BorderRadius--sm); + box-shadow: var(--pf-global--BoxShadow--sm); + + &:not(:global(.pf-m-disabled)) { + background-color: var(--pf-global--BackgroundColor--100); + } + + &:after { + display: none; + } + + &:hover { + border: none; + box-shadow: var(--pf-global--BoxShadow--md); + } + } +} diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/controls/ScaleIndicator.js b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/ScaleIndicator.js new file mode 100644 index 00000000..58d2ccc9 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/ScaleIndicator.js @@ -0,0 +1,18 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { TILE_SIZE_IN_METERS, TILE_SIZE_IN_PIXELS } from '../MapConstants' +import { scaleIndicator } from './ScaleIndicator.module.scss' + +function ScaleIndicator({ scale }) { + return ( + <div className={scaleIndicator} style={{ width: TILE_SIZE_IN_PIXELS * scale }}> + {TILE_SIZE_IN_METERS}m + </div> + ) +} + +ScaleIndicator.propTypes = { + scale: PropTypes.number.isRequired, +} + +export default ScaleIndicator diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/controls/ScaleIndicator.module.scss b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/ScaleIndicator.module.scss new file mode 100644 index 00000000..f19e0ff2 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/ScaleIndicator.module.scss @@ -0,0 +1,10 @@ +.scaleIndicator { + position: absolute; + right: 10px; + bottom: 10px; + z-index: 50; + + border: solid 2px #212529; + border-top: none; + border-left: none; +} diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Toolbar.js b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Toolbar.js new file mode 100644 index 00000000..469fd515 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Toolbar.js @@ -0,0 +1,35 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { control, toolBar } from './Toolbar.module.scss' +import { Button } from '@patternfly/react-core' +import { SearchPlusIcon, SearchMinusIcon } from '@patternfly/react-icons' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faCamera } from '@fortawesome/free-solid-svg-icons' + +function Toolbar({ onZoom, onExport }) { + return ( + <div className={toolBar}> + <Button variant="tertiary" title="Zoom in" onClick={() => onZoom(true)} className={control}> + <SearchPlusIcon /> + </Button> + <Button variant="tertiary" title="Zoom out" onClick={() => onZoom(false)} className={control}> + <SearchMinusIcon /> + </Button> + <Button + variant="tertiary" + title="Export Canvas to PNG Image" + onClick={() => onExport()} + className={control} + > + <FontAwesomeIcon icon={faCamera} /> + </Button> + </div> + ) +} + +Toolbar.propTypes = { + onZoom: PropTypes.func, + onExport: PropTypes.func, +} + +export default Toolbar diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Toolbar.module.scss b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Toolbar.module.scss new file mode 100644 index 00000000..0d505acc --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Toolbar.module.scss @@ -0,0 +1,29 @@ +.toolBar { + position: absolute; + bottom: var(--pf-global--spacer--md); + left: var(--pf-global--spacer--xl); +} + +.control { + &:global(.pf-m-tertiary) { + margin-right: var(--pf-global--spacer--xs); + margin-top: var(--pf-global--spacer--xs); + background-color: var(--pf-global--BackgroundColor--100); + border: none; + border-radius: var(--pf-global--BorderRadius--sm); + box-shadow: var(--pf-global--BoxShadow--sm); + + &:not(:global(.pf-m-disabled)) { + background-color: var(--pf-global--BackgroundColor--100); + } + + &:after { + display: none; + } + + &:hover { + border: none; + box-shadow: var(--pf-global--BoxShadow--md); + } + } +} diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/Backdrop.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/Backdrop.js new file mode 100644 index 00000000..93037b51 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/Backdrop.js @@ -0,0 +1,10 @@ +import React from 'react' +import { Rect } from 'react-konva' +import { BACKDROP_COLOR } from '../../../../util/colors' +import { MAP_SIZE_IN_PIXELS } from '../MapConstants' + +function Backdrop() { + return <Rect x={0} y={0} width={MAP_SIZE_IN_PIXELS} height={MAP_SIZE_IN_PIXELS} fill={BACKDROP_COLOR} /> +} + +export default Backdrop diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/GrayLayer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/GrayLayer.js new file mode 100644 index 00000000..08c687f6 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/GrayLayer.js @@ -0,0 +1,24 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { Rect } from 'react-konva' +import { GRAYED_OUT_AREA_COLOR } from '../../../../util/colors' +import { MAP_SIZE_IN_PIXELS } from '../MapConstants' + +function GrayLayer({ onClick }) { + return ( + <Rect + x={0} + y={0} + width={MAP_SIZE_IN_PIXELS} + height={MAP_SIZE_IN_PIXELS} + fill={GRAYED_OUT_AREA_COLOR} + onClick={onClick} + /> + ) +} + +GrayLayer.propTypes = { + onClick: PropTypes.func, +} + +export default GrayLayer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/HoverTile.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/HoverTile.js new file mode 100644 index 00000000..20c2c6d1 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/HoverTile.js @@ -0,0 +1,30 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { Rect } from 'react-konva' +import { ROOM_HOVER_INVALID_COLOR, ROOM_HOVER_VALID_COLOR } from '../../../../util/colors' +import { TILE_SIZE_IN_PIXELS } from '../MapConstants' + +function HoverTile({ x, y, isValid, scale = 1, onClick }) { + return ( + <Rect + x={x} + y={y} + scaleX={scale} + scaleY={scale} + width={TILE_SIZE_IN_PIXELS} + height={TILE_SIZE_IN_PIXELS} + fill={isValid ? ROOM_HOVER_VALID_COLOR : ROOM_HOVER_INVALID_COLOR} + onClick={onClick} + /> + ) +} + +HoverTile.propTypes = { + x: PropTypes.number.isRequired, + y: PropTypes.number.isRequired, + isValid: PropTypes.bool.isRequired, + scale: PropTypes.number, + onClick: PropTypes.func.isRequired, +} + +export default HoverTile diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/ImageComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/ImageComponent.js new file mode 100644 index 00000000..7d304b6b --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/ImageComponent.js @@ -0,0 +1,36 @@ +import PropTypes from 'prop-types' +import React, { useEffect, useState } from 'react' +import { Image } from 'react-konva' + +const imageCaches = {} + +function ImageComponent({ src, x, y, width, height, opacity }) { + const [image, setImage] = useState(null) + + useEffect(() => { + if (imageCaches[src]) { + setImage(imageCaches[src]) + return + } + + const image = new window.Image() + image.src = src + image.onload = () => { + setImage(image) + imageCaches[src] = image + } + }, [src]) + + return <Image image={image} x={x} y={y} width={width} height={height} opacity={opacity} /> +} + +ImageComponent.propTypes = { + src: PropTypes.string.isRequired, + x: PropTypes.number.isRequired, + y: PropTypes.number.isRequired, + width: PropTypes.number.isRequired, + height: PropTypes.number.isRequired, + opacity: PropTypes.number.isRequired, +} + +export default ImageComponent diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/RackFillBar.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/RackFillBar.js new file mode 100644 index 00000000..aa284944 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/RackFillBar.js @@ -0,0 +1,68 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { Group, Rect } from 'react-konva' +import { + RACK_ENERGY_BAR_BACKGROUND_COLOR, + RACK_ENERGY_BAR_FILL_COLOR, + RACK_SPACE_BAR_BACKGROUND_COLOR, + RACK_SPACE_BAR_FILL_COLOR, +} from '../../../../util/colors' +import { + OBJECT_BORDER_WIDTH_IN_PIXELS, + OBJECT_MARGIN_IN_PIXELS, + RACK_FILL_ICON_OPACITY, + RACK_FILL_ICON_WIDTH, + TILE_SIZE_IN_PIXELS, +} from '../MapConstants' +import ImageComponent from './ImageComponent' + +function RackFillBar({ positionX, positionY, type, fillFraction }) { + const halfOfObjectBorderWidth = OBJECT_BORDER_WIDTH_IN_PIXELS / 2 + const x = + positionX * TILE_SIZE_IN_PIXELS + + OBJECT_MARGIN_IN_PIXELS + + (type === 'space' ? halfOfObjectBorderWidth : 0.5 * (TILE_SIZE_IN_PIXELS - 2 * OBJECT_MARGIN_IN_PIXELS)) + const startY = positionY * TILE_SIZE_IN_PIXELS + OBJECT_MARGIN_IN_PIXELS + halfOfObjectBorderWidth + const width = 0.5 * (TILE_SIZE_IN_PIXELS - OBJECT_MARGIN_IN_PIXELS * 2) - halfOfObjectBorderWidth + const fullHeight = TILE_SIZE_IN_PIXELS - OBJECT_MARGIN_IN_PIXELS * 2 - OBJECT_BORDER_WIDTH_IN_PIXELS + + const fractionHeight = fillFraction * fullHeight + const fractionY = + (positionY + 1) * TILE_SIZE_IN_PIXELS - OBJECT_MARGIN_IN_PIXELS - halfOfObjectBorderWidth - fractionHeight + + return ( + <Group> + <Rect + x={x} + y={startY} + width={width} + height={fullHeight} + fill={type === 'space' ? RACK_SPACE_BAR_BACKGROUND_COLOR : RACK_ENERGY_BAR_BACKGROUND_COLOR} + /> + <Rect + x={x} + y={fractionY} + width={width} + height={fractionHeight} + fill={type === 'space' ? RACK_SPACE_BAR_FILL_COLOR : RACK_ENERGY_BAR_FILL_COLOR} + /> + <ImageComponent + src={'/img/topology/rack-' + type + '-icon.png'} + x={x + width * 0.5 - RACK_FILL_ICON_WIDTH * 0.5} + y={startY + fullHeight * 0.5 - RACK_FILL_ICON_WIDTH * 0.5} + width={RACK_FILL_ICON_WIDTH} + height={RACK_FILL_ICON_WIDTH} + opacity={RACK_FILL_ICON_OPACITY} + /> + </Group> + ) +} + +RackFillBar.propTypes = { + positionX: PropTypes.number.isRequired, + positionY: PropTypes.number.isRequired, + type: PropTypes.string.isRequired, + fillFraction: PropTypes.number.isRequired, +} + +export default RackFillBar diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/RoomTile.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/RoomTile.js new file mode 100644 index 00000000..e7329dc0 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/RoomTile.js @@ -0,0 +1,24 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { Rect } from 'react-konva' +import { Tile } from '../../../../shapes' +import { TILE_SIZE_IN_PIXELS } from '../MapConstants' + +function RoomTile({ tile, color }) { + return ( + <Rect + x={tile.positionX * TILE_SIZE_IN_PIXELS} + y={tile.positionY * TILE_SIZE_IN_PIXELS} + width={TILE_SIZE_IN_PIXELS} + height={TILE_SIZE_IN_PIXELS} + fill={color} + /> + ) +} + +RoomTile.propTypes = { + tile: Tile, + color: PropTypes.string, +} + +export default RoomTile diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/TileObject.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/TileObject.js new file mode 100644 index 00000000..3211f187 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/TileObject.js @@ -0,0 +1,27 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { Rect } from 'react-konva' +import { OBJECT_BORDER_COLOR } from '../../../../util/colors' +import { OBJECT_BORDER_WIDTH_IN_PIXELS, OBJECT_MARGIN_IN_PIXELS, TILE_SIZE_IN_PIXELS } from '../MapConstants' + +function TileObject({ positionX, positionY, color }) { + return ( + <Rect + x={positionX * TILE_SIZE_IN_PIXELS + OBJECT_MARGIN_IN_PIXELS} + y={positionY * TILE_SIZE_IN_PIXELS + OBJECT_MARGIN_IN_PIXELS} + width={TILE_SIZE_IN_PIXELS - OBJECT_MARGIN_IN_PIXELS * 2} + height={TILE_SIZE_IN_PIXELS - OBJECT_MARGIN_IN_PIXELS * 2} + fill={color} + stroke={OBJECT_BORDER_COLOR} + strokeWidth={OBJECT_BORDER_WIDTH_IN_PIXELS} + /> + ) +} + +TileObject.propTypes = { + positionX: PropTypes.number.isRequired, + positionY: PropTypes.number.isRequired, + color: PropTypes.string.isRequired, +} + +export default TileObject diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/TilePlusIcon.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/TilePlusIcon.js new file mode 100644 index 00000000..186c2b3a --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/TilePlusIcon.js @@ -0,0 +1,44 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { Group, Line } from 'react-konva' +import { TILE_PLUS_COLOR } from '../../../../util/colors' +import { TILE_PLUS_MARGIN_IN_PIXELS, TILE_PLUS_WIDTH_IN_PIXELS, TILE_SIZE_IN_PIXELS } from '../MapConstants' + +function TilePlusIcon({ x, y, scale = 1 }) { + const linePoints = [ + [ + x + 0.5 * TILE_SIZE_IN_PIXELS * scale, + y + TILE_PLUS_MARGIN_IN_PIXELS * scale, + x + 0.5 * TILE_SIZE_IN_PIXELS * scale, + y + TILE_SIZE_IN_PIXELS * scale - TILE_PLUS_MARGIN_IN_PIXELS * scale, + ], + [ + x + TILE_PLUS_MARGIN_IN_PIXELS * scale, + y + 0.5 * TILE_SIZE_IN_PIXELS * scale, + x + TILE_SIZE_IN_PIXELS * scale - TILE_PLUS_MARGIN_IN_PIXELS * scale, + y + 0.5 * TILE_SIZE_IN_PIXELS * scale, + ], + ] + return ( + <Group> + {linePoints.map((points, index) => ( + <Line + key={index} + points={points} + lineCap="round" + stroke={TILE_PLUS_COLOR} + strokeWidth={TILE_PLUS_WIDTH_IN_PIXELS * scale} + listening={false} + /> + ))} + </Group> + ) +} + +TilePlusIcon.propTypes = { + x: PropTypes.number, + y: PropTypes.number, + scale: PropTypes.number, +} + +export default TilePlusIcon diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/WallSegment.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/WallSegment.js new file mode 100644 index 00000000..4f18813e --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/WallSegment.js @@ -0,0 +1,32 @@ +import React from 'react' +import { Line } from 'react-konva' +import { WallSegment as WallSegmentShape } from '../../../../shapes' +import { WALL_COLOR } from '../../../../util/colors' +import { TILE_SIZE_IN_PIXELS, WALL_WIDTH_IN_PIXELS } from '../MapConstants' + +function WallSegment({ wallSegment }) { + let points + if (wallSegment.isHorizontal) { + points = [ + wallSegment.startPosX * TILE_SIZE_IN_PIXELS, + wallSegment.startPosY * TILE_SIZE_IN_PIXELS, + (wallSegment.startPosX + wallSegment.length) * TILE_SIZE_IN_PIXELS, + wallSegment.startPosY * TILE_SIZE_IN_PIXELS, + ] + } else { + points = [ + wallSegment.startPosX * TILE_SIZE_IN_PIXELS, + wallSegment.startPosY * TILE_SIZE_IN_PIXELS, + wallSegment.startPosX * TILE_SIZE_IN_PIXELS, + (wallSegment.startPosY + wallSegment.length) * TILE_SIZE_IN_PIXELS, + ] + } + + return <Line points={points} lineCap="round" stroke={WALL_COLOR} strokeWidth={WALL_WIDTH_IN_PIXELS} /> +} + +WallSegment.propTypes = { + wallSegment: WallSegmentShape, +} + +export default WallSegment diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/groups/GridGroup.js b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/GridGroup.js new file mode 100644 index 00000000..d66a18de --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/GridGroup.js @@ -0,0 +1,36 @@ +import React from 'react' +import { Group, Line } from 'react-konva' +import { GRID_COLOR } from '../../../../util/colors' +import { GRID_LINE_WIDTH_IN_PIXELS, MAP_SIZE, MAP_SIZE_IN_PIXELS, TILE_SIZE_IN_PIXELS } from '../MapConstants' + +const MAP_COORDINATE_ENTRIES = Array.from(new Array(MAP_SIZE), (x, i) => i) +const HORIZONTAL_POINT_PAIRS = MAP_COORDINATE_ENTRIES.map((index) => [ + 0, + index * TILE_SIZE_IN_PIXELS, + MAP_SIZE_IN_PIXELS, + index * TILE_SIZE_IN_PIXELS, +]) +const VERTICAL_POINT_PAIRS = MAP_COORDINATE_ENTRIES.map((index) => [ + index * TILE_SIZE_IN_PIXELS, + 0, + index * TILE_SIZE_IN_PIXELS, + MAP_SIZE_IN_PIXELS, +]) + +function GridGroup() { + return ( + <Group> + {HORIZONTAL_POINT_PAIRS.concat(VERTICAL_POINT_PAIRS).map((points, index) => ( + <Line + key={index} + points={points} + stroke={GRID_COLOR} + strokeWidth={GRID_LINE_WIDTH_IN_PIXELS} + listening={false} + /> + ))} + </Group> + ) +} + +export default GridGroup diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/groups/RackGroup.js b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/RackGroup.js new file mode 100644 index 00000000..46030135 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/RackGroup.js @@ -0,0 +1,25 @@ +import React from 'react' +import { Group } from 'react-konva' +import { Tile } from '../../../../shapes' +import { RACK_BACKGROUND_COLOR } from '../../../../util/colors' +import TileObject from '../elements/TileObject' +import RackSpaceFillContainer from '../RackSpaceFillContainer' +import RackEnergyFillContainer from '../RackEnergyFillContainer' + +function RackGroup({ tile }) { + return ( + <Group> + <TileObject positionX={tile.positionX} positionY={tile.positionY} color={RACK_BACKGROUND_COLOR} /> + <Group> + <RackSpaceFillContainer tileId={tile._id} positionX={tile.positionX} positionY={tile.positionY} /> + <RackEnergyFillContainer tileId={tile._id} positionX={tile.positionX} positionY={tile.positionY} /> + </Group> + </Group> + ) +} + +RackGroup.propTypes = { + tile: Tile, +} + +export default RackGroup diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/groups/RoomGroup.js b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/RoomGroup.js new file mode 100644 index 00000000..a42e7bb7 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/RoomGroup.js @@ -0,0 +1,52 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { Group } from 'react-konva' +import { InteractionLevel, Room } from '../../../../shapes' +import GrayContainer from '../GrayContainer' +import TileContainer from '../TileContainer' +import WallContainer from '../WallContainer' + +function RoomGroup({ room, interactionLevel, currentRoomInConstruction, onClick }) { + if (currentRoomInConstruction === room._id) { + return ( + <Group onClick={onClick}> + {room.tiles.map((tileId) => ( + <TileContainer key={tileId} tileId={tileId} newTile={true} /> + ))} + </Group> + ) + } + + return ( + <Group onClick={onClick}> + {(() => { + if ( + (interactionLevel.mode === 'RACK' || interactionLevel.mode === 'MACHINE') && + interactionLevel.roomId === room._id + ) { + return [ + room.tiles + .filter((tileId) => tileId !== interactionLevel.tileId) + .map((tileId) => <TileContainer key={tileId} tileId={tileId} />), + <GrayContainer key={-1} />, + room.tiles + .filter((tileId) => tileId === interactionLevel.tileId) + .map((tileId) => <TileContainer key={tileId} tileId={tileId} />), + ] + } else { + return room.tiles.map((tileId) => <TileContainer key={tileId} tileId={tileId} />) + } + })()} + <WallContainer roomId={room._id} /> + </Group> + ) +} + +RoomGroup.propTypes = { + room: Room, + interactionLevel: InteractionLevel, + currentRoomInConstruction: PropTypes.string, + onClick: PropTypes.func, +} + +export default RoomGroup diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/groups/TileGroup.js b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/TileGroup.js new file mode 100644 index 00000000..f2084017 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/TileGroup.js @@ -0,0 +1,36 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { Group } from 'react-konva' +import { Tile } from '../../../../shapes' +import { ROOM_DEFAULT_COLOR, ROOM_IN_CONSTRUCTION_COLOR } from '../../../../util/colors' +import RoomTile from '../elements/RoomTile' +import RackContainer from '../RackContainer' + +function TileGroup({ tile, newTile, onClick }) { + let tileObject + if (tile.rack) { + tileObject = <RackContainer tile={tile} /> + } else { + tileObject = null + } + + let color = ROOM_DEFAULT_COLOR + if (newTile) { + color = ROOM_IN_CONSTRUCTION_COLOR + } + + return ( + <Group onClick={() => onClick(tile)}> + <RoomTile tile={tile} color={color} /> + {tileObject} + </Group> + ) +} + +TileGroup.propTypes = { + tile: Tile, + newTile: PropTypes.bool, + onClick: PropTypes.func, +} + +export default TileGroup diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/groups/TopologyGroup.js b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/TopologyGroup.js new file mode 100644 index 00000000..011dcf34 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/TopologyGroup.js @@ -0,0 +1,44 @@ +import React from 'react' +import { Group } from 'react-konva' +import { InteractionLevel, Topology } from '../../../../shapes' +import RoomContainer from '../RoomContainer' +import GrayContainer from '../GrayContainer' + +function TopologyGroup({ topology, interactionLevel }) { + if (!topology) { + return <Group /> + } + + if (interactionLevel.mode === 'BUILDING') { + return ( + <Group> + {topology.rooms.map((roomId) => ( + <RoomContainer key={roomId} roomId={roomId} /> + ))} + </Group> + ) + } + + return ( + <Group> + {topology.rooms + .filter((roomId) => roomId !== interactionLevel.roomId) + .map((roomId) => ( + <RoomContainer key={roomId} roomId={roomId} /> + ))} + {interactionLevel.mode === 'ROOM' ? <GrayContainer /> : null} + {topology.rooms + .filter((roomId) => roomId === interactionLevel.roomId) + .map((roomId) => ( + <RoomContainer key={roomId} roomId={roomId} /> + ))} + </Group> + ) +} + +TopologyGroup.propTypes = { + topology: Topology, + interactionLevel: InteractionLevel, +} + +export default TopologyGroup diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/groups/WallGroup.js b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/WallGroup.js new file mode 100644 index 00000000..6cbd1cd0 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/WallGroup.js @@ -0,0 +1,22 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { Group } from 'react-konva' +import { Tile } from '../../../../shapes' +import { deriveWallLocations } from '../../../../util/tile-calculations' +import WallSegment from '../elements/WallSegment' + +function WallGroup({ tiles }) { + return ( + <Group> + {deriveWallLocations(tiles).map((wallSegment, index) => ( + <WallSegment key={index} wallSegment={wallSegment} /> + ))} + </Group> + ) +} + +WallGroup.propTypes = { + tiles: PropTypes.arrayOf(Tile).isRequired, +} + +export default WallGroup diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/layers/HoverLayerComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/map/layers/HoverLayerComponent.js new file mode 100644 index 00000000..2b1060c0 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/layers/HoverLayerComponent.js @@ -0,0 +1,55 @@ +import PropTypes from 'prop-types' +import React, { useMemo, useState } from 'react' +import { Layer } from 'react-konva/lib/ReactKonva' +import HoverTile from '../elements/HoverTile' +import { TILE_SIZE_IN_PIXELS } from '../MapConstants' +import { useEffectRef } from '../../../../util/effect-ref' + +function HoverLayerComponent({ isEnabled, isValid, onClick, children }) { + const [[mouseWorldX, mouseWorldY], setPos] = useState([0, 0]) + + const layerRef = useEffectRef((layer) => { + if (!layer) { + return + } + + const stage = layer.getStage() + + // Transform used to convert mouse coordinates to world coordinates + const transform = stage.getAbsoluteTransform().copy() + transform.invert() + + stage.on('mousemove.hover', () => { + const { x, y } = transform.point(stage.getPointerPosition()) + setPos([x, y]) + }) + return () => stage.off('mousemove.hover') + }) + + const gridX = Math.floor(mouseWorldX / TILE_SIZE_IN_PIXELS) + const gridY = Math.floor(mouseWorldY / TILE_SIZE_IN_PIXELS) + const valid = useMemo(() => isEnabled && isValid(gridX, gridY), [isEnabled, isValid, gridX, gridY]) + + if (!isEnabled) { + return <Layer /> + } + + const x = gridX * TILE_SIZE_IN_PIXELS + const y = gridY * TILE_SIZE_IN_PIXELS + + return ( + <Layer opacity={0.6} ref={layerRef}> + <HoverTile x={x} y={y} isValid={valid} onClick={() => (valid ? onClick(gridX, gridY) : undefined)} /> + {children ? React.cloneElement(children, { x, y, scale: 1 }) : undefined} + </Layer> + ) +} + +HoverLayerComponent.propTypes = { + isEnabled: PropTypes.bool.isRequired, + isValid: PropTypes.func.isRequired, + onClick: PropTypes.func.isRequired, + children: PropTypes.node, +} + +export default HoverLayerComponent diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/layers/MapLayer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/layers/MapLayer.js new file mode 100644 index 00000000..c902532b --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/layers/MapLayer.js @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React from 'react' +import { Group, Layer } from 'react-konva' +import Backdrop from '../elements/Backdrop' +import TopologyContainer from '../TopologyContainer' +import GridGroup from '../groups/GridGroup' + +function MapLayer() { + return ( + <Layer> + <Group> + <Backdrop /> + <TopologyContainer /> + <GridGroup /> + </Group> + </Layer> + ) +} + +export default MapLayer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/layers/ObjectHoverLayer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/layers/ObjectHoverLayer.js new file mode 100644 index 00000000..1f00de36 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/layers/ObjectHoverLayer.js @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { addRackToTile } from '../../../../redux/actions/topology/room' +import { findTileWithPosition } from '../../../../util/tile-calculations' +import HoverLayerComponent from './HoverLayerComponent' +import TilePlusIcon from '../elements/TilePlusIcon' + +function ObjectHoverLayer() { + const isEnabled = useSelector((state) => state.construction.inRackConstructionMode) + const isValid = useSelector((state) => (x, y) => { + if (state.interactionLevel.mode !== 'ROOM') { + return false + } + + const currentRoom = state.topology.rooms[state.interactionLevel.roomId] + const tiles = currentRoom.tiles.map((tileId) => state.topology.tiles[tileId]) + const tile = findTileWithPosition(tiles, x, y) + + return !(tile === null || tile.rack) + }) + + const dispatch = useDispatch() + const onClick = (x, y) => dispatch(addRackToTile(x, y)) + return ( + <HoverLayerComponent onClick={onClick} isEnabled={isEnabled} isValid={isValid}> + <TilePlusIcon /> + </HoverLayerComponent> + ) +} + +export default ObjectHoverLayer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/layers/RoomHoverLayer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/layers/RoomHoverLayer.js new file mode 100644 index 00000000..5e351691 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/layers/RoomHoverLayer.js @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { toggleTileAtLocation } from '../../../../redux/actions/topology/building' +import { + deriveValidNextTilePositions, + findPositionInPositions, + findPositionInRooms, +} from '../../../../util/tile-calculations' +import HoverLayerComponent from './HoverLayerComponent' + +function RoomHoverLayer() { + const dispatch = useDispatch() + const onClick = (x, y) => dispatch(toggleTileAtLocation(x, y)) + const isEnabled = useSelector((state) => state.construction.currentRoomInConstruction !== '-1') + const isValid = useSelector((state) => (x, y) => { + const newRoom = { ...state.topology.rooms[state.construction.currentRoomInConstruction] } + const oldRooms = Object.keys(state.topology.rooms) + .map((id) => ({ ...state.topology.rooms[id] })) + .filter( + (room) => + state.topology.root.rooms.indexOf(room._id) !== -1 && + room._id !== state.construction.currentRoomInConstruction + ) + + ;[...oldRooms, newRoom].forEach((room) => { + room.tiles = room.tiles.map((tileId) => state.topology.tiles[tileId]) + }) + if (newRoom.tiles.length === 0) { + return findPositionInRooms(oldRooms, x, y) === -1 + } + + const validNextPositions = deriveValidNextTilePositions(oldRooms, newRoom.tiles) + return findPositionInPositions(validNextPositions, x, y) !== -1 + }) + + return <HoverLayerComponent onClick={onClick} isEnabled={isEnabled} isValid={isValid} /> +} + +export default RoomHoverLayer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/NameComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/NameComponent.js new file mode 100644 index 00000000..ececd07b --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/NameComponent.js @@ -0,0 +1,69 @@ +import PropTypes from 'prop-types' +import React, { useRef, useState } from 'react' +import { Button, TextInput } from '@patternfly/react-core' +import { PencilAltIcon, CheckIcon, TimesIcon } from '@patternfly/react-icons' + +function NameComponent({ name, onEdit }) { + const [isEditing, setEditing] = useState(false) + const nameInput = useRef(null) + + const onCancel = () => { + nameInput.current.value = name + setEditing(false) + } + + const onSubmit = (event) => { + if (event) { + event.preventDefault() + } + + const name = nameInput.current.value + if (name) { + onEdit(name) + } + + setEditing(false) + } + + return ( + <form + className={`pf-c-inline-edit ${isEditing ? 'pf-m-inline-editable' : ''} pf-u-display-inline-block`} + onSubmit={onSubmit} + > + <div className="pf-c-inline-edit__group"> + <div className="pf-c-inline-edit__value" id="single-inline-edit-example-label"> + {name} + </div> + <div className="pf-c-inline-edit__action pf-m-enable-editable"> + <Button className="pf-u-py-0" variant="plain" aria-label="Edit" onClick={() => setEditing(true)}> + <PencilAltIcon /> + </Button> + </div> + </div> + <div className="pf-c-inline-edit__group"> + <div className="pf-c-inline-edit__input"> + <TextInput type="text" defaultValue={name} ref={nameInput} aria-label="Editable text input" /> + </div> + <div className="pf-c-inline-edit__group pf-m-action-group pf-m-icon-group"> + <div className="pf-c-inline-edit__action pf-m-valid"> + <Button className="pf-u-py-0" variant="plain" aria-label="Save edits" onClick={onSubmit}> + <CheckIcon /> + </Button> + </div> + <div className="pf-c-inline-edit__action"> + <Button className="pf-u-py-0" variant="plain" aria-label="Cancel edits" onClick={onCancel}> + <TimesIcon /> + </Button> + </div> + </div> + </div> + </form> + ) +} + +NameComponent.propTypes = { + name: PropTypes.string, + onEdit: PropTypes.func, +} + +export default NameComponent diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/TopologySidebar.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/TopologySidebar.js new file mode 100644 index 00000000..5d9340b2 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/TopologySidebar.js @@ -0,0 +1,83 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { InteractionLevel } from '../../../shapes' +import BuildingSidebar from './building/BuildingSidebar' +import { + Button, + DrawerActions, + DrawerCloseButton, + DrawerHead, + DrawerPanelBody, + DrawerPanelContent, + Flex, + Title, +} from '@patternfly/react-core' +import { AngleLeftIcon } from '@patternfly/react-icons' +import { useDispatch } from 'react-redux' +import { backButton } from './TopologySidebar.module.scss' +import RoomSidebar from './room/RoomSidebar' +import RackSidebar from './rack/RackSidebar' +import MachineSidebar from './machine/MachineSidebar' +import { goDownOneInteractionLevel } from '../../../redux/actions/interaction-level' + +const name = { + BUILDING: 'Building', + ROOM: 'Room', + RACK: 'Rack', + MACHINE: 'Machine', +} + +function TopologySidebar({ interactionLevel, onClose }) { + let sidebarContent + + switch (interactionLevel.mode) { + case 'BUILDING': + sidebarContent = <BuildingSidebar /> + break + case 'ROOM': + sidebarContent = <RoomSidebar roomId={interactionLevel.roomId} /> + break + case 'RACK': + sidebarContent = <RackSidebar tileId={interactionLevel.tileId} /> + break + case 'MACHINE': + sidebarContent = <MachineSidebar tileId={interactionLevel.tileId} position={interactionLevel.position} /> + break + default: + sidebarContent = 'Missing Content' + } + + const dispatch = useDispatch() + const onClick = () => dispatch(goDownOneInteractionLevel()) + + return ( + <DrawerPanelContent isResizable defaultSize="450px" minSize="400px"> + <DrawerHead> + <Flex> + <Button + variant="tertiary" + isSmall + className={backButton} + onClick={interactionLevel.mode === 'BUILDING' ? onClose : onClick} + > + <AngleLeftIcon /> + </Button> + <Title className="pf-u-align-self-center" headingLevel="h1"> + {name[interactionLevel.mode]} + </Title> + </Flex> + <DrawerActions> + <DrawerCloseButton onClose={onClose} /> + </DrawerActions> + </DrawerHead> + <DrawerPanelBody>{sidebarContent}</DrawerPanelBody> + </DrawerPanelContent> + ) +} + +TopologySidebar.propTypes = { + interactionLevel: InteractionLevel, + onClose: PropTypes.func, +} + +export default TopologySidebar diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/TopologySidebar.module.scss b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/TopologySidebar.module.scss new file mode 100644 index 00000000..45dc98da --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/TopologySidebar.module.scss @@ -0,0 +1,37 @@ +/*! + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +.backButton { + &:global(.pf-c-button) { + align-self: center; + --pf-c-button--after--BorderColor: var(--pf-global--BorderColor--light-100); + color: var(--pf-global--Color--400); + + --pf-c-button--PaddingRight: var(--pf-global--spacer--sm); + --pf-c-button--PaddingLeft: var(--pf-global--spacer--sm); + + &:hover, + &:focus { + --pf-c-button--after--BorderColor: var(--pf-global--BorderColor--100); + } + } +} diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/BuildingSidebar.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/BuildingSidebar.js new file mode 100644 index 00000000..5fcd46be --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/BuildingSidebar.js @@ -0,0 +1,8 @@ +import React from 'react' +import NewRoomConstructionContainer from './NewRoomConstructionContainer' + +function BuildingSidebar() { + return <NewRoomConstructionContainer /> +} + +export default BuildingSidebar diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/NewRoomConstructionComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/NewRoomConstructionComponent.js new file mode 100644 index 00000000..9fc85d0c --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/NewRoomConstructionComponent.js @@ -0,0 +1,46 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { Button, Toolbar, ToolbarContent, ToolbarGroup, ToolbarItem } from '@patternfly/react-core' +import PlusIcon from '@patternfly/react-icons/dist/js/icons/plus-icon' +import CheckIcon from '@patternfly/react-icons/dist/js/icons/check-icon' + +function NewRoomConstructionComponent({ onStart, onFinish, onCancel, currentRoomInConstruction }) { + if (currentRoomInConstruction === '-1') { + return ( + <Button isBlock icon={<PlusIcon />} onClick={onStart}> + Construct a new room + </Button> + ) + } + return ( + <Toolbar + inset={{ + default: 'insetNone', + }} + > + <ToolbarContent> + <ToolbarGroup> + <ToolbarItem> + <Button icon={<CheckIcon />} onClick={onFinish}> + Finalize new room + </Button> + </ToolbarItem> + <ToolbarItem widths={{ default: '100%' }}> + <Button isBlock variant="secondary" onClick={onCancel}> + Cancel + </Button> + </ToolbarItem> + </ToolbarGroup> + </ToolbarContent> + </Toolbar> + ) +} + +NewRoomConstructionComponent.propTypes = { + onStart: PropTypes.func, + onFinish: PropTypes.func, + onCancel: PropTypes.func, + currentRoomInConstruction: PropTypes.string, +} + +export default NewRoomConstructionComponent diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/NewRoomConstructionContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/NewRoomConstructionContainer.js new file mode 100644 index 00000000..c149b224 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/NewRoomConstructionContainer.js @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { + cancelNewRoomConstruction, + finishNewRoomConstruction, + startNewRoomConstruction, +} from '../../../../redux/actions/topology/building' +import NewRoomConstructionComponent from './NewRoomConstructionComponent' + +function NewRoomConstructionButton() { + const currentRoomInConstruction = useSelector((state) => state.construction.currentRoomInConstruction) + const dispatch = useDispatch() + + return ( + <NewRoomConstructionComponent + onStart={() => dispatch(startNewRoomConstruction())} + onFinish={() => dispatch(finishNewRoomConstruction())} + onCancel={() => dispatch(cancelNewRoomConstruction())} + currentRoomInConstruction={currentRoomInConstruction} + /> + ) +} + +export default NewRoomConstructionButton diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/DeleteMachine.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/DeleteMachine.js new file mode 100644 index 00000000..a4b9457b --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/DeleteMachine.js @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import React, { useState } from 'react' +import { useDispatch } from 'react-redux' +import { Button } from '@patternfly/react-core' +import { TrashIcon } from '@patternfly/react-icons' +import ConfirmationModal from '../../../util/modals/ConfirmationModal' +import { deleteMachine } from '../../../../redux/actions/topology/machine' + +function DeleteMachine({ machineId }) { + const dispatch = useDispatch() + const [isVisible, setVisible] = useState(false) + const callback = (isConfirmed) => { + if (isConfirmed) { + dispatch(deleteMachine(machineId)) + } + setVisible(false) + } + return ( + <> + <Button variant="danger" icon={<TrashIcon />} isBlock onClick={() => setVisible(true)}> + Delete this machine + </Button> + <ConfirmationModal + title="Delete this machine" + message="Are you sure you want to delete this machine?" + isOpen={isVisible} + callback={callback} + /> + </> + ) +} + +DeleteMachine.propTypes = { + machineId: PropTypes.string.isRequired, +} + +export default DeleteMachine diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/MachineSidebar.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/MachineSidebar.js new file mode 100644 index 00000000..9268f615 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/MachineSidebar.js @@ -0,0 +1,49 @@ +import PropTypes from 'prop-types' +import React from 'react' +import UnitTabsComponent from './UnitTabsComponent' +import DeleteMachine from './DeleteMachine' +import { + TextContent, + TextList, + TextListItem, + TextListItemVariants, + TextListVariants, + Title, +} from '@patternfly/react-core' +import { useSelector } from 'react-redux' + +function MachineSidebar({ tileId, position }) { + const machine = useSelector(({ topology }) => { + const rack = topology.racks[topology.tiles[tileId].rack] + return topology.machines[rack.machines[position - 1]] + }) + const machineId = machine._id + return ( + <div> + <TextContent> + <Title headingLevel="h2">Details</Title> + <TextList component={TextListVariants.dl}> + <TextListItem component={TextListItemVariants.dt}>Name</TextListItem> + <TextListItem component={TextListItemVariants.dd}> + Machine at position {machine.position} + </TextListItem> + </TextList> + + <Title headingLevel="h2">Actions</Title> + <DeleteMachine machineId={machineId} /> + + <Title headingLevel="h2">Units</Title> + </TextContent> + <div className="pf-u-h-100"> + <UnitTabsComponent machineId={machineId} /> + </div> + </div> + ) +} + +MachineSidebar.propTypes = { + tileId: PropTypes.string.isRequired, + position: PropTypes.number.isRequired, +} + +export default MachineSidebar diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddComponent.js new file mode 100644 index 00000000..88591208 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddComponent.js @@ -0,0 +1,42 @@ +import PropTypes from 'prop-types' +import React, { useState } from 'react' +import { Button, InputGroup, Select, SelectOption, SelectVariant } from '@patternfly/react-core' +import PlusIcon from '@patternfly/react-icons/dist/js/icons/plus-icon' + +function UnitAddComponent({ units, onAdd }) { + const [isOpen, setOpen] = useState(false) + const [selected, setSelected] = useState(null) + + return ( + <InputGroup> + <Select + variant={SelectVariant.single} + placeholderText="Select a unit" + aria-label="Select Unit" + onToggle={() => setOpen(!isOpen)} + isOpen={isOpen} + onSelect={(_, selection) => { + setSelected(selection) + setOpen(false) + }} + selections={selected} + > + {units.map((unit) => ( + <SelectOption value={unit._id} key={unit._id}> + {unit.name} + </SelectOption> + ))} + </Select> + <Button icon={<PlusIcon />} variant="control" onClick={() => onAdd(selected)}> + Add + </Button> + </InputGroup> + ) +} + +UnitAddComponent.propTypes = { + units: PropTypes.array.isRequired, + onAdd: PropTypes.func.isRequired, +} + +export default UnitAddComponent diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddContainer.js new file mode 100644 index 00000000..6b136120 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddContainer.js @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import React from 'react' +import { useDispatch, useSelector } from 'react-redux' +import UnitAddComponent from './UnitAddComponent' +import { addUnit } from '../../../../redux/actions/topology/machine' + +function UnitAddContainer({ machineId, unitType }) { + const units = useSelector((state) => Object.values(state.topology[unitType])) + const dispatch = useDispatch() + + const onAdd = (id) => dispatch(addUnit(machineId, unitType, id)) + + return <UnitAddComponent onAdd={onAdd} units={units} /> +} + +UnitAddContainer.propTypes = { + machineId: PropTypes.string.isRequired, + unitType: PropTypes.string.isRequired, +} + +export default UnitAddContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListComponent.js new file mode 100644 index 00000000..daa3e7a7 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListComponent.js @@ -0,0 +1,112 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { + Button, + DataList, + DataListAction, + DataListCell, + DataListItem, + DataListItemCells, + DataListItemRow, + DescriptionList, + DescriptionListDescription, + DescriptionListGroup, + DescriptionListTerm, + EmptyState, + EmptyStateBody, + EmptyStateIcon, + Popover, + Title, +} from '@patternfly/react-core' +import { CubesIcon, InfoIcon, TrashIcon } from '@patternfly/react-icons' +import { ProcessingUnit, StorageUnit } from '../../../../shapes' + +function UnitInfo({ unit, unitType }) { + if (unitType === 'cpu' || unitType === 'gpu') { + return ( + <DescriptionList> + <DescriptionListGroup> + <DescriptionListTerm>Clock Frequency</DescriptionListTerm> + <DescriptionListDescription>{unit.clockRateMhz} MHz</DescriptionListDescription> + </DescriptionListGroup> + <DescriptionListGroup> + <DescriptionListTerm>Number of Cores</DescriptionListTerm> + <DescriptionListDescription>{unit.numberOfCores}</DescriptionListDescription> + </DescriptionListGroup> + <DescriptionListGroup> + <DescriptionListTerm>Energy Consumption</DescriptionListTerm> + <DescriptionListDescription>{unit.energyConsumptionW} W</DescriptionListDescription> + </DescriptionListGroup> + </DescriptionList> + ) + } + + return ( + <DescriptionList> + <DescriptionListGroup> + <DescriptionListTerm>Speed</DescriptionListTerm> + <DescriptionListDescription>{unit.speedMbPerS} Mb/s</DescriptionListDescription> + </DescriptionListGroup> + <DescriptionListGroup> + <DescriptionListTerm>Capacity</DescriptionListTerm> + <DescriptionListDescription>{unit.sizeMb} MB</DescriptionListDescription> + </DescriptionListGroup> + <DescriptionListGroup> + <DescriptionListTerm>Energy Consumption</DescriptionListTerm> + <DescriptionListDescription>{unit.energyConsumptionW} W</DescriptionListDescription> + </DescriptionListGroup> + </DescriptionList> + ) +} + +UnitInfo.propTypes = { + unitType: PropTypes.string.isRequired, + unit: PropTypes.oneOfType([ProcessingUnit, StorageUnit]).isRequired, +} + +function UnitListComponent({ unitType, units, onDelete }) { + if (units.length === 0) { + return ( + <EmptyState> + <EmptyStateIcon icon={CubesIcon} /> + <Title headingLevel="h5" size="lg"> + No units found + </Title> + <EmptyStateBody>You have not configured any units yet. Add some with the menu above!</EmptyStateBody> + </EmptyState> + ) + } + + return ( + <DataList aria-label="Machine Units" isCompact> + {units.map((unit, index) => ( + <DataListItem key={index}> + <DataListItemRow> + <DataListItemCells dataListCells={[<DataListCell key="unit">{unit.name}</DataListCell>]} /> + <DataListAction id="goto" aria-label="Goto Machine" aria-labelledby="goto"> + <Popover + headerContent="Unit Information" + bodyContent={<UnitInfo unitType={unitType} unit={unit} />} + > + <Button isSmall variant="plain" className="pf-u-p-0"> + <InfoIcon /> + </Button> + </Popover> + <Button isSmall variant="plain" className="pf-u-p-0" onClick={() => onDelete(units[index])}> + <TrashIcon /> + </Button> + </DataListAction> + </DataListItemRow> + </DataListItem> + ))} + </DataList> + ) +} + +UnitListComponent.propTypes = { + unitType: PropTypes.string.isRequired, + units: PropTypes.arrayOf(PropTypes.oneOfType([ProcessingUnit, StorageUnit])).isRequired, + onDelete: PropTypes.func, +} + +export default UnitListComponent diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListContainer.js new file mode 100644 index 00000000..6dcc414f --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListContainer.js @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import React from 'react' +import { useDispatch, useSelector } from 'react-redux' +import UnitListComponent from './UnitListComponent' +import { deleteUnit } from '../../../../redux/actions/topology/machine' + +function UnitListContainer({ machineId, unitType }) { + const dispatch = useDispatch() + const units = useSelector((state) => { + const machine = state.topology.machines[machineId] + return machine[unitType].map((id) => state.topology[unitType][id]) + }) + + const onDelete = (unit) => dispatch(deleteUnit(machineId, unitType, unit._id)) + + return <UnitListComponent units={units} unitType={unitType} onDelete={onDelete} /> +} + +UnitListContainer.propTypes = { + machineId: PropTypes.string.isRequired, + unitType: PropTypes.string.isRequired, +} + +export default UnitListContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitTabsComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitTabsComponent.js new file mode 100644 index 00000000..b800e9d4 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitTabsComponent.js @@ -0,0 +1,36 @@ +import PropTypes from 'prop-types' +import React, { useState } from 'react' +import { Tab, Tabs, TabTitleText } from '@patternfly/react-core' +import UnitAddContainer from './UnitAddContainer' +import UnitListContainer from './UnitListContainer' + +function UnitTabsComponent({ machineId }) { + const [activeTab, setActiveTab] = useState('cpu-units') + + return ( + <Tabs activeKey={activeTab} onSelect={(_, tab) => setActiveTab(tab)}> + <Tab eventKey="cpu-units" title={<TabTitleText>CPU</TabTitleText>}> + <UnitAddContainer machineId={machineId} unitType="cpus" /> + <UnitListContainer machineId={machineId} unitType="cpus" /> + </Tab> + <Tab eventKey="gpu-units" title={<TabTitleText>GPU</TabTitleText>}> + <UnitAddContainer machineId={machineId} unitType="gpus" /> + <UnitListContainer machineId={machineId} unitType="gpus" /> + </Tab> + <Tab eventKey="memory-units" title={<TabTitleText>Memory</TabTitleText>}> + <UnitAddContainer machineId={machineId} unitType="memories" /> + <UnitListContainer machineId={machineId} unitType="memories" /> + </Tab> + <Tab eventKey="storage-units" title={<TabTitleText>Storage</TabTitleText>}> + <UnitAddContainer machineId={machineId} unitType="storages" /> + <UnitListContainer machineId={machineId} unitType="storages" /> + </Tab> + </Tabs> + ) +} + +UnitTabsComponent.propTypes = { + machineId: PropTypes.string.isRequired, +} + +export default UnitTabsComponent diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/AddPrefab.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/AddPrefab.js new file mode 100644 index 00000000..e944c2e8 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/AddPrefab.js @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import React from 'react' +import { useDispatch } from 'react-redux' +import { Button } from '@patternfly/react-core' +import { SaveIcon } from '@patternfly/react-icons' +import { addPrefab } from '../../../../api/prefabs' + +function AddPrefab({ tileId }) { + const dispatch = useDispatch() + const onClick = () => dispatch(addPrefab('name', tileId)) + return ( + <Button variant="primary" icon={<SaveIcon />} isBlock onClick={onClick} className="pf-u-mb-sm"> + Save this rack to a prefab + </Button> + ) +} + +AddPrefab.propTypes = { + tileId: PropTypes.string.isRequired, +} + +export default AddPrefab diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/DeleteRackContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/DeleteRackContainer.js new file mode 100644 index 00000000..0583a7a4 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/DeleteRackContainer.js @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import React, { useState } from 'react' +import { useDispatch, useSelector } from 'react-redux' +import TrashIcon from '@patternfly/react-icons/dist/js/icons/trash-icon' +import { Button } from '@patternfly/react-core' +import ConfirmationModal from '../../../util/modals/ConfirmationModal' +import { deleteRack } from '../../../../redux/actions/topology/rack' + +function DeleteRackContainer({ tileId }) { + const dispatch = useDispatch() + const [isVisible, setVisible] = useState(false) + const rackId = useSelector((state) => state.topology.tiles[tileId].rack) + const callback = (isConfirmed) => { + if (isConfirmed) { + dispatch(deleteRack(tileId, rackId)) + } + setVisible(false) + } + return ( + <> + <Button variant="danger" icon={<TrashIcon />} isBlock onClick={() => setVisible(true)}> + Delete this rack + </Button> + <ConfirmationModal + title="Delete this rack" + message="Are you sure you want to delete this rack?" + isOpen={isVisible} + callback={callback} + /> + </> + ) +} + +DeleteRackContainer.propTypes = { + tileId: PropTypes.string.isRequired, +} + +export default DeleteRackContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineComponent.js new file mode 100644 index 00000000..921622d6 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineComponent.js @@ -0,0 +1,46 @@ +import PropTypes from 'prop-types' +import React from 'react' +import Image from 'next/image' +import { Flex, Label } from '@patternfly/react-core' +import { Machine } from '../../../../shapes' + +const UnitIcon = ({ id, type }) => ( + <Image + src={'/img/topology/' + id + '-icon.png'} + alt={'Machine contains ' + type + ' units'} + layout="intrinsic" + height={24} + width={24} + /> +) + +UnitIcon.propTypes = { + id: PropTypes.string, + type: PropTypes.string, +} + +function MachineComponent({ machine, onClick }) { + const hasNoUnits = + machine.cpus.length + machine.gpus.length + machine.memories.length + machine.storages.length === 0 + + return ( + <Flex onClick={() => onClick()}> + {machine.cpus.length > 0 ? <UnitIcon id="cpu" type="CPU" /> : undefined} + {machine.gpus.length > 0 ? <UnitIcon id="gpu" type="GPU" /> : undefined} + {machine.memories.length > 0 ? <UnitIcon id="memory" type="memory" /> : undefined} + {machine.storages.length > 0 ? <UnitIcon id="storage" type="storage" /> : undefined} + {hasNoUnits ? ( + <Label variant="outline" color="orange"> + Machine with no units + </Label> + ) : undefined} + </Flex> + ) +} + +MachineComponent.propTypes = { + machine: Machine.isRequired, + onClick: PropTypes.func, +} + +export default MachineComponent diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListComponent.js new file mode 100644 index 00000000..de7a2140 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListComponent.js @@ -0,0 +1,73 @@ +import PropTypes from 'prop-types' +import React from 'react' +import MachineComponent from './MachineComponent' +import { + Badge, + Button, + DataList, + DataListAction, + DataListCell, + DataListItem, + DataListItemCells, + DataListItemRow, +} from '@patternfly/react-core' +import { AngleRightIcon, PlusIcon } from '@patternfly/react-icons' +import { Machine } from '../../../../shapes' + +function MachineListComponent({ machines = [], onSelect, onAdd }) { + return ( + <DataList aria-label="Rack Units"> + {machines.map((machine, index) => + machine ? ( + <DataListItem key={index} onClick={() => onSelect(index + 1)}> + <DataListItemRow> + <DataListItemCells + dataListCells={[ + <DataListCell isIcon key="icon"> + <Badge isRead>{machines.length - index}U</Badge> + </DataListCell>, + <DataListCell key="primary content"> + <MachineComponent onClick={() => onSelect(index + 1)} machine={machine} /> + </DataListCell>, + ]} + /> + <DataListAction id="goto" aria-label="Goto Machine" aria-labelledby="goto"> + <Button isSmall variant="plain" className="pf-u-p-0"> + <AngleRightIcon /> + </Button> + </DataListAction> + </DataListItemRow> + </DataListItem> + ) : ( + <DataListItem key={index}> + <DataListItemRow> + <DataListItemCells + dataListCells={[ + <DataListCell isIcon key="icon"> + <Badge isRead>{machines.length - index}U</Badge> + </DataListCell>, + <DataListCell key="add" className="text-secondary"> + Empty Slot + </DataListCell>, + ]} + /> + <DataListAction id="add" aria-label="Add Machine" aria-labelledby="add"> + <Button isSmall variant="plain" className="pf-u-p-0" onClick={() => onAdd(index + 1)}> + <PlusIcon /> + </Button> + </DataListAction> + </DataListItemRow> + </DataListItem> + ) + )} + </DataList> + ) +} + +MachineListComponent.propTypes = { + machines: PropTypes.arrayOf(Machine), + onSelect: PropTypes.func.isRequired, + onAdd: PropTypes.func.isRequired, +} + +export default MachineListComponent diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListContainer.js new file mode 100644 index 00000000..619bb4e2 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListContainer.js @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import React, { useMemo } from 'react' +import { useDispatch, useSelector } from 'react-redux' +import MachineListComponent from './MachineListComponent' +import { goFromRackToMachine } from '../../../../redux/actions/interaction-level' +import { addMachine } from '../../../../redux/actions/topology/rack' + +function MachineListContainer({ tileId, ...props }) { + const rack = useSelector((state) => state.topology.racks[state.topology.tiles[tileId].rack]) + const machines = useSelector((state) => rack.machines.map((id) => state.topology.machines[id])) + const machinesNull = useMemo(() => { + const res = Array(rack.capacity).fill(null) + for (const machine of machines) { + res[machine.position - 1] = machine + } + return res + }, [rack, machines]) + const dispatch = useDispatch() + + return ( + <MachineListComponent + {...props} + machines={machinesNull} + onAdd={(index) => dispatch(addMachine(rack._id, index))} + onSelect={(index) => dispatch(goFromRackToMachine(index))} + /> + ) +} + +MachineListContainer.propTypes = { + tileId: PropTypes.string.isRequired, +} + +export default MachineListContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackNameContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackNameContainer.js new file mode 100644 index 00000000..30f38cce --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackNameContainer.js @@ -0,0 +1,22 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { useDispatch, useSelector } from 'react-redux' +import NameComponent from '../NameComponent' +import { editRackName } from '../../../../redux/actions/topology/rack' + +const RackNameContainer = ({ tileId }) => { + const { name: rackName, _id } = useSelector((state) => state.topology.racks[state.topology.tiles[tileId].rack]) + const dispatch = useDispatch() + const callback = (name) => { + if (name) { + dispatch(editRackName(_id, name)) + } + } + return <NameComponent name={rackName} onEdit={callback} /> +} + +RackNameContainer.propTypes = { + tileId: PropTypes.string.isRequired, +} + +export default RackNameContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.js new file mode 100644 index 00000000..8f6ff135 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.js @@ -0,0 +1,58 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { machineListContainer, sidebarContainer } from './RackSidebar.module.scss' +import RackNameContainer from './RackNameContainer' +import AddPrefab from './AddPrefab' +import DeleteRackContainer from './DeleteRackContainer' +import MachineListContainer from './MachineListContainer' +import { + Skeleton, + TextContent, + TextList, + TextListItem, + TextListItemVariants, + TextListVariants, + Title, +} from '@patternfly/react-core' +import { useSelector } from 'react-redux' + +function RackSidebar({ tileId }) { + const rack = useSelector((state) => state.topology.racks[state.topology.tiles[tileId].rack]) + + return ( + <div className={sidebarContainer}> + <TextContent> + <Title headingLevel="h2">Details</Title> + <TextList component={TextListVariants.dl}> + <TextListItem + component={TextListItemVariants.dt} + className="pf-u-display-inline-flex pf-u-align-items-center" + > + Name + </TextListItem> + <TextListItem component={TextListItemVariants.dd}> + <RackNameContainer tileId={tileId} /> + </TextListItem> + <TextListItem component={TextListItemVariants.dt}>Capacity</TextListItem> + <TextListItem component={TextListItemVariants.dd}> + {rack?.capacity ?? <Skeleton screenreaderText="Loading rack" />} + </TextListItem> + </TextList> + <Title headingLevel="h2">Actions</Title> + <AddPrefab tileId={tileId} /> + <DeleteRackContainer tileId={tileId} /> + + <Title headingLevel="h2">Slots</Title> + </TextContent> + <div className={machineListContainer}> + <MachineListContainer tileId={tileId} /> + </div> + </div> + ) +} + +RackSidebar.propTypes = { + tileId: PropTypes.string.isRequired, +} + +export default RackSidebar diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.module.scss b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.module.scss new file mode 100644 index 00000000..6f258aec --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.module.scss @@ -0,0 +1,12 @@ +.sidebarContainer { + display: flex; + height: 100%; + max-height: 100%; + flex-direction: column; +} + +.machineListContainer { + flex: 1; + overflow-y: scroll; + margin-top: 10px; +} diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/DeleteRoomContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/DeleteRoomContainer.js new file mode 100644 index 00000000..29b8f78a --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/DeleteRoomContainer.js @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import React, { useState } from 'react' +import { useDispatch } from 'react-redux' +import ConfirmationModal from '../../../util/modals/ConfirmationModal' +import { deleteRoom } from '../../../../redux/actions/topology/room' +import TrashIcon from '@patternfly/react-icons/dist/js/icons/trash-icon' +import { Button } from '@patternfly/react-core' + +function DeleteRoomContainer({ roomId }) { + const dispatch = useDispatch() + const [isVisible, setVisible] = useState(false) + const callback = (isConfirmed) => { + if (isConfirmed) { + dispatch(deleteRoom(roomId)) + } + setVisible(false) + } + return ( + <> + <Button variant="danger" icon={<TrashIcon />} isBlock onClick={() => setVisible(true)}> + Delete this room + </Button> + <ConfirmationModal + title="Delete this room" + message="Are you sure you want to delete this room?" + isOpen={isVisible} + callback={callback} + /> + </> + ) +} + +DeleteRoomContainer.propTypes = { + roomId: PropTypes.string.isRequired, +} + +export default DeleteRoomContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/EditRoomContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/EditRoomContainer.js new file mode 100644 index 00000000..7a278cd6 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/EditRoomContainer.js @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import React from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { finishRoomEdit, startRoomEdit } from '../../../../redux/actions/topology/building' +import CheckIcon from '@patternfly/react-icons/dist/js/icons/check-icon' +import PencilAltIcon from '@patternfly/react-icons/dist/js/icons/pencil-alt-icon' +import { Button } from '@patternfly/react-core' + +function EditRoomContainer({ roomId }) { + const isEditing = useSelector((state) => state.construction.currentRoomInConstruction !== '-1') + const isInRackConstructionMode = useSelector((state) => state.construction.inRackConstructionMode) + + const dispatch = useDispatch() + const onEdit = () => dispatch(startRoomEdit(roomId)) + const onFinish = () => dispatch(finishRoomEdit()) + + return isEditing ? ( + <Button variant="tertiary" icon={<CheckIcon />} isBlock onClick={onFinish} className="pf-u-mb-sm"> + Finish editing room + </Button> + ) : ( + <Button + variant="tertiary" + icon={<PencilAltIcon />} + isBlock + disabled={isInRackConstructionMode} + onClick={() => (isInRackConstructionMode ? undefined : onEdit())} + className="pf-u-mb-sm" + > + Edit the tiles of this room + </Button> + ) +} + +EditRoomContainer.propTypes = { + roomId: PropTypes.string.isRequired, +} + +export default EditRoomContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RackConstructionComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RackConstructionComponent.js new file mode 100644 index 00000000..a384d5d5 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RackConstructionComponent.js @@ -0,0 +1,35 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { Button } from '@patternfly/react-core' +import { PlusIcon, TimesIcon } from '@patternfly/react-icons' + +const RackConstructionComponent = ({ onStart, onStop, inRackConstructionMode, isEditingRoom }) => { + if (inRackConstructionMode) { + return ( + <Button isBlock={true} icon={<TimesIcon />} onClick={onStop} className="pf-u-mb-sm"> + Stop rack construction + </Button> + ) + } + + return ( + <Button + icon={<PlusIcon />} + isBlock + isDisabled={isEditingRoom} + onClick={() => (isEditingRoom ? undefined : onStart())} + className="pf-u-mb-sm" + > + Start rack construction + </Button> + ) +} + +RackConstructionComponent.propTypes = { + onStart: PropTypes.func, + onStop: PropTypes.func, + inRackConstructionMode: PropTypes.bool, + isEditingRoom: PropTypes.bool, +} + +export default RackConstructionComponent diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RackConstructionContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RackConstructionContainer.js new file mode 100644 index 00000000..e04287a5 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RackConstructionContainer.js @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { startRackConstruction, stopRackConstruction } from '../../../../redux/actions/topology/room' +import RackConstructionComponent from './RackConstructionComponent' + +function RackConstructionContainer(props) { + const isRackConstructionMode = useSelector((state) => state.construction.inRackConstructionMode) + const isEditingRoom = useSelector((state) => state.construction.currentRoomInConstruction !== '-1') + + const dispatch = useDispatch() + const onStart = () => dispatch(startRackConstruction()) + const onStop = () => dispatch(stopRackConstruction()) + return ( + <RackConstructionComponent + {...props} + inRackConstructionMode={isRackConstructionMode} + isEditingRoom={isEditingRoom} + onStart={onStart} + onStop={onStop} + /> + ) +} + +export default RackConstructionContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RoomName.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RoomName.js new file mode 100644 index 00000000..fb52d826 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RoomName.js @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import React from 'react' +import { useDispatch, useSelector } from 'react-redux' +import NameComponent from '../NameComponent' +import { editRoomName } from '../../../../redux/actions/topology/room' + +function RoomName({ roomId }) { + const { name: roomName, _id } = useSelector((state) => state.topology.rooms[roomId]) + const dispatch = useDispatch() + const callback = (name) => { + if (name) { + dispatch(editRoomName(_id, name)) + } + } + return <NameComponent name={roomName} onEdit={callback} /> +} + +RoomName.propTypes = { + roomId: PropTypes.string.isRequired, +} + +export default RoomName diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RoomSidebar.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RoomSidebar.js new file mode 100644 index 00000000..6ad489e0 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RoomSidebar.js @@ -0,0 +1,43 @@ +import PropTypes from 'prop-types' +import React from 'react' +import RoomName from './RoomName' +import RackConstructionContainer from './RackConstructionContainer' +import EditRoomContainer from './EditRoomContainer' +import DeleteRoomContainer from './DeleteRoomContainer' +import { + TextContent, + TextList, + TextListItem, + TextListItemVariants, + TextListVariants, + Title, +} from '@patternfly/react-core' + +const RoomSidebar = ({ roomId }) => { + return ( + <TextContent> + <Title headingLevel="h2">Details</Title> + <TextList component={TextListVariants.dl}> + <TextListItem + component={TextListItemVariants.dt} + className="pf-u-display-inline-flex pf-u-align-items-center" + > + Name + </TextListItem> + <TextListItem component={TextListItemVariants.dd}> + <RoomName roomId={roomId} /> + </TextListItem> + </TextList> + <Title headingLevel="h2">Construction</Title> + <RackConstructionContainer /> + <EditRoomContainer roomId={roomId} /> + <DeleteRoomContainer roomId={roomId} /> + </TextContent> + ) +} + +RoomSidebar.propTypes = { + roomId: PropTypes.string.isRequired, +} + +export default RoomSidebar diff --git a/opendc-web/opendc-web-ui/src/components/util/BreadcrumbLink.js b/opendc-web/opendc-web-ui/src/components/util/BreadcrumbLink.js new file mode 100644 index 00000000..c6ab214a --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/util/BreadcrumbLink.js @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import Link from 'next/link' + +const BreadcrumbLink = ({ children, href, ...props }) => ( + <Link href={href}> + <a {...props}>{children}</a> + </Link> +) + +BreadcrumbLink.propTypes = { + children: PropTypes.node, + href: PropTypes.string.isRequired, +} + +export default BreadcrumbLink diff --git a/opendc-web/opendc-web-ui/src/components/util/NavItemLink.js b/opendc-web/opendc-web-ui/src/components/util/NavItemLink.js new file mode 100644 index 00000000..c0d109bd --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/util/NavItemLink.js @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import Link from 'next/link' +import PropTypes from 'prop-types' + +const NavItemLink = ({ children, href, ...props }) => ( + <Link href={href}> + <a {...props}>{children}</a> + </Link> +) + +NavItemLink.propTypes = { + children: PropTypes.node, + href: PropTypes.string.isRequired, +} + +export default NavItemLink diff --git a/opendc-web/opendc-web-ui/src/components/util/TableEmptyState.js b/opendc-web/opendc-web-ui/src/components/util/TableEmptyState.js new file mode 100644 index 00000000..9d16ffbb --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/util/TableEmptyState.js @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import { Bullseye, EmptyState, EmptyStateBody, EmptyStateIcon, Spinner, Title } from '@patternfly/react-core' +import { SearchIcon, CubesIcon } from '@patternfly/react-icons' +import { Status } from '../../shapes' + +function TableEmptyState({ + status, + isFiltering, + loadingTitle = 'Loading', + emptyTitle = 'No results found', + emptyText = 'No results found of this type.', + emptyAction = '', +}) { + if (status === 'loading') { + return ( + <Bullseye> + <EmptyState variant="xs"> + <EmptyStateIcon variant="container" component={Spinner} /> + <Title headingLevel="h4" size="md"> + {loadingTitle} + </Title> + </EmptyState> + </Bullseye> + ) + } else if (status === 'error') { + return ( + <EmptyState variant="xs"> + <Title headingLevel="h4" size="md"> + Unable to connect + </Title> + <EmptyStateBody> + There was an error retrieving data. Check your connection and try again. + </EmptyStateBody> + </EmptyState> + ) + } else if (status === 'idle') { + return ( + <EmptyState variant="xs"> + <EmptyStateIcon icon={CubesIcon} /> + <Title headingLevel="h4" size="md"> + {emptyTitle} + </Title> + <EmptyStateBody>No results available at this moment.</EmptyStateBody> + </EmptyState> + ) + } else if (isFiltering) { + return ( + <EmptyState variant="xs"> + <EmptyStateIcon icon={SearchIcon} /> + <Title headingLevel="h4" size="md"> + No results found + </Title> + <EmptyStateBody> + No results match this filter criteria. Remove all filters or clear all filters to show results. + </EmptyStateBody> + </EmptyState> + ) + } + + return ( + <EmptyState variant="xs"> + <EmptyStateIcon icon={CubesIcon} /> + <Title headingLevel="h4" size="md"> + {emptyTitle} + </Title> + <EmptyStateBody>{emptyText}</EmptyStateBody> + {emptyAction} + </EmptyState> + ) +} + +TableEmptyState.propTypes = { + status: Status.isRequired, + isFiltering: PropTypes.bool, + loadingTitle: PropTypes.string, + emptyTitle: PropTypes.string, + emptyText: PropTypes.string, + emptyAction: PropTypes.node, +} + +export default TableEmptyState diff --git a/opendc-web/opendc-web-ui/src/components/util/modals/ConfirmationModal.js b/opendc-web/opendc-web-ui/src/components/util/modals/ConfirmationModal.js new file mode 100644 index 00000000..f6e1c98b --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/util/modals/ConfirmationModal.js @@ -0,0 +1,27 @@ +import PropTypes from 'prop-types' +import React from 'react' +import Modal from './Modal' + +function ConfirmationModal({ title, message, isOpen, callback }) { + return ( + <Modal + title={title} + isOpen={isOpen} + onSubmit={() => callback(true)} + onCancel={() => callback(false)} + submitButtonType="danger" + submitButtonText="Confirm" + > + {message} + </Modal> + ) +} + +ConfirmationModal.propTypes = { + title: PropTypes.string.isRequired, + message: PropTypes.string.isRequired, + isOpen: PropTypes.bool.isRequired, + callback: PropTypes.func.isRequired, +} + +export default ConfirmationModal diff --git a/opendc-web/opendc-web-ui/src/components/util/modals/Modal.js b/opendc-web/opendc-web-ui/src/components/util/modals/Modal.js new file mode 100644 index 00000000..d4577062 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/util/modals/Modal.js @@ -0,0 +1,38 @@ +import React from 'react' +import PropTypes from 'prop-types' +import { Button, Modal as PModal, ModalVariant } from '@patternfly/react-core' + +function Modal({ children, title, isOpen, onSubmit, onCancel, submitButtonType, submitButtonText }) { + const actions = [ + <Button variant={submitButtonType} onClick={onSubmit} key="confirm"> + {submitButtonText} + </Button>, + <Button variant="link" onClick={onCancel} key="cancel"> + Cancel + </Button>, + ] + + return ( + <PModal variant={ModalVariant.small} isOpen={isOpen} onClose={onCancel} title={title} actions={actions}> + {children} + </PModal> + ) +} + +Modal.propTypes = { + title: PropTypes.string.isRequired, + isOpen: PropTypes.bool, + onSubmit: PropTypes.func.isRequired, + onCancel: PropTypes.func.isRequired, + submitButtonType: PropTypes.string, + submitButtonText: PropTypes.string, + children: PropTypes.node, +} + +Modal.defaultProps = { + submitButtonType: 'primary', + submitButtonText: 'Save', + isOpen: false, +} + +export default Modal diff --git a/opendc-web/opendc-web-ui/src/components/util/modals/TextInputModal.js b/opendc-web/opendc-web-ui/src/components/util/modals/TextInputModal.js new file mode 100644 index 00000000..392a729e --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/util/modals/TextInputModal.js @@ -0,0 +1,70 @@ +import PropTypes from 'prop-types' +import React, { useRef, useState } from 'react' +import Modal from './Modal' +import { Form, FormGroup, TextInput } from '@patternfly/react-core' + +function TextInputModal({ title, label, isOpen, callback, initialValue }) { + const textInput = useRef(null) + const [isSubmitted, setSubmitted] = useState(false) + const [isValid, setValid] = useState(true) + + const resetState = () => { + textInput.current.value = '' + setSubmitted(false) + setValid(false) + } + const onSubmit = (event) => { + const value = textInput.current.value + setSubmitted(true) + + if (event) { + event.preventDefault() + } + + if (!value) { + setValid(false) + return false + } + + callback(value) + resetState() + return true + } + const onCancel = () => { + callback(undefined) + resetState() + } + + return ( + <Modal title={title} isOpen={isOpen} onSubmit={onSubmit} onCancel={onCancel}> + <Form onSubmit={onSubmit}> + <FormGroup + label={label} + fieldId="text-input" + isRequired + validated={isSubmitted && !isValid ? 'error' : 'default'} + helperTextInvalid="This field cannot be empty" + > + <TextInput + id="text-input" + name="text-input" + isRequired + type="text" + ref={textInput} + defaultValue={initialValue} + /> + </FormGroup> + </Form> + </Modal> + ) +} + +TextInputModal.propTypes = { + title: PropTypes.string.isRequired, + label: PropTypes.string.isRequired, + isOpen: PropTypes.bool.isRequired, + callback: PropTypes.func.isRequired, + initialValue: PropTypes.string, +} + +export default TextInputModal diff --git a/opendc-web/opendc-web-ui/src/config.js b/opendc-web/opendc-web-ui/src/config.js deleted file mode 100644 index 13f4abf2..00000000 --- a/opendc-web/opendc-web-ui/src/config.js +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -function getConfig(name) { - if (process.env.NODE_ENV === 'production' && window.config_overrides) { - const value = window.config_overrides[name] - if (value !== `$${name}`) { - return value - } - } - - return process.env[name] -} - -const config = { - API_BASE_URL: getConfig('REACT_APP_API_BASE_URL'), - OAUTH_CLIENT_ID: getConfig('REACT_APP_OAUTH_CLIENT_ID'), - SENTRY_DSN: getConfig('REACT_APP_SENTRY_DSN'), -} - -export default config diff --git a/opendc-web/opendc-web-ui/src/containers/app/map/GrayContainer.js b/opendc-web/opendc-web-ui/src/containers/app/map/GrayContainer.js deleted file mode 100644 index 9e4a6969..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/map/GrayContainer.js +++ /dev/null @@ -1,13 +0,0 @@ -import { connect } from 'react-redux' -import { goDownOneInteractionLevel } from '../../../actions/interaction-level' -import GrayLayer from '../../../components/app/map/elements/GrayLayer' - -const mapDispatchToProps = (dispatch) => { - return { - onClick: () => dispatch(goDownOneInteractionLevel()), - } -} - -const GrayContainer = connect(undefined, mapDispatchToProps)(GrayLayer) - -export default GrayContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/map/MapStage.js b/opendc-web/opendc-web-ui/src/containers/app/map/MapStage.js deleted file mode 100644 index 23c920b6..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/map/MapStage.js +++ /dev/null @@ -1,22 +0,0 @@ -import { connect } from 'react-redux' -import { setMapDimensions, setMapPositionWithBoundsCheck, zoomInOnPosition } from '../../../actions/map' -import MapStageComponent from '../../../components/app/map/MapStageComponent' - -const mapStateToProps = (state) => { - return { - mapPosition: state.map.position, - mapDimensions: state.map.dimensions, - } -} - -const mapDispatchToProps = (dispatch) => { - return { - zoomInOnPosition: (zoomIn, x, y) => dispatch(zoomInOnPosition(zoomIn, x, y)), - setMapPositionWithBoundsCheck: (x, y) => dispatch(setMapPositionWithBoundsCheck(x, y)), - setMapDimensions: (width, height) => dispatch(setMapDimensions(width, height)), - } -} - -const MapStage = connect(mapStateToProps, mapDispatchToProps)(MapStageComponent) - -export default MapStage diff --git a/opendc-web/opendc-web-ui/src/containers/app/map/RackContainer.js b/opendc-web/opendc-web-ui/src/containers/app/map/RackContainer.js deleted file mode 100644 index 40077608..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/map/RackContainer.js +++ /dev/null @@ -1,12 +0,0 @@ -import { connect } from 'react-redux' -import RackGroup from '../../../components/app/map/groups/RackGroup' - -const mapStateToProps = (state) => { - return { - interactionLevel: state.interactionLevel, - } -} - -const RackContainer = connect(mapStateToProps)(RackGroup) - -export default RackContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/map/RackEnergyFillContainer.js b/opendc-web/opendc-web-ui/src/containers/app/map/RackEnergyFillContainer.js deleted file mode 100644 index 53746271..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/map/RackEnergyFillContainer.js +++ /dev/null @@ -1,26 +0,0 @@ -import { connect } from 'react-redux' -import RackFillBar from '../../../components/app/map/elements/RackFillBar' - -const mapStateToProps = (state, ownProps) => { - let energyConsumptionTotal = 0 - const rack = state.objects.rack[state.objects.tile[ownProps.tileId].rackId] - const machineIds = rack.machineIds - machineIds.forEach((machineId) => { - if (machineId !== null) { - const machine = state.objects.machine[machineId] - machine.cpuIds.forEach((id) => (energyConsumptionTotal += state.objects.cpu[id].energyConsumptionW)) - machine.gpuIds.forEach((id) => (energyConsumptionTotal += state.objects.gpu[id].energyConsumptionW)) - machine.memoryIds.forEach((id) => (energyConsumptionTotal += state.objects.memory[id].energyConsumptionW)) - machine.storageIds.forEach((id) => (energyConsumptionTotal += state.objects.storage[id].energyConsumptionW)) - } - }) - - return { - type: 'energy', - fillFraction: Math.min(1, energyConsumptionTotal / rack.powerCapacityW), - } -} - -const RackSpaceFillContainer = connect(mapStateToProps)(RackFillBar) - -export default RackSpaceFillContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/map/RackSpaceFillContainer.js b/opendc-web/opendc-web-ui/src/containers/app/map/RackSpaceFillContainer.js deleted file mode 100644 index 0509a5a5..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/map/RackSpaceFillContainer.js +++ /dev/null @@ -1,14 +0,0 @@ -import { connect } from 'react-redux' -import RackFillBar from '../../../components/app/map/elements/RackFillBar' - -const mapStateToProps = (state, ownProps) => { - const machineIds = state.objects.rack[state.objects.tile[ownProps.tileId].rackId].machineIds - return { - type: 'space', - fillFraction: machineIds.filter((id) => id !== null).length / machineIds.length, - } -} - -const RackSpaceFillContainer = connect(mapStateToProps)(RackFillBar) - -export default RackSpaceFillContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/map/RoomContainer.js b/opendc-web/opendc-web-ui/src/containers/app/map/RoomContainer.js deleted file mode 100644 index 91bf4e5d..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/map/RoomContainer.js +++ /dev/null @@ -1,21 +0,0 @@ -import { connect } from 'react-redux' -import { goFromBuildingToRoom } from '../../../actions/interaction-level' -import RoomGroup from '../../../components/app/map/groups/RoomGroup' - -const mapStateToProps = (state, ownProps) => { - return { - interactionLevel: state.interactionLevel, - currentRoomInConstruction: state.construction.currentRoomInConstruction, - room: state.objects.room[ownProps.roomId], - } -} - -const mapDispatchToProps = (dispatch, ownProps) => { - return { - onClick: () => dispatch(goFromBuildingToRoom(ownProps.roomId)), - } -} - -const RoomContainer = connect(mapStateToProps, mapDispatchToProps)(RoomGroup) - -export default RoomContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/map/TileContainer.js b/opendc-web/opendc-web-ui/src/containers/app/map/TileContainer.js deleted file mode 100644 index 04d6c8d6..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/map/TileContainer.js +++ /dev/null @@ -1,26 +0,0 @@ -import { connect } from 'react-redux' -import { goFromRoomToRack } from '../../../actions/interaction-level' -import TileGroup from '../../../components/app/map/groups/TileGroup' - -const mapStateToProps = (state, ownProps) => { - const tile = state.objects.tile[ownProps.tileId] - - return { - interactionLevel: state.interactionLevel, - tile, - } -} - -const mapDispatchToProps = (dispatch) => { - return { - onClick: (tile) => { - if (tile.rackId) { - dispatch(goFromRoomToRack(tile._id)) - } - }, - } -} - -const TileContainer = connect(mapStateToProps, mapDispatchToProps)(TileGroup) - -export default TileContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/map/TopologyContainer.js b/opendc-web/opendc-web-ui/src/containers/app/map/TopologyContainer.js deleted file mode 100644 index de43a151..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/map/TopologyContainer.js +++ /dev/null @@ -1,17 +0,0 @@ -import { connect } from 'react-redux' -import TopologyGroup from '../../../components/app/map/groups/TopologyGroup' - -const mapStateToProps = (state) => { - if (state.currentTopologyId === '-1') { - return {} - } - - return { - topology: state.objects.topology[state.currentTopologyId], - interactionLevel: state.interactionLevel, - } -} - -const TopologyContainer = connect(mapStateToProps)(TopologyGroup) - -export default TopologyContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/map/WallContainer.js b/opendc-web/opendc-web-ui/src/containers/app/map/WallContainer.js deleted file mode 100644 index 67f8a242..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/map/WallContainer.js +++ /dev/null @@ -1,12 +0,0 @@ -import { connect } from 'react-redux' -import WallGroup from '../../../components/app/map/groups/WallGroup' - -const mapStateToProps = (state, ownProps) => { - return { - tiles: state.objects.room[ownProps.roomId].tileIds.map((tileId) => state.objects.tile[tileId]), - } -} - -const WallContainer = connect(mapStateToProps)(WallGroup) - -export default WallContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/map/controls/ScaleIndicatorContainer.js b/opendc-web/opendc-web-ui/src/containers/app/map/controls/ScaleIndicatorContainer.js deleted file mode 100644 index fa3b9d22..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/map/controls/ScaleIndicatorContainer.js +++ /dev/null @@ -1,12 +0,0 @@ -import { connect } from 'react-redux' -import ScaleIndicatorComponent from '../../../../components/app/map/controls/ScaleIndicatorComponent' - -const mapStateToProps = (state) => { - return { - scale: state.map.scale, - } -} - -const ScaleIndicatorContainer = connect(mapStateToProps)(ScaleIndicatorComponent) - -export default ScaleIndicatorContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/map/controls/ZoomControlContainer.js b/opendc-web/opendc-web-ui/src/containers/app/map/controls/ZoomControlContainer.js deleted file mode 100644 index ddc68cc7..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/map/controls/ZoomControlContainer.js +++ /dev/null @@ -1,19 +0,0 @@ -import { connect } from 'react-redux' -import { zoomInOnCenter } from '../../../../actions/map' -import ZoomControlComponent from '../../../../components/app/map/controls/ZoomControlComponent' - -const mapStateToProps = (state) => { - return { - mapScale: state.map.scale, - } -} - -const mapDispatchToProps = (dispatch) => { - return { - zoomInOnCenter: (zoomIn) => dispatch(zoomInOnCenter(zoomIn)), - } -} - -const ZoomControlContainer = connect(mapStateToProps, mapDispatchToProps)(ZoomControlComponent) - -export default ZoomControlContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/map/layers/MapLayer.js b/opendc-web/opendc-web-ui/src/containers/app/map/layers/MapLayer.js deleted file mode 100644 index 8596cb9c..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/map/layers/MapLayer.js +++ /dev/null @@ -1,13 +0,0 @@ -import { connect } from 'react-redux' -import MapLayerComponent from '../../../../components/app/map/layers/MapLayerComponent' - -const mapStateToProps = (state) => { - return { - mapPosition: state.map.position, - mapScale: state.map.scale, - } -} - -const MapLayer = connect(mapStateToProps)(MapLayerComponent) - -export default MapLayer diff --git a/opendc-web/opendc-web-ui/src/containers/app/map/layers/ObjectHoverLayer.js b/opendc-web/opendc-web-ui/src/containers/app/map/layers/ObjectHoverLayer.js deleted file mode 100644 index a4927862..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/map/layers/ObjectHoverLayer.js +++ /dev/null @@ -1,33 +0,0 @@ -import { connect } from 'react-redux' -import { addRackToTile } from '../../../../actions/topology/room' -import ObjectHoverLayerComponent from '../../../../components/app/map/layers/ObjectHoverLayerComponent' -import { findTileWithPosition } from '../../../../util/tile-calculations' - -const mapStateToProps = (state) => { - return { - mapPosition: state.map.position, - mapScale: state.map.scale, - isEnabled: () => state.construction.inRackConstructionMode, - isValid: (x, y) => { - if (state.interactionLevel.mode !== 'ROOM') { - return false - } - - const currentRoom = state.objects.room[state.interactionLevel.roomId] - const tiles = currentRoom.tileIds.map((tileId) => state.objects.tile[tileId]) - const tile = findTileWithPosition(tiles, x, y) - - return !(tile === null || tile.rackId) - }, - } -} - -const mapDispatchToProps = (dispatch) => { - return { - onClick: (x, y) => dispatch(addRackToTile(x, y)), - } -} - -const ObjectHoverLayer = connect(mapStateToProps, mapDispatchToProps)(ObjectHoverLayerComponent) - -export default ObjectHoverLayer diff --git a/opendc-web/opendc-web-ui/src/containers/app/map/layers/RoomHoverLayer.js b/opendc-web/opendc-web-ui/src/containers/app/map/layers/RoomHoverLayer.js deleted file mode 100644 index 66404f9e..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/map/layers/RoomHoverLayer.js +++ /dev/null @@ -1,46 +0,0 @@ -import { connect } from 'react-redux' -import { toggleTileAtLocation } from '../../../../actions/topology/building' -import RoomHoverLayerComponent from '../../../../components/app/map/layers/RoomHoverLayerComponent' -import { - deriveValidNextTilePositions, - findPositionInPositions, - findPositionInRooms, -} from '../../../../util/tile-calculations' - -const mapStateToProps = (state) => { - return { - mapPosition: state.map.position, - mapScale: state.map.scale, - isEnabled: () => state.construction.currentRoomInConstruction !== '-1', - isValid: (x, y) => { - const newRoom = Object.assign({}, state.objects.room[state.construction.currentRoomInConstruction]) - const oldRooms = Object.keys(state.objects.room) - .map((id) => Object.assign({}, state.objects.room[id])) - .filter( - (room) => - state.objects.topology[state.currentTopologyId].roomIds.indexOf(room._id) !== -1 && - room._id !== state.construction.currentRoomInConstruction - ) - - ;[...oldRooms, newRoom].forEach((room) => { - room.tiles = room.tileIds.map((tileId) => state.objects.tile[tileId]) - }) - if (newRoom.tileIds.length === 0) { - return findPositionInRooms(oldRooms, x, y) === -1 - } - - const validNextPositions = deriveValidNextTilePositions(oldRooms, newRoom.tiles) - return findPositionInPositions(validNextPositions, x, y) !== -1 - }, - } -} - -const mapDispatchToProps = (dispatch) => { - return { - onClick: (x, y) => dispatch(toggleTileAtLocation(x, y)), - } -} - -const RoomHoverLayer = connect(mapStateToProps, mapDispatchToProps)(RoomHoverLayerComponent) - -export default RoomHoverLayer diff --git a/opendc-web/opendc-web-ui/src/containers/app/results/PortfolioResultsContainer.js b/opendc-web/opendc-web-ui/src/containers/app/results/PortfolioResultsContainer.js deleted file mode 100644 index 4b430e54..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/results/PortfolioResultsContainer.js +++ /dev/null @@ -1,28 +0,0 @@ -import { connect } from 'react-redux' -import PortfolioResultsComponent from '../../../components/app/results/PortfolioResultsComponent' - -const mapStateToProps = (state) => { - if ( - state.currentPortfolioId === '-1' || - !state.objects.portfolio[state.currentPortfolioId] || - state.objects.portfolio[state.currentPortfolioId].scenarioIds - .map((scenarioId) => state.objects.scenario[scenarioId]) - .some((s) => s === undefined) - ) { - return { - portfolio: undefined, - scenarios: [], - } - } - - return { - portfolio: state.objects.portfolio[state.currentPortfolioId], - scenarios: state.objects.portfolio[state.currentPortfolioId].scenarioIds.map( - (scenarioId) => state.objects.scenario[scenarioId] - ), - } -} - -const PortfolioResultsContainer = connect(mapStateToProps)(PortfolioResultsComponent) - -export default PortfolioResultsContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/PortfolioListContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/PortfolioListContainer.js deleted file mode 100644 index b32c8b1d..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/PortfolioListContainer.js +++ /dev/null @@ -1,45 +0,0 @@ -import { connect } from 'react-redux' -import { withRouter } from 'react-router-dom' -import PortfolioListComponent from '../../../../components/app/sidebars/project/PortfolioListComponent' -import { deletePortfolio, setCurrentPortfolio } from '../../../../actions/portfolios' -import { openNewPortfolioModal } from '../../../../actions/modals/portfolios' -import { getState } from '../../../../util/state-utils' -import { setCurrentTopology } from '../../../../actions/topology/building' - -const mapStateToProps = (state) => { - let portfolios = state.objects.project[state.currentProjectId] - ? state.objects.project[state.currentProjectId].portfolioIds.map((t) => state.objects.portfolio[t]) - : [] - if (portfolios.filter((t) => !t).length > 0) { - portfolios = [] - } - - return { - currentProjectId: state.currentProjectId, - currentPortfolioId: state.currentPortfolioId, - portfolios, - } -} - -const mapDispatchToProps = (dispatch, ownProps) => { - return { - onNewPortfolio: () => { - dispatch(openNewPortfolioModal()) - }, - onChoosePortfolio: (portfolioId) => { - dispatch(setCurrentPortfolio(portfolioId)) - }, - onDeletePortfolio: async (id) => { - if (id) { - const state = await getState(dispatch) - dispatch(deletePortfolio(id)) - dispatch(setCurrentTopology(state.objects.project[state.currentProjectId].topologyIds[0])) - ownProps.history.push(`/projects/${state.currentProjectId}`) - } - }, - } -} - -const PortfolioListContainer = withRouter(connect(mapStateToProps, mapDispatchToProps)(PortfolioListComponent)) - -export default PortfolioListContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/ProjectSidebarContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/ProjectSidebarContainer.js deleted file mode 100644 index 49001099..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/ProjectSidebarContainer.js +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react' -import { withRouter } from 'react-router-dom' -import ProjectSidebarComponent from '../../../../components/app/sidebars/project/ProjectSidebarComponent' -import { isCollapsible } from '../../../../util/sidebar-space' - -const ProjectSidebarContainer = withRouter(({ location, ...props }) => ( - <ProjectSidebarComponent collapsible={isCollapsible(location)} {...props} /> -)) - -export default ProjectSidebarContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/ScenarioListContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/ScenarioListContainer.js deleted file mode 100644 index 415e2792..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/ScenarioListContainer.js +++ /dev/null @@ -1,41 +0,0 @@ -import { connect } from 'react-redux' -import ScenarioListComponent from '../../../../components/app/sidebars/project/ScenarioListComponent' -import { openNewScenarioModal } from '../../../../actions/modals/scenarios' -import { deleteScenario, setCurrentScenario } from '../../../../actions/scenarios' -import { setCurrentPortfolio } from '../../../../actions/portfolios' - -const mapStateToProps = (state, ownProps) => { - let scenarios = state.objects.portfolio[ownProps.portfolioId] - ? state.objects.portfolio[ownProps.portfolioId].scenarioIds.map((t) => state.objects.scenario[t]) - : [] - if (scenarios.filter((t) => !t).length > 0) { - scenarios = [] - } - - return { - currentProjectId: state.currentProjectId, - currentScenarioId: state.currentScenarioId, - scenarios, - } -} - -const mapDispatchToProps = (dispatch) => { - return { - onNewScenario: (currentPortfolioId) => { - dispatch(setCurrentPortfolio(currentPortfolioId)) - dispatch(openNewScenarioModal()) - }, - onChooseScenario: (portfolioId, scenarioId) => { - dispatch(setCurrentScenario(portfolioId, scenarioId)) - }, - onDeleteScenario: (id) => { - if (id) { - dispatch(deleteScenario(id)) - } - }, - } -} - -const ScenarioListContainer = connect(mapStateToProps, mapDispatchToProps)(ScenarioListComponent) - -export default ScenarioListContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/TopologyListContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/TopologyListContainer.js deleted file mode 100644 index e1de18f9..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/TopologyListContainer.js +++ /dev/null @@ -1,46 +0,0 @@ -import { connect } from 'react-redux' -import TopologyListComponent from '../../../../components/app/sidebars/project/TopologyListComponent' -import { setCurrentTopology } from '../../../../actions/topology/building' -import { openNewTopologyModal } from '../../../../actions/modals/topology' -import { withRouter } from 'react-router-dom' -import { getState } from '../../../../util/state-utils' -import { deleteTopology } from '../../../../actions/topologies' - -const mapStateToProps = (state) => { - let topologies = state.objects.project[state.currentProjectId] - ? state.objects.project[state.currentProjectId].topologyIds.map((t) => state.objects.topology[t]) - : [] - if (topologies.filter((t) => !t).length > 0) { - topologies = [] - } - - return { - currentTopologyId: state.currentTopologyId, - topologies, - } -} - -const mapDispatchToProps = (dispatch, ownProps) => { - return { - onChooseTopology: async (id) => { - dispatch(setCurrentTopology(id)) - const state = await getState(dispatch) - ownProps.history.push(`/projects/${state.currentProjectId}`) - }, - onNewTopology: () => { - dispatch(openNewTopologyModal()) - }, - onDeleteTopology: async (id) => { - if (id) { - const state = await getState(dispatch) - dispatch(deleteTopology(id)) - dispatch(setCurrentTopology(state.objects.project[state.currentProjectId].topologyIds[0])) - ownProps.history.push(`/projects/${state.currentProjectId}`) - } - }, - } -} - -const TopologyListContainer = withRouter(connect(mapStateToProps, mapDispatchToProps)(TopologyListComponent)) - -export default TopologyListContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/TopologySidebarContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/TopologySidebarContainer.js deleted file mode 100644 index fe7c02fd..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/TopologySidebarContainer.js +++ /dev/null @@ -1,12 +0,0 @@ -import { connect } from 'react-redux' -import TopologySidebarComponent from '../../../../components/app/sidebars/topology/TopologySidebarComponent' - -const mapStateToProps = (state) => { - return { - interactionLevel: state.interactionLevel, - } -} - -const TopologySidebarContainer = connect(mapStateToProps)(TopologySidebarComponent) - -export default TopologySidebarContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/building/BuildingSidebarContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/building/BuildingSidebarContainer.js deleted file mode 100644 index a0b52e56..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/building/BuildingSidebarContainer.js +++ /dev/null @@ -1,5 +0,0 @@ -import BuildingSidebarComponent from '../../../../../components/app/sidebars/topology/building/BuildingSidebarComponent' - -const BuildingSidebarContainer = BuildingSidebarComponent - -export default BuildingSidebarContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/building/NewRoomConstructionContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/building/NewRoomConstructionContainer.js deleted file mode 100644 index ea9e9e60..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/building/NewRoomConstructionContainer.js +++ /dev/null @@ -1,25 +0,0 @@ -import { connect } from 'react-redux' -import { - cancelNewRoomConstruction, - finishNewRoomConstruction, - startNewRoomConstruction, -} from '../../../../../actions/topology/building' -import StartNewRoomConstructionComponent from '../../../../../components/app/sidebars/topology/building/NewRoomConstructionComponent' - -const mapStateToProps = (state) => { - return { - currentRoomInConstruction: state.construction.currentRoomInConstruction, - } -} - -const mapDispatchToProps = (dispatch) => { - return { - onStart: () => dispatch(startNewRoomConstruction()), - onFinish: () => dispatch(finishNewRoomConstruction()), - onCancel: () => dispatch(cancelNewRoomConstruction()), - } -} - -const NewRoomConstructionButton = connect(mapStateToProps, mapDispatchToProps)(StartNewRoomConstructionComponent) - -export default NewRoomConstructionButton diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/BackToRackContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/BackToRackContainer.js deleted file mode 100644 index 24287ab0..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/BackToRackContainer.js +++ /dev/null @@ -1,13 +0,0 @@ -import { connect } from 'react-redux' -import { goDownOneInteractionLevel } from '../../../../../actions/interaction-level' -import BackToRackComponent from '../../../../../components/app/sidebars/topology/machine/BackToRackComponent' - -const mapDispatchToProps = (dispatch) => { - return { - onClick: () => dispatch(goDownOneInteractionLevel()), - } -} - -const BackToRackContainer = connect(undefined, mapDispatchToProps)(BackToRackComponent) - -export default BackToRackContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/DeleteMachineContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/DeleteMachineContainer.js deleted file mode 100644 index 65e683e6..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/DeleteMachineContainer.js +++ /dev/null @@ -1,13 +0,0 @@ -import { connect } from 'react-redux' -import { openDeleteMachineModal } from '../../../../../actions/modals/topology' -import DeleteMachineComponent from '../../../../../components/app/sidebars/topology/machine/DeleteMachineComponent' - -const mapDispatchToProps = (dispatch) => { - return { - onClick: () => dispatch(openDeleteMachineModal()), - } -} - -const DeleteMachineContainer = connect(undefined, mapDispatchToProps)(DeleteMachineComponent) - -export default DeleteMachineContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/MachineNameContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/MachineNameContainer.js deleted file mode 100644 index 1cf35b05..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/MachineNameContainer.js +++ /dev/null @@ -1,12 +0,0 @@ -import { connect } from 'react-redux' -import MachineNameComponent from '../../../../../components/app/sidebars/topology/machine/MachineNameComponent' - -const mapStateToProps = (state) => { - return { - position: state.interactionLevel.position, - } -} - -const MachineNameContainer = connect(mapStateToProps)(MachineNameComponent) - -export default MachineNameContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/MachineSidebarContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/MachineSidebarContainer.js deleted file mode 100644 index b04e3118..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/MachineSidebarContainer.js +++ /dev/null @@ -1,15 +0,0 @@ -import { connect } from 'react-redux' -import MachineSidebarComponent from '../../../../../components/app/sidebars/topology/machine/MachineSidebarComponent' - -const mapStateToProps = (state) => { - return { - machineId: - state.objects.rack[state.objects.tile[state.interactionLevel.tileId].rackId].machineIds[ - state.interactionLevel.position - 1 - ], - } -} - -const MachineSidebarContainer = connect(mapStateToProps)(MachineSidebarComponent) - -export default MachineSidebarContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitAddContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitAddContainer.js deleted file mode 100644 index 29e48016..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitAddContainer.js +++ /dev/null @@ -1,19 +0,0 @@ -import { connect } from 'react-redux' -import { addUnit } from '../../../../../actions/topology/machine' -import UnitAddComponent from '../../../../../components/app/sidebars/topology/machine/UnitAddComponent' - -const mapStateToProps = (state, ownProps) => { - return { - units: Object.values(state.objects[ownProps.unitType]), - } -} - -const mapDispatchToProps = (dispatch, ownProps) => { - return { - onAdd: (id) => dispatch(addUnit(ownProps.unitType, id)), - } -} - -const UnitAddContainer = connect(mapStateToProps, mapDispatchToProps)(UnitAddComponent) - -export default UnitAddContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitContainer.js deleted file mode 100644 index f334f9f2..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitContainer.js +++ /dev/null @@ -1,20 +0,0 @@ -import { connect } from 'react-redux' -import { deleteUnit } from '../../../../../actions/topology/machine' -import UnitComponent from '../../../../../components/app/sidebars/topology/machine/UnitComponent' - -const mapStateToProps = (state, ownProps) => { - return { - unit: state.objects[ownProps.unitType][ownProps.unitId], - index: ownProps.unitId, - } -} - -const mapDispatchToProps = (dispatch, ownProps) => { - return { - onDelete: () => dispatch(deleteUnit(ownProps.unitType, ownProps.index)), - } -} - -const UnitContainer = connect(mapStateToProps, mapDispatchToProps)(UnitComponent) - -export default UnitContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitListContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitListContainer.js deleted file mode 100644 index f382ff74..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitListContainer.js +++ /dev/null @@ -1,17 +0,0 @@ -import { connect } from 'react-redux' -import UnitListComponent from '../../../../../components/app/sidebars/topology/machine/UnitListComponent' - -const mapStateToProps = (state, ownProps) => { - return { - unitIds: - state.objects.machine[ - state.objects.rack[state.objects.tile[state.interactionLevel.tileId].rackId].machineIds[ - state.interactionLevel.position - 1 - ] - ][ownProps.unitType + 'Ids'], - } -} - -const UnitListContainer = connect(mapStateToProps)(UnitListComponent) - -export default UnitListContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitTabsContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitTabsContainer.js deleted file mode 100644 index 00fe4067..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitTabsContainer.js +++ /dev/null @@ -1,5 +0,0 @@ -import UnitTabsComponent from '../../../../../components/app/sidebars/topology/machine/UnitTabsComponent' - -const UnitTabsContainer = UnitTabsComponent - -export default UnitTabsContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/AddPrefabContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/AddPrefabContainer.js deleted file mode 100644 index c941e745..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/AddPrefabContainer.js +++ /dev/null @@ -1,13 +0,0 @@ -import { connect } from 'react-redux' -import { addPrefab } from '../../../../../actions/prefabs' -import AddPrefabComponent from '../../../../../components/app/sidebars/topology/rack/AddPrefabComponent' - -const mapDispatchToProps = (dispatch) => { - return { - onClick: () => dispatch(addPrefab('name')), - } -} - -const AddPrefabContainer = connect(undefined, mapDispatchToProps)(AddPrefabComponent) - -export default AddPrefabContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/BackToRoomContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/BackToRoomContainer.js deleted file mode 100644 index 58c3b082..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/BackToRoomContainer.js +++ /dev/null @@ -1,13 +0,0 @@ -import { connect } from 'react-redux' -import { goDownOneInteractionLevel } from '../../../../../actions/interaction-level' -import BackToRoomComponent from '../../../../../components/app/sidebars/topology/rack/BackToRoomComponent' - -const mapDispatchToProps = (dispatch) => { - return { - onClick: () => dispatch(goDownOneInteractionLevel()), - } -} - -const BackToRoomContainer = connect(undefined, mapDispatchToProps)(BackToRoomComponent) - -export default BackToRoomContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/DeleteRackContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/DeleteRackContainer.js deleted file mode 100644 index 8229a359..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/DeleteRackContainer.js +++ /dev/null @@ -1,13 +0,0 @@ -import { connect } from 'react-redux' -import { openDeleteRackModal } from '../../../../../actions/modals/topology' -import DeleteRackComponent from '../../../../../components/app/sidebars/topology/rack/DeleteRackComponent' - -const mapDispatchToProps = (dispatch) => { - return { - onClick: () => dispatch(openDeleteRackModal()), - } -} - -const DeleteRackContainer = connect(undefined, mapDispatchToProps)(DeleteRackComponent) - -export default DeleteRackContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/EmptySlotContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/EmptySlotContainer.js deleted file mode 100644 index cf341da9..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/EmptySlotContainer.js +++ /dev/null @@ -1,13 +0,0 @@ -import { connect } from 'react-redux' -import { addMachine } from '../../../../../actions/topology/rack' -import EmptySlotComponent from '../../../../../components/app/sidebars/topology/rack/EmptySlotComponent' - -const mapDispatchToProps = (dispatch, ownProps) => { - return { - onAdd: () => dispatch(addMachine(ownProps.position)), - } -} - -const EmptySlotContainer = connect(undefined, mapDispatchToProps)(EmptySlotComponent) - -export default EmptySlotContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/MachineContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/MachineContainer.js deleted file mode 100644 index fe12827d..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/MachineContainer.js +++ /dev/null @@ -1,19 +0,0 @@ -import { connect } from 'react-redux' -import { goFromRackToMachine } from '../../../../../actions/interaction-level' -import MachineComponent from '../../../../../components/app/sidebars/topology/rack/MachineComponent' - -const mapStateToProps = (state, ownProps) => { - return { - machine: state.objects.machine[ownProps.machineId], - } -} - -const mapDispatchToProps = (dispatch, ownProps) => { - return { - onClick: () => dispatch(goFromRackToMachine(ownProps.position)), - } -} - -const MachineContainer = connect(mapStateToProps, mapDispatchToProps)(MachineComponent) - -export default MachineContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/MachineListContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/MachineListContainer.js deleted file mode 100644 index bc5a285a..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/MachineListContainer.js +++ /dev/null @@ -1,12 +0,0 @@ -import { connect } from 'react-redux' -import MachineListComponent from '../../../../../components/app/sidebars/topology/rack/MachineListComponent' - -const mapStateToProps = (state) => { - return { - machineIds: state.objects.rack[state.objects.tile[state.interactionLevel.tileId].rackId].machineIds, - } -} - -const MachineListContainer = connect(mapStateToProps)(MachineListComponent) - -export default MachineListContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/RackNameContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/RackNameContainer.js deleted file mode 100644 index 504dbc61..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/RackNameContainer.js +++ /dev/null @@ -1,19 +0,0 @@ -import { connect } from 'react-redux' -import { openEditRackNameModal } from '../../../../../actions/modals/topology' -import RackNameComponent from '../../../../../components/app/sidebars/topology/rack/RackNameComponent' - -const mapStateToProps = (state) => { - return { - rackName: state.objects.rack[state.objects.tile[state.interactionLevel.tileId].rackId].name, - } -} - -const mapDispatchToProps = (dispatch) => { - return { - onEdit: () => dispatch(openEditRackNameModal()), - } -} - -const RackNameContainer = connect(mapStateToProps, mapDispatchToProps)(RackNameComponent) - -export default RackNameContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/RackSidebarContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/RackSidebarContainer.js deleted file mode 100644 index 453d7e41..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/RackSidebarContainer.js +++ /dev/null @@ -1,12 +0,0 @@ -import { connect } from 'react-redux' -import RackSidebarComponent from '../../../../../components/app/sidebars/topology/rack/RackSidebarComponent' - -const mapStateToProps = (state) => { - return { - rackId: state.objects.tile[state.interactionLevel.tileId].rackId, - } -} - -const RackSidebarContainer = connect(mapStateToProps)(RackSidebarComponent) - -export default RackSidebarContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/BackToBuildingContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/BackToBuildingContainer.js deleted file mode 100644 index 4c1ab99d..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/BackToBuildingContainer.js +++ /dev/null @@ -1,13 +0,0 @@ -import { connect } from 'react-redux' -import { goDownOneInteractionLevel } from '../../../../../actions/interaction-level' -import BackToBuildingComponent from '../../../../../components/app/sidebars/topology/room/BackToBuildingComponent' - -const mapDispatchToProps = (dispatch) => { - return { - onClick: () => dispatch(goDownOneInteractionLevel()), - } -} - -const BackToBuildingContainer = connect(undefined, mapDispatchToProps)(BackToBuildingComponent) - -export default BackToBuildingContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/DeleteRoomContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/DeleteRoomContainer.js deleted file mode 100644 index 636fa5c5..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/DeleteRoomContainer.js +++ /dev/null @@ -1,13 +0,0 @@ -import { connect } from 'react-redux' -import { openDeleteRoomModal } from '../../../../../actions/modals/topology' -import DeleteRoomComponent from '../../../../../components/app/sidebars/topology/room/DeleteRoomComponent' - -const mapDispatchToProps = (dispatch) => { - return { - onClick: () => dispatch(openDeleteRoomModal()), - } -} - -const DeleteRoomContainer = connect(undefined, mapDispatchToProps)(DeleteRoomComponent) - -export default DeleteRoomContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/EditRoomContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/EditRoomContainer.js deleted file mode 100644 index d17a45d1..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/EditRoomContainer.js +++ /dev/null @@ -1,21 +0,0 @@ -import { connect } from 'react-redux' -import { finishRoomEdit, startRoomEdit } from '../../../../../actions/topology/building' -import EditRoomComponent from '../../../../../components/app/sidebars/topology/room/EditRoomComponent' - -const mapStateToProps = (state) => { - return { - isEditing: state.construction.currentRoomInConstruction !== '-1', - isInRackConstructionMode: state.construction.inRackConstructionMode, - } -} - -const mapDispatchToProps = (dispatch) => { - return { - onEdit: () => dispatch(startRoomEdit()), - onFinish: () => dispatch(finishRoomEdit()), - } -} - -const EditRoomContainer = connect(mapStateToProps, mapDispatchToProps)(EditRoomComponent) - -export default EditRoomContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/RackConstructionContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/RackConstructionContainer.js deleted file mode 100644 index cd8319de..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/RackConstructionContainer.js +++ /dev/null @@ -1,21 +0,0 @@ -import { connect } from 'react-redux' -import { startRackConstruction, stopRackConstruction } from '../../../../../actions/topology/room' -import RackConstructionComponent from '../../../../../components/app/sidebars/topology/room/RackConstructionComponent' - -const mapStateToProps = (state) => { - return { - inRackConstructionMode: state.construction.inRackConstructionMode, - isEditingRoom: state.construction.currentRoomInConstruction !== '-1', - } -} - -const mapDispatchToProps = (dispatch) => { - return { - onStart: () => dispatch(startRackConstruction()), - onStop: () => dispatch(stopRackConstruction()), - } -} - -const RackConstructionContainer = connect(mapStateToProps, mapDispatchToProps)(RackConstructionComponent) - -export default RackConstructionContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/RoomNameContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/RoomNameContainer.js deleted file mode 100644 index cab16016..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/RoomNameContainer.js +++ /dev/null @@ -1,19 +0,0 @@ -import { connect } from 'react-redux' -import { openEditRoomNameModal } from '../../../../../actions/modals/topology' -import RoomNameComponent from '../../../../../components/app/sidebars/topology/room/RoomNameComponent' - -const mapStateToProps = (state) => { - return { - roomName: state.objects.room[state.interactionLevel.roomId].name, - } -} - -const mapDispatchToProps = (dispatch) => { - return { - onEdit: () => dispatch(openEditRoomNameModal()), - } -} - -const RoomNameContainer = connect(mapStateToProps, mapDispatchToProps)(RoomNameComponent) - -export default RoomNameContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/RoomSidebarContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/RoomSidebarContainer.js deleted file mode 100644 index 8c3ca8ab..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/RoomSidebarContainer.js +++ /dev/null @@ -1,12 +0,0 @@ -import { connect } from 'react-redux' -import RoomSidebarComponent from '../../../../../components/app/sidebars/topology/room/RoomSidebarComponent' - -const mapStateToProps = (state) => { - return { - roomId: state.interactionLevel.roomId, - } -} - -const RoomSidebarContainer = connect(mapStateToProps)(RoomSidebarComponent) - -export default RoomSidebarContainer diff --git a/opendc-web/opendc-web-ui/src/containers/auth/Login.js b/opendc-web/opendc-web-ui/src/containers/auth/Login.js deleted file mode 100644 index bebe015c..00000000 --- a/opendc-web/opendc-web-ui/src/containers/auth/Login.js +++ /dev/null @@ -1,63 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import GoogleLogin from 'react-google-login' -import { connect } from 'react-redux' -import { logIn } from '../../actions/auth' -import config from '../../config' - -class LoginContainer extends React.Component { - static propTypes = { - visible: PropTypes.bool.isRequired, - onLogin: PropTypes.func.isRequired, - } - - onAuthResponse(response) { - this.props.onLogin({ - email: response.getBasicProfile().getEmail(), - givenName: response.getBasicProfile().getGivenName(), - familyName: response.getBasicProfile().getFamilyName(), - googleId: response.googleId, - authToken: response.getAuthResponse().id_token, - expiresAt: response.getAuthResponse().expires_at, - }) - } - - onAuthFailure(error) { - console.error(error) - } - - render() { - if (!this.props.visible) { - return <span /> - } - - return ( - <GoogleLogin - clientId={config['OAUTH_CLIENT_ID']} - onSuccess={this.onAuthResponse.bind(this)} - onFailure={this.onAuthFailure.bind(this)} - render={(renderProps) => ( - <span onClick={renderProps.onClick} className="login btn btn-primary"> - <span className="fa fa-google" /> Login with Google - </span> - )} - /> - ) - } -} - -const mapStateToProps = (state, ownProps) => { - return { - visible: ownProps.visible, - } -} - -const mapDispatchToProps = (dispatch) => { - return { - onLogin: (payload) => dispatch(logIn(payload)), - } -} - -const Login = connect(mapStateToProps, mapDispatchToProps)(LoginContainer) - -export default Login diff --git a/opendc-web/opendc-web-ui/src/containers/auth/Logout.js b/opendc-web/opendc-web-ui/src/containers/auth/Logout.js deleted file mode 100644 index 22400381..00000000 --- a/opendc-web/opendc-web-ui/src/containers/auth/Logout.js +++ /dev/null @@ -1,13 +0,0 @@ -import { connect } from 'react-redux' -import { logOut } from '../../actions/auth' -import LogoutButton from '../../components/navigation/LogoutButton' - -const mapDispatchToProps = (dispatch) => { - return { - onLogout: () => dispatch(logOut()), - } -} - -const Logout = connect(undefined, mapDispatchToProps)(LogoutButton) - -export default Logout diff --git a/opendc-web/opendc-web-ui/src/containers/auth/ProfileName.js b/opendc-web/opendc-web-ui/src/containers/auth/ProfileName.js deleted file mode 100644 index 06da75ab..00000000 --- a/opendc-web/opendc-web-ui/src/containers/auth/ProfileName.js +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react' -import { connect } from 'react-redux' - -const mapStateToProps = (state) => { - return { - text: state.auth.givenName + ' ' + state.auth.familyName, - } -} - -const SpanElement = ({ text }) => <span>{text}</span> - -const ProfileName = connect(mapStateToProps)(SpanElement) - -export default ProfileName diff --git a/opendc-web/opendc-web-ui/src/containers/modals/DeleteMachineModal.js b/opendc-web/opendc-web-ui/src/containers/modals/DeleteMachineModal.js deleted file mode 100644 index f30febdb..00000000 --- a/opendc-web/opendc-web-ui/src/containers/modals/DeleteMachineModal.js +++ /dev/null @@ -1,35 +0,0 @@ -import React from 'react' -import { connect } from 'react-redux' -import { closeDeleteMachineModal } from '../../actions/modals/topology' -import { deleteMachine } from '../../actions/topology/machine' -import ConfirmationModal from '../../components/modals/ConfirmationModal' - -const DeleteMachineModalComponent = ({ visible, callback }) => ( - <ConfirmationModal - title="Delete this machine" - message="Are you sure you want to delete this machine?" - show={visible} - callback={callback} - /> -) - -const mapStateToProps = (state) => { - return { - visible: state.modals.deleteMachineModalVisible, - } -} - -const mapDispatchToProps = (dispatch) => { - return { - callback: (isConfirmed) => { - if (isConfirmed) { - dispatch(deleteMachine()) - } - dispatch(closeDeleteMachineModal()) - }, - } -} - -const DeleteMachineModal = connect(mapStateToProps, mapDispatchToProps)(DeleteMachineModalComponent) - -export default DeleteMachineModal diff --git a/opendc-web/opendc-web-ui/src/containers/modals/DeleteProfileModal.js b/opendc-web/opendc-web-ui/src/containers/modals/DeleteProfileModal.js deleted file mode 100644 index e7c4014d..00000000 --- a/opendc-web/opendc-web-ui/src/containers/modals/DeleteProfileModal.js +++ /dev/null @@ -1,35 +0,0 @@ -import React from 'react' -import { connect } from 'react-redux' -import { closeDeleteProfileModal } from '../../actions/modals/profile' -import { deleteCurrentUser } from '../../actions/users' -import ConfirmationModal from '../../components/modals/ConfirmationModal' - -const DeleteProfileModalComponent = ({ visible, callback }) => ( - <ConfirmationModal - title="Delete my account" - message="Are you sure you want to delete your OpenDC account?" - show={visible} - callback={callback} - /> -) - -const mapStateToProps = (state) => { - return { - visible: state.modals.deleteProfileModalVisible, - } -} - -const mapDispatchToProps = (dispatch) => { - return { - callback: (isConfirmed) => { - if (isConfirmed) { - dispatch(deleteCurrentUser()) - } - dispatch(closeDeleteProfileModal()) - }, - } -} - -const DeleteProfileModal = connect(mapStateToProps, mapDispatchToProps)(DeleteProfileModalComponent) - -export default DeleteProfileModal diff --git a/opendc-web/opendc-web-ui/src/containers/modals/DeleteRackModal.js b/opendc-web/opendc-web-ui/src/containers/modals/DeleteRackModal.js deleted file mode 100644 index 0cb22a7e..00000000 --- a/opendc-web/opendc-web-ui/src/containers/modals/DeleteRackModal.js +++ /dev/null @@ -1,35 +0,0 @@ -import React from 'react' -import { connect } from 'react-redux' -import { closeDeleteRackModal } from '../../actions/modals/topology' -import { deleteRack } from '../../actions/topology/rack' -import ConfirmationModal from '../../components/modals/ConfirmationModal' - -const DeleteRackModalComponent = ({ visible, callback }) => ( - <ConfirmationModal - title="Delete this rack" - message="Are you sure you want to delete this rack?" - show={visible} - callback={callback} - /> -) - -const mapStateToProps = (state) => { - return { - visible: state.modals.deleteRackModalVisible, - } -} - -const mapDispatchToProps = (dispatch) => { - return { - callback: (isConfirmed) => { - if (isConfirmed) { - dispatch(deleteRack()) - } - dispatch(closeDeleteRackModal()) - }, - } -} - -const DeleteRackModal = connect(mapStateToProps, mapDispatchToProps)(DeleteRackModalComponent) - -export default DeleteRackModal diff --git a/opendc-web/opendc-web-ui/src/containers/modals/DeleteRoomModal.js b/opendc-web/opendc-web-ui/src/containers/modals/DeleteRoomModal.js deleted file mode 100644 index 1f6eef92..00000000 --- a/opendc-web/opendc-web-ui/src/containers/modals/DeleteRoomModal.js +++ /dev/null @@ -1,35 +0,0 @@ -import React from 'react' -import { connect } from 'react-redux' -import { closeDeleteRoomModal } from '../../actions/modals/topology' -import { deleteRoom } from '../../actions/topology/room' -import ConfirmationModal from '../../components/modals/ConfirmationModal' - -const DeleteRoomModalComponent = ({ visible, callback }) => ( - <ConfirmationModal - title="Delete this room" - message="Are you sure you want to delete this room?" - show={visible} - callback={callback} - /> -) - -const mapStateToProps = (state) => { - return { - visible: state.modals.deleteRoomModalVisible, - } -} - -const mapDispatchToProps = (dispatch) => { - return { - callback: (isConfirmed) => { - if (isConfirmed) { - dispatch(deleteRoom()) - } - dispatch(closeDeleteRoomModal()) - }, - } -} - -const DeleteRoomModal = connect(mapStateToProps, mapDispatchToProps)(DeleteRoomModalComponent) - -export default DeleteRoomModal diff --git a/opendc-web/opendc-web-ui/src/containers/modals/EditRackNameModal.js b/opendc-web/opendc-web-ui/src/containers/modals/EditRackNameModal.js deleted file mode 100644 index 9128f449..00000000 --- a/opendc-web/opendc-web-ui/src/containers/modals/EditRackNameModal.js +++ /dev/null @@ -1,40 +0,0 @@ -import React from 'react' -import { connect } from 'react-redux' -import { closeEditRackNameModal } from '../../actions/modals/topology' -import { editRackName } from '../../actions/topology/rack' -import TextInputModal from '../../components/modals/TextInputModal' - -const EditRackNameModalComponent = ({ visible, previousName, callback }) => ( - <TextInputModal - title="Edit rack name" - label="Rack name" - show={visible} - initialValue={previousName} - callback={callback} - /> -) - -const mapStateToProps = (state) => { - return { - visible: state.modals.editRackNameModalVisible, - previousName: - state.interactionLevel.mode === 'RACK' - ? state.objects.rack[state.objects.tile[state.interactionLevel.tileId].rackId].name - : '', - } -} - -const mapDispatchToProps = (dispatch) => { - return { - callback: (name) => { - if (name) { - dispatch(editRackName(name)) - } - dispatch(closeEditRackNameModal()) - }, - } -} - -const EditRackNameModal = connect(mapStateToProps, mapDispatchToProps)(EditRackNameModalComponent) - -export default EditRackNameModal diff --git a/opendc-web/opendc-web-ui/src/containers/modals/EditRoomNameModal.js b/opendc-web/opendc-web-ui/src/containers/modals/EditRoomNameModal.js deleted file mode 100644 index 8032a5d1..00000000 --- a/opendc-web/opendc-web-ui/src/containers/modals/EditRoomNameModal.js +++ /dev/null @@ -1,38 +0,0 @@ -import React from 'react' -import { connect } from 'react-redux' -import { closeEditRoomNameModal } from '../../actions/modals/topology' -import { editRoomName } from '../../actions/topology/room' -import TextInputModal from '../../components/modals/TextInputModal' - -const EditRoomNameModalComponent = ({ visible, previousName, callback }) => ( - <TextInputModal - title="Edit room name" - label="Room name" - show={visible} - initialValue={previousName} - callback={callback} - /> -) - -const mapStateToProps = (state) => { - return { - visible: state.modals.editRoomNameModalVisible, - previousName: - state.interactionLevel.mode === 'ROOM' ? state.objects.room[state.interactionLevel.roomId].name : '', - } -} - -const mapDispatchToProps = (dispatch) => { - return { - callback: (name) => { - if (name) { - dispatch(editRoomName(name)) - } - dispatch(closeEditRoomNameModal()) - }, - } -} - -const EditRoomNameModal = connect(mapStateToProps, mapDispatchToProps)(EditRoomNameModalComponent) - -export default EditRoomNameModal diff --git a/opendc-web/opendc-web-ui/src/containers/modals/NewPortfolioModal.js b/opendc-web/opendc-web-ui/src/containers/modals/NewPortfolioModal.js deleted file mode 100644 index 6cf12d8e..00000000 --- a/opendc-web/opendc-web-ui/src/containers/modals/NewPortfolioModal.js +++ /dev/null @@ -1,30 +0,0 @@ -import { connect } from 'react-redux' -import NewPortfolioModalComponent from '../../components/modals/custom-components/NewPortfolioModalComponent' -import { addPortfolio } from '../../actions/portfolios' -import { closeNewPortfolioModal } from '../../actions/modals/portfolios' - -const mapStateToProps = (state) => { - return { - show: state.modals.newPortfolioModalVisible, - } -} - -const mapDispatchToProps = (dispatch) => { - return { - callback: (name, targets) => { - if (name) { - dispatch( - addPortfolio({ - name, - targets, - }) - ) - } - dispatch(closeNewPortfolioModal()) - }, - } -} - -const NewPortfolioModal = connect(mapStateToProps, mapDispatchToProps)(NewPortfolioModalComponent) - -export default NewPortfolioModal diff --git a/opendc-web/opendc-web-ui/src/containers/modals/NewProjectModal.js b/opendc-web/opendc-web-ui/src/containers/modals/NewProjectModal.js deleted file mode 100644 index d306dc45..00000000 --- a/opendc-web/opendc-web-ui/src/containers/modals/NewProjectModal.js +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react' -import { connect } from 'react-redux' -import { closeNewProjectModal } from '../../actions/modals/projects' -import { addProject } from '../../actions/projects' -import TextInputModal from '../../components/modals/TextInputModal' - -const NewProjectModalComponent = ({ visible, callback }) => ( - <TextInputModal title="New Project" label="Project title" show={visible} callback={callback} /> -) - -const mapStateToProps = (state) => { - return { - visible: state.modals.newProjectModalVisible, - } -} - -const mapDispatchToProps = (dispatch) => { - return { - callback: (text) => { - if (text) { - dispatch(addProject(text)) - } - dispatch(closeNewProjectModal()) - }, - } -} - -const NewProjectModal = connect(mapStateToProps, mapDispatchToProps)(NewProjectModalComponent) - -export default NewProjectModal diff --git a/opendc-web/opendc-web-ui/src/containers/modals/NewScenarioModal.js b/opendc-web/opendc-web-ui/src/containers/modals/NewScenarioModal.js deleted file mode 100644 index 7d774fa4..00000000 --- a/opendc-web/opendc-web-ui/src/containers/modals/NewScenarioModal.js +++ /dev/null @@ -1,50 +0,0 @@ -import { connect } from 'react-redux' -import NewScenarioModalComponent from '../../components/modals/custom-components/NewScenarioModalComponent' -import { addScenario } from '../../actions/scenarios' -import { closeNewScenarioModal } from '../../actions/modals/scenarios' - -const mapStateToProps = (state) => { - let topologies = - state.currentProjectId !== '-1' - ? state.objects.project[state.currentProjectId].topologyIds.map((t) => state.objects.topology[t]) - : [] - if (topologies.filter((t) => !t).length > 0) { - topologies = [] - } - - return { - show: state.modals.newScenarioModalVisible, - currentPortfolioId: state.currentPortfolioId, - currentPortfolioScenarioIds: - state.currentPortfolioId !== '-1' && state.objects.portfolio[state.currentPortfolioId] - ? state.objects.portfolio[state.currentPortfolioId].scenarioIds - : [], - traces: Object.values(state.objects.trace), - topologies, - schedulers: Object.values(state.objects.scheduler), - } -} - -const mapDispatchToProps = (dispatch) => { - return { - callback: (name, portfolioId, trace, topology, operational) => { - if (name) { - dispatch( - addScenario({ - portfolioId, - name, - trace, - topology, - operational, - }) - ) - } - - dispatch(closeNewScenarioModal()) - }, - } -} - -const NewScenarioModal = connect(mapStateToProps, mapDispatchToProps)(NewScenarioModalComponent) - -export default NewScenarioModal diff --git a/opendc-web/opendc-web-ui/src/containers/modals/NewTopologyModal.js b/opendc-web/opendc-web-ui/src/containers/modals/NewTopologyModal.js deleted file mode 100644 index 0acf6cf2..00000000 --- a/opendc-web/opendc-web-ui/src/containers/modals/NewTopologyModal.js +++ /dev/null @@ -1,42 +0,0 @@ -import { connect } from 'react-redux' -import NewTopologyModalComponent from '../../components/modals/custom-components/NewTopologyModalComponent' -import { closeNewTopologyModal } from '../../actions/modals/topology' -import { addTopology } from '../../actions/topologies' - -const mapStateToProps = (state) => { - let topologies = state.objects.project[state.currentProjectId] - ? state.objects.project[state.currentProjectId].topologyIds.map((t) => state.objects.topology[t]) - : [] - if (topologies.filter((t) => !t).length > 0) { - topologies = [] - } - - return { - show: state.modals.changeTopologyModalVisible, - topologies, - } -} - -const mapDispatchToProps = (dispatch) => { - return { - onCreateTopology: (name) => { - if (name) { - dispatch(addTopology(name, undefined)) - } - dispatch(closeNewTopologyModal()) - }, - onDuplicateTopology: (name, id) => { - if (name) { - dispatch(addTopology(name, id)) - } - dispatch(closeNewTopologyModal()) - }, - onCancel: () => { - dispatch(closeNewTopologyModal()) - }, - } -} - -const NewTopologyModal = connect(mapStateToProps, mapDispatchToProps)(NewTopologyModalComponent) - -export default NewTopologyModal diff --git a/opendc-web/opendc-web-ui/src/containers/navigation/AppNavbarContainer.js b/opendc-web/opendc-web-ui/src/containers/navigation/AppNavbarContainer.js deleted file mode 100644 index 845d54e1..00000000 --- a/opendc-web/opendc-web-ui/src/containers/navigation/AppNavbarContainer.js +++ /dev/null @@ -1,12 +0,0 @@ -import { connect } from 'react-redux' -import AppNavbarComponent from '../../components/navigation/AppNavbarComponent' - -const mapStateToProps = (state) => { - return { - project: state.currentProjectId !== '-1' ? state.objects.project[state.currentProjectId] : undefined, - } -} - -const AppNavbarContainer = connect(mapStateToProps)(AppNavbarComponent) - -export default AppNavbarContainer diff --git a/opendc-web/opendc-web-ui/src/containers/projects/FilterLink.js b/opendc-web/opendc-web-ui/src/containers/projects/FilterLink.js deleted file mode 100644 index dfd6affe..00000000 --- a/opendc-web/opendc-web-ui/src/containers/projects/FilterLink.js +++ /dev/null @@ -1,19 +0,0 @@ -import { connect } from 'react-redux' -import { setAuthVisibilityFilter } from '../../actions/projects' -import FilterButton from '../../components/projects/FilterButton' - -const mapStateToProps = (state, ownProps) => { - return { - active: state.projectList.authVisibilityFilter === ownProps.filter, - } -} - -const mapDispatchToProps = (dispatch, ownProps) => { - return { - onClick: () => dispatch(setAuthVisibilityFilter(ownProps.filter)), - } -} - -const FilterLink = connect(mapStateToProps, mapDispatchToProps)(FilterButton) - -export default FilterLink diff --git a/opendc-web/opendc-web-ui/src/containers/projects/NewProjectButtonContainer.js b/opendc-web/opendc-web-ui/src/containers/projects/NewProjectButtonContainer.js deleted file mode 100644 index ffd4a4a3..00000000 --- a/opendc-web/opendc-web-ui/src/containers/projects/NewProjectButtonContainer.js +++ /dev/null @@ -1,13 +0,0 @@ -import { connect } from 'react-redux' -import { openNewProjectModal } from '../../actions/modals/projects' -import NewProjectButtonComponent from '../../components/projects/NewProjectButtonComponent' - -const mapDispatchToProps = (dispatch) => { - return { - onClick: () => dispatch(openNewProjectModal()), - } -} - -const NewProjectButtonContainer = connect(undefined, mapDispatchToProps)(NewProjectButtonComponent) - -export default NewProjectButtonContainer diff --git a/opendc-web/opendc-web-ui/src/containers/projects/ProjectActions.js b/opendc-web/opendc-web-ui/src/containers/projects/ProjectActions.js deleted file mode 100644 index 8bcbb7fd..00000000 --- a/opendc-web/opendc-web-ui/src/containers/projects/ProjectActions.js +++ /dev/null @@ -1,20 +0,0 @@ -import { connect } from 'react-redux' -import { deleteProject } from '../../actions/projects' -import ProjectActionButtons from '../../components/projects/ProjectActionButtons' - -const mapStateToProps = (state, ownProps) => { - return { - projectId: ownProps.projectId, - } -} - -const mapDispatchToProps = (dispatch) => { - return { - onViewUsers: (id) => {}, // TODO implement user viewing - onDelete: (id) => dispatch(deleteProject(id)), - } -} - -const ProjectActions = connect(mapStateToProps, mapDispatchToProps)(ProjectActionButtons) - -export default ProjectActions diff --git a/opendc-web/opendc-web-ui/src/containers/projects/VisibleProjectAuthList.js b/opendc-web/opendc-web-ui/src/containers/projects/VisibleProjectAuthList.js deleted file mode 100644 index f0010540..00000000 --- a/opendc-web/opendc-web-ui/src/containers/projects/VisibleProjectAuthList.js +++ /dev/null @@ -1,32 +0,0 @@ -import { connect } from 'react-redux' -import ProjectList from '../../components/projects/ProjectAuthList' - -const getVisibleProjectAuths = (projectAuths, filter) => { - switch (filter) { - case 'SHOW_ALL': - return projectAuths - case 'SHOW_OWN': - return projectAuths.filter((projectAuth) => projectAuth.authorizationLevel === 'OWN') - case 'SHOW_SHARED': - return projectAuths.filter((projectAuth) => projectAuth.authorizationLevel !== 'OWN') - default: - return projectAuths - } -} - -const mapStateToProps = (state) => { - const denormalizedAuthorizations = state.projectList.authorizationsOfCurrentUser.map((authorizationIds) => { - const authorization = state.objects.authorization[authorizationIds] - authorization.user = state.objects.user[authorization.userId] - authorization.project = state.objects.project[authorization.projectId] - return authorization - }) - - return { - authorizations: getVisibleProjectAuths(denormalizedAuthorizations, state.projectList.authVisibilityFilter), - } -} - -const VisibleProjectAuthList = connect(mapStateToProps)(ProjectList) - -export default VisibleProjectAuthList diff --git a/opendc-web/opendc-web-ui/src/data/experiments.js b/opendc-web/opendc-web-ui/src/data/experiments.js new file mode 100644 index 00000000..a76ea53f --- /dev/null +++ b/opendc-web/opendc-web-ui/src/data/experiments.js @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { useQuery } from 'react-query' +import { fetchTraces } from '../api/traces' +import { fetchSchedulers } from '../api/schedulers' + +/** + * Configure the query defaults for the experiment endpoints. + */ +export function configureExperimentClient(queryClient, auth) { + queryClient.setQueryDefaults('traces', { queryFn: () => fetchTraces(auth) }) + queryClient.setQueryDefaults('schedulers', { queryFn: () => fetchSchedulers(auth) }) +} + +/** + * Return the available traces to experiment with. + */ +export function useTraces() { + return useQuery('traces') +} + +/** + * Return the available schedulers to experiment with. + */ +export function useSchedulers() { + return useQuery('schedulers') +} diff --git a/opendc-web/opendc-web-ui/src/data/project.js b/opendc-web/opendc-web-ui/src/data/project.js new file mode 100644 index 00000000..9dcd8532 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/data/project.js @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { useQuery } from 'react-query' +import { addProject, deleteProject, fetchProject, fetchProjects } from '../api/projects' +import { addPortfolio, deletePortfolio, fetchPortfolio, fetchPortfoliosOfProject } from '../api/portfolios' +import { addScenario, deleteScenario, fetchScenario, fetchScenariosOfPortfolio } from '../api/scenarios' + +/** + * Configure the query defaults for the project endpoints. + */ +export function configureProjectClient(queryClient, auth) { + queryClient.setQueryDefaults('projects', { + queryFn: ({ queryKey }) => (queryKey.length === 1 ? fetchProjects(auth) : fetchProject(auth, queryKey[1])), + }) + + queryClient.setMutationDefaults('addProject', { + mutationFn: (data) => addProject(auth, data), + onSuccess: async (result) => { + queryClient.setQueryData('projects', (old = []) => [...old, result]) + queryClient.setQueryData(['projects', result._id], result) + }, + }) + queryClient.setMutationDefaults('deleteProject', { + mutationFn: (id) => deleteProject(auth, id), + onSuccess: async (result) => { + queryClient.setQueryData('projects', (old = []) => old.filter((project) => project._id !== result._id)) + queryClient.removeQueries(['projects', result._id]) + }, + }) + + queryClient.setQueryDefaults('portfolios', { + queryFn: ({ queryKey }) => fetchPortfolio(auth, queryKey[1]), + }) + queryClient.setQueryDefaults('project-portfolios', { + queryFn: ({ queryKey }) => fetchPortfoliosOfProject(auth, queryKey[1]), + }) + queryClient.setMutationDefaults('addPortfolio', { + mutationFn: (data) => addPortfolio(auth, data), + onSuccess: async (result) => { + queryClient.setQueryData(['projects', result.projectId], (old) => ({ + ...old, + portfolioIds: [...old.portfolioIds, result._id], + })) + queryClient.setQueryData(['project-portfolios', result.projectId], (old = []) => [...old, result]) + queryClient.setQueryData(['portfolios', result._id], result) + }, + }) + queryClient.setMutationDefaults('deletePortfolio', { + mutationFn: (id) => deletePortfolio(auth, id), + onSuccess: async (result) => { + queryClient.setQueryData(['projects', result.projectId], (old) => ({ + ...old, + portfolioIds: old.portfolioIds.filter((id) => id !== result._id), + })) + queryClient.setQueryData(['project-portfolios', result.projectId], (old = []) => + old.filter((portfolio) => portfolio._id !== result._id) + ) + queryClient.removeQueries(['portfolios', result._id]) + }, + }) + + queryClient.setQueryDefaults('scenarios', { + queryFn: ({ queryKey }) => fetchScenario(auth, queryKey[1]), + }) + queryClient.setQueryDefaults('portfolio-scenarios', { + queryFn: ({ queryKey }) => fetchScenariosOfPortfolio(auth, queryKey[1]), + }) + queryClient.setMutationDefaults('addScenario', { + mutationFn: (data) => addScenario(auth, data), + onSuccess: async (result) => { + // Register updated scenario in cache + queryClient.setQueryData(['scenarios', result._id], result) + queryClient.setQueryData(['portfolio-scenarios', result.portfolioId], (old = []) => [...old, result]) + + // Add scenario id to portfolio + queryClient.setQueryData(['portfolios', result.portfolioId], (old) => ({ + ...old, + scenarioIds: [...old.scenarioIds, result._id], + })) + }, + }) + queryClient.setMutationDefaults('deleteScenario', { + mutationFn: (id) => deleteScenario(auth, id), + onSuccess: async (result) => { + queryClient.setQueryData(['portfolios', result.portfolioId], (old) => ({ + ...old, + scenarioIds: old.scenarioIds.filter((id) => id !== result._id), + })) + queryClient.setQueryData(['portfolio-scenarios', result.portfolioId], (old = []) => + old.filter((scenario) => scenario._id !== result._id) + ) + queryClient.removeQueries(['scenarios', result._id]) + }, + }) +} + +/** + * Return the available projects. + */ +export function useProjects(options = {}) { + return useQuery('projects', options) +} + +/** + * Return the project with the specified identifier. + */ +export function useProject(projectId, options = {}) { + return useQuery(['projects', projectId], { enabled: !!projectId, ...options }) +} + +/** + * Return the portfolio with the specified identifier. + */ +export function usePortfolio(portfolioId, options = {}) { + return useQuery(['portfolios', portfolioId], { enabled: !!portfolioId, ...options }) +} + +/** + * Return the portfolios of the specified project. + */ +export function useProjectPortfolios(projectId, options = {}) { + return useQuery(['project-portfolios', projectId], { enabled: !!projectId, ...options }) +} + +/** + * Return the scenarios of the specified portfolio. + */ +export function usePortfolioScenarios(portfolioId, options = {}) { + return useQuery(['portfolio-scenarios', portfolioId], { enabled: !!portfolioId, ...options }) +} diff --git a/opendc-web/opendc-web-ui/src/data/query.js b/opendc-web/opendc-web-ui/src/data/query.js new file mode 100644 index 00000000..59eaa684 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/data/query.js @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { useMemo } from 'react' +import { QueryClient } from 'react-query' +import { useAuth } from '../auth' +import { configureExperimentClient } from './experiments' +import { configureProjectClient } from './project' +import { configureTopologyClient } from './topology' + +let queryClient + +function createQueryClient(auth) { + const client = new QueryClient() + configureProjectClient(client, auth) + configureExperimentClient(client, auth) + configureTopologyClient(client, auth) + return client +} + +function initializeQueryClient(auth) { + const _queryClient = queryClient ?? createQueryClient(auth) + + // For SSG and SSR always create a new query client + if (typeof window === 'undefined') return _queryClient + // Create the query client once in the client + if (!queryClient) queryClient = _queryClient + + return _queryClient +} + +/** + * Obtain a cached query client. + */ +export function useNewQueryClient() { + const auth = useAuth() + return useMemo(() => initializeQueryClient(auth), []) // eslint-disable-line react-hooks/exhaustive-deps +} diff --git a/opendc-web/opendc-web-ui/src/data/topology.js b/opendc-web/opendc-web-ui/src/data/topology.js new file mode 100644 index 00000000..e068ed8e --- /dev/null +++ b/opendc-web/opendc-web-ui/src/data/topology.js @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { useQuery } from 'react-query' +import { addTopology, deleteTopology, fetchTopologiesOfProject, fetchTopology, updateTopology } from '../api/topologies' + +/** + * Configure the query defaults for the topology endpoints. + */ +export function configureTopologyClient(queryClient, auth) { + queryClient.setQueryDefaults('topologies', { queryFn: ({ queryKey }) => fetchTopology(auth, queryKey[1]) }) + queryClient.setQueryDefaults('project-topologies', { + queryFn: ({ queryKey }) => fetchTopologiesOfProject(auth, queryKey[1]), + }) + + queryClient.setMutationDefaults('addTopology', { + mutationFn: (data) => addTopology(auth, data), + onSuccess: async (result) => { + queryClient.setQueryData(['projects', result.projectId], (old) => ({ + ...old, + topologyIds: [...old.topologyIds, result._id], + })) + queryClient.setQueryData(['project-topologies', result.projectId], (old = []) => [...old, result]) + queryClient.setQueryData(['topologies', result._id], result) + }, + }) + queryClient.setMutationDefaults('updateTopology', { + mutationFn: (data) => updateTopology(auth, data), + onSuccess: (result) => queryClient.setQueryData(['topologies', result._id], result), + }) + queryClient.setMutationDefaults('deleteTopology', { + mutationFn: (id) => deleteTopology(auth, id), + onSuccess: async (result) => { + queryClient.setQueryData(['projects', result.projectId], (old) => ({ + ...old, + topologyIds: old.topologyIds.filter((id) => id !== result._id), + })) + queryClient.setQueryData(['project-topologies', result.projectId], (old = []) => + old.filter((topology) => topology._id !== result._id) + ) + queryClient.removeQueries(['topologies', result._id]) + }, + }) +} + +/** + * Return the current active topology. + */ +export function useTopology(topologyId, options = {}) { + return useQuery(['topologies', topologyId], { enabled: !!topologyId, ...options }) +} + +/** + * Return the topologies of the specified project. + */ +export function useProjectTopologies(projectId, options = {}) { + return useQuery(['project-topologies', projectId], { enabled: !!projectId, ...options }) +} diff --git a/opendc-web/opendc-web-ui/src/hotkeys.js b/opendc-web/opendc-web-ui/src/hotkeys.js new file mode 100644 index 00000000..1c4d2621 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/hotkeys.js @@ -0,0 +1,6 @@ +export const KeymapConfiguration = { + MOVE_LEFT: ['a', 'left'], + MOVE_RIGHT: ['d', 'right'], + MOVE_UP: ['w', 'up'], + MOVE_DOWN: ['s', 'down'], +} diff --git a/opendc-web/opendc-web-ui/src/index.js b/opendc-web/opendc-web-ui/src/index.js deleted file mode 100644 index ae3a5ddc..00000000 --- a/opendc-web/opendc-web-ui/src/index.js +++ /dev/null @@ -1,32 +0,0 @@ -import React from 'react' -import ReactDOM from 'react-dom' -import * as Sentry from '@sentry/react' -import { Integrations } from '@sentry/tracing' -import { Provider } from 'react-redux' -import { setupSocketConnection } from './api/socket' -import './index.sass' -import Routes from './routes' -import config from './config' -import configureStore from './store/configure-store' - -setupSocketConnection(() => { - const store = configureStore() - - // Initialize Sentry if the user has configured a DSN - const dsn = config['SENTRY_DSN'] - if (dsn) { - Sentry.init({ - environment: process.env.NODE_ENV, - dsn: dsn, - integrations: [new Integrations.BrowserTracing()], - tracesSampleRate: 0.1, - }) - } - - ReactDOM.render( - <Provider store={store}> - <Routes /> - </Provider>, - document.getElementById('root') - ) -}) diff --git a/opendc-web/opendc-web-ui/src/index.sass b/opendc-web/opendc-web-ui/src/index.sass deleted file mode 100644 index a78f7a19..00000000 --- a/opendc-web/opendc-web-ui/src/index.sass +++ /dev/null @@ -1,52 +0,0 @@ -@import "~bootstrap/scss/bootstrap" - -@import ./style-globals/_mixins.sass -@import ./style-globals/_variables.sass - -html, body, #root - margin: 0 - padding: 0 - width: 100% - height: 100% - - font-family: Roboto, Helvetica, Verdana, sans-serif - background: #eee - - // Scroll padding for top navbar - scroll-padding-top: 60px - -.full-height - position: relative - height: 100% !important - -.page-container - padding-top: 60px - -.text-page-container - padding-top: 80px - display: flex - flex-flow: column - -.vertically-expanding-container - flex: 1 1 auto - overflow-y: auto - -.bottom-btn-container - flex: 0 1 auto - padding: 20px 0 - -.btn, .list-group-item-action, .clickable - +clickable - -.btn-circle - +border-radius(50%) - -a, a:hover - text-decoration: none - -.app-page-container - padding-left: $side-bar-width - padding-top: 15px - -.w-70 - width: 70% !important diff --git a/opendc-web/opendc-web-ui/src/pages/404.js b/opendc-web/opendc-web-ui/src/pages/404.js new file mode 100644 index 00000000..0939bc56 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/pages/404.js @@ -0,0 +1,38 @@ +import React from 'react' +import Head from 'next/head' +import { AppPage } from '../components/AppPage' +import { + Bullseye, + EmptyState, + EmptyStateBody, + EmptyStateIcon, + PageSection, + PageSectionVariants, + Title, +} from '@patternfly/react-core' +import { UnknownIcon } from '@patternfly/react-icons' + +const NotFound = () => { + return ( + <AppPage> + <Head> + <title>Page Not Found - OpenDC</title> + </Head> + <PageSection variant={PageSectionVariants.light}> + <Bullseye> + <EmptyState> + <EmptyStateIcon variant="container" component={UnknownIcon} /> + <Title size="lg" headingLevel="h4"> + 404: That page does not exist + </Title> + <EmptyStateBody> + The requested page is not found. Try refreshing the page if it was recently added. + </EmptyStateBody> + </EmptyState> + </Bullseye> + </PageSection> + </AppPage> + ) +} + +export default NotFound diff --git a/opendc-web/opendc-web-ui/src/pages/App.js b/opendc-web/opendc-web-ui/src/pages/App.js deleted file mode 100644 index cbc805b8..00000000 --- a/opendc-web/opendc-web-ui/src/pages/App.js +++ /dev/null @@ -1,137 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import DocumentTitle from 'react-document-title' -import { connect } from 'react-redux' -import { ShortcutManager } from 'react-shortcuts' -import { openPortfolioSucceeded } from '../actions/portfolios' -import { openProjectSucceeded } from '../actions/projects' -import ToolPanelComponent from '../components/app/map/controls/ToolPanelComponent' -import LoadingScreen from '../components/app/map/LoadingScreen' -import ScaleIndicatorContainer from '../containers/app/map/controls/ScaleIndicatorContainer' -import MapStage from '../containers/app/map/MapStage' -import TopologySidebarContainer from '../containers/app/sidebars/topology/TopologySidebarContainer' -import DeleteMachineModal from '../containers/modals/DeleteMachineModal' -import DeleteRackModal from '../containers/modals/DeleteRackModal' -import DeleteRoomModal from '../containers/modals/DeleteRoomModal' -import EditRackNameModal from '../containers/modals/EditRackNameModal' -import EditRoomNameModal from '../containers/modals/EditRoomNameModal' -import KeymapConfiguration from '../shortcuts/keymap' -import NewTopologyModal from '../containers/modals/NewTopologyModal' -import AppNavbarContainer from '../containers/navigation/AppNavbarContainer' -import ProjectSidebarContainer from '../containers/app/sidebars/project/ProjectSidebarContainer' -import { openScenarioSucceeded } from '../actions/scenarios' -import NewPortfolioModal from '../containers/modals/NewPortfolioModal' -import NewScenarioModal from '../containers/modals/NewScenarioModal' -import PortfolioResultsContainer from '../containers/app/results/PortfolioResultsContainer' - -const shortcutManager = new ShortcutManager(KeymapConfiguration) - -class AppComponent extends React.Component { - static propTypes = { - projectId: PropTypes.string.isRequired, - portfolioId: PropTypes.string, - scenarioId: PropTypes.string, - projectName: PropTypes.string, - } - static childContextTypes = { - shortcuts: PropTypes.object.isRequired, - } - - componentDidMount() { - if (this.props.scenarioId) { - this.props.openScenarioSucceeded(this.props.projectId, this.props.portfolioId, this.props.scenarioId) - } else if (this.props.portfolioId) { - this.props.openPortfolioSucceeded(this.props.projectId, this.props.portfolioId) - } else { - this.props.openProjectSucceeded(this.props.projectId) - } - } - - getChildContext() { - return { - shortcuts: shortcutManager, - } - } - - render() { - const constructionElements = this.props.topologyIsLoading ? ( - <div className="full-height d-flex align-items-center justify-content-center"> - <LoadingScreen /> - </div> - ) : ( - <div className="full-height"> - <MapStage /> - <ScaleIndicatorContainer /> - <ToolPanelComponent /> - <ProjectSidebarContainer /> - <TopologySidebarContainer /> - </div> - ) - - const portfolioElements = ( - <div className="full-height app-page-container"> - <ProjectSidebarContainer /> - <div className="container-fluid full-height"> - <PortfolioResultsContainer /> - </div> - </div> - ) - - const scenarioElements = ( - <div className="full-height app-page-container"> - <ProjectSidebarContainer /> - <div className="container-fluid full-height"> - <h2>Scenario loading</h2> - </div> - </div> - ) - - return ( - <DocumentTitle - title={this.props.projectName ? this.props.projectName + ' - OpenDC' : 'Simulation - OpenDC'} - > - <div className="page-container full-height"> - <AppNavbarContainer fullWidth={true} /> - {this.props.scenarioId - ? scenarioElements - : this.props.portfolioId - ? portfolioElements - : constructionElements} - <NewTopologyModal /> - <NewPortfolioModal /> - <NewScenarioModal /> - <EditRoomNameModal /> - <DeleteRoomModal /> - <EditRackNameModal /> - <DeleteRackModal /> - <DeleteMachineModal /> - </div> - </DocumentTitle> - ) - } -} - -const mapStateToProps = (state) => { - let projectName = undefined - if (state.currentProjectId !== '-1' && state.objects.project[state.currentProjectId]) { - projectName = state.objects.project[state.currentProjectId].name - } - - return { - topologyIsLoading: state.currentTopologyId === '-1', - projectName, - } -} - -const mapDispatchToProps = (dispatch) => { - return { - openProjectSucceeded: (projectId) => dispatch(openProjectSucceeded(projectId)), - openPortfolioSucceeded: (projectId, portfolioId) => dispatch(openPortfolioSucceeded(projectId, portfolioId)), - openScenarioSucceeded: (projectId, portfolioId, scenarioId) => - dispatch(openScenarioSucceeded(projectId, portfolioId, scenarioId)), - } -} - -const App = connect(mapStateToProps, mapDispatchToProps)(AppComponent) - -export default App diff --git a/opendc-web/opendc-web-ui/src/pages/Home.js b/opendc-web/opendc-web-ui/src/pages/Home.js deleted file mode 100644 index 6fc940c0..00000000 --- a/opendc-web/opendc-web-ui/src/pages/Home.js +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react' -import DocumentTitle from 'react-document-title' -import ContactSection from '../components/home/ContactSection' -import IntroSection from '../components/home/IntroSection' -import JumbotronHeader from '../components/home/JumbotronHeader' -import ModelingSection from '../components/home/ModelingSection' -import SimulationSection from '../components/home/SimulationSection' -import StakeholderSection from '../components/home/StakeholderSection' -import TeamSection from '../components/home/TeamSection' -import TechnologiesSection from '../components/home/TechnologiesSection' -import HomeNavbar from '../components/navigation/HomeNavbar' -import './Home.sass' - -function Home() { - return ( - <div> - <HomeNavbar /> - <div className="body-wrapper page-container"> - <JumbotronHeader /> - <IntroSection /> - <StakeholderSection /> - <ModelingSection /> - <SimulationSection /> - <TechnologiesSection /> - <TeamSection /> - <ContactSection /> - <DocumentTitle title="OpenDC" /> - </div> - </div> - ) -} - -export default Home diff --git a/opendc-web/opendc-web-ui/src/pages/Home.sass b/opendc-web/opendc-web-ui/src/pages/Home.sass deleted file mode 100644 index 79cb9698..00000000 --- a/opendc-web/opendc-web-ui/src/pages/Home.sass +++ /dev/null @@ -1,9 +0,0 @@ -.body-wrapper - position: relative - overflow-y: hidden - -.intro-section, .modeling-section, .technologies-section - background-color: #fff - -.stakeholder-section, .simulation-section, .team-section - background-color: #f2f2f2 diff --git a/opendc-web/opendc-web-ui/src/pages/NotFound.js b/opendc-web/opendc-web-ui/src/pages/NotFound.js deleted file mode 100644 index 72be7342..00000000 --- a/opendc-web/opendc-web-ui/src/pages/NotFound.js +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react' -import DocumentTitle from 'react-document-title' -import TerminalWindow from '../components/not-found/TerminalWindow' -import './NotFound.sass' - -const NotFound = () => ( - <DocumentTitle title="Page Not Found - OpenDC"> - <div className="not-found-backdrop"> - <TerminalWindow /> - </div> - </DocumentTitle> -) - -export default NotFound diff --git a/opendc-web/opendc-web-ui/src/pages/NotFound.sass b/opendc-web/opendc-web-ui/src/pages/NotFound.sass deleted file mode 100644 index 59231f7a..00000000 --- a/opendc-web/opendc-web-ui/src/pages/NotFound.sass +++ /dev/null @@ -1,11 +0,0 @@ -.not-found-backdrop - position: absolute - left: 0 - top: 0 - - margin: 0 - padding: 0 - width: 100% - height: 100% - - background-image: linear-gradient(135deg, #00678a, #008fbf, #00A6D6) diff --git a/opendc-web/opendc-web-ui/src/pages/Profile.js b/opendc-web/opendc-web-ui/src/pages/Profile.js deleted file mode 100644 index 0d94b519..00000000 --- a/opendc-web/opendc-web-ui/src/pages/Profile.js +++ /dev/null @@ -1,35 +0,0 @@ -import React from 'react' -import DocumentTitle from 'react-document-title' -import { connect } from 'react-redux' -import { openDeleteProfileModal } from '../actions/modals/profile' -import DeleteProfileModal from '../containers/modals/DeleteProfileModal' -import AppNavbarContainer from '../containers/navigation/AppNavbarContainer' - -const ProfileContainer = ({ onDelete }) => ( - <DocumentTitle title="My Profile - OpenDC"> - <div className="full-height"> - <AppNavbarContainer fullWidth={false} /> - <div className="container text-page-container full-height"> - <button className="btn btn-danger mb-2 ml-auto mr-auto" style={{ maxWidth: 300 }} onClick={onDelete}> - Delete my account on OpenDC - </button> - <p className="text-muted text-center"> - This does not delete your Google account, but simply disconnects it from the OpenDC platform and - deletes any project info that is associated with you (projects you own and any authorizations you - may have on other projects). - </p> - </div> - <DeleteProfileModal /> - </div> - </DocumentTitle> -) - -const mapDispatchToProps = (dispatch) => { - return { - onDelete: () => dispatch(openDeleteProfileModal()), - } -} - -const Profile = connect(undefined, mapDispatchToProps)(ProfileContainer) - -export default Profile diff --git a/opendc-web/opendc-web-ui/src/pages/Projects.js b/opendc-web/opendc-web-ui/src/pages/Projects.js deleted file mode 100644 index bb54aaa5..00000000 --- a/opendc-web/opendc-web-ui/src/pages/Projects.js +++ /dev/null @@ -1,43 +0,0 @@ -import React from 'react' -import DocumentTitle from 'react-document-title' -import { connect } from 'react-redux' -import { openNewProjectModal } from '../actions/modals/projects' -import { fetchAuthorizationsOfCurrentUser } from '../actions/users' -import ProjectFilterPanel from '../components/projects/FilterPanel' -import NewProjectModal from '../containers/modals/NewProjectModal' -import NewProjectButtonContainer from '../containers/projects/NewProjectButtonContainer' -import VisibleProjectList from '../containers/projects/VisibleProjectAuthList' -import AppNavbarContainer from '../containers/navigation/AppNavbarContainer' - -class ProjectsContainer extends React.Component { - componentDidMount() { - this.props.fetchAuthorizationsOfCurrentUser() - } - - render() { - return ( - <DocumentTitle title="My Projects - OpenDC"> - <div className="full-height"> - <AppNavbarContainer fullWidth={false} /> - <div className="container text-page-container full-height"> - <ProjectFilterPanel /> - <VisibleProjectList /> - <NewProjectButtonContainer /> - </div> - <NewProjectModal /> - </div> - </DocumentTitle> - ) - } -} - -const mapDispatchToProps = (dispatch) => { - return { - fetchAuthorizationsOfCurrentUser: () => dispatch(fetchAuthorizationsOfCurrentUser()), - openNewProjectModal: () => dispatch(openNewProjectModal()), - } -} - -const Projects = connect(undefined, mapDispatchToProps)(ProjectsContainer) - -export default Projects diff --git a/opendc-web/opendc-web-ui/src/pages/_app.js b/opendc-web/opendc-web-ui/src/pages/_app.js new file mode 100644 index 00000000..900ff405 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/pages/_app.js @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import Head from 'next/head' +import { Provider } from 'react-redux' +import { useNewQueryClient } from '../data/query' +import { useStore } from '../redux' +import { AuthProvider, useRequireAuth } from '../auth' +import * as Sentry from '@sentry/react' +import { Integrations } from '@sentry/tracing' +import { QueryClientProvider } from 'react-query' + +import '@patternfly/react-core/dist/styles/base.css' +import '@patternfly/react-styles/css/utilities/Alignment/alignment.css' +import '@patternfly/react-styles/css/utilities/BackgroundColor/BackgroundColor.css' +import '@patternfly/react-styles/css/utilities/BoxShadow/box-shadow.css' +import '@patternfly/react-styles/css/utilities/Display/display.css' +import '@patternfly/react-styles/css/utilities/Flex/flex.css' +import '@patternfly/react-styles/css/utilities/Float/float.css' +import '@patternfly/react-styles/css/utilities/Sizing/sizing.css' +import '@patternfly/react-styles/css/utilities/Spacing/spacing.css' +import '@patternfly/react-styles/css/utilities/Text/text.css' +import '@patternfly/react-styles/css/components/InlineEdit/inline-edit.css' +import '../style/index.scss' + +// This setup is necessary to forward the Auth0 context to the Redux context +function Inner({ Component, pageProps }) { + // Force user to be authorized + useRequireAuth() + + const queryClient = useNewQueryClient() + const store = useStore(pageProps.initialReduxState, { queryClient }) + return ( + <QueryClientProvider client={queryClient}> + <Provider store={store}> + <Component {...pageProps} /> + </Provider> + </QueryClientProvider> + ) +} + +Inner.propTypes = { + Component: PropTypes.func, + pageProps: PropTypes.shape({ + initialReduxState: PropTypes.object, + }).isRequired, +} + +const dsn = process.env.NEXT_PUBLIC_SENTRY_DSN +// Initialize Sentry if the user has configured a DSN +if (process.browser && dsn) { + if (dsn) { + Sentry.init({ + environment: process.env.NODE_ENV, + dsn: dsn, + integrations: [new Integrations.BrowserTracing()], + tracesSampleRate: 0.1, + }) + } +} + +export default function App(props) { + return ( + <> + <Head> + <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" /> + <meta name="theme-color" content="#00A6D6" /> + </Head> + <Sentry.ErrorBoundary fallback={'An error has occurred'}> + <AuthProvider> + <Inner {...props} /> + </AuthProvider> + </Sentry.ErrorBoundary> + </> + ) +} diff --git a/opendc-web/opendc-web-ui/src/pages/_document.js b/opendc-web/opendc-web-ui/src/pages/_document.js new file mode 100644 index 00000000..51d8d3e0 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/pages/_document.js @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import Document, { Html, Head, Main, NextScript } from 'next/document' + +class OpenDCDocument extends Document { + render() { + return ( + <Html lang="en"> + <Head> + <meta charSet="utf-8" /> + <meta name="theme-color" content="#00A6D6" /> + <meta + name="description" + content="Collaborative Datacenter Simulation and Exploration for Everybody" + /> + <meta name="author" content="@Large Research" /> + <meta + name="keywords" + content="OpenDC, Datacenter, Simulation, Simulator, Collaborative, Distributed, Cluster" + /> + <link rel="manifest" href="/manifest.json" /> + <link rel="shortcut icon" href="/favicon.ico" /> + + {/* Twitter Card data */} + <meta name="twitter:card" content="summary" /> + <meta name="twitter:site" content="@LargeResearch" /> + <meta name="twitter:title" content="OpenDC" /> + <meta + name="twitter:description" + content="Collaborative Datacenter Simulation and Exploration for Everybody" + /> + <meta name="twitter:creator" content="@LargeResearch" /> + <meta name="twitter:image" content="http://opendc.org/img/logo.png" /> + + {/* OpenGraph meta tags */} + <meta property="og:title" content="OpenDC" /> + <meta property="og:site_name" content="OpenDC" /> + <meta property="og:type" content="website" /> + <meta property="og:image" content="http://opendc.org/img/logo.png" /> + <meta property="og:url" content="http://opendc.org/" /> + <meta + property="og:description" + content="OpenDC provides collaborative online datacenter modeling, diverse and effective datacenter simulation, and exploratory datacenter performance feedback." + /> + <meta property="og:locale" content="en_US" /> + + {/* CDN Dependencies */} + <link + href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" + rel="stylesheet" + /> + + {/* Google Analytics */} + <script async src="https://www.googletagmanager.com/gtag/js?id=UA-84285092-3" /> + <script + dangerouslySetInnerHTML={{ + __html: ` + window.dataLayer = window.dataLayer || []; + function gtag(){dataLayer.push(arguments);} + gtag('js', new Date()); + gtag('config', 'UA-84285092-3'); + `, + }} + /> + </Head> + <body> + <Main /> + <NextScript /> + </body> + </Html> + ) + } +} + +export default OpenDCDocument diff --git a/opendc-web/opendc-web-ui/src/pages/logout.js b/opendc-web/opendc-web-ui/src/pages/logout.js new file mode 100644 index 00000000..38d5968e --- /dev/null +++ b/opendc-web/opendc-web-ui/src/pages/logout.js @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React from 'react' +import Head from 'next/head' +import { AppPage } from '../components/AppPage' +import { PageSection, PageSectionVariants } from '@patternfly/react-core' + +function Logout() { + return ( + <AppPage> + <Head> + <title>Logged Out - OpenDC</title> + </Head> + <PageSection variant={PageSectionVariants.light}>Logged out successfully</PageSection> + </AppPage> + ) +} + +export default Logout diff --git a/opendc-web/opendc-web-ui/src/pages/projects/[project]/index.js b/opendc-web/opendc-web-ui/src/pages/projects/[project]/index.js new file mode 100644 index 00000000..c07a2c31 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/pages/projects/[project]/index.js @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { useRouter } from 'next/router' +import ContextSelectionSection from '../../../components/context/ContextSelectionSection' +import ProjectOverview from '../../../components/projects/ProjectOverview' +import ProjectSelector from '../../../components/context/ProjectSelector' +import { useProject } from '../../../data/project' +import { AppPage } from '../../../components/AppPage' +import Head from 'next/head' +import { + Breadcrumb, + BreadcrumbItem, + PageSection, + PageSectionVariants, + Skeleton, + Text, + TextContent, +} from '@patternfly/react-core' +import BreadcrumbLink from '../../../components/util/BreadcrumbLink' + +function Project() { + const router = useRouter() + const { project: projectId } = router.query + + const { data: project } = useProject(projectId) + + const breadcrumb = ( + <Breadcrumb> + <BreadcrumbItem to="/projects" component={BreadcrumbLink}> + Projects + </BreadcrumbItem> + <BreadcrumbItem to={`/projects/${projectId}`} component={BreadcrumbLink} isActive> + Project details + </BreadcrumbItem> + </Breadcrumb> + ) + + const contextSelectors = ( + <ContextSelectionSection> + <ProjectSelector projectId={projectId} /> + </ContextSelectionSection> + ) + + return ( + <AppPage breadcrumb={breadcrumb} contextSelectors={contextSelectors}> + <Head> + <title>{project?.name ?? 'Project'} - OpenDC</title> + </Head> + <PageSection variant={PageSectionVariants.light}> + <TextContent> + <Text component="h1"> + {project?.name ?? <Skeleton width="15%" screenreaderText="Loading project" />} + </Text> + </TextContent> + </PageSection> + <PageSection isFilled> + <ProjectOverview projectId={projectId} /> + </PageSection> + </AppPage> + ) +} + +export default Project diff --git a/opendc-web/opendc-web-ui/src/pages/projects/[project]/portfolios/[portfolio].js b/opendc-web/opendc-web-ui/src/pages/projects/[project]/portfolios/[portfolio].js new file mode 100644 index 00000000..d1533d98 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/pages/projects/[project]/portfolios/[portfolio].js @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { useRouter } from 'next/router' +import Head from 'next/head' +import React, { useRef } from 'react' +import { + Breadcrumb, + BreadcrumbItem, + Divider, + PageSection, + PageSectionVariants, + Tab, + TabContent, + Tabs, + TabTitleText, + Text, + TextContent, +} from '@patternfly/react-core' +import { AppPage } from '../../../../components/AppPage' +import ContextSelectionSection from '../../../../components/context/ContextSelectionSection' +import PortfolioSelector from '../../../../components/context/PortfolioSelector' +import ProjectSelector from '../../../../components/context/ProjectSelector' +import BreadcrumbLink from '../../../../components/util/BreadcrumbLink' +import PortfolioOverview from '../../../../components/portfolios/PortfolioOverview' +import PortfolioResults from '../../../../components/portfolios/PortfolioResults' + +/** + * Page that displays the results in a portfolio. + */ +function Portfolio() { + const router = useRouter() + const { project: projectId, portfolio: portfolioId } = router.query + + const overviewRef = useRef(null) + const resultsRef = useRef(null) + + const breadcrumb = ( + <Breadcrumb> + <BreadcrumbItem to="/projects" component={BreadcrumbLink}> + Projects + </BreadcrumbItem> + <BreadcrumbItem to={`/projects/${projectId}`} component={BreadcrumbLink}> + Project details + </BreadcrumbItem> + <BreadcrumbItem to={`/projects/${projectId}/portfolios/${portfolioId}`} component={BreadcrumbLink} isActive> + Portfolio + </BreadcrumbItem> + </Breadcrumb> + ) + + const contextSelectors = ( + <ContextSelectionSection> + <ProjectSelector projectId={projectId} /> + <PortfolioSelector projectId={projectId} portfolioId={portfolioId} /> + </ContextSelectionSection> + ) + + return ( + <AppPage breadcrumb={breadcrumb} contextSelectors={contextSelectors}> + <Head> + <title>Portfolio - OpenDC</title> + </Head> + <PageSection variant={PageSectionVariants.light}> + <TextContent> + <Text component="h1">Portfolio</Text> + </TextContent> + </PageSection> + <PageSection type="none" variant={PageSectionVariants.light} className="pf-c-page__main-tabs" sticky="top"> + <Divider component="div" /> + <Tabs defaultActiveKey={0} className="pf-m-page-insets"> + <Tab + eventKey={0} + title={<TabTitleText>Overview</TabTitleText>} + tabContentId="overview" + tabContentRef={overviewRef} + /> + <Tab + eventKey={1} + title={<TabTitleText>Results</TabTitleText>} + tabContentId="results" + tabContentRef={resultsRef} + /> + </Tabs> + </PageSection> + <PageSection isFilled> + <TabContent eventKey={0} id="overview" ref={overviewRef} aria-label="Overview tab"> + <PortfolioOverview portfolioId={portfolioId} /> + </TabContent> + <TabContent eventKey={1} id="results" ref={resultsRef} aria-label="Results tab" hidden> + <PortfolioResults portfolioId={portfolioId} /> + </TabContent> + </PageSection> + </AppPage> + ) +} + +export default Portfolio diff --git a/opendc-web/opendc-web-ui/src/pages/projects/[project]/topologies/[topology].js b/opendc-web/opendc-web-ui/src/pages/projects/[project]/topologies/[topology].js new file mode 100644 index 00000000..858f9b16 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/pages/projects/[project]/topologies/[topology].js @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { useRouter } from 'next/router' +import ContextSelectionSection from '../../../../components/context/ContextSelectionSection' +import ProjectSelector from '../../../../components/context/ProjectSelector' +import TopologySelector from '../../../../components/context/TopologySelector' +import TopologyOverview from '../../../../components/topologies/TopologyOverview' +import { useProject } from '../../../../data/project' +import { useDispatch } from 'react-redux' +import React, { useEffect, useState } from 'react' +import Head from 'next/head' +import { AppPage } from '../../../../components/AppPage' +import { + Breadcrumb, + BreadcrumbItem, + Divider, + PageSection, + PageSectionVariants, + Tab, + TabContent, + Tabs, + TabTitleText, + Text, + TextContent, +} from '@patternfly/react-core' +import BreadcrumbLink from '../../../../components/util/BreadcrumbLink' +import TopologyMap from '../../../../components/topologies/TopologyMap' +import { goToRoom } from '../../../../redux/actions/interaction-level' +import { openTopology } from '../../../../redux/actions/topologies' + +/** + * Page that displays a datacenter topology. + */ +function Topology() { + const router = useRouter() + const { project: projectId, topology: topologyId } = router.query + + const { data: project } = useProject(projectId) + + const dispatch = useDispatch() + useEffect(() => { + if (topologyId) { + dispatch(openTopology(topologyId)) + } + }, [topologyId, dispatch]) + + const [activeTab, setActiveTab] = useState('overview') + + const breadcrumb = ( + <Breadcrumb> + <BreadcrumbItem to="/projects" component={BreadcrumbLink}> + Projects + </BreadcrumbItem> + <BreadcrumbItem to={`/projects/${projectId}`} component={BreadcrumbLink}> + Project details + </BreadcrumbItem> + <BreadcrumbItem to={`/projects/${projectId}/topologies/${topologyId}`} component={BreadcrumbLink} isActive> + Topology + </BreadcrumbItem> + </Breadcrumb> + ) + + const contextSelectors = ( + <ContextSelectionSection> + <ProjectSelector projectId={projectId} /> + <TopologySelector projectId={projectId} topologyId={topologyId} /> + </ContextSelectionSection> + ) + + return ( + <AppPage breadcrumb={breadcrumb} contextSelectors={contextSelectors}> + <Head> + <title>{project?.name ?? 'Topologies'} - OpenDC</title> + </Head> + <PageSection variant={PageSectionVariants.light}> + <TextContent> + <Text component="h1">Topology</Text> + </TextContent> + </PageSection> + <PageSection type="none" variant={PageSectionVariants.light} className="pf-c-page__main-tabs" sticky="top"> + <Divider component="div" /> + <Tabs + activeKey={activeTab} + onSelect={(_, tabIndex) => setActiveTab(tabIndex)} + className="pf-m-page-insets" + > + <Tab eventKey="overview" title={<TabTitleText>Overview</TabTitleText>} tabContentId="overview" /> + <Tab + eventKey="floor-plan" + title={<TabTitleText>Floor Plan</TabTitleText>} + tabContentId="floor-plan" + /> + </Tabs> + </PageSection> + <PageSection padding={activeTab === 'floor-plan' && { default: 'noPadding' }} isFilled> + <TabContent id="overview" aria-label="Overview tab" hidden={activeTab !== 'overview'}> + <TopologyOverview + topologyId={topologyId} + onSelect={(type, obj) => { + if (type === 'room') { + dispatch(goToRoom(obj._id)) + setActiveTab('floor-plan') + } + }} + /> + </TabContent> + <TabContent + id="floor-plan" + aria-label="Floor Plan tab" + className="pf-u-h-100" + hidden={activeTab !== 'floor-plan'} + > + <TopologyMap /> + </TabContent> + </PageSection> + </AppPage> + ) +} + +export default Topology diff --git a/opendc-web/opendc-web-ui/src/pages/projects/index.js b/opendc-web/opendc-web-ui/src/pages/projects/index.js new file mode 100644 index 00000000..eb77701e --- /dev/null +++ b/opendc-web/opendc-web-ui/src/pages/projects/index.js @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React, { useMemo, useState } from 'react' +import Head from 'next/head' +import ProjectFilterPanel from '../../components/projects/FilterPanel' +import { useAuth } from '../../auth' +import { AppPage } from '../../components/AppPage' +import { PageSection, PageSectionVariants, Text, TextContent } from '@patternfly/react-core' +import { useProjects } from '../../data/project' +import ProjectTable from '../../components/projects/ProjectTable' +import { useMutation } from 'react-query' +import NewProject from '../../components/projects/NewProject' + +const getVisibleProjects = (projects, filter, userId) => { + switch (filter) { + case 'SHOW_ALL': + return projects + case 'SHOW_OWN': + return projects.filter((project) => + project.authorizations.some((a) => a.userId === userId && a.level === 'OWN') + ) + case 'SHOW_SHARED': + return projects.filter((project) => + project.authorizations.some((a) => a.userId === userId && a.level !== 'OWN') + ) + default: + return projects + } +} + +function Projects() { + const { user } = useAuth() + const { status, data: projects } = useProjects() + const [filter, setFilter] = useState('SHOW_ALL') + const visibleProjects = useMemo(() => getVisibleProjects(projects ?? [], filter, user?.sub), [ + projects, + filter, + user?.sub, + ]) + + const { mutate: deleteProject } = useMutation('deleteProject') + + return ( + <AppPage> + <Head> + <title>My Projects - OpenDC</title> + </Head> + <PageSection variant={PageSectionVariants.light}> + <TextContent> + <Text component="h1">My Projects</Text> + </TextContent> + </PageSection> + <PageSection variant={PageSectionVariants.light} isFilled> + <ProjectFilterPanel onSelect={setFilter} activeFilter={filter} /> + <ProjectTable + status={status} + isFiltering={filter !== 'SHOW_ALL'} + projects={visibleProjects} + onDelete={(project) => deleteProject(project._id)} + /> + <NewProject /> + </PageSection> + </AppPage> + ) +} + +export default Projects diff --git a/opendc-web/opendc-web-ui/src/reducers/auth.js b/opendc-web/opendc-web-ui/src/reducers/auth.js deleted file mode 100644 index 399a4b10..00000000 --- a/opendc-web/opendc-web-ui/src/reducers/auth.js +++ /dev/null @@ -1,12 +0,0 @@ -import { LOG_IN_SUCCEEDED, LOG_OUT } from '../actions/auth' - -export function auth(state = {}, action) { - switch (action.type) { - case LOG_IN_SUCCEEDED: - return action.payload - case LOG_OUT: - return {} - default: - return state - } -} diff --git a/opendc-web/opendc-web-ui/src/reducers/construction-mode.js b/opendc-web/opendc-web-ui/src/reducers/construction-mode.js deleted file mode 100644 index 257dddd2..00000000 --- a/opendc-web/opendc-web-ui/src/reducers/construction-mode.js +++ /dev/null @@ -1,52 +0,0 @@ -import { combineReducers } from 'redux' -import { GO_DOWN_ONE_INTERACTION_LEVEL } from '../actions/interaction-level' -import { - CANCEL_NEW_ROOM_CONSTRUCTION_SUCCEEDED, - FINISH_NEW_ROOM_CONSTRUCTION, - FINISH_ROOM_EDIT, - SET_CURRENT_TOPOLOGY, - START_NEW_ROOM_CONSTRUCTION_SUCCEEDED, - START_ROOM_EDIT, -} from '../actions/topology/building' -import { DELETE_ROOM, START_RACK_CONSTRUCTION, STOP_RACK_CONSTRUCTION } from '../actions/topology/room' -import { OPEN_PORTFOLIO_SUCCEEDED } from '../actions/portfolios' -import { OPEN_SCENARIO_SUCCEEDED } from '../actions/scenarios' - -export function currentRoomInConstruction(state = '-1', action) { - switch (action.type) { - case START_NEW_ROOM_CONSTRUCTION_SUCCEEDED: - return action.roomId - case START_ROOM_EDIT: - return action.roomId - case CANCEL_NEW_ROOM_CONSTRUCTION_SUCCEEDED: - case FINISH_NEW_ROOM_CONSTRUCTION: - case OPEN_PORTFOLIO_SUCCEEDED: - case OPEN_SCENARIO_SUCCEEDED: - case FINISH_ROOM_EDIT: - case SET_CURRENT_TOPOLOGY: - case DELETE_ROOM: - return '-1' - default: - return state - } -} - -export function inRackConstructionMode(state = false, action) { - switch (action.type) { - case START_RACK_CONSTRUCTION: - return true - case STOP_RACK_CONSTRUCTION: - case OPEN_PORTFOLIO_SUCCEEDED: - case OPEN_SCENARIO_SUCCEEDED: - case SET_CURRENT_TOPOLOGY: - case GO_DOWN_ONE_INTERACTION_LEVEL: - return false - default: - return state - } -} - -export const construction = combineReducers({ - currentRoomInConstruction, - inRackConstructionMode, -}) diff --git a/opendc-web/opendc-web-ui/src/reducers/current-ids.js b/opendc-web/opendc-web-ui/src/reducers/current-ids.js deleted file mode 100644 index 9b46aa60..00000000 --- a/opendc-web/opendc-web-ui/src/reducers/current-ids.js +++ /dev/null @@ -1,54 +0,0 @@ -import { OPEN_PORTFOLIO_SUCCEEDED, SET_CURRENT_PORTFOLIO } from '../actions/portfolios' -import { OPEN_PROJECT_SUCCEEDED } from '../actions/projects' -import { SET_CURRENT_TOPOLOGY } from '../actions/topology/building' -import { OPEN_SCENARIO_SUCCEEDED, SET_CURRENT_SCENARIO } from '../actions/scenarios' - -export function currentTopologyId(state = '-1', action) { - switch (action.type) { - case SET_CURRENT_TOPOLOGY: - return action.topologyId - default: - return state - } -} - -export function currentProjectId(state = '-1', action) { - switch (action.type) { - case OPEN_PROJECT_SUCCEEDED: - return action.id - case OPEN_PORTFOLIO_SUCCEEDED: - case OPEN_SCENARIO_SUCCEEDED: - return action.projectId - default: - return state - } -} - -export function currentPortfolioId(state = '-1', action) { - switch (action.type) { - case OPEN_PORTFOLIO_SUCCEEDED: - case SET_CURRENT_PORTFOLIO: - case SET_CURRENT_SCENARIO: - return action.portfolioId - case OPEN_SCENARIO_SUCCEEDED: - return action.portfolioId - case OPEN_PROJECT_SUCCEEDED: - case SET_CURRENT_TOPOLOGY: - return '-1' - default: - return state - } -} -export function currentScenarioId(state = '-1', action) { - switch (action.type) { - case OPEN_SCENARIO_SUCCEEDED: - case SET_CURRENT_SCENARIO: - return action.scenarioId - case OPEN_PORTFOLIO_SUCCEEDED: - case SET_CURRENT_TOPOLOGY: - case OPEN_PROJECT_SUCCEEDED: - return '-1' - default: - return state - } -} diff --git a/opendc-web/opendc-web-ui/src/reducers/index.js b/opendc-web/opendc-web-ui/src/reducers/index.js deleted file mode 100644 index 787d5a74..00000000 --- a/opendc-web/opendc-web-ui/src/reducers/index.js +++ /dev/null @@ -1,25 +0,0 @@ -import { combineReducers } from 'redux' -import { auth } from './auth' -import { construction } from './construction-mode' -import { currentPortfolioId, currentProjectId, currentScenarioId, currentTopologyId } from './current-ids' -import { interactionLevel } from './interaction-level' -import { map } from './map' -import { modals } from './modals' -import { objects } from './objects' -import { projectList } from './project-list' - -const rootReducer = combineReducers({ - objects, - modals, - projectList, - construction, - map, - currentProjectId, - currentTopologyId, - currentPortfolioId, - currentScenarioId, - interactionLevel, - auth, -}) - -export default rootReducer diff --git a/opendc-web/opendc-web-ui/src/reducers/interaction-level.js b/opendc-web/opendc-web-ui/src/reducers/interaction-level.js deleted file mode 100644 index eafcb269..00000000 --- a/opendc-web/opendc-web-ui/src/reducers/interaction-level.js +++ /dev/null @@ -1,61 +0,0 @@ -import { OPEN_PORTFOLIO_SUCCEEDED } from '../actions/portfolios' -import { - GO_DOWN_ONE_INTERACTION_LEVEL, - GO_FROM_BUILDING_TO_ROOM, - GO_FROM_RACK_TO_MACHINE, - GO_FROM_ROOM_TO_RACK, -} from '../actions/interaction-level' -import { OPEN_PROJECT_SUCCEEDED } from '../actions/projects' -import { SET_CURRENT_TOPOLOGY } from '../actions/topology/building' -import { OPEN_SCENARIO_SUCCEEDED } from '../actions/scenarios' - -export function interactionLevel(state = { mode: 'BUILDING' }, action) { - switch (action.type) { - case OPEN_PORTFOLIO_SUCCEEDED: - case OPEN_SCENARIO_SUCCEEDED: - case OPEN_PROJECT_SUCCEEDED: - case SET_CURRENT_TOPOLOGY: - return { - mode: 'BUILDING', - } - case GO_FROM_BUILDING_TO_ROOM: - return { - mode: 'ROOM', - roomId: action.roomId, - } - case GO_FROM_ROOM_TO_RACK: - return { - mode: 'RACK', - roomId: state.roomId, - tileId: action.tileId, - } - case GO_FROM_RACK_TO_MACHINE: - return { - mode: 'MACHINE', - roomId: state.roomId, - tileId: state.tileId, - position: action.position, - } - case GO_DOWN_ONE_INTERACTION_LEVEL: - if (state.mode === 'ROOM') { - return { - mode: 'BUILDING', - } - } else if (state.mode === 'RACK') { - return { - mode: 'ROOM', - roomId: state.roomId, - } - } else if (state.mode === 'MACHINE') { - return { - mode: 'RACK', - roomId: state.roomId, - tileId: state.tileId, - } - } else { - return state - } - default: - return state - } -} diff --git a/opendc-web/opendc-web-ui/src/reducers/map.js b/opendc-web/opendc-web-ui/src/reducers/map.js deleted file mode 100644 index de712c15..00000000 --- a/opendc-web/opendc-web-ui/src/reducers/map.js +++ /dev/null @@ -1,35 +0,0 @@ -import { combineReducers } from 'redux' -import { SET_MAP_DIMENSIONS, SET_MAP_POSITION, SET_MAP_SCALE } from '../actions/map' - -export function position(state = { x: 0, y: 0 }, action) { - switch (action.type) { - case SET_MAP_POSITION: - return { x: action.x, y: action.y } - default: - return state - } -} - -export function dimensions(state = { width: 600, height: 400 }, action) { - switch (action.type) { - case SET_MAP_DIMENSIONS: - return { width: action.width, height: action.height } - default: - return state - } -} - -export function scale(state = 1, action) { - switch (action.type) { - case SET_MAP_SCALE: - return action.scale - default: - return state - } -} - -export const map = combineReducers({ - position, - dimensions, - scale, -}) diff --git a/opendc-web/opendc-web-ui/src/reducers/modals.js b/opendc-web/opendc-web-ui/src/reducers/modals.js deleted file mode 100644 index a7656373..00000000 --- a/opendc-web/opendc-web-ui/src/reducers/modals.js +++ /dev/null @@ -1,45 +0,0 @@ -import { combineReducers } from 'redux' -import { CLOSE_DELETE_PROFILE_MODAL, OPEN_DELETE_PROFILE_MODAL } from '../actions/modals/profile' -import { CLOSE_NEW_PROJECT_MODAL, OPEN_NEW_PROJECT_MODAL } from '../actions/modals/projects' -import { - CLOSE_NEW_TOPOLOGY_MODAL, - CLOSE_DELETE_MACHINE_MODAL, - CLOSE_DELETE_RACK_MODAL, - CLOSE_DELETE_ROOM_MODAL, - CLOSE_EDIT_RACK_NAME_MODAL, - CLOSE_EDIT_ROOM_NAME_MODAL, - OPEN_NEW_TOPOLOGY_MODAL, - OPEN_DELETE_MACHINE_MODAL, - OPEN_DELETE_RACK_MODAL, - OPEN_DELETE_ROOM_MODAL, - OPEN_EDIT_RACK_NAME_MODAL, - OPEN_EDIT_ROOM_NAME_MODAL, -} from '../actions/modals/topology' -import { CLOSE_NEW_PORTFOLIO_MODAL, OPEN_NEW_PORTFOLIO_MODAL } from '../actions/modals/portfolios' -import { CLOSE_NEW_SCENARIO_MODAL, OPEN_NEW_SCENARIO_MODAL } from '../actions/modals/scenarios' - -function modal(openAction, closeAction) { - return function (state = false, action) { - switch (action.type) { - case openAction: - return true - case closeAction: - return false - default: - return state - } - } -} - -export const modals = combineReducers({ - newProjectModalVisible: modal(OPEN_NEW_PROJECT_MODAL, CLOSE_NEW_PROJECT_MODAL), - deleteProfileModalVisible: modal(OPEN_DELETE_PROFILE_MODAL, CLOSE_DELETE_PROFILE_MODAL), - changeTopologyModalVisible: modal(OPEN_NEW_TOPOLOGY_MODAL, CLOSE_NEW_TOPOLOGY_MODAL), - editRoomNameModalVisible: modal(OPEN_EDIT_ROOM_NAME_MODAL, CLOSE_EDIT_ROOM_NAME_MODAL), - deleteRoomModalVisible: modal(OPEN_DELETE_ROOM_MODAL, CLOSE_DELETE_ROOM_MODAL), - editRackNameModalVisible: modal(OPEN_EDIT_RACK_NAME_MODAL, CLOSE_EDIT_RACK_NAME_MODAL), - deleteRackModalVisible: modal(OPEN_DELETE_RACK_MODAL, CLOSE_DELETE_RACK_MODAL), - deleteMachineModalVisible: modal(OPEN_DELETE_MACHINE_MODAL, CLOSE_DELETE_MACHINE_MODAL), - newPortfolioModalVisible: modal(OPEN_NEW_PORTFOLIO_MODAL, CLOSE_NEW_PORTFOLIO_MODAL), - newScenarioModalVisible: modal(OPEN_NEW_SCENARIO_MODAL, CLOSE_NEW_SCENARIO_MODAL), -}) diff --git a/opendc-web/opendc-web-ui/src/reducers/objects.js b/opendc-web/opendc-web-ui/src/reducers/objects.js deleted file mode 100644 index 1f721b2e..00000000 --- a/opendc-web/opendc-web-ui/src/reducers/objects.js +++ /dev/null @@ -1,64 +0,0 @@ -import { combineReducers } from 'redux' -import { - ADD_ID_TO_STORE_OBJECT_LIST_PROP, - ADD_PROP_TO_STORE_OBJECT, - ADD_TO_STORE, - REMOVE_ID_FROM_STORE_OBJECT_LIST_PROP, -} from '../actions/objects' -import { CPU_UNITS, GPU_UNITS, MEMORY_UNITS, STORAGE_UNITS } from '../util/unit-specifications' - -export const objects = combineReducers({ - project: object('project'), - user: object('user'), - authorization: objectWithId('authorization', (object) => [object.userId, object.projectId]), - cpu: object('cpu', CPU_UNITS), - gpu: object('gpu', GPU_UNITS), - memory: object('memory', MEMORY_UNITS), - storage: object('storage', STORAGE_UNITS), - machine: object('machine'), - rack: object('rack'), - tile: object('tile'), - room: object('room'), - topology: object('topology'), - trace: object('trace'), - scheduler: object('scheduler'), - portfolio: object('portfolio'), - scenario: object('scenario'), - prefab: object('prefab'), -}) - -function object(type, defaultState = {}) { - return objectWithId(type, (object) => object._id, defaultState) -} - -function objectWithId(type, getId, defaultState = {}) { - return (state = defaultState, action) => { - if (action.objectType !== type) { - return state - } - - if (action.type === ADD_TO_STORE) { - return Object.assign({}, state, { - [getId(action.object)]: action.object, - }) - } else if (action.type === ADD_PROP_TO_STORE_OBJECT) { - return Object.assign({}, state, { - [action.objectId]: Object.assign({}, state[action.objectId], action.propObject), - }) - } else if (action.type === ADD_ID_TO_STORE_OBJECT_LIST_PROP) { - return Object.assign({}, state, { - [action.objectId]: Object.assign({}, state[action.objectId], { - [action.propName]: [...state[action.objectId][action.propName], action.id], - }), - }) - } else if (action.type === REMOVE_ID_FROM_STORE_OBJECT_LIST_PROP) { - return Object.assign({}, state, { - [action.objectId]: Object.assign({}, state[action.objectId], { - [action.propName]: state[action.objectId][action.propName].filter((id) => id !== action.id), - }), - }) - } - - return state - } -} diff --git a/opendc-web/opendc-web-ui/src/reducers/project-list.js b/opendc-web/opendc-web-ui/src/reducers/project-list.js deleted file mode 100644 index 1f1aa8d0..00000000 --- a/opendc-web/opendc-web-ui/src/reducers/project-list.js +++ /dev/null @@ -1,30 +0,0 @@ -import { combineReducers } from 'redux' -import { ADD_PROJECT_SUCCEEDED, DELETE_PROJECT_SUCCEEDED, SET_AUTH_VISIBILITY_FILTER } from '../actions/projects' -import { FETCH_AUTHORIZATIONS_OF_CURRENT_USER_SUCCEEDED } from '../actions/users' - -export function authorizationsOfCurrentUser(state = [], action) { - switch (action.type) { - case FETCH_AUTHORIZATIONS_OF_CURRENT_USER_SUCCEEDED: - return action.authorizationsOfCurrentUser - case ADD_PROJECT_SUCCEEDED: - return [...state, action.authorization] - case DELETE_PROJECT_SUCCEEDED: - return state.filter((authorization) => authorization[1] !== action.id) - default: - return state - } -} - -export function authVisibilityFilter(state = 'SHOW_ALL', action) { - switch (action.type) { - case SET_AUTH_VISIBILITY_FILTER: - return action.filter - default: - return state - } -} - -export const projectList = combineReducers({ - authorizationsOfCurrentUser, - authVisibilityFilter, -}) diff --git a/opendc-web/opendc-web-ui/src/redux/actions/interaction-level.js b/opendc-web/opendc-web-ui/src/redux/actions/interaction-level.js new file mode 100644 index 00000000..8381eeef --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/actions/interaction-level.js @@ -0,0 +1,57 @@ +export const GO_FROM_BUILDING_TO_ROOM = 'GO_FROM_BUILDING_TO_ROOM' +export const GO_FROM_ROOM_TO_RACK = 'GO_FROM_ROOM_TO_RACK' +export const GO_FROM_RACK_TO_MACHINE = 'GO_FROM_RACK_TO_MACHINE' +export const GO_DOWN_ONE_INTERACTION_LEVEL = 'GO_DOWN_ONE_INTERACTION_LEVEL' + +export function goToRoom(roomId) { + return { + type: GO_FROM_BUILDING_TO_ROOM, + roomId, + } +} + +export function goFromBuildingToRoom(roomId) { + return (dispatch, getState) => { + const { interactionLevel } = getState() + if (interactionLevel.mode !== 'BUILDING') { + return + } + + dispatch({ + type: GO_FROM_BUILDING_TO_ROOM, + roomId, + }) + } +} + +export function goFromRoomToRack(tileId) { + return (dispatch, getState) => { + const { interactionLevel } = getState() + if (interactionLevel.mode !== 'ROOM') { + return + } + dispatch({ + type: GO_FROM_ROOM_TO_RACK, + tileId, + }) + } +} + +export function goFromRackToMachine(position) { + return (dispatch, getState) => { + const { interactionLevel } = getState() + if (interactionLevel.mode !== 'RACK') { + return + } + dispatch({ + type: GO_FROM_RACK_TO_MACHINE, + position, + }) + } +} + +export function goDownOneInteractionLevel() { + return { + type: GO_DOWN_ONE_INTERACTION_LEVEL, + } +} diff --git a/opendc-web/opendc-web-ui/src/redux/actions/topologies.js b/opendc-web/opendc-web-ui/src/redux/actions/topologies.js new file mode 100644 index 00000000..fc697cc2 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/actions/topologies.js @@ -0,0 +1,27 @@ +export const OPEN_TOPOLOGY = 'OPEN_TOPOLOGY' +export const ADD_TOPOLOGY = 'ADD_TOPOLOGY' +export const STORE_TOPOLOGY = 'STORE_TOPOLOGY' + +export function openTopology(id) { + return { + type: OPEN_TOPOLOGY, + id, + } +} + +export function addTopology(projectId, name, duplicateId) { + return { + type: ADD_TOPOLOGY, + projectId, + name, + duplicateId, + } +} + +export function storeTopology(topology, entities) { + return { + type: STORE_TOPOLOGY, + topology, + entities, + } +} diff --git a/opendc-web/opendc-web-ui/src/redux/actions/topology/building.js b/opendc-web/opendc-web-ui/src/redux/actions/topology/building.js new file mode 100644 index 00000000..939c24a4 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/actions/topology/building.js @@ -0,0 +1,113 @@ +import { uuid } from 'uuidv4' +import { addRoom, deleteRoom } from './room' + +export const START_NEW_ROOM_CONSTRUCTION = 'START_NEW_ROOM_CONSTRUCTION' +export const START_NEW_ROOM_CONSTRUCTION_SUCCEEDED = 'START_NEW_ROOM_CONSTRUCTION_SUCCEEDED' +export const FINISH_NEW_ROOM_CONSTRUCTION = 'FINISH_NEW_ROOM_CONSTRUCTION' +export const CANCEL_NEW_ROOM_CONSTRUCTION = 'CANCEL_NEW_ROOM_CONSTRUCTION' +export const CANCEL_NEW_ROOM_CONSTRUCTION_SUCCEEDED = 'CANCEL_NEW_ROOM_CONSTRUCTION_SUCCEEDED' +export const START_ROOM_EDIT = 'START_ROOM_EDIT' +export const FINISH_ROOM_EDIT = 'FINISH_ROOM_EDIT' +export const ADD_TILE = 'ADD_TILE' +export const DELETE_TILE = 'DELETE_TILE' + +export function startNewRoomConstruction() { + return (dispatch, getState) => { + const { topology } = getState() + const topologyId = topology.root._id + const room = { + _id: uuid(), + name: 'Room', + topologyId, + tiles: [], + } + + dispatch(addRoom(topologyId, room)) + dispatch(startNewRoomConstructionSucceeded(room._id)) + } +} + +export function startNewRoomConstructionSucceeded(roomId) { + return { + type: START_NEW_ROOM_CONSTRUCTION_SUCCEEDED, + roomId, + } +} + +export function finishNewRoomConstruction() { + return (dispatch, getState) => { + const { topology, construction } = getState() + if (topology.rooms[construction.currentRoomInConstruction].tiles.length === 0) { + dispatch(cancelNewRoomConstruction()) + return + } + + dispatch({ + type: FINISH_NEW_ROOM_CONSTRUCTION, + }) + } +} + +export function cancelNewRoomConstruction() { + return (dispatch, getState) => { + const { construction } = getState() + const roomId = construction.currentRoomInConstruction + dispatch(deleteRoom(roomId)) + dispatch(cancelNewRoomConstructionSucceeded()) + } +} + +export function cancelNewRoomConstructionSucceeded() { + return { + type: CANCEL_NEW_ROOM_CONSTRUCTION_SUCCEEDED, + } +} + +export function startRoomEdit(roomId) { + return { + type: START_ROOM_EDIT, + roomId: roomId, + } +} + +export function finishRoomEdit() { + return { + type: FINISH_ROOM_EDIT, + } +} + +export function toggleTileAtLocation(positionX, positionY) { + return (dispatch, getState) => { + const { topology, construction } = getState() + + const roomId = construction.currentRoomInConstruction + const tileIds = topology.rooms[roomId].tiles + for (const tileId of tileIds) { + if (topology.tiles[tileId].positionX === positionX && topology.tiles[tileId].positionY === positionY) { + dispatch(deleteTile(tileId)) + return + } + } + + dispatch(addTile(roomId, positionX, positionY)) + } +} + +export function addTile(roomId, positionX, positionY) { + return { + type: ADD_TILE, + tile: { + _id: uuid(), + roomId, + positionX, + positionY, + }, + } +} + +export function deleteTile(tileId) { + return { + type: DELETE_TILE, + tileId, + } +} diff --git a/opendc-web/opendc-web-ui/src/redux/actions/topology/machine.js b/opendc-web/opendc-web-ui/src/redux/actions/topology/machine.js new file mode 100644 index 00000000..93320884 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/actions/topology/machine.js @@ -0,0 +1,28 @@ +export const DELETE_MACHINE = 'DELETE_MACHINE' +export const ADD_UNIT = 'ADD_UNIT' +export const DELETE_UNIT = 'DELETE_UNIT' + +export function deleteMachine(machineId) { + return { + type: DELETE_MACHINE, + machineId, + } +} + +export function addUnit(machineId, unitType, unitId) { + return { + type: ADD_UNIT, + machineId, + unitType, + unitId, + } +} + +export function deleteUnit(machineId, unitType, unitId) { + return { + type: DELETE_UNIT, + machineId, + unitType, + unitId, + } +} diff --git a/opendc-web/opendc-web-ui/src/redux/actions/topology/rack.js b/opendc-web/opendc-web-ui/src/redux/actions/topology/rack.js new file mode 100644 index 00000000..c319d966 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/actions/topology/rack.js @@ -0,0 +1,36 @@ +import { uuid } from 'uuidv4' + +export const EDIT_RACK_NAME = 'EDIT_RACK_NAME' +export const DELETE_RACK = 'DELETE_RACK' +export const ADD_MACHINE = 'ADD_MACHINE' + +export function editRackName(rackId, name) { + return { + type: EDIT_RACK_NAME, + name, + rackId, + } +} + +export function deleteRack(tileId, rackId) { + return { + type: DELETE_RACK, + rackId, + tileId, + } +} + +export function addMachine(rackId, position) { + return { + type: ADD_MACHINE, + machine: { + _id: uuid(), + rackId, + position, + cpus: [], + gpus: [], + memories: [], + storages: [], + }, + } +} diff --git a/opendc-web/opendc-web-ui/src/redux/actions/topology/room.js b/opendc-web/opendc-web-ui/src/redux/actions/topology/room.js new file mode 100644 index 00000000..bd447db5 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/actions/topology/room.js @@ -0,0 +1,74 @@ +import { uuid } from 'uuidv4' +import { + DEFAULT_RACK_SLOT_CAPACITY, + DEFAULT_RACK_POWER_CAPACITY, +} from '../../../components/topologies/map/MapConstants' +import { findTileWithPosition } from '../../../util/tile-calculations' + +export const ADD_ROOM = 'ADD_ROOM' +export const EDIT_ROOM_NAME = 'EDIT_ROOM_NAME' +export const DELETE_ROOM = 'DELETE_ROOM' +export const START_RACK_CONSTRUCTION = 'START_RACK_CONSTRUCTION' +export const STOP_RACK_CONSTRUCTION = 'STOP_RACK_CONSTRUCTION' +export const ADD_RACK_TO_TILE = 'ADD_RACK_TO_TILE' + +export function addRoom(topologyId, room) { + return { + type: ADD_ROOM, + room: { + _id: uuid(), + topologyId, + ...room, + }, + } +} + +export function editRoomName(roomId, name) { + return { + type: EDIT_ROOM_NAME, + name, + roomId, + } +} + +export function startRackConstruction() { + return { + type: START_RACK_CONSTRUCTION, + } +} + +export function stopRackConstruction() { + return { + type: STOP_RACK_CONSTRUCTION, + } +} + +export function addRackToTile(positionX, positionY) { + return (dispatch, getState) => { + const { topology, interactionLevel } = getState() + const currentRoom = topology.rooms[interactionLevel.roomId] + const tiles = currentRoom.tiles.map((tileId) => topology.tiles[tileId]) + const tile = findTileWithPosition(tiles, positionX, positionY) + + if (tile !== null) { + dispatch({ + type: ADD_RACK_TO_TILE, + rack: { + _id: uuid(), + name: 'Rack', + tileId: tile._id, + capacity: DEFAULT_RACK_SLOT_CAPACITY, + powerCapacityW: DEFAULT_RACK_POWER_CAPACITY, + machines: [], + }, + }) + } + } +} + +export function deleteRoom(roomId) { + return { + type: DELETE_ROOM, + roomId, + } +} diff --git a/opendc-web/opendc-web-ui/src/redux/index.js b/opendc-web/opendc-web-ui/src/redux/index.js new file mode 100644 index 00000000..fa0c9d23 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/index.js @@ -0,0 +1,58 @@ +import { useMemo } from 'react' +import { applyMiddleware, compose, createStore } from 'redux' +import { createLogger } from 'redux-logger' +import createSagaMiddleware from 'redux-saga' +import thunk from 'redux-thunk' +import rootReducer from './reducers' +import rootSaga from './sagas' +import { createReduxEnhancer } from '@sentry/react' + +let store + +function initStore(initialState, ctx) { + const sagaMiddleware = createSagaMiddleware({ context: ctx }) + + const middlewares = [thunk, sagaMiddleware] + + if (process.env.NODE_ENV !== 'production') { + middlewares.push(createLogger()) + } + + let middleware = applyMiddleware(...middlewares) + + if (process.env.NEXT_PUBLIC_SENTRY_DSN) { + middleware = compose(middleware, createReduxEnhancer()) + } + + const configuredStore = createStore(rootReducer, initialState, middleware) + sagaMiddleware.run(rootSaga) + store = configuredStore + + return configuredStore +} + +export const initializeStore = (preloadedState, ctx) => { + let _store = store ?? initStore(preloadedState, ctx) + + // After navigating to a page with an initial Redux state, merge that state + // with the current state in the store, and create a new store + if (preloadedState && store) { + _store = initStore({ + ...store.getState(), + ...preloadedState, + }) + // Reset the current store + store = undefined + } + + // For SSG and SSR always create a new store + if (typeof window === 'undefined') return _store + // Create the store once in the client + if (!store) store = _store + + return _store +} + +export function useStore(initialState, ctx) { + return useMemo(() => initializeStore(initialState, ctx), [initialState, ctx]) +} diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/construction-mode.js b/opendc-web/opendc-web-ui/src/redux/reducers/construction-mode.js new file mode 100644 index 00000000..d0aac5ae --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/reducers/construction-mode.js @@ -0,0 +1,43 @@ +import { combineReducers } from 'redux' +import { GO_DOWN_ONE_INTERACTION_LEVEL } from '../actions/interaction-level' +import { + CANCEL_NEW_ROOM_CONSTRUCTION_SUCCEEDED, + FINISH_NEW_ROOM_CONSTRUCTION, + FINISH_ROOM_EDIT, + START_NEW_ROOM_CONSTRUCTION_SUCCEEDED, + START_ROOM_EDIT, +} from '../actions/topology/building' +import { DELETE_ROOM, START_RACK_CONSTRUCTION, STOP_RACK_CONSTRUCTION } from '../actions/topology/room' + +export function currentRoomInConstruction(state = '-1', action) { + switch (action.type) { + case START_NEW_ROOM_CONSTRUCTION_SUCCEEDED: + return action.roomId + case START_ROOM_EDIT: + return action.roomId + case CANCEL_NEW_ROOM_CONSTRUCTION_SUCCEEDED: + case FINISH_NEW_ROOM_CONSTRUCTION: + case FINISH_ROOM_EDIT: + case DELETE_ROOM: + return '-1' + default: + return state + } +} + +export function inRackConstructionMode(state = false, action) { + switch (action.type) { + case START_RACK_CONSTRUCTION: + return true + case STOP_RACK_CONSTRUCTION: + case GO_DOWN_ONE_INTERACTION_LEVEL: + return false + default: + return state + } +} + +export const construction = combineReducers({ + currentRoomInConstruction, + inRackConstructionMode, +}) diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/index.js b/opendc-web/opendc-web-ui/src/redux/reducers/index.js new file mode 100644 index 00000000..7ffb1211 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/reducers/index.js @@ -0,0 +1,12 @@ +import { combineReducers } from 'redux' +import { construction } from './construction-mode' +import { interactionLevel } from './interaction-level' +import topology from './topology' + +const rootReducer = combineReducers({ + topology, + construction, + interactionLevel, +}) + +export default rootReducer diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/interaction-level.js b/opendc-web/opendc-web-ui/src/redux/reducers/interaction-level.js new file mode 100644 index 00000000..b30c68b9 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/reducers/interaction-level.js @@ -0,0 +1,68 @@ +import { + GO_DOWN_ONE_INTERACTION_LEVEL, + GO_FROM_BUILDING_TO_ROOM, + GO_FROM_RACK_TO_MACHINE, + GO_FROM_ROOM_TO_RACK, +} from '../actions/interaction-level' +import { DELETE_MACHINE } from '../actions/topology/machine' +import { DELETE_RACK } from '../actions/topology/rack' +import { DELETE_ROOM } from '../actions/topology/room' + +export function interactionLevel(state = { mode: 'BUILDING' }, action) { + switch (action.type) { + case GO_FROM_BUILDING_TO_ROOM: + return { + mode: 'ROOM', + roomId: action.roomId, + } + case GO_FROM_ROOM_TO_RACK: + return { + mode: 'RACK', + roomId: state.roomId, + tileId: action.tileId, + } + case GO_FROM_RACK_TO_MACHINE: + return { + mode: 'MACHINE', + roomId: state.roomId, + tileId: state.tileId, + position: action.position, + } + case GO_DOWN_ONE_INTERACTION_LEVEL: + if (state.mode === 'ROOM') { + return { + mode: 'BUILDING', + } + } else if (state.mode === 'RACK') { + return { + mode: 'ROOM', + roomId: state.roomId, + } + } else if (state.mode === 'MACHINE') { + return { + mode: 'RACK', + roomId: state.roomId, + tileId: state.tileId, + } + } else { + return state + } + case DELETE_MACHINE: + return { + mode: 'RACK', + roomId: state.roomId, + tileId: state.tileId, + } + case DELETE_RACK: + return { + mode: 'ROOM', + roomId: state.roomId, + } + case DELETE_ROOM: + return { + mode: 'BUILDING', + } + default: + return state + } +} diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/topology/index.js b/opendc-web/opendc-web-ui/src/redux/reducers/topology/index.js new file mode 100644 index 00000000..b1c7d29e --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/reducers/topology/index.js @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { CPU_UNITS, GPU_UNITS, MEMORY_UNITS, STORAGE_UNITS } from '../../../util/unit-specifications' +import machine from './machine' +import rack from './rack' +import room from './room' +import tile from './tile' +import topology from './topology' + +function objects(state = {}, action) { + return { + cpus: CPU_UNITS, + gpus: GPU_UNITS, + memories: MEMORY_UNITS, + storages: STORAGE_UNITS, + machines: machine(state.machines, action, state), + racks: rack(state.racks, action, state), + tiles: tile(state.tiles, action, state), + rooms: room(state.rooms, action, state), + root: topology(state.root, action, state), + } +} + +export default objects diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/topology/machine.js b/opendc-web/opendc-web-ui/src/redux/reducers/topology/machine.js new file mode 100644 index 00000000..41773014 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/reducers/topology/machine.js @@ -0,0 +1,47 @@ +import produce from 'immer' +import { STORE_TOPOLOGY } from '../../actions/topologies' +import { DELETE_MACHINE, ADD_UNIT, DELETE_UNIT } from '../../actions/topology/machine' +import { ADD_MACHINE, DELETE_RACK } from '../../actions/topology/rack' + +function machine(state = {}, action, { racks }) { + switch (action.type) { + case STORE_TOPOLOGY: + return action.entities.machines || {} + case ADD_MACHINE: + return produce(state, (draft) => { + const { machine } = action + draft[machine._id] = machine + }) + case DELETE_MACHINE: + return produce(state, (draft) => { + const { machineId } = action + delete draft[machineId] + }) + case ADD_UNIT: + return produce(state, (draft) => { + const { machineId, unitType, unitId } = action + draft[machineId][unitType].push(unitId) + }) + case DELETE_UNIT: + return produce(state, (draft) => { + const { machineId, unitType, unitId } = action + const units = draft[machineId][unitType] + const index = units.indexOf(unitId) + units.splice(index, 1) + }) + case DELETE_RACK: + return produce(state, (draft) => { + const { rackId } = action + const rack = racks[rackId] + + for (const id of rack.machines) { + const machine = draft[id] + machine.rackId = undefined + } + }) + default: + return state + } +} + +export default machine diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/topology/rack.js b/opendc-web/opendc-web-ui/src/redux/reducers/topology/rack.js new file mode 100644 index 00000000..9cc37124 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/reducers/topology/rack.js @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import produce from 'immer' +import { STORE_TOPOLOGY } from '../../actions/topologies' +import { DELETE_MACHINE } from '../../actions/topology/machine' +import { DELETE_RACK, EDIT_RACK_NAME, ADD_MACHINE } from '../../actions/topology/rack' +import { ADD_RACK_TO_TILE } from '../../actions/topology/room' + +function rack(state = {}, action, { machines }) { + switch (action.type) { + case STORE_TOPOLOGY: + return action.entities.racks || {} + case ADD_RACK_TO_TILE: + return produce(state, (draft) => { + const { rack } = action + draft[rack._id] = rack + }) + case EDIT_RACK_NAME: + return produce(state, (draft) => { + const { rackId, name } = action + draft[rackId].name = name + }) + case DELETE_RACK: + return produce(state, (draft) => { + const { rackId } = action + delete draft[rackId] + }) + case ADD_MACHINE: + return produce(state, (draft) => { + const { machine } = action + draft[machine.rackId].machines.push(machine._id) + }) + case DELETE_MACHINE: + return produce(state, (draft) => { + const { machineId } = action + const machine = machines[machineId] + const rack = draft[machine.rackId] + const index = rack.machines.indexOf(machineId) + rack.machines.splice(index, 1) + }) + default: + return state + } +} + +export default rack diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/topology/room.js b/opendc-web/opendc-web-ui/src/redux/reducers/topology/room.js new file mode 100644 index 00000000..b61c9d82 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/reducers/topology/room.js @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import produce from 'immer' +import { STORE_TOPOLOGY } from '../../actions/topologies' +import { ADD_TILE, DELETE_TILE } from '../../actions/topology/building' +import { DELETE_ROOM, EDIT_ROOM_NAME, ADD_ROOM } from '../../actions/topology/room' + +function room(state = {}, action, { tiles }) { + switch (action.type) { + case STORE_TOPOLOGY: + return action.entities.rooms || {} + case ADD_ROOM: + return produce(state, (draft) => { + const { room } = action + draft[room._id] = room + }) + case DELETE_ROOM: + return produce(state, (draft) => { + const { roomId } = action + delete draft[roomId] + }) + case EDIT_ROOM_NAME: + return produce(state, (draft) => { + const { roomId, name } = action + draft[roomId].name = name + }) + case ADD_TILE: + return produce(state, (draft) => { + const { tile } = action + draft[tile.roomId].tiles.push(tile._id) + }) + case DELETE_TILE: + return produce(state, (draft) => { + const { tileId } = action + const tile = tiles[tileId] + const room = draft[tile.roomId] + const index = room.tiles.indexOf(tileId) + room.tiles.splice(index, 1) + }) + default: + return state + } +} + +export default room diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/topology/tile.js b/opendc-web/opendc-web-ui/src/redux/reducers/topology/tile.js new file mode 100644 index 00000000..e0c5dd33 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/reducers/topology/tile.js @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import produce from 'immer' +import { STORE_TOPOLOGY } from '../../actions/topologies' +import { ADD_TILE, DELETE_TILE } from '../../actions/topology/building' +import { DELETE_RACK } from '../../actions/topology/rack' +import { ADD_RACK_TO_TILE } from '../../actions/topology/room' + +function tile(state = {}, action, { racks }) { + switch (action.type) { + case STORE_TOPOLOGY: + return action.entities.tiles || {} + case ADD_TILE: + return produce(state, (draft) => { + const { tile } = action + draft[tile._id] = tile + }) + case DELETE_TILE: + return produce(state, (draft) => { + const { tileId } = action + delete draft[tileId] + }) + case ADD_RACK_TO_TILE: + return produce(state, (draft) => { + const { rack } = action + draft[rack.tileId].rack = rack._id + }) + case DELETE_RACK: + return produce(state, (draft) => { + const { rackId } = action + const rack = racks[rackId] + draft[rack.tileId].rack = undefined + }) + default: + return state + } +} + +export default tile diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/topology/topology.js b/opendc-web/opendc-web-ui/src/redux/reducers/topology/topology.js new file mode 100644 index 00000000..da0e6988 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/reducers/topology/topology.js @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import produce from 'immer' +import { STORE_TOPOLOGY } from '../../actions/topologies' +import { ADD_ROOM, DELETE_ROOM } from '../../actions/topology/room' + +function topology(state = undefined, action) { + switch (action.type) { + case STORE_TOPOLOGY: + return action.topology + case ADD_ROOM: + return produce(state, (draft) => { + const { room } = action + draft.rooms.push(room._id) + }) + case DELETE_ROOM: + return produce(state, (draft) => { + const { roomId } = action + const index = draft.rooms.indexOf(roomId) + draft.rooms.splice(index, 1) + }) + default: + return state + } +} + +export default topology diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/index.js b/opendc-web/opendc-web-ui/src/redux/sagas/index.js new file mode 100644 index 00000000..0fabdb6d --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/sagas/index.js @@ -0,0 +1,7 @@ +import { fork } from 'redux-saga/effects' +import { watchServer, updateServer } from './topology' + +export default function* rootSaga() { + yield fork(watchServer) + yield fork(updateServer) +} diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/topology.js b/opendc-web/opendc-web-ui/src/redux/sagas/topology.js new file mode 100644 index 00000000..f40cff28 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/sagas/topology.js @@ -0,0 +1,75 @@ +import { QueryObserver, MutationObserver } from 'react-query' +import { normalize, denormalize } from 'normalizr' +import { select, put, take, race, getContext, call } from 'redux-saga/effects' +import { eventChannel } from 'redux-saga' +import { Topology } from '../../util/topology-schema' +import { storeTopology, OPEN_TOPOLOGY } from '../actions/topologies' + +/** + * Update the topology on the server. + */ +export function* updateServer() { + const queryClient = yield getContext('queryClient') + const mutationObserver = new MutationObserver(queryClient, { mutationKey: 'updateTopology' }) + + while (true) { + yield take( + (action) => + action.type.startsWith('EDIT') || action.type.startsWith('ADD') || action.type.startsWith('DELETE') + ) + const topology = yield select((state) => state.topology) + + if (!topology.root) { + continue + } + + const denormalizedTopology = denormalize(topology.root, Topology, topology) + yield call([mutationObserver, mutationObserver.mutate], denormalizedTopology) + } +} + +/** + * Watch the topology on the server for changes. + */ +export function* watchServer() { + let { id } = yield take(OPEN_TOPOLOGY) + while (true) { + const channel = yield queryObserver(id) + + while (true) { + const [action, response] = yield race([take(OPEN_TOPOLOGY), take(channel)]) + + if (action) { + id = action.id + break + } + + const { isFetched, data } = response + // Only update the topology on the client-side when a new topology was fetched + if (isFetched) { + const { result: topologyId, entities } = normalize(data, Topology) + yield put(storeTopology(entities.topologies[topologyId], entities)) + } + } + } +} + +/** + * Observe changes for the topology with the specified identifier. + */ +function* queryObserver(id) { + const queryClient = yield getContext('queryClient') + const observer = new QueryObserver(queryClient, { queryKey: ['topologies', id] }) + + return eventChannel((emitter) => { + const unsubscribe = observer.subscribe((result) => { + emitter(result) + }) + + // Update result to make sure we did not miss any query updates + // between creating the observer and subscribing to it. + observer.updateResult() + + return unsubscribe + }) +} diff --git a/opendc-web/opendc-web-ui/src/routes/index.js b/opendc-web/opendc-web-ui/src/routes/index.js deleted file mode 100644 index 4291a046..00000000 --- a/opendc-web/opendc-web-ui/src/routes/index.js +++ /dev/null @@ -1,40 +0,0 @@ -import React from 'react' -import { BrowserRouter, Redirect, Route, Switch } from 'react-router-dom' -import { userIsLoggedIn } from '../auth/index' -import App from '../pages/App' -import Home from '../pages/Home' -import NotFound from '../pages/NotFound' -import Profile from '../pages/Profile' -import Projects from '../pages/Projects' - -const ProtectedComponent = (component) => () => (userIsLoggedIn() ? component : <Redirect to="/" />) -const AppComponent = ({ match }) => - userIsLoggedIn() ? ( - <App - projectId={match.params.projectId} - portfolioId={match.params.portfolioId} - scenarioId={match.params.scenarioId} - /> - ) : ( - <Redirect to="/" /> - ) - -const Routes = () => ( - <BrowserRouter> - <Switch> - <Route exact path="/" component={Home} /> - <Route exact path="/projects" render={ProtectedComponent(<Projects />)} /> - <Route exact path="/projects/:projectId" component={AppComponent} /> - <Route exact path="/projects/:projectId/portfolios/:portfolioId" component={AppComponent} /> - <Route - exact - path="/projects/:projectId/portfolios/:portfolioId/scenarios/:scenarioId" - component={AppComponent} - /> - <Route exact path="/profile" render={ProtectedComponent(<Profile />)} /> - <Route path="/*" component={NotFound} /> - </Switch> - </BrowserRouter> -) - -export default Routes diff --git a/opendc-web/opendc-web-ui/src/sagas/index.js b/opendc-web/opendc-web-ui/src/sagas/index.js deleted file mode 100644 index 6332b2fb..00000000 --- a/opendc-web/opendc-web-ui/src/sagas/index.js +++ /dev/null @@ -1,80 +0,0 @@ -import { takeEvery } from 'redux-saga/effects' -import { LOG_IN } from '../actions/auth' -import { ADD_PORTFOLIO, DELETE_PORTFOLIO, OPEN_PORTFOLIO_SUCCEEDED, UPDATE_PORTFOLIO } from '../actions/portfolios' -import { ADD_PROJECT, DELETE_PROJECT, OPEN_PROJECT_SUCCEEDED } from '../actions/projects' -import { - ADD_TILE, - CANCEL_NEW_ROOM_CONSTRUCTION, - DELETE_TILE, - START_NEW_ROOM_CONSTRUCTION, -} from '../actions/topology/building' -import { ADD_UNIT, DELETE_MACHINE, DELETE_UNIT } from '../actions/topology/machine' -import { ADD_MACHINE, DELETE_RACK, EDIT_RACK_NAME } from '../actions/topology/rack' -import { ADD_RACK_TO_TILE, DELETE_ROOM, EDIT_ROOM_NAME } from '../actions/topology/room' -import { DELETE_CURRENT_USER, FETCH_AUTHORIZATIONS_OF_CURRENT_USER } from '../actions/users' -import { onAddPortfolio, onDeletePortfolio, onOpenPortfolioSucceeded, onUpdatePortfolio } from './portfolios' -import { onDeleteCurrentUser } from './profile' -import { onOpenProjectSucceeded, onProjectAdd, onProjectDelete } from './projects' -import { - onAddMachine, - onAddRackToTile, - onAddTile, - onAddTopology, - onAddUnit, - onCancelNewRoomConstruction, - onDeleteMachine, - onDeleteRack, - onDeleteRoom, - onDeleteTile, - onDeleteTopology, - onDeleteUnit, - onEditRackName, - onEditRoomName, - onStartNewRoomConstruction, -} from './topology' -import { onFetchAuthorizationsOfCurrentUser, onFetchLoggedInUser } from './users' -import { ADD_TOPOLOGY, DELETE_TOPOLOGY } from '../actions/topologies' -import { ADD_SCENARIO, DELETE_SCENARIO, OPEN_SCENARIO_SUCCEEDED, UPDATE_SCENARIO } from '../actions/scenarios' -import { onAddScenario, onDeleteScenario, onOpenScenarioSucceeded, onUpdateScenario } from './scenarios' -import { onAddPrefab } from './prefabs' -import { ADD_PREFAB } from '../actions/prefabs' - -export default function* rootSaga() { - yield takeEvery(LOG_IN, onFetchLoggedInUser) - - yield takeEvery(FETCH_AUTHORIZATIONS_OF_CURRENT_USER, onFetchAuthorizationsOfCurrentUser) - yield takeEvery(ADD_PROJECT, onProjectAdd) - yield takeEvery(DELETE_PROJECT, onProjectDelete) - - yield takeEvery(DELETE_CURRENT_USER, onDeleteCurrentUser) - - yield takeEvery(OPEN_PROJECT_SUCCEEDED, onOpenProjectSucceeded) - yield takeEvery(OPEN_PORTFOLIO_SUCCEEDED, onOpenPortfolioSucceeded) - yield takeEvery(OPEN_SCENARIO_SUCCEEDED, onOpenScenarioSucceeded) - - yield takeEvery(ADD_TOPOLOGY, onAddTopology) - yield takeEvery(DELETE_TOPOLOGY, onDeleteTopology) - yield takeEvery(START_NEW_ROOM_CONSTRUCTION, onStartNewRoomConstruction) - yield takeEvery(CANCEL_NEW_ROOM_CONSTRUCTION, onCancelNewRoomConstruction) - yield takeEvery(ADD_TILE, onAddTile) - yield takeEvery(DELETE_TILE, onDeleteTile) - yield takeEvery(EDIT_ROOM_NAME, onEditRoomName) - yield takeEvery(DELETE_ROOM, onDeleteRoom) - yield takeEvery(EDIT_RACK_NAME, onEditRackName) - yield takeEvery(DELETE_RACK, onDeleteRack) - yield takeEvery(ADD_RACK_TO_TILE, onAddRackToTile) - yield takeEvery(ADD_MACHINE, onAddMachine) - yield takeEvery(DELETE_MACHINE, onDeleteMachine) - yield takeEvery(ADD_UNIT, onAddUnit) - yield takeEvery(DELETE_UNIT, onDeleteUnit) - - yield takeEvery(ADD_PORTFOLIO, onAddPortfolio) - yield takeEvery(UPDATE_PORTFOLIO, onUpdatePortfolio) - yield takeEvery(DELETE_PORTFOLIO, onDeletePortfolio) - - yield takeEvery(ADD_SCENARIO, onAddScenario) - yield takeEvery(UPDATE_SCENARIO, onUpdateScenario) - yield takeEvery(DELETE_SCENARIO, onDeleteScenario) - - yield takeEvery(ADD_PREFAB, onAddPrefab) -} diff --git a/opendc-web/opendc-web-ui/src/sagas/objects.js b/opendc-web/opendc-web-ui/src/sagas/objects.js deleted file mode 100644 index 313d9976..00000000 --- a/opendc-web/opendc-web-ui/src/sagas/objects.js +++ /dev/null @@ -1,229 +0,0 @@ -import { call, put, select } from 'redux-saga/effects' -import { addToStore } from '../actions/objects' -import { getAllSchedulers } from '../api/routes/schedulers' -import { getProject } from '../api/routes/projects' -import { getAllTraces } from '../api/routes/traces' -import { getUser } from '../api/routes/users' -import { getTopology, updateTopology } from '../api/routes/topologies' -import { uuid } from 'uuidv4' - -export const OBJECT_SELECTORS = { - project: (state) => state.objects.project, - user: (state) => state.objects.user, - authorization: (state) => state.objects.authorization, - portfolio: (state) => state.objects.portfolio, - scenario: (state) => state.objects.scenario, - cpu: (state) => state.objects.cpu, - gpu: (state) => state.objects.gpu, - memory: (state) => state.objects.memory, - storage: (state) => state.objects.storage, - machine: (state) => state.objects.machine, - rack: (state) => state.objects.rack, - tile: (state) => state.objects.tile, - room: (state) => state.objects.room, - topology: (state) => state.objects.topology, -} - -function* fetchAndStoreObject(objectType, id, apiCall) { - const objectStore = yield select(OBJECT_SELECTORS[objectType]) - let object = objectStore[id] - if (!object) { - object = yield apiCall - yield put(addToStore(objectType, object)) - } - return object -} - -function* fetchAndStoreObjects(objectType, apiCall) { - const objects = yield apiCall - for (let object of objects) { - yield put(addToStore(objectType, object)) - } - return objects -} - -export const fetchAndStoreProject = (id) => fetchAndStoreObject('project', id, call(getProject, id)) - -export const fetchAndStoreUser = (id) => fetchAndStoreObject('user', id, call(getUser, id)) - -export const fetchAndStoreTopology = function* (id) { - const topologyStore = yield select(OBJECT_SELECTORS['topology']) - const roomStore = yield select(OBJECT_SELECTORS['room']) - const tileStore = yield select(OBJECT_SELECTORS['tile']) - const rackStore = yield select(OBJECT_SELECTORS['rack']) - const machineStore = yield select(OBJECT_SELECTORS['machine']) - - let topology = topologyStore[id] - if (!topology) { - const fullTopology = yield call(getTopology, id) - - for (let roomIdx in fullTopology.rooms) { - const fullRoom = fullTopology.rooms[roomIdx] - - generateIdIfNotPresent(fullRoom) - - if (!roomStore[fullRoom._id]) { - for (let tileIdx in fullRoom.tiles) { - const fullTile = fullRoom.tiles[tileIdx] - - generateIdIfNotPresent(fullTile) - - if (!tileStore[fullTile._id]) { - if (fullTile.rack) { - const fullRack = fullTile.rack - - generateIdIfNotPresent(fullRack) - - if (!rackStore[fullRack._id]) { - for (let machineIdx in fullRack.machines) { - const fullMachine = fullRack.machines[machineIdx] - - generateIdIfNotPresent(fullMachine) - - if (!machineStore[fullMachine._id]) { - let machine = (({ _id, position, cpus, gpus, memories, storages }) => ({ - _id, - rackId: fullRack._id, - position, - cpuIds: cpus.map((u) => u._id), - gpuIds: gpus.map((u) => u._id), - memoryIds: memories.map((u) => u._id), - storageIds: storages.map((u) => u._id), - }))(fullMachine) - yield put(addToStore('machine', machine)) - } - } - - const filledSlots = new Array(fullRack.capacity).fill(null) - fullRack.machines.forEach( - (machine) => (filledSlots[machine.position - 1] = machine._id) - ) - let rack = (({ _id, name, capacity, powerCapacityW }) => ({ - _id, - name, - capacity, - powerCapacityW, - machineIds: filledSlots, - }))(fullRack) - yield put(addToStore('rack', rack)) - } - } - - let tile = (({ _id, positionX, positionY, rack }) => ({ - _id, - roomId: fullRoom._id, - positionX, - positionY, - rackId: rack ? rack._id : undefined, - }))(fullTile) - yield put(addToStore('tile', tile)) - } - } - - let room = (({ _id, name, tiles }) => ({ _id, name, tileIds: tiles.map((t) => t._id) }))(fullRoom) - yield put(addToStore('room', room)) - } - } - - topology = (({ _id, name, rooms }) => ({ _id, name, roomIds: rooms.map((r) => r._id) }))(fullTopology) - yield put(addToStore('topology', topology)) - - // TODO consider pushing the IDs - } - - return topology -} - -const generateIdIfNotPresent = (obj) => { - if (!obj._id) { - obj._id = uuid() - } -} - -export const updateTopologyOnServer = function* (id) { - const topology = yield getTopologyAsObject(id, true) - - yield call(updateTopology, topology) -} - -export const getTopologyAsObject = function* (id, keepIds) { - const topologyStore = yield select(OBJECT_SELECTORS['topology']) - const rooms = yield getAllRooms(topologyStore[id].roomIds, keepIds) - return { - _id: keepIds ? id : undefined, - name: topologyStore[id].name, - rooms: rooms, - } -} - -export const getAllRooms = function* (roomIds, keepIds) { - const roomStore = yield select(OBJECT_SELECTORS['room']) - - let rooms = [] - - for (let id of roomIds) { - let tiles = yield getAllRoomTiles(roomStore[id], keepIds) - rooms.push({ - _id: keepIds ? id : undefined, - name: roomStore[id].name, - tiles: tiles, - }) - } - return rooms -} - -export const getAllRoomTiles = function* (roomStore, keepIds) { - let tiles = [] - - for (let id of roomStore.tileIds) { - tiles.push(yield getTileById(id, keepIds)) - } - return tiles -} - -export const getTileById = function* (id, keepIds) { - const tileStore = yield select(OBJECT_SELECTORS['tile']) - return { - _id: keepIds ? id : undefined, - positionX: tileStore[id].positionX, - positionY: tileStore[id].positionY, - rack: !tileStore[id].rackId ? undefined : yield getRackById(tileStore[id].rackId, keepIds), - } -} - -export const getRackById = function* (id, keepIds) { - const rackStore = yield select(OBJECT_SELECTORS['rack']) - const machineStore = yield select(OBJECT_SELECTORS['machine']) - const cpuStore = yield select(OBJECT_SELECTORS['cpu']) - const gpuStore = yield select(OBJECT_SELECTORS['gpu']) - const memoryStore = yield select(OBJECT_SELECTORS['memory']) - const storageStore = yield select(OBJECT_SELECTORS['storage']) - - return { - _id: keepIds ? rackStore[id]._id : undefined, - name: rackStore[id].name, - capacity: rackStore[id].capacity, - powerCapacityW: rackStore[id].powerCapacityW, - machines: rackStore[id].machineIds - .filter((m) => m !== null) - .map((machineId) => ({ - _id: keepIds ? machineId : undefined, - position: machineStore[machineId].position, - cpus: machineStore[machineId].cpuIds.map((id) => cpuStore[id]), - gpus: machineStore[machineId].gpuIds.map((id) => gpuStore[id]), - memories: machineStore[machineId].memoryIds.map((id) => memoryStore[id]), - storages: machineStore[machineId].storageIds.map((id) => storageStore[id]), - })), - } -} - -export const fetchAndStoreAllTraces = () => fetchAndStoreObjects('trace', call(getAllTraces)) - -export const fetchAndStoreAllSchedulers = function* () { - const objects = yield call(getAllSchedulers) - for (let object of objects) { - object._id = object.name - yield put(addToStore('scheduler', object)) - } - return objects -} diff --git a/opendc-web/opendc-web-ui/src/sagas/portfolios.js b/opendc-web/opendc-web-ui/src/sagas/portfolios.js deleted file mode 100644 index ed9bfd29..00000000 --- a/opendc-web/opendc-web-ui/src/sagas/portfolios.js +++ /dev/null @@ -1,131 +0,0 @@ -import { call, put, select, delay } from 'redux-saga/effects' -import { addPropToStoreObject, addToStore } from '../actions/objects' -import { addPortfolio, deletePortfolio, getPortfolio, updatePortfolio } from '../api/routes/portfolios' -import { getProject } from '../api/routes/projects' -import { fetchAndStoreAllSchedulers, fetchAndStoreAllTraces } from './objects' -import { fetchAndStoreAllTopologiesOfProject } from './topology' -import { getScenario } from '../api/routes/scenarios' - -export function* onOpenPortfolioSucceeded(action) { - try { - const project = yield call(getProject, action.projectId) - yield put(addToStore('project', project)) - yield fetchAndStoreAllTopologiesOfProject(project._id) - yield fetchPortfoliosOfProject() - yield fetchAndStoreAllSchedulers() - yield fetchAndStoreAllTraces() - - yield watchForPortfolioResults() - } catch (error) { - console.error(error) - } -} - -export function* watchForPortfolioResults() { - try { - const currentPortfolioId = yield select((state) => state.currentPortfolioId) - let unfinishedScenarios = yield getCurrentUnfinishedScenarios() - - while (unfinishedScenarios.length > 0) { - yield delay(3000) - yield fetchPortfolioWithScenarios(currentPortfolioId) - unfinishedScenarios = yield getCurrentUnfinishedScenarios() - } - } catch (error) { - console.error(error) - } -} - -export function* getCurrentUnfinishedScenarios() { - try { - const currentPortfolioId = yield select((state) => state.currentPortfolioId) - const scenarioIds = yield select((state) => state.objects.portfolio[currentPortfolioId].scenarioIds) - const scenarioObjects = yield select((state) => state.objects.scenario) - const scenarios = scenarioIds.map((s) => scenarioObjects[s]) - return scenarios.filter((s) => !s || s.simulation.state === 'QUEUED' || s.simulation.state === 'RUNNING') - } catch (error) { - console.error(error) - } -} - -export function* fetchPortfoliosOfProject() { - try { - const currentProjectId = yield select((state) => state.currentProjectId) - const currentProject = yield select((state) => state.objects.project[currentProjectId]) - - yield fetchAndStoreAllSchedulers() - yield fetchAndStoreAllTraces() - - for (let i in currentProject.portfolioIds) { - yield fetchPortfolioWithScenarios(currentProject.portfolioIds[i]) - } - } catch (error) { - console.error(error) - } -} - -export function* fetchPortfolioWithScenarios(portfolioId) { - try { - const portfolio = yield call(getPortfolio, portfolioId) - yield put(addToStore('portfolio', portfolio)) - - for (let i in portfolio.scenarioIds) { - const scenario = yield call(getScenario, portfolio.scenarioIds[i]) - yield put(addToStore('scenario', scenario)) - } - return portfolio - } catch (error) { - console.error(error) - } -} - -export function* onAddPortfolio(action) { - try { - const currentProjectId = yield select((state) => state.currentProjectId) - - const portfolio = yield call( - addPortfolio, - currentProjectId, - Object.assign({}, action.portfolio, { - projectId: currentProjectId, - scenarioIds: [], - }) - ) - yield put(addToStore('portfolio', portfolio)) - - const portfolioIds = yield select((state) => state.objects.project[currentProjectId].portfolioIds) - yield put( - addPropToStoreObject('project', currentProjectId, { - portfolioIds: portfolioIds.concat([portfolio._id]), - }) - ) - } catch (error) { - console.error(error) - } -} - -export function* onUpdatePortfolio(action) { - try { - const portfolio = yield call(updatePortfolio, action.portfolio._id, action.portfolio) - yield put(addToStore('portfolio', portfolio)) - } catch (error) { - console.error(error) - } -} - -export function* onDeletePortfolio(action) { - try { - yield call(deletePortfolio, action.id) - - const currentProjectId = yield select((state) => state.currentProjectId) - const portfolioIds = yield select((state) => state.objects.project[currentProjectId].portfolioIds) - - yield put( - addPropToStoreObject('project', currentProjectId, { - portfolioIds: portfolioIds.filter((id) => id !== action.id), - }) - ) - } catch (error) { - console.error(error) - } -} diff --git a/opendc-web/opendc-web-ui/src/sagas/prefabs.js b/opendc-web/opendc-web-ui/src/sagas/prefabs.js deleted file mode 100644 index 16cf3d62..00000000 --- a/opendc-web/opendc-web-ui/src/sagas/prefabs.js +++ /dev/null @@ -1,15 +0,0 @@ -import { call, put, select } from 'redux-saga/effects' -import { addToStore } from '../actions/objects' -import { addPrefab } from '../api/routes/prefabs' -import { getRackById } from './objects' - -export function* onAddPrefab(action) { - try { - const currentRackId = yield select((state) => state.objects.tile[state.interactionLevel.tileId].rackId) - const currentRackJson = yield getRackById(currentRackId, false) - const prefab = yield call(addPrefab, { name: action.name, rack: currentRackJson }) - yield put(addToStore('prefab', prefab)) - } catch (error) { - console.error(error) - } -} diff --git a/opendc-web/opendc-web-ui/src/sagas/profile.js b/opendc-web/opendc-web-ui/src/sagas/profile.js deleted file mode 100644 index e914ba56..00000000 --- a/opendc-web/opendc-web-ui/src/sagas/profile.js +++ /dev/null @@ -1,12 +0,0 @@ -import { call, put } from 'redux-saga/effects' -import { deleteCurrentUserSucceeded } from '../actions/users' -import { deleteUser } from '../api/routes/users' - -export function* onDeleteCurrentUser(action) { - try { - yield call(deleteUser, action.userId) - yield put(deleteCurrentUserSucceeded()) - } catch (error) { - console.error(error) - } -} diff --git a/opendc-web/opendc-web-ui/src/sagas/projects.js b/opendc-web/opendc-web-ui/src/sagas/projects.js deleted file mode 100644 index fdeea132..00000000 --- a/opendc-web/opendc-web-ui/src/sagas/projects.js +++ /dev/null @@ -1,48 +0,0 @@ -import { call, put } from 'redux-saga/effects' -import { addToStore } from '../actions/objects' -import { addProjectSucceeded, deleteProjectSucceeded } from '../actions/projects' -import { addProject, deleteProject, getProject } from '../api/routes/projects' -import { fetchAndStoreAllTopologiesOfProject } from './topology' -import { fetchAndStoreAllSchedulers, fetchAndStoreAllTraces } from './objects' -import { fetchPortfoliosOfProject } from './portfolios' - -export function* onOpenProjectSucceeded(action) { - try { - const project = yield call(getProject, action.id) - yield put(addToStore('project', project)) - - yield fetchAndStoreAllTopologiesOfProject(action.id, true) - yield fetchPortfoliosOfProject() - yield fetchAndStoreAllSchedulers() - yield fetchAndStoreAllTraces() - } catch (error) { - console.error(error) - } -} - -export function* onProjectAdd(action) { - try { - const project = yield call(addProject, { name: action.name }) - yield put(addToStore('project', project)) - - const authorization = { - projectId: project._id, - userId: action.userId, - authorizationLevel: 'OWN', - project, - } - yield put(addToStore('authorization', authorization)) - yield put(addProjectSucceeded([authorization.userId, authorization.projectId])) - } catch (error) { - console.error(error) - } -} - -export function* onProjectDelete(action) { - try { - yield call(deleteProject, action.id) - yield put(deleteProjectSucceeded(action.id)) - } catch (error) { - console.error(error) - } -} diff --git a/opendc-web/opendc-web-ui/src/sagas/scenarios.js b/opendc-web/opendc-web-ui/src/sagas/scenarios.js deleted file mode 100644 index 59223610..00000000 --- a/opendc-web/opendc-web-ui/src/sagas/scenarios.js +++ /dev/null @@ -1,65 +0,0 @@ -import { call, put, select } from 'redux-saga/effects' -import { addPropToStoreObject, addToStore } from '../actions/objects' -import { getProject } from '../api/routes/projects' -import { fetchAndStoreAllSchedulers, fetchAndStoreAllTraces } from './objects' -import { fetchAndStoreAllTopologiesOfProject } from './topology' -import { addScenario, deleteScenario, updateScenario } from '../api/routes/scenarios' -import { fetchPortfolioWithScenarios, watchForPortfolioResults } from './portfolios' - -export function* onOpenScenarioSucceeded(action) { - try { - const project = yield call(getProject, action.projectId) - yield put(addToStore('project', project)) - yield fetchAndStoreAllTopologiesOfProject(project._id) - yield fetchAndStoreAllSchedulers() - yield fetchAndStoreAllTraces() - yield fetchPortfolioWithScenarios(action.portfolioId) - - // TODO Fetch scenario-specific metrics - } catch (error) { - console.error(error) - } -} - -export function* onAddScenario(action) { - try { - const scenario = yield call(addScenario, action.scenario.portfolioId, action.scenario) - yield put(addToStore('scenario', scenario)) - - const scenarioIds = yield select((state) => state.objects.portfolio[action.scenario.portfolioId].scenarioIds) - yield put( - addPropToStoreObject('portfolio', action.scenario.portfolioId, { - scenarioIds: scenarioIds.concat([scenario._id]), - }) - ) - yield watchForPortfolioResults() - } catch (error) { - console.error(error) - } -} - -export function* onUpdateScenario(action) { - try { - const scenario = yield call(updateScenario, action.scenario._id, action.scenario) - yield put(addToStore('scenario', scenario)) - } catch (error) { - console.error(error) - } -} - -export function* onDeleteScenario(action) { - try { - yield call(deleteScenario, action.id) - - const currentPortfolioId = yield select((state) => state.currentPortfolioId) - const scenarioIds = yield select((state) => state.objects.portfolio[currentPortfolioId].scenarioIds) - - yield put( - addPropToStoreObject('scenario', currentPortfolioId, { - scenarioIds: scenarioIds.filter((id) => id !== action.id), - }) - ) - } catch (error) { - console.error(error) - } -} diff --git a/opendc-web/opendc-web-ui/src/sagas/topology.js b/opendc-web/opendc-web-ui/src/sagas/topology.js deleted file mode 100644 index bba1ebb1..00000000 --- a/opendc-web/opendc-web-ui/src/sagas/topology.js +++ /dev/null @@ -1,311 +0,0 @@ -import { call, put, select } from 'redux-saga/effects' -import { goDownOneInteractionLevel } from '../actions/interaction-level' -import { - addIdToStoreObjectListProp, - addPropToStoreObject, - addToStore, - removeIdFromStoreObjectListProp, -} from '../actions/objects' -import { - cancelNewRoomConstructionSucceeded, - setCurrentTopology, - startNewRoomConstructionSucceeded, -} from '../actions/topology/building' -import { - DEFAULT_RACK_POWER_CAPACITY, - DEFAULT_RACK_SLOT_CAPACITY, - MAX_NUM_UNITS_PER_MACHINE, -} from '../components/app/map/MapConstants' -import { fetchAndStoreTopology, getTopologyAsObject, updateTopologyOnServer } from './objects' -import { uuid } from 'uuidv4' -import { addTopology, deleteTopology } from '../api/routes/topologies' - -export function* fetchAndStoreAllTopologiesOfProject(projectId, setTopology = false) { - try { - const project = yield select((state) => state.objects.project[projectId]) - - for (let i in project.topologyIds) { - yield fetchAndStoreTopology(project.topologyIds[i]) - } - - if (setTopology) { - yield put(setCurrentTopology(project.topologyIds[0])) - } - } catch (error) { - console.error(error) - } -} - -export function* onAddTopology(action) { - try { - const currentProjectId = yield select((state) => state.currentProjectId) - - let topologyToBeCreated - if (action.duplicateId) { - topologyToBeCreated = yield getTopologyAsObject(action.duplicateId, false) - topologyToBeCreated = Object.assign({}, topologyToBeCreated, { - name: action.name, - }) - } else { - topologyToBeCreated = { name: action.name, rooms: [] } - } - - const topology = yield call( - addTopology, - Object.assign({}, topologyToBeCreated, { - projectId: currentProjectId, - }) - ) - yield fetchAndStoreTopology(topology._id) - - const topologyIds = yield select((state) => state.objects.project[currentProjectId].topologyIds) - yield put( - addPropToStoreObject('project', currentProjectId, { - topologyIds: topologyIds.concat([topology._id]), - }) - ) - yield put(setCurrentTopology(topology._id)) - } catch (error) { - console.error(error) - } -} - -export function* onDeleteTopology(action) { - try { - const currentProjectId = yield select((state) => state.currentProjectId) - const topologyIds = yield select((state) => state.objects.project[currentProjectId].topologyIds) - const currentTopologyId = yield select((state) => state.currentTopologyId) - if (currentTopologyId === action.id) { - yield put(setCurrentTopology(topologyIds.filter((t) => t !== action.id)[0])) - } - - yield call(deleteTopology, action.id) - - yield put( - addPropToStoreObject('project', currentProjectId, { - topologyIds: topologyIds.filter((id) => id !== action.id), - }) - ) - } catch (error) { - console.error(error) - } -} - -export function* onStartNewRoomConstruction() { - try { - const topologyId = yield select((state) => state.currentTopologyId) - const room = { - _id: uuid(), - name: 'Room', - topologyId, - tileIds: [], - } - yield put(addToStore('room', room)) - yield put(addIdToStoreObjectListProp('topology', topologyId, 'roomIds', room._id)) - yield updateTopologyOnServer(topologyId) - yield put(startNewRoomConstructionSucceeded(room._id)) - } catch (error) { - console.error(error) - } -} - -export function* onCancelNewRoomConstruction() { - try { - const topologyId = yield select((state) => state.currentTopologyId) - const roomId = yield select((state) => state.construction.currentRoomInConstruction) - yield put(removeIdFromStoreObjectListProp('topology', topologyId, 'roomIds', roomId)) - // TODO remove room from store, too - yield updateTopologyOnServer(topologyId) - yield put(cancelNewRoomConstructionSucceeded()) - } catch (error) { - console.error(error) - } -} - -export function* onAddTile(action) { - try { - const topologyId = yield select((state) => state.currentTopologyId) - const roomId = yield select((state) => state.construction.currentRoomInConstruction) - const tile = { - _id: uuid(), - roomId, - positionX: action.positionX, - positionY: action.positionY, - } - yield put(addToStore('tile', tile)) - yield put(addIdToStoreObjectListProp('room', roomId, 'tileIds', tile._id)) - yield updateTopologyOnServer(topologyId) - } catch (error) { - console.error(error) - } -} - -export function* onDeleteTile(action) { - try { - const topologyId = yield select((state) => state.currentTopologyId) - const roomId = yield select((state) => state.construction.currentRoomInConstruction) - yield put(removeIdFromStoreObjectListProp('room', roomId, 'tileIds', action.tileId)) - yield updateTopologyOnServer(topologyId) - } catch (error) { - console.error(error) - } -} - -export function* onEditRoomName(action) { - try { - const topologyId = yield select((state) => state.currentTopologyId) - const roomId = yield select((state) => state.interactionLevel.roomId) - const room = Object.assign({}, yield select((state) => state.objects.room[roomId])) - room.name = action.name - yield put(addPropToStoreObject('room', roomId, { name: action.name })) - yield updateTopologyOnServer(topologyId) - } catch (error) { - console.error(error) - } -} - -export function* onDeleteRoom() { - try { - const topologyId = yield select((state) => state.currentTopologyId) - const roomId = yield select((state) => state.interactionLevel.roomId) - yield put(goDownOneInteractionLevel()) - yield put(removeIdFromStoreObjectListProp('topology', topologyId, 'roomIds', roomId)) - yield updateTopologyOnServer(topologyId) - } catch (error) { - console.error(error) - } -} - -export function* onEditRackName(action) { - try { - const topologyId = yield select((state) => state.currentTopologyId) - const rackId = yield select((state) => state.objects.tile[state.interactionLevel.tileId].rackId) - const rack = Object.assign({}, yield select((state) => state.objects.rack[rackId])) - rack.name = action.name - yield put(addPropToStoreObject('rack', rackId, { name: action.name })) - yield updateTopologyOnServer(topologyId) - } catch (error) { - console.error(error) - } -} - -export function* onDeleteRack() { - try { - const topologyId = yield select((state) => state.currentTopologyId) - const tileId = yield select((state) => state.interactionLevel.tileId) - yield put(goDownOneInteractionLevel()) - yield put(addPropToStoreObject('tile', tileId, { rackId: undefined })) - yield updateTopologyOnServer(topologyId) - } catch (error) { - console.error(error) - } -} - -export function* onAddRackToTile(action) { - try { - const topologyId = yield select((state) => state.currentTopologyId) - const rack = { - _id: uuid(), - name: 'Rack', - capacity: DEFAULT_RACK_SLOT_CAPACITY, - powerCapacityW: DEFAULT_RACK_POWER_CAPACITY, - } - rack.machineIds = new Array(rack.capacity).fill(null) - yield put(addToStore('rack', rack)) - yield put(addPropToStoreObject('tile', action.tileId, { rackId: rack._id })) - yield updateTopologyOnServer(topologyId) - } catch (error) { - console.error(error) - } -} - -export function* onAddMachine(action) { - try { - const topologyId = yield select((state) => state.currentTopologyId) - const rackId = yield select((state) => state.objects.tile[state.interactionLevel.tileId].rackId) - const rack = yield select((state) => state.objects.rack[rackId]) - - const machine = { - _id: uuid(), - rackId, - position: action.position, - cpuIds: [], - gpuIds: [], - memoryIds: [], - storageIds: [], - } - yield put(addToStore('machine', machine)) - - const machineIds = [...rack.machineIds] - machineIds[machine.position - 1] = machine._id - yield put(addPropToStoreObject('rack', rackId, { machineIds })) - yield updateTopologyOnServer(topologyId) - } catch (error) { - console.error(error) - } -} - -export function* onDeleteMachine() { - try { - const topologyId = yield select((state) => state.currentTopologyId) - const tileId = yield select((state) => state.interactionLevel.tileId) - const position = yield select((state) => state.interactionLevel.position) - const rack = yield select((state) => state.objects.rack[state.objects.tile[tileId].rackId]) - const machineIds = [...rack.machineIds] - machineIds[position - 1] = null - yield put(goDownOneInteractionLevel()) - yield put(addPropToStoreObject('rack', rack._id, { machineIds })) - yield updateTopologyOnServer(topologyId) - } catch (error) { - console.error(error) - } -} - -export function* onAddUnit(action) { - try { - const topologyId = yield select((state) => state.currentTopologyId) - const tileId = yield select((state) => state.interactionLevel.tileId) - const position = yield select((state) => state.interactionLevel.position) - const machine = yield select( - (state) => - state.objects.machine[state.objects.rack[state.objects.tile[tileId].rackId].machineIds[position - 1]] - ) - - if (machine[action.unitType + 'Ids'].length >= MAX_NUM_UNITS_PER_MACHINE) { - return - } - - const units = [...machine[action.unitType + 'Ids'], action.id] - yield put( - addPropToStoreObject('machine', machine._id, { - [action.unitType + 'Ids']: units, - }) - ) - yield updateTopologyOnServer(topologyId) - } catch (error) { - console.error(error) - } -} - -export function* onDeleteUnit(action) { - try { - const topologyId = yield select((state) => state.currentTopologyId) - const tileId = yield select((state) => state.interactionLevel.tileId) - const position = yield select((state) => state.interactionLevel.position) - const machine = yield select( - (state) => - state.objects.machine[state.objects.rack[state.objects.tile[tileId].rackId].machineIds[position - 1]] - ) - const unitIds = machine[action.unitType + 'Ids'].slice() - unitIds.splice(action.index, 1) - - yield put( - addPropToStoreObject('machine', machine._id, { - [action.unitType + 'Ids']: unitIds, - }) - ) - yield updateTopologyOnServer(topologyId) - } catch (error) { - console.error(error) - } -} diff --git a/opendc-web/opendc-web-ui/src/sagas/users.js b/opendc-web/opendc-web-ui/src/sagas/users.js deleted file mode 100644 index 74e652f6..00000000 --- a/opendc-web/opendc-web-ui/src/sagas/users.js +++ /dev/null @@ -1,44 +0,0 @@ -import { call, put } from 'redux-saga/effects' -import { logInSucceeded } from '../actions/auth' -import { addToStore } from '../actions/objects' -import { fetchAuthorizationsOfCurrentUserSucceeded } from '../actions/users' -import { performTokenSignIn } from '../api/routes/token-signin' -import { addUser } from '../api/routes/users' -import { saveAuthLocalStorage } from '../auth/index' -import { fetchAndStoreProject, fetchAndStoreUser } from './objects' - -export function* onFetchLoggedInUser(action) { - try { - const tokenResponse = yield call(performTokenSignIn, action.payload.authToken) - - let userId = tokenResponse.userId - - if (tokenResponse.isNewUser) { - saveAuthLocalStorage({ authToken: action.payload.authToken }) - const newUser = yield call(addUser, action.payload) - userId = newUser._id - } - - yield put(logInSucceeded(Object.assign({ userId }, action.payload))) - } catch (error) { - console.error(error) - } -} - -export function* onFetchAuthorizationsOfCurrentUser(action) { - try { - const user = yield call(fetchAndStoreUser, action.userId) - - for (const authorization of user.authorizations) { - authorization.userId = action.userId - yield put(addToStore('authorization', authorization)) - yield fetchAndStoreProject(authorization.projectId) - } - - const authorizationIds = user.authorizations.map((authorization) => [action.userId, authorization.projectId]) - - yield put(fetchAuthorizationsOfCurrentUserSucceeded(authorizationIds)) - } catch (error) { - console.error(error) - } -} diff --git a/opendc-web/opendc-web-ui/src/shapes.js b/opendc-web/opendc-web-ui/src/shapes.js new file mode 100644 index 00000000..abdf146e --- /dev/null +++ b/opendc-web/opendc-web-ui/src/shapes.js @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' + +export const User = PropTypes.shape({ + _id: PropTypes.string.isRequired, + googleId: PropTypes.string.isRequired, + email: PropTypes.string.isRequired, + givenName: PropTypes.string.isRequired, + familyName: PropTypes.string.isRequired, + authorizations: PropTypes.array.isRequired, +}) + +export const Project = PropTypes.shape({ + _id: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + datetimeCreated: PropTypes.string.isRequired, + datetimeLastEdited: PropTypes.string.isRequired, + topologyIds: PropTypes.array.isRequired, + portfolioIds: PropTypes.array.isRequired, +}) + +export const ProcessingUnit = PropTypes.shape({ + _id: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + clockRateMhz: PropTypes.number.isRequired, + numberOfCores: PropTypes.number.isRequired, + energyConsumptionW: PropTypes.number.isRequired, +}) + +export const StorageUnit = PropTypes.shape({ + _id: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + speedMbPerS: PropTypes.number.isRequired, + sizeMb: PropTypes.number.isRequired, + energyConsumptionW: PropTypes.number.isRequired, +}) + +export const Machine = PropTypes.shape({ + _id: PropTypes.string.isRequired, + position: PropTypes.number.isRequired, + cpus: PropTypes.arrayOf(PropTypes.string), + gpus: PropTypes.arrayOf(PropTypes.string), + memories: PropTypes.arrayOf(PropTypes.string), + storages: PropTypes.arrayOf(PropTypes.string), +}) + +export const Rack = PropTypes.shape({ + _id: PropTypes.string.isRequired, + capacity: PropTypes.number.isRequired, + powerCapacityW: PropTypes.number.isRequired, + machines: PropTypes.arrayOf(PropTypes.string), +}) + +export const Tile = PropTypes.shape({ + _id: PropTypes.string.isRequired, + positionX: PropTypes.number.isRequired, + positionY: PropTypes.number.isRequired, + rack: PropTypes.string, +}) + +export const Room = PropTypes.shape({ + _id: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + tiles: PropTypes.arrayOf(PropTypes.string), +}) + +export const Topology = PropTypes.shape({ + _id: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + rooms: PropTypes.arrayOf(PropTypes.string), +}) + +export const Scheduler = PropTypes.shape({ + name: PropTypes.string.isRequired, +}) + +export const Trace = PropTypes.shape({ + _id: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + type: PropTypes.string.isRequired, +}) + +export const Portfolio = PropTypes.shape({ + _id: PropTypes.string.isRequired, + projectId: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + scenarioIds: PropTypes.arrayOf(PropTypes.string).isRequired, + targets: PropTypes.shape({ + enabledMetrics: PropTypes.arrayOf(PropTypes.string).isRequired, + repeatsPerScenario: PropTypes.number.isRequired, + }).isRequired, +}) + +export const Scenario = PropTypes.shape({ + _id: PropTypes.string.isRequired, + portfolioId: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + simulation: PropTypes.shape({ + state: PropTypes.string.isRequired, + }).isRequired, + trace: PropTypes.shape({ + traceId: PropTypes.string.isRequired, + trace: Trace, + loadSamplingFraction: PropTypes.number.isRequired, + }).isRequired, + topology: PropTypes.shape({ + topologyId: PropTypes.string.isRequired, + topology: Topology, + }).isRequired, + operational: PropTypes.shape({ + failuresEnabled: PropTypes.bool.isRequired, + performanceInterferenceEnabled: PropTypes.bool.isRequired, + schedulerName: PropTypes.string.isRequired, + scheduler: Scheduler, + }).isRequired, + results: PropTypes.object, +}) + +export const WallSegment = PropTypes.shape({ + startPosX: PropTypes.number.isRequired, + startPosY: PropTypes.number.isRequired, + isHorizontal: PropTypes.bool.isRequired, + length: PropTypes.number.isRequired, +}) + +export const InteractionLevel = PropTypes.shape({ + mode: PropTypes.string.isRequired, + roomId: PropTypes.string, + rackId: PropTypes.string, +}) + +export const Status = PropTypes.oneOf(['idle', 'loading', 'error', 'success']) diff --git a/opendc-web/opendc-web-ui/src/shapes/index.js b/opendc-web/opendc-web-ui/src/shapes/index.js deleted file mode 100644 index 9fab6f5d..00000000 --- a/opendc-web/opendc-web-ui/src/shapes/index.js +++ /dev/null @@ -1,148 +0,0 @@ -import PropTypes from 'prop-types' - -const Shapes = {} - -Shapes.User = PropTypes.shape({ - _id: PropTypes.string.isRequired, - googleId: PropTypes.string.isRequired, - email: PropTypes.string.isRequired, - givenName: PropTypes.string.isRequired, - familyName: PropTypes.string.isRequired, - authorizations: PropTypes.array.isRequired, -}) - -Shapes.Project = PropTypes.shape({ - _id: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - datetimeCreated: PropTypes.string.isRequired, - datetimeLastEdited: PropTypes.string.isRequired, - topologyIds: PropTypes.array.isRequired, - portfolioIds: PropTypes.array.isRequired, -}) - -Shapes.Authorization = PropTypes.shape({ - userId: PropTypes.string.isRequired, - user: Shapes.User, - projectId: PropTypes.string.isRequired, - project: Shapes.Project, - authorizationLevel: PropTypes.string.isRequired, -}) - -Shapes.ProcessingUnit = PropTypes.shape({ - _id: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - clockRateMhz: PropTypes.number.isRequired, - numberOfCores: PropTypes.number.isRequired, - energyConsumptionW: PropTypes.number.isRequired, -}) - -Shapes.StorageUnit = PropTypes.shape({ - _id: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - speedMbPerS: PropTypes.number.isRequired, - sizeMb: PropTypes.number.isRequired, - energyConsumptionW: PropTypes.number.isRequired, -}) - -Shapes.Machine = PropTypes.shape({ - _id: PropTypes.string.isRequired, - rackId: PropTypes.string.isRequired, - position: PropTypes.number.isRequired, - cpuIds: PropTypes.arrayOf(PropTypes.string.isRequired), - cpus: PropTypes.arrayOf(Shapes.ProcessingUnit), - gpuIds: PropTypes.arrayOf(PropTypes.string.isRequired), - gpus: PropTypes.arrayOf(Shapes.ProcessingUnit), - memoryIds: PropTypes.arrayOf(PropTypes.string.isRequired), - memories: PropTypes.arrayOf(Shapes.StorageUnit), - storageIds: PropTypes.arrayOf(PropTypes.string.isRequired), - storages: PropTypes.arrayOf(Shapes.StorageUnit), -}) - -Shapes.Rack = PropTypes.shape({ - _id: PropTypes.string.isRequired, - capacity: PropTypes.number.isRequired, - powerCapacityW: PropTypes.number.isRequired, - machines: PropTypes.arrayOf(Shapes.Machine), -}) - -Shapes.Tile = PropTypes.shape({ - _id: PropTypes.string.isRequired, - roomId: PropTypes.string.isRequired, - positionX: PropTypes.number.isRequired, - positionY: PropTypes.number.isRequired, - rackId: PropTypes.string, - rack: Shapes.Rack, -}) - -Shapes.Room = PropTypes.shape({ - _id: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - tiles: PropTypes.arrayOf(Shapes.Tile), -}) - -Shapes.Topology = PropTypes.shape({ - _id: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - rooms: PropTypes.arrayOf(Shapes.Room), -}) - -Shapes.Scheduler = PropTypes.shape({ - name: PropTypes.string.isRequired, -}) - -Shapes.Trace = PropTypes.shape({ - _id: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - type: PropTypes.string.isRequired, -}) - -Shapes.Portfolio = PropTypes.shape({ - _id: PropTypes.string.isRequired, - projectId: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - scenarioIds: PropTypes.arrayOf(PropTypes.string).isRequired, - targets: PropTypes.shape({ - enabledMetrics: PropTypes.arrayOf(PropTypes.string).isRequired, - repeatsPerScenario: PropTypes.number.isRequired, - }).isRequired, -}) - -Shapes.Scenario = PropTypes.shape({ - _id: PropTypes.string.isRequired, - portfolioId: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - simulation: PropTypes.shape({ - state: PropTypes.string.isRequired, - }).isRequired, - trace: PropTypes.shape({ - traceId: PropTypes.string.isRequired, - trace: Shapes.Trace, - loadSamplingFraction: PropTypes.number.isRequired, - }).isRequired, - topology: PropTypes.shape({ - topologyId: PropTypes.string.isRequired, - topology: Shapes.Topology, - }).isRequired, - operational: PropTypes.shape({ - failuresEnabled: PropTypes.bool.isRequired, - performanceInterferenceEnabled: PropTypes.bool.isRequired, - schedulerName: PropTypes.string.isRequired, - scheduler: Shapes.Scheduler, - }).isRequired, - results: PropTypes.object, -}) - -Shapes.WallSegment = PropTypes.shape({ - startPosX: PropTypes.number.isRequired, - startPosY: PropTypes.number.isRequired, - isHorizontal: PropTypes.bool.isRequired, - length: PropTypes.number.isRequired, -}) - -Shapes.InteractionLevel = PropTypes.shape({ - mode: PropTypes.string.isRequired, - roomId: PropTypes.string, - rackId: PropTypes.string, -}) - -export default Shapes diff --git a/opendc-web/opendc-web-ui/src/shortcuts/keymap.js b/opendc-web/opendc-web-ui/src/shortcuts/keymap.js deleted file mode 100644 index 797340d7..00000000 --- a/opendc-web/opendc-web-ui/src/shortcuts/keymap.js +++ /dev/null @@ -1,10 +0,0 @@ -const KeymapConfiguration = { - MAP: { - MOVE_LEFT: ['a', 'left'], - MOVE_RIGHT: ['d', 'right'], - MOVE_UP: ['w', 'up'], - MOVE_DOWN: ['s', 'down'], - }, -} - -export default KeymapConfiguration diff --git a/opendc-web/opendc-web-ui/src/store/configure-store.js b/opendc-web/opendc-web-ui/src/store/configure-store.js deleted file mode 100644 index d8f343ed..00000000 --- a/opendc-web/opendc-web-ui/src/store/configure-store.js +++ /dev/null @@ -1,35 +0,0 @@ -import { applyMiddleware, compose, createStore } from 'redux' -import persistState from 'redux-localstorage' -import { createLogger } from 'redux-logger' -import createSagaMiddleware from 'redux-saga' -import thunk from 'redux-thunk' -import { authRedirectMiddleware } from '../auth/index' -import rootReducer from '../reducers/index' -import rootSaga from '../sagas/index' -import { dummyMiddleware } from './middlewares/dummy-middleware' -import { viewportAdjustmentMiddleware } from './middlewares/viewport-adjustment' - -const sagaMiddleware = createSagaMiddleware() - -let logger -if (process.env.NODE_ENV !== 'production') { - logger = createLogger() -} - -const middlewares = [ - process.env.NODE_ENV === 'production' ? dummyMiddleware : logger, - thunk, - sagaMiddleware, - authRedirectMiddleware, - viewportAdjustmentMiddleware, -] - -export let store = undefined - -export default function configureStore() { - const configuredStore = createStore(rootReducer, compose(persistState('auth'), applyMiddleware(...middlewares))) - sagaMiddleware.run(rootSaga) - store = configuredStore - - return configuredStore -} diff --git a/opendc-web/opendc-web-ui/src/store/middlewares/dummy-middleware.js b/opendc-web/opendc-web-ui/src/store/middlewares/dummy-middleware.js deleted file mode 100644 index 5ba35691..00000000 --- a/opendc-web/opendc-web-ui/src/store/middlewares/dummy-middleware.js +++ /dev/null @@ -1,3 +0,0 @@ -export const dummyMiddleware = (store) => (next) => (action) => { - next(action) -} diff --git a/opendc-web/opendc-web-ui/src/store/middlewares/viewport-adjustment.js b/opendc-web/opendc-web-ui/src/store/middlewares/viewport-adjustment.js deleted file mode 100644 index b4472c54..00000000 --- a/opendc-web/opendc-web-ui/src/store/middlewares/viewport-adjustment.js +++ /dev/null @@ -1,73 +0,0 @@ -import { SET_MAP_DIMENSIONS, setMapPosition, setMapScale } from '../../actions/map' -import { SET_CURRENT_TOPOLOGY } from '../../actions/topology/building' -import { - MAP_MAX_SCALE, - MAP_MIN_SCALE, - SIDEBAR_WIDTH, - TILE_SIZE_IN_PIXELS, - VIEWPORT_PADDING, -} from '../../components/app/map/MapConstants' -import { calculateRoomListBounds } from '../../util/tile-calculations' - -export const viewportAdjustmentMiddleware = (store) => (next) => (action) => { - const state = store.getState() - - let topologyId = '-1' - let mapDimensions = {} - if (action.type === SET_CURRENT_TOPOLOGY && action.topologyId !== '-1') { - topologyId = action.topologyId - mapDimensions = state.map.dimensions - } else if (action.type === SET_MAP_DIMENSIONS && state.currentTopologyId !== '-1') { - topologyId = state.currentTopologyId - mapDimensions = { width: action.width, height: action.height } - } - - if (topologyId !== '-1') { - const roomIds = state.objects.topology[topologyId].roomIds - const rooms = roomIds.map((id) => Object.assign({}, state.objects.room[id])) - rooms.forEach((room) => (room.tiles = room.tileIds.map((tileId) => state.objects.tile[tileId]))) - - let hasNoTiles = true - for (let i in rooms) { - if (rooms[i].tiles.length > 0) { - hasNoTiles = false - break - } - } - - if (!hasNoTiles) { - const viewportParams = calculateParametersToZoomInOnRooms(rooms, mapDimensions.width, mapDimensions.height) - store.dispatch(setMapPosition(viewportParams.newX, viewportParams.newY)) - store.dispatch(setMapScale(viewportParams.newScale)) - } - } - - next(action) -} - -function calculateParametersToZoomInOnRooms(rooms, mapWidth, mapHeight) { - const bounds = calculateRoomListBounds(rooms) - const newScale = calculateNewScale(bounds, mapWidth, mapHeight) - - // Coordinates of the center of the room, relative to the global origin of the map - const roomCenterCoordinates = { - x: bounds.center.x * TILE_SIZE_IN_PIXELS * newScale, - y: bounds.center.y * TILE_SIZE_IN_PIXELS * newScale, - } - - const newX = -roomCenterCoordinates.x + mapWidth / 2 - const newY = -roomCenterCoordinates.y + mapHeight / 2 - - return { newScale, newX, newY } -} - -function calculateNewScale(bounds, mapWidth, mapHeight) { - const width = bounds.max.x - bounds.min.x - const height = bounds.max.y - bounds.min.y - - const scaleX = (mapWidth - 2 * SIDEBAR_WIDTH) / (width * TILE_SIZE_IN_PIXELS + 2 * VIEWPORT_PADDING) - const scaleY = mapHeight / (height * TILE_SIZE_IN_PIXELS + 2 * VIEWPORT_PADDING) - const newScale = Math.min(scaleX, scaleY) - - return Math.min(Math.max(MAP_MIN_SCALE, newScale), MAP_MAX_SCALE) -} diff --git a/opendc-web/opendc-web-ui/src/style-globals/_mixins.sass b/opendc-web/opendc-web-ui/src/style-globals/_mixins.sass deleted file mode 100644 index d0a8d1ac..00000000 --- a/opendc-web/opendc-web-ui/src/style-globals/_mixins.sass +++ /dev/null @@ -1,21 +0,0 @@ -=transition($property, $time) - -webkit-transition: $property $time - -moz-transition: $property $time - -o-transition: $property $time - transition: $property $time - -=user-select - -webkit-user-select: none - -moz-user-select: none - -ms-user-select: none - user-select: none - -=border-radius($length) - -webkit-border-radius: $length - -moz-border-radius: $length - border-radius: $length - -/* General Button Abstractions */ -=clickable - cursor: pointer - +user-select diff --git a/opendc-web/opendc-web-ui/src/style-globals/_variables.sass b/opendc-web/opendc-web-ui/src/style-globals/_variables.sass deleted file mode 100644 index 7553caa0..00000000 --- a/opendc-web/opendc-web-ui/src/style-globals/_variables.sass +++ /dev/null @@ -1,31 +0,0 @@ -// Sizes and Margins -$document-padding: 20px -$inter-element-margin: 5px -$standard-border-radius: 5px -$side-menu-width: 350px -$color-indicator-width: 140px - -$global-padding: 30px -$side-bar-width: 350px -$navbar-height: 50px -$navbar-padding: 10px - -// Durations -$transition-length: 150ms - -// Colors -$gray-very-dark: #5c5c5c -$gray-dark: #aaa -$gray-semi-dark: #bbb -$gray-semi-light: #ccc -$gray-light: #ddd -$gray-very-light: #eee -$blue: #00A6D6 -$blue-dark: #0087b5 -$blue-very-dark: #006182 -$blue-light: #deebf7 - -// Media queries -$screen-sm: 768px -$screen-md: 992px -$screen-lg: 1200px diff --git a/opendc-web/opendc-web-ui/src/style/index.scss b/opendc-web/opendc-web-ui/src/style/index.scss new file mode 100644 index 00000000..ff84e24a --- /dev/null +++ b/opendc-web/opendc-web-ui/src/style/index.scss @@ -0,0 +1,36 @@ +/*! + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +body, +#__next { + margin: 0; + padding: 0; + width: 100%; + height: 100%; + + background: #eee; +} + +.full-height { + position: relative; + height: 100% !important; +} diff --git a/opendc-web/opendc-web-ui/src/util/authorizations.js b/opendc-web/opendc-web-ui/src/util/authorizations.js index 4086b35d..ce5d34b6 100644 --- a/opendc-web/opendc-web-ui/src/util/authorizations.js +++ b/opendc-web/opendc-web-ui/src/util/authorizations.js @@ -1,7 +1,11 @@ +import HomeIcon from '@patternfly/react-icons/dist/js/icons/home-icon' +import EditIcon from '@patternfly/react-icons/dist/js/icons/edit-icon' +import EyeIcon from '@patternfly/react-icons/dist/js/icons/eye-icon' + export const AUTH_ICON_MAP = { - OWN: 'home', - EDIT: 'pencil', - VIEW: 'eye', + OWN: HomeIcon, + EDIT: EditIcon, + VIEW: EyeIcon, } export const AUTH_DESCRIPTION_MAP = { diff --git a/opendc-web/opendc-web-ui/src/util/available-metrics.js b/opendc-web/opendc-web-ui/src/util/available-metrics.js index 807bc0c1..b21ab150 100644 --- a/opendc-web/opendc-web-ui/src/util/available-metrics.js +++ b/opendc-web/opendc-web-ui/src/util/available-metrics.js @@ -1,12 +1,28 @@ +export const METRIC_GROUPS = { + 'Host Metrics': [ + 'total_overcommitted_burst', + 'total_power_draw', + 'total_failure_vm_slices', + 'total_granted_burst', + 'total_interfered_burst', + 'total_requested_burst', + 'mean_cpu_usage', + 'mean_cpu_demand', + 'mean_num_deployed_images', + 'max_num_deployed_images', + ], + 'Compute Service Metrics': ['total_vms_submitted', 'total_vms_queued', 'total_vms_finished', 'total_vms_failed'], +} + export const AVAILABLE_METRICS = [ + 'mean_cpu_usage', + 'mean_cpu_demand', + 'total_requested_burst', + 'total_granted_burst', 'total_overcommitted_burst', + 'total_interfered_burst', 'total_power_draw', 'total_failure_vm_slices', - 'total_granted_burst', - 'total_interfered_burst', - 'total_requested_burst', - 'mean_cpu_usage', - 'mean_cpu_demand', 'mean_num_deployed_images', 'max_num_deployed_images', 'total_vms_submitted', @@ -65,3 +81,22 @@ export const METRIC_UNITS = { total_vms_finished: 'VMs', total_vms_failed: 'VMs', } + +export const METRIC_DESCRIPTIONS = { + total_overcommitted_burst: + 'The total CPU clock cycles lost due to overcommitting of resources. This metric is an indicator for resource overload.', + total_requested_burst: 'The total CPU clock cycles that were requested by all virtual machines.', + total_granted_burst: 'The total CPU clock cycles executed by the hosts.', + total_interfered_burst: 'The total CPU clock cycles lost due to resource interference between virtual machines.', + total_power_draw: 'The average power usage in watts.', + mean_cpu_usage: 'The average amount of CPU clock cycles consumed by all virtual machines on a host.', + mean_cpu_demand: 'The average amount of CPU clock cycles requested by all powered on virtual machines on a host.', + mean_num_deployed_images: 'The average number of virtual machines deployed on a host.', + max_num_deployed_images: 'The maximum number of virtual machines deployed at any time.', + total_failure_vm_slices: 'The total amount of CPU clock cycles lost due to failure.', + total_vms_submitted: 'The total number of virtual machines scheduled by the compute service.', + total_vms_queued: + 'The maximum number of virtual machines waiting to be scheduled by the compute service at any point.', + total_vms_finished: 'The total number of virtual machines that completed successfully.', + total_vms_failed: 'The total number of virtual machines that failed during execution.', +} diff --git a/opendc-web/opendc-web-ui/src/util/date-time.js b/opendc-web/opendc-web-ui/src/util/date-time.js index 66efdf5b..7e2f6623 100644 --- a/opendc-web/opendc-web-ui/src/util/date-time.js +++ b/opendc-web/opendc-web-ui/src/util/date-time.js @@ -7,19 +7,7 @@ * @returns {string} A human-friendly string version of that date and time. */ export function parseAndFormatDateTime(dateTimeString) { - return formatDateTime(parseDateTime(dateTimeString)) -} - -/** - * Parses date-time string representations and returns a parsed object. - * - * The format assumed is "YYYY-MM-DDTHH:MM:SS". - * - * @param dateTimeString A string expressing a date and a time, in the above mentioned format. - * @returns {object} A Date object with the parsed date and time information as content. - */ -export function parseDateTime(dateTimeString) { - return new Date(dateTimeString + '.000Z') + return formatDateTime(new Date(dateTimeString)) } /** diff --git a/opendc-web/opendc-web-ui/src/util/date-time.test.js b/opendc-web/opendc-web-ui/src/util/date-time.test.js index 3d95eba6..431e39f7 100644 --- a/opendc-web/opendc-web-ui/src/util/date-time.test.js +++ b/opendc-web/opendc-web-ui/src/util/date-time.test.js @@ -1,18 +1,4 @@ -import { convertSecondsToFormattedTime, parseDateTime } from './date-time' - -describe('date-time parsing', () => { - it('reads components properly', () => { - const dateString = '2017-09-27T20:55:01' - const parsedDate = parseDateTime(dateString) - - expect(parsedDate.getUTCFullYear()).toEqual(2017) - expect(parsedDate.getUTCMonth()).toEqual(8) - expect(parsedDate.getUTCDate()).toEqual(27) - expect(parsedDate.getUTCHours()).toEqual(20) - expect(parsedDate.getUTCMinutes()).toEqual(55) - expect(parsedDate.getUTCSeconds()).toEqual(1) - }) -}) +import { convertSecondsToFormattedTime } from './date-time' describe('tick formatting', () => { it("returns '0s' for numbers <= 0", () => { diff --git a/opendc-web/opendc-web-ui/src/util/effect-ref.js b/opendc-web/opendc-web-ui/src/util/effect-ref.js new file mode 100644 index 00000000..cda0324b --- /dev/null +++ b/opendc-web/opendc-web-ui/src/util/effect-ref.js @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { useCallback, useRef } from 'react' + +const noop = () => {} + +/** + * A hook that will invoke the specified callback when the reference returned by this function is initialized. + * The callback can return an optional clean up function. + */ +export function useEffectRef(callback) { + const disposeRef = useRef(noop) + return useCallback((element) => { + disposeRef.current() + disposeRef.current = noop + + if (element) { + disposeRef.current = callback(element) || noop + } + }, []) // eslint-disable-line react-hooks/exhaustive-deps +} diff --git a/opendc-web/opendc-web-ui/src/util/sidebar-space.js b/opendc-web/opendc-web-ui/src/util/sidebar-space.js deleted file mode 100644 index ef09d40a..00000000 --- a/opendc-web/opendc-web-ui/src/util/sidebar-space.js +++ /dev/null @@ -1,2 +0,0 @@ -export const isCollapsible = (location) => - location.pathname.indexOf('portfolios') === -1 && location.pathname.indexOf('scenarios') === -1 diff --git a/opendc-web/opendc-web-ui/src/util/state-utils.js b/opendc-web/opendc-web-ui/src/util/state-utils.js deleted file mode 100644 index e5b695c3..00000000 --- a/opendc-web/opendc-web-ui/src/util/state-utils.js +++ /dev/null @@ -1,6 +0,0 @@ -export const getState = (dispatch) => - new Promise((resolve) => { - dispatch((dispatch, getState) => { - resolve(getState()) - }) - }) diff --git a/opendc-web/opendc-web-ui/src/util/tile-calculations.js b/opendc-web/opendc-web-ui/src/util/tile-calculations.js index 764ae6ac..374ca48c 100644 --- a/opendc-web/opendc-web-ui/src/util/tile-calculations.js +++ b/opendc-web/opendc-web-ui/src/util/tile-calculations.js @@ -18,8 +18,8 @@ function getWallSegments(tiles) { } let doInsert = true - for (let tileIndex in tiles) { - if (tiles[tileIndex].positionX === x + dX && tiles[tileIndex].positionY === y + dY) { + for (const tile of tiles) { + if (tile.positionX === x + dX && tile.positionY === y + dY) { doInsert = false break } diff --git a/opendc-web/opendc-web-ui/src/util/timeline.js b/opendc-web/opendc-web-ui/src/util/timeline.js deleted file mode 100644 index 7c8a3ef0..00000000 --- a/opendc-web/opendc-web-ui/src/util/timeline.js +++ /dev/null @@ -1,9 +0,0 @@ -export function convertTickToPercentage(tick, maxTick) { - if (maxTick === 0) { - return '0%' - } else if (tick > maxTick) { - return (maxTick / (maxTick + 1)) * 100 + '%' - } - - return (tick / (maxTick + 1)) * 100 + '%' -} diff --git a/opendc-web/opendc-web-ui/src/util/topology-schema.js b/opendc-web/opendc-web-ui/src/util/topology-schema.js new file mode 100644 index 00000000..7779ccfe --- /dev/null +++ b/opendc-web/opendc-web-ui/src/util/topology-schema.js @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { schema } from 'normalizr' + +const Cpu = new schema.Entity('cpus', {}, { idAttribute: '_id' }) +const Gpu = new schema.Entity('gpus', {}, { idAttribute: '_id' }) +const Memory = new schema.Entity('memories', {}, { idAttribute: '_id' }) +const Storage = new schema.Entity('storages', {}, { idAttribute: '_id' }) + +export const Machine = new schema.Entity( + 'machines', + { + cpus: [Cpu], + gpus: [Gpu], + memories: [Memory], + storages: [Storage], + }, + { idAttribute: '_id' } +) + +export const Rack = new schema.Entity('racks', { machines: [Machine] }, { idAttribute: '_id' }) + +export const Tile = new schema.Entity('tiles', { rack: Rack }, { idAttribute: '_id' }) + +export const Room = new schema.Entity('rooms', { tiles: [Tile] }, { idAttribute: '_id' }) + +export const Topology = new schema.Entity('topologies', { rooms: [Room] }, { idAttribute: '_id' }) diff --git a/opendc-web/opendc-web-ui/yarn.lock b/opendc-web/opendc-web-ui/yarn.lock index 10f974f1..1c898dd9 100644 --- a/opendc-web/opendc-web-ui/yarn.lock +++ b/opendc-web/opendc-web-ui/yarn.lock @@ -2,1330 +2,314 @@ # yarn lockfile v1 -"@babel/code-frame@7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e" - integrity sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g== - dependencies: - "@babel/highlight" "^7.8.3" - -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.3", "@babel/code-frame@^7.8.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.3.tgz#324bcfd8d35cd3d47dae18cde63d752086435e9a" - integrity sha512-fDx9eNW0qz0WkUeqL6tXEXzVlPh6Y5aCDEZesl0xBGA8ndRukX91Uk44ZqnkECp01NAZUdCAl+aiQNGi0k88Eg== - dependencies: - "@babel/highlight" "^7.10.3" - -"@babel/compat-data@^7.10.1", "@babel/compat-data@^7.10.3", "@babel/compat-data@^7.9.0": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.10.3.tgz#9af3e033f36e8e2d6e47570db91e64a846f5d382" - integrity sha512-BDIfJ9uNZuI0LajPfoYV28lX8kyCPMHY6uY4WH1lJdcicmAfxCK5ASzaeV0D/wsUaRH/cLk+amuxtC37sZ8TUg== - dependencies: - browserslist "^4.12.0" - invariant "^2.2.4" - semver "^5.5.0" - -"@babel/core@7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.9.0.tgz#ac977b538b77e132ff706f3b8a4dbad09c03c56e" - integrity sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w== - dependencies: - "@babel/code-frame" "^7.8.3" - "@babel/generator" "^7.9.0" - "@babel/helper-module-transforms" "^7.9.0" - "@babel/helpers" "^7.9.0" - "@babel/parser" "^7.9.0" - "@babel/template" "^7.8.6" - "@babel/traverse" "^7.9.0" - "@babel/types" "^7.9.0" - convert-source-map "^1.7.0" - debug "^4.1.0" - gensync "^1.0.0-beta.1" - json5 "^2.1.2" - lodash "^4.17.13" - resolve "^1.3.2" - semver "^5.4.1" - source-map "^0.5.0" - -"@babel/core@^7.1.0", "@babel/core@^7.4.5": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.10.3.tgz#73b0e8ddeec1e3fdd7a2de587a60e17c440ec77e" - integrity sha512-5YqWxYE3pyhIi84L84YcwjeEgS+fa7ZjK6IBVGTjDVfm64njkR2lfDhVR5OudLk8x2GK59YoSyVv+L/03k1q9w== - dependencies: - "@babel/code-frame" "^7.10.3" - "@babel/generator" "^7.10.3" - "@babel/helper-module-transforms" "^7.10.1" - "@babel/helpers" "^7.10.1" - "@babel/parser" "^7.10.3" - "@babel/template" "^7.10.3" - "@babel/traverse" "^7.10.3" - "@babel/types" "^7.10.3" - convert-source-map "^1.7.0" - debug "^4.1.0" - gensync "^1.0.0-beta.1" - json5 "^2.1.2" - lodash "^4.17.13" - resolve "^1.3.2" - semver "^5.4.1" - source-map "^0.5.0" - -"@babel/generator@^7.10.3", "@babel/generator@^7.4.0", "@babel/generator@^7.9.0": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.10.3.tgz#32b9a0d963a71d7a54f5f6c15659c3dbc2a523a5" - integrity sha512-drt8MUHbEqRzNR0xnF8nMehbY11b1SDkRw03PSNH/3Rb2Z35oxkddVSi3rcaak0YJQ86PCuE7Qx1jSFhbLNBMA== - dependencies: - "@babel/types" "^7.10.3" - jsesc "^2.5.1" - lodash "^4.17.13" - source-map "^0.5.0" - -"@babel/helper-annotate-as-pure@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.1.tgz#f6d08acc6f70bbd59b436262553fb2e259a1a268" - integrity sha512-ewp3rvJEwLaHgyWGe4wQssC2vjks3E80WiUe2BpMb0KhreTjMROCbxXcEovTrbeGVdQct5VjQfrv9EgC+xMzCw== - dependencies: - "@babel/types" "^7.10.1" - -"@babel/helper-builder-binary-assignment-operator-visitor@^7.10.1": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.3.tgz#4e9012d6701bef0030348d7f9c808209bd3e8687" - integrity sha512-lo4XXRnBlU6eRM92FkiZxpo1xFLmv3VsPFk61zJKMm7XYJfwqXHsYJTY6agoc4a3L8QPw1HqWehO18coZgbT6A== - dependencies: - "@babel/helper-explode-assignable-expression" "^7.10.3" - "@babel/types" "^7.10.3" - -"@babel/helper-builder-react-jsx-experimental@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.10.1.tgz#9a7d58ad184d3ac3bafb1a452cec2bad7e4a0bc8" - integrity sha512-irQJ8kpQUV3JasXPSFQ+LCCtJSc5ceZrPFVj6TElR6XCHssi3jV8ch3odIrNtjJFRZZVbrOEfJMI79TPU/h1pQ== - dependencies: - "@babel/helper-annotate-as-pure" "^7.10.1" - "@babel/helper-module-imports" "^7.10.1" - "@babel/types" "^7.10.1" - -"@babel/helper-builder-react-jsx@^7.10.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.10.3.tgz#62c4b7bb381153a0a5f8d83189b94b9fb5384fc5" - integrity sha512-vkxmuFvmovtqTZknyMGj9+uQAZzz5Z9mrbnkJnPkaYGfKTaSsYcjQdXP0lgrWLVh8wU6bCjOmXOpx+kqUi+S5Q== - dependencies: - "@babel/helper-annotate-as-pure" "^7.10.1" - "@babel/types" "^7.10.3" - -"@babel/helper-compilation-targets@^7.10.2", "@babel/helper-compilation-targets@^7.8.7": - version "7.10.2" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.2.tgz#a17d9723b6e2c750299d2a14d4637c76936d8285" - integrity sha512-hYgOhF4To2UTB4LTaZepN/4Pl9LD4gfbJx8A34mqoluT8TLbof1mhUlYuNWTEebONa8+UlCC4X0TEXu7AOUyGA== - dependencies: - "@babel/compat-data" "^7.10.1" - browserslist "^4.12.0" - invariant "^2.2.4" - levenary "^1.1.1" - semver "^5.5.0" - -"@babel/helper-create-class-features-plugin@^7.10.1", "@babel/helper-create-class-features-plugin@^7.10.3", "@babel/helper-create-class-features-plugin@^7.8.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.3.tgz#2783daa6866822e3d5ed119163b50f0fc3ae4b35" - integrity sha512-iRT9VwqtdFmv7UheJWthGc/h2s7MqoweBF9RUj77NFZsg9VfISvBTum3k6coAhJ8RWv2tj3yUjA03HxPd0vfpQ== - dependencies: - "@babel/helper-function-name" "^7.10.3" - "@babel/helper-member-expression-to-functions" "^7.10.3" - "@babel/helper-optimise-call-expression" "^7.10.3" - "@babel/helper-plugin-utils" "^7.10.3" - "@babel/helper-replace-supers" "^7.10.1" - "@babel/helper-split-export-declaration" "^7.10.1" - -"@babel/helper-create-regexp-features-plugin@^7.10.1", "@babel/helper-create-regexp-features-plugin@^7.8.3": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.1.tgz#1b8feeab1594cbcfbf3ab5a3bbcabac0468efdbd" - integrity sha512-Rx4rHS0pVuJn5pJOqaqcZR4XSgeF9G/pO/79t+4r7380tXFJdzImFnxMU19f83wjSrmKHq6myrM10pFHTGzkUA== - dependencies: - "@babel/helper-annotate-as-pure" "^7.10.1" - "@babel/helper-regex" "^7.10.1" - regexpu-core "^4.7.0" - -"@babel/helper-define-map@^7.10.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.10.3.tgz#d27120a5e57c84727b30944549b2dfeca62401a8" - integrity sha512-bxRzDi4Sin/k0drWCczppOhov1sBSdBvXJObM1NLHQzjhXhwRtn7aRWGvLJWCYbuu2qUk3EKs6Ci9C9ps8XokQ== - dependencies: - "@babel/helper-function-name" "^7.10.3" - "@babel/types" "^7.10.3" - lodash "^4.17.13" - -"@babel/helper-explode-assignable-expression@^7.10.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.10.3.tgz#9dc14f0cfa2833ea830a9c8a1c742b6e7461b05e" - integrity sha512-0nKcR64XrOC3lsl+uhD15cwxPvaB6QKUDlD84OT9C3myRbhJqTMYir69/RWItUvHpharv0eJ/wk7fl34ONSwZw== - dependencies: - "@babel/traverse" "^7.10.3" - "@babel/types" "^7.10.3" - -"@babel/helper-function-name@^7.10.1", "@babel/helper-function-name@^7.10.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.10.3.tgz#79316cd75a9fa25ba9787ff54544307ed444f197" - integrity sha512-FvSj2aiOd8zbeqijjgqdMDSyxsGHaMt5Tr0XjQsGKHD3/1FP3wksjnLAWzxw7lvXiej8W1Jt47SKTZ6upQNiRw== - dependencies: - "@babel/helper-get-function-arity" "^7.10.3" - "@babel/template" "^7.10.3" - "@babel/types" "^7.10.3" - -"@babel/helper-get-function-arity@^7.10.1", "@babel/helper-get-function-arity@^7.10.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.3.tgz#3a28f7b28ccc7719eacd9223b659fdf162e4c45e" - integrity sha512-iUD/gFsR+M6uiy69JA6fzM5seno8oE85IYZdbVVEuQaZlEzMO2MXblh+KSPJgsZAUx0EEbWXU0yJaW7C9CdAVg== - dependencies: - "@babel/types" "^7.10.3" - -"@babel/helper-hoist-variables@^7.10.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.3.tgz#d554f52baf1657ffbd7e5137311abc993bb3f068" - integrity sha512-9JyafKoBt5h20Yv1+BXQMdcXXavozI1vt401KBiRc2qzUepbVnd7ogVNymY1xkQN9fekGwfxtotH2Yf5xsGzgg== - dependencies: - "@babel/types" "^7.10.3" - -"@babel/helper-member-expression-to-functions@^7.10.1", "@babel/helper-member-expression-to-functions@^7.10.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.3.tgz#bc3663ac81ac57c39148fef4c69bf48a77ba8dd6" - integrity sha512-q7+37c4EPLSjNb2NmWOjNwj0+BOyYlssuQ58kHEWk1Z78K5i8vTUsteq78HMieRPQSl/NtpQyJfdjt3qZ5V2vw== - dependencies: - "@babel/types" "^7.10.3" - -"@babel/helper-module-imports@^7.10.1", "@babel/helper-module-imports@^7.10.3", "@babel/helper-module-imports@^7.8.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.10.3.tgz#766fa1d57608e53e5676f23ae498ec7a95e1b11a" - integrity sha512-Jtqw5M9pahLSUWA+76nhK9OG8nwYXzhQzVIGFoNaHnXF/r4l7kz4Fl0UAW7B6mqC5myoJiBP5/YQlXQTMfHI9w== - dependencies: - "@babel/types" "^7.10.3" - -"@babel/helper-module-transforms@^7.10.1", "@babel/helper-module-transforms@^7.9.0": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.10.1.tgz#24e2f08ee6832c60b157bb0936c86bef7210c622" - integrity sha512-RLHRCAzyJe7Q7sF4oy2cB+kRnU4wDZY/H2xJFGof+M+SJEGhZsb+GFj5j1AD8NiSaVBJ+Pf0/WObiXu/zxWpFg== - dependencies: - "@babel/helper-module-imports" "^7.10.1" - "@babel/helper-replace-supers" "^7.10.1" - "@babel/helper-simple-access" "^7.10.1" - "@babel/helper-split-export-declaration" "^7.10.1" - "@babel/template" "^7.10.1" - "@babel/types" "^7.10.1" - lodash "^4.17.13" - -"@babel/helper-optimise-call-expression@^7.10.1", "@babel/helper-optimise-call-expression@^7.10.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.3.tgz#f53c4b6783093195b0f69330439908841660c530" - integrity sha512-kT2R3VBH/cnSz+yChKpaKRJQJWxdGoc6SjioRId2wkeV3bK0wLLioFpJROrX0U4xr/NmxSSAWT/9Ih5snwIIzg== - dependencies: - "@babel/types" "^7.10.3" - -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.1", "@babel/helper-plugin-utils@^7.10.3", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.3.tgz#aac45cccf8bc1873b99a85f34bceef3beb5d3244" - integrity sha512-j/+j8NAWUTxOtx4LKHybpSClxHoq6I91DQ/mKgAXn5oNUPIUiGppjPIX3TDtJWPrdfP9Kfl7e4fgVMiQR9VE/g== - -"@babel/helper-regex@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.10.1.tgz#021cf1a7ba99822f993222a001cc3fec83255b96" - integrity sha512-7isHr19RsIJWWLLFn21ubFt223PjQyg1HY7CZEMRr820HttHPpVvrsIN3bUOo44DEfFV4kBXO7Abbn9KTUZV7g== - dependencies: - lodash "^4.17.13" - -"@babel/helper-remap-async-to-generator@^7.10.1", "@babel/helper-remap-async-to-generator@^7.10.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.10.3.tgz#18564f8a6748be466970195b876e8bba3bccf442" - integrity sha512-sLB7666ARbJUGDO60ZormmhQOyqMX/shKBXZ7fy937s+3ID8gSrneMvKSSb+8xIM5V7Vn6uNVtOY1vIm26XLtA== - dependencies: - "@babel/helper-annotate-as-pure" "^7.10.1" - "@babel/helper-wrap-function" "^7.10.1" - "@babel/template" "^7.10.3" - "@babel/traverse" "^7.10.3" - "@babel/types" "^7.10.3" - -"@babel/helper-replace-supers@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.10.1.tgz#ec6859d20c5d8087f6a2dc4e014db7228975f13d" - integrity sha512-SOwJzEfpuQwInzzQJGjGaiG578UYmyi2Xw668klPWV5n07B73S0a9btjLk/52Mlcxa+5AdIYqws1KyXRfMoB7A== - dependencies: - "@babel/helper-member-expression-to-functions" "^7.10.1" - "@babel/helper-optimise-call-expression" "^7.10.1" - "@babel/traverse" "^7.10.1" - "@babel/types" "^7.10.1" - -"@babel/helper-simple-access@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.10.1.tgz#08fb7e22ace9eb8326f7e3920a1c2052f13d851e" - integrity sha512-VSWpWzRzn9VtgMJBIWTZ+GP107kZdQ4YplJlCmIrjoLVSi/0upixezHCDG8kpPVTBJpKfxTH01wDhh+jS2zKbw== - dependencies: - "@babel/template" "^7.10.1" - "@babel/types" "^7.10.1" - -"@babel/helper-split-export-declaration@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.1.tgz#c6f4be1cbc15e3a868e4c64a17d5d31d754da35f" - integrity sha512-UQ1LVBPrYdbchNhLwj6fetj46BcFwfS4NllJo/1aJsT+1dLTEnXJL0qHqtY7gPzF8S2fXBJamf1biAXV3X077g== - dependencies: - "@babel/types" "^7.10.1" - -"@babel/helper-validator-identifier@^7.10.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.3.tgz#60d9847f98c4cea1b279e005fdb7c28be5412d15" - integrity sha512-bU8JvtlYpJSBPuj1VUmKpFGaDZuLxASky3LhaKj3bmpSTY6VWooSM8msk+Z0CZoErFye2tlABF6yDkT3FOPAXw== - -"@babel/helper-wrap-function@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.10.1.tgz#956d1310d6696257a7afd47e4c42dfda5dfcedc9" - integrity sha512-C0MzRGteVDn+H32/ZgbAv5r56f2o1fZSA/rj/TYo8JEJNHg+9BdSmKBUND0shxWRztWhjlT2cvHYuynpPsVJwQ== - dependencies: - "@babel/helper-function-name" "^7.10.1" - "@babel/template" "^7.10.1" - "@babel/traverse" "^7.10.1" - "@babel/types" "^7.10.1" - -"@babel/helpers@^7.10.1", "@babel/helpers@^7.9.0": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.10.1.tgz#a6827b7cb975c9d9cef5fd61d919f60d8844a973" - integrity sha512-muQNHF+IdU6wGgkaJyhhEmI54MOZBKsFfsXFhboz1ybwJ1Kl7IHlbm2a++4jwrmY5UYsgitt5lfqo1wMFcHmyw== - dependencies: - "@babel/template" "^7.10.1" - "@babel/traverse" "^7.10.1" - "@babel/types" "^7.10.1" - -"@babel/highlight@^7.10.3", "@babel/highlight@^7.8.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.3.tgz#c633bb34adf07c5c13156692f5922c81ec53f28d" - integrity sha512-Ih9B/u7AtgEnySE2L2F0Xm0GaM729XqqLfHkalTsbjXGyqmf/6M0Cu0WpvqueUlW+xk88BHw9Nkpj49naU+vWw== - dependencies: - "@babel/helper-validator-identifier" "^7.10.3" +"@auth0/auth0-react@^1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@auth0/auth0-react/-/auth0-react-1.7.0.tgz#0fbfaf69b7ce5b50fcd82fb087a5fc6fc314e91e" + integrity sha512-D0JVqt8kvB8t+KkYAVyfTXxkMx0bJBTPNiQKWbiSwy9rm1PNVKgbS1/ABpf6DNbPeSdpOzVOQp0NVw5s6JivXw== + dependencies: + "@auth0/auth0-spa-js" "^1.17.1" + +"@auth0/auth0-spa-js@^1.17.1": + version "1.17.1" + resolved "https://registry.yarnpkg.com/@auth0/auth0-spa-js/-/auth0-spa-js-1.17.1.tgz#038f356019d1a0a7a48c694f95cf3cca69082317" + integrity sha512-1XfySBfqZnqrWeaCROZ2IMLD+8rQ9TlfttLv0utS91d4rjIKX1g/zTcLYqP68QnqKKGHaMOIee/eQJLnk/o+ow== + dependencies: + abortcontroller-polyfill "^1.7.3" + browser-tabs-lock "^1.2.14" + core-js "^3.16.1" + es-cookie "^1.3.2" + fast-text-encoding "^1.0.3" + promise-polyfill "^8.2.0" + unfetch "^4.2.0" + +"@babel/code-frame@7.12.11": + version "7.12.11" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" + integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw== + dependencies: + "@babel/highlight" "^7.10.4" + +"@babel/code-frame@^7.0.0": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.14.5.tgz#23b08d740e83f49c5e59945fbf1b43e80bbf4edb" + integrity sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw== + dependencies: + "@babel/highlight" "^7.14.5" + +"@babel/helper-plugin-utils@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz#5ac822ce97eec46741ab70a517971e443a70c5a9" + integrity sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ== + +"@babel/helper-validator-identifier@^7.14.5", "@babel/helper-validator-identifier@^7.14.9": + version "7.14.9" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz#6654d171b2024f6d8ee151bf2509699919131d48" + integrity sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g== + +"@babel/highlight@^7.10.4", "@babel/highlight@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.14.5.tgz#6861a52f03966405001f6aa534a01a24d99e8cd9" + integrity sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg== + dependencies: + "@babel/helper-validator-identifier" "^7.14.5" chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.10.3", "@babel/parser@^7.4.3", "@babel/parser@^7.7.0", "@babel/parser@^7.9.0": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.3.tgz#7e71d892b0d6e7d04a1af4c3c79d72c1f10f5315" - integrity sha512-oJtNJCMFdIMwXGmx+KxuaD7i3b8uS7TTFYW/FNG2BT8m+fmGHoiPYoH0Pe3gya07WuFmM5FCDIr1x0irkD/hyA== - -"@babel/plugin-proposal-async-generator-functions@^7.10.3", "@babel/plugin-proposal-async-generator-functions@^7.8.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.3.tgz#5a02453d46e5362e2073c7278beab2e53ad7d939" - integrity sha512-WUUWM7YTOudF4jZBAJIW9D7aViYC/Fn0Pln4RIHlQALyno3sXSjqmTA4Zy1TKC2D49RCR8Y/Pn4OIUtEypK3CA== - dependencies: - "@babel/helper-plugin-utils" "^7.10.3" - "@babel/helper-remap-async-to-generator" "^7.10.3" - "@babel/plugin-syntax-async-generators" "^7.8.0" - -"@babel/plugin-proposal-class-properties@7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.8.3.tgz#5e06654af5cd04b608915aada9b2a6788004464e" - integrity sha512-EqFhbo7IosdgPgZggHaNObkmO1kNUe3slaKu54d5OWvy+p9QIKOzK1GAEpAIsZtWVtPXUHSMcT4smvDrCfY4AA== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-proposal-class-properties@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.1.tgz#046bc7f6550bb08d9bd1d4f060f5f5a4f1087e01" - integrity sha512-sqdGWgoXlnOdgMXU+9MbhzwFRgxVLeiGBqTrnuS7LC2IBU31wSsESbTUreT2O418obpfPdGUR2GbEufZF1bpqw== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.1" - -"@babel/plugin-proposal-decorators@7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.8.3.tgz#2156860ab65c5abf068c3f67042184041066543e" - integrity sha512-e3RvdvS4qPJVTe288DlXjwKflpfy1hr0j5dz5WpIYYeP7vQZg2WfAEIp8k5/Lwis/m5REXEteIz6rrcDtXXG7w== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-syntax-decorators" "^7.8.3" - -"@babel/plugin-proposal-dynamic-import@^7.10.1", "@babel/plugin-proposal-dynamic-import@^7.8.3": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.1.tgz#e36979dc1dc3b73f6d6816fc4951da2363488ef0" - integrity sha512-Cpc2yUVHTEGPlmiQzXj026kqwjEQAD9I4ZC16uzdbgWgitg/UHKHLffKNCQZ5+y8jpIZPJcKcwsr2HwPh+w3XA== - dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - "@babel/plugin-syntax-dynamic-import" "^7.8.0" - -"@babel/plugin-proposal-json-strings@^7.10.1", "@babel/plugin-proposal-json-strings@^7.8.3": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.10.1.tgz#b1e691ee24c651b5a5e32213222b2379734aff09" - integrity sha512-m8r5BmV+ZLpWPtMY2mOKN7wre6HIO4gfIiV+eOmsnZABNenrt/kzYBwrh+KOfgumSWpnlGs5F70J8afYMSJMBg== - dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - "@babel/plugin-syntax-json-strings" "^7.8.0" - -"@babel/plugin-proposal-nullish-coalescing-operator@7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.8.3.tgz#e4572253fdeed65cddeecfdab3f928afeb2fd5d2" - integrity sha512-TS9MlfzXpXKt6YYomudb/KU7nQI6/xnapG6in1uZxoxDghuSMZsPb6D2fyUwNYSAp4l1iR7QtFOjkqcRYcUsfw== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" - -"@babel/plugin-proposal-nullish-coalescing-operator@^7.10.1", "@babel/plugin-proposal-nullish-coalescing-operator@^7.8.3": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.1.tgz#02dca21673842ff2fe763ac253777f235e9bbf78" - integrity sha512-56cI/uHYgL2C8HVuHOuvVowihhX0sxb3nnfVRzUeVHTWmRHTZrKuAh/OBIMggGU/S1g/1D2CRCXqP+3u7vX7iA== - dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" - -"@babel/plugin-proposal-numeric-separator@7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.8.3.tgz#5d6769409699ec9b3b68684cd8116cedff93bad8" - integrity sha512-jWioO1s6R/R+wEHizfaScNsAx+xKgwTLNXSh7tTC4Usj3ItsPEhYkEpU4h+lpnBwq7NBVOJXfO6cRFYcX69JUQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-syntax-numeric-separator" "^7.8.3" - -"@babel/plugin-proposal-numeric-separator@^7.10.1", "@babel/plugin-proposal-numeric-separator@^7.8.3": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.10.1.tgz#a9a38bc34f78bdfd981e791c27c6fdcec478c123" - integrity sha512-jjfym4N9HtCiNfyyLAVD8WqPYeHUrw4ihxuAynWj6zzp2gf9Ey2f7ImhFm6ikB3CLf5Z/zmcJDri6B4+9j9RsA== - dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - "@babel/plugin-syntax-numeric-separator" "^7.10.1" - -"@babel/plugin-proposal-object-rest-spread@^7.10.3", "@babel/plugin-proposal-object-rest-spread@^7.9.0": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.10.3.tgz#b8d0d22f70afa34ad84b7a200ff772f9b9fce474" - integrity sha512-ZZh5leCIlH9lni5bU/wB/UcjtcVLgR8gc+FAgW2OOY+m9h1II3ItTO1/cewNUcsIDZSYcSaz/rYVls+Fb0ExVQ== - dependencies: - "@babel/helper-plugin-utils" "^7.10.3" - "@babel/plugin-syntax-object-rest-spread" "^7.8.0" - "@babel/plugin-transform-parameters" "^7.10.1" - -"@babel/plugin-proposal-optional-catch-binding@^7.10.1", "@babel/plugin-proposal-optional-catch-binding@^7.8.3": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.10.1.tgz#c9f86d99305f9fa531b568ff5ab8c964b8b223d2" - integrity sha512-VqExgeE62YBqI3ogkGoOJp1R6u12DFZjqwJhqtKc2o5m1YTUuUWnos7bZQFBhwkxIFpWYJ7uB75U7VAPPiKETA== - dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" - -"@babel/plugin-proposal-optional-chaining@7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.9.0.tgz#31db16b154c39d6b8a645292472b98394c292a58" - integrity sha512-NDn5tu3tcv4W30jNhmc2hyD5c56G6cXx4TesJubhxrJeCvuuMpttxr0OnNCqbZGhFjLrg+NIhxxC+BK5F6yS3w== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-syntax-optional-chaining" "^7.8.0" - -"@babel/plugin-proposal-optional-chaining@^7.10.3", "@babel/plugin-proposal-optional-chaining@^7.9.0": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.10.3.tgz#9a726f94622b653c0a3a7a59cdce94730f526f7c" - integrity sha512-yyG3n9dJ1vZ6v5sfmIlMMZ8azQoqx/5/nZTSWX1td6L1H1bsjzA8TInDChpafCZiJkeOFzp/PtrfigAQXxI1Ng== - dependencies: - "@babel/helper-plugin-utils" "^7.10.3" - "@babel/plugin-syntax-optional-chaining" "^7.8.0" - -"@babel/plugin-proposal-private-methods@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.10.1.tgz#ed85e8058ab0fe309c3f448e5e1b73ca89cdb598" - integrity sha512-RZecFFJjDiQ2z6maFprLgrdnm0OzoC23Mx89xf1CcEsxmHuzuXOdniEuI+S3v7vjQG4F5sa6YtUp+19sZuSxHg== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.1" - -"@babel/plugin-proposal-unicode-property-regex@^7.10.1", "@babel/plugin-proposal-unicode-property-regex@^7.4.4", "@babel/plugin-proposal-unicode-property-regex@^7.8.3": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.10.1.tgz#dc04feb25e2dd70c12b05d680190e138fa2c0c6f" - integrity sha512-JjfngYRvwmPwmnbRZyNiPFI8zxCZb8euzbCG/LxyKdeTb59tVciKo9GK9bi6JYKInk1H11Dq9j/zRqIH4KigfQ== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.1" - -"@babel/plugin-syntax-async-generators@^7.8.0": - version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" - integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-class-properties@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.1.tgz#d5bc0645913df5b17ad7eda0fa2308330bde34c5" - integrity sha512-Gf2Yx/iRs1JREDtVZ56OrjjgFHCaldpTnuy9BHla10qyVT3YkIIGEtoDWhyop0ksu1GvNjHIoYRBqm3zoR1jyQ== - dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - -"@babel/plugin-syntax-decorators@^7.8.3": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.10.1.tgz#16b869c4beafc9a442565147bda7ce0967bd4f13" - integrity sha512-a9OAbQhKOwSle1Vr0NJu/ISg1sPfdEkfRKWpgPuzhnWWzForou2gIeUIIwjAMHRekhhpJ7eulZlYs0H14Cbi+g== - dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - -"@babel/plugin-syntax-dynamic-import@^7.8.0": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" - integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-flow@^7.8.3": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.10.1.tgz#cd4bbca62fb402babacb174f64f8734310d742f0" - integrity sha512-b3pWVncLBYoPP60UOTc7NMlbtsHQ6ITim78KQejNHK6WJ2mzV5kCcg4mIWpasAfJEgwVTibwo2e+FU7UEIKQUg== - dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - -"@babel/plugin-syntax-json-strings@^7.8.0": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" - integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-jsx@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.10.1.tgz#0ae371134a42b91d5418feb3c8c8d43e1565d2da" - integrity sha512-+OxyOArpVFXQeXKLO9o+r2I4dIoVoy6+Uu0vKELrlweDM3QJADZj+Z+5ERansZqIZBcLj42vHnDI8Rz9BnRIuQ== - dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - -"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.0": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" - integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-numeric-separator@^7.10.1", "@babel/plugin-syntax-numeric-separator@^7.8.0", "@babel/plugin-syntax-numeric-separator@^7.8.3": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.1.tgz#25761ee7410bc8cf97327ba741ee94e4a61b7d99" - integrity sha512-uTd0OsHrpe3tH5gRPTxG8Voh99/WCU78vIm5NMRYPAqC8lR4vajt6KkCAknCHrx24vkPdd/05yfdGSB4EIY2mg== - dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - -"@babel/plugin-syntax-object-rest-spread@^7.0.0", "@babel/plugin-syntax-object-rest-spread@^7.8.0": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" - integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-catch-binding@^7.8.0": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" - integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-chaining@^7.8.0": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" - integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-top-level-await@^7.10.1", "@babel/plugin-syntax-top-level-await@^7.8.3": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.10.1.tgz#8b8733f8c57397b3eaa47ddba8841586dcaef362" - integrity sha512-hgA5RYkmZm8FTFT3yu2N9Bx7yVVOKYT6yEdXXo6j2JTm0wNxgqaGeQVaSHRjhfnQbX91DtjFB6McRFSlcJH3xQ== - dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - -"@babel/plugin-syntax-typescript@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.10.1.tgz#5e82bc27bb4202b93b949b029e699db536733810" - integrity sha512-X/d8glkrAtra7CaQGMiGs/OGa6XgUzqPcBXCIGFCpCqnfGlT0Wfbzo/B89xHhnInTaItPK8LALblVXcUOEh95Q== - dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - -"@babel/plugin-transform-arrow-functions@^7.10.1", "@babel/plugin-transform-arrow-functions@^7.8.3": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.10.1.tgz#cb5ee3a36f0863c06ead0b409b4cc43a889b295b" - integrity sha512-6AZHgFJKP3DJX0eCNJj01RpytUa3SOGawIxweHkNX2L6PYikOZmoh5B0d7hIHaIgveMjX990IAa/xK7jRTN8OA== - dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - -"@babel/plugin-transform-async-to-generator@^7.10.1", "@babel/plugin-transform-async-to-generator@^7.8.3": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.10.1.tgz#e5153eb1a3e028f79194ed8a7a4bf55f862b2062" - integrity sha512-XCgYjJ8TY2slj6SReBUyamJn3k2JLUIiiR5b6t1mNCMSvv7yx+jJpaewakikp0uWFQSF7ChPPoe3dHmXLpISkg== - dependencies: - "@babel/helper-module-imports" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.1" - "@babel/helper-remap-async-to-generator" "^7.10.1" - -"@babel/plugin-transform-block-scoped-functions@^7.10.1", "@babel/plugin-transform-block-scoped-functions@^7.8.3": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.10.1.tgz#146856e756d54b20fff14b819456b3e01820b85d" - integrity sha512-B7K15Xp8lv0sOJrdVAoukKlxP9N59HS48V1J3U/JGj+Ad+MHq+am6xJVs85AgXrQn4LV8vaYFOB+pr/yIuzW8Q== - dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - -"@babel/plugin-transform-block-scoping@^7.10.1", "@babel/plugin-transform-block-scoping@^7.8.3": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.10.1.tgz#47092d89ca345811451cd0dc5d91605982705d5e" - integrity sha512-8bpWG6TtF5akdhIm/uWTyjHqENpy13Fx8chg7pFH875aNLwX8JxIxqm08gmAT+Whe6AOmaTeLPe7dpLbXt+xUw== - dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - lodash "^4.17.13" - -"@babel/plugin-transform-classes@^7.10.3", "@babel/plugin-transform-classes@^7.9.0": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.3.tgz#8d9a656bc3d01f3ff69e1fccb354b0f9d72ac544" - integrity sha512-irEX0ChJLaZVC7FvvRoSIxJlmk0IczFLcwaRXUArBKYHCHbOhe57aG8q3uw/fJsoSXvZhjRX960hyeAGlVBXZw== - dependencies: - "@babel/helper-annotate-as-pure" "^7.10.1" - "@babel/helper-define-map" "^7.10.3" - "@babel/helper-function-name" "^7.10.3" - "@babel/helper-optimise-call-expression" "^7.10.3" - "@babel/helper-plugin-utils" "^7.10.3" - "@babel/helper-replace-supers" "^7.10.1" - "@babel/helper-split-export-declaration" "^7.10.1" - globals "^11.1.0" - -"@babel/plugin-transform-computed-properties@^7.10.3", "@babel/plugin-transform-computed-properties@^7.8.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.3.tgz#d3aa6eef67cb967150f76faff20f0abbf553757b" - integrity sha512-GWzhaBOsdbjVFav96drOz7FzrcEW6AP5nax0gLIpstiFaI3LOb2tAg06TimaWU6YKOfUACK3FVrxPJ4GSc5TgA== - dependencies: - "@babel/helper-plugin-utils" "^7.10.3" - -"@babel/plugin-transform-destructuring@^7.10.1", "@babel/plugin-transform-destructuring@^7.8.3": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.10.1.tgz#abd58e51337815ca3a22a336b85f62b998e71907" - integrity sha512-V/nUc4yGWG71OhaTH705pU8ZSdM6c1KmmLP8ys59oOYbT7RpMYAR3MsVOt6OHL0WzG7BlTU076va9fjJyYzJMA== - dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - -"@babel/plugin-transform-dotall-regex@^7.10.1", "@babel/plugin-transform-dotall-regex@^7.4.4", "@babel/plugin-transform-dotall-regex@^7.8.3": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.10.1.tgz#920b9fec2d78bb57ebb64a644d5c2ba67cc104ee" - integrity sha512-19VIMsD1dp02RvduFUmfzj8uknaO3uiHHF0s3E1OHnVsNj8oge8EQ5RzHRbJjGSetRnkEuBYO7TG1M5kKjGLOA== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.1" - -"@babel/plugin-transform-duplicate-keys@^7.10.1", "@babel/plugin-transform-duplicate-keys@^7.8.3": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.10.1.tgz#c900a793beb096bc9d4d0a9d0cde19518ffc83b9" - integrity sha512-wIEpkX4QvX8Mo9W6XF3EdGttrIPZWozHfEaDTU0WJD/TDnXMvdDh30mzUl/9qWhnf7naicYartcEfUghTCSNpA== - dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - -"@babel/plugin-transform-exponentiation-operator@^7.10.1", "@babel/plugin-transform-exponentiation-operator@^7.8.3": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.10.1.tgz#279c3116756a60dd6e6f5e488ba7957db9c59eb3" - integrity sha512-lr/przdAbpEA2BUzRvjXdEDLrArGRRPwbaF9rvayuHRvdQ7lUTTkZnhZrJ4LE2jvgMRFF4f0YuPQ20vhiPYxtA== - dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.1" - -"@babel/plugin-transform-flow-strip-types@7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.9.0.tgz#8a3538aa40434e000b8f44a3c5c9ac7229bd2392" - integrity sha512-7Qfg0lKQhEHs93FChxVLAvhBshOPQDtJUTVHr/ZwQNRccCm4O9D79r9tVSoV8iNwjP1YgfD+e/fgHcPkN1qEQg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-syntax-flow" "^7.8.3" - -"@babel/plugin-transform-for-of@^7.10.1", "@babel/plugin-transform-for-of@^7.9.0": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.1.tgz#ff01119784eb0ee32258e8646157ba2501fcfda5" - integrity sha512-US8KCuxfQcn0LwSCMWMma8M2R5mAjJGsmoCBVwlMygvmDUMkTCykc84IqN1M7t+agSfOmLYTInLCHJM+RUoz+w== +"@babel/plugin-syntax-jsx@7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.14.5.tgz#000e2e25d8673cce49300517a3eda44c263e4201" + integrity sha512-ohuFIsOMXJnbOMRfX7/w7LocdR6R7whhuRD4ax8IipLcLPlZGJKkBxgHp++U4N/vKyU16/YDQr2f5seajD3jIw== dependencies: - "@babel/helper-plugin-utils" "^7.10.1" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-function-name@^7.10.1", "@babel/plugin-transform-function-name@^7.8.3": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.10.1.tgz#4ed46fd6e1d8fde2a2ec7b03c66d853d2c92427d" - integrity sha512-//bsKsKFBJfGd65qSNNh1exBy5Y9gD9ZN+DvrJ8f7HXr4avE5POW6zB7Rj6VnqHV33+0vXWUwJT0wSHubiAQkw== +"@babel/runtime-corejs3@^7.10.2": + version "7.15.4" + resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.15.4.tgz#403139af262b9a6e8f9ba04a6fdcebf8de692bf1" + integrity sha512-lWcAqKeB624/twtTc3w6w/2o9RqJPaNBhPGK6DKLSiwuVWC7WFkypWyNg+CpZoyJH0jVzv1uMtXZ/5/lQOLtCg== dependencies: - "@babel/helper-function-name" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.1" - -"@babel/plugin-transform-literals@^7.10.1", "@babel/plugin-transform-literals@^7.8.3": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.10.1.tgz#5794f8da82846b22e4e6631ea1658bce708eb46a" - integrity sha512-qi0+5qgevz1NHLZroObRm5A+8JJtibb7vdcPQF1KQE12+Y/xxl8coJ+TpPW9iRq+Mhw/NKLjm+5SHtAHCC7lAw== - dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - -"@babel/plugin-transform-member-expression-literals@^7.10.1", "@babel/plugin-transform-member-expression-literals@^7.8.3": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.10.1.tgz#90347cba31bca6f394b3f7bd95d2bbfd9fce2f39" - integrity sha512-UmaWhDokOFT2GcgU6MkHC11i0NQcL63iqeufXWfRy6pUOGYeCGEKhvfFO6Vz70UfYJYHwveg62GS83Rvpxn+NA== - dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - -"@babel/plugin-transform-modules-amd@^7.10.1", "@babel/plugin-transform-modules-amd@^7.9.0": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.1.tgz#65950e8e05797ebd2fe532b96e19fc5482a1d52a" - integrity sha512-31+hnWSFRI4/ACFr1qkboBbrTxoBIzj7qA69qlq8HY8p7+YCzkCT6/TvQ1a4B0z27VeWtAeJd6pr5G04dc1iHw== - dependencies: - "@babel/helper-module-transforms" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.1" - babel-plugin-dynamic-import-node "^2.3.3" - -"@babel/plugin-transform-modules-commonjs@^7.10.1", "@babel/plugin-transform-modules-commonjs@^7.9.0": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.10.1.tgz#d5ff4b4413ed97ffded99961056e1fb980fb9301" - integrity sha512-AQG4fc3KOah0vdITwt7Gi6hD9BtQP/8bhem7OjbaMoRNCH5Djx42O2vYMfau7QnAzQCa+RJnhJBmFFMGpQEzrg== - dependencies: - "@babel/helper-module-transforms" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.1" - "@babel/helper-simple-access" "^7.10.1" - babel-plugin-dynamic-import-node "^2.3.3" - -"@babel/plugin-transform-modules-systemjs@^7.10.3", "@babel/plugin-transform-modules-systemjs@^7.9.0": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.3.tgz#004ae727b122b7b146b150d50cba5ffbff4ac56b" - integrity sha512-GWXWQMmE1GH4ALc7YXW56BTh/AlzvDWhUNn9ArFF0+Cz5G8esYlVbXfdyHa1xaD1j+GnBoCeoQNlwtZTVdiG/A== - dependencies: - "@babel/helper-hoist-variables" "^7.10.3" - "@babel/helper-module-transforms" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.3" - babel-plugin-dynamic-import-node "^2.3.3" - -"@babel/plugin-transform-modules-umd@^7.10.1", "@babel/plugin-transform-modules-umd@^7.9.0": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.10.1.tgz#ea080911ffc6eb21840a5197a39ede4ee67b1595" - integrity sha512-EIuiRNMd6GB6ulcYlETnYYfgv4AxqrswghmBRQbWLHZxN4s7mupxzglnHqk9ZiUpDI4eRWewedJJNj67PWOXKA== - dependencies: - "@babel/helper-module-transforms" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.1" - -"@babel/plugin-transform-named-capturing-groups-regex@^7.10.3", "@babel/plugin-transform-named-capturing-groups-regex@^7.8.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.10.3.tgz#a4f8444d1c5a46f35834a410285f2c901c007ca6" - integrity sha512-I3EH+RMFyVi8Iy/LekQm948Z4Lz4yKT7rK+vuCAeRm0kTa6Z5W7xuhRxDNJv0FPya/her6AUgrDITb70YHtTvA== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.8.3" - -"@babel/plugin-transform-new-target@^7.10.1", "@babel/plugin-transform-new-target@^7.8.3": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.10.1.tgz#6ee41a5e648da7632e22b6fb54012e87f612f324" - integrity sha512-MBlzPc1nJvbmO9rPr1fQwXOM2iGut+JC92ku6PbiJMMK7SnQc1rytgpopveE3Evn47gzvGYeCdgfCDbZo0ecUw== - dependencies: - "@babel/helper-plugin-utils" "^7.10.1" + core-js-pure "^3.16.0" + regenerator-runtime "^0.13.4" -"@babel/plugin-transform-object-super@^7.10.1", "@babel/plugin-transform-object-super@^7.8.3": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.1.tgz#2e3016b0adbf262983bf0d5121d676a5ed9c4fde" - integrity sha512-WnnStUDN5GL+wGQrJylrnnVlFhFmeArINIR9gjhSeYyvroGhBrSAXYg/RHsnfzmsa+onJrTJrEClPzgNmmQ4Gw== +"@babel/runtime@7.15.3": + version "7.15.3" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.15.3.tgz#2e1c2880ca118e5b2f9988322bd8a7656a32502b" + integrity sha512-OvwMLqNXkCXSz1kSm58sEsNuhqOx/fKpnUnKnFB5v8uDda5bLNEHNgKPvhDN6IU0LDcnHQ90LlJ0Q6jnyBSIBA== dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - "@babel/helper-replace-supers" "^7.10.1" + regenerator-runtime "^0.13.4" -"@babel/plugin-transform-parameters@^7.10.1", "@babel/plugin-transform-parameters@^7.8.7": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.1.tgz#b25938a3c5fae0354144a720b07b32766f683ddd" - integrity sha512-tJ1T0n6g4dXMsL45YsSzzSDZCxiHXAQp/qHrucOq5gEHncTA3xDxnd5+sZcoQp+N1ZbieAaB8r/VUCG0gqseOg== +"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.9.2": + version "7.15.4" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.15.4.tgz#fd17d16bfdf878e6dd02d19753a39fa8a8d9c84a" + integrity sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw== dependencies: - "@babel/helper-get-function-arity" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.1" + regenerator-runtime "^0.13.4" -"@babel/plugin-transform-property-literals@^7.10.1", "@babel/plugin-transform-property-literals@^7.8.3": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.1.tgz#cffc7315219230ed81dc53e4625bf86815b6050d" - integrity sha512-Kr6+mgag8auNrgEpbfIWzdXYOvqDHZOF0+Bx2xh4H2EDNwcbRb9lY6nkZg8oSjsX+DH9Ebxm9hOqtKW+gRDeNA== +"@babel/types@7.15.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.15.0.tgz#61af11f2286c4e9c69ca8deb5f4375a73c72dcbd" + integrity sha512-OBvfqnllOIdX4ojTHpwZbpvz4j3EWyjkZEdmjH0/cgsd6QOdSgU8rLSk6ard/pcW7rlmjdVSX/AWOaORR1uNOQ== dependencies: - "@babel/helper-plugin-utils" "^7.10.1" + "@babel/helper-validator-identifier" "^7.14.9" + to-fast-properties "^2.0.0" -"@babel/plugin-transform-react-constant-elements@^7.0.0": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.10.1.tgz#c7f117a54657cba3f9d32012e050fc89982df9e1" - integrity sha512-V4os6bkWt/jbrzfyVcZn2ZpuHZkvj3vyBU0U/dtS8SZuMS7Rfx5oknTrtfyXJ2/QZk8gX7Yls5Z921ItNpE30Q== +"@eslint/eslintrc@^0.4.3": + version "0.4.3" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c" + integrity sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw== dependencies: - "@babel/helper-plugin-utils" "^7.10.1" + ajv "^6.12.4" + debug "^4.1.1" + espree "^7.3.0" + globals "^13.9.0" + ignore "^4.0.6" + import-fresh "^3.2.1" + js-yaml "^3.13.1" + minimatch "^3.0.4" + strip-json-comments "^3.1.1" -"@babel/plugin-transform-react-display-name@7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.8.3.tgz#70ded987c91609f78353dd76d2fb2a0bb991e8e5" - integrity sha512-3Jy/PCw8Fe6uBKtEgz3M82ljt+lTg+xJaM4og+eyu83qLT87ZUSckn0wy7r31jflURWLO83TW6Ylf7lyXj3m5A== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" +"@fortawesome/fontawesome-common-types@^0.2.36": + version "0.2.36" + resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.36.tgz#b44e52db3b6b20523e0c57ef8c42d315532cb903" + integrity sha512-a/7BiSgobHAgBWeN7N0w+lAhInrGxksn13uK7231n2m8EDPE3BMCl9NZLTGrj9ZXfCmC6LM0QLqXidIizVQ6yg== -"@babel/plugin-transform-react-display-name@^7.10.1", "@babel/plugin-transform-react-display-name@^7.8.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.10.3.tgz#e3c246e1b4f3e52cc7633e237ad9194c0ec482e7" - integrity sha512-dOV44bnSW5KZ6kYF6xSHBth7TFiHHZReYXH/JH3XnFNV+soEL1F5d8JT7AJ3ZBncd19Qul7SN4YpBnyWOnQ8KA== +"@fortawesome/fontawesome-svg-core@^1.2.35": + version "1.2.36" + resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.36.tgz#4f2ea6f778298e0c47c6524ce2e7fd58eb6930e3" + integrity sha512-YUcsLQKYb6DmaJjIHdDWpBIGCcyE/W+p/LMGvjQem55Mm2XWVAP5kWTMKWLv9lwpCVjpLxPyOMOyUocP1GxrtA== dependencies: - "@babel/helper-plugin-utils" "^7.10.3" + "@fortawesome/fontawesome-common-types" "^0.2.36" -"@babel/plugin-transform-react-jsx-development@^7.10.1", "@babel/plugin-transform-react-jsx-development@^7.9.0": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.10.1.tgz#1ac6300d8b28ef381ee48e6fec430cc38047b7f3" - integrity sha512-XwDy/FFoCfw9wGFtdn5Z+dHh6HXKHkC6DwKNWpN74VWinUagZfDcEJc3Y8Dn5B3WMVnAllX8Kviaw7MtC5Epwg== +"@fortawesome/free-brands-svg-icons@^5.15.3": + version "5.15.4" + resolved "https://registry.yarnpkg.com/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-5.15.4.tgz#ec8a44dd383bcdd58aa7d1c96f38251e6fec9733" + integrity sha512-f1witbwycL9cTENJegcmcZRYyawAFbm8+c6IirLmwbbpqz46wyjbQYLuxOc7weXFXfB7QR8/Vd2u5R3q6JYD9g== dependencies: - "@babel/helper-builder-react-jsx-experimental" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.1" - "@babel/plugin-syntax-jsx" "^7.10.1" + "@fortawesome/fontawesome-common-types" "^0.2.36" -"@babel/plugin-transform-react-jsx-self@^7.10.1", "@babel/plugin-transform-react-jsx-self@^7.9.0": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.10.1.tgz#22143e14388d72eb88649606bb9e46f421bc3821" - integrity sha512-4p+RBw9d1qV4S749J42ZooeQaBomFPrSxa9JONLHJ1TxCBo3TzJ79vtmG2S2erUT8PDDrPdw4ZbXGr2/1+dILA== +"@fortawesome/free-solid-svg-icons@^5.15.3": + version "5.15.4" + resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.4.tgz#2a68f3fc3ddda12e52645654142b9e4e8fbb6cc5" + integrity sha512-JLmQfz6tdtwxoihXLg6lT78BorrFyCf59SAwBM6qV/0zXyVeDygJVb3fk+j5Qat+Yvcxp1buLTY5iDh1ZSAQ8w== dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - "@babel/plugin-syntax-jsx" "^7.10.1" + "@fortawesome/fontawesome-common-types" "^0.2.36" -"@babel/plugin-transform-react-jsx-source@^7.10.1", "@babel/plugin-transform-react-jsx-source@^7.9.0": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.10.1.tgz#30db3d4ee3cdebbb26a82a9703673714777a4273" - integrity sha512-neAbaKkoiL+LXYbGDvh6PjPG+YeA67OsZlE78u50xbWh2L1/C81uHiNP5d1fw+uqUIoiNdCC8ZB+G4Zh3hShJA== +"@fortawesome/react-fontawesome@^0.1.14": + version "0.1.15" + resolved "https://registry.yarnpkg.com/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.15.tgz#1450b838f905981d721bf07e14e3b52c0e9a91ed" + integrity sha512-/HFHdcoLESxxMkqZAcZ6RXDJ69pVApwdwRos/B2kiMWxDSAX2dFK8Er2/+rG+RsrzWB/dsAyjefLmemgmfE18g== dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - "@babel/plugin-syntax-jsx" "^7.10.1" + prop-types "^15.7.2" -"@babel/plugin-transform-react-jsx@^7.10.1", "@babel/plugin-transform-react-jsx@^7.9.1": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.10.3.tgz#c07ad86b7c159287c89b643f201f59536231048e" - integrity sha512-Y21E3rZmWICRJnvbGVmDLDZ8HfNDIwjGF3DXYHx1le0v0mIHCs0Gv5SavyW5Z/jgAHLaAoJPiwt+Dr7/zZKcOQ== +"@hapi/accept@5.0.2": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@hapi/accept/-/accept-5.0.2.tgz#ab7043b037e68b722f93f376afb05e85c0699523" + integrity sha512-CmzBx/bXUR8451fnZRuZAJRlzgm0Jgu5dltTX/bszmR2lheb9BpyN47Q1RbaGTsvFzn0PXAEs+lXDKfshccYZw== dependencies: - "@babel/helper-builder-react-jsx" "^7.10.3" - "@babel/helper-builder-react-jsx-experimental" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.3" - "@babel/plugin-syntax-jsx" "^7.10.1" + "@hapi/boom" "9.x.x" + "@hapi/hoek" "9.x.x" -"@babel/plugin-transform-react-pure-annotations@^7.10.1": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.10.3.tgz#97840981673fcb0df2cc33fb25b56cc421f7deef" - integrity sha512-n/fWYGqvTl7OLZs/QcWaKMFdADPvC3V6jYuEOpPyvz97onsW9TXn196fHnHW1ZgkO20/rxLOgKnEtN1q9jkgqA== +"@hapi/boom@9.x.x": + version "9.1.4" + resolved "https://registry.yarnpkg.com/@hapi/boom/-/boom-9.1.4.tgz#1f9dad367c6a7da9f8def24b4a986fc5a7bd9db6" + integrity sha512-Ls1oH8jaN1vNsqcaHVYJrKmgMcKsC1wcp8bujvXrHaAqD2iDYq3HoOwsxwo09Cuda5R5nC0o0IxlrlTuvPuzSw== dependencies: - "@babel/helper-annotate-as-pure" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.3" - -"@babel/plugin-transform-regenerator@^7.10.3", "@babel/plugin-transform-regenerator@^7.8.7": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.3.tgz#6ec680f140a5ceefd291c221cb7131f6d7e8cb6d" - integrity sha512-H5kNeW0u8mbk0qa1jVIVTeJJL6/TJ81ltD4oyPx0P499DhMJrTmmIFCmJ3QloGpQG8K9symccB7S7SJpCKLwtw== - dependencies: - regenerator-transform "^0.14.2" - -"@babel/plugin-transform-reserved-words@^7.10.1", "@babel/plugin-transform-reserved-words@^7.8.3": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.1.tgz#0fc1027312b4d1c3276a57890c8ae3bcc0b64a86" - integrity sha512-qN1OMoE2nuqSPmpTqEM7OvJ1FkMEV+BjVeZZm9V9mq/x1JLKQ4pcv8riZJMNN3u2AUGl0ouOMjRr2siecvHqUQ== - dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - -"@babel/plugin-transform-runtime@7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.9.0.tgz#45468c0ae74cc13204e1d3b1f4ce6ee83258af0b" - integrity sha512-pUu9VSf3kI1OqbWINQ7MaugnitRss1z533436waNXp+0N3ur3zfut37sXiQMxkuCF4VUjwZucen/quskCh7NHw== - dependencies: - "@babel/helper-module-imports" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - resolve "^1.8.1" - semver "^5.5.1" - -"@babel/plugin-transform-shorthand-properties@^7.10.1", "@babel/plugin-transform-shorthand-properties@^7.8.3": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.1.tgz#e8b54f238a1ccbae482c4dce946180ae7b3143f3" - integrity sha512-AR0E/lZMfLstScFwztApGeyTHJ5u3JUKMjneqRItWeEqDdHWZwAOKycvQNCasCK/3r5YXsuNG25funcJDu7Y2g== - dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - -"@babel/plugin-transform-spread@^7.10.1", "@babel/plugin-transform-spread@^7.8.3": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.10.1.tgz#0c6d618a0c4461a274418460a28c9ccf5239a7c8" - integrity sha512-8wTPym6edIrClW8FI2IoaePB91ETOtg36dOkj3bYcNe7aDMN2FXEoUa+WrmPc4xa1u2PQK46fUX2aCb+zo9rfw== - dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - -"@babel/plugin-transform-sticky-regex@^7.10.1", "@babel/plugin-transform-sticky-regex@^7.8.3": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.1.tgz#90fc89b7526228bed9842cff3588270a7a393b00" - integrity sha512-j17ojftKjrL7ufX8ajKvwRilwqTok4q+BjkknmQw9VNHnItTyMP5anPFzxFJdCQs7clLcWpCV3ma+6qZWLnGMA== - dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - "@babel/helper-regex" "^7.10.1" - -"@babel/plugin-transform-template-literals@^7.10.3", "@babel/plugin-transform-template-literals@^7.8.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.3.tgz#69d39b3d44b31e7b4864173322565894ce939b25" - integrity sha512-yaBn9OpxQra/bk0/CaA4wr41O0/Whkg6nqjqApcinxM7pro51ojhX6fv1pimAnVjVfDy14K0ULoRL70CA9jWWA== - dependencies: - "@babel/helper-annotate-as-pure" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.3" - -"@babel/plugin-transform-typeof-symbol@^7.10.1", "@babel/plugin-transform-typeof-symbol@^7.8.4": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.10.1.tgz#60c0239b69965d166b80a84de7315c1bc7e0bb0e" - integrity sha512-qX8KZcmbvA23zDi+lk9s6hC1FM7jgLHYIjuLgULgc8QtYnmB3tAVIYkNoKRQ75qWBeyzcoMoK8ZQmogGtC/w0g== - dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - -"@babel/plugin-transform-typescript@^7.9.0": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.10.3.tgz#b3b35fb34ef0bd628b4b8329b0e5f985369201d4" - integrity sha512-qU9Lu7oQyh3PGMQncNjQm8RWkzw6LqsWZQlZPQMgrGt6s3YiBIaQ+3CQV/FA/icGS5XlSWZGwo/l8ErTyelS0Q== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.10.3" - "@babel/helper-plugin-utils" "^7.10.3" - "@babel/plugin-syntax-typescript" "^7.10.1" - -"@babel/plugin-transform-unicode-escapes@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.10.1.tgz#add0f8483dab60570d9e03cecef6c023aa8c9940" - integrity sha512-zZ0Poh/yy1d4jeDWpx/mNwbKJVwUYJX73q+gyh4bwtG0/iUlzdEu0sLMda8yuDFS6LBQlT/ST1SJAR6zYwXWgw== - dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - -"@babel/plugin-transform-unicode-regex@^7.10.1", "@babel/plugin-transform-unicode-regex@^7.8.3": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.10.1.tgz#6b58f2aea7b68df37ac5025d9c88752443a6b43f" - integrity sha512-Y/2a2W299k0VIUdbqYm9X2qS6fE0CUBhhiPpimK6byy7OJ/kORLlIX+J6UrjgNu5awvs62k+6RSslxhcvVw2Tw== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.1" - -"@babel/preset-env@7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.9.0.tgz#a5fc42480e950ae8f5d9f8f2bbc03f52722df3a8" - integrity sha512-712DeRXT6dyKAM/FMbQTV/FvRCms2hPCx+3weRjZ8iQVQWZejWWk1wwG6ViWMyqb/ouBbGOl5b6aCk0+j1NmsQ== - dependencies: - "@babel/compat-data" "^7.9.0" - "@babel/helper-compilation-targets" "^7.8.7" - "@babel/helper-module-imports" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-proposal-async-generator-functions" "^7.8.3" - "@babel/plugin-proposal-dynamic-import" "^7.8.3" - "@babel/plugin-proposal-json-strings" "^7.8.3" - "@babel/plugin-proposal-nullish-coalescing-operator" "^7.8.3" - "@babel/plugin-proposal-numeric-separator" "^7.8.3" - "@babel/plugin-proposal-object-rest-spread" "^7.9.0" - "@babel/plugin-proposal-optional-catch-binding" "^7.8.3" - "@babel/plugin-proposal-optional-chaining" "^7.9.0" - "@babel/plugin-proposal-unicode-property-regex" "^7.8.3" - "@babel/plugin-syntax-async-generators" "^7.8.0" - "@babel/plugin-syntax-dynamic-import" "^7.8.0" - "@babel/plugin-syntax-json-strings" "^7.8.0" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" - "@babel/plugin-syntax-numeric-separator" "^7.8.0" - "@babel/plugin-syntax-object-rest-spread" "^7.8.0" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" - "@babel/plugin-syntax-optional-chaining" "^7.8.0" - "@babel/plugin-syntax-top-level-await" "^7.8.3" - "@babel/plugin-transform-arrow-functions" "^7.8.3" - "@babel/plugin-transform-async-to-generator" "^7.8.3" - "@babel/plugin-transform-block-scoped-functions" "^7.8.3" - "@babel/plugin-transform-block-scoping" "^7.8.3" - "@babel/plugin-transform-classes" "^7.9.0" - "@babel/plugin-transform-computed-properties" "^7.8.3" - "@babel/plugin-transform-destructuring" "^7.8.3" - "@babel/plugin-transform-dotall-regex" "^7.8.3" - "@babel/plugin-transform-duplicate-keys" "^7.8.3" - "@babel/plugin-transform-exponentiation-operator" "^7.8.3" - "@babel/plugin-transform-for-of" "^7.9.0" - "@babel/plugin-transform-function-name" "^7.8.3" - "@babel/plugin-transform-literals" "^7.8.3" - "@babel/plugin-transform-member-expression-literals" "^7.8.3" - "@babel/plugin-transform-modules-amd" "^7.9.0" - "@babel/plugin-transform-modules-commonjs" "^7.9.0" - "@babel/plugin-transform-modules-systemjs" "^7.9.0" - "@babel/plugin-transform-modules-umd" "^7.9.0" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.8.3" - "@babel/plugin-transform-new-target" "^7.8.3" - "@babel/plugin-transform-object-super" "^7.8.3" - "@babel/plugin-transform-parameters" "^7.8.7" - "@babel/plugin-transform-property-literals" "^7.8.3" - "@babel/plugin-transform-regenerator" "^7.8.7" - "@babel/plugin-transform-reserved-words" "^7.8.3" - "@babel/plugin-transform-shorthand-properties" "^7.8.3" - "@babel/plugin-transform-spread" "^7.8.3" - "@babel/plugin-transform-sticky-regex" "^7.8.3" - "@babel/plugin-transform-template-literals" "^7.8.3" - "@babel/plugin-transform-typeof-symbol" "^7.8.4" - "@babel/plugin-transform-unicode-regex" "^7.8.3" - "@babel/preset-modules" "^0.1.3" - "@babel/types" "^7.9.0" - browserslist "^4.9.1" - core-js-compat "^3.6.2" - invariant "^2.2.2" - levenary "^1.1.1" - semver "^5.5.0" - -"@babel/preset-env@^7.4.5": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.10.3.tgz#3e58c9861bbd93b6a679987c7e4bd365c56c80c9" - integrity sha512-jHaSUgiewTmly88bJtMHbOd1bJf2ocYxb5BWKSDQIP5tmgFuS/n0gl+nhSrYDhT33m0vPxp+rP8oYYgPgMNQlg== - dependencies: - "@babel/compat-data" "^7.10.3" - "@babel/helper-compilation-targets" "^7.10.2" - "@babel/helper-module-imports" "^7.10.3" - "@babel/helper-plugin-utils" "^7.10.3" - "@babel/plugin-proposal-async-generator-functions" "^7.10.3" - "@babel/plugin-proposal-class-properties" "^7.10.1" - "@babel/plugin-proposal-dynamic-import" "^7.10.1" - "@babel/plugin-proposal-json-strings" "^7.10.1" - "@babel/plugin-proposal-nullish-coalescing-operator" "^7.10.1" - "@babel/plugin-proposal-numeric-separator" "^7.10.1" - "@babel/plugin-proposal-object-rest-spread" "^7.10.3" - "@babel/plugin-proposal-optional-catch-binding" "^7.10.1" - "@babel/plugin-proposal-optional-chaining" "^7.10.3" - "@babel/plugin-proposal-private-methods" "^7.10.1" - "@babel/plugin-proposal-unicode-property-regex" "^7.10.1" - "@babel/plugin-syntax-async-generators" "^7.8.0" - "@babel/plugin-syntax-class-properties" "^7.10.1" - "@babel/plugin-syntax-dynamic-import" "^7.8.0" - "@babel/plugin-syntax-json-strings" "^7.8.0" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" - "@babel/plugin-syntax-numeric-separator" "^7.10.1" - "@babel/plugin-syntax-object-rest-spread" "^7.8.0" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" - "@babel/plugin-syntax-optional-chaining" "^7.8.0" - "@babel/plugin-syntax-top-level-await" "^7.10.1" - "@babel/plugin-transform-arrow-functions" "^7.10.1" - "@babel/plugin-transform-async-to-generator" "^7.10.1" - "@babel/plugin-transform-block-scoped-functions" "^7.10.1" - "@babel/plugin-transform-block-scoping" "^7.10.1" - "@babel/plugin-transform-classes" "^7.10.3" - "@babel/plugin-transform-computed-properties" "^7.10.3" - "@babel/plugin-transform-destructuring" "^7.10.1" - "@babel/plugin-transform-dotall-regex" "^7.10.1" - "@babel/plugin-transform-duplicate-keys" "^7.10.1" - "@babel/plugin-transform-exponentiation-operator" "^7.10.1" - "@babel/plugin-transform-for-of" "^7.10.1" - "@babel/plugin-transform-function-name" "^7.10.1" - "@babel/plugin-transform-literals" "^7.10.1" - "@babel/plugin-transform-member-expression-literals" "^7.10.1" - "@babel/plugin-transform-modules-amd" "^7.10.1" - "@babel/plugin-transform-modules-commonjs" "^7.10.1" - "@babel/plugin-transform-modules-systemjs" "^7.10.3" - "@babel/plugin-transform-modules-umd" "^7.10.1" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.10.3" - "@babel/plugin-transform-new-target" "^7.10.1" - "@babel/plugin-transform-object-super" "^7.10.1" - "@babel/plugin-transform-parameters" "^7.10.1" - "@babel/plugin-transform-property-literals" "^7.10.1" - "@babel/plugin-transform-regenerator" "^7.10.3" - "@babel/plugin-transform-reserved-words" "^7.10.1" - "@babel/plugin-transform-shorthand-properties" "^7.10.1" - "@babel/plugin-transform-spread" "^7.10.1" - "@babel/plugin-transform-sticky-regex" "^7.10.1" - "@babel/plugin-transform-template-literals" "^7.10.3" - "@babel/plugin-transform-typeof-symbol" "^7.10.1" - "@babel/plugin-transform-unicode-escapes" "^7.10.1" - "@babel/plugin-transform-unicode-regex" "^7.10.1" - "@babel/preset-modules" "^0.1.3" - "@babel/types" "^7.10.3" - browserslist "^4.12.0" - core-js-compat "^3.6.2" - invariant "^2.2.2" - levenary "^1.1.1" - semver "^5.5.0" - -"@babel/preset-modules@^0.1.3": - version "0.1.3" - resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.3.tgz#13242b53b5ef8c883c3cf7dddd55b36ce80fbc72" - integrity sha512-Ra3JXOHBq2xd56xSF7lMKXdjBn3T772Y1Wet3yWnkDly9zHvJki029tAFzvAAK5cf4YV3yoxuP61crYRol6SVg== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-proposal-unicode-property-regex" "^7.4.4" - "@babel/plugin-transform-dotall-regex" "^7.4.4" - "@babel/types" "^7.4.4" - esutils "^2.0.2" + "@hapi/hoek" "9.x.x" -"@babel/preset-react@7.9.1": - version "7.9.1" - resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.9.1.tgz#b346403c36d58c3bb544148272a0cefd9c28677a" - integrity sha512-aJBYF23MPj0RNdp/4bHnAP0NVqqZRr9kl0NAOP4nJCex6OYVio59+dnQzsAWFuogdLyeaKA1hmfUIVZkY5J+TQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-transform-react-display-name" "^7.8.3" - "@babel/plugin-transform-react-jsx" "^7.9.1" - "@babel/plugin-transform-react-jsx-development" "^7.9.0" - "@babel/plugin-transform-react-jsx-self" "^7.9.0" - "@babel/plugin-transform-react-jsx-source" "^7.9.0" - -"@babel/preset-react@^7.0.0": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.10.1.tgz#e2ab8ae9a363ec307b936589f07ed753192de041" - integrity sha512-Rw0SxQ7VKhObmFjD/cUcKhPTtzpeviEFX1E6PgP+cYOhQ98icNqtINNFANlsdbQHrmeWnqdxA4Tmnl1jy5tp3Q== - dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - "@babel/plugin-transform-react-display-name" "^7.10.1" - "@babel/plugin-transform-react-jsx" "^7.10.1" - "@babel/plugin-transform-react-jsx-development" "^7.10.1" - "@babel/plugin-transform-react-jsx-self" "^7.10.1" - "@babel/plugin-transform-react-jsx-source" "^7.10.1" - "@babel/plugin-transform-react-pure-annotations" "^7.10.1" - -"@babel/preset-typescript@7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.9.0.tgz#87705a72b1f0d59df21c179f7c3d2ef4b16ce192" - integrity sha512-S4cueFnGrIbvYJgwsVFKdvOmpiL0XGw9MFW9D0vgRys5g36PBhZRL8NX8Gr2akz8XRtzq6HuDXPD/1nniagNUg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-transform-typescript" "^7.9.0" - -"@babel/runtime-corejs3@^7.8.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.10.3.tgz#931ed6941d3954924a7aa967ee440e60c507b91a" - integrity sha512-HA7RPj5xvJxQl429r5Cxr2trJwOfPjKiqhCXcdQPSqO2G0RHPZpXu4fkYmBaTKCp2c/jRaMK9GB/lN+7zvvFPw== - dependencies: - core-js-pure "^3.0.0" - regenerator-runtime "^0.13.4" +"@hapi/hoek@9.x.x": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.2.0.tgz#f3933a44e365864f4dad5db94158106d511e8131" + integrity sha512-sqKVVVOe5ivCaXDWivIJYVSaEgdQK9ul7a4Kity5Iw7u9+wBAPbX1RMSnLLmp7O4Vzj0WOWwMAJsTL00xwaNug== -"@babel/runtime@7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.0.tgz#337eda67401f5b066a6f205a3113d4ac18ba495b" - integrity sha512-cTIudHnzuWLS56ik4DnRnqqNf8MkdUzV4iFFI1h7Jo9xvrpQROYaAnaSd2mHLQAzzZAPfATynX5ord6YlNYNMA== +"@humanwhocodes/config-array@^0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9" + integrity sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg== dependencies: - regenerator-runtime "^0.13.4" + "@humanwhocodes/object-schema" "^1.2.0" + debug "^4.1.1" + minimatch "^3.0.4" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.3.tgz#670d002655a7c366540c67f6fd3342cd09500364" - integrity sha512-RzGO0RLSdokm9Ipe/YD+7ww8X2Ro79qiXZF3HU9ljrM+qnJmH1Vqth+hbiQZy761LnMJTMitHDuKVYTk3k4dLw== - dependencies: - regenerator-runtime "^0.13.4" +"@humanwhocodes/object-schema@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz#87de7af9c231826fdd68ac7258f77c429e0e5fcf" + integrity sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w== -"@babel/runtime@^7.1.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.2.0": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.1.tgz#b4116a6b6711d010b2dad3b7b6e43bf1b9954740" - integrity sha512-J5AIf3vPj3UwXaAzb5j1xM4WAQDX3EMgemF8rjCP3SoW09LfRKAXQKt6CoVYl230P6iWdRcBbnLDDdnqWxZSCA== - dependencies: - regenerator-runtime "^0.13.4" +"@juggle/resize-observer@^3.3.1": + version "3.3.1" + resolved "https://registry.yarnpkg.com/@juggle/resize-observer/-/resize-observer-3.3.1.tgz#b50a781709c81e10701004214340f25475a171a0" + integrity sha512-zMM9Ds+SawiUkakS7y94Ymqx+S0ORzpG3frZirN3l+UlXUmSUR7hF4wxCVqW+ei94JzV5kt0uXBcoOEAuiydrw== -"@babel/template@^7.10.1", "@babel/template@^7.10.3", "@babel/template@^7.4.0", "@babel/template@^7.8.6": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.3.tgz#4d13bc8e30bf95b0ce9d175d30306f42a2c9a7b8" - integrity sha512-5BjI4gdtD+9fHZUsaxPHPNpwa+xRkDO7c7JbhYn2afvrkDu5SfAAbi9AIMXw2xEhO/BR35TqiW97IqNvCo/GqA== - dependencies: - "@babel/code-frame" "^7.10.3" - "@babel/parser" "^7.10.3" - "@babel/types" "^7.10.3" - -"@babel/traverse@^7.1.0", "@babel/traverse@^7.10.1", "@babel/traverse@^7.10.3", "@babel/traverse@^7.4.3", "@babel/traverse@^7.7.0", "@babel/traverse@^7.9.0": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.10.3.tgz#0b01731794aa7b77b214bcd96661f18281155d7e" - integrity sha512-qO6623eBFhuPm0TmmrUFMT1FulCmsSeJuVGhiLodk2raUDFhhTECLd9E9jC4LBIWziqt4wgF6KuXE4d+Jz9yug== - dependencies: - "@babel/code-frame" "^7.10.3" - "@babel/generator" "^7.10.3" - "@babel/helper-function-name" "^7.10.3" - "@babel/helper-split-export-declaration" "^7.10.1" - "@babel/parser" "^7.10.3" - "@babel/types" "^7.10.3" - debug "^4.1.0" - globals "^11.1.0" - lodash "^4.17.13" - -"@babel/types@^7.0.0", "@babel/types@^7.10.1", "@babel/types@^7.10.3", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.7.0", "@babel/types@^7.9.0": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.10.3.tgz#6535e3b79fea86a6b09e012ea8528f935099de8e" - integrity sha512-nZxaJhBXBQ8HVoIcGsf9qWep3Oh3jCENK54V4mRF7qaJabVsAYdbTtmSD8WmAp1R6ytPiu5apMwSXyxB1WlaBA== - dependencies: - "@babel/helper-validator-identifier" "^7.10.3" - lodash "^4.17.13" - to-fast-properties "^2.0.0" +"@napi-rs/triples@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@napi-rs/triples/-/triples-1.0.3.tgz#76d6d0c3f4d16013c61e45dfca5ff1e6c31ae53c" + integrity sha512-jDJTpta+P4p1NZTFVLHJ/TLFVYVcOqv6l8xwOeBKNPMgY/zDYH/YH7SJbvrr/h1RcS9GzbPcLKGzpuK9cV56UA== + +"@next/env@11.1.2": + version "11.1.2" + resolved "https://registry.yarnpkg.com/@next/env/-/env-11.1.2.tgz#27996efbbc54c5f949f5e8c0a156e3aa48369b99" + integrity sha512-+fteyVdQ7C/OoulfcF6vd1Yk0FEli4453gr8kSFbU8sKseNSizYq6df5MKz/AjwLptsxrUeIkgBdAzbziyJ3mA== + +"@next/eslint-plugin-next@11.1.2": + version "11.1.2" + resolved "https://registry.yarnpkg.com/@next/eslint-plugin-next/-/eslint-plugin-next-11.1.2.tgz#f26cf90bcb6cd2e4645e2ba253bbc9aaaa43a170" + integrity sha512-cN+ojHRsufr9Yz0rtvjv8WI5En0RPZRJnt0y16Ha7DD+0n473evz8i1ETEJHmOLeR7iPJR0zxRrxeTN/bJMOjg== + dependencies: + glob "7.1.7" + +"@next/polyfill-module@11.1.2": + version "11.1.2" + resolved "https://registry.yarnpkg.com/@next/polyfill-module/-/polyfill-module-11.1.2.tgz#1fe92c364fdc81add775a16c678f5057c6aace98" + integrity sha512-xZmixqADM3xxtqBV0TpAwSFzWJP0MOQzRfzItHXf1LdQHWb0yofHHC+7eOrPFic8+ZGz5y7BdPkkgR1S25OymA== + +"@next/react-dev-overlay@11.1.2": + version "11.1.2" + resolved "https://registry.yarnpkg.com/@next/react-dev-overlay/-/react-dev-overlay-11.1.2.tgz#73795dc5454b7af168bac93df7099965ebb603be" + integrity sha512-rDF/mGY2NC69mMg2vDqzVpCOlWqnwPUXB2zkARhvknUHyS6QJphPYv9ozoPJuoT/QBs49JJd9KWaAzVBvq920A== + dependencies: + "@babel/code-frame" "7.12.11" + anser "1.4.9" + chalk "4.0.0" + classnames "2.2.6" + css.escape "1.5.1" + data-uri-to-buffer "3.0.1" + platform "1.3.6" + shell-quote "1.7.2" + source-map "0.8.0-beta.0" + stacktrace-parser "0.1.10" + strip-ansi "6.0.0" -"@cnakazawa/watch@^1.0.3": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" - integrity sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ== - dependencies: - exec-sh "^0.3.2" - minimist "^1.2.0" +"@next/react-refresh-utils@11.1.2": + version "11.1.2" + resolved "https://registry.yarnpkg.com/@next/react-refresh-utils/-/react-refresh-utils-11.1.2.tgz#44ea40d8e773e4b77bad85e24f6ac041d5e4b4a5" + integrity sha512-hsoJmPfhVqjZ8w4IFzoo8SyECVnN+8WMnImTbTKrRUHOVJcYMmKLL7xf7T0ft00tWwAl/3f3Q3poWIN2Ueql/Q== -"@csstools/convert-colors@^1.4.0": - version "1.4.0" - resolved "https://registry.yarnpkg.com/@csstools/convert-colors/-/convert-colors-1.4.0.tgz#ad495dc41b12e75d588c6db8b9834f08fa131eb7" - integrity sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw== +"@next/swc-darwin-arm64@11.1.2": + version "11.1.2" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-11.1.2.tgz#93226c38db488c4b62b30a53b530e87c969b8251" + integrity sha512-hZuwOlGOwBZADA8EyDYyjx3+4JGIGjSHDHWrmpI7g5rFmQNltjlbaefAbiU5Kk7j3BUSDwt30quJRFv3nyJQ0w== -"@csstools/normalize.css@^10.1.0": - version "10.1.0" - resolved "https://registry.yarnpkg.com/@csstools/normalize.css/-/normalize.css-10.1.0.tgz#f0950bba18819512d42f7197e56c518aa491cf18" - integrity sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg== +"@next/swc-darwin-x64@11.1.2": + version "11.1.2" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-11.1.2.tgz#792003989f560c00677b5daeff360b35b510db83" + integrity sha512-PGOp0E1GisU+EJJlsmJVGE+aPYD0Uh7zqgsrpD3F/Y3766Ptfbe1lEPPWnRDl+OzSSrSrX1lkyM/Jlmh5OwNvA== -"@hapi/address@2.x.x": - version "2.1.4" - resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5" - integrity sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ== +"@next/swc-linux-x64-gnu@11.1.2": + version "11.1.2" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-11.1.2.tgz#8216b2ae1f21f0112958735c39dd861088108f37" + integrity sha512-YcDHTJjn/8RqvyJVB6pvEKXihDcdrOwga3GfMv/QtVeLphTouY4BIcEUfrG5+26Nf37MP1ywN3RRl1TxpurAsQ== -"@hapi/bourne@1.x.x": - version "1.3.2" - resolved "https://registry.yarnpkg.com/@hapi/bourne/-/bourne-1.3.2.tgz#0a7095adea067243ce3283e1b56b8a8f453b242a" - integrity sha512-1dVNHT76Uu5N3eJNTYcvxee+jzX4Z9lfciqRRHCU27ihbUcYi+iSc2iml5Ke1LXe1SyJCLA0+14Jh4tXJgOppA== - -"@hapi/hoek@8.x.x", "@hapi/hoek@^8.3.0": - version "8.5.1" - resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-8.5.1.tgz#fde96064ca446dec8c55a8c2f130957b070c6e06" - integrity sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow== - -"@hapi/joi@^15.0.0": - version "15.1.1" - resolved "https://registry.yarnpkg.com/@hapi/joi/-/joi-15.1.1.tgz#c675b8a71296f02833f8d6d243b34c57b8ce19d7" - integrity sha512-entf8ZMOK8sc+8YfeOlM8pCfg3b5+WZIKBfUaaJT8UsjAAPjartzxIYm3TIbjvA4u+u++KbcXD38k682nVHDAQ== - dependencies: - "@hapi/address" "2.x.x" - "@hapi/bourne" "1.x.x" - "@hapi/hoek" "8.x.x" - "@hapi/topo" "3.x.x" - -"@hapi/topo@3.x.x": - version "3.1.6" - resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-3.1.6.tgz#68d935fa3eae7fdd5ab0d7f953f3205d8b2bfc29" - integrity sha512-tAag0jEcjwH+P2quUfipd7liWCNX2F8NvYjQp2wtInsZxnMlypdw0FtAOLxtvvkO+GSRRbmNi8m/5y42PQJYCQ== - dependencies: - "@hapi/hoek" "^8.3.0" - -"@jest/console@^24.7.1", "@jest/console@^24.9.0": - version "24.9.0" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-24.9.0.tgz#79b1bc06fb74a8cfb01cbdedf945584b1b9707f0" - integrity sha512-Zuj6b8TnKXi3q4ymac8EQfc3ea/uhLeCGThFqXeC8H9/raaH8ARPUTdId+XyGd03Z4In0/VjD2OYFcBF09fNLQ== - dependencies: - "@jest/source-map" "^24.9.0" - chalk "^2.0.1" - slash "^2.0.0" - -"@jest/core@^24.9.0": - version "24.9.0" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-24.9.0.tgz#2ceccd0b93181f9c4850e74f2a9ad43d351369c4" - integrity sha512-Fogg3s4wlAr1VX7q+rhV9RVnUv5tD7VuWfYy1+whMiWUrvl7U3QJSJyWcDio9Lq2prqYsZaeTv2Rz24pWGkJ2A== - dependencies: - "@jest/console" "^24.7.1" - "@jest/reporters" "^24.9.0" - "@jest/test-result" "^24.9.0" - "@jest/transform" "^24.9.0" - "@jest/types" "^24.9.0" - ansi-escapes "^3.0.0" - chalk "^2.0.1" - exit "^0.1.2" - graceful-fs "^4.1.15" - jest-changed-files "^24.9.0" - jest-config "^24.9.0" - jest-haste-map "^24.9.0" - jest-message-util "^24.9.0" - jest-regex-util "^24.3.0" - jest-resolve "^24.9.0" - jest-resolve-dependencies "^24.9.0" - jest-runner "^24.9.0" - jest-runtime "^24.9.0" - jest-snapshot "^24.9.0" - jest-util "^24.9.0" - jest-validate "^24.9.0" - jest-watcher "^24.9.0" - micromatch "^3.1.10" - p-each-series "^1.0.0" - realpath-native "^1.1.0" - rimraf "^2.5.4" - slash "^2.0.0" - strip-ansi "^5.0.0" - -"@jest/environment@^24.3.0", "@jest/environment@^24.9.0": - version "24.9.0" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-24.9.0.tgz#21e3afa2d65c0586cbd6cbefe208bafade44ab18" - integrity sha512-5A1QluTPhvdIPFYnO3sZC3smkNeXPVELz7ikPbhUj0bQjB07EoE9qtLrem14ZUYWdVayYbsjVwIiL4WBIMV4aQ== - dependencies: - "@jest/fake-timers" "^24.9.0" - "@jest/transform" "^24.9.0" - "@jest/types" "^24.9.0" - jest-mock "^24.9.0" - -"@jest/fake-timers@^24.3.0", "@jest/fake-timers@^24.9.0": - version "24.9.0" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-24.9.0.tgz#ba3e6bf0eecd09a636049896434d306636540c93" - integrity sha512-eWQcNa2YSwzXWIMC5KufBh3oWRIijrQFROsIqt6v/NS9Io/gknw1jsAC9c+ih/RQX4A3O7SeWAhQeN0goKhT9A== - dependencies: - "@jest/types" "^24.9.0" - jest-message-util "^24.9.0" - jest-mock "^24.9.0" - -"@jest/reporters@^24.9.0": - version "24.9.0" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-24.9.0.tgz#86660eff8e2b9661d042a8e98a028b8d631a5b43" - integrity sha512-mu4X0yjaHrffOsWmVLzitKmmmWSQ3GGuefgNscUSWNiUNcEOSEQk9k3pERKEQVBb0Cnn88+UESIsZEMH3o88Gw== - dependencies: - "@jest/environment" "^24.9.0" - "@jest/test-result" "^24.9.0" - "@jest/transform" "^24.9.0" - "@jest/types" "^24.9.0" - chalk "^2.0.1" - exit "^0.1.2" - glob "^7.1.2" - istanbul-lib-coverage "^2.0.2" - istanbul-lib-instrument "^3.0.1" - istanbul-lib-report "^2.0.4" - istanbul-lib-source-maps "^3.0.1" - istanbul-reports "^2.2.6" - jest-haste-map "^24.9.0" - jest-resolve "^24.9.0" - jest-runtime "^24.9.0" - jest-util "^24.9.0" - jest-worker "^24.6.0" - node-notifier "^5.4.2" - slash "^2.0.0" - source-map "^0.6.0" - string-length "^2.0.0" - -"@jest/source-map@^24.3.0", "@jest/source-map@^24.9.0": - version "24.9.0" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-24.9.0.tgz#0e263a94430be4b41da683ccc1e6bffe2a191714" - integrity sha512-/Xw7xGlsZb4MJzNDgB7PW5crou5JqWiBQaz6xyPd3ArOg2nfn/PunV8+olXbbEZzNl591o5rWKE9BRDaFAuIBg== - dependencies: - callsites "^3.0.0" - graceful-fs "^4.1.15" - source-map "^0.6.0" - -"@jest/test-result@^24.9.0": - version "24.9.0" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-24.9.0.tgz#11796e8aa9dbf88ea025757b3152595ad06ba0ca" - integrity sha512-XEFrHbBonBJ8dGp2JmF8kP/nQI/ImPpygKHwQ/SY+es59Z3L5PI4Qb9TQQMAEeYsThG1xF0k6tmG0tIKATNiiA== - dependencies: - "@jest/console" "^24.9.0" - "@jest/types" "^24.9.0" - "@types/istanbul-lib-coverage" "^2.0.0" - -"@jest/test-sequencer@^24.9.0": - version "24.9.0" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-24.9.0.tgz#f8f334f35b625a4f2f355f2fe7e6036dad2e6b31" - integrity sha512-6qqsU4o0kW1dvA95qfNog8v8gkRN9ph6Lz7r96IvZpHdNipP2cBcb07J1Z45mz/VIS01OHJ3pY8T5fUY38tg4A== - dependencies: - "@jest/test-result" "^24.9.0" - jest-haste-map "^24.9.0" - jest-runner "^24.9.0" - jest-runtime "^24.9.0" - -"@jest/transform@^24.9.0": - version "24.9.0" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-24.9.0.tgz#4ae2768b296553fadab09e9ec119543c90b16c56" - integrity sha512-TcQUmyNRxV94S0QpMOnZl0++6RMiqpbH/ZMccFB/amku6Uwvyb1cjYX7xkp5nGNkbX4QPH/FcB6q1HBTHynLmQ== - dependencies: - "@babel/core" "^7.1.0" - "@jest/types" "^24.9.0" - babel-plugin-istanbul "^5.1.0" - chalk "^2.0.1" - convert-source-map "^1.4.0" - fast-json-stable-stringify "^2.0.0" - graceful-fs "^4.1.15" - jest-haste-map "^24.9.0" - jest-regex-util "^24.9.0" - jest-util "^24.9.0" - micromatch "^3.1.10" - pirates "^4.0.1" - realpath-native "^1.1.0" - slash "^2.0.0" - source-map "^0.6.1" - write-file-atomic "2.4.1" +"@next/swc-win32-x64-msvc@11.1.2": + version "11.1.2" + resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-11.1.2.tgz#e15824405df137129918205e43cb5e9339589745" + integrity sha512-e/pIKVdB+tGQYa1cW3sAeHm8gzEri/HYLZHT4WZojrUxgWXqx8pk7S7Xs47uBcFTqBDRvK3EcQpPLf3XdVsDdg== -"@jest/types@^24.3.0", "@jest/types@^24.9.0": - version "24.9.0" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-24.9.0.tgz#63cb26cb7500d069e5a389441a7c6ab5e909fc59" - integrity sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw== +"@node-rs/helper@1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@node-rs/helper/-/helper-1.2.1.tgz#e079b05f21ff4329d82c4e1f71c0290e4ecdc70c" + integrity sha512-R5wEmm8nbuQU0YGGmYVjEc0OHtYsuXdpRG+Ut/3wZ9XAvQWyThN08bTh2cBJgoZxHQUPtvRfeQuxcAgLuiBISg== dependencies: - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^1.1.1" - "@types/yargs" "^13.0.0" + "@napi-rs/triples" "^1.0.3" -"@mrmlnc/readdir-enhanced@^2.2.1": - version "2.2.1" - resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde" - integrity sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g== +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== dependencies: - call-me-maybe "^1.0.1" - glob-to-regexp "^0.3.0" + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" -"@nodelib/fs.stat@^1.1.2": - version "1.1.3" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b" - integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw== +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@patternfly/react-core@^4.152.4": + version "4.152.4" + resolved "https://registry.yarnpkg.com/@patternfly/react-core/-/react-core-4.152.4.tgz#fa008d155ab0c4161aab27bb67ad88fb8689dee2" + integrity sha512-saMnAoF3KnYB5k6K04Y6NtO61bXYl71yzGPYSrM/DjyWDlIGLqPPhMe4kbWafLTpSgJdkxPoODgVMDp2ZIJ0Jw== + dependencies: + "@patternfly/react-icons" "^4.11.14" + "@patternfly/react-styles" "^4.11.13" + "@patternfly/react-tokens" "^4.12.15" + focus-trap "6.2.2" + react-dropzone "9.0.0" + tippy.js "5.1.2" + tslib "^2.0.0" + +"@patternfly/react-icons@^4.11.14": + version "4.11.14" + resolved "https://registry.yarnpkg.com/@patternfly/react-icons/-/react-icons-4.11.14.tgz#ef7a2de3f78d5bd83715afb9651a185a89901c22" + integrity sha512-SBwID1p+UaQ9BSmzIRFr+BJEhYgx1rWHlm2HIZzhoz7BG3Q7byaQ8ZNfZLm0D+ZGVJQ+fq0zUHGE1nzxDPFqNQ== + +"@patternfly/react-styles@^4.11.13": + version "4.11.13" + resolved "https://registry.yarnpkg.com/@patternfly/react-styles/-/react-styles-4.11.13.tgz#6595adc5ff40585add0128d27d420322b65b86a9" + integrity sha512-svhnWIqZwJt1cOxwYjvz6lVYeL+c9D17xpKqlkJapXRxJL3ppTfIqwBrT3o9+02ElaXUTKt4xjMkSnEVjw4qxA== + +"@patternfly/react-table@^4.29.58": + version "4.29.58" + resolved "https://registry.yarnpkg.com/@patternfly/react-table/-/react-table-4.29.58.tgz#be77a6eee6a60c26e1f6939aaae007f21be52355" + integrity sha512-0KaypqnO1baaS8ESpaMyPcGmOsAva7vO9GzBiiiKnYizfjsPPE58KryyiLu0fEXBT6ESzYYm8I6pZNzzlI50Ww== + dependencies: + "@patternfly/react-core" "^4.152.4" + "@patternfly/react-icons" "^4.11.14" + "@patternfly/react-styles" "^4.11.13" + "@patternfly/react-tokens" "^4.12.15" + lodash "^4.17.19" + tslib "^2.0.0" + +"@patternfly/react-tokens@^4.12.15": + version "4.12.15" + resolved "https://registry.yarnpkg.com/@patternfly/react-tokens/-/react-tokens-4.12.15.tgz#af760a1b378edce85fd2f7394553911b6ee3c4f0" + integrity sha512-lRW0qxGjuFEPMweBSQFHNRNoxavx5uR8b28f0lPN0Jlz4QsaCFVTmHM2XqflOHDpjE8SPJW/hJMSsyUrqnM5dw== "@redux-saga/core@^1.1.3": version "1.1.3" @@ -1371,269 +355,127 @@ resolved "https://registry.yarnpkg.com/@redux-saga/types/-/types-1.1.0.tgz#0e81ce56b4883b4b2a3001ebe1ab298b84237204" integrity sha512-afmTuJrylUU/0OtqzaRkbyYFFNgCF73Bvel/sw90pvGrWIZ+vyoIJqA6eMSoA6+nb443kTmulmBtC9NerXboNg== -"@sentry/browser@5.27.3": - version "5.27.3" - resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.27.3.tgz#02e78a4502ee99988d3cbb0075a11ec44b503871" - integrity sha512-vczS+XTW4Nk2A7TIpAw8IVFHpp+NK6mV9euBG2I61Bs2QbQY9yKLfbjiln/yH2Q8X4THX6MKa0GuiPoCEeq3uw== +"@rushstack/eslint-patch@^1.0.6": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.0.6.tgz#023d72a5c4531b4ce204528971700a78a85a0c50" + integrity sha512-Myxw//kzromB9yWgS8qYGuGVf91oBUUJpNvy5eM50sqvmKLbKjwLxohJnkWGTeeI9v9IBMtPLxz5Gc60FIfvCA== + +"@sentry/browser@5.30.0": + version "5.30.0" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.30.0.tgz#c28f49d551db3172080caef9f18791a7fd39e3b3" + integrity sha512-rOb58ZNVJWh1VuMuBG1mL9r54nZqKeaIlwSlvzJfc89vyfd7n6tQ1UXMN383QBz/MS5H5z44Hy5eE+7pCrYAfw== dependencies: - "@sentry/core" "5.27.3" - "@sentry/types" "5.27.3" - "@sentry/utils" "5.27.3" + "@sentry/core" "5.30.0" + "@sentry/types" "5.30.0" + "@sentry/utils" "5.30.0" tslib "^1.9.3" -"@sentry/core@5.27.3": - version "5.27.3" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.27.3.tgz#d7a175b71596b7eb4b2e8b4cd1858a60d95813bb" - integrity sha512-yqepQO88jSt5hy0awpk61AxI4oHB09LjVbUEk4nJDg+1YXuND23cuZvH+Sp2jCZX2vrsw2tefwflToYfA8/U2w== +"@sentry/core@5.30.0": + version "5.30.0" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.30.0.tgz#6b203664f69e75106ee8b5a2fe1d717379b331f3" + integrity sha512-TmfrII8w1PQZSZgPpUESqjB+jC6MvZJZdLtE/0hZ+SrnKhW3x5WlYLvTXZpcWePYBku7rl2wn1RZu6uT0qCTeg== dependencies: - "@sentry/hub" "5.27.3" - "@sentry/minimal" "5.27.3" - "@sentry/types" "5.27.3" - "@sentry/utils" "5.27.3" + "@sentry/hub" "5.30.0" + "@sentry/minimal" "5.30.0" + "@sentry/types" "5.30.0" + "@sentry/utils" "5.30.0" tslib "^1.9.3" -"@sentry/hub@5.27.3": - version "5.27.3" - resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.27.3.tgz#f509c2fd38f500afef6030504e82510dbd0649d6" - integrity sha512-icEH3hr6NVQkpowXZcPOs9IgJZP5lMKtvud4mVioSpkd+NxtRdKrGEX4eF2TCviOJc9Md0mV4K+aL5Au7hxggQ== +"@sentry/hub@5.30.0": + version "5.30.0" + resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.30.0.tgz#2453be9b9cb903404366e198bd30c7ca74cdc100" + integrity sha512-2tYrGnzb1gKz2EkMDQcfLrDTvmGcQPuWxLnJKXJvYTQDGLlEvi2tWz1VIHjunmOvJrB5aIQLhm+dcMRwFZDCqQ== dependencies: - "@sentry/types" "5.27.3" - "@sentry/utils" "5.27.3" + "@sentry/types" "5.30.0" + "@sentry/utils" "5.30.0" tslib "^1.9.3" -"@sentry/minimal@5.27.3": - version "5.27.3" - resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.27.3.tgz#c9263bdd6270bfeae64137177448911dff568e53" - integrity sha512-ng01cM0rsE1RMjqVTpPLN0ZVkTo0I675usM1krkpQe8ddW6tfQ6EJWpt02/BrpQZRQzTtfWp6/RyB1KFXg6icg== +"@sentry/minimal@5.30.0": + version "5.30.0" + resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.30.0.tgz#ce3d3a6a273428e0084adcb800bc12e72d34637b" + integrity sha512-BwWb/owZKtkDX+Sc4zCSTNcvZUq7YcH3uAVlmh/gtR9rmUvbzAA3ewLuB3myi4wWRAMEtny6+J/FN/x+2wn9Xw== dependencies: - "@sentry/hub" "5.27.3" - "@sentry/types" "5.27.3" + "@sentry/hub" "5.30.0" + "@sentry/types" "5.30.0" tslib "^1.9.3" -"@sentry/react@^5.27.3": - version "5.27.3" - resolved "https://registry.yarnpkg.com/@sentry/react/-/react-5.27.3.tgz#aefff1cb2249a4e7f123c7467d1da205d5c02e92" - integrity sha512-p7E+djSUVKz02HoRVDX+zamjV8+RL4bqoPnS9JQESweB0sRTYlpvi+CqWLYWNWnamWQWOl97hOw/lLDpo4kUSA== +"@sentry/react@^5.30.0": + version "5.30.0" + resolved "https://registry.yarnpkg.com/@sentry/react/-/react-5.30.0.tgz#320e05f766b6a26faefa8d76d1101fd50c69f541" + integrity sha512-dvn4mqCgbeEuUXEGp5P9PaW5j4GWTFUSdx/yG8f9IxNZv5zM+7otjog9ukrubFZvlxVxD/PrIxK0MhadfFY/Dw== dependencies: - "@sentry/browser" "5.27.3" - "@sentry/minimal" "5.27.3" - "@sentry/types" "5.27.3" - "@sentry/utils" "5.27.3" + "@sentry/browser" "5.30.0" + "@sentry/minimal" "5.30.0" + "@sentry/types" "5.30.0" + "@sentry/utils" "5.30.0" hoist-non-react-statics "^3.3.2" tslib "^1.9.3" -"@sentry/tracing@^5.27.3": - version "5.27.3" - resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-5.27.3.tgz#787e57a2f7071e375f4fad0f3c3a5ff3381928e7" - integrity sha512-UWrHMdGxPfx1u558CWm1tptc2z0BuqCHVe2+BNN7POahq5BkpbGqaotyPQTBHbfmcs6QGfsMG57ou8HQFrBxyA== +"@sentry/tracing@^5.30.0": + version "5.30.0" + resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-5.30.0.tgz#501d21f00c3f3be7f7635d8710da70d9419d4e1f" + integrity sha512-dUFowCr0AIMwiLD7Fs314Mdzcug+gBVo/+NCMyDw8tFxJkwWAKl7Qa2OZxLQ0ZHjakcj1hNKfCQJ9rhyfOl4Aw== dependencies: - "@sentry/hub" "5.27.3" - "@sentry/minimal" "5.27.3" - "@sentry/types" "5.27.3" - "@sentry/utils" "5.27.3" + "@sentry/hub" "5.30.0" + "@sentry/minimal" "5.30.0" + "@sentry/types" "5.30.0" + "@sentry/utils" "5.30.0" tslib "^1.9.3" -"@sentry/types@5.27.3": - version "5.27.3" - resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.27.3.tgz#d377508769bc658d672c287166c7f6c5db45660c" - integrity sha512-PkWhMArFMxBb1g3HtMEL8Ea9PYae2MU0z9CMIWiqzerFy2ZpKG98IU3pt8ic4JkmKQdwB8hDiZpRPMHhW0WYwQ== +"@sentry/types@5.30.0": + version "5.30.0" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.30.0.tgz#19709bbe12a1a0115bc790b8942917da5636f402" + integrity sha512-R8xOqlSTZ+htqrfteCWU5Nk0CDN5ApUTvrlvBuiH1DyP6czDZ4ktbZB0hAgBlVcK0U+qpD3ag3Tqqpa5Q67rPw== -"@sentry/utils@5.27.3": - version "5.27.3" - resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.27.3.tgz#1fc45dfad1f1e4398bee58684d8947666d8d3003" - integrity sha512-R9WvFrRBALZvCzu/9BsuXBCfkNxz4MwdBNSXaBsJo4afQw1ljkjIc9DpHzlL9S9goIwXo81Buwmr5gGDO6aH+Q== +"@sentry/utils@5.30.0": + version "5.30.0" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.30.0.tgz#9a5bd7ccff85ccfe7856d493bffa64cabc41e980" + integrity sha512-zaYmoH0NWWtvnJjC9/CBseXMtKHm/tm40sz3YfJRxeQjyzRqNQPgivpd9R/oDJCYj999mzdW382p/qi2ypjLww== dependencies: - "@sentry/types" "5.27.3" + "@sentry/types" "5.30.0" tslib "^1.9.3" -"@svgr/babel-plugin-add-jsx-attribute@^4.2.0": - version "4.2.0" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-4.2.0.tgz#dadcb6218503532d6884b210e7f3c502caaa44b1" - integrity sha512-j7KnilGyZzYr/jhcrSYS3FGWMZVaqyCG0vzMCwzvei0coIkczuYMcniK07nI0aHJINciujjH11T72ICW5eL5Ig== - -"@svgr/babel-plugin-remove-jsx-attribute@^4.2.0": - version "4.2.0" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-4.2.0.tgz#297550b9a8c0c7337bea12bdfc8a80bb66f85abc" - integrity sha512-3XHLtJ+HbRCH4n28S7y/yZoEQnRpl0tvTZQsHqvaeNXPra+6vE5tbRliH3ox1yZYPCxrlqaJT/Mg+75GpDKlvQ== - -"@svgr/babel-plugin-remove-jsx-empty-expression@^4.2.0": - version "4.2.0" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-4.2.0.tgz#c196302f3e68eab6a05e98af9ca8570bc13131c7" - integrity sha512-yTr2iLdf6oEuUE9MsRdvt0NmdpMBAkgK8Bjhl6epb+eQWk6abBaX3d65UZ3E3FWaOwePyUgNyNCMVG61gGCQ7w== - -"@svgr/babel-plugin-replace-jsx-attribute-value@^4.2.0": - version "4.2.0" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-4.2.0.tgz#310ec0775de808a6a2e4fd4268c245fd734c1165" - integrity sha512-U9m870Kqm0ko8beHawRXLGLvSi/ZMrl89gJ5BNcT452fAjtF2p4uRzXkdzvGJJJYBgx7BmqlDjBN/eCp5AAX2w== - -"@svgr/babel-plugin-svg-dynamic-title@^4.3.3": - version "4.3.3" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-4.3.3.tgz#2cdedd747e5b1b29ed4c241e46256aac8110dd93" - integrity sha512-w3Be6xUNdwgParsvxkkeZb545VhXEwjGMwExMVBIdPQJeyMQHqm9Msnb2a1teHBqUYL66qtwfhNkbj1iarCG7w== - -"@svgr/babel-plugin-svg-em-dimensions@^4.2.0": - version "4.2.0" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-4.2.0.tgz#9a94791c9a288108d20a9d2cc64cac820f141391" - integrity sha512-C0Uy+BHolCHGOZ8Dnr1zXy/KgpBOkEUYY9kI/HseHVPeMbluaX3CijJr7D4C5uR8zrc1T64nnq/k63ydQuGt4w== - -"@svgr/babel-plugin-transform-react-native-svg@^4.2.0": - version "4.2.0" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-4.2.0.tgz#151487322843359a1ca86b21a3815fd21a88b717" - integrity sha512-7YvynOpZDpCOUoIVlaaOUU87J4Z6RdD6spYN4eUb5tfPoKGSF9OG2NuhgYnq4jSkAxcpMaXWPf1cePkzmqTPNw== - -"@svgr/babel-plugin-transform-svg-component@^4.2.0": - version "4.2.0" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-4.2.0.tgz#5f1e2f886b2c85c67e76da42f0f6be1b1767b697" - integrity sha512-hYfYuZhQPCBVotABsXKSCfel2slf/yvJY8heTVX1PCTaq/IgASq1IyxPPKJ0chWREEKewIU/JMSsIGBtK1KKxw== - -"@svgr/babel-preset@^4.3.3": - version "4.3.3" - resolved "https://registry.yarnpkg.com/@svgr/babel-preset/-/babel-preset-4.3.3.tgz#a75d8c2f202ac0e5774e6bfc165d028b39a1316c" - integrity sha512-6PG80tdz4eAlYUN3g5GZiUjg2FMcp+Wn6rtnz5WJG9ITGEF1pmFdzq02597Hn0OmnQuCVaBYQE1OVFAnwOl+0A== - dependencies: - "@svgr/babel-plugin-add-jsx-attribute" "^4.2.0" - "@svgr/babel-plugin-remove-jsx-attribute" "^4.2.0" - "@svgr/babel-plugin-remove-jsx-empty-expression" "^4.2.0" - "@svgr/babel-plugin-replace-jsx-attribute-value" "^4.2.0" - "@svgr/babel-plugin-svg-dynamic-title" "^4.3.3" - "@svgr/babel-plugin-svg-em-dimensions" "^4.2.0" - "@svgr/babel-plugin-transform-react-native-svg" "^4.2.0" - "@svgr/babel-plugin-transform-svg-component" "^4.2.0" - -"@svgr/core@^4.3.3": - version "4.3.3" - resolved "https://registry.yarnpkg.com/@svgr/core/-/core-4.3.3.tgz#b37b89d5b757dc66e8c74156d00c368338d24293" - integrity sha512-qNuGF1QON1626UCaZamWt5yedpgOytvLj5BQZe2j1k1B8DUG4OyugZyfEwBeXozCUwhLEpsrgPrE+eCu4fY17w== - dependencies: - "@svgr/plugin-jsx" "^4.3.3" - camelcase "^5.3.1" - cosmiconfig "^5.2.1" - -"@svgr/hast-util-to-babel-ast@^4.3.2": - version "4.3.2" - resolved "https://registry.yarnpkg.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-4.3.2.tgz#1d5a082f7b929ef8f1f578950238f630e14532b8" - integrity sha512-JioXclZGhFIDL3ddn4Kiq8qEqYM2PyDKV0aYno8+IXTLuYt6TOgHUbUAAFvqtb0Xn37NwP0BTHglejFoYr8RZg== - dependencies: - "@babel/types" "^7.4.4" - -"@svgr/plugin-jsx@^4.3.3": - version "4.3.3" - resolved "https://registry.yarnpkg.com/@svgr/plugin-jsx/-/plugin-jsx-4.3.3.tgz#e2ba913dbdfbe85252a34db101abc7ebd50992fa" - integrity sha512-cLOCSpNWQnDB1/v+SUENHH7a0XY09bfuMKdq9+gYvtuwzC2rU4I0wKGFEp1i24holdQdwodCtDQdFtJiTCWc+w== - dependencies: - "@babel/core" "^7.4.5" - "@svgr/babel-preset" "^4.3.3" - "@svgr/hast-util-to-babel-ast" "^4.3.2" - svg-parser "^2.0.0" +"@types/d3-path@^2": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-2.0.1.tgz#ca03dfa8b94d8add97ad0cd97e96e2006b4763cb" + integrity sha512-6K8LaFlztlhZO7mwsZg7ClRsdLg3FJRzIIi6SZXDWmmSJc2x8dd2VkESbLXdk3p8cuvz71f36S0y8Zv2AxqvQw== -"@svgr/plugin-svgo@^4.3.1": - version "4.3.1" - resolved "https://registry.yarnpkg.com/@svgr/plugin-svgo/-/plugin-svgo-4.3.1.tgz#daac0a3d872e3f55935c6588dd370336865e9e32" - integrity sha512-PrMtEDUWjX3Ea65JsVCwTIXuSqa3CG9px+DluF1/eo9mlDrgrtFE7NE/DjdhjJgSM9wenlVBzkzneSIUgfUI/w== +"@types/d3-scale@^3.0.0": + version "3.3.2" + resolved "https://registry.yarnpkg.com/@types/d3-scale/-/d3-scale-3.3.2.tgz#18c94e90f4f1c6b1ee14a70f14bfca2bd1c61d06" + integrity sha512-gGqr7x1ost9px3FvIfUMi5XA/F/yAf4UkUDtdQhpH92XCT0Oa7zkkRzY61gPVJq+DxpHn/btouw5ohWkbBsCzQ== dependencies: - cosmiconfig "^5.2.1" - merge-deep "^3.0.2" - svgo "^1.2.2" - -"@svgr/webpack@4.3.3": - version "4.3.3" - resolved "https://registry.yarnpkg.com/@svgr/webpack/-/webpack-4.3.3.tgz#13cc2423bf3dff2d494f16b17eb7eacb86895017" - integrity sha512-bjnWolZ6KVsHhgyCoYRFmbd26p8XVbulCzSG53BDQqAr+JOAderYK7CuYrB3bDjHJuF6LJ7Wrr42+goLRV9qIg== - dependencies: - "@babel/core" "^7.4.5" - "@babel/plugin-transform-react-constant-elements" "^7.0.0" - "@babel/preset-env" "^7.4.5" - "@babel/preset-react" "^7.0.0" - "@svgr/core" "^4.3.3" - "@svgr/plugin-jsx" "^4.3.3" - "@svgr/plugin-svgo" "^4.3.1" - loader-utils "^1.2.3" - -"@types/babel__core@^7.1.0": - version "7.1.9" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.9.tgz#77e59d438522a6fb898fa43dc3455c6e72f3963d" - integrity sha512-sY2RsIJ5rpER1u3/aQ8OFSI7qGIy8o1NEEbgb2UaJcvOtXOMpd39ko723NBpjQFg9SIX7TXtjejZVGeIMLhoOw== - dependencies: - "@babel/parser" "^7.1.0" - "@babel/types" "^7.0.0" - "@types/babel__generator" "*" - "@types/babel__template" "*" - "@types/babel__traverse" "*" - -"@types/babel__generator@*": - version "7.6.1" - resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.1.tgz#4901767b397e8711aeb99df8d396d7ba7b7f0e04" - integrity sha512-bBKm+2VPJcMRVwNhxKu8W+5/zT7pwNEqeokFOmbvVSqGzFneNxYcEBro9Ac7/N9tlsaPYnZLK8J1LWKkMsLAew== - dependencies: - "@babel/types" "^7.0.0" - -"@types/babel__template@*": - version "7.0.2" - resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.0.2.tgz#4ff63d6b52eddac1de7b975a5223ed32ecea9307" - integrity sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg== - dependencies: - "@babel/parser" "^7.1.0" - "@babel/types" "^7.0.0" - -"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": - version "7.0.12" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.0.12.tgz#22f49a028e69465390f87bb103ebd61bd086b8f5" - integrity sha512-t4CoEokHTfcyfb4hUaF9oOHu9RmmNWnm1CP0YmMqOOfClKascOmvlEM736vlqeScuGvBDsHkf8R2INd4DWreQA== - dependencies: - "@babel/types" "^7.3.0" - -"@types/color-name@^1.1.1": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" - integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== + "@types/d3-time" "^2" -"@types/eslint-visitor-keys@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" - integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag== - -"@types/glob@^7.1.1": - version "7.1.2" - resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.2.tgz#06ca26521353a545d94a0adc74f38a59d232c987" - integrity sha512-VgNIkxK+j7Nz5P7jvUZlRvhuPSmsEfS03b0alKcq5V/STUKAa3Plemsn5mrQUO7am6OErJ4rhGEGJbACclrtRA== +"@types/d3-shape@^2.0.0": + version "2.1.3" + resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-2.1.3.tgz#35d397b9e687abaa0de82343b250b9897b8cacf3" + integrity sha512-HAhCel3wP93kh4/rq+7atLdybcESZ5bRHDEZUojClyZWsRuEMo3A52NGYJSh48SxfxEU6RZIVbZL2YFZ2OAlzQ== dependencies: - "@types/minimatch" "*" - "@types/node" "*" - -"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762" - integrity sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw== + "@types/d3-path" "^2" -"@types/istanbul-lib-report@*": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" - integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== - dependencies: - "@types/istanbul-lib-coverage" "*" +"@types/d3-time@^2": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@types/d3-time/-/d3-time-2.1.1.tgz#743fdc821c81f86537cbfece07093ac39b4bc342" + integrity sha512-9MVYlmIgmRR31C5b4FVSWtuMmBHh2mOWQYfl7XAYOa8dsnb7iEmUmRSWSFgXFtkjxO65d7hTUHQC+RhR/9IWFg== -"@types/istanbul-reports@^1.1.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-1.1.2.tgz#e875cc689e47bce549ec81f3df5e6f6f11cfaeb2" - integrity sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw== +"@types/hoist-non-react-statics@^3.3.0": + version "3.3.1" + resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" + integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA== dependencies: - "@types/istanbul-lib-coverage" "*" - "@types/istanbul-lib-report" "*" + "@types/react" "*" + hoist-non-react-statics "^3.3.0" -"@types/json-schema@^7.0.3", "@types/json-schema@^7.0.4": - version "7.0.5" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.5.tgz#dcce4430e64b443ba8945f0290fb564ad5bac6dd" - integrity sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ== - -"@types/minimatch@*": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" - integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= "@types/node@*": - version "14.0.14" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.14.tgz#24a0b5959f16ac141aeb0c5b3cd7a15b7c64cbce" - integrity sha512-syUgf67ZQpaJj01/tRTknkMNoBBLWJOBODF0Zm4NrXmiSuxjymFrxnTu1QVYRubhVkRcZLYZG8STTwJRdVm/WQ== + version "16.7.13" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.7.13.tgz#86fae356b03b5a12f2506c6cf6cd9287b205973f" + integrity sha512-pLUPDn+YG3FYEt/pHI74HmnJOWzeR+tOIQzUx93pi9M7D8OE7PSLr97HboXwk5F+JS+TLtWuzCOW97AHjmOXXA== "@types/parse-json@^4.0.0": version "4.0.0" @@ -1641,392 +483,154 @@ integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== "@types/prop-types@*": - version "15.7.3" - resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" - integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw== + version "15.7.4" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11" + integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ== -"@types/q@^1.5.1": - version "1.5.4" - resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24" - integrity sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug== +"@types/react-redux@^7.1.16": + version "7.1.18" + resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.18.tgz#2bf8fd56ebaae679a90ebffe48ff73717c438e04" + integrity sha512-9iwAsPyJ9DLTRH+OFeIrm9cAbIj1i2ANL3sKQFATqnPWRbg+jEFXyZOKHiQK/N86pNRXbb4HRxAxo0SIX1XwzQ== + dependencies: + "@types/hoist-non-react-statics" "^3.3.0" + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + redux "^4.0.0" "@types/react@*": - version "16.9.41" - resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.41.tgz#925137ee4d2ff406a0ecf29e8e9237390844002e" - integrity sha512-6cFei7F7L4wwuM+IND/Q2cV1koQUvJ8iSV+Gwn0c3kvABZ691g7sp3hfEQHOUBJtccl1gPi+EyNjMIl9nGA0ug== + version "17.0.20" + resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.20.tgz#a4284b184d47975c71658cd69e759b6bd37c3b8c" + integrity sha512-wWZrPlihslrPpcKyCSlmIlruakxr57/buQN1RjlIeaaTWDLtJkTtRW429MoQJergvVKc4IWBpRhWw7YNh/7GVA== dependencies: "@types/prop-types" "*" - csstype "^2.2.0" - -"@types/stack-utils@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" - integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw== - -"@types/uuid@8.0.0": - version "8.0.0" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.0.0.tgz#165aae4819ad2174a17476dbe66feebd549556c0" - integrity sha512-xSQfNcvOiE5f9dyd4Kzxbof1aTrLobL278pGLKOZI6esGfZ7ts9Ka16CzIN6Y8hFHE1C7jIBZokULhK1bOgjRw== + "@types/scheduler" "*" + csstype "^3.0.2" -"@types/yargs-parser@*": - version "15.0.0" - resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d" - integrity sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw== - -"@types/yargs@^13.0.0": - version "13.0.9" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-13.0.9.tgz#44028e974343c7afcf3960f1a2b1099c39a7b5e1" - integrity sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg== - dependencies: - "@types/yargs-parser" "*" - -"@typescript-eslint/eslint-plugin@^2.10.0": - version "2.34.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.34.0.tgz#6f8ce8a46c7dea4a6f1d171d2bb8fbae6dac2be9" - integrity sha512-4zY3Z88rEE99+CNvTbXSyovv2z9PNOVffTWD2W8QF5s2prBQtwN2zadqERcrHpcR7O/+KMI3fcTAmUUhK/iQcQ== - dependencies: - "@typescript-eslint/experimental-utils" "2.34.0" - functional-red-black-tree "^1.0.1" - regexpp "^3.0.0" - tsutils "^3.17.1" - -"@typescript-eslint/experimental-utils@2.34.0": - version "2.34.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.34.0.tgz#d3524b644cdb40eebceca67f8cf3e4cc9c8f980f" - integrity sha512-eS6FTkq+wuMJ+sgtuNTtcqavWXqsflWcfBnlYhg/nS4aZ1leewkXGbvBhaapn1q6qf4M71bsR1tez5JTRMuqwA== - dependencies: - "@types/json-schema" "^7.0.3" - "@typescript-eslint/typescript-estree" "2.34.0" - eslint-scope "^5.0.0" - eslint-utils "^2.0.0" - -"@typescript-eslint/parser@^2.10.0": - version "2.34.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.34.0.tgz#50252630ca319685420e9a39ca05fe185a256bc8" - integrity sha512-03ilO0ucSD0EPTw2X4PntSIRFtDPWjrVq7C3/Z3VQHRC7+13YB55rcJI3Jt+YgeHbjUdJPcPa7b23rXCBokuyA== - dependencies: - "@types/eslint-visitor-keys" "^1.0.0" - "@typescript-eslint/experimental-utils" "2.34.0" - "@typescript-eslint/typescript-estree" "2.34.0" - eslint-visitor-keys "^1.1.0" - -"@typescript-eslint/typescript-estree@2.34.0": - version "2.34.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.34.0.tgz#14aeb6353b39ef0732cc7f1b8285294937cf37d5" - integrity sha512-OMAr+nJWKdlVM9LOqCqh3pQQPwxHAN7Du8DR6dmwCrAmxtiXQnhHJ6tBNtf+cggqfo51SG/FCwnKhXCIM7hnVg== - dependencies: - debug "^4.1.1" - eslint-visitor-keys "^1.1.0" - glob "^7.1.6" +"@types/resize-observer-browser@^0.1.6": + version "0.1.6" + resolved "https://registry.yarnpkg.com/@types/resize-observer-browser/-/resize-observer-browser-0.1.6.tgz#d8e6c2f830e2650dc06fe74464472ff64b54a302" + integrity sha512-61IfTac0s9jvNtBCpyo86QeaN8qqpMGHdK0uGKCCIy2dt5/Yk84VduHIdWAcmkC5QvdkPL0p5eWYgUZtHKKUVg== + +"@types/scheduler@*": + version "0.16.2" + resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" + integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== + +"@types/uuid@8.3.1": + version "8.3.1" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.1.tgz#1a32969cf8f0364b3d8c8af9cc3555b7805df14f" + integrity sha512-Y2mHTRAbqfFkpjldbkHGY8JIzRN6XqYRliG8/24FcHm2D2PwW24fl5xMRTVGdrb7iMrwCaIEbLWerGIkXuFWVg== + +"@typescript-eslint/parser@^4.20.0": + version "4.31.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.31.0.tgz#87b7cd16b24b9170c77595d8b1363f8047121e05" + integrity sha512-oWbzvPh5amMuTmKaf1wp0ySxPt2ZXHnFQBN2Szu1O//7LmOvgaKTCIDNLK2NvzpmVd5A2M/1j/rujBqO37hj3w== + dependencies: + "@typescript-eslint/scope-manager" "4.31.0" + "@typescript-eslint/types" "4.31.0" + "@typescript-eslint/typescript-estree" "4.31.0" + debug "^4.3.1" + +"@typescript-eslint/scope-manager@4.31.0": + version "4.31.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.31.0.tgz#9be33aed4e9901db753803ba233b70d79a87fc3e" + integrity sha512-LJ+xtl34W76JMRLjbaQorhR0hfRAlp3Lscdiz9NeI/8i+q0hdBZ7BsiYieLoYWqy+AnRigaD3hUwPFugSzdocg== + dependencies: + "@typescript-eslint/types" "4.31.0" + "@typescript-eslint/visitor-keys" "4.31.0" + +"@typescript-eslint/types@4.31.0": + version "4.31.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.31.0.tgz#9a7c86fcc1620189567dc4e46cad7efa07ee8dce" + integrity sha512-9XR5q9mk7DCXgXLS7REIVs+BaAswfdHhx91XqlJklmqWpTALGjygWVIb/UnLh4NWhfwhR5wNe1yTyCInxVhLqQ== + +"@typescript-eslint/typescript-estree@4.31.0": + version "4.31.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.31.0.tgz#4da4cb6274a7ef3b21d53f9e7147cc76f278a078" + integrity sha512-QHl2014t3ptg+xpmOSSPn5hm4mY8D4s97ftzyk9BZ8RxYQ3j73XcwuijnJ9cMa6DO4aLXeo8XS3z1omT9LA/Eg== + dependencies: + "@typescript-eslint/types" "4.31.0" + "@typescript-eslint/visitor-keys" "4.31.0" + debug "^4.3.1" + globby "^11.0.3" is-glob "^4.0.1" - lodash "^4.17.15" - semver "^7.3.2" - tsutils "^3.17.1" - -"@webassemblyjs/ast@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.8.5.tgz#51b1c5fe6576a34953bf4b253df9f0d490d9e359" - integrity sha512-aJMfngIZ65+t71C3y2nBBg5FFG0Okt9m0XEgWZ7Ywgn1oMAT8cNwx00Uv1cQyHtidq0Xn94R4TAywO+LCQ+ZAQ== - dependencies: - "@webassemblyjs/helper-module-context" "1.8.5" - "@webassemblyjs/helper-wasm-bytecode" "1.8.5" - "@webassemblyjs/wast-parser" "1.8.5" - -"@webassemblyjs/floating-point-hex-parser@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.8.5.tgz#1ba926a2923613edce496fd5b02e8ce8a5f49721" - integrity sha512-9p+79WHru1oqBh9ewP9zW95E3XAo+90oth7S5Re3eQnECGq59ly1Ri5tsIipKGpiStHsUYmY3zMLqtk3gTcOtQ== - -"@webassemblyjs/helper-api-error@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.8.5.tgz#c49dad22f645227c5edb610bdb9697f1aab721f7" - integrity sha512-Za/tnzsvnqdaSPOUXHyKJ2XI7PDX64kWtURyGiJJZKVEdFOsdKUCPTNEVFZq3zJ2R0G5wc2PZ5gvdTRFgm81zA== - -"@webassemblyjs/helper-buffer@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.8.5.tgz#fea93e429863dd5e4338555f42292385a653f204" - integrity sha512-Ri2R8nOS0U6G49Q86goFIPNgjyl6+oE1abW1pS84BuhP1Qcr5JqMwRFT3Ah3ADDDYGEgGs1iyb1DGX+kAi/c/Q== - -"@webassemblyjs/helper-code-frame@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.8.5.tgz#9a740ff48e3faa3022b1dff54423df9aa293c25e" - integrity sha512-VQAadSubZIhNpH46IR3yWO4kZZjMxN1opDrzePLdVKAZ+DFjkGD/rf4v1jap744uPVU6yjL/smZbRIIJTOUnKQ== - dependencies: - "@webassemblyjs/wast-printer" "1.8.5" - -"@webassemblyjs/helper-fsm@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.8.5.tgz#ba0b7d3b3f7e4733da6059c9332275d860702452" - integrity sha512-kRuX/saORcg8se/ft6Q2UbRpZwP4y7YrWsLXPbbmtepKr22i8Z4O3V5QE9DbZK908dh5Xya4Un57SDIKwB9eow== - -"@webassemblyjs/helper-module-context@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.8.5.tgz#def4b9927b0101dc8cbbd8d1edb5b7b9c82eb245" - integrity sha512-/O1B236mN7UNEU4t9X7Pj38i4VoU8CcMHyy3l2cV/kIF4U5KoHXDVqcDuOs1ltkac90IM4vZdHc52t1x8Yfs3g== - dependencies: - "@webassemblyjs/ast" "1.8.5" - mamacro "^0.0.3" - -"@webassemblyjs/helper-wasm-bytecode@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.8.5.tgz#537a750eddf5c1e932f3744206551c91c1b93e61" - integrity sha512-Cu4YMYG3Ddl72CbmpjU/wbP6SACcOPVbHN1dI4VJNJVgFwaKf1ppeFJrwydOG3NDHxVGuCfPlLZNyEdIYlQ6QQ== - -"@webassemblyjs/helper-wasm-section@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.8.5.tgz#74ca6a6bcbe19e50a3b6b462847e69503e6bfcbf" - integrity sha512-VV083zwR+VTrIWWtgIUpqfvVdK4ff38loRmrdDBgBT8ADXYsEZ5mPQ4Nde90N3UYatHdYoDIFb7oHzMncI02tA== - dependencies: - "@webassemblyjs/ast" "1.8.5" - "@webassemblyjs/helper-buffer" "1.8.5" - "@webassemblyjs/helper-wasm-bytecode" "1.8.5" - "@webassemblyjs/wasm-gen" "1.8.5" - -"@webassemblyjs/ieee754@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.8.5.tgz#712329dbef240f36bf57bd2f7b8fb9bf4154421e" - integrity sha512-aaCvQYrvKbY/n6wKHb/ylAJr27GglahUO89CcGXMItrOBqRarUMxWLJgxm9PJNuKULwN5n1csT9bYoMeZOGF3g== - dependencies: - "@xtuc/ieee754" "^1.2.0" - -"@webassemblyjs/leb128@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.8.5.tgz#044edeb34ea679f3e04cd4fd9824d5e35767ae10" - integrity sha512-plYUuUwleLIziknvlP8VpTgO4kqNaH57Y3JnNa6DLpu/sGcP6hbVdfdX5aHAV716pQBKrfuU26BJK29qY37J7A== - dependencies: - "@xtuc/long" "4.2.2" - -"@webassemblyjs/utf8@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.8.5.tgz#a8bf3b5d8ffe986c7c1e373ccbdc2a0915f0cedc" - integrity sha512-U7zgftmQriw37tfD934UNInokz6yTmn29inT2cAetAsaU9YeVCveWEwhKL1Mg4yS7q//NGdzy79nlXh3bT8Kjw== - -"@webassemblyjs/wasm-edit@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.8.5.tgz#962da12aa5acc1c131c81c4232991c82ce56e01a" - integrity sha512-A41EMy8MWw5yvqj7MQzkDjU29K7UJq1VrX2vWLzfpRHt3ISftOXqrtojn7nlPsZ9Ijhp5NwuODuycSvfAO/26Q== - dependencies: - "@webassemblyjs/ast" "1.8.5" - "@webassemblyjs/helper-buffer" "1.8.5" - "@webassemblyjs/helper-wasm-bytecode" "1.8.5" - "@webassemblyjs/helper-wasm-section" "1.8.5" - "@webassemblyjs/wasm-gen" "1.8.5" - "@webassemblyjs/wasm-opt" "1.8.5" - "@webassemblyjs/wasm-parser" "1.8.5" - "@webassemblyjs/wast-printer" "1.8.5" - -"@webassemblyjs/wasm-gen@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.8.5.tgz#54840766c2c1002eb64ed1abe720aded714f98bc" - integrity sha512-BCZBT0LURC0CXDzj5FXSc2FPTsxwp3nWcqXQdOZE4U7h7i8FqtFK5Egia6f9raQLpEKT1VL7zr4r3+QX6zArWg== - dependencies: - "@webassemblyjs/ast" "1.8.5" - "@webassemblyjs/helper-wasm-bytecode" "1.8.5" - "@webassemblyjs/ieee754" "1.8.5" - "@webassemblyjs/leb128" "1.8.5" - "@webassemblyjs/utf8" "1.8.5" - -"@webassemblyjs/wasm-opt@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.8.5.tgz#b24d9f6ba50394af1349f510afa8ffcb8a63d264" - integrity sha512-HKo2mO/Uh9A6ojzu7cjslGaHaUU14LdLbGEKqTR7PBKwT6LdPtLLh9fPY33rmr5wcOMrsWDbbdCHq4hQUdd37Q== - dependencies: - "@webassemblyjs/ast" "1.8.5" - "@webassemblyjs/helper-buffer" "1.8.5" - "@webassemblyjs/wasm-gen" "1.8.5" - "@webassemblyjs/wasm-parser" "1.8.5" - -"@webassemblyjs/wasm-parser@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.8.5.tgz#21576f0ec88b91427357b8536383668ef7c66b8d" - integrity sha512-pi0SYE9T6tfcMkthwcgCpL0cM9nRYr6/6fjgDtL6q/ZqKHdMWvxitRi5JcZ7RI4SNJJYnYNaWy5UUrHQy998lw== - dependencies: - "@webassemblyjs/ast" "1.8.5" - "@webassemblyjs/helper-api-error" "1.8.5" - "@webassemblyjs/helper-wasm-bytecode" "1.8.5" - "@webassemblyjs/ieee754" "1.8.5" - "@webassemblyjs/leb128" "1.8.5" - "@webassemblyjs/utf8" "1.8.5" - -"@webassemblyjs/wast-parser@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.8.5.tgz#e10eecd542d0e7bd394f6827c49f3df6d4eefb8c" - integrity sha512-daXC1FyKWHF1i11obK086QRlsMsY4+tIOKgBqI1lxAnkp9xe9YMcgOxm9kLe+ttjs5aWV2KKE1TWJCN57/Btsg== - dependencies: - "@webassemblyjs/ast" "1.8.5" - "@webassemblyjs/floating-point-hex-parser" "1.8.5" - "@webassemblyjs/helper-api-error" "1.8.5" - "@webassemblyjs/helper-code-frame" "1.8.5" - "@webassemblyjs/helper-fsm" "1.8.5" - "@xtuc/long" "4.2.2" - -"@webassemblyjs/wast-printer@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.8.5.tgz#114bbc481fd10ca0e23b3560fa812748b0bae5bc" - integrity sha512-w0U0pD4EhlnvRyeJzBqaVSJAo9w/ce7/WPogeXLzGkO6hzhr4GnQIZ4W4uUt5b9ooAaXPtnXlj0gzsXEOUNYMg== - dependencies: - "@webassemblyjs/ast" "1.8.5" - "@webassemblyjs/wast-parser" "1.8.5" - "@xtuc/long" "4.2.2" - -"@xtuc/ieee754@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" - integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== - -"@xtuc/long@4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" - integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== + semver "^7.3.5" + tsutils "^3.21.0" -abab@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.3.tgz#623e2075e02eb2d3f2475e49f99c91846467907a" - integrity sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg== - -accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: - version "1.3.7" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" - integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== +"@typescript-eslint/visitor-keys@4.31.0": + version "4.31.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.31.0.tgz#4e87b7761cb4e0e627dc2047021aa693fc76ea2b" + integrity sha512-HUcRp2a9I+P21+O21yu3ezv3GEPGjyGiXoEUQwZXjR8UxRApGeLyWH4ZIIUSalE28aG4YsV6GjtaAVB3QKOu0w== dependencies: - mime-types "~2.1.24" - negotiator "0.6.2" - -acorn-globals@^4.1.0, acorn-globals@^4.3.0: - version "4.3.4" - resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.3.4.tgz#9fa1926addc11c97308c4e66d7add0d40c3272e7" - integrity sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A== - dependencies: - acorn "^6.0.1" - acorn-walk "^6.0.1" - -acorn-jsx@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.2.0.tgz#4c66069173d6fdd68ed85239fc256226182b2ebe" - integrity sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ== - -acorn-walk@^6.0.1: - version "6.2.0" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-6.2.0.tgz#123cb8f3b84c2171f1f7fb252615b1c78a6b1a8c" - integrity sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA== + "@typescript-eslint/types" "4.31.0" + eslint-visitor-keys "^2.0.0" -acorn@^5.5.3: - version "5.7.4" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e" - integrity sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg== - -acorn@^6.0.1, acorn@^6.0.4, acorn@^6.2.1: - version "6.4.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474" - integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA== +abortcontroller-polyfill@^1.7.3: + version "1.7.3" + resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.3.tgz#1b5b487bd6436b5b764fd52a612509702c3144b5" + integrity sha512-zetDJxd89y3X99Kvo4qFx8GKlt6GsvN3UcRZHwU6iFA/0KiOmhkTVhe8oRoTBiTVPZu09x3vCra47+w8Yz1+2Q== -acorn@^7.1.1: - version "7.3.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.3.1.tgz#85010754db53c3fbaf3b9ea3e083aa5c5d147ffd" - integrity sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA== +acorn-jsx@^5.3.1: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -address@1.1.2, address@^1.0.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/address/-/address-1.1.2.tgz#bf1116c9c758c51b7a933d296b72c221ed9428b6" - integrity sha512-aT6camzM4xEA54YVJYSqxz1kv4IHnQZRtThJJHhUMRExaU5spC7jX5ugSwTaTgJliIgs4VhZOk7htClvQ/LmRA== - -adjust-sourcemap-loader@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/adjust-sourcemap-loader/-/adjust-sourcemap-loader-2.0.0.tgz#6471143af75ec02334b219f54bc7970c52fb29a4" - integrity sha512-4hFsTsn58+YjrU9qKzML2JSSDqKvN8mUGQ0nNIrfPi8hmIONT4L3uUaT6MKdMsZ9AjsU6D2xDkZxCkbQPxChrA== - dependencies: - assert "1.4.1" - camelcase "5.0.0" - loader-utils "1.2.3" - object-path "0.11.4" - regex-parser "2.2.10" - -after@0.8.2: - version "0.8.2" - resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" - integrity sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8= +acorn@^7.4.0: + version "7.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" + integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== aggregate-error@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.0.1.tgz#db2fe7246e536f40d9b5442a39e117d7dd6a24e0" - integrity sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA== + version "3.1.0" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== dependencies: clean-stack "^2.0.0" indent-string "^4.0.0" -ajv-errors@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d" - integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ== - -ajv-keywords@^3.1.0, ajv-keywords@^3.4.1: - version "3.5.0" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.0.tgz#5c894537098785926d71e696114a53ce768ed773" - integrity sha512-eyoaac3btgU8eJlvh01En8OCKzRqlLe2G5jDsCr3RiE2uLGMEEB1aaGwVVpwR8M95956tGH6R+9edC++OvzaVw== - -ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.5.5: - version "6.12.2" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.2.tgz#c629c5eced17baf314437918d2da88c99d5958cd" - integrity sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ== +ajv@^6.10.0, ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== dependencies: fast-deep-equal "^3.1.1" fast-json-stable-stringify "^2.0.0" json-schema-traverse "^0.4.1" uri-js "^4.2.2" -alphanum-sort@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" - integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM= - -ansi-colors@^3.0.0, ansi-colors@^3.2.1: - version "3.2.4" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf" - integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA== - -ansi-escapes@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" - integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== - -ansi-escapes@^4.2.1, ansi-escapes@^4.3.0: - version "4.3.1" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61" - integrity sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA== +ajv@^8.0.1: + version "8.6.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.6.2.tgz#2fb45e0e5fcbc0813326c1c3da535d1881bb0571" + integrity sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w== dependencies: - type-fest "^0.11.0" - -ansi-html@0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e" - integrity sha1-gTWEAhliqenm/QOflA0S9WynhZ4= + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" -ansi-regex@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= +anser@1.4.9: + version "1.4.9" + resolved "https://registry.yarnpkg.com/anser/-/anser-1.4.9.tgz#1f85423a5dcf8da4631a341665ff675b96845760" + integrity sha512-AI+BjTeGt2+WFk4eWcqbQ7snZpDBt8SaLlj0RT2h5xfdWaiy51OjYvqwMrNzJLGy8iOAL6nKDITWO+rd4MkYEA== -ansi-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" - integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= +ansi-colors@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" + integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== -ansi-regex@^4.0.0, ansi-regex@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" - integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== +ansi-escapes@^4.3.0: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" ansi-regex@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== -ansi-styles@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" - integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= - -ansi-styles@^3.2.0, ansi-styles@^3.2.1: +ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== @@ -2034,38 +638,24 @@ ansi-styles@^3.2.0, ansi-styles@^3.2.1: color-convert "^1.9.0" ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" - integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== dependencies: - "@types/color-name" "^1.1.1" color-convert "^2.0.1" -anymatch@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" - integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== - dependencies: - micromatch "^3.1.4" - normalize-path "^2.1.1" - -anymatch@~3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" - integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== +anymatch@~3.1.1, anymatch@~3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" + integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== dependencies: normalize-path "^3.0.0" picomatch "^2.0.4" -approximate-number@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/approximate-number/-/approximate-number-2.0.0.tgz#43c7fbfbbb0070a412131d65581f868b24d1eb29" - integrity sha1-Q8f7+7sAcKQSEx1lWB+GiyTR6yk= - -aproba@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" - integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== +approximate-number@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/approximate-number/-/approximate-number-2.1.0.tgz#8dbb27957f4f230e386b57258f8f0c75f32313c8" + integrity sha512-EioK6nto/hEnwaJ7d/TG1WOZ9o0zTyIFVP4Lk7zzR/3I4O7ivkBNo7EvLC2Xh2j2HD/cb9sUqXHdexfGXCXYDA== argparse@^1.0.7: version "1.0.10" @@ -2074,125 +664,68 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" -aria-query@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-3.0.0.tgz#65b3fcc1ca1155a8c9ae64d6eee297f15d5133cc" - integrity sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w= +aria-query@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b" + integrity sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA== dependencies: - ast-types-flow "0.0.7" - commander "^2.11.0" + "@babel/runtime" "^7.10.2" + "@babel/runtime-corejs3" "^7.10.2" -arity-n@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/arity-n/-/arity-n-1.0.4.tgz#d9e76b11733e08569c0847ae7b39b2860b30b745" - integrity sha1-2edrEXM+CFacCEeuezmyhgswt0U= - -arr-diff@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" - integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= - -arr-flatten@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" - integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== - -arr-union@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" - integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= - -array-equal@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93" - integrity sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM= - -array-flatten@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= - -array-flatten@^2.1.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099" - integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== - -array-includes@^3.0.3, array-includes@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.1.tgz#cdd67e6852bdf9c1215460786732255ed2459348" - integrity sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ== +array-includes@^3.1.1, array-includes@^3.1.2, array-includes@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.3.tgz#c7f619b382ad2afaf5326cddfdc0afc61af7690a" + integrity sha512-gcem1KlBU7c9rB+Rq8/3PPKsK2kjqeEBa3bD5kkQo4nYlOHQCJqIJFqBXDEfwaRuYTT4E+FxA9xez7Gf/e3Q7A== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.17.0" + es-abstract "^1.18.0-next.2" + get-intrinsic "^1.1.1" is-string "^1.0.5" -array-union@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" - integrity sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk= - dependencies: - array-uniq "^1.0.1" - -array-uniq@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" - integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY= - -array-unique@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" - integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== -array.prototype.flat@^1.2.1: - version "1.2.3" - resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz#0de82b426b0318dbfdb940089e38b043d37f6c7b" - integrity sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ== +array.prototype.flat@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.4.tgz#6ef638b43312bd401b4c6199fdec7e2dc9e9a123" + integrity sha512-4470Xi3GAPAjZqFcljX2xzckv1qeKPizoNkiS0+O4IoPR2ZNpcjE0pkhdihlDouK+x6QOast26B4Q/O9DJnwSg== dependencies: + call-bind "^1.0.0" define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" + es-abstract "^1.18.0-next.1" -arraybuffer.slice@~0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz#3bbc4275dd584cc1b10809b89d4e8b63a69e7675" - integrity sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog== - -arrify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" - integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= - -asap@~2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" - integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= +array.prototype.flatmap@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.2.4.tgz#94cfd47cc1556ec0747d97f7c7738c58122004c9" + integrity sha512-r9Z0zYoxqHz60vvQbWEdXIEtCwHF0yxaWfno9qzXeNHvfyl3BZqygmGzb84dsubyaXLH4husF+NFgMSdpZhk2Q== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + es-abstract "^1.18.0-next.1" + function-bind "^1.1.1" -asn1.js@^4.0.0: - version "4.10.1" - resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0" - integrity sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw== +asn1.js@^5.2.0: + version "5.4.1" + resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07" + integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA== dependencies: bn.js "^4.0.0" inherits "^2.0.1" minimalistic-assert "^1.0.0" + safer-buffer "^2.1.0" -asn1@~0.2.3: - version "0.2.4" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" - integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== - dependencies: - safer-buffer "~2.1.0" - -assert-plus@1.0.0, assert-plus@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" - integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= - -assert@1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/assert/-/assert-1.4.1.tgz#99912d591836b5a6f5b345c0f07eefc08fc65d91" - integrity sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE= +assert@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/assert/-/assert-2.0.0.tgz#95fc1c616d48713510680f2eaf2d10dd22e02d32" + integrity sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A== dependencies: - util "0.10.3" + es6-object-assign "^1.1.0" + is-nan "^1.2.1" + object-is "^1.0.1" + util "^0.12.0" assert@^1.1.1: version "1.5.0" @@ -2202,367 +735,77 @@ assert@^1.1.1: object-assign "^4.1.1" util "0.10.3" -assign-symbols@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" - integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= - -ast-types-flow@0.0.7, ast-types-flow@^0.0.7: +ast-types-flow@^0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad" integrity sha1-9wtzXGvKGlycItmCw+Oef+ujva0= -astral-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" - integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== +ast-types@0.13.2: + version "0.13.2" + resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.13.2.tgz#df39b677a911a83f3a049644fb74fdded23cea48" + integrity sha512-uWMHxJxtfj/1oZClOxDEV1sQ1HCDkA4MG8Gr69KKeBjEVH0R84WlejZ0y2DcwyBlpAEMltmVYkVgqfLFb2oyiA== astral-regex@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== -async-each@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" - integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== - -async-limiter@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" - integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== - -async@^2.6.2: - version "2.6.3" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" - integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== +attr-accept@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/attr-accept/-/attr-accept-1.1.3.tgz#48230c79f93790ef2775fcec4f0db0f5db41ca52" + integrity sha512-iT40nudw8zmCweivz6j58g+RT33I4KbaIvRUhjNmDwO2WmsQUxFEZZYZ5w3vXe5x5MX9D7mfvA/XaLOZYFR9EQ== dependencies: - lodash "^4.17.14" - -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= + core-js "^2.5.0" -atob@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" - integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== - -autoprefixer@^9.6.1: - version "9.8.4" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.4.tgz#736f1012673a70fa3464671d78d41abd54512863" - integrity sha512-84aYfXlpUe45lvmS+HoAWKCkirI/sw4JK0/bTeeqgHYco3dcsOn0NqdejISjptsYwNji/21dnkDri9PsYKk89A== - dependencies: - browserslist "^4.12.0" - caniuse-lite "^1.0.30001087" - colorette "^1.2.0" - normalize-range "^0.1.2" - num2fraction "^1.2.2" - postcss "^7.0.32" - postcss-value-parser "^4.1.0" - -aws-sign2@~0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" - integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= +available-typed-arrays@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" + integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== -aws4@^1.8.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.0.tgz#a17b3a8ea811060e74d47d306122400ad4497ae2" - integrity sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA== +axe-core@^4.0.2: + version "4.3.3" + resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.3.3.tgz#b55cd8e8ddf659fe89b064680e1c6a4dceab0325" + integrity sha512-/lqqLAmuIPi79WYfRpy2i8z+x+vxU3zX2uAm0gs1q52qTuKwolOj1P8XbufpXcsydrpKx2yGn2wzAnxCMV86QA== -axobject-query@^2.0.2: +axobject-query@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be" integrity sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA== -babel-code-frame@^6.22.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" - integrity sha1-Y/1D99weO7fONZR9uP42mj9Yx0s= - dependencies: - chalk "^1.1.3" - esutils "^2.0.2" - js-tokens "^3.0.2" - -babel-eslint@10.1.0: - version "10.1.0" - resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.1.0.tgz#6968e568a910b78fb3779cdd8b6ac2f479943232" - integrity sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg== - dependencies: - "@babel/code-frame" "^7.0.0" - "@babel/parser" "^7.7.0" - "@babel/traverse" "^7.7.0" - "@babel/types" "^7.7.0" - eslint-visitor-keys "^1.0.0" - resolve "^1.12.0" - -babel-extract-comments@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/babel-extract-comments/-/babel-extract-comments-1.0.0.tgz#0a2aedf81417ed391b85e18b4614e693a0351a21" - integrity sha512-qWWzi4TlddohA91bFwgt6zO/J0X+io7Qp184Fw0m2JYRSTZnJbFR8+07KmzudHCZgOiKRCrjhylwv9Xd8gfhVQ== - dependencies: - babylon "^6.18.0" - -babel-jest@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-24.9.0.tgz#3fc327cb8467b89d14d7bc70e315104a783ccd54" - integrity sha512-ntuddfyiN+EhMw58PTNL1ph4C9rECiQXjI4nMMBKBaNjXvqLdkXpPRcMSr4iyBrJg/+wz9brFUD6RhOAT6r4Iw== - dependencies: - "@jest/transform" "^24.9.0" - "@jest/types" "^24.9.0" - "@types/babel__core" "^7.1.0" - babel-plugin-istanbul "^5.1.0" - babel-preset-jest "^24.9.0" - chalk "^2.4.2" - slash "^2.0.0" - -babel-loader@8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.1.0.tgz#c611d5112bd5209abe8b9fa84c3e4da25275f1c3" - integrity sha512-7q7nC1tYOrqvUrN3LQK4GwSk/TQorZSOlO9C+RZDZpODgyN4ZlCqE5q9cDsyWOliN+aU9B4JX01xK9eJXowJLw== - dependencies: - find-cache-dir "^2.1.0" - loader-utils "^1.4.0" - mkdirp "^0.5.3" - pify "^4.0.1" - schema-utils "^2.6.5" - -babel-plugin-dynamic-import-node@^2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3" - integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ== - dependencies: - object.assign "^4.1.0" - -babel-plugin-istanbul@^5.1.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-5.2.0.tgz#df4ade83d897a92df069c4d9a25cf2671293c854" - integrity sha512-5LphC0USA8t4i1zCtjbbNb6jJj/9+X6P37Qfirc/70EQ34xKlMW+a1RHGwxGI+SwWpNwZ27HqvzAobeqaXwiZw== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - find-up "^3.0.0" - istanbul-lib-instrument "^3.3.0" - test-exclude "^5.2.3" - -babel-plugin-jest-hoist@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.9.0.tgz#4f837091eb407e01447c8843cbec546d0002d756" - integrity sha512-2EMA2P8Vp7lG0RAzr4HXqtYwacfMErOuv1U3wrvxHX6rD1sV6xS3WXG3r8TRQ2r6w8OhvSdWt+z41hQNwNm3Xw== - dependencies: - "@types/babel__traverse" "^7.0.6" - -babel-plugin-macros@2.8.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz#0f958a7cc6556b1e65344465d99111a1e5e10138" - integrity sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg== - dependencies: - "@babel/runtime" "^7.7.2" - cosmiconfig "^6.0.0" - resolve "^1.12.0" - -babel-plugin-named-asset-import@^0.3.6: - version "0.3.6" - resolved "https://registry.yarnpkg.com/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.6.tgz#c9750a1b38d85112c9e166bf3ef7c5dbc605f4be" - integrity sha512-1aGDUfL1qOOIoqk9QKGIo2lANk+C7ko/fqH0uIyC71x3PEGz0uVP8ISgfEsFuG+FKmjHTvFK/nNM8dowpmUxLA== - -babel-plugin-syntax-object-rest-spread@^6.8.0: - version "6.13.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5" - integrity sha1-/WU28rzhODb/o6VFjEkDpZe7O/U= - -babel-plugin-transform-object-rest-spread@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz#0f36692d50fef6b7e2d4b3ac1478137a963b7b06" - integrity sha1-DzZpLVD+9rfi1LOsFHgTepY7ewY= - dependencies: - babel-plugin-syntax-object-rest-spread "^6.8.0" - babel-runtime "^6.26.0" - -babel-plugin-transform-react-remove-prop-types@0.4.24: - version "0.4.24" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz#f2edaf9b4c6a5fbe5c1d678bfb531078c1555f3a" - integrity sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA== - -babel-preset-jest@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-24.9.0.tgz#192b521e2217fb1d1f67cf73f70c336650ad3cdc" - integrity sha512-izTUuhE4TMfTRPF92fFwD2QfdXaZW08qvWTFCI51V8rW5x00UuPgc3ajRoWofXOuxjfcOM5zzSYsQS3H8KGCAg== - dependencies: - "@babel/plugin-syntax-object-rest-spread" "^7.0.0" - babel-plugin-jest-hoist "^24.9.0" - -babel-preset-react-app@^9.1.2: - version "9.1.2" - resolved "https://registry.yarnpkg.com/babel-preset-react-app/-/babel-preset-react-app-9.1.2.tgz#54775d976588a8a6d1a99201a702befecaf48030" - integrity sha512-k58RtQOKH21NyKtzptoAvtAODuAJJs3ZhqBMl456/GnXEQ/0La92pNmwgWoMn5pBTrsvk3YYXdY7zpY4e3UIxA== - dependencies: - "@babel/core" "7.9.0" - "@babel/plugin-proposal-class-properties" "7.8.3" - "@babel/plugin-proposal-decorators" "7.8.3" - "@babel/plugin-proposal-nullish-coalescing-operator" "7.8.3" - "@babel/plugin-proposal-numeric-separator" "7.8.3" - "@babel/plugin-proposal-optional-chaining" "7.9.0" - "@babel/plugin-transform-flow-strip-types" "7.9.0" - "@babel/plugin-transform-react-display-name" "7.8.3" - "@babel/plugin-transform-runtime" "7.9.0" - "@babel/preset-env" "7.9.0" - "@babel/preset-react" "7.9.1" - "@babel/preset-typescript" "7.9.0" - "@babel/runtime" "7.9.0" - babel-plugin-macros "2.8.0" - babel-plugin-transform-react-remove-prop-types "0.4.24" - -babel-runtime@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" - integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4= - dependencies: - core-js "^2.4.0" - regenerator-runtime "^0.11.0" - -babylon@^6.18.0: - version "6.18.0" - resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" - integrity sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ== - -backo2@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" - integrity sha1-MasayLEpNjRj41s+u2n038+6eUc= - -balanced-match@^0.4.2: - version "0.4.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838" - integrity sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg= - balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= - -base64-arraybuffer@0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8" - integrity sha1-c5JncZI7Whl0etZmqlzUv5xunOg= + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== base64-js@^1.0.2: - version "1.3.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" - integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== - -base@^0.11.1: - version "0.11.2" - resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" - integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== - dependencies: - cache-base "^1.0.1" - class-utils "^0.3.5" - component-emitter "^1.2.1" - define-property "^1.0.0" - isobject "^3.0.1" - mixin-deep "^1.2.0" - pascalcase "^0.1.1" - -batch@0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" - integrity sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY= - -bcrypt-pbkdf@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" - integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= - dependencies: - tweetnacl "^0.14.3" + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== -better-assert@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/better-assert/-/better-assert-1.0.2.tgz#40866b9e1b9e0b55b481894311e68faffaebc522" - integrity sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI= - dependencies: - callsite "1.0.0" +big-integer@^1.6.16: + version "1.6.48" + resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.48.tgz#8fd88bd1632cba4a1c8c3e3d7159f08bb95b4b9e" + integrity sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w== big.js@^5.2.2: version "5.2.2" resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== -binary-extensions@^1.0.0: - version "1.13.1" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" - integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== - binary-extensions@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.1.0.tgz#30fa40c9e7fe07dbc895678cd287024dea241dd9" - integrity sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ== - -bindings@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" - integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== - dependencies: - file-uri-to-path "1.0.0" - -blob@0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.5.tgz#d680eeef25f8cd91ad533f5b01eed48e64caf683" - integrity sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig== - -bluebird@^3.5.5: - version "3.7.2" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" - integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== - -bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.4.0: - version "4.11.9" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.9.tgz#26d556829458f9d1e81fc48952493d0ba3507828" - integrity sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw== - -bn.js@^5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.1.2.tgz#c9686902d3c9a27729f43ab10f9d79c2004da7b0" - integrity sha512-40rZaf3bUNKTVYu9sIeeEGOg7g14Yvnj9kH7b50EiwX0Q7A6umbvfI5tvHaOERH0XigqKkfLkFQxzb4e6CIXnA== - -body-parser@1.19.0: - version "1.19.0" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" - integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== - dependencies: - bytes "3.1.0" - content-type "~1.0.4" - debug "2.6.9" - depd "~1.1.2" - http-errors "1.7.2" - iconv-lite "0.4.24" - on-finished "~2.3.0" - qs "6.7.0" - raw-body "2.4.0" - type-is "~1.6.17" - -bonjour@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/bonjour/-/bonjour-3.5.0.tgz#8e890a183d8ee9a2393b3844c691a42bcf7bc9f5" - integrity sha1-jokKGD2O6aI5OzhExpGkK897yfU= - dependencies: - array-flatten "^2.1.0" - deep-equal "^1.0.1" - dns-equal "^1.0.0" - dns-txt "^2.0.2" - multicast-dns "^6.0.1" - multicast-dns-service-types "^1.1.0" + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== -boolbase@^1.0.0, boolbase@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" - integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= +bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9: + version "4.12.0" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" + integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== -bootstrap@4.5.3: - version "4.5.3" - resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.5.3.tgz#c6a72b355aaf323920be800246a6e4ef30997fe6" - integrity sha512-o9ppKQioXGqhw8Z7mah6KdTYpNQY//tipnkxppWhPbiSWdD+1raYsnhwEZjkTHYbGee4cVQ0Rx65EhOY/HNLcQ== +bn.js@^5.0.0, bn.js@^5.1.1: + version "5.2.0" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.0.tgz#358860674396c6997771a9d051fcc1b57d4ae002" + integrity sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw== brace-expansion@^1.1.7: version "1.1.11" @@ -2572,22 +815,6 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^2.3.1, braces@^2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" - integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== - dependencies: - arr-flatten "^1.1.0" - array-unique "^0.3.2" - extend-shallow "^2.0.1" - fill-range "^4.0.0" - isobject "^3.0.1" - repeat-element "^1.1.2" - snapdragon "^0.8.1" - snapdragon-node "^2.0.1" - split-string "^3.0.2" - to-regex "^3.0.1" - braces@^3.0.1, braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" @@ -2595,22 +822,31 @@ braces@^3.0.1, braces@~3.0.2: dependencies: fill-range "^7.0.1" -brorand@^1.0.1: +broadcast-channel@^3.4.1: + version "3.7.0" + resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-3.7.0.tgz#2dfa5c7b4289547ac3f6705f9c00af8723889937" + integrity sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg== + dependencies: + "@babel/runtime" "^7.7.2" + detect-node "^2.1.0" + js-sha3 "0.8.0" + microseconds "0.2.0" + nano-time "1.0.0" + oblivious-set "1.0.0" + rimraf "3.0.2" + unload "2.2.0" + +brorand@^1.0.1, brorand@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= -browser-process-hrtime@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" - integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== - -browser-resolve@^1.11.3: - version "1.11.3" - resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.3.tgz#9b7cbb3d0f510e4cb86bdbd796124d28b5890af6" - integrity sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ== +browser-tabs-lock@^1.2.14: + version "1.2.15" + resolved "https://registry.yarnpkg.com/browser-tabs-lock/-/browser-tabs-lock-1.2.15.tgz#d5012e652e2a0cb4eba471b0a2300c2fa5d92788" + integrity sha512-J8K9vdivK0Di+b8SBdE7EZxDr88TnATing7XoLw6+nFkXMQ6sVBh92K3NQvZlZU91AIkFRi0w3sztk5Z+vsswA== dependencies: - resolve "1.1.7" + lodash ">=4.17.21" browserify-aes@^1.0.0, browserify-aes@^1.0.4: version "1.2.0" @@ -2644,77 +880,59 @@ browserify-des@^1.0.0: safe-buffer "^5.1.2" browserify-rsa@^4.0.0, browserify-rsa@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524" - integrity sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ= + version "4.1.0" + resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.1.0.tgz#b2fd06b5b75ae297f7ce2dc651f918f5be158c8d" + integrity sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog== dependencies: - bn.js "^4.1.0" + bn.js "^5.0.0" randombytes "^2.0.1" browserify-sign@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.0.tgz#545d0b1b07e6b2c99211082bf1b12cce7a0b0e11" - integrity sha512-hEZC1KEeYuoHRqhGhTy6gWrpJA3ZDjFWv0DE61643ZnOXAKJb3u7yWcrU0mMc9SwAqK1n7myPGndkp0dFG7NFA== + version "4.2.1" + resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.1.tgz#eaf4add46dd54be3bb3b36c0cf15abbeba7956c3" + integrity sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg== dependencies: bn.js "^5.1.1" browserify-rsa "^4.0.1" create-hash "^1.2.0" create-hmac "^1.1.7" - elliptic "^6.5.2" + elliptic "^6.5.3" inherits "^2.0.4" parse-asn1 "^5.1.5" readable-stream "^3.6.0" safe-buffer "^5.2.0" -browserify-zlib@^0.2.0: +browserify-zlib@0.2.0, browserify-zlib@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" integrity sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA== dependencies: pako "~1.0.5" -browserslist@4.10.0: - version "4.10.0" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.10.0.tgz#f179737913eaf0d2b98e4926ac1ca6a15cbcc6a9" - integrity sha512-TpfK0TDgv71dzuTsEAlQiHeWQ/tiPqgNZVdv046fvNtBZrjbv2O3TsWCDU0AWGJJKCF/KsjNdLzR9hXOsh/CfA== - dependencies: - caniuse-lite "^1.0.30001035" - electron-to-chromium "^1.3.378" - node-releases "^1.1.52" - pkg-up "^3.1.0" - -browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.6.2, browserslist@^4.6.4, browserslist@^4.8.5, browserslist@^4.9.1: - version "4.12.2" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.12.2.tgz#76653d7e4c57caa8a1a28513e2f4e197dc11a711" - integrity sha512-MfZaeYqR8StRZdstAK9hCKDd2StvePCYp5rHzQCPicUjfFliDgmuaBNPHYUTpAywBN8+Wc/d7NYVFkO0aqaBUw== +browserslist@4.16.6: + version "4.16.6" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.6.tgz#d7901277a5a88e554ed305b183ec9b0c08f66fa2" + integrity sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ== dependencies: - caniuse-lite "^1.0.30001088" - electron-to-chromium "^1.3.483" - escalade "^3.0.1" - node-releases "^1.1.58" - -bser@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" - integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== - dependencies: - node-int64 "^0.4.0" - -buffer-from@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" - integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== - -buffer-indexof@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c" - integrity sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g== + caniuse-lite "^1.0.30001219" + colorette "^1.2.2" + electron-to-chromium "^1.3.723" + escalade "^3.1.1" + node-releases "^1.1.71" buffer-xor@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk= +buffer@5.6.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786" + integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw== + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + buffer@^4.3.0: version "4.9.2" resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" @@ -2729,161 +947,30 @@ builtin-status-codes@^3.0.0: resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= -bytes@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" - integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= - bytes@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== -cacache@^12.0.2: - version "12.0.4" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.4.tgz#668bcbd105aeb5f1d92fe25570ec9525c8faa40c" - integrity sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ== - dependencies: - bluebird "^3.5.5" - chownr "^1.1.1" - figgy-pudding "^3.5.1" - glob "^7.1.4" - graceful-fs "^4.1.15" - infer-owner "^1.0.3" - lru-cache "^5.1.1" - mississippi "^3.0.0" - mkdirp "^0.5.1" - move-concurrently "^1.0.1" - promise-inflight "^1.0.1" - rimraf "^2.6.3" - ssri "^6.0.1" - unique-filename "^1.1.1" - y18n "^4.0.0" - -cacache@^13.0.1: - version "13.0.1" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-13.0.1.tgz#a8000c21697089082f85287a1aec6e382024a71c" - integrity sha512-5ZvAxd05HDDU+y9BVvcqYu2LLXmPnQ0hW62h32g4xBTgL/MppR4/04NHfj/ycM2y6lmTnbw6HVi+1eN0Psba6w== - dependencies: - chownr "^1.1.2" - figgy-pudding "^3.5.1" - fs-minipass "^2.0.0" - glob "^7.1.4" - graceful-fs "^4.2.2" - infer-owner "^1.0.4" - lru-cache "^5.1.1" - minipass "^3.0.0" - minipass-collect "^1.0.2" - minipass-flush "^1.0.5" - minipass-pipeline "^1.2.2" - mkdirp "^0.5.1" - move-concurrently "^1.0.1" - p-map "^3.0.0" - promise-inflight "^1.0.1" - rimraf "^2.7.1" - ssri "^7.0.0" - unique-filename "^1.1.1" - -cache-base@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" - integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== - dependencies: - collection-visit "^1.0.0" - component-emitter "^1.2.1" - get-value "^2.0.6" - has-value "^1.0.0" - isobject "^3.0.1" - set-value "^2.0.0" - to-object-path "^0.3.0" - union-value "^1.0.0" - unset-value "^1.0.0" - -call-me-maybe@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b" - integrity sha1-JtII6onje1y95gJQoV8DHBak1ms= - -caller-callsite@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134" - integrity sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ= - dependencies: - callsites "^2.0.0" - -caller-path@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4" - integrity sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ= +call-bind@^1.0.0, call-bind@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== dependencies: - caller-callsite "^2.0.0" - -callsite@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20" - integrity sha1-KAOY5dZkvXQDi28JBRU+borxvCA= - -callsites@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" - integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA= + function-bind "^1.1.1" + get-intrinsic "^1.0.2" callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== -camel-case@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.1.tgz#1fc41c854f00e2f7d0139dfeba1542d6896fe547" - integrity sha512-7fa2WcG4fYFkclIvEmxBbTvmibwF2/agfEBc6q3lOpVu0A13ltLsA+Hr/8Hp6kp5f+G7hKi6t8lys6XxP+1K6Q== - dependencies: - pascal-case "^3.1.1" - tslib "^1.10.0" - -camelcase@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.0.0.tgz#03295527d58bd3cd4aa75363f35b2e8d97be2f42" - integrity sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA== - -camelcase@5.3.1, camelcase@^5.0.0, camelcase@^5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" - integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== +caniuse-lite@^1.0.30001202, caniuse-lite@^1.0.30001219, caniuse-lite@^1.0.30001228: + version "1.0.30001255" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001255.tgz#f3b09b59ab52e39e751a569523618f47c4298ca0" + integrity sha512-F+A3N9jTZL882f/fg/WWVnKSu6IOo3ueLz4zwaOPbPYHNmM/ZaDUyzyJwS1mZhX7Ex5jqTyW599Gdelh5PDYLQ== -caniuse-api@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" - integrity sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw== - dependencies: - browserslist "^4.0.0" - caniuse-lite "^1.0.0" - lodash.memoize "^4.1.2" - lodash.uniq "^4.5.0" - -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001035, caniuse-lite@^1.0.30001087, caniuse-lite@^1.0.30001088: - version "1.0.30001090" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001090.tgz#ff7766332f60e80fea4903f30d360622e5551850" - integrity sha512-QzPRKDCyp7RhjczTPZaqK3CjPA5Ht2UnXhZhCI4f7QiB5JK6KEuZBxIzyWnB3wO4hgAj4GMRxAhuiacfw0Psjg== - -capture-exit@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4" - integrity sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g== - dependencies: - rsvp "^4.8.4" - -case-sensitive-paths-webpack-plugin@2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.3.0.tgz#23ac613cc9a856e4f88ff8bb73bbb5e989825cf7" - integrity sha512-/4YgnZS8y1UXXmC02xD5rRrBEu6T5ub+mQHLNRj0fzTRbgdBYhsNo2V5EqwgqrExjxsjtF/OpAKAMkKsxbD5XQ== - -caseless@~0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" - integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= - -chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.4.1, chalk@^2.4.2: +chalk@2.4.2, chalk@^2.0.0: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -2892,39 +979,23 @@ chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.4.1, chalk@^2.4. escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" - integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= - dependencies: - ansi-styles "^2.2.1" - escape-string-regexp "^1.0.2" - has-ansi "^2.0.0" - strip-ansi "^3.0.0" - supports-color "^2.0.0" - -chalk@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" - integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== +chalk@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.0.0.tgz#6e98081ed2d17faab615eb52ac66ec1fe6209e72" + integrity sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A== dependencies: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" - integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== +chalk@^4.0.0, chalk@^4.1.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== dependencies: ansi-styles "^4.1.0" supports-color "^7.1.0" -chardet@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" - integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== - -"chokidar@>=3.0.0 <4.0.0": +chokidar@3.5.1: version "3.5.1" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a" integrity sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw== @@ -2939,51 +1010,20 @@ chardet@^0.7.0: optionalDependencies: fsevents "~2.3.1" -chokidar@^2.1.8: - version "2.1.8" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" - integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg== - dependencies: - anymatch "^2.0.0" - async-each "^1.0.1" - braces "^2.3.2" - glob-parent "^3.1.0" - inherits "^2.0.3" - is-binary-path "^1.0.0" - is-glob "^4.0.0" - normalize-path "^3.0.0" - path-is-absolute "^1.0.0" - readdirp "^2.2.1" - upath "^1.1.1" - optionalDependencies: - fsevents "^1.2.7" - -chokidar@^3.3.0, chokidar@^3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.0.tgz#b30611423ce376357c765b9b8f904b9fba3c0be8" - integrity sha512-aXAaho2VJtisB/1fg1+3nlLJqGOuewTzQpd/Tz0yTg2R0e4IGtshYvtjowyEumcBv2z+y4+kc75Mz7j5xJskcQ== +"chokidar@>=3.0.0 <4.0.0": + version "3.5.2" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75" + integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ== dependencies: - anymatch "~3.1.1" + anymatch "~3.1.2" braces "~3.0.2" - glob-parent "~5.1.0" + glob-parent "~5.1.2" is-binary-path "~2.1.0" is-glob "~4.0.1" normalize-path "~3.0.0" - readdirp "~3.4.0" + readdirp "~3.6.0" optionalDependencies: - fsevents "~2.1.2" - -chownr@^1.1.1, chownr@^1.1.2: - version "1.1.4" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" - integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== - -chrome-trace-event@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4" - integrity sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ== - dependencies: - tslib "^1.9.0" + fsevents "~2.3.2" ci-info@^2.0.0: version "2.0.0" @@ -2998,27 +1038,15 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: inherits "^2.0.1" safe-buffer "^5.0.1" -class-utils@^0.3.5: - version "0.3.6" - resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" - integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== - dependencies: - arr-union "^3.1.0" - define-property "^0.2.5" - isobject "^3.0.0" - static-extend "^0.1.1" - -classnames@^2.2.3, classnames@^2.2.5, classnames@~2.2.5: +classnames@2.2.6: version "2.2.6" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce" integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q== -clean-css@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.3.tgz#507b5de7d97b48ee53d84adb0160ff6216380f78" - integrity sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA== - dependencies: - source-map "~0.6.0" +classnames@^2.2.5, classnames@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e" + integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA== clean-stack@^2.0.0: version "2.2.0" @@ -3032,7 +1060,7 @@ cli-cursor@^3.1.0: dependencies: restore-cursor "^3.1.0" -cli-truncate@2.1.0, cli-truncate@^2.1.0: +cli-truncate@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7" integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg== @@ -3040,77 +1068,7 @@ cli-truncate@2.1.0, cli-truncate@^2.1.0: slice-ansi "^3.0.0" string-width "^4.2.0" -cli-width@^2.0.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48" - integrity sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw== - -cliui@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49" - integrity sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ== - dependencies: - string-width "^2.1.1" - strip-ansi "^4.0.0" - wrap-ansi "^2.0.0" - -cliui@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" - integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== - dependencies: - string-width "^3.1.0" - strip-ansi "^5.2.0" - wrap-ansi "^5.1.0" - -clone-deep@^0.2.4: - version "0.2.4" - resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-0.2.4.tgz#4e73dd09e9fb971cc38670c5dced9c1896481cc6" - integrity sha1-TnPdCen7lxzDhnDF3O2cGJZIHMY= - dependencies: - for-own "^0.1.3" - is-plain-object "^2.0.1" - kind-of "^3.0.2" - lazy-cache "^1.0.3" - shallow-clone "^0.1.2" - -clone-deep@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" - integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== - dependencies: - is-plain-object "^2.0.4" - kind-of "^6.0.2" - shallow-clone "^3.0.0" - -co@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" - integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= - -coa@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/coa/-/coa-2.0.2.tgz#43f6c21151b4ef2bf57187db0d73de229e3e7ec3" - integrity sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA== - dependencies: - "@types/q" "^1.5.1" - chalk "^2.4.1" - q "^1.1.2" - -code-point-at@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" - integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= - -collection-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" - integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= - dependencies: - map-visit "^1.0.0" - object-visit "^1.0.0" - -color-convert@^1.9.0, color-convert@^1.9.1: +color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== @@ -3129,63 +1087,20 @@ color-name@1.1.3: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= -color-name@^1.0.0, color-name@~1.1.4: +color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -color-string@^1.5.2: - version "1.5.3" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.3.tgz#c9bbc5f01b58b5492f3d6857459cb6590ce204cc" - integrity sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw== - dependencies: - color-name "^1.0.0" - simple-swizzle "^0.2.2" - -color@^3.0.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/color/-/color-3.1.2.tgz#68148e7f85d41ad7649c5fa8c8106f098d229e10" - integrity sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg== - dependencies: - color-convert "^1.9.1" - color-string "^1.5.2" - -colorette@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.0.tgz#45306add826d196e8c87236ac05d797f25982e63" - integrity sha512-soRSroY+OF/8OdA3PTQXwaDJeMc7TfknKKrxeSCencL2a4+Tx5zhxmmv7hdpCjhKBjehzp8+bwe/T68K0hpIjw== - -combined-stream@^1.0.6, combined-stream@~1.0.6: - version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - -combokeys@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/combokeys/-/combokeys-3.0.1.tgz#fc8ca5c3f5f2d2b03a458544cb88b14ab5f53f86" - integrity sha512-5nAfaLZ3oO3kA+/xdoL7t197UJTz2WWidyH3BBeU6hqHtvyFERICd0y3DQFrQkJFTKBrtUDck/xCLLoFpnjaCw== - -commander@^2.11.0, commander@^2.20.0: - version "2.20.3" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" - integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== - -commander@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" - integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== - -commander@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" - integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== +colorette@^1.2.2: + version "1.3.0" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.3.0.tgz#ff45d2f0edb244069d3b772adeb04fed38d0a0af" + integrity sha512-ecORCqbSFP7Wm8Y6lyqMJjexBQqXSF7SSeaTyGGphogUjBlFP9m9o08wy86HL2uB7fMTxtOUzLMk7ogKcxMg1w== -common-tags@^1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937" - integrity sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw== +commander@^6.2.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" + integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== commondir@^1.0.1: version "1.0.1" @@ -3198,56 +1113,9 @@ compare-versions@^3.6.0: integrity sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA== complex.js@^2.0.11: - version "2.0.11" - resolved "https://registry.yarnpkg.com/complex.js/-/complex.js-2.0.11.tgz#09a873fbf15ffd8c18c9c2201ccef425c32b8bf1" - integrity sha512-6IArJLApNtdg1P1dFtn3dnyzoZBEF0MwMnrfF1exSBRpZYoy4yieMkpZhQDC0uwctw48vii0CFVyHfpgZ/DfGw== - -component-bind@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/component-bind/-/component-bind-1.0.0.tgz#00c608ab7dcd93897c0009651b1d3a8e1e73bbd1" - integrity sha1-AMYIq33Nk4l8AAllGx06jh5zu9E= - -component-emitter@1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" - integrity sha1-E3kY1teCg/ffemt8WmPhQOaUJeY= - -component-emitter@^1.2.1, component-emitter@~1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" - integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== - -component-inherit@0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143" - integrity sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM= - -compose-function@3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/compose-function/-/compose-function-3.0.3.tgz#9ed675f13cc54501d30950a486ff6a7ba3ab185f" - integrity sha1-ntZ18TzFRQHTCVCkhv9qe6OrGF8= - dependencies: - arity-n "^1.0.4" - -compressible@~2.0.16: - version "2.0.18" - resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" - integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== - dependencies: - mime-db ">= 1.43.0 < 2" - -compression@^1.7.4: - version "1.7.4" - resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" - integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== - dependencies: - accepts "~1.3.5" - bytes "3.0.0" - compressible "~2.0.16" - debug "2.6.9" - on-headers "~1.0.2" - safe-buffer "5.1.2" - vary "~1.1.2" + version "2.0.15" + resolved "https://registry.yarnpkg.com/complex.js/-/complex.js-2.0.15.tgz#7add6848b4c1d12aa9262f7df925ebe7a51a7406" + integrity sha512-gDBvQU8IG139ZBQTSo2qvDFP+lANMGluM779csXOr6ny1NUtA3wkUnCFjlDNH/moAVfXtvClYt6G0zarFbtz5w== computed-styles@^1.1.2: version "1.1.2" @@ -3259,148 +1127,61 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -concat-stream@^1.5.0: - version "1.6.2" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" - integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== - dependencies: - buffer-from "^1.0.0" - inherits "^2.0.3" - readable-stream "^2.2.2" - typedarray "^0.0.6" - -confusing-browser-globals@^1.0.9: - version "1.0.9" - resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.9.tgz#72bc13b483c0276801681871d4898516f8f54fdd" - integrity sha512-KbS1Y0jMtyPgIxjO7ZzMAuUpAKMt1SzCL9fsrKsX6b0zJPTaT0SiSPmewwVZg9UAO83HVIlEhZF84LIjZ0lmAw== - -connect-history-api-fallback@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc" - integrity sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg== - console-browserify@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA== -constants-browserify@^1.0.0: +constants-browserify@1.0.0, constants-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U= -contains-path@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" - integrity sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo= - -content-disposition@0.5.3: - version "0.5.3" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" - integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== - dependencies: - safe-buffer "5.1.2" - -content-type@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" - integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== - -convert-source-map@1.7.0, convert-source-map@^1.4.0, convert-source-map@^1.7.0: +convert-source-map@1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA== dependencies: safe-buffer "~5.1.1" -convert-source-map@^0.3.3: - version "0.3.5" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-0.3.5.tgz#f1d802950af7dd2631a1febe0596550c86ab3190" - integrity sha1-8dgClQr33SYxof6+BZZVDIarMZA= +core-js-pure@^3.16.0: + version "3.17.2" + resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.17.2.tgz#ba6311b6aa1e2f2adeba4ac6ec51a9ff40bdc1af" + integrity sha512-2VV7DlIbooyTI7Bh+yzOOWL9tGwLnQKHno7qATE+fqZzDKYr6llVjVQOzpD/QLZFgXDPb8T71pJokHEZHEYJhQ== -cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= - -cookie@0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" - integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== +core-js@^2.5.0: + version "2.6.12" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" + integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== -copy-concurrently@^1.0.0: - version "1.0.5" - resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0" - integrity sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A== - dependencies: - aproba "^1.1.1" - fs-write-stream-atomic "^1.0.8" - iferr "^0.1.5" - mkdirp "^0.5.1" - rimraf "^2.5.4" - run-queue "^1.0.0" - -copy-descriptor@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" - integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= - -core-js-compat@^3.6.2: - version "3.6.5" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.6.5.tgz#2a51d9a4e25dfd6e690251aa81f99e3c05481f1c" - integrity sha512-7ItTKOhOZbznhXAQ2g/slGg1PJV5zDO/WdkTwi7UEOJmkvsE32PWvx6mKtDjiMpjnR2CNf6BAD6sSxIlv7ptng== - dependencies: - browserslist "^4.8.5" - semver "7.0.0" - -core-js-pure@^3.0.0: - version "3.6.5" - resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.6.5.tgz#c79e75f5e38dbc85a662d91eea52b8256d53b813" - integrity sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA== - -core-js@^2.4.0, core-js@^2.6.10: - version "2.6.11" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" - integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg== - -core-js@^3.5.0: - version "3.6.5" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.5.tgz#7395dc273af37fb2e50e9bd3d9fe841285231d1a" - integrity sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA== - -core-util-is@1.0.2, core-util-is@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= +core-js@^3.16.1: + version "3.17.2" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.17.2.tgz#f960eae710dc62c29cca93d5332e3660e289db10" + integrity sha512-XkbXqhcXeMHPRk2ItS+zQYliAMilea2euoMsnpRRdDad6b2VY6CQQcwz1K8AnWesfw4p165RzY0bTnr3UrbYiA== -cosmiconfig@^5.0.0, cosmiconfig@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a" - integrity sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA== - dependencies: - import-fresh "^2.0.0" - is-directory "^0.3.1" - js-yaml "^3.13.1" - parse-json "^4.0.0" +core-util-is@~1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== -cosmiconfig@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-6.0.0.tgz#da4fee853c52f6b1e6935f41c1a2fc50bd4a9982" - integrity sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg== +cosmiconfig@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz#714d756522cace867867ccb4474c5d01bbae5d6d" + integrity sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ== dependencies: "@types/parse-json" "^4.0.0" - import-fresh "^3.1.0" + import-fresh "^3.2.1" parse-json "^5.0.0" path-type "^4.0.0" - yaml "^1.7.2" + yaml "^1.10.0" create-ecdh@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.3.tgz#c9111b6f33045c4697f144787f9254cdc77c45ff" - integrity sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw== + version "4.0.4" + resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e" + integrity sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A== dependencies: bn.js "^4.1.0" - elliptic "^6.0.0" + elliptic "^6.5.3" create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: version "1.2.0" @@ -3425,35 +1206,7 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: safe-buffer "^5.0.1" sha.js "^2.4.8" -create-react-context@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/create-react-context/-/create-react-context-0.3.0.tgz#546dede9dc422def0d3fc2fe03afe0bc0f4f7d8c" - integrity sha512-dNldIoSuNSvlTJ7slIKC/ZFGKexBMBrrcc+TTe1NdmROnaASuLPvqpwj9v4XS4uXZ8+YPu0sNmShX2rXI5LNsw== - dependencies: - gud "^1.0.0" - warning "^4.0.3" - -cross-spawn@7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.1.tgz#0ab56286e0f7c24e153d04cc2aa027e43a9a5d14" - integrity sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -cross-spawn@^6.0.0, cross-spawn@^6.0.5: - version "6.0.5" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" - integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== - dependencies: - nice-try "^1.0.4" - path-key "^2.0.1" - semver "^5.5.0" - shebang-command "^1.2.0" - which "^1.2.9" - -cross-spawn@^7.0.0: +cross-spawn@^7.0.0, cross-spawn@^7.0.2: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -3462,7 +1215,7 @@ cross-spawn@^7.0.0: shebang-command "^2.0.0" which "^2.0.1" -crypto-browserify@^3.11.0: +crypto-browserify@3.12.0, crypto-browserify@^3.11.0: version "3.12.0" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg== @@ -3479,371 +1232,136 @@ crypto-browserify@^3.11.0: randombytes "^2.0.0" randomfill "^1.0.3" -css-blank-pseudo@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz#dfdefd3254bf8a82027993674ccf35483bfcb3c5" - integrity sha512-LHz35Hr83dnFeipc7oqFDmsjHdljj3TQtxGGiNWSOsTLIAubSm4TEz8qCaKFpk7idaQ1GfWscF4E6mgpBysA1w== - dependencies: - postcss "^7.0.5" - -css-color-names@0.0.4, css-color-names@^0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" - integrity sha1-gIrcLnnPhHOAabZGyyDsJ762KeA= - -css-declaration-sorter@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz#c198940f63a76d7e36c1e71018b001721054cb22" - integrity sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA== - dependencies: - postcss "^7.0.1" - timsort "^0.3.0" - -css-has-pseudo@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/css-has-pseudo/-/css-has-pseudo-0.10.0.tgz#3c642ab34ca242c59c41a125df9105841f6966ee" - integrity sha512-Z8hnfsZu4o/kt+AuFzeGpLVhFOGO9mluyHBaA2bA8aCGTwah5sT3WV/fTHH8UNZUytOIImuGPrl/prlb4oX4qQ== - dependencies: - postcss "^7.0.6" - postcss-selector-parser "^5.0.0-rc.4" - -css-loader@3.4.2: - version "3.4.2" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-3.4.2.tgz#d3fdb3358b43f233b78501c5ed7b1c6da6133202" - integrity sha512-jYq4zdZT0oS0Iykt+fqnzVLRIeiPWhka+7BqPn+oSIpWJAHak5tmB/WZrJ2a21JhCeFyNnnlroSl8c+MtVndzA== - dependencies: - camelcase "^5.3.1" - cssesc "^3.0.0" - icss-utils "^4.1.1" - loader-utils "^1.2.3" - normalize-path "^3.0.0" - postcss "^7.0.23" - postcss-modules-extract-imports "^2.0.0" - postcss-modules-local-by-default "^3.0.2" - postcss-modules-scope "^2.1.1" - postcss-modules-values "^3.0.0" - postcss-value-parser "^4.0.2" - schema-utils "^2.6.0" - -css-prefers-color-scheme@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/css-prefers-color-scheme/-/css-prefers-color-scheme-3.1.1.tgz#6f830a2714199d4f0d0d0bb8a27916ed65cff1f4" - integrity sha512-MTu6+tMs9S3EUqzmqLXEcgNRbNkkD/TGFvowpeoWJn5Vfq7FMgsmRQs9X5NXAURiOBmOxm/lLjsDNXDE6k9bhg== - dependencies: - postcss "^7.0.5" +css-unit-converter@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/css-unit-converter/-/css-unit-converter-1.1.2.tgz#4c77f5a1954e6dbff60695ecb214e3270436ab21" + integrity sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA== -css-select-base-adapter@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7" - integrity sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w== +css.escape@1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" + integrity sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s= -css-select@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" - integrity sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg= +cssnano-preset-simple@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssnano-preset-simple/-/cssnano-preset-simple-3.0.0.tgz#e95d0012699ca2c741306e9a3b8eeb495a348dbe" + integrity sha512-vxQPeoMRqUT3c/9f0vWeVa2nKQIHFpogtoBvFdW4GQ3IvEJ6uauCP6p3Y5zQDLFcI7/+40FTgX12o7XUL0Ko+w== dependencies: - boolbase "~1.0.0" - css-what "2.1" - domutils "1.5.1" - nth-check "~1.0.1" + caniuse-lite "^1.0.30001202" -css-select@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-2.1.0.tgz#6a34653356635934a81baca68d0255432105dbef" - integrity sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ== +cssnano-simple@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssnano-simple/-/cssnano-simple-3.0.0.tgz#a4b8ccdef4c7084af97e19bc5b93b4ecf211e90f" + integrity sha512-oU3ueli5Dtwgh0DyeohcIEE00QVfbPR3HzyXdAl89SfnQG3y0/qcpfLVW+jPIh3/rgMZGwuW96rejZGaYE9eUg== dependencies: - boolbase "^1.0.0" - css-what "^3.2.1" - domutils "^1.7.0" - nth-check "^1.0.2" + cssnano-preset-simple "^3.0.0" -css-tree@1.0.0-alpha.37: - version "1.0.0-alpha.37" - resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22" - integrity sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg== - dependencies: - mdn-data "2.0.4" - source-map "^0.6.1" +csstype@^3.0.2: + version "3.0.8" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.8.tgz#d2266a792729fb227cd216fb572f43728e1ad340" + integrity sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw== -css-tree@1.0.0-alpha.39: - version "1.0.0-alpha.39" - resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.39.tgz#2bff3ffe1bb3f776cf7eefd91ee5cba77a149eeb" - integrity sha512-7UvkEYgBAHRG9Nt980lYxjsTrCyHFN53ky3wVsDkiMdVqylqRt+Zc+jm5qw7/qyOvN2dHSYtX0e4MbCCExSvnA== +d3-array@2, d3-array@^2.3.0: + version "2.12.1" + resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-2.12.1.tgz#e20b41aafcdffdf5d50928004ececf815a465e81" + integrity sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ== dependencies: - mdn-data "2.0.6" - source-map "^0.6.1" + internmap "^1.0.0" -css-what@2.1: - version "2.1.3" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" - integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg== +"d3-color@1 - 2": + version "2.0.0" + resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-2.0.0.tgz#8d625cab42ed9b8f601a1760a389f7ea9189d62e" + integrity sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ== -css-what@^3.2.1: - version "3.3.0" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.3.0.tgz#10fec696a9ece2e591ac772d759aacabac38cd39" - integrity sha512-pv9JPyatiPaQ6pf4OvD/dbfm0o5LviWmwxNWzblYf/1u9QZd0ihV+PMwy5jdQWQ3349kZmKEx9WXuSka2dM4cg== +"d3-format@1 - 2": + version "2.0.0" + resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-2.0.0.tgz#a10bcc0f986c372b729ba447382413aabf5b0767" + integrity sha512-Ab3S6XuE/Q+flY96HXT0jOXcM4EAClYFnRGY5zsjRGNy6qCYrQsMffs7cV5Q9xejb35zxW5hf/guKw34kvIKsA== -css@^2.0.0: - version "2.2.4" - resolved "https://registry.yarnpkg.com/css/-/css-2.2.4.tgz#c646755c73971f2bba6a601e2cf2fd71b1298929" - integrity sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw== +"d3-interpolate@1.2.0 - 2", d3-interpolate@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-2.0.1.tgz#98be499cfb8a3b94d4ff616900501a64abc91163" + integrity sha512-c5UhwwTs/yybcmTpAVqwSFl6vrQ8JZJoT5F7xNFK9pymv5C0Ymcc9/LIJHtYIggg/yS9YHw8i8O8tgb9pupjeQ== dependencies: - inherits "^2.0.3" - source-map "^0.6.1" - source-map-resolve "^0.5.2" - urix "^0.1.0" - -cssdb@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/cssdb/-/cssdb-4.4.0.tgz#3bf2f2a68c10f5c6a08abd92378331ee803cddb0" - integrity sha512-LsTAR1JPEM9TpGhl/0p3nQecC2LJ0kD8X5YARu1hk/9I1gril5vDtMZyNxcEpxxDj34YNck/ucjuoUd66K03oQ== + d3-color "1 - 2" -cssesc@^2.0.0: +"d3-path@1 - 2": version "2.0.0" - resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-2.0.0.tgz#3b13bd1bb1cb36e1bcb5a4dcd27f54c5dcb35703" - integrity sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg== - -cssesc@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" - integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== - -cssnano-preset-default@^4.0.7: - version "4.0.7" - resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz#51ec662ccfca0f88b396dcd9679cdb931be17f76" - integrity sha512-x0YHHx2h6p0fCl1zY9L9roD7rnlltugGu7zXSKQx6k2rYw0Hi3IqxcoAGF7u9Q5w1nt7vK0ulxV8Lo+EvllGsA== - dependencies: - css-declaration-sorter "^4.0.1" - cssnano-util-raw-cache "^4.0.1" - postcss "^7.0.0" - postcss-calc "^7.0.1" - postcss-colormin "^4.0.3" - postcss-convert-values "^4.0.1" - postcss-discard-comments "^4.0.2" - postcss-discard-duplicates "^4.0.2" - postcss-discard-empty "^4.0.1" - postcss-discard-overridden "^4.0.1" - postcss-merge-longhand "^4.0.11" - postcss-merge-rules "^4.0.3" - postcss-minify-font-values "^4.0.2" - postcss-minify-gradients "^4.0.2" - postcss-minify-params "^4.0.2" - postcss-minify-selectors "^4.0.2" - postcss-normalize-charset "^4.0.1" - postcss-normalize-display-values "^4.0.2" - postcss-normalize-positions "^4.0.2" - postcss-normalize-repeat-style "^4.0.2" - postcss-normalize-string "^4.0.2" - postcss-normalize-timing-functions "^4.0.2" - postcss-normalize-unicode "^4.0.1" - postcss-normalize-url "^4.0.1" - postcss-normalize-whitespace "^4.0.2" - postcss-ordered-values "^4.1.2" - postcss-reduce-initial "^4.0.3" - postcss-reduce-transforms "^4.0.2" - postcss-svgo "^4.0.2" - postcss-unique-selectors "^4.0.1" - -cssnano-util-get-arguments@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz#ed3a08299f21d75741b20f3b81f194ed49cc150f" - integrity sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8= - -cssnano-util-get-match@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz#c0e4ca07f5386bb17ec5e52250b4f5961365156d" - integrity sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0= + resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-2.0.0.tgz#55d86ac131a0548adae241eebfb56b4582dd09d8" + integrity sha512-ZwZQxKhBnv9yHaiWd6ZU4x5BtCQ7pXszEV9CU6kRgwIQVQGLMv1oiL4M+MK/n79sYzsj+gcgpPQSctJUsLN7fA== -cssnano-util-raw-cache@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz#b26d5fd5f72a11dfe7a7846fb4c67260f96bf282" - integrity sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA== +d3-scale@^3.2.3: + version "3.3.0" + resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-3.3.0.tgz#28c600b29f47e5b9cd2df9749c206727966203f3" + integrity sha512-1JGp44NQCt5d1g+Yy+GeOnZP7xHo0ii8zsQp6PGzd+C1/dl0KGsp9A7Mxwp+1D1o4unbTTxVdU/ZOIEBoeZPbQ== dependencies: - postcss "^7.0.0" + d3-array "^2.3.0" + d3-format "1 - 2" + d3-interpolate "1.2.0 - 2" + d3-time "^2.1.1" + d3-time-format "2 - 3" -cssnano-util-same-parent@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz#574082fb2859d2db433855835d9a8456ea18bbf3" - integrity sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q== - -cssnano@^4.1.10: - version "4.1.10" - resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-4.1.10.tgz#0ac41f0b13d13d465487e111b778d42da631b8b2" - integrity sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ== +d3-shape@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-2.1.0.tgz#3b6a82ccafbc45de55b57fcf956c584ded3b666f" + integrity sha512-PnjUqfM2PpskbSLTJvAzp2Wv4CZsnAgTfcVRTwW03QR3MkXF8Uo7B1y/lWkAsmbKwuecto++4NlsYcvYpXpTHA== dependencies: - cosmiconfig "^5.0.0" - cssnano-preset-default "^4.0.7" - is-resolvable "^1.0.0" - postcss "^7.0.0" + d3-path "1 - 2" -csso@^4.0.2: - version "4.0.3" - resolved "https://registry.yarnpkg.com/csso/-/csso-4.0.3.tgz#0d9985dc852c7cc2b2cacfbbe1079014d1a8e903" - integrity sha512-NL3spysxUkcrOgnpsT4Xdl2aiEiBG6bXswAABQVHcMrfjjBisFOKwLDOmf4wf32aPdcJws1zds2B0Rg+jqMyHQ== +"d3-time-format@2 - 3": + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-3.0.0.tgz#df8056c83659e01f20ac5da5fdeae7c08d5f1bb6" + integrity sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag== dependencies: - css-tree "1.0.0-alpha.39" - -cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0", cssom@^0.3.4: - version "0.3.8" - resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" - integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== + d3-time "1 - 2" -cssstyle@^1.0.0, cssstyle@^1.1.1: - version "1.4.0" - resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-1.4.0.tgz#9d31328229d3c565c61e586b02041a28fccdccf1" - integrity sha512-GBrLZYZ4X4x6/QEoBnIrqb8B/f5l4+8me2dkom/j1Gtbxy0kBv6OGzKuAsGM75bkGwGAFkt56Iwg28S3XTZgSA== +"d3-time@1 - 2", d3-time@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-2.1.1.tgz#e9d8a8a88691f4548e68ca085e5ff956724a6682" + integrity sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ== dependencies: - cssom "0.3.x" - -csstype@^2.2.0: - version "2.6.10" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.10.tgz#e63af50e66d7c266edb6b32909cfd0aabe03928b" - integrity sha512-D34BqZU4cIlMCY93rZHbrq9pjTAQJ3U8S8rfBqjwHxkGPThWFjzZDQpgMJY0QViLxth6ZKYiwFBo14RdN44U/w== - -cyclist@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" - integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= + d3-array "2" -d3-array@^1.2.0: - version "1.2.4" - resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-1.2.4.tgz#635ce4d5eea759f6f605863dbcfc30edc737f71f" - integrity sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw== - -d3-collection@1: +damerau-levenshtein@^1.0.6: version "1.0.7" - resolved "https://registry.yarnpkg.com/d3-collection/-/d3-collection-1.0.7.tgz#349bd2aa9977db071091c13144d5e4f16b5b310e" - integrity sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A== - -d3-color@1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-1.4.1.tgz#c52002bf8846ada4424d55d97982fef26eb3bc8a" - integrity sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q== - -d3-format@1: - version "1.4.4" - resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.4.4.tgz#356925f28d0fd7c7983bfad593726fce46844030" - integrity sha512-TWks25e7t8/cqctxCmxpUuzZN11QxIA7YrMbram94zMQ0PXjE4LVIMe/f6a4+xxL8HQ3OsAFULOINQi1pE62Aw== - -d3-interpolate@1, d3-interpolate@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-1.4.0.tgz#526e79e2d80daa383f9e0c1c1c7dcc0f0583e987" - integrity sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA== - dependencies: - d3-color "1" - -d3-path@1: - version "1.0.9" - resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-1.0.9.tgz#48c050bb1fe8c262493a8caf5524e3e9591701cf" - integrity sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg== + resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.7.tgz#64368003512a1a6992593741a09a9d31a836f55d" + integrity sha512-VvdQIPGdWP0SqFXghj79Wf/5LArmreyMsGLa6FG6iC4t3j7j5s71TrwWmT/4akbDQIqjfACkLZmjXhA7g2oUZw== -d3-scale@^2.1.0: - version "2.2.2" - resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-2.2.2.tgz#4e880e0b2745acaaddd3ede26a9e908a9e17b81f" - integrity sha512-LbeEvGgIb8UMcAa0EATLNX0lelKWGYDQiPdHj+gLblGVhGLyNbaCn3EvrJf0A3Y/uOOU5aD6MTh5ZFCdEwGiCw== - dependencies: - d3-array "^1.2.0" - d3-collection "1" - d3-format "1" - d3-interpolate "1" - d3-time "1" - d3-time-format "2" - -d3-shape@^1.2.0: - version "1.3.7" - resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.3.7.tgz#df63801be07bc986bc54f63789b4fe502992b5d7" - integrity sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw== - dependencies: - d3-path "1" - -d3-time-format@2: - version "2.2.3" - resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-2.2.3.tgz#0c9a12ee28342b2037e5ea1cf0b9eb4dd75f29cb" - integrity sha512-RAHNnD8+XvC4Zc4d2A56Uw0yJoM7bsvOlJR33bclxq399Rak/b9bhvu/InjxdWhPtkgU53JJcleJTGkNRnN6IA== - dependencies: - d3-time "1" - -d3-time@1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-1.1.0.tgz#b1e19d307dae9c900b7e5b25ffc5dcc249a8a0f1" - integrity sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA== - -d@1, d@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" - integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== - dependencies: - es5-ext "^0.10.50" - type "^1.0.1" - -damerau-levenshtein@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz#143c1641cb3d85c60c32329e26899adea8701791" - integrity sha512-JVrozIeElnj3QzfUIt8tB8YMluBJom4Vw9qTPpjGYQ9fYlB3D/rb6OordUxf3xeFB35LKWs0xqcO5U6ySvBtug== - -dashdash@^1.12.0: - version "1.14.1" - resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" - integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= - dependencies: - assert-plus "^1.0.0" - -data-urls@^1.0.0, data-urls@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-1.1.0.tgz#15ee0582baa5e22bb59c77140da8f9c76963bbfe" - integrity sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ== - dependencies: - abab "^2.0.0" - whatwg-mimetype "^2.2.0" - whatwg-url "^7.0.0" +data-uri-to-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz#594b8973938c5bc2c33046535785341abc4f3636" + integrity sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og== -debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9: +debug@2, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== dependencies: ms "2.0.0" -debug@^3.1.1, debug@^3.2.5: - version "3.2.6" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" - integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== - dependencies: - ms "^2.1.1" - -debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@~4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" - integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== +debug@^3.2.7: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== dependencies: ms "^2.1.1" -debug@~3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" - integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== +debug@^4.0.1, debug@^4.1.1, debug@^4.2.0, debug@^4.3.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" + integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== dependencies: - ms "2.0.0" - -decamelize@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" - integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= + ms "2.1.2" decimal.js-light@^2.4.1: - version "2.5.0" - resolved "https://registry.yarnpkg.com/decimal.js-light/-/decimal.js-light-2.5.0.tgz#ca7faf504c799326df94b0ab920424fdfc125348" - integrity sha512-b3VJCbd2hwUpeRGG3Toob+CRo8W22xplipNhP3tN7TSVB/cyMX71P1vM2Xjc9H74uV6dS2hDDmo/rHq8L87Upg== - -decimal.js@^10.2.0: - version "10.2.0" - resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.2.0.tgz#39466113a9e036111d02f82489b5fd6b0b5ed231" - integrity sha512-vDPw+rDgn3bZe1+F/pyEwb1oMG2XTlRVgAa6B4KccTEpYgF8w6eQllVbQcfIJnZyvzFtFpxnpGtx8dd7DJp/Rw== + version "2.5.1" + resolved "https://registry.yarnpkg.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz#134fd32508f19e208f4fb2f8dac0d2626a867934" + integrity sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg== -decode-uri-component@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" - integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= +decimal.js@^10.2.1: + version "10.3.1" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783" + integrity sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ== dedent@^0.7.0: version "0.7.0" @@ -3855,78 +1373,18 @@ deep-diff@^0.3.5: resolved "https://registry.yarnpkg.com/deep-diff/-/deep-diff-0.3.8.tgz#c01de63efb0eec9798801d40c7e0dae25b582c84" integrity sha1-wB3mPvsO7JeYgB1Ax+Da4ltYLIQ= -deep-equal@^1.0.1, deep-equal@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a" - integrity sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g== - dependencies: - is-arguments "^1.0.4" - is-date-object "^1.0.1" - is-regex "^1.0.4" - object-is "^1.0.1" - object-keys "^1.1.1" - regexp.prototype.flags "^1.2.0" - -deep-is@~0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" - integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= - -default-gateway@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-4.2.0.tgz#167104c7500c2115f6dd69b0a536bb8ed720552b" - integrity sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA== - dependencies: - execa "^1.0.0" - ip-regex "^2.1.0" +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== -define-properties@^1.1.2, define-properties@^1.1.3: +define-properties@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== dependencies: object-keys "^1.0.12" -define-property@^0.2.5: - version "0.2.5" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" - integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= - dependencies: - is-descriptor "^0.1.0" - -define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" - integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= - dependencies: - is-descriptor "^1.0.0" - -define-property@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" - integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== - dependencies: - is-descriptor "^1.0.2" - isobject "^3.0.1" - -del@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/del/-/del-4.1.1.tgz#9e8f117222ea44a31ff3a156c049b99052a9f0b4" - integrity sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ== - dependencies: - "@types/glob" "^7.1.1" - globby "^6.1.0" - is-path-cwd "^2.0.0" - is-path-in-cwd "^2.0.0" - p-map "^2.0.0" - pify "^4.0.1" - rimraf "^2.6.3" - -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= - depd@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" @@ -3940,33 +1398,10 @@ des.js@^1.0.0: inherits "^2.0.1" minimalistic-assert "^1.0.0" -destroy@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" - integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= - -detect-newline@^2.1.0: +detect-node@^2.0.4, detect-node@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2" - integrity sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I= - -detect-node@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c" - integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw== - -detect-port-alt@1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/detect-port-alt/-/detect-port-alt-1.1.6.tgz#24707deabe932d4a3cf621302027c2b266568275" - integrity sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q== - dependencies: - address "^1.0.1" - debug "^2.6.0" - -diff-sequences@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.9.0.tgz#5715d6244e2aa65f48bba0bc972db0b0b11e95b5" - integrity sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew== + resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" + integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== diffie-hellman@^5.0.0: version "5.0.3" @@ -3977,41 +1412,12 @@ diffie-hellman@^5.0.0: miller-rabin "^4.0.0" randombytes "^2.0.0" -dir-glob@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.0.0.tgz#0b205d2b6aef98238ca286598a8204d29d0a0034" - integrity sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag== - dependencies: - arrify "^1.0.1" - path-type "^3.0.0" - -dns-equal@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" - integrity sha1-s55/HabrCnW6nBcySzR1PEfgZU0= - -dns-packet@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.1.tgz#12aa426981075be500b910eedcd0b47dd7deda5a" - integrity sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg== - dependencies: - ip "^1.1.0" - safe-buffer "^5.0.1" - -dns-txt@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/dns-txt/-/dns-txt-2.0.2.tgz#b91d806f5d27188e4ab3e7d107d881a1cc4642b6" - integrity sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY= - dependencies: - buffer-indexof "^1.0.0" - -doctrine@1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" - integrity sha1-N53Ocw9hZvds76TmcHoVmwLFpvo= +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== dependencies: - esutils "^2.0.2" - isarray "^1.0.0" + path-type "^4.0.0" doctrine@^2.1.0: version "2.1.0" @@ -4027,13 +1433,6 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" -dom-converter@^0.2: - version "0.2.0" - resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" - integrity sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA== - dependencies: - utila "~0.4" - dom-helpers@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.4.0.tgz#e9b369700f959f62ecde5a6babde4bccd9169af8" @@ -4041,246 +1440,112 @@ dom-helpers@^3.4.0: dependencies: "@babel/runtime" "^7.1.2" -dom-serializer@0: - version "0.2.2" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" - integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g== - dependencies: - domelementtype "^2.0.1" - entities "^2.0.0" +domain-browser@4.19.0: + version "4.19.0" + resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-4.19.0.tgz#1093e17c0a17dbd521182fe90d49ac1370054af1" + integrity sha512-fRA+BaAWOR/yr/t7T9E9GJztHPeFjj8U35ajyAjCDtAAnTn1Rc1f6W6VGPJrO1tkQv9zWu+JRof7z6oQtiYVFQ== domain-browser@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== -domelementtype@1, domelementtype@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" - integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== - -domelementtype@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.0.1.tgz#1f8bdfe91f5a78063274e803b4bdcedf6e94f94d" - integrity sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ== - -domexception@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90" - integrity sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug== - dependencies: - webidl-conversions "^4.0.2" - -domhandler@^2.3.0: - version "2.4.2" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803" - integrity sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA== - dependencies: - domelementtype "1" - -domutils@1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" - integrity sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8= - dependencies: - dom-serializer "0" - domelementtype "1" - -domutils@^1.5.1, domutils@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" - integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg== - dependencies: - dom-serializer "0" - domelementtype "1" - -dot-case@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.3.tgz#21d3b52efaaba2ea5fda875bb1aa8124521cf4aa" - integrity sha512-7hwEmg6RiSQfm/GwPL4AAWXKy3YNNZA3oFv2Pdiey0mwkRCPZ9x6SZbkLcn8Ma5PYeVokzoD4Twv2n7LKp5WeA== - dependencies: - no-case "^3.0.3" - tslib "^1.10.0" - -dot-prop@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.2.0.tgz#c34ecc29556dc45f1f4c22697b6f4904e0cc4fcb" - integrity sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A== - dependencies: - is-obj "^2.0.0" - -dotenv-expand@5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" - integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== - -dotenv@8.2.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" - integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== - -duplexer@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" - integrity sha1-rOb/gIwc5mtX0ev5eXessCM0z8E= - -duplexify@^3.4.2, duplexify@^3.6.0: - version "3.7.1" - resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" - integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g== - dependencies: - end-of-stream "^1.0.0" - inherits "^2.0.1" - readable-stream "^2.0.0" - stream-shift "^1.0.0" - -ecc-jsbn@~0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" - integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= - dependencies: - jsbn "~0.1.0" - safer-buffer "^2.1.0" - -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= - -electron-to-chromium@^1.3.378, electron-to-chromium@^1.3.483: - version "1.3.483" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.483.tgz#9269e7cfc1c8e72709824da171cbe47ca5e3ca9e" - integrity sha512-+05RF8S9rk8S0G8eBCqBRBaRq7+UN3lDs2DAvnG8SBSgQO3hjy0+qt4CmRk5eiuGbTcaicgXfPmBi31a+BD3lg== +electron-to-chromium@^1.3.723: + version "1.3.831" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.831.tgz#96201f83f6eef05054b532a897fd3cd73cd60250" + integrity sha512-0tc2lPzgEipHCyRcvDTTaBk5+jSPfNaCvbQdevNMqJkHLvrBiwhygPR0hDyPZEK7Xztvv+58gSFKJ/AUVT1yYQ== -elliptic@^6.0.0, elliptic@^6.5.2: - version "6.5.3" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.3.tgz#cb59eb2efdaf73a0bd78ccd7015a62ad6e0f93d6" - integrity sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw== +elliptic@^6.5.3: + version "6.5.4" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" + integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== dependencies: - bn.js "^4.4.0" - brorand "^1.0.1" + bn.js "^4.11.9" + brorand "^1.1.0" hash.js "^1.0.0" - hmac-drbg "^1.0.0" - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - minimalistic-crypto-utils "^1.0.0" - -emoji-regex@^7.0.1, emoji-regex@^7.0.2: - version "7.0.3" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" - integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== + hmac-drbg "^1.0.1" + inherits "^2.0.4" + minimalistic-assert "^1.0.1" + minimalistic-crypto-utils "^1.0.1" emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== +emoji-regex@^9.0.0: + version "9.2.2" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + emojis-list@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" integrity sha1-TapNnbAPmBmIDHn6RXrlsJof04k= -emojis-list@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" - integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== - -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= +encoding@0.1.13: + version "0.1.13" + resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" + integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== + dependencies: + iconv-lite "^0.6.2" -end-of-stream@^1.0.0, end-of-stream@^1.1.0: +end-of-stream@^1.1.0: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== dependencies: once "^1.4.0" -engine.io-client@~3.4.0: - version "3.4.3" - resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.4.3.tgz#192d09865403e3097e3575ebfeb3861c4d01a66c" - integrity sha512-0NGY+9hioejTEJCaSJZfWZLk4FPI9dN+1H1C4+wj2iuFba47UgZbJzfWs4aNFajnX/qAaYKbe2lLTfEEWzCmcw== - dependencies: - component-emitter "~1.3.0" - component-inherit "0.0.3" - debug "~4.1.0" - engine.io-parser "~2.2.0" - has-cors "1.1.0" - indexof "0.0.1" - parseqs "0.0.5" - parseuri "0.0.5" - ws "~6.1.0" - xmlhttprequest-ssl "~1.5.4" - yeast "0.1.2" - -engine.io-parser@~2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-2.2.0.tgz#312c4894f57d52a02b420868da7b5c1c84af80ed" - integrity sha512-6I3qD9iUxotsC5HEMuuGsKA0cXerGz+4uGcXQEkfBidgKf0amsjrrtwcbwK/nzpZBxclXlV7gGl9dgWvu4LF6w== - dependencies: - after "0.8.2" - arraybuffer.slice "~0.0.7" - base64-arraybuffer "0.1.5" - blob "0.0.5" - has-binary2 "~1.0.2" - -enhanced-resolve@^4.1.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.2.0.tgz#5d43bda4a0fd447cb0ebbe71bef8deff8805ad0d" - integrity sha512-S7eiFb/erugyd1rLb6mQ3Vuq+EXHv5cpCkNqqIkYkBgN2QdFnyCZzFBleqwGEx4lgNGYij81BWnCrFNK7vxvjQ== - dependencies: - graceful-fs "^4.1.2" - memory-fs "^0.5.0" - tapable "^1.0.0" - -enquirer@^2.3.5: - version "2.3.5" - resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.5.tgz#3ab2b838df0a9d8ab9e7dff235b0e8712ef92381" - integrity sha512-BNT1C08P9XD0vNg3J475yIUG+mVdp9T6towYFHUv897X0KoHBjB1shyrNmhmtHWKP17iSWgo7Gqh7BBuzLZMSA== +enhanced-resolve@^5.7.0: + version "5.8.2" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.8.2.tgz#15ddc779345cbb73e97c611cd00c01c1e7bf4d8b" + integrity sha512-F27oB3WuHDzvR2DOGNTaYy0D5o0cnrv8TeI482VM4kYgQd/FT9lUQwuNsJ0oOHtBUq7eiW5ytqzp7nBFknL+GA== dependencies: - ansi-colors "^3.2.1" - -entities@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" - integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== - -entities@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.3.tgz#5c487e5742ab93c15abb5da22759b8590ec03b7f" - integrity sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ== + graceful-fs "^4.2.4" + tapable "^2.2.0" -errno@^0.1.3, errno@~0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" - integrity sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg== +enquirer@^2.3.5, enquirer@^2.3.6: + version "2.3.6" + resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" + integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== dependencies: - prr "~1.0.1" + ansi-colors "^4.1.1" -error-ex@^1.2.0, error-ex@^1.3.1: +error-ex@^1.3.1: version "1.3.2" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== dependencies: is-arrayish "^0.2.1" -es-abstract@^1.17.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.5: - version "1.17.6" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.6.tgz#9142071707857b2cacc7b89ecb670316c3e2d52a" - integrity sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw== +es-abstract@^1.18.0-next.1, es-abstract@^1.18.0-next.2, es-abstract@^1.18.2, es-abstract@^1.18.5: + version "1.18.5" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.5.tgz#9b10de7d4c206a3581fd5b2124233e04db49ae19" + integrity sha512-DDggyJLoS91CkJjgauM5c0yZMjiD1uK3KcaCeAmffGwZ+ODWzOkPN4QwRbsK5DOFf06fywmyLci3ZD8jLGhVYA== dependencies: + call-bind "^1.0.2" es-to-primitive "^1.2.1" function-bind "^1.1.1" + get-intrinsic "^1.1.1" has "^1.0.3" - has-symbols "^1.0.1" - is-callable "^1.2.0" - is-regex "^1.1.0" - object-inspect "^1.7.0" + has-symbols "^1.0.2" + internal-slot "^1.0.3" + is-callable "^1.2.3" + is-negative-zero "^2.0.1" + is-regex "^1.1.3" + is-string "^1.0.6" + object-inspect "^1.11.0" object-keys "^1.1.1" - object.assign "^4.1.0" - string.prototype.trimend "^1.0.1" - string.prototype.trimstart "^1.0.1" + object.assign "^4.1.2" + string.prototype.trimend "^1.0.4" + string.prototype.trimstart "^1.0.4" + unbox-primitive "^1.0.1" + +es-cookie@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/es-cookie/-/es-cookie-1.3.2.tgz#80e831597f72a25721701bdcb21d990319acd831" + integrity sha512-UTlYYhXGLOy05P/vKVT2Ui7WtC7NiRzGtJyAKKn32g5Gvcjn7KAClLPWlipCtxIus934dFg9o9jXiBL0nP+t9Q== es-to-primitive@^1.2.1: version "1.2.1" @@ -4291,313 +1556,263 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" -es5-ext@^0.10.35, es5-ext@^0.10.50: - version "0.10.53" - resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1" - integrity sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q== - dependencies: - es6-iterator "~2.0.3" - es6-symbol "~3.1.3" - next-tick "~1.0.0" - -es6-iterator@2.0.3, es6-iterator@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" - integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c= - dependencies: - d "1" - es5-ext "^0.10.35" - es6-symbol "^3.1.1" - -es6-symbol@^3.1.1, es6-symbol@~3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" - integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== - dependencies: - d "^1.0.1" - ext "^1.1.2" - -escalade@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.0.1.tgz#52568a77443f6927cd0ab9c73129137533c965ed" - integrity sha512-DR6NO3h9niOT+MZs7bjxlj2a1k+POu5RN8CLTPX2+i78bRi9eLe7+0zXgUHMnGXWybYcL61E9hGhPKqedy8tQA== +es6-object-assign@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/es6-object-assign/-/es6-object-assign-1.1.0.tgz#c2c3582656247c39ea107cb1e6652b6f9f24523c" + integrity sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw= -escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== escape-latex@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/escape-latex/-/escape-latex-1.2.0.tgz#07c03818cf7dac250cce517f4fda1b001ef2bca1" integrity sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw== -escape-string-regexp@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" - integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== - -escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: +escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= -escodegen@^1.11.0, escodegen@^1.9.1: - version "1.14.3" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" - integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw== - dependencies: - esprima "^4.0.1" - estraverse "^4.2.0" - esutils "^2.0.2" - optionator "^0.8.1" - optionalDependencies: - source-map "~0.6.1" - -eslint-config-react-app@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/eslint-config-react-app/-/eslint-config-react-app-5.2.1.tgz#698bf7aeee27f0cea0139eaef261c7bf7dd623df" - integrity sha512-pGIZ8t0mFLcV+6ZirRgYK6RVqUIKRIi9MmgzUEmrIknsn3AdO0I32asO86dJgloHq+9ZPl8UIg8mYrvgP5u2wQ== - dependencies: - confusing-browser-globals "^1.0.9" - -eslint-import-resolver-node@^0.3.2: - version "0.3.4" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz#85ffa81942c25012d8231096ddf679c03042c717" - integrity sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA== +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-config-next@^11.1.2: + version "11.1.2" + resolved "https://registry.yarnpkg.com/eslint-config-next/-/eslint-config-next-11.1.2.tgz#73c918f2fa6120d5f65080bf3fcf6b154905707e" + integrity sha512-dFutecxX2Z5/QVlLwdtKt+gIfmNMP8Qx6/qZh3LM/DFVdGJEAnUKrr4VwGmACB2kx/PQ5bx3R+QxnEg4fDPiTg== + dependencies: + "@next/eslint-plugin-next" "11.1.2" + "@rushstack/eslint-patch" "^1.0.6" + "@typescript-eslint/parser" "^4.20.0" + eslint-import-resolver-node "^0.3.4" + eslint-import-resolver-typescript "^2.4.0" + eslint-plugin-import "^2.22.1" + eslint-plugin-jsx-a11y "^6.4.1" + eslint-plugin-react "^7.23.1" + eslint-plugin-react-hooks "^4.2.0" + +eslint-import-resolver-node@^0.3.4, eslint-import-resolver-node@^0.3.6: + version "0.3.6" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz#4048b958395da89668252001dbd9eca6b83bacbd" + integrity sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw== dependencies: - debug "^2.6.9" - resolve "^1.13.1" + debug "^3.2.7" + resolve "^1.20.0" -eslint-loader@3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/eslint-loader/-/eslint-loader-3.0.3.tgz#e018e3d2722381d982b1201adb56819c73b480ca" - integrity sha512-+YRqB95PnNvxNp1HEjQmvf9KNvCin5HXYYseOXVC2U0KEcw4IkQ2IQEBG46j7+gW39bMzeu0GsUhVbBY3Votpw== +eslint-import-resolver-typescript@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-2.4.0.tgz#ec1e7063ebe807f0362a7320543aaed6fe1100e1" + integrity sha512-useJKURidCcldRLCNKWemr1fFQL1SzB3G4a0li6lFGvlc5xGe1hY343bvG07cbpCzPuM/lK19FIJB3XGFSkplA== dependencies: - fs-extra "^8.1.0" - loader-fs-cache "^1.0.2" - loader-utils "^1.2.3" - object-hash "^2.0.1" - schema-utils "^2.6.1" + debug "^4.1.1" + glob "^7.1.6" + is-glob "^4.0.1" + resolve "^1.17.0" + tsconfig-paths "^3.9.0" -eslint-module-utils@^2.4.1: - version "2.6.0" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz#579ebd094f56af7797d19c9866c9c9486629bfa6" - integrity sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA== +eslint-module-utils@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.6.2.tgz#94e5540dd15fe1522e8ffa3ec8db3b7fa7e7a534" + integrity sha512-QG8pcgThYOuqxupd06oYTZoNOGaUdTY1PqK+oS6ElF6vs4pBdk/aYxFVQQXzcrAqp9m7cl7lb2ubazX+g16k2Q== dependencies: - debug "^2.6.9" + debug "^3.2.7" pkg-dir "^2.0.0" -eslint-plugin-flowtype@4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-flowtype/-/eslint-plugin-flowtype-4.6.0.tgz#82b2bd6f21770e0e5deede0228e456cb35308451" - integrity sha512-W5hLjpFfZyZsXfo5anlu7HM970JBDqbEshAJUkeczP6BFCIfJXuiIBQXyberLRtOStT0OGPF8efeTbxlHk4LpQ== +eslint-plugin-import@^2.22.1: + version "2.24.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.24.2.tgz#2c8cd2e341f3885918ee27d18479910ade7bb4da" + integrity sha512-hNVtyhiEtZmpsabL4neEj+6M5DCLgpYyG9nzJY8lZQeQXEn5UPW1DpUdsMHMXsq98dbNm7nt1w9ZMSVpfJdi8Q== dependencies: - lodash "^4.17.15" - -eslint-plugin-import@2.20.1: - version "2.20.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.20.1.tgz#802423196dcb11d9ce8435a5fc02a6d3b46939b3" - integrity sha512-qQHgFOTjguR+LnYRoToeZWT62XM55MBVXObHM6SKFd1VzDcX/vqT1kAz8ssqigh5eMj8qXcRoXXGZpPP6RfdCw== - dependencies: - array-includes "^3.0.3" - array.prototype.flat "^1.2.1" - contains-path "^0.1.0" + array-includes "^3.1.3" + array.prototype.flat "^1.2.4" debug "^2.6.9" - doctrine "1.5.0" - eslint-import-resolver-node "^0.3.2" - eslint-module-utils "^2.4.1" + doctrine "^2.1.0" + eslint-import-resolver-node "^0.3.6" + eslint-module-utils "^2.6.2" + find-up "^2.0.0" has "^1.0.3" + is-core-module "^2.6.0" minimatch "^3.0.4" - object.values "^1.1.0" - read-pkg-up "^2.0.0" - resolve "^1.12.0" - -eslint-plugin-jsx-a11y@6.2.3: - version "6.2.3" - resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.2.3.tgz#b872a09d5de51af70a97db1eea7dc933043708aa" - integrity sha512-CawzfGt9w83tyuVekn0GDPU9ytYtxyxyFZ3aSWROmnRRFQFT2BiPJd7jvRdzNDi6oLWaS2asMeYSNMjWTV4eNg== - dependencies: - "@babel/runtime" "^7.4.5" - aria-query "^3.0.0" - array-includes "^3.0.3" + object.values "^1.1.4" + pkg-up "^2.0.0" + read-pkg-up "^3.0.0" + resolve "^1.20.0" + tsconfig-paths "^3.11.0" + +eslint-plugin-jsx-a11y@^6.4.1: + version "6.4.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.4.1.tgz#a2d84caa49756942f42f1ffab9002436391718fd" + integrity sha512-0rGPJBbwHoGNPU73/QCLP/vveMlM1b1Z9PponxO87jfr6tuH5ligXbDT6nHSSzBC8ovX2Z+BQu7Bk5D/Xgq9zg== + dependencies: + "@babel/runtime" "^7.11.2" + aria-query "^4.2.2" + array-includes "^3.1.1" ast-types-flow "^0.0.7" - axobject-query "^2.0.2" - damerau-levenshtein "^1.0.4" - emoji-regex "^7.0.2" + axe-core "^4.0.2" + axobject-query "^2.2.0" + damerau-levenshtein "^1.0.6" + emoji-regex "^9.0.0" has "^1.0.3" - jsx-ast-utils "^2.2.1" + jsx-ast-utils "^3.1.0" + language-tags "^1.0.5" -eslint-plugin-react-hooks@^1.6.1: - version "1.7.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-1.7.0.tgz#6210b6d5a37205f0b92858f895a4e827020a7d04" - integrity sha512-iXTCFcOmlWvw4+TOE8CLWj6yX1GwzT0Y6cUfHHZqWnSk144VmVIRcVGtUAzrLES7C798lmvnt02C7rxaOX1HNA== +eslint-plugin-react-hooks@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.2.0.tgz#8c229c268d468956334c943bb45fc860280f5556" + integrity sha512-623WEiZJqxR7VdxFCKLI6d6LLpwJkGPYKODnkH3D7WpOG5KM8yWueBd8TLsNAetEJNF5iJmolaAKO3F8yzyVBQ== -eslint-plugin-react@7.19.0: - version "7.19.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.19.0.tgz#6d08f9673628aa69c5559d33489e855d83551666" - integrity sha512-SPT8j72CGuAP+JFbT0sJHOB80TX/pu44gQ4vXH/cq+hQTiY2PuZ6IHkqXJV6x1b28GDdo1lbInjKUrrdUf0LOQ== +eslint-plugin-react@^7.23.1: + version "7.25.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.25.1.tgz#9286b7cd9bf917d40309760f403e53016eda8331" + integrity sha512-P4j9K1dHoFXxDNP05AtixcJEvIT6ht8FhYKsrkY0MPCPaUMYijhpWwNiRDZVtA8KFuZOkGSeft6QwH8KuVpJug== dependencies: - array-includes "^3.1.1" + array-includes "^3.1.3" + array.prototype.flatmap "^1.2.4" doctrine "^2.1.0" + estraverse "^5.2.0" has "^1.0.3" - jsx-ast-utils "^2.2.3" - object.entries "^1.1.1" - object.fromentries "^2.0.2" - object.values "^1.1.1" + jsx-ast-utils "^2.4.1 || ^3.0.0" + minimatch "^3.0.4" + object.entries "^1.1.4" + object.fromentries "^2.0.4" + object.values "^1.1.4" prop-types "^15.7.2" - resolve "^1.15.1" - semver "^6.3.0" - string.prototype.matchall "^4.0.2" - xregexp "^4.3.0" - -eslint-scope@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" - integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg== - dependencies: - esrecurse "^4.1.0" - estraverse "^4.1.1" + resolve "^2.0.0-next.3" + string.prototype.matchall "^4.0.5" -eslint-scope@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.0.tgz#d0f971dfe59c69e0cada684b23d49dbf82600ce5" - integrity sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w== +eslint-scope@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== dependencies: - esrecurse "^4.1.0" + esrecurse "^4.3.0" estraverse "^4.1.1" -eslint-utils@^1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f" - integrity sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q== - dependencies: - eslint-visitor-keys "^1.1.0" - -eslint-utils@^2.0.0: +eslint-utils@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== dependencies: eslint-visitor-keys "^1.1.0" -eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0: +eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== -eslint@^6.6.0: - version "6.8.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.8.0.tgz#62262d6729739f9275723824302fb227c8c93ffb" - integrity sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig== +eslint-visitor-keys@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" + integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== + +eslint@^7.32.0: + version "7.32.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" + integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA== dependencies: - "@babel/code-frame" "^7.0.0" + "@babel/code-frame" "7.12.11" + "@eslint/eslintrc" "^0.4.3" + "@humanwhocodes/config-array" "^0.5.0" ajv "^6.10.0" - chalk "^2.1.0" - cross-spawn "^6.0.5" + chalk "^4.0.0" + cross-spawn "^7.0.2" debug "^4.0.1" doctrine "^3.0.0" - eslint-scope "^5.0.0" - eslint-utils "^1.4.3" - eslint-visitor-keys "^1.1.0" - espree "^6.1.2" - esquery "^1.0.1" + enquirer "^2.3.5" + escape-string-regexp "^4.0.0" + eslint-scope "^5.1.1" + eslint-utils "^2.1.0" + eslint-visitor-keys "^2.0.0" + espree "^7.3.1" + esquery "^1.4.0" esutils "^2.0.2" - file-entry-cache "^5.0.1" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" functional-red-black-tree "^1.0.1" - glob-parent "^5.0.0" - globals "^12.1.0" + glob-parent "^5.1.2" + globals "^13.6.0" ignore "^4.0.6" import-fresh "^3.0.0" imurmurhash "^0.1.4" - inquirer "^7.0.0" is-glob "^4.0.0" js-yaml "^3.13.1" json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.3.0" - lodash "^4.17.14" + levn "^0.4.1" + lodash.merge "^4.6.2" minimatch "^3.0.4" - mkdirp "^0.5.1" natural-compare "^1.4.0" - optionator "^0.8.3" + optionator "^0.9.1" progress "^2.0.0" - regexpp "^2.0.1" - semver "^6.1.2" - strip-ansi "^5.2.0" - strip-json-comments "^3.0.1" - table "^5.2.3" + regexpp "^3.1.0" + semver "^7.2.1" + strip-ansi "^6.0.0" + strip-json-comments "^3.1.0" + table "^6.0.9" text-table "^0.2.0" v8-compile-cache "^2.0.3" -espree@^6.1.2: - version "6.2.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-6.2.1.tgz#77fc72e1fd744a2052c20f38a5b575832e82734a" - integrity sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw== +espree@^7.3.0, espree@^7.3.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" + integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g== dependencies: - acorn "^7.1.1" - acorn-jsx "^5.2.0" - eslint-visitor-keys "^1.1.0" + acorn "^7.4.0" + acorn-jsx "^5.3.1" + eslint-visitor-keys "^1.3.0" -esprima@^4.0.0, esprima@^4.0.1: +esprima@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esquery@^1.0.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.3.1.tgz#b78b5828aa8e214e29fb74c4d5b752e1c033da57" - integrity sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ== +esquery@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" + integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== dependencies: estraverse "^5.1.0" -esrecurse@^4.1.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf" - integrity sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ== +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== dependencies: - estraverse "^4.1.0" + estraverse "^5.2.0" -estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0: +estraverse@^4.1.1: version "4.3.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== -estraverse@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.1.0.tgz#374309d39fd935ae500e7b92e8a6b4c720e59642" - integrity sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw== +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880" + integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ== esutils@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -etag@~1.8.1: +etag@1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= -eventemitter3@^4.0.0: - version "4.0.4" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.4.tgz#b5463ace635a083d018bdc7c917b4c5f10a85384" - integrity sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ== - -events@^1.0.2: - version "1.1.1" - resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" - integrity sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ= +eventemitter3@^4.0.1: + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== events@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.1.0.tgz#84279af1b34cb75aa88bf5ff291f6d0bd9b31a59" - integrity sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg== - -eventsource@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-1.0.7.tgz#8fbc72c93fcd34088090bc0a4e64f4b5cee6d8d0" - integrity sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ== - dependencies: - original "^1.0.0" + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: version "1.0.3" @@ -4607,28 +1822,10 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: md5.js "^1.3.4" safe-buffer "^5.1.1" -exec-sh@^0.3.2: - version "0.3.4" - resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.4.tgz#3a018ceb526cc6f6df2bb504b2bfe8e3a4934ec5" - integrity sha512-sEFIkc61v75sWeOe72qyrqg2Qg0OuLESziUDk/O/z2qgS15y2gWVFrI6f2Qn/qw/0/NCfCEsmNA4zOjkwEZT1A== - -execa@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" - integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== - dependencies: - cross-spawn "^6.0.0" - get-stream "^4.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" - -execa@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/execa/-/execa-4.0.2.tgz#ad87fb7b2d9d564f70d2b62d511bee41d5cbb240" - integrity sha512-QI2zLa6CjGWdiQsmSkZoGtDx2N+cQIGb3yNolGTdjSQzydzLgYYf8LRuagp7S7fPimjcrzUDSUFd/MgzELMi4Q== +execa@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" + integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== dependencies: cross-spawn "^7.0.0" get-stream "^5.0.0" @@ -4640,231 +1837,67 @@ execa@^4.0.1: signal-exit "^3.0.2" strip-final-newline "^2.0.0" -exit@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" - integrity sha1-BjJjj42HfMghB9MKD/8aF8uhzQw= - -expand-brackets@^2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" - integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= - dependencies: - debug "^2.3.3" - define-property "^0.2.5" - extend-shallow "^2.0.1" - posix-character-classes "^0.1.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -expect@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/expect/-/expect-24.9.0.tgz#b75165b4817074fa4a157794f46fe9f1ba15b6ca" - integrity sha512-wvVAx8XIol3Z5m9zvZXiyZOQ+sRJqNTIm6sGjdWlaZIeupQGO3WbYI+15D/AmEwZywL6wtJkbAbJtzkOfBuR0Q== - dependencies: - "@jest/types" "^24.9.0" - ansi-styles "^3.2.0" - jest-get-type "^24.9.0" - jest-matcher-utils "^24.9.0" - jest-message-util "^24.9.0" - jest-regex-util "^24.9.0" - -express@^4.17.1: - version "4.17.1" - resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" - integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g== - dependencies: - accepts "~1.3.7" - array-flatten "1.1.1" - body-parser "1.19.0" - content-disposition "0.5.3" - content-type "~1.0.4" - cookie "0.4.0" - cookie-signature "1.0.6" - debug "2.6.9" - depd "~1.1.2" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "~1.1.2" - fresh "0.5.2" - merge-descriptors "1.0.1" - methods "~1.1.2" - on-finished "~2.3.0" - parseurl "~1.3.3" - path-to-regexp "0.1.7" - proxy-addr "~2.0.5" - qs "6.7.0" - range-parser "~1.2.1" - safe-buffer "5.1.2" - send "0.17.1" - serve-static "1.14.1" - setprototypeof "1.1.1" - statuses "~1.5.0" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" - -ext@^1.1.2: - version "1.4.0" - resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244" - integrity sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A== - dependencies: - type "^2.0.0" - -extend-shallow@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" - integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= - dependencies: - is-extendable "^0.1.0" - -extend-shallow@^3.0.0, extend-shallow@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" - integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= - dependencies: - assign-symbols "^1.0.0" - is-extendable "^1.0.1" - -extend@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" - integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== - -external-editor@^3.0.3: - version "3.1.0" - resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" - integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== - dependencies: - chardet "^0.7.0" - iconv-lite "^0.4.24" - tmp "^0.0.33" - -extglob@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" - integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== - dependencies: - array-unique "^0.3.2" - define-property "^1.0.0" - expand-brackets "^2.1.4" - extend-shallow "^2.0.1" - fragment-cache "^0.2.1" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -extsprintf@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" - integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= - -extsprintf@^1.2.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" - integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= - -fast-deep-equal@^3.1.1: +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-glob@^2.0.2: - version "2.2.7" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.7.tgz#6953857c3afa475fff92ee6015d52da70a4cd39d" - integrity sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw== +fast-equals@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/fast-equals/-/fast-equals-2.0.3.tgz#7039b0a039909f345a2ce53f6202a14e5f392efc" + integrity sha512-0EMw4TTUxsMDpDkCg0rXor2gsg+npVrMIHbEhvD0HZyIhUX6AktC/yasm+qKwfyswd06Qy95ZKk8p2crTo0iPA== + +fast-glob@^3.1.1: + version "3.2.7" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1" + integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q== dependencies: - "@mrmlnc/readdir-enhanced" "^2.2.1" - "@nodelib/fs.stat" "^1.1.2" - glob-parent "^3.1.0" - is-glob "^4.0.0" - merge2 "^1.2.3" - micromatch "^3.1.10" + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== -fast-levenshtein@~2.0.6: +fast-levenshtein@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= -faye-websocket@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4" - integrity sha1-TkkvjQTftviQA1B/btvy1QHnxvQ= - dependencies: - websocket-driver ">=0.5.1" - -faye-websocket@~0.11.1: - version "0.11.3" - resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.3.tgz#5c0e9a8968e8912c286639fde977a8b209f2508e" - integrity sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA== - dependencies: - websocket-driver ">=0.5.1" - -fb-watchman@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.1.tgz#fc84fb39d2709cf3ff6d743706157bb5708a8a85" - integrity sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg== - dependencies: - bser "2.1.1" - -figgy-pudding@^3.5.1: - version "3.5.2" - resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" - integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw== - -figures@^3.0.0, figures@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" - integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== - dependencies: - escape-string-regexp "^1.0.5" +fast-text-encoding@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz#ec02ac8e01ab8a319af182dae2681213cfe9ce53" + integrity sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig== -file-entry-cache@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" - integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== +fastq@^1.6.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.12.0.tgz#ed7b6ab5d62393fb2cc591c853652a5c318bf794" + integrity sha512-VNX0QkHK3RsXVKr9KrlUv/FoTa0NdbYoHHl7uXHv2rzyHSlxjdNAKug2twd9luJxpcyNeAgf5iPPMutJO67Dfg== dependencies: - flat-cache "^2.0.1" + reusify "^1.0.4" -file-loader@4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-4.3.0.tgz#780f040f729b3d18019f20605f723e844b8a58af" - integrity sha512-aKrYPYjF1yG3oX0kWRrqrSMfgftm7oJW5M+m4owoldH5C51C0RkIwB++JbRvEW3IU6/ZG5n8UvEcdgwOt2UOWA== +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== dependencies: - loader-utils "^1.2.3" - schema-utils "^2.5.0" + flat-cache "^3.0.4" file-saver@^1.3.3: version "1.3.8" resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-1.3.8.tgz#e68a30c7cb044e2fb362b428469feb291c2e09d8" integrity sha512-spKHSBQIxxS81N/O21WmuXA2F6wppUCsutpzenOeZzOCCJ5gEfcbqJP983IrpLXzYmXnMUa6J03SubcNPdKrlg== -file-uri-to-path@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" - integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== - -filesize@6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/filesize/-/filesize-6.0.1.tgz#f850b509909c7c86f7e450ea19006c31c2ed3d2f" - integrity sha512-u4AYWPgbI5GBhs6id1KdImZWn5yfyFrrQ8OWZdN7ZMfA8Bf4HcO0BGo9bmUIEV8yrp8I1xVfJ/dn90GtFNNJcg== - -fill-range@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" - integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= +file-selector@^0.1.8: + version "0.1.19" + resolved "https://registry.yarnpkg.com/file-selector/-/file-selector-0.1.19.tgz#8ecc9d069a6f544f2e4a096b64a8052e70ec8abf" + integrity sha512-kCWw3+Aai8Uox+5tHCNgMFaUdgidxvMnLWO6fM5sZ0hA2wlHP5/DHGF0ECe84BiB95qdJbKNEJhWKVDvMN+JDQ== dependencies: - extend-shallow "^2.0.1" - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range "^2.1.0" + tslib "^2.0.1" fill-range@^7.0.1: version "7.0.1" @@ -4873,38 +1906,7 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" -finalhandler@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" - integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== - dependencies: - debug "2.6.9" - encodeurl "~1.0.2" - escape-html "~1.0.3" - on-finished "~2.3.0" - parseurl "~1.3.3" - statuses "~1.5.0" - unpipe "~1.0.0" - -find-cache-dir@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-0.1.1.tgz#c8defae57c8a52a8a784f9e31c57c742e993a0b9" - integrity sha1-yN765XyKUqinhPnjHFfHQumToLk= - dependencies: - commondir "^1.0.1" - mkdirp "^0.5.1" - pkg-dir "^1.0.0" - -find-cache-dir@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" - integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ== - dependencies: - commondir "^1.0.1" - make-dir "^2.0.0" - pkg-dir "^3.0.0" - -find-cache-dir@^3.2.0: +find-cache-dir@3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.1.tgz#89b33fad4a4670daa94f855f7fbe31d6d84fe880" integrity sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ== @@ -4913,22 +1915,6 @@ find-cache-dir@^3.2.0: make-dir "^3.0.2" pkg-dir "^4.1.0" -find-up@4.1.0, find-up@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -find-up@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" - integrity sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8= - dependencies: - path-exists "^2.0.0" - pinkie-promise "^2.0.0" - find-up@^2.0.0, find-up@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" @@ -4936,195 +1922,65 @@ find-up@^2.0.0, find-up@^2.1.0: dependencies: locate-path "^2.0.0" -find-up@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" - integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== - dependencies: - locate-path "^3.0.0" - -find-versions@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/find-versions/-/find-versions-3.2.0.tgz#10297f98030a786829681690545ef659ed1d254e" - integrity sha512-P8WRou2S+oe222TOCHitLy8zj+SIsVJh52VP4lvXkaFVnOFFdoWv1H1Jjvel1aI6NCFOAaeAVm8qrI0odiLcww== +find-up@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== dependencies: - semver-regex "^2.0.0" + locate-path "^5.0.0" + path-exists "^4.0.0" -flat-cache@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" - integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== dependencies: - flatted "^2.0.0" - rimraf "2.6.3" - write "1.0.3" - -flatted@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" - integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== - -flatten@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b" - integrity sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg== + locate-path "^6.0.0" + path-exists "^4.0.0" -flush-write-stream@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8" - integrity sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w== +find-versions@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/find-versions/-/find-versions-4.0.0.tgz#3c57e573bf97769b8cb8df16934b627915da4965" + integrity sha512-wgpWy002tA+wgmO27buH/9KzyEOQnKsG/R0yrcjPT9BOFm0zRBVQbZ95nRGXWMywS8YR5knRbpohio0bcJABxQ== dependencies: - inherits "^2.0.3" - readable-stream "^2.3.6" - -follow-redirects@^1.0.0: - version "1.12.1" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.12.1.tgz#de54a6205311b93d60398ebc01cf7015682312b6" - integrity sha512-tmRv0AVuR7ZyouUHLeNSiO6pqulF7dYa3s19c6t+wz9LD69/uSzdMxJ2S91nTI9U3rt/IldxpzMOFejp6f0hjg== - -for-in@^0.1.3: - version "0.1.8" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.8.tgz#d8773908e31256109952b1fdb9b3fa867d2775e1" - integrity sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE= - -for-in@^1.0.1, for-in@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" - integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= + semver-regex "^3.1.2" -for-own@^0.1.3: - version "0.1.5" - resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce" - integrity sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4= +flat-cache@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" + integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== dependencies: - for-in "^1.0.1" - -forever-agent@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" - integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= + flatted "^3.1.0" + rimraf "^3.0.2" -fork-ts-checker-webpack-plugin@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-3.1.1.tgz#a1642c0d3e65f50c2cc1742e9c0a80f441f86b19" - integrity sha512-DuVkPNrM12jR41KM2e+N+styka0EgLkTnXmNcXdgOM37vtGeY+oCBK/Jx0hzSeEU6memFCtWb4htrHPMDfwwUQ== - dependencies: - babel-code-frame "^6.22.0" - chalk "^2.4.1" - chokidar "^3.3.0" - micromatch "^3.1.10" - minimatch "^3.0.4" - semver "^5.6.0" - tapable "^1.0.0" - worker-rpc "^0.1.0" +flatted@^3.1.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.2.tgz#64bfed5cb68fe3ca78b3eb214ad97b63bedce561" + integrity sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA== -form-data@~2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" - integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== +focus-trap@6.2.2: + version "6.2.2" + resolved "https://registry.yarnpkg.com/focus-trap/-/focus-trap-6.2.2.tgz#0e6f391415b0697c99da932702dedd13084fa131" + integrity sha512-qWovH9+LGoKqREvJaTCzJyO0hphQYGz+ap5Hc4NqXHNhZBdxCi5uBPPcaOUw66fHmzXLVwvETLvFgpwPILqKpg== dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.6" - mime-types "^2.1.12" + tabbable "^5.1.4" -forwarded@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" - integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= +foreach@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" + integrity sha1-C+4AUBiusmDQo6865ljdATbsG5k= fraction.js@^4.0.12: - version "4.0.12" - resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.0.12.tgz#0526d47c65a5fb4854df78bc77f7bec708d7b8c3" - integrity sha512-8Z1K0VTG4hzYY7kA/1sj4/r1/RWLBD3xwReT/RCrUCbzPszjNQCCsy3ktkU/eaEqX3MYa4pY37a52eiBlPMlhA== - -fragment-cache@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" - integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= - dependencies: - map-cache "^0.2.2" - -fresh@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= - -from2@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" - integrity sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8= - dependencies: - inherits "^2.0.1" - readable-stream "^2.0.0" - -fs-extra@^4.0.2: - version "4.0.3" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94" - integrity sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg== - dependencies: - graceful-fs "^4.1.2" - jsonfile "^4.0.0" - universalify "^0.1.0" - -fs-extra@^7.0.0: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" - integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== - dependencies: - graceful-fs "^4.1.2" - jsonfile "^4.0.0" - universalify "^0.1.0" - -fs-extra@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" - integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^4.0.0" - universalify "^0.1.0" - -fs-minipass@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" - integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== - dependencies: - minipass "^3.0.0" - -fs-write-stream-atomic@^1.0.8: - version "1.0.10" - resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9" - integrity sha1-tH31NJPvkR33VzHnCp3tAYnbQMk= - dependencies: - graceful-fs "^4.1.2" - iferr "^0.1.5" - imurmurhash "^0.1.4" - readable-stream "1 || 2" + version "4.1.1" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.1.1.tgz#ac4e520473dae67012d618aab91eda09bcb400ff" + integrity sha512-MHOhvvxHTfRFpF1geTK9czMIZ6xclsEor2wkIGYYq+PxcQqT7vStJqjhe6S1TenZrMZzo+wlqOufBDVepUEgPg== fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= -fsevents@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.2.tgz#4c0a1fb34bc68e543b4b82a9ec392bfbda840805" - integrity sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA== - -fsevents@^1.2.7: - version "1.2.13" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.13.tgz#f325cb0455592428bcf11b383370ef70e3bfcc38" - integrity sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw== - dependencies: - bindings "^1.5.0" - nan "^2.12.1" - -fsevents@~2.1.2: - version "2.1.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" - integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== - -fsevents@~2.3.1: +fsevents@~2.3.1, fsevents@~2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== @@ -5139,76 +1995,50 @@ functional-red-black-tree@^1.0.1: resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= -gensync@^1.0.0-beta.1: - version "1.0.0-beta.1" - resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269" - integrity sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg== - -get-caller-file@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" - integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w== +get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" + integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" -get-caller-file@^2.0.1: - version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== +get-orientation@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/get-orientation/-/get-orientation-1.1.2.tgz#20507928951814f8a91ded0a0e67b29dfab98947" + integrity sha512-/pViTfifW+gBbh/RnlFYHINvELT9Znt+SYyDKAUL6uV6By019AK/s+i9XP4jSwq7lwP38Fd8HVeTxym3+hkwmQ== + dependencies: + stream-parser "^0.3.1" get-own-enumerable-property-symbols@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664" integrity sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g== -get-stream@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" - integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== - dependencies: - pump "^3.0.0" - get-stream@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.1.0.tgz#01203cdc92597f9b909067c3e656cc1f4d3c4dc9" - integrity sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw== + version "5.2.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== dependencies: pump "^3.0.0" -get-value@^2.0.3, get-value@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" - integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= - -getpass@^0.1.1: - version "0.1.7" - resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" - integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= - dependencies: - assert-plus "^1.0.0" - -glob-parent@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" - integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4= - dependencies: - is-glob "^3.1.0" - path-dirname "^1.0.0" - -glob-parent@^5.0.0, glob-parent@~5.1.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" - integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== +glob-parent@^5.1.2, glob-parent@~5.1.0, glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: is-glob "^4.0.1" -glob-to-regexp@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" - integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs= +glob-to-regexp@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" + integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== -glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: - version "7.1.6" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" - integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== +glob@7.1.7, glob@^7.1.3, glob@^7.1.6: + version "7.1.7" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" + integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" @@ -5217,122 +2047,34 @@ glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: once "^1.3.0" path-is-absolute "^1.0.0" -global-modules@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" - integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A== - dependencies: - global-prefix "^3.0.0" - -global-prefix@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-3.0.0.tgz#fc85f73064df69f50421f47f883fe5b913ba9b97" - integrity sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg== - dependencies: - ini "^1.3.5" - kind-of "^6.0.2" - which "^1.3.1" - -globals@^11.1.0: - version "11.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" - integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== - -globals@^12.1.0: - version "12.4.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8" - integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg== - dependencies: - type-fest "^0.8.1" - -globby@8.0.2: - version "8.0.2" - resolved "https://registry.yarnpkg.com/globby/-/globby-8.0.2.tgz#5697619ccd95c5275dbb2d6faa42087c1a941d8d" - integrity sha512-yTzMmKygLp8RUpG1Ymu2VXPSJQZjNAZPD4ywgYEaG7e4tBJeUQBO8OpXrf1RCNcEs5alsoJYPAMiIHP0cmeC7w== - dependencies: - array-union "^1.0.1" - dir-glob "2.0.0" - fast-glob "^2.0.2" - glob "^7.1.2" - ignore "^3.3.5" - pify "^3.0.0" - slash "^1.0.0" - -globby@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" - integrity sha1-9abXDoOV4hyFj7BInWTfAkJNUGw= +globals@^13.6.0, globals@^13.9.0: + version "13.11.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.11.0.tgz#40ef678da117fe7bd2e28f1fab24951bd0255be7" + integrity sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g== dependencies: - array-union "^1.0.1" - glob "^7.0.3" - object-assign "^4.0.1" - pify "^2.0.0" - pinkie-promise "^2.0.0" - -graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2: - version "4.2.4" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" - integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== + type-fest "^0.20.2" -growly@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" - integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= - -gud@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/gud/-/gud-1.0.0.tgz#a489581b17e6a70beca9abe3ae57de7a499852c0" - integrity sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw== - -gzip-size@5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-5.1.1.tgz#cb9bee692f87c0612b232840a873904e4c135274" - integrity sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA== +globby@^11.0.3: + version "11.0.4" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5" + integrity sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg== dependencies: - duplexer "^0.1.1" - pify "^4.0.1" - -handle-thing@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" - integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== - -har-schema@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" - integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= - -har-validator@~5.1.3: - version "5.1.3" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" - integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== - dependencies: - ajv "^6.5.5" - har-schema "^2.0.0" - -harmony-reflect@^1.4.6: - version "1.6.1" - resolved "https://registry.yarnpkg.com/harmony-reflect/-/harmony-reflect-1.6.1.tgz#c108d4f2bb451efef7a37861fdbdae72c9bdefa9" - integrity sha512-WJTeyp0JzGtHcuMsi7rw2VwtkvLa+JyfEKJCFyfcS0+CDkjQ5lHPu7zEhFZP+PDSRrEgXa5Ah0l1MbgbE41XjA== - -has-ansi@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" - integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= - dependencies: - ansi-regex "^2.0.0" + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.1.1" + ignore "^5.1.4" + merge2 "^1.3.0" + slash "^3.0.0" -has-binary2@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-binary2/-/has-binary2-1.0.3.tgz#7776ac627f3ea77250cfc332dab7ddf5e4f5d11d" - integrity sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw== - dependencies: - isarray "2.0.1" +graceful-fs@^4.1.2, graceful-fs@^4.2.4: + version "4.2.8" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" + integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== -has-cors@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/has-cors/-/has-cors-1.1.0.tgz#5e474793f7ea9843d1bb99c23eef49ff126fff39" - integrity sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk= +has-bigints@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" + integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== has-flag@^3.0.0: version "3.0.0" @@ -5344,43 +2086,19 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-symbols@^1.0.0, has-symbols@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" - integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== - -has-value@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" - integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= - dependencies: - get-value "^2.0.3" - has-values "^0.1.4" - isobject "^2.0.0" - -has-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" - integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= - dependencies: - get-value "^2.0.6" - has-values "^1.0.0" - isobject "^3.0.0" - -has-values@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" - integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= +has-symbols@^1.0.1, has-symbols@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" + integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== -has-values@^1.0.0: +has-tostringtag@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" - integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" + integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" + has-symbols "^1.0.2" -has@^1.0.0, has@^1.0.3: +has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== @@ -5404,29 +2122,12 @@ hash.js@^1.0.0, hash.js@^1.0.3: inherits "^2.0.3" minimalistic-assert "^1.0.1" -he@^1.2.0: +he@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== -hex-color-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" - integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== - -history@^4.9.0: - version "4.10.1" - resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3" - integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew== - dependencies: - "@babel/runtime" "^7.1.2" - loose-envify "^1.2.0" - resolve-pathname "^3.0.0" - tiny-invariant "^1.0.2" - tiny-warning "^1.0.0" - value-equal "^1.0.1" - -hmac-drbg@^1.0.0: +hmac-drbg@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= @@ -5435,7 +2136,7 @@ hmac-drbg@^1.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.2: +hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -5443,116 +2144,11 @@ hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.2: react-is "^16.7.0" hosted-git-info@^2.1.4: - version "2.8.8" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" - integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== - -hpack.js@^2.1.6: - version "2.1.6" - resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" - integrity sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI= - dependencies: - inherits "^2.0.1" - obuf "^1.0.0" - readable-stream "^2.0.1" - wbuf "^1.1.0" - -hsl-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/hsl-regex/-/hsl-regex-1.0.0.tgz#d49330c789ed819e276a4c0d272dffa30b18fe6e" - integrity sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4= - -hsla-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/hsla-regex/-/hsla-regex-1.0.0.tgz#c1ce7a3168c8c6614033a4b5f7877f3b225f9c38" - integrity sha1-wc56MWjIxmFAM6S194d/OyJfnDg= - -html-comment-regex@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.2.tgz#97d4688aeb5c81886a364faa0cad1dda14d433a7" - integrity sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ== - -html-encoding-sniffer@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz#e70d84b94da53aa375e11fe3a351be6642ca46f8" - integrity sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw== - dependencies: - whatwg-encoding "^1.0.1" - -html-entities@^1.2.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.3.1.tgz#fb9a1a4b5b14c5daba82d3e34c6ae4fe701a0e44" - integrity sha512-rhE/4Z3hIhzHAUKbW8jVcCyuT5oJCXXqhN/6mXXVCpzTmvJnoH2HL/bt3EZ6p55jbFJBeAe1ZNpL5BugLujxNA== + version "2.8.9" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" + integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== -html-escaper@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" - integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== - -html-minifier-terser@^5.0.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz#922e96f1f3bb60832c2634b79884096389b1f054" - integrity sha512-ZPr5MNObqnV/T9akshPKbVgyOqLmy+Bxo7juKCfTfnjNniTAMdy4hz21YQqoofMBJD2kdREaqPPdThoR78Tgxg== - dependencies: - camel-case "^4.1.1" - clean-css "^4.2.3" - commander "^4.1.1" - he "^1.2.0" - param-case "^3.0.3" - relateurl "^0.2.7" - terser "^4.6.3" - -html-webpack-plugin@4.0.0-beta.11: - version "4.0.0-beta.11" - resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-4.0.0-beta.11.tgz#3059a69144b5aecef97708196ca32f9e68677715" - integrity sha512-4Xzepf0qWxf8CGg7/WQM5qBB2Lc/NFI7MhU59eUDTkuQp3skZczH4UA1d6oQyDEIoMDgERVhRyTdtUPZ5s5HBg== - dependencies: - html-minifier-terser "^5.0.1" - loader-utils "^1.2.3" - lodash "^4.17.15" - pretty-error "^2.1.1" - tapable "^1.1.3" - util.promisify "1.0.0" - -htmlparser2@^3.3.0: - version "3.10.1" - resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" - integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ== - dependencies: - domelementtype "^1.3.1" - domhandler "^2.3.0" - domutils "^1.5.1" - entities "^1.1.1" - inherits "^2.0.1" - readable-stream "^3.1.1" - -http-deceiver@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" - integrity sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc= - -http-errors@1.7.2: - version "1.7.2" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" - integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg== - dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.1" - statuses ">= 1.5.0 < 2" - toidentifier "1.0.0" - -http-errors@~1.6.2: - version "1.6.3" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" - integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0= - dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.0" - statuses ">= 1.4.0 < 2" - -http-errors@~1.7.2: +http-errors@1.7.3: version "1.7.3" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== @@ -5563,40 +2159,7 @@ http-errors@~1.7.2: statuses ">= 1.5.0 < 2" toidentifier "1.0.0" -http-parser-js@>=0.5.1: - version "0.5.2" - resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.2.tgz#da2e31d237b393aae72ace43882dd7e270a8ff77" - integrity sha512-opCO9ASqg5Wy2FNo7A0sxy71yGbbkJJXLdgMK04Tcypw9jr2MgWbyubb0+WdmDmGnFflO7fRbqbaihh/ENDlRQ== - -http-proxy-middleware@0.19.1: - version "0.19.1" - resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz#183c7dc4aa1479150306498c210cdaf96080a43a" - integrity sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q== - dependencies: - http-proxy "^1.17.0" - is-glob "^4.0.0" - lodash "^4.17.11" - micromatch "^3.1.10" - -http-proxy@^1.17.0: - version "1.18.1" - resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" - integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== - dependencies: - eventemitter3 "^4.0.0" - follow-redirects "^1.0.0" - requires-port "^1.0.0" - -http-signature@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" - integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= - dependencies: - assert-plus "^1.0.0" - jsprim "^1.2.2" - sshpk "^1.7.0" - -https-browserify@^1.0.0: +https-browserify@1.0.0, https-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= @@ -5606,106 +2169,71 @@ human-signals@^1.1.1: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== -husky@~4.2.5: - version "4.2.5" - resolved "https://registry.yarnpkg.com/husky/-/husky-4.2.5.tgz#2b4f7622673a71579f901d9885ed448394b5fa36" - integrity sha512-SYZ95AjKcX7goYVZtVZF2i6XiZcHknw50iXvY7b0MiGoj5RwdgRQNEHdb+gPDPCXKlzwrybjFjkL6FOj8uRhZQ== +husky@^4.3.8: + version "4.3.8" + resolved "https://registry.yarnpkg.com/husky/-/husky-4.3.8.tgz#31144060be963fd6850e5cc8f019a1dfe194296d" + integrity sha512-LCqqsB0PzJQ/AlCgfrfzRe3e3+NvmefAdKQhRYpxS4u6clblBoDdzzvHi8fmxKRzvMxPY/1WZWzomPZww0Anow== dependencies: chalk "^4.0.0" ci-info "^2.0.0" compare-versions "^3.6.0" - cosmiconfig "^6.0.0" - find-versions "^3.2.0" + cosmiconfig "^7.0.0" + find-versions "^4.0.0" opencollective-postinstall "^2.0.2" - pkg-dir "^4.2.0" + pkg-dir "^5.0.0" please-upgrade-node "^3.2.0" slash "^3.0.0" which-pm-runs "^1.0.0" -iconv-lite@0.4.24, iconv-lite@^0.4.24: +iconv-lite@0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== dependencies: safer-buffer ">= 2.1.2 < 3" -icss-utils@^4.0.0, icss-utils@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-4.1.1.tgz#21170b53789ee27447c2f47dd683081403f9a467" - integrity sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA== - dependencies: - postcss "^7.0.14" - -identity-obj-proxy@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz#94d2bda96084453ef36fbc5aaec37e0f79f1fc14" - integrity sha1-lNK9qWCERT7zb7xarsN+D3nx/BQ= +iconv-lite@^0.6.2: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== dependencies: - harmony-reflect "^1.4.6" + safer-buffer ">= 2.1.2 < 3.0.0" ieee754@^1.1.4: - version "1.1.13" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" - integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== - -iferr@^0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" - integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE= - -ignore@^3.3.5: - version "3.3.10" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043" - integrity sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug== + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== ignore@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== -immer@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/immer/-/immer-1.10.0.tgz#bad67605ba9c810275d91e1c2a47d4582e98286d" - integrity sha512-O3sR1/opvCDGLEVcvrGTMtLac8GJ5IwZC4puPrLuRj3l7ICKvkmA0vGuU9OW8mV9WIBRnaxp5GJh9IEAaNOoYg== +ignore@^5.1.4: + version "5.1.8" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" + integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== -import-cwd@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9" - integrity sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk= +image-size@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/image-size/-/image-size-1.0.0.tgz#58b31fe4743b1cec0a0ac26f5c914d3c5b2f0750" + integrity sha512-JLJ6OwBfO1KcA+TvJT+v8gbE6iWbj24LyDNFgFEN0lzegn6cC6a/p3NIDaepMsJjQjlUWqIC7wJv8lBFxPNjcw== dependencies: - import-from "^2.1.0" + queue "6.0.2" -import-fresh@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546" - integrity sha1-2BNVwVYS04bGH53dOSLUMEgipUY= - dependencies: - caller-path "^2.0.0" - resolve-from "^3.0.0" +immer@^9.0.6: + version "9.0.6" + resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.6.tgz#7a96bf2674d06c8143e327cbf73539388ddf1a73" + integrity sha512-G95ivKpy+EvVAnAab4fVa4YGYn24J1SpEktnJX7JJ45Bd7xqME/SCplFzYFmTbrkwZbQ4xJK1xMTUYBkN6pWsQ== -import-fresh@^3.0.0, import-fresh@^3.1.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66" - integrity sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ== +import-fresh@^3.0.0, import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== dependencies: parent-module "^1.0.0" resolve-from "^4.0.0" -import-from@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/import-from/-/import-from-2.1.0.tgz#335db7f2a7affd53aaa471d4b8021dee36b7f3b1" - integrity sha1-M1238qev/VOqpHHUuAId7ja387E= - dependencies: - resolve-from "^3.0.0" - -import-local@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d" - integrity sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ== - dependencies: - pkg-dir "^3.0.0" - resolve-cwd "^2.0.0" - imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" @@ -5716,21 +2244,6 @@ indent-string@^4.0.0: resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== -indexes-of@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" - integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc= - -indexof@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" - integrity sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10= - -infer-owner@^1.0.3, infer-owner@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" - integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== - inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -5739,7 +2252,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3, inherits@~2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -5754,138 +2267,39 @@ inherits@2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= -ini@^1.3.5: - version "1.3.5" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" - integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== - -inquirer@7.0.4: - version "7.0.4" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.0.4.tgz#99af5bde47153abca23f5c7fc30db247f39da703" - integrity sha512-Bu5Td5+j11sCkqfqmUTiwv+tWisMtP0L7Q8WrqA2C/BbBhy1YTdFrvjjlrKq8oagA/tLQBski2Gcx/Sqyi2qSQ== - dependencies: - ansi-escapes "^4.2.1" - chalk "^2.4.2" - cli-cursor "^3.1.0" - cli-width "^2.0.0" - external-editor "^3.0.3" - figures "^3.0.0" - lodash "^4.17.15" - mute-stream "0.0.8" - run-async "^2.2.0" - rxjs "^6.5.3" - string-width "^4.1.0" - strip-ansi "^5.1.0" - through "^2.3.6" - -inquirer@^7.0.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.2.0.tgz#63ce99d823090de7eb420e4bb05e6f3449aa389a" - integrity sha512-E0c4rPwr9ByePfNlTIB8z51kK1s2n6jrHuJeEHENl/sbq2G/S1auvibgEwNR4uSyiU+PiYHqSwsgGiXjG8p5ZQ== - dependencies: - ansi-escapes "^4.2.1" - chalk "^3.0.0" - cli-cursor "^3.1.0" - cli-width "^2.0.0" - external-editor "^3.0.3" - figures "^3.0.0" - lodash "^4.17.15" - mute-stream "0.0.8" - run-async "^2.4.0" - rxjs "^6.5.3" - string-width "^4.1.0" - strip-ansi "^6.0.0" - through "^2.3.6" - -internal-ip@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-4.3.0.tgz#845452baad9d2ca3b69c635a137acb9a0dad0907" - integrity sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg== - dependencies: - default-gateway "^4.2.0" - ipaddr.js "^1.9.0" - -internal-slot@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.2.tgz#9c2e9fb3cd8e5e4256c6f45fe310067fcfa378a3" - integrity sha512-2cQNfwhAfJIkU4KZPkDI+Gj5yNNnbqi40W9Gge6dfnk4TocEVm00B3bdiL+JINrbGJil2TeHvM4rETGzk/f/0g== +internal-slot@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" + integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA== dependencies: - es-abstract "^1.17.0-next.1" + get-intrinsic "^1.1.0" has "^1.0.3" - side-channel "^1.0.2" - -invariant@^2.1.0, invariant@^2.2.2, invariant@^2.2.4: - version "2.2.4" - resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" - integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== - dependencies: - loose-envify "^1.0.0" - -invert-kv@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02" - integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA== - -ip-regex@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" - integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk= - -ip@^1.1.0, ip@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" - integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= - -ipaddr.js@1.9.1, ipaddr.js@^1.9.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" - integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + side-channel "^1.0.4" -is-absolute-url@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6" - integrity sha1-UFMN+4T8yap9vnhS6Do3uTufKqY= - -is-absolute-url@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-3.0.3.tgz#96c6a22b6a23929b11ea0afb1836c36ad4a5d698" - integrity sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q== - -is-accessor-descriptor@^0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" - integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= - dependencies: - kind-of "^3.0.2" - -is-accessor-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" - integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== - dependencies: - kind-of "^6.0.0" +internmap@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/internmap/-/internmap-1.0.1.tgz#0017cc8a3b99605f0302f2b198d272e015e5df95" + integrity sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw== is-arguments@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.0.4.tgz#3faf966c7cba0ff437fb31f6250082fcf0448cf3" - integrity sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA== + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" + integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= -is-arrayish@^0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" - integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== - -is-binary-path@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" - integrity sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg= +is-bigint@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" + integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== dependencies: - binary-extensions "^1.0.0" + has-bigints "^1.0.1" is-binary-path@~2.1.0: version "2.1.0" @@ -5894,127 +2308,49 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" -is-buffer@^1.0.2, is-buffer@^1.1.5: - version "1.1.6" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" - integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== - -is-callable@^1.1.4, is-callable@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.0.tgz#83336560b54a38e35e3a2df7afd0454d691468bb" - integrity sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw== - -is-ci@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" - integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== - dependencies: - ci-info "^2.0.0" - -is-color-stop@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-color-stop/-/is-color-stop-1.1.0.tgz#cfff471aee4dd5c9e158598fbe12967b5cdad345" - integrity sha1-z/9HGu5N1cnhWFmPvhKWe1za00U= +is-boolean-object@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" + integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== dependencies: - css-color-names "^0.0.4" - hex-color-regex "^1.1.0" - hsl-regex "^1.0.0" - hsla-regex "^1.0.0" - rgb-regex "^1.0.1" - rgba-regex "^1.0.0" + call-bind "^1.0.2" + has-tostringtag "^1.0.0" -is-data-descriptor@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" - integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= - dependencies: - kind-of "^3.0.2" +is-callable@^1.1.4, is-callable@^1.2.3: + version "1.2.4" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945" + integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w== -is-data-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" - integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== +is-core-module@^2.2.0, is-core-module@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.6.0.tgz#d7553b2526fe59b92ba3e40c8df757ec8a709e19" + integrity sha512-wShG8vs60jKfPWpF2KZRaAtvt3a20OAn7+IJ6hLPECpSABLcKtFKTTI4ZtH5QcBruBHlq+WsdHWyz0BCZW7svQ== dependencies: - kind-of "^6.0.0" + has "^1.0.3" is-date-object@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" - integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== - -is-descriptor@^0.1.0: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" - integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== - dependencies: - is-accessor-descriptor "^0.1.6" - is-data-descriptor "^0.1.4" - kind-of "^5.0.0" - -is-descriptor@^1.0.0, is-descriptor@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" - integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== - dependencies: - is-accessor-descriptor "^1.0.0" - is-data-descriptor "^1.0.0" - kind-of "^6.0.2" - -is-directory@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" - integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE= - -is-docker@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.0.0.tgz#2cb0df0e75e2d064fe1864c37cdeacb7b2dcf25b" - integrity sha512-pJEdRugimx4fBMra5z2/5iRdZ63OhYV0vr0Dwm5+xtW4D1FvRkB8hamMIhnWfyJeDdyr/aa7BDyNbtG38VxgoQ== - -is-extendable@^0.1.0, is-extendable@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" - integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= - -is-extendable@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" - integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" + integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== dependencies: - is-plain-object "^2.0.4" + has-tostringtag "^1.0.0" -is-extglob@^2.1.0, is-extglob@^2.1.1: +is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= -is-fullwidth-code-point@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" - integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= - dependencies: - number-is-nan "^1.0.0" - -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= - is-fullwidth-code-point@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== -is-generator-fn@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" - integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== - -is-glob@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" - integrity sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo= +is-generator-function@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" + integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== dependencies: - is-extglob "^2.1.0" + has-tostringtag "^1.0.0" is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: version "4.0.1" @@ -6023,12 +2359,25 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" -is-number@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" - integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= +is-nan@^1.2.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.3.2.tgz#043a54adea31748b55b6cd4e09aadafa69bd9e1d" + integrity sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w== dependencies: - kind-of "^3.0.2" + call-bind "^1.0.0" + define-properties "^1.1.3" + +is-negative-zero@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" + integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== + +is-number-object@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.6.tgz#6a7aaf838c7f0686a50b4553f7e54a96494e89f0" + integrity sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g== + dependencies: + has-tostringtag "^1.0.0" is-number@^7.0.0: version "7.0.0" @@ -6040,723 +2389,121 @@ is-obj@^1.0.1: resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8= -is-obj@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" - integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== - -is-path-cwd@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" - integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ== - -is-path-in-cwd@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz#bfe2dca26c69f397265a4009963602935a053acb" - integrity sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ== - dependencies: - is-path-inside "^2.1.0" - -is-path-inside@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-2.1.0.tgz#7c9810587d659a40d27bcdb4d5616eab059494b2" - integrity sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg== - dependencies: - path-is-inside "^1.0.2" - -is-plain-obj@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" - integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= - -is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - -is-regex@^1.0.4, is-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.0.tgz#ece38e389e490df0dc21caea2bd596f987f767ff" - integrity sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw== +is-regex@^1.1.3: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== dependencies: - has-symbols "^1.0.1" + call-bind "^1.0.2" + has-tostringtag "^1.0.0" is-regexp@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069" integrity sha1-/S2INUXEa6xaYz57mgnof6LLUGk= -is-resolvable@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" - integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== - -is-root@2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-root/-/is-root-2.1.0.tgz#809e18129cf1129644302a4f8544035d51984a9c" - integrity sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg== - -is-stream@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" - integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= - is-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" - integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== - -is-string@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" - integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ== + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== -is-svg@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-3.0.0.tgz#9321dbd29c212e5ca99c4fa9794c714bcafa2f75" - integrity sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ== +is-string@^1.0.5, is-string@^1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" + integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== dependencies: - html-comment-regex "^1.1.0" + has-tostringtag "^1.0.0" -is-symbol@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" - integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== +is-symbol@^1.0.2, is-symbol@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== dependencies: - has-symbols "^1.0.1" - -is-typedarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= - -is-windows@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" - integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== - -is-wsl@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" - integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= + has-symbols "^1.0.2" -is-wsl@^2.1.1: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" - integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== +is-typed-array@^1.1.3, is-typed-array@^1.1.7: + version "1.1.8" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.8.tgz#cbaa6585dc7db43318bc5b89523ea384a6f65e79" + integrity sha512-HqH41TNZq2fgtGT8WHVFVJhBVGuY3AnP3Q36K8JKXUxSxRgk/d+7NjmwG2vo2mYmXK8UYZKu0qH8bVP5gEisjA== dependencies: - is-docker "^2.0.0" + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + es-abstract "^1.18.5" + foreach "^2.0.5" + has-tostringtag "^1.0.0" -isarray@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" - integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= +is-unicode-supported@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== -isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: +isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= -isarray@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e" - integrity sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4= - isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= -isobject@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" - integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= - dependencies: - isarray "1.0.0" - -isobject@^3.0.0, isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= - -isstream@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" - integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= - -istanbul-lib-coverage@^2.0.2, istanbul-lib-coverage@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz#675f0ab69503fad4b1d849f736baaca803344f49" - integrity sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA== - -istanbul-lib-instrument@^3.0.1, istanbul-lib-instrument@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz#a5f63d91f0bbc0c3e479ef4c5de027335ec6d630" - integrity sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA== - dependencies: - "@babel/generator" "^7.4.0" - "@babel/parser" "^7.4.3" - "@babel/template" "^7.4.0" - "@babel/traverse" "^7.4.3" - "@babel/types" "^7.4.0" - istanbul-lib-coverage "^2.0.5" - semver "^6.0.0" - -istanbul-lib-report@^2.0.4: - version "2.0.8" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz#5a8113cd746d43c4889eba36ab10e7d50c9b4f33" - integrity sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ== - dependencies: - istanbul-lib-coverage "^2.0.5" - make-dir "^2.1.0" - supports-color "^6.1.0" - -istanbul-lib-source-maps@^3.0.1: - version "3.0.6" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz#284997c48211752ec486253da97e3879defba8c8" - integrity sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw== - dependencies: - debug "^4.1.1" - istanbul-lib-coverage "^2.0.5" - make-dir "^2.1.0" - rimraf "^2.6.3" - source-map "^0.6.1" - -istanbul-reports@^2.2.6: - version "2.2.7" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-2.2.7.tgz#5d939f6237d7b48393cc0959eab40cd4fd056931" - integrity sha512-uu1F/L1o5Y6LzPVSVZXNOoD/KXpJue9aeLRd0sM9uMXfZvzomB0WxVamWb5ue8kA2vVWEmW7EG+A5n3f1kqHKg== - dependencies: - html-escaper "^2.0.0" - javascript-natural-sort@^0.7.1: version "0.7.1" resolved "https://registry.yarnpkg.com/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz#f9e2303d4507f6d74355a73664d1440fb5a0ef59" integrity sha1-+eIwPUUH9tdDVac2ZNFED7Wg71k= -jest-changed-files@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-24.9.0.tgz#08d8c15eb79a7fa3fc98269bc14b451ee82f8039" - integrity sha512-6aTWpe2mHF0DhL28WjdkO8LyGjs3zItPET4bMSeXU6T3ub4FPMw+mcOcbdGXQOAfmLcxofD23/5Bl9Z4AkFwqg== - dependencies: - "@jest/types" "^24.9.0" - execa "^1.0.0" - throat "^4.0.0" - -jest-cli@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-24.9.0.tgz#ad2de62d07472d419c6abc301fc432b98b10d2af" - integrity sha512-+VLRKyitT3BWoMeSUIHRxV/2g8y9gw91Jh5z2UmXZzkZKpbC08CSehVxgHUwTpy+HwGcns/tqafQDJW7imYvGg== - dependencies: - "@jest/core" "^24.9.0" - "@jest/test-result" "^24.9.0" - "@jest/types" "^24.9.0" - chalk "^2.0.1" - exit "^0.1.2" - import-local "^2.0.0" - is-ci "^2.0.0" - jest-config "^24.9.0" - jest-util "^24.9.0" - jest-validate "^24.9.0" - prompts "^2.0.1" - realpath-native "^1.1.0" - yargs "^13.3.0" - -jest-config@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-24.9.0.tgz#fb1bbc60c73a46af03590719efa4825e6e4dd1b5" - integrity sha512-RATtQJtVYQrp7fvWg6f5y3pEFj9I+H8sWw4aKxnDZ96mob5i5SD6ZEGWgMLXQ4LE8UurrjbdlLWdUeo+28QpfQ== - dependencies: - "@babel/core" "^7.1.0" - "@jest/test-sequencer" "^24.9.0" - "@jest/types" "^24.9.0" - babel-jest "^24.9.0" - chalk "^2.0.1" - glob "^7.1.1" - jest-environment-jsdom "^24.9.0" - jest-environment-node "^24.9.0" - jest-get-type "^24.9.0" - jest-jasmine2 "^24.9.0" - jest-regex-util "^24.3.0" - jest-resolve "^24.9.0" - jest-util "^24.9.0" - jest-validate "^24.9.0" - micromatch "^3.1.10" - pretty-format "^24.9.0" - realpath-native "^1.1.0" - -jest-diff@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-24.9.0.tgz#931b7d0d5778a1baf7452cb816e325e3724055da" - integrity sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ== - dependencies: - chalk "^2.0.1" - diff-sequences "^24.9.0" - jest-get-type "^24.9.0" - pretty-format "^24.9.0" - -jest-docblock@^24.3.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-24.9.0.tgz#7970201802ba560e1c4092cc25cbedf5af5a8ce2" - integrity sha512-F1DjdpDMJMA1cN6He0FNYNZlo3yYmOtRUnktrT9Q37njYzC5WEaDdmbynIgy0L/IvXvvgsG8OsqhLPXTpfmZAA== - dependencies: - detect-newline "^2.1.0" - -jest-each@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-24.9.0.tgz#eb2da602e2a610898dbc5f1f6df3ba86b55f8b05" - integrity sha512-ONi0R4BvW45cw8s2Lrx8YgbeXL1oCQ/wIDwmsM3CqM/nlblNCPmnC3IPQlMbRFZu3wKdQ2U8BqM6lh3LJ5Bsog== - dependencies: - "@jest/types" "^24.9.0" - chalk "^2.0.1" - jest-get-type "^24.9.0" - jest-util "^24.9.0" - pretty-format "^24.9.0" - -jest-environment-jsdom-fourteen@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/jest-environment-jsdom-fourteen/-/jest-environment-jsdom-fourteen-1.0.1.tgz#4cd0042f58b4ab666950d96532ecb2fc188f96fb" - integrity sha512-DojMX1sY+at5Ep+O9yME34CdidZnO3/zfPh8UW+918C5fIZET5vCjfkegixmsi7AtdYfkr4bPlIzmWnlvQkP7Q== - dependencies: - "@jest/environment" "^24.3.0" - "@jest/fake-timers" "^24.3.0" - "@jest/types" "^24.3.0" - jest-mock "^24.0.0" - jest-util "^24.0.0" - jsdom "^14.1.0" - -jest-environment-jsdom@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-24.9.0.tgz#4b0806c7fc94f95edb369a69cc2778eec2b7375b" - integrity sha512-Zv9FV9NBRzLuALXjvRijO2351DRQeLYXtpD4xNvfoVFw21IOKNhZAEUKcbiEtjTkm2GsJ3boMVgkaR7rN8qetA== - dependencies: - "@jest/environment" "^24.9.0" - "@jest/fake-timers" "^24.9.0" - "@jest/types" "^24.9.0" - jest-mock "^24.9.0" - jest-util "^24.9.0" - jsdom "^11.5.1" - -jest-environment-node@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-24.9.0.tgz#333d2d2796f9687f2aeebf0742b519f33c1cbfd3" - integrity sha512-6d4V2f4nxzIzwendo27Tr0aFm+IXWa0XEUnaH6nU0FMaozxovt+sfRvh4J47wL1OvF83I3SSTu0XK+i4Bqe7uA== - dependencies: - "@jest/environment" "^24.9.0" - "@jest/fake-timers" "^24.9.0" - "@jest/types" "^24.9.0" - jest-mock "^24.9.0" - jest-util "^24.9.0" - -jest-get-type@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.9.0.tgz#1684a0c8a50f2e4901b6644ae861f579eed2ef0e" - integrity sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q== - -jest-haste-map@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-24.9.0.tgz#b38a5d64274934e21fa417ae9a9fbeb77ceaac7d" - integrity sha512-kfVFmsuWui2Sj1Rp1AJ4D9HqJwE4uwTlS/vO+eRUaMmd54BFpli2XhMQnPC2k4cHFVbB2Q2C+jtI1AGLgEnCjQ== - dependencies: - "@jest/types" "^24.9.0" - anymatch "^2.0.0" - fb-watchman "^2.0.0" - graceful-fs "^4.1.15" - invariant "^2.2.4" - jest-serializer "^24.9.0" - jest-util "^24.9.0" - jest-worker "^24.9.0" - micromatch "^3.1.10" - sane "^4.0.3" - walker "^1.0.7" - optionalDependencies: - fsevents "^1.2.7" - -jest-jasmine2@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-24.9.0.tgz#1f7b1bd3242c1774e62acabb3646d96afc3be6a0" - integrity sha512-Cq7vkAgaYKp+PsX+2/JbTarrk0DmNhsEtqBXNwUHkdlbrTBLtMJINADf2mf5FkowNsq8evbPc07/qFO0AdKTzw== - dependencies: - "@babel/traverse" "^7.1.0" - "@jest/environment" "^24.9.0" - "@jest/test-result" "^24.9.0" - "@jest/types" "^24.9.0" - chalk "^2.0.1" - co "^4.6.0" - expect "^24.9.0" - is-generator-fn "^2.0.0" - jest-each "^24.9.0" - jest-matcher-utils "^24.9.0" - jest-message-util "^24.9.0" - jest-runtime "^24.9.0" - jest-snapshot "^24.9.0" - jest-util "^24.9.0" - pretty-format "^24.9.0" - throat "^4.0.0" - -jest-leak-detector@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-24.9.0.tgz#b665dea7c77100c5c4f7dfcb153b65cf07dcf96a" - integrity sha512-tYkFIDsiKTGwb2FG1w8hX9V0aUb2ot8zY/2nFg087dUageonw1zrLMP4W6zsRO59dPkTSKie+D4rhMuP9nRmrA== - dependencies: - jest-get-type "^24.9.0" - pretty-format "^24.9.0" - -jest-matcher-utils@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-24.9.0.tgz#f5b3661d5e628dffe6dd65251dfdae0e87c3a073" - integrity sha512-OZz2IXsu6eaiMAwe67c1T+5tUAtQyQx27/EMEkbFAGiw52tB9em+uGbzpcgYVpA8wl0hlxKPZxrly4CXU/GjHA== - dependencies: - chalk "^2.0.1" - jest-diff "^24.9.0" - jest-get-type "^24.9.0" - pretty-format "^24.9.0" - -jest-message-util@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-24.9.0.tgz#527f54a1e380f5e202a8d1149b0ec872f43119e3" - integrity sha512-oCj8FiZ3U0hTP4aSui87P4L4jC37BtQwUMqk+zk/b11FR19BJDeZsZAvIHutWnmtw7r85UmR3CEWZ0HWU2mAlw== - dependencies: - "@babel/code-frame" "^7.0.0" - "@jest/test-result" "^24.9.0" - "@jest/types" "^24.9.0" - "@types/stack-utils" "^1.0.1" - chalk "^2.0.1" - micromatch "^3.1.10" - slash "^2.0.0" - stack-utils "^1.0.1" - -jest-mock@^24.0.0, jest-mock@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-24.9.0.tgz#c22835541ee379b908673ad51087a2185c13f1c6" - integrity sha512-3BEYN5WbSq9wd+SyLDES7AHnjH9A/ROBwmz7l2y+ol+NtSFO8DYiEBzoO1CeFc9a8DYy10EO4dDFVv/wN3zl1w== - dependencies: - "@jest/types" "^24.9.0" - -jest-pnp-resolver@^1.2.1: - version "1.2.2" - resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" - integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== - -jest-regex-util@^24.3.0, jest-regex-util@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-24.9.0.tgz#c13fb3380bde22bf6575432c493ea8fe37965636" - integrity sha512-05Cmb6CuxaA+Ys6fjr3PhvV3bGQmO+2p2La4hFbU+W5uOc479f7FdLXUWXw4pYMAhhSZIuKHwSXSu6CsSBAXQA== - -jest-resolve-dependencies@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-24.9.0.tgz#ad055198959c4cfba8a4f066c673a3f0786507ab" - integrity sha512-Fm7b6AlWnYhT0BXy4hXpactHIqER7erNgIsIozDXWl5dVm+k8XdGVe1oTg1JyaFnOxarMEbax3wyRJqGP2Pq+g== - dependencies: - "@jest/types" "^24.9.0" - jest-regex-util "^24.3.0" - jest-snapshot "^24.9.0" - -jest-resolve@24.9.0, jest-resolve@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-24.9.0.tgz#dff04c7687af34c4dd7e524892d9cf77e5d17321" - integrity sha512-TaLeLVL1l08YFZAt3zaPtjiVvyy4oSA6CRe+0AFPPVX3Q/VI0giIWWoAvoS5L96vj9Dqxj4fB5p2qrHCmTU/MQ== - dependencies: - "@jest/types" "^24.9.0" - browser-resolve "^1.11.3" - chalk "^2.0.1" - jest-pnp-resolver "^1.2.1" - realpath-native "^1.1.0" - -jest-runner@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-24.9.0.tgz#574fafdbd54455c2b34b4bdf4365a23857fcdf42" - integrity sha512-KksJQyI3/0mhcfspnxxEOBueGrd5E4vV7ADQLT9ESaCzz02WnbdbKWIf5Mkaucoaj7obQckYPVX6JJhgUcoWWg== - dependencies: - "@jest/console" "^24.7.1" - "@jest/environment" "^24.9.0" - "@jest/test-result" "^24.9.0" - "@jest/types" "^24.9.0" - chalk "^2.4.2" - exit "^0.1.2" - graceful-fs "^4.1.15" - jest-config "^24.9.0" - jest-docblock "^24.3.0" - jest-haste-map "^24.9.0" - jest-jasmine2 "^24.9.0" - jest-leak-detector "^24.9.0" - jest-message-util "^24.9.0" - jest-resolve "^24.9.0" - jest-runtime "^24.9.0" - jest-util "^24.9.0" - jest-worker "^24.6.0" - source-map-support "^0.5.6" - throat "^4.0.0" - -jest-runtime@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-24.9.0.tgz#9f14583af6a4f7314a6a9d9f0226e1a781c8e4ac" - integrity sha512-8oNqgnmF3v2J6PVRM2Jfuj8oX3syKmaynlDMMKQ4iyzbQzIG6th5ub/lM2bCMTmoTKM3ykcUYI2Pw9xwNtjMnw== - dependencies: - "@jest/console" "^24.7.1" - "@jest/environment" "^24.9.0" - "@jest/source-map" "^24.3.0" - "@jest/transform" "^24.9.0" - "@jest/types" "^24.9.0" - "@types/yargs" "^13.0.0" - chalk "^2.0.1" - exit "^0.1.2" - glob "^7.1.3" - graceful-fs "^4.1.15" - jest-config "^24.9.0" - jest-haste-map "^24.9.0" - jest-message-util "^24.9.0" - jest-mock "^24.9.0" - jest-regex-util "^24.3.0" - jest-resolve "^24.9.0" - jest-snapshot "^24.9.0" - jest-util "^24.9.0" - jest-validate "^24.9.0" - realpath-native "^1.1.0" - slash "^2.0.0" - strip-bom "^3.0.0" - yargs "^13.3.0" - -jest-serializer@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-24.9.0.tgz#e6d7d7ef96d31e8b9079a714754c5d5c58288e73" - integrity sha512-DxYipDr8OvfrKH3Kel6NdED3OXxjvxXZ1uIY2I9OFbGg+vUkkg7AGvi65qbhbWNPvDckXmzMPbK3u3HaDO49bQ== - -jest-snapshot@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-24.9.0.tgz#ec8e9ca4f2ec0c5c87ae8f925cf97497b0e951ba" - integrity sha512-uI/rszGSs73xCM0l+up7O7a40o90cnrk429LOiK3aeTvfC0HHmldbd81/B7Ix81KSFe1lwkbl7GnBGG4UfuDew== - dependencies: - "@babel/types" "^7.0.0" - "@jest/types" "^24.9.0" - chalk "^2.0.1" - expect "^24.9.0" - jest-diff "^24.9.0" - jest-get-type "^24.9.0" - jest-matcher-utils "^24.9.0" - jest-message-util "^24.9.0" - jest-resolve "^24.9.0" - mkdirp "^0.5.1" - natural-compare "^1.4.0" - pretty-format "^24.9.0" - semver "^6.2.0" - -jest-util@^24.0.0, jest-util@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-24.9.0.tgz#7396814e48536d2e85a37de3e4c431d7cb140162" - integrity sha512-x+cZU8VRmOJxbA1K5oDBdxQmdq0OIdADarLxk0Mq+3XS4jgvhG/oKGWcIDCtPG0HgjxOYvF+ilPJQsAyXfbNOg== - dependencies: - "@jest/console" "^24.9.0" - "@jest/fake-timers" "^24.9.0" - "@jest/source-map" "^24.9.0" - "@jest/test-result" "^24.9.0" - "@jest/types" "^24.9.0" - callsites "^3.0.0" - chalk "^2.0.1" - graceful-fs "^4.1.15" - is-ci "^2.0.0" - mkdirp "^0.5.1" - slash "^2.0.0" - source-map "^0.6.0" - -jest-validate@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-24.9.0.tgz#0775c55360d173cd854e40180756d4ff52def8ab" - integrity sha512-HPIt6C5ACwiqSiwi+OfSSHbK8sG7akG8eATl+IPKaeIjtPOeBUd/g3J7DghugzxrGjI93qS/+RPKe1H6PqvhRQ== - dependencies: - "@jest/types" "^24.9.0" - camelcase "^5.3.1" - chalk "^2.0.1" - jest-get-type "^24.9.0" - leven "^3.1.0" - pretty-format "^24.9.0" - -jest-watch-typeahead@0.4.2: - version "0.4.2" - resolved "https://registry.yarnpkg.com/jest-watch-typeahead/-/jest-watch-typeahead-0.4.2.tgz#e5be959698a7fa2302229a5082c488c3c8780a4a" - integrity sha512-f7VpLebTdaXs81rg/oj4Vg/ObZy2QtGzAmGLNsqUS5G5KtSN68tFcIsbvNODfNyQxU78g7D8x77o3bgfBTR+2Q== - dependencies: - ansi-escapes "^4.2.1" - chalk "^2.4.1" - jest-regex-util "^24.9.0" - jest-watcher "^24.3.0" - slash "^3.0.0" - string-length "^3.1.0" - strip-ansi "^5.0.0" - -jest-watcher@^24.3.0, jest-watcher@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-24.9.0.tgz#4b56e5d1ceff005f5b88e528dc9afc8dd4ed2b3b" - integrity sha512-+/fLOfKPXXYJDYlks62/4R4GoT+GU1tYZed99JSCOsmzkkF7727RqKrjNAxtfO4YpGv11wybgRvCjR73lK2GZw== - dependencies: - "@jest/test-result" "^24.9.0" - "@jest/types" "^24.9.0" - "@types/yargs" "^13.0.0" - ansi-escapes "^3.0.0" - chalk "^2.0.1" - jest-util "^24.9.0" - string-length "^2.0.0" - -jest-worker@^24.6.0, jest-worker@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-24.9.0.tgz#5dbfdb5b2d322e98567898238a9697bcce67b3e5" - integrity sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw== - dependencies: - merge-stream "^2.0.0" - supports-color "^6.1.0" - -jest-worker@^25.1.0: - version "25.5.0" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-25.5.0.tgz#2611d071b79cea0f43ee57a3d118593ac1547db1" - integrity sha512-/dsSmUkIy5EBGfv/IjjqmFxrNAUpBERfGs1oHROyD7yxjG/w+t0GOJDX8O1k32ySmd7+a5IhnJU2qQFcJ4n1vw== +jest-worker@27.0.0-next.5: + version "27.0.0-next.5" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.0.0-next.5.tgz#5985ee29b12a4e191f4aae4bb73b97971d86ec28" + integrity sha512-mk0umAQ5lT+CaOJ+Qp01N6kz48sJG2kr2n1rX0koqKf6FIygQV0qLOdN9SCYID4IVeSigDOcPeGLozdMLYfb5g== dependencies: + "@types/node" "*" merge-stream "^2.0.0" - supports-color "^7.0.0" + supports-color "^8.0.0" -jest@24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest/-/jest-24.9.0.tgz#987d290c05a08b52c56188c1002e368edb007171" - integrity sha512-YvkBL1Zm7d2B1+h5fHEOdyjCG+sGMz4f8D86/0HiqJ6MB4MnDc8FgP5vdWsGnemOQro7lnYo8UakZ3+5A0jxGw== - dependencies: - import-local "^2.0.0" - jest-cli "^24.9.0" +js-sha3@0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" + integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-tokens@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" - integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls= - js-yaml@^3.13.1: - version "3.14.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" - integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A== + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== dependencies: argparse "^1.0.7" esprima "^4.0.0" -jsbn@~0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" - integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= - -jsdom@^11.5.1: - version "11.12.0" - resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-11.12.0.tgz#1a80d40ddd378a1de59656e9e6dc5a3ba8657bc8" - integrity sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw== - dependencies: - abab "^2.0.0" - acorn "^5.5.3" - acorn-globals "^4.1.0" - array-equal "^1.0.0" - cssom ">= 0.3.2 < 0.4.0" - cssstyle "^1.0.0" - data-urls "^1.0.0" - domexception "^1.0.1" - escodegen "^1.9.1" - html-encoding-sniffer "^1.0.2" - left-pad "^1.3.0" - nwsapi "^2.0.7" - parse5 "4.0.0" - pn "^1.1.0" - request "^2.87.0" - request-promise-native "^1.0.5" - sax "^1.2.4" - symbol-tree "^3.2.2" - tough-cookie "^2.3.4" - w3c-hr-time "^1.0.1" - webidl-conversions "^4.0.2" - whatwg-encoding "^1.0.3" - whatwg-mimetype "^2.1.0" - whatwg-url "^6.4.1" - ws "^5.2.0" - xml-name-validator "^3.0.0" - -jsdom@^14.1.0: - version "14.1.0" - resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-14.1.0.tgz#916463b6094956b0a6c1782c94e380cd30e1981b" - integrity sha512-O901mfJSuTdwU2w3Sn+74T+RnDVP+FuV5fH8tcPWyqrseRAb0s5xOtPgCFiPOtLcyK7CLIJwPyD83ZqQWvA5ng== - dependencies: - abab "^2.0.0" - acorn "^6.0.4" - acorn-globals "^4.3.0" - array-equal "^1.0.0" - cssom "^0.3.4" - cssstyle "^1.1.1" - data-urls "^1.1.0" - domexception "^1.0.1" - escodegen "^1.11.0" - html-encoding-sniffer "^1.0.2" - nwsapi "^2.1.3" - parse5 "5.1.0" - pn "^1.1.0" - request "^2.88.0" - request-promise-native "^1.0.5" - saxes "^3.1.9" - symbol-tree "^3.2.2" - tough-cookie "^2.5.0" - w3c-hr-time "^1.0.1" - w3c-xmlserializer "^1.1.2" - webidl-conversions "^4.0.2" - whatwg-encoding "^1.0.5" - whatwg-mimetype "^2.3.0" - whatwg-url "^7.0.0" - ws "^6.1.2" - xml-name-validator "^3.0.0" - -jsesc@^2.5.1: - version "2.5.2" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" - integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== - -jsesc@~0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" - integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= - -json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: +json-parse-better-errors@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + json-schema-traverse@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== -json-schema@0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" - integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= -json-stable-stringify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" - integrity sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8= - dependencies: - jsonify "~0.0.0" - -json-stringify-safe@~5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" - integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= - -json3@^3.3.2: - version "3.3.3" - resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.3.tgz#7fc10e375fc5ae42c4705a5cc0aa6f62be305b81" - integrity sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA== - json5@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" @@ -6764,163 +2511,58 @@ json5@^1.0.1: dependencies: minimist "^1.2.0" -json5@^2.1.2: - version "2.1.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43" - integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA== - dependencies: - minimist "^1.2.5" - -jsonfile@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" - integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= - optionalDependencies: - graceful-fs "^4.1.6" - -jsonify@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" - integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM= - -jsprim@^1.2.2: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" - integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= - dependencies: - assert-plus "1.0.0" - extsprintf "1.3.0" - json-schema "0.2.3" - verror "1.10.0" - -jsx-ast-utils@^2.2.1, jsx-ast-utils@^2.2.3: - version "2.4.1" - resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.4.1.tgz#1114a4c1209481db06c690c2b4f488cc665f657e" - integrity sha512-z1xSldJ6imESSzOjd3NNkieVJKRlKYSOtMG8SFyCj2FIrvSaSuli/WjpBkEzCBoR9bYYYFgqJw61Xhu7Lcgk+w== - dependencies: - array-includes "^3.1.1" - object.assign "^4.1.0" - -just-reduce-object@^1.0.3: - version "1.1.0" - resolved "https://registry.yarnpkg.com/just-reduce-object/-/just-reduce-object-1.1.0.tgz#d29d172264f8511c74462de30d72d5838b6967e6" - integrity sha512-nGyg7N9FEZsyrGQNilkyVLxKPsf96iel5v0DrozQ19ML+96HntyS/53bOP68iK/kZUGvsL3FKygV8nQYYhgTFw== - -killable@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892" - integrity sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg== - -kind-of@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-2.0.1.tgz#018ec7a4ce7e3a86cb9141be519d24c8faa981b5" - integrity sha1-AY7HpM5+OobLkUG+UZ0kyPqpgbU= - dependencies: - is-buffer "^1.0.2" - -kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" - integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= - dependencies: - is-buffer "^1.1.5" - -kind-of@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" - integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= - dependencies: - is-buffer "^1.1.5" - -kind-of@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" - integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== - -kind-of@^6.0.0, kind-of@^6.0.2: - version "6.0.3" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" - integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== - -kleur@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" - integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== - -konva@~6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/konva/-/konva-6.0.0.tgz#9b3d13a4622f353c4ce736fbf1fa4b6483240649" - integrity sha512-YTwmtz3KzbzdC0KDRHWLzuk0KXB4NUdaQqytrxacXE1C39V6wCk7Nnu0wgq+GdGbG6m8A1qiEU9TSJ19qdIzDw== - -last-call-webpack-plugin@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/last-call-webpack-plugin/-/last-call-webpack-plugin-3.0.0.tgz#9742df0e10e3cf46e5c0381c2de90d3a7a2d7555" - integrity sha512-7KI2l2GIZa9p2spzPIVZBYyNKkN+e/SQPpnjlTiPhdbDW3F86tdKKELxKpzJ5sgU19wQWsACULZmpTPYHeWO5w== - dependencies: - lodash "^4.17.5" - webpack-sources "^1.1.0" - -lazy-cache@^0.2.3: - version "0.2.7" - resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-0.2.7.tgz#7feddf2dcb6edb77d11ef1d117ab5ffdf0ab1b65" - integrity sha1-f+3fLctu23fRHvHRF6tf/fCrG2U= - -lazy-cache@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" - integrity sha1-odePw6UEdMuAhF07O24dpJpEbo4= - -lcid@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/lcid/-/lcid-2.0.0.tgz#6ef5d2df60e52f82eb228a4c373e8d1f397253cf" - integrity sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA== +"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz#41108d2cec408c3453c1bbe8a4aae9e1e2bd8f82" + integrity sha512-EIsmt3O3ljsU6sot/J4E1zDRxfBNrhjyf/OKjlydwgEimQuznlM4Wv7U+ueONJMyEn1WRE0K8dhi3dVAXYT24Q== dependencies: - invert-kv "^2.0.0" + array-includes "^3.1.2" + object.assign "^4.1.2" -left-pad@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.3.0.tgz#5b8a3a7765dfe001261dde915589e782f8c94d1e" - integrity sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA== +konva@^7.2.5: + version "7.2.5" + resolved "https://registry.yarnpkg.com/konva/-/konva-7.2.5.tgz#9b4ac3a353e6be66e3e69123bf2a0cbc61efeb26" + integrity sha512-yk/li8rUF+09QNlOdkwbEId+QvfATMe/aMGVouWW1oFoUVTYWHsQuIAE6lWy11DK8mLJEJijkNAXC5K+NVlMew== -leven@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" - integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== +language-subtag-registry@~0.3.2: + version "0.3.21" + resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz#04ac218bea46f04cb039084602c6da9e788dd45a" + integrity sha512-L0IqwlIXjilBVVYKFT37X9Ih11Um5NEl9cbJIuU/SwP/zEEAbBPOnEeeuxVMf45ydWQRDQN3Nqc96OgbH1K+Pg== -levenary@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/levenary/-/levenary-1.1.1.tgz#842a9ee98d2075aa7faeedbe32679e9205f46f77" - integrity sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ== +language-tags@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/language-tags/-/language-tags-1.0.5.tgz#d321dbc4da30ba8bf3024e040fa5c14661f9193a" + integrity sha1-0yHbxNowuovzAk4ED6XBRmH5GTo= dependencies: - leven "^3.1.0" + language-subtag-registry "~0.3.2" -levn@^0.3.0, levn@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" - integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== dependencies: - prelude-ls "~1.1.2" - type-check "~0.3.2" + prelude-ls "^1.2.1" + type-check "~0.4.0" lines-and-columns@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= -lint-staged@~10.2.2: - version "10.2.11" - resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.2.11.tgz#713c80877f2dc8b609b05bc59020234e766c9720" - integrity sha512-LRRrSogzbixYaZItE2APaS4l2eJMjjf5MbclRZpLJtcQJShcvUzKXsNeZgsLIZ0H0+fg2tL4B59fU9wHIHtFIA== +lint-staged@^10.5.4: + version "10.5.4" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.5.4.tgz#cd153b5f0987d2371fc1d2847a409a2fe705b665" + integrity sha512-EechC3DdFic/TdOPgj/RB3FicqE6932LTHCUm0Y2fsD9KGlLB+RwJl2q1IYBIvEsKzDOgn0D4gll+YxG5RsrKg== dependencies: - chalk "^4.0.0" - cli-truncate "2.1.0" - commander "^5.1.0" - cosmiconfig "^6.0.0" - debug "^4.1.1" + chalk "^4.1.0" + cli-truncate "^2.1.0" + commander "^6.2.0" + cosmiconfig "^7.0.0" + debug "^4.2.0" dedent "^0.7.0" - enquirer "^2.3.5" - execa "^4.0.1" - listr2 "^2.1.0" + enquirer "^2.3.6" + execa "^4.1.0" + listr2 "^3.2.2" log-symbols "^4.0.0" micromatch "^4.0.2" normalize-path "^3.0.0" @@ -6928,29 +2570,18 @@ lint-staged@~10.2.2: string-argv "0.3.1" stringify-object "^3.3.0" -listr2@^2.1.0: - version "2.1.8" - resolved "https://registry.yarnpkg.com/listr2/-/listr2-2.1.8.tgz#8af7ebc70cdbe866ddbb6c80909142bd45758f1f" - integrity sha512-Op+hheiChfAphkJ5qUxZtHgyjlX9iNnAeFS/S134xw7mVSg0YVrQo1IY4/K+ElY6XgOPg2Ij4z07urUXR+YEew== +listr2@^3.2.2: + version "3.11.1" + resolved "https://registry.yarnpkg.com/listr2/-/listr2-3.11.1.tgz#a9bab5cd5874fd3cb7827118dbea6fedefbcb43f" + integrity sha512-ZXQvQfmH9iWLlb4n3hh31yicXDxlzB0pE7MM1zu6kgbVL4ivEsO4H8IPh4E682sC8RjnYO9anose+zT52rrpyg== dependencies: - chalk "^4.0.0" cli-truncate "^2.1.0" - figures "^3.2.0" - indent-string "^4.0.0" + colorette "^1.2.2" log-update "^4.0.0" p-map "^4.0.0" - rxjs "^6.5.5" + rxjs "^6.6.7" through "^2.3.8" - -load-json-file@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" - integrity sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg= - dependencies: - graceful-fs "^4.1.2" - parse-json "^2.2.0" - pify "^2.0.0" - strip-bom "^3.0.0" + wrap-ansi "^7.0.0" load-json-file@^4.0.0: version "4.0.0" @@ -6962,19 +2593,6 @@ load-json-file@^4.0.0: pify "^3.0.0" strip-bom "^3.0.0" -loader-fs-cache@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/loader-fs-cache/-/loader-fs-cache-1.0.3.tgz#f08657646d607078be2f0a032f8bd69dd6f277d9" - integrity sha512-ldcgZpjNJj71n+2Mf6yetz+c9bM4xpKtNds4LbqXzU/PTdeAX0g3ytnU1AJMEcTk2Lex4Smpe3Q/eCTsvUBxbA== - dependencies: - find-cache-dir "^0.1.1" - mkdirp "^0.5.1" - -loader-runner@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" - integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw== - loader-utils@1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7" @@ -6984,15 +2602,6 @@ loader-utils@1.2.3: emojis-list "^2.0.0" json5 "^1.0.1" -loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" - integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== - dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^1.0.1" - locate-path@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" @@ -7001,14 +2610,6 @@ locate-path@^2.0.0: p-locate "^2.0.0" path-exists "^3.0.0" -locate-path@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" - integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== - dependencies: - p-locate "^3.0.0" - path-exists "^3.0.0" - locate-path@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" @@ -7016,67 +2617,55 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" -lodash._reinterpolate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" - integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.clonedeep@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" + integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= -lodash.memoize@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" - integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== lodash.sortby@^4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= -lodash.template@^4.4.0, lodash.template@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab" - integrity sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A== - dependencies: - lodash._reinterpolate "^3.0.0" - lodash.templatesettings "^4.0.0" - -lodash.templatesettings@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz#e481310f049d3cf6d47e912ad09313b154f0fb33" - integrity sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ== - dependencies: - lodash._reinterpolate "^3.0.0" - lodash.throttle@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" integrity sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ= -lodash.uniq@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" - integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= - -"lodash@>=3.5 <5", lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.5: - version "4.17.15" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" - integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== +lodash.truncate@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" + integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM= -lodash@~4.17.4: - version "4.17.19" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" - integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== +lodash@>=4.17.21, lodash@^4.17.19: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== log-symbols@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920" - integrity sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA== + version "4.1.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== dependencies: - chalk "^4.0.0" + chalk "^4.1.0" + is-unicode-supported "^0.1.0" log-update@^4.0.0: version "4.0.0" @@ -7088,39 +2677,19 @@ log-update@^4.0.0: slice-ansi "^4.0.0" wrap-ansi "^6.2.0" -loglevel@^1.6.6: - version "1.6.8" - resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.8.tgz#8a25fb75d092230ecd4457270d80b54e28011171" - integrity sha512-bsU7+gc9AJ2SqpzxwU3+1fedl8zAntbtC5XYlt3s2j1hJcn2PsXSmgN8TaLG/J1/2mod4+cE/3vNL70/c1RNCA== - -loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0: +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== dependencies: js-tokens "^3.0.0 || ^4.0.0" -lower-case@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.1.tgz#39eeb36e396115cc05e29422eaea9e692c9408c7" - integrity sha512-LiWgfDLLb1dwbFQZsSglpRj+1ctGnayXz3Uv0/WO8n558JycT5fg6zkNcnW0G68Nn0aEldTFeEfmjCfmqry/rQ== - dependencies: - tslib "^1.10.0" - -lru-cache@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" - integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== - dependencies: - yallist "^3.0.2" - -make-dir@^2.0.0, make-dir@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" - integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== dependencies: - pify "^4.0.1" - semver "^5.6.0" + yallist "^4.0.0" make-dir@^3.0.2: version "3.1.0" @@ -7129,49 +2698,21 @@ make-dir@^3.0.2: dependencies: semver "^6.0.0" -makeerror@1.0.x: - version "1.0.11" - resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c" - integrity sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw= - dependencies: - tmpl "1.0.x" - -mamacro@^0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/mamacro/-/mamacro-0.0.3.tgz#ad2c9576197c9f1abf308d0787865bd975a3f3e4" - integrity sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA== - -map-age-cleaner@^0.1.1: - version "0.1.3" - resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a" - integrity sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w== - dependencies: - p-defer "^1.0.0" - -map-cache@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" - integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= - -map-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" - integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= +match-sorter@^6.0.2: + version "6.3.0" + resolved "https://registry.yarnpkg.com/match-sorter/-/match-sorter-6.3.0.tgz#454a1b31ed218cddbce6231a0ecb5fdc549fed01" + integrity sha512-efYOf/wUpNb8FgNY+cOD2EIJI1S5I7YPKsw0LBp7wqPh5pmMS6i/wr3ZWwfwrAw1NvqTA2KUReVRWDX84lUcOQ== dependencies: - object-visit "^1.0.0" + "@babel/runtime" "^7.12.5" + remove-accents "0.4.2" -math-expression-evaluator@^1.2.14: - version "1.2.22" - resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.22.tgz#c14dcb3d8b4d150e5dcea9c68c8dad80309b0d5e" - integrity sha512-L0j0tFVZBQQLeEjmWOvDLoRciIY8gQGWahvkztXUal8jH8R5Rlqo9GCvgqvXcy9LQhEWdQCVvzqAbxgYNt4blQ== - -mathjs@~7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/mathjs/-/mathjs-7.1.0.tgz#83226e336b8b258b046a139865373e667db94afb" - integrity sha512-Km6PO2UR+COs5mru5auKQKi84GKBryuL5JDdKeAxAi0QV8mH/qwpZKLnzrycxBacQ/X/4Z4Kn+gtYc5gEeWsDQ== +mathjs@^7.6.0: + version "7.6.0" + resolved "https://registry.yarnpkg.com/mathjs/-/mathjs-7.6.0.tgz#f0b7579e0756b13422995d0c4f29bd17d65d4dcc" + integrity sha512-abywR28hUpKF4at5jE8Ys+Kigk40eKMT5mcBLD0/dtsqjfOLbtzd3WjlRqIopNo7oQ6FME51qph6lb8h/bhpUg== dependencies: complex.js "^2.0.11" - decimal.js "^10.2.0" + decimal.js "^10.2.1" escape-latex "^1.2.0" fraction.js "^4.0.12" javascript-natural-sort "^0.7.1" @@ -7188,106 +2729,28 @@ md5.js@^1.3.4: inherits "^2.0.1" safe-buffer "^5.1.2" -mdn-data@2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b" - integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA== - -mdn-data@2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.6.tgz#852dc60fcaa5daa2e8cf6c9189c440ed3e042978" - integrity sha512-rQvjv71olwNHgiTbfPZFkJtjNMciWgswYeciZhtvWLO8bmX3TnhyA62I6sTWOyZssWHJJjY6/KiWwqQsWWsqOA== - -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= - -mem@^4.0.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/mem/-/mem-4.3.0.tgz#461af497bc4ae09608cdb2e60eefb69bff744178" - integrity sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w== - dependencies: - map-age-cleaner "^0.1.1" - mimic-fn "^2.0.0" - p-is-promise "^2.0.0" - -memory-fs@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" - integrity sha1-OpoguEYlI+RHz7x+i7gO1me/xVI= - dependencies: - errno "^0.1.3" - readable-stream "^2.0.1" - -memory-fs@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.5.0.tgz#324c01288b88652966d161db77838720845a8e3c" - integrity sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA== - dependencies: - errno "^0.1.3" - readable-stream "^2.0.1" - -merge-deep@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/merge-deep/-/merge-deep-3.0.2.tgz#f39fa100a4f1bd34ff29f7d2bf4508fbb8d83ad2" - integrity sha512-T7qC8kg4Zoti1cFd8Cr0M+qaZfOwjlPDEdZIIPPB2JZctjaPM4fX+i7HOId69tAti2fvO6X5ldfYUONDODsrkA== - dependencies: - arr-union "^3.1.0" - clone-deep "^0.2.4" - kind-of "^3.0.2" - -merge-descriptors@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= - merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== -merge2@^1.2.3: +merge2@^1.3.0: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== -methods@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= - -microevent.ts@~0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/microevent.ts/-/microevent.ts-0.1.1.tgz#70b09b83f43df5172d0205a63025bce0f7357fa0" - integrity sha512-jo1OfR4TaEwd5HOrt5+tAZ9mqT4jmpNAusXtyfNzqVm9uiSYFZlKM1wYL4oU7azZW/PxQW53wM0S6OR1JHNa2g== - -micromatch@^3.1.10, micromatch@^3.1.4: - version "3.1.10" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" - integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - braces "^2.3.1" - define-property "^2.0.2" - extend-shallow "^3.0.2" - extglob "^2.0.4" - fragment-cache "^0.2.1" - kind-of "^6.0.2" - nanomatch "^1.2.9" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.2" - -micromatch@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" - integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== +micromatch@^4.0.2, micromatch@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" + integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== dependencies: braces "^3.0.1" - picomatch "^2.0.5" + picomatch "^2.2.3" + +microseconds@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/microseconds/-/microseconds-0.2.0.tgz#233b25f50c62a65d861f978a4a4f8ec18797dc39" + integrity sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA== miller-rabin@^4.0.0: version "4.0.1" @@ -7297,249 +2760,152 @@ miller-rabin@^4.0.0: bn.js "^4.0.0" brorand "^1.0.1" -mime-db@1.44.0, "mime-db@>= 1.43.0 < 2": - version "1.44.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" - integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== - -mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24: - version "2.1.27" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" - integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== - dependencies: - mime-db "1.44.0" - -mime@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" - integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== - -mime@^2.4.4: - version "2.4.6" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.6.tgz#e5b407c90db442f2beb5b162373d07b69affa4d1" - integrity sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA== - -mimic-fn@^2.0.0, mimic-fn@^2.1.0: +mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -mini-create-react-context@^0.3.0: - version "0.3.3" - resolved "https://registry.yarnpkg.com/mini-create-react-context/-/mini-create-react-context-0.3.3.tgz#b1b2bc6604d3a6c5d9752bad7692615410ebb38e" - integrity sha512-TtF6hZE59SGmS4U8529qB+jJFeW6asTLDIpPgvPLSCsooAwJS7QprHIFTqv9/Qh3NdLwQxFYgiHX5lqb6jqzPA== - dependencies: - "@babel/runtime" "^7.12.1" - tiny-warning "^1.0.3" - -mini-css-extract-plugin@0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.9.0.tgz#47f2cf07aa165ab35733b1fc97d4c46c0564339e" - integrity sha512-lp3GeY7ygcgAmVIcRPBVhIkf8Us7FZjA+ILpal44qLdSu11wmjKQ3d9k15lfD7pO4esu9eUIAW7qiYIBppv40A== - dependencies: - loader-utils "^1.1.0" - normalize-url "1.9.1" - schema-utils "^1.0.0" - webpack-sources "^1.1.0" - minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== -minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: +minimalistic-crypto-utils@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= -minimatch@3.0.4, minimatch@^3.0.4: +minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== dependencies: brace-expansion "^1.1.7" -minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5: +minimist@^1.2.0: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== -minipass-collect@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" - integrity sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA== - dependencies: - minipass "^3.0.0" - -minipass-flush@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373" - integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw== - dependencies: - minipass "^3.0.0" - -minipass-pipeline@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.3.tgz#55f7839307d74859d6e8ada9c3ebe72cec216a34" - integrity sha512-cFOknTvng5vqnwOpDsZTWhNll6Jf8o2x+/diplafmxpuIymAjzoOolZG0VvQf3V2HgqzJNhnuKHYp2BqDgz8IQ== - dependencies: - minipass "^3.0.0" - -minipass@^3.0.0, minipass@^3.1.1: - version "3.1.3" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.3.tgz#7d42ff1f39635482e15f9cdb53184deebd5815fd" - integrity sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg== - dependencies: - yallist "^4.0.0" - -mississippi@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022" - integrity sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA== - dependencies: - concat-stream "^1.5.0" - duplexify "^3.4.2" - end-of-stream "^1.1.0" - flush-write-stream "^1.0.0" - from2 "^2.1.0" - parallel-transform "^1.1.0" - pump "^3.0.0" - pumpify "^1.3.3" - stream-each "^1.1.0" - through2 "^2.0.0" - -mixin-deep@^1.2.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" - integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== - dependencies: - for-in "^1.0.2" - is-extendable "^1.0.1" - -mixin-object@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/mixin-object/-/mixin-object-2.0.1.tgz#4fb949441dab182540f1fe035ba60e1947a5e57e" - integrity sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4= - dependencies: - for-in "^0.1.3" - is-extendable "^0.1.1" - -mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@~0.5.1: - version "0.5.5" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" - integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== - dependencies: - minimist "^1.2.5" - -move-concurrently@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" - integrity sha1-viwAX9oy4LKa8fBdfEszIUxwH5I= - dependencies: - aproba "^1.1.1" - copy-concurrently "^1.0.0" - fs-write-stream-atomic "^1.0.8" - mkdirp "^0.5.1" - rimraf "^2.5.4" - run-queue "^1.0.3" - ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= -ms@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" - integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== - -ms@^2.1.1: +ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -multicast-dns-service-types@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901" - integrity sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE= - -multicast-dns@^6.0.1: - version "6.2.3" - resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-6.2.3.tgz#a0ec7bd9055c4282f790c3c82f4e28db3b31b229" - integrity sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g== - dependencies: - dns-packet "^1.3.1" - thunky "^1.0.2" - -mute-stream@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" - integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== - -nan@^2.12.1: - version "2.14.1" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01" - integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw== - -nanomatch@^1.2.9: - version "1.2.13" - resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" - integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - define-property "^2.0.2" - extend-shallow "^3.0.2" - fragment-cache "^0.2.1" - is-windows "^1.0.2" - kind-of "^6.0.2" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" +ms@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +nano-time@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/nano-time/-/nano-time-1.0.0.tgz#b0554f69ad89e22d0907f7a12b0993a5d96137ef" + integrity sha1-sFVPaa2J4i0JB/ehKwmTpdlhN+8= + dependencies: + big-integer "^1.6.16" + +nanoid@^3.1.23: + version "3.1.25" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.25.tgz#09ca32747c0e543f0e1814b7d3793477f9c8e152" + integrity sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q== + +native-url@0.3.4: + version "0.3.4" + resolved "https://registry.yarnpkg.com/native-url/-/native-url-0.3.4.tgz#29c943172aed86c63cee62c8c04db7f5756661f8" + integrity sha512-6iM8R99ze45ivyH8vybJ7X0yekIcPf5GgLV5K0ENCbmRcaRIDoj37BC8iLEmaaBfqqb8enuZ5p0uhY+lVAbAcA== + dependencies: + querystring "^0.2.0" natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= -negotiator@0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" - integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== +next-transpile-modules@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/next-transpile-modules/-/next-transpile-modules-8.0.0.tgz#56375cdc25ae5d23a834195f277fc2737b26cb97" + integrity sha512-Q2f2yB0zMJ8KJbIYAeZoIxG6cSfVk813zr6B5HzsLMBVcJ3FaF8lKr7WG66n0KlHCwjLSmf/6EkgI6QQVWHrDw== + dependencies: + enhanced-resolve "^5.7.0" + escalade "^3.1.1" + +next@^11.1.2: + version "11.1.2" + resolved "https://registry.yarnpkg.com/next/-/next-11.1.2.tgz#527475787a9a362f1bc916962b0c0655cc05bc91" + integrity sha512-azEYL0L+wFjv8lstLru3bgvrzPvK0P7/bz6B/4EJ9sYkXeW8r5Bjh78D/Ol7VOg0EIPz0CXoe72hzAlSAXo9hw== + dependencies: + "@babel/runtime" "7.15.3" + "@hapi/accept" "5.0.2" + "@next/env" "11.1.2" + "@next/polyfill-module" "11.1.2" + "@next/react-dev-overlay" "11.1.2" + "@next/react-refresh-utils" "11.1.2" + "@node-rs/helper" "1.2.1" + assert "2.0.0" + ast-types "0.13.2" + browserify-zlib "0.2.0" + browserslist "4.16.6" + buffer "5.6.0" + caniuse-lite "^1.0.30001228" + chalk "2.4.2" + chokidar "3.5.1" + constants-browserify "1.0.0" + crypto-browserify "3.12.0" + cssnano-simple "3.0.0" + domain-browser "4.19.0" + encoding "0.1.13" + etag "1.8.1" + find-cache-dir "3.3.1" + get-orientation "1.1.2" + https-browserify "1.0.0" + image-size "1.0.0" + jest-worker "27.0.0-next.5" + native-url "0.3.4" + node-fetch "2.6.1" + node-html-parser "1.4.9" + node-libs-browser "^2.2.1" + os-browserify "0.3.0" + p-limit "3.1.0" + path-browserify "1.0.1" + pnp-webpack-plugin "1.6.4" + postcss "8.2.15" + process "0.11.10" + querystring-es3 "0.2.1" + raw-body "2.4.1" + react-is "17.0.2" + react-refresh "0.8.3" + stream-browserify "3.0.0" + stream-http "3.1.1" + string_decoder "1.3.0" + styled-jsx "4.0.1" + timers-browserify "2.0.12" + tty-browserify "0.0.1" + use-subscription "1.5.1" + util "0.12.4" + vm-browserify "1.1.2" + watchpack "2.1.1" + optionalDependencies: + "@next/swc-darwin-arm64" "11.1.2" + "@next/swc-darwin-x64" "11.1.2" + "@next/swc-linux-x64-gnu" "11.1.2" + "@next/swc-win32-x64-msvc" "11.1.2" -neo-async@^2.5.0, neo-async@^2.6.1: +node-fetch@2.6.1: version "2.6.1" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c" - integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw== - -next-tick@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" - integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= - -nice-try@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" - integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" + integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== -no-case@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.3.tgz#c21b434c1ffe48b39087e86cfb4d2582e9df18f8" - integrity sha512-ehY/mVQCf9BL0gKfsJBvFJen+1V//U+0HQMPrWct40ixE4jnv0bfvxDbWtAHL9EcaPEOJHVVYKoQn1TlZUB8Tw== +node-html-parser@1.4.9: + version "1.4.9" + resolved "https://registry.yarnpkg.com/node-html-parser/-/node-html-parser-1.4.9.tgz#3c8f6cac46479fae5800725edb532e9ae8fd816c" + integrity sha512-UVcirFD1Bn0O+TSmloHeHqZZCxHjvtIeGdVdGMhyZ8/PWlEiZaZ5iJzR189yKZr8p0FXN58BUeC7RHRkf/KYGw== dependencies: - lower-case "^2.0.1" - tslib "^1.10.0" - -node-forge@0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.9.0.tgz#d624050edbb44874adca12bb9a52ec63cb782579" - integrity sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ== - -node-int64@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" - integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs= + he "1.2.0" node-libs-browser@^2.2.1: version "2.2.1" @@ -7570,26 +2936,10 @@ node-libs-browser@^2.2.1: util "^0.11.0" vm-browserify "^1.0.1" -node-modules-regexp@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" - integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA= - -node-notifier@^5.4.2: - version "5.4.3" - resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.4.3.tgz#cb72daf94c93904098e28b9c590fd866e464bd50" - integrity sha512-M4UBGcs4jeOK9CjTsYwkvH6/MzuUmGCyTW+kCY7uO+1ZVr0+FHGdPdIf5CCLqAaxnRrWidyoQlNkMIIVwbKB8Q== - dependencies: - growly "^1.3.0" - is-wsl "^1.1.0" - semver "^5.5.0" - shellwords "^0.1.1" - which "^1.3.0" - -node-releases@^1.1.52, node-releases@^1.1.58: - version "1.1.58" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.58.tgz#8ee20eef30fa60e52755fcc0942def5a734fe935" - integrity sha512-NxBudgVKiRh/2aPWMgPR7bPTX0VPmGx5QBwCtdHitnqFE5/O8DeBXuIMH1nwNnw/aMo6AjOrpsHzfY3UbUJ7yg== +node-releases@^1.1.71: + version "1.1.75" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.75.tgz#6dd8c876b9897a1b8e5a02de26afa79bb54ebbfe" + integrity sha512-Qe5OUajvqrqDSy6wrWFmMwfJ0jVgwiw4T3KqmbTcZ62qW0gQkheXYhcFM1+lOVcGUoRxcEcfyvFMAnDgaF1VWw== normalize-package-data@^2.3.2: version "2.5.0" @@ -7601,44 +2951,15 @@ normalize-package-data@^2.3.2: semver "2 || 3 || 4 || 5" validate-npm-package-license "^3.0.1" -normalize-path@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" - integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= - dependencies: - remove-trailing-separator "^1.0.1" - normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -normalize-range@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" - integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI= - -normalize-url@1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c" - integrity sha1-LMDWazHqIwNkWENuNiDYWVTGbDw= - dependencies: - object-assign "^4.0.1" - prepend-http "^1.0.0" - query-string "^4.1.0" - sort-keys "^1.0.0" - -normalize-url@^3.0.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" - integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg== - -npm-run-path@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" - integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= - dependencies: - path-key "^2.0.0" +normalizr@^3.6.1: + version "3.6.1" + resolved "https://registry.yarnpkg.com/normalizr/-/normalizr-3.6.1.tgz#d367ab840e031ff382141b8d81ce279292ff69fe" + integrity sha512-8iEmqXmPtll8PwbEFrbPoDxVw7MKnNvt3PZzR2Xvq9nggEEOgBlNICPXYzyZ4w4AkHUzCU998mdatER3n2VaMA== npm-run-path@^4.0.0: version "4.0.1" @@ -7647,157 +2968,71 @@ npm-run-path@^4.0.0: dependencies: path-key "^3.0.0" -nth-check@^1.0.2, nth-check@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" - integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg== - dependencies: - boolbase "~1.0.0" - -num2fraction@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" - integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4= - -number-is-nan@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" - integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= - -nwsapi@^2.0.7, nwsapi@^2.1.3: - version "2.2.0" - resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" - integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== - -oauth-sign@~0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" - integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== - -object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= -object-component@0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/object-component/-/object-component-0.0.3.tgz#f0c69aa50efc95b866c186f400a33769cb2f1291" - integrity sha1-8MaapQ78lbhmwYb0AKM3acsvEpE= - -object-copy@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" - integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= - dependencies: - copy-descriptor "^0.1.0" - define-property "^0.2.5" - kind-of "^3.0.3" - -object-hash@^2.0.1: - version "2.0.3" - resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.0.3.tgz#d12db044e03cd2ca3d77c0570d87225b02e1e6ea" - integrity sha512-JPKn0GMu+Fa3zt3Bmr66JhokJU5BaNBIh4ZeTlaCBzrBsOeXzwcKKAK1tbLiPKgvwmPXsDvvLHoWh5Bm7ofIYg== - -object-inspect@^1.7.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0" - integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA== +object-inspect@^1.11.0, object-inspect@^1.9.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1" + integrity sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg== object-is@^1.0.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.2.tgz#c5d2e87ff9e119f78b7a088441519e2eec1573b6" - integrity sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ== + version "1.1.5" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" + integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.17.5" -object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: +object-keys@^1.0.12, object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== -object-path@0.11.4: - version "0.11.4" - resolved "https://registry.yarnpkg.com/object-path/-/object-path-0.11.4.tgz#370ae752fbf37de3ea70a861c23bba8915691949" - integrity sha1-NwrnUvvzfePqcKhhwju6iRVpGUk= - -object-visit@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" - integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= - dependencies: - isobject "^3.0.0" - -object.assign@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" - integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== - dependencies: - define-properties "^1.1.2" - function-bind "^1.1.1" - has-symbols "^1.0.0" - object-keys "^1.0.11" - -object.entries@^1.1.0, object.entries@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.2.tgz#bc73f00acb6b6bb16c203434b10f9a7e797d3add" - integrity sha512-BQdB9qKmb/HyNdMNWVr7O3+z5MUIx3aiegEIJqjMBbBf0YT9RRxTJSim4mzFqtyr7PDAHigq0N9dO0m0tRakQA== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" - has "^1.0.3" - -object.fromentries@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.2.tgz#4a09c9b9bb3843dd0f89acdb517a794d4f355ac9" - integrity sha512-r3ZiBH7MQppDJVLx6fhD618GKNG40CZYH9wgwdhKxBDDbQgjeWGGd4AtkZad84d291YxvWe7bJGuE65Anh0dxQ== +object.assign@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" + integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== dependencies: + call-bind "^1.0.0" define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - function-bind "^1.1.1" - has "^1.0.3" + has-symbols "^1.0.1" + object-keys "^1.1.1" -object.getownpropertydescriptors@^2.0.3, object.getownpropertydescriptors@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649" - integrity sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg== +object.entries@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.4.tgz#43ccf9a50bc5fd5b649d45ab1a579f24e088cafd" + integrity sha512-h4LWKWE+wKQGhtMjZEBud7uLGhqyLwj8fpHOarZhD2uY3C9cRtk57VQ89ke3moByLXMedqs3XCHzyb4AmA2DjA== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" + es-abstract "^1.18.2" -object.pick@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" - integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= - dependencies: - isobject "^3.0.1" - -object.values@^1.1.0, object.values@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.1.tgz#68a99ecde356b7e9295a3c5e0ce31dc8c953de5e" - integrity sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA== +object.fromentries@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.4.tgz#26e1ba5c4571c5c6f0890cef4473066456a120b8" + integrity sha512-EsFBshs5RUUpQEY1D4q/m59kMfz4YJvxuNCJcv/jWwOJr34EaVnG11ZrZa0UHB3wnzV1wx8m58T4hQL8IuNXlQ== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - function-bind "^1.1.1" + es-abstract "^1.18.0-next.2" has "^1.0.3" -obuf@^1.0.0, obuf@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" - integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== - -on-finished@~2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" - integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= +object.values@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.4.tgz#0d273762833e816b693a637d30073e7051535b30" + integrity sha512-TnGo7j4XSnKQoK3MfvkzqKCi0nVe/D9I9IjwTNYdb/fxYHpjrluHVOgw0AF6jrRFGMPHdfuidR09tIDiIvnaSg== dependencies: - ee-first "1.1.1" + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.18.2" -on-headers@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" - integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== +oblivious-set@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/oblivious-set/-/oblivious-set-1.0.0.tgz#c8316f2c2fb6ff7b11b6158db3234c49f733c566" + integrity sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw== once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" @@ -7807,99 +3042,40 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: wrappy "1" onetime@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.0.tgz#fff0f3c91617fe62bb50189636e99ac8a6df7be5" - integrity sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q== + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== dependencies: mimic-fn "^2.1.0" -open@^7.0.2: - version "7.0.4" - resolved "https://registry.yarnpkg.com/open/-/open-7.0.4.tgz#c28a9d315e5c98340bf979fdcb2e58664aa10d83" - integrity sha512-brSA+/yq+b08Hsr4c8fsEW2CRzk1BmfN3SAK/5VCHQ9bdoZJ4qa/+AfR0xHjlbbZUyPkUHs1b8x1RqdyZdkVqQ== - dependencies: - is-docker "^2.0.0" - is-wsl "^2.1.1" - opencollective-postinstall@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259" integrity sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q== -opn@^5.5.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc" - integrity sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA== - dependencies: - is-wsl "^1.1.0" - -optimize-css-assets-webpack-plugin@5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.3.tgz#e2f1d4d94ad8c0af8967ebd7cf138dcb1ef14572" - integrity sha512-q9fbvCRS6EYtUKKSwI87qm2IxlyJK5b4dygW1rKUBT6mMDhdG5e5bZT63v6tnJR9F9FB/H5a0HTmtw+laUBxKA== - dependencies: - cssnano "^4.1.10" - last-call-webpack-plugin "^3.0.0" - -optionator@^0.8.1, optionator@^0.8.3: - version "0.8.3" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" - integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== +optionator@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" + integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== dependencies: - deep-is "~0.1.3" - fast-levenshtein "~2.0.6" - levn "~0.3.0" - prelude-ls "~1.1.2" - type-check "~0.3.2" - word-wrap "~1.2.3" + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.3" -original@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/original/-/original-1.0.2.tgz#e442a61cffe1c5fd20a65f3261c26663b303f25f" - integrity sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg== - dependencies: - url-parse "^1.4.3" - -os-browserify@^0.3.0: +os-browserify@0.3.0, os-browserify@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= -os-locale@^3.0.0: +p-limit@3.1.0, p-limit@^3.0.2: version "3.1.0" - resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a" - integrity sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q== + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== dependencies: - execa "^1.0.0" - lcid "^2.0.0" - mem "^4.0.0" - -os-tmpdir@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= - -p-defer@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" - integrity sha1-n26xgvbJqozXQwBKfU+WsZaw+ww= - -p-each-series@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-1.0.0.tgz#930f3d12dd1f50e7434457a22cd6f04ac6ad7f71" - integrity sha1-kw89Et0fUOdDRFeiLNbwSsatf3E= - dependencies: - p-reduce "^1.0.0" - -p-finally@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" - integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= - -p-is-promise@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-2.1.0.tgz#918cebaea248a62cf7ffab8e3bca8c5f882fc42e" - integrity sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg== + yocto-queue "^0.1.0" p-limit@^1.1.0: version "1.3.0" @@ -7908,7 +3084,7 @@ p-limit@^1.1.0: dependencies: p-try "^1.0.0" -p-limit@^2.0.0, p-limit@^2.2.0, p-limit@^2.2.2: +p-limit@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== @@ -7922,13 +3098,6 @@ p-locate@^2.0.0: dependencies: p-limit "^1.1.0" -p-locate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" - integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== - dependencies: - p-limit "^2.0.0" - p-locate@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" @@ -7936,17 +3105,12 @@ p-locate@^4.1.0: dependencies: p-limit "^2.2.0" -p-map@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" - integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw== - -p-map@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-3.0.0.tgz#d704d9af8a2ba684e2600d9a215983d4141a979d" - integrity sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ== +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== dependencies: - aggregate-error "^3.0.0" + p-limit "^3.0.2" p-map@^4.0.0: version "4.0.0" @@ -7955,18 +3119,6 @@ p-map@^4.0.0: dependencies: aggregate-error "^3.0.0" -p-reduce@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-reduce/-/p-reduce-1.0.0.tgz#18c2b0dd936a4690a529f8231f58a0fdb6a47dfa" - integrity sha1-GMKw3ZNqRpClKfgjH1ig/bakffo= - -p-retry@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-3.0.1.tgz#316b4c8893e2c8dc1cfa891f406c4b422bebf328" - integrity sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w== - dependencies: - retry "^0.12.0" - p-try@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" @@ -7982,23 +3134,6 @@ pako@~1.0.5: resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== -parallel-transform@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.2.0.tgz#9049ca37d6cb2182c3b1d2c720be94d14a5814fc" - integrity sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg== - dependencies: - cyclist "^1.0.1" - inherits "^2.0.3" - readable-stream "^2.1.5" - -param-case@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.3.tgz#4be41f8399eff621c56eebb829a5e451d9801238" - integrity sha512-VWBVyimc1+QrzappRs7waeN2YmoZFCGXWASRYX1/rGHtXqEcrGEIDm+jqIwFa2fRXNgQEwrxaYuIrX0WcAguTA== - dependencies: - dot-case "^3.0.3" - tslib "^1.10.0" - parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" @@ -8007,24 +3142,16 @@ parent-module@^1.0.0: callsites "^3.0.0" parse-asn1@^5.0.0, parse-asn1@^5.1.5: - version "5.1.5" - resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.5.tgz#003271343da58dc94cace494faef3d2147ecea0e" - integrity sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ== + version "5.1.6" + resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.6.tgz#385080a3ec13cb62a62d39409cb3e88844cdaed4" + integrity sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw== dependencies: - asn1.js "^4.0.0" + asn1.js "^5.2.0" browserify-aes "^1.0.0" - create-hash "^1.1.0" evp_bytestokey "^1.0.0" pbkdf2 "^3.0.3" safe-buffer "^5.1.1" -parse-json@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" - integrity sha1-9ID0BDTvgHQfhGkJn43qGPVaTck= - dependencies: - error-ex "^1.2.0" - parse-json@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" @@ -8034,73 +3161,24 @@ parse-json@^4.0.0: json-parse-better-errors "^1.0.1" parse-json@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.0.0.tgz#73e5114c986d143efa3712d4ea24db9a4266f60f" - integrity sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw== + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== dependencies: "@babel/code-frame" "^7.0.0" error-ex "^1.3.1" - json-parse-better-errors "^1.0.1" + json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" -parse5@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608" - integrity sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA== - -parse5@5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.0.tgz#c59341c9723f414c452975564c7c00a68d58acd2" - integrity sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ== - -parseqs@0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d" - integrity sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0= - dependencies: - better-assert "~1.0.0" - -parseuri@0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.5.tgz#80204a50d4dbb779bfdc6ebe2778d90e4bce320a" - integrity sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo= - dependencies: - better-assert "~1.0.0" - -parseurl@~1.3.2, parseurl@~1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" - integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== - -pascal-case@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.1.tgz#5ac1975133ed619281e88920973d2cd1f279de5f" - integrity sha512-XIeHKqIrsquVTQL2crjq3NfJUxmdLasn3TYOU0VBM+UX2a6ztAWBlJQBePLGY7VHW8+2dRadeIPK5+KImwTxQA== - dependencies: - no-case "^3.0.3" - tslib "^1.10.0" - -pascalcase@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" - integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= - path-browserify@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ== -path-dirname@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" - integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA= - -path-exists@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" - integrity sha1-D+tsZPD8UY2adU3V77YscCJ2H0s= - dependencies: - pinkie-promise "^2.0.0" +path-browserify@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd" + integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g== path-exists@^3.0.0: version "3.0.0" @@ -8117,44 +3195,15 @@ path-is-absolute@^1.0.0: resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= -path-is-inside@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" - integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= - -path-key@^2.0.0, path-key@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" - integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= - path-key@^3.0.0, path-key@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== path-parse@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" - integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== - -path-to-regexp@0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= - -path-to-regexp@^1.7.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" - integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== - dependencies: - isarray "0.0.1" - -path-type@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" - integrity sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM= - dependencies: - pify "^2.0.0" + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== path-type@^3.0.0: version "3.0.0" @@ -8169,9 +3218,9 @@ path-type@^4.0.0: integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== pbkdf2@^3.0.3: - version "3.1.1" - resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.1.tgz#cb8724b0fada984596856d1a6ebafd3584654b94" - integrity sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg== + version "3.1.2" + resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.2.tgz#dd822aa0887580e52f1a039dc3eda108efae3075" + integrity sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA== dependencies: create-hash "^1.1.2" create-hmac "^1.1.4" @@ -8184,52 +3233,16 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1: - version "2.2.2" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" - integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== - -pify@^2.0.0: +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3: version "2.3.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" - integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" + integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== pify@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= -pify@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" - integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== - -pinkie-promise@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" - integrity sha1-ITXW36ejWMBprJsXh3YogihFD/o= - dependencies: - pinkie "^2.0.0" - -pinkie@^2.0.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" - integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= - -pirates@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.1.tgz#643a92caf894566f91b2b986d2c66950a8e2fb87" - integrity sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA== - dependencies: - node-modules-regexp "^1.0.0" - -pkg-dir@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-1.0.0.tgz#7a4b508a8d5bb2d629d447056ff4e9c9314cf3d4" - integrity sha1-ektQio1bstYp1EcFb/TpyTFM89Q= - dependencies: - find-up "^1.0.0" - pkg-dir@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" @@ -8237,31 +3250,31 @@ pkg-dir@^2.0.0: dependencies: find-up "^2.1.0" -pkg-dir@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3" - integrity sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw== - dependencies: - find-up "^3.0.0" - -pkg-dir@^4.1.0, pkg-dir@^4.2.0: +pkg-dir@^4.1.0: version "4.2.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== dependencies: find-up "^4.0.0" -pkg-up@3.1.0, pkg-up@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5" - integrity sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA== +pkg-dir@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-5.0.0.tgz#a02d6aebe6ba133a928f74aec20bafdfe6b8e760" + integrity sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA== dependencies: - find-up "^3.0.0" + find-up "^5.0.0" -platform@^1.3.0: - version "1.3.5" - resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.5.tgz#fb6958c696e07e2918d2eeda0f0bc9448d733444" - integrity sha512-TuvHS8AOIZNAlE77WUDiR4rySV/VMptyMfcfeoMgs4P8apaZM3JrnbzBiixKUv+XR6i+BXrQh8WAnjaSPFO65Q== +pkg-up@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-2.0.0.tgz#c819ac728059a461cab1c3889a2be3c49a004d7f" + integrity sha1-yBmscoBZpGHKscOImivjxJoATX8= + dependencies: + find-up "^2.1.0" + +platform@1.3.6: + version "1.3.6" + resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.6.tgz#48b4ce983164b209c2d45a107adb31f473a6e7a7" + integrity sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg== please-upgrade-node@^3.2.0: version "3.2.0" @@ -8270,11 +3283,6 @@ please-upgrade-node@^3.2.0: dependencies: semver-compare "^1.0.0" -pn@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb" - integrity sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA== - pnp-webpack-plugin@1.6.4: version "1.6.4" resolved "https://registry.yarnpkg.com/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz#c9711ac4dc48a685dabafc86f8b6dd9f8df84149" @@ -8282,735 +3290,41 @@ pnp-webpack-plugin@1.6.4: dependencies: ts-pnp "^1.1.6" -popper.js@^1.14.4: +popper.js@^1.16.0: version "1.16.1" resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1.tgz#2a223cb3dc7b6213d740e40372be40de43e65b1b" integrity sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ== -portfinder@^1.0.25: - version "1.0.26" - resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.26.tgz#475658d56ca30bed72ac7f1378ed350bd1b64e70" - integrity sha512-Xi7mKxJHHMI3rIUrnm/jjUgwhbYMkp/XKEcZX3aG4BrumLpq3nmoQMX+ClYnDZnZ/New7IatC1no5RX0zo1vXQ== - dependencies: - async "^2.6.2" - debug "^3.1.1" - mkdirp "^0.5.1" - -posix-character-classes@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" - integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= - -postcss-attribute-case-insensitive@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-4.0.2.tgz#d93e46b504589e94ac7277b0463226c68041a880" - integrity sha512-clkFxk/9pcdb4Vkn0hAHq3YnxBQ2p0CGD1dy24jN+reBck+EWxMbxSUqN4Yj7t0w8csl87K6p0gxBe1utkJsYA== - dependencies: - postcss "^7.0.2" - postcss-selector-parser "^6.0.2" - -postcss-browser-comments@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-browser-comments/-/postcss-browser-comments-3.0.0.tgz#1248d2d935fb72053c8e1f61a84a57292d9f65e9" - integrity sha512-qfVjLfq7HFd2e0HW4s1dvU8X080OZdG46fFbIBFjW7US7YPDcWfRvdElvwMJr2LI6hMmD+7LnH2HcmXTs+uOig== - dependencies: - postcss "^7" - -postcss-calc@^7.0.1: - version "7.0.2" - resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-7.0.2.tgz#504efcd008ca0273120568b0792b16cdcde8aac1" - integrity sha512-rofZFHUg6ZIrvRwPeFktv06GdbDYLcGqh9EwiMutZg+a0oePCCw1zHOEiji6LCpyRcjTREtPASuUqeAvYlEVvQ== - dependencies: - postcss "^7.0.27" - postcss-selector-parser "^6.0.2" - postcss-value-parser "^4.0.2" - -postcss-color-functional-notation@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/postcss-color-functional-notation/-/postcss-color-functional-notation-2.0.1.tgz#5efd37a88fbabeb00a2966d1e53d98ced93f74e0" - integrity sha512-ZBARCypjEDofW4P6IdPVTLhDNXPRn8T2s1zHbZidW6rPaaZvcnCS2soYFIQJrMZSxiePJ2XIYTlcb2ztr/eT2g== - dependencies: - postcss "^7.0.2" - postcss-values-parser "^2.0.0" - -postcss-color-gray@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/postcss-color-gray/-/postcss-color-gray-5.0.0.tgz#532a31eb909f8da898ceffe296fdc1f864be8547" - integrity sha512-q6BuRnAGKM/ZRpfDascZlIZPjvwsRye7UDNalqVz3s7GDxMtqPY6+Q871liNxsonUw8oC61OG+PSaysYpl1bnw== - dependencies: - "@csstools/convert-colors" "^1.4.0" - postcss "^7.0.5" - postcss-values-parser "^2.0.0" - -postcss-color-hex-alpha@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/postcss-color-hex-alpha/-/postcss-color-hex-alpha-5.0.3.tgz#a8d9ca4c39d497c9661e374b9c51899ef0f87388" - integrity sha512-PF4GDel8q3kkreVXKLAGNpHKilXsZ6xuu+mOQMHWHLPNyjiUBOr75sp5ZKJfmv1MCus5/DWUGcK9hm6qHEnXYw== - dependencies: - postcss "^7.0.14" - postcss-values-parser "^2.0.1" - -postcss-color-mod-function@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/postcss-color-mod-function/-/postcss-color-mod-function-3.0.3.tgz#816ba145ac11cc3cb6baa905a75a49f903e4d31d" - integrity sha512-YP4VG+xufxaVtzV6ZmhEtc+/aTXH3d0JLpnYfxqTvwZPbJhWqp8bSY3nfNzNRFLgB4XSaBA82OE4VjOOKpCdVQ== - dependencies: - "@csstools/convert-colors" "^1.4.0" - postcss "^7.0.2" - postcss-values-parser "^2.0.0" - -postcss-color-rebeccapurple@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-4.0.1.tgz#c7a89be872bb74e45b1e3022bfe5748823e6de77" - integrity sha512-aAe3OhkS6qJXBbqzvZth2Au4V3KieR5sRQ4ptb2b2O8wgvB3SJBsdG+jsn2BZbbwekDG8nTfcCNKcSfe/lEy8g== - dependencies: - postcss "^7.0.2" - postcss-values-parser "^2.0.0" - -postcss-colormin@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-4.0.3.tgz#ae060bce93ed794ac71264f08132d550956bd381" - integrity sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw== - dependencies: - browserslist "^4.0.0" - color "^3.0.0" - has "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-convert-values@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz#ca3813ed4da0f812f9d43703584e449ebe189a7f" - integrity sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ== - dependencies: - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-custom-media@^7.0.8: - version "7.0.8" - resolved "https://registry.yarnpkg.com/postcss-custom-media/-/postcss-custom-media-7.0.8.tgz#fffd13ffeffad73621be5f387076a28b00294e0c" - integrity sha512-c9s5iX0Ge15o00HKbuRuTqNndsJUbaXdiNsksnVH8H4gdc+zbLzr/UasOwNG6CTDpLFekVY4672eWdiiWu2GUg== - dependencies: - postcss "^7.0.14" - -postcss-custom-properties@^8.0.11: - version "8.0.11" - resolved "https://registry.yarnpkg.com/postcss-custom-properties/-/postcss-custom-properties-8.0.11.tgz#2d61772d6e92f22f5e0d52602df8fae46fa30d97" - integrity sha512-nm+o0eLdYqdnJ5abAJeXp4CEU1c1k+eB2yMCvhgzsds/e0umabFrN6HoTy/8Q4K5ilxERdl/JD1LO5ANoYBeMA== - dependencies: - postcss "^7.0.17" - postcss-values-parser "^2.0.1" - -postcss-custom-selectors@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/postcss-custom-selectors/-/postcss-custom-selectors-5.1.2.tgz#64858c6eb2ecff2fb41d0b28c9dd7b3db4de7fba" - integrity sha512-DSGDhqinCqXqlS4R7KGxL1OSycd1lydugJ1ky4iRXPHdBRiozyMHrdu0H3o7qNOCiZwySZTUI5MV0T8QhCLu+w== - dependencies: - postcss "^7.0.2" - postcss-selector-parser "^5.0.0-rc.3" - -postcss-dir-pseudo-class@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-5.0.0.tgz#6e3a4177d0edb3abcc85fdb6fbb1c26dabaeaba2" - integrity sha512-3pm4oq8HYWMZePJY+5ANriPs3P07q+LW6FAdTlkFH2XqDdP4HeeJYMOzn0HYLhRSjBO3fhiqSwwU9xEULSrPgw== - dependencies: - postcss "^7.0.2" - postcss-selector-parser "^5.0.0-rc.3" - -postcss-discard-comments@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz#1fbabd2c246bff6aaad7997b2b0918f4d7af4033" - integrity sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg== - dependencies: - postcss "^7.0.0" - -postcss-discard-duplicates@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz#3fe133cd3c82282e550fc9b239176a9207b784eb" - integrity sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ== - dependencies: - postcss "^7.0.0" - -postcss-discard-empty@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz#c8c951e9f73ed9428019458444a02ad90bb9f765" - integrity sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w== - dependencies: - postcss "^7.0.0" - -postcss-discard-overridden@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz#652aef8a96726f029f5e3e00146ee7a4e755ff57" - integrity sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg== - dependencies: - postcss "^7.0.0" - -postcss-double-position-gradients@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/postcss-double-position-gradients/-/postcss-double-position-gradients-1.0.0.tgz#fc927d52fddc896cb3a2812ebc5df147e110522e" - integrity sha512-G+nV8EnQq25fOI8CH/B6krEohGWnF5+3A6H/+JEpOncu5dCnkS1QQ6+ct3Jkaepw1NGVqqOZH6lqrm244mCftA== - dependencies: - postcss "^7.0.5" - postcss-values-parser "^2.0.0" - -postcss-env-function@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/postcss-env-function/-/postcss-env-function-2.0.2.tgz#0f3e3d3c57f094a92c2baf4b6241f0b0da5365d7" - integrity sha512-rwac4BuZlITeUbiBq60h/xbLzXY43qOsIErngWa4l7Mt+RaSkT7QBjXVGTcBHupykkblHMDrBFh30zchYPaOUw== - dependencies: - postcss "^7.0.2" - postcss-values-parser "^2.0.0" - -postcss-flexbugs-fixes@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-4.1.0.tgz#e094a9df1783e2200b7b19f875dcad3b3aff8b20" - integrity sha512-jr1LHxQvStNNAHlgco6PzY308zvLklh7SJVYuWUwyUQncofaAlD2l+P/gxKHOdqWKe7xJSkVLFF/2Tp+JqMSZA== - dependencies: - postcss "^7.0.0" - -postcss-focus-visible@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-focus-visible/-/postcss-focus-visible-4.0.0.tgz#477d107113ade6024b14128317ade2bd1e17046e" - integrity sha512-Z5CkWBw0+idJHSV6+Bgf2peDOFf/x4o+vX/pwcNYrWpXFrSfTkQ3JQ1ojrq9yS+upnAlNRHeg8uEwFTgorjI8g== - dependencies: - postcss "^7.0.2" - -postcss-focus-within@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-focus-within/-/postcss-focus-within-3.0.0.tgz#763b8788596cee9b874c999201cdde80659ef680" - integrity sha512-W0APui8jQeBKbCGZudW37EeMCjDeVxKgiYfIIEo8Bdh5SpB9sxds/Iq8SEuzS0Q4YFOlG7EPFulbbxujpkrV2w== - dependencies: - postcss "^7.0.2" - -postcss-font-variant@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-font-variant/-/postcss-font-variant-4.0.0.tgz#71dd3c6c10a0d846c5eda07803439617bbbabacc" - integrity sha512-M8BFYKOvCrI2aITzDad7kWuXXTm0YhGdP9Q8HanmN4EF1Hmcgs1KK5rSHylt/lUJe8yLxiSwWAHdScoEiIxztg== - dependencies: - postcss "^7.0.2" - -postcss-gap-properties@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/postcss-gap-properties/-/postcss-gap-properties-2.0.0.tgz#431c192ab3ed96a3c3d09f2ff615960f902c1715" - integrity sha512-QZSqDaMgXCHuHTEzMsS2KfVDOq7ZFiknSpkrPJY6jmxbugUPTuSzs/vuE5I3zv0WAS+3vhrlqhijiprnuQfzmg== - dependencies: - postcss "^7.0.2" - -postcss-image-set-function@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/postcss-image-set-function/-/postcss-image-set-function-3.0.1.tgz#28920a2f29945bed4c3198d7df6496d410d3f288" - integrity sha512-oPTcFFip5LZy8Y/whto91L9xdRHCWEMs3e1MdJxhgt4jy2WYXfhkng59fH5qLXSCPN8k4n94p1Czrfe5IOkKUw== - dependencies: - postcss "^7.0.2" - postcss-values-parser "^2.0.0" - -postcss-initial@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/postcss-initial/-/postcss-initial-3.0.2.tgz#f018563694b3c16ae8eaabe3c585ac6319637b2d" - integrity sha512-ugA2wKonC0xeNHgirR4D3VWHs2JcU08WAi1KFLVcnb7IN89phID6Qtg2RIctWbnvp1TM2BOmDtX8GGLCKdR8YA== - dependencies: - lodash.template "^4.5.0" - postcss "^7.0.2" - -postcss-lab-function@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/postcss-lab-function/-/postcss-lab-function-2.0.1.tgz#bb51a6856cd12289ab4ae20db1e3821ef13d7d2e" - integrity sha512-whLy1IeZKY+3fYdqQFuDBf8Auw+qFuVnChWjmxm/UhHWqNHZx+B99EwxTvGYmUBqe3Fjxs4L1BoZTJmPu6usVg== - dependencies: - "@csstools/convert-colors" "^1.4.0" - postcss "^7.0.2" - postcss-values-parser "^2.0.0" - -postcss-load-config@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-2.1.0.tgz#c84d692b7bb7b41ddced94ee62e8ab31b417b003" - integrity sha512-4pV3JJVPLd5+RueiVVB+gFOAa7GWc25XQcMp86Zexzke69mKf6Nx9LRcQywdz7yZI9n1udOxmLuAwTBypypF8Q== - dependencies: - cosmiconfig "^5.0.0" - import-cwd "^2.0.0" - -postcss-loader@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-3.0.0.tgz#6b97943e47c72d845fa9e03f273773d4e8dd6c2d" - integrity sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA== - dependencies: - loader-utils "^1.1.0" - postcss "^7.0.0" - postcss-load-config "^2.0.0" - schema-utils "^1.0.0" - -postcss-logical@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-logical/-/postcss-logical-3.0.0.tgz#2495d0f8b82e9f262725f75f9401b34e7b45d5b5" - integrity sha512-1SUKdJc2vuMOmeItqGuNaC+N8MzBWFWEkAnRnLpFYj1tGGa7NqyVBujfRtgNa2gXR+6RkGUiB2O5Vmh7E2RmiA== - dependencies: - postcss "^7.0.2" - -postcss-media-minmax@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-media-minmax/-/postcss-media-minmax-4.0.0.tgz#b75bb6cbc217c8ac49433e12f22048814a4f5ed5" - integrity sha512-fo9moya6qyxsjbFAYl97qKO9gyre3qvbMnkOZeZwlsW6XYFsvs2DMGDlchVLfAd8LHPZDxivu/+qW2SMQeTHBw== - dependencies: - postcss "^7.0.2" - -postcss-merge-longhand@^4.0.11: - version "4.0.11" - resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz#62f49a13e4a0ee04e7b98f42bb16062ca2549e24" - integrity sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw== - dependencies: - css-color-names "0.0.4" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - stylehacks "^4.0.0" - -postcss-merge-rules@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz#362bea4ff5a1f98e4075a713c6cb25aefef9a650" - integrity sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ== - dependencies: - browserslist "^4.0.0" - caniuse-api "^3.0.0" - cssnano-util-same-parent "^4.0.0" - postcss "^7.0.0" - postcss-selector-parser "^3.0.0" - vendors "^1.0.0" - -postcss-minify-font-values@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz#cd4c344cce474343fac5d82206ab2cbcb8afd5a6" - integrity sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg== - dependencies: - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-minify-gradients@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz#93b29c2ff5099c535eecda56c4aa6e665a663471" - integrity sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q== - dependencies: - cssnano-util-get-arguments "^4.0.0" - is-color-stop "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-minify-params@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz#6b9cef030c11e35261f95f618c90036d680db874" - integrity sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg== - dependencies: - alphanum-sort "^1.0.0" - browserslist "^4.0.0" - cssnano-util-get-arguments "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - uniqs "^2.0.0" - -postcss-minify-selectors@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz#e2e5eb40bfee500d0cd9243500f5f8ea4262fbd8" - integrity sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g== - dependencies: - alphanum-sort "^1.0.0" - has "^1.0.0" - postcss "^7.0.0" - postcss-selector-parser "^3.0.0" - -postcss-modules-extract-imports@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz#818719a1ae1da325f9832446b01136eeb493cd7e" - integrity sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ== - dependencies: - postcss "^7.0.5" - -postcss-modules-local-by-default@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.2.tgz#e8a6561be914aaf3c052876377524ca90dbb7915" - integrity sha512-jM/V8eqM4oJ/22j0gx4jrp63GSvDH6v86OqyTHHUvk4/k1vceipZsaymiZ5PvocqZOl5SFHiFJqjs3la0wnfIQ== - dependencies: - icss-utils "^4.1.1" - postcss "^7.0.16" - postcss-selector-parser "^6.0.2" - postcss-value-parser "^4.0.0" - -postcss-modules-scope@^2.1.1: - version "2.2.0" - resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz#385cae013cc7743f5a7d7602d1073a89eaae62ee" - integrity sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ== - dependencies: - postcss "^7.0.6" - postcss-selector-parser "^6.0.0" - -postcss-modules-values@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz#5b5000d6ebae29b4255301b4a3a54574423e7f10" - integrity sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg== - dependencies: - icss-utils "^4.0.0" - postcss "^7.0.6" - -postcss-nesting@^7.0.0: - version "7.0.1" - resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-7.0.1.tgz#b50ad7b7f0173e5b5e3880c3501344703e04c052" - integrity sha512-FrorPb0H3nuVq0Sff7W2rnc3SmIcruVC6YwpcS+k687VxyxO33iE1amna7wHuRVzM8vfiYofXSBHNAZ3QhLvYg== - dependencies: - postcss "^7.0.2" - -postcss-normalize-charset@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz#8b35add3aee83a136b0471e0d59be58a50285dd4" - integrity sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g== - dependencies: - postcss "^7.0.0" - -postcss-normalize-display-values@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz#0dbe04a4ce9063d4667ed2be476bb830c825935a" - integrity sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ== - dependencies: - cssnano-util-get-match "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-positions@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz#05f757f84f260437378368a91f8932d4b102917f" - integrity sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA== - dependencies: - cssnano-util-get-arguments "^4.0.0" - has "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-repeat-style@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz#c4ebbc289f3991a028d44751cbdd11918b17910c" - integrity sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q== - dependencies: - cssnano-util-get-arguments "^4.0.0" - cssnano-util-get-match "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-string@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz#cd44c40ab07a0c7a36dc5e99aace1eca4ec2690c" - integrity sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA== - dependencies: - has "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-timing-functions@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz#8e009ca2a3949cdaf8ad23e6b6ab99cb5e7d28d9" - integrity sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A== - dependencies: - cssnano-util-get-match "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-unicode@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz#841bd48fdcf3019ad4baa7493a3d363b52ae1cfb" - integrity sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg== - dependencies: - browserslist "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-url@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz#10e437f86bc7c7e58f7b9652ed878daaa95faae1" - integrity sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA== - dependencies: - is-absolute-url "^2.0.0" - normalize-url "^3.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-whitespace@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz#bf1d4070fe4fcea87d1348e825d8cc0c5faa7d82" - integrity sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA== - dependencies: - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize@8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize/-/postcss-normalize-8.0.1.tgz#90e80a7763d7fdf2da6f2f0f82be832ce4f66776" - integrity sha512-rt9JMS/m9FHIRroDDBGSMsyW1c0fkvOJPy62ggxSHUldJO7B195TqFMqIf+lY5ezpDcYOV4j86aUp3/XbxzCCQ== - dependencies: - "@csstools/normalize.css" "^10.1.0" - browserslist "^4.6.2" - postcss "^7.0.17" - postcss-browser-comments "^3.0.0" - sanitize.css "^10.0.0" - -postcss-ordered-values@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz#0cf75c820ec7d5c4d280189559e0b571ebac0eee" - integrity sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw== - dependencies: - cssnano-util-get-arguments "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-overflow-shorthand@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/postcss-overflow-shorthand/-/postcss-overflow-shorthand-2.0.0.tgz#31ecf350e9c6f6ddc250a78f0c3e111f32dd4c30" - integrity sha512-aK0fHc9CBNx8jbzMYhshZcEv8LtYnBIRYQD5i7w/K/wS9c2+0NSR6B3OVMu5y0hBHYLcMGjfU+dmWYNKH0I85g== - dependencies: - postcss "^7.0.2" - -postcss-page-break@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/postcss-page-break/-/postcss-page-break-2.0.0.tgz#add52d0e0a528cabe6afee8b46e2abb277df46bf" - integrity sha512-tkpTSrLpfLfD9HvgOlJuigLuk39wVTbbd8RKcy8/ugV2bNBUW3xU+AIqyxhDrQr1VUj1RmyJrBn1YWrqUm9zAQ== - dependencies: - postcss "^7.0.2" - -postcss-place@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-place/-/postcss-place-4.0.1.tgz#e9f39d33d2dc584e46ee1db45adb77ca9d1dcc62" - integrity sha512-Zb6byCSLkgRKLODj/5mQugyuj9bvAAw9LqJJjgwz5cYryGeXfFZfSXoP1UfveccFmeq0b/2xxwcTEVScnqGxBg== - dependencies: - postcss "^7.0.2" - postcss-values-parser "^2.0.0" - -postcss-preset-env@6.7.0: - version "6.7.0" - resolved "https://registry.yarnpkg.com/postcss-preset-env/-/postcss-preset-env-6.7.0.tgz#c34ddacf8f902383b35ad1e030f178f4cdf118a5" - integrity sha512-eU4/K5xzSFwUFJ8hTdTQzo2RBLbDVt83QZrAvI07TULOkmyQlnYlpwep+2yIK+K+0KlZO4BvFcleOCCcUtwchg== - dependencies: - autoprefixer "^9.6.1" - browserslist "^4.6.4" - caniuse-lite "^1.0.30000981" - css-blank-pseudo "^0.1.4" - css-has-pseudo "^0.10.0" - css-prefers-color-scheme "^3.1.1" - cssdb "^4.4.0" - postcss "^7.0.17" - postcss-attribute-case-insensitive "^4.0.1" - postcss-color-functional-notation "^2.0.1" - postcss-color-gray "^5.0.0" - postcss-color-hex-alpha "^5.0.3" - postcss-color-mod-function "^3.0.3" - postcss-color-rebeccapurple "^4.0.1" - postcss-custom-media "^7.0.8" - postcss-custom-properties "^8.0.11" - postcss-custom-selectors "^5.1.2" - postcss-dir-pseudo-class "^5.0.0" - postcss-double-position-gradients "^1.0.0" - postcss-env-function "^2.0.2" - postcss-focus-visible "^4.0.0" - postcss-focus-within "^3.0.0" - postcss-font-variant "^4.0.0" - postcss-gap-properties "^2.0.0" - postcss-image-set-function "^3.0.1" - postcss-initial "^3.0.0" - postcss-lab-function "^2.0.1" - postcss-logical "^3.0.0" - postcss-media-minmax "^4.0.0" - postcss-nesting "^7.0.0" - postcss-overflow-shorthand "^2.0.0" - postcss-page-break "^2.0.0" - postcss-place "^4.0.1" - postcss-pseudo-class-any-link "^6.0.0" - postcss-replace-overflow-wrap "^3.0.0" - postcss-selector-matches "^4.0.0" - postcss-selector-not "^4.0.0" - -postcss-pseudo-class-any-link@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-6.0.0.tgz#2ed3eed393b3702879dec4a87032b210daeb04d1" - integrity sha512-lgXW9sYJdLqtmw23otOzrtbDXofUdfYzNm4PIpNE322/swES3VU9XlXHeJS46zT2onFO7V1QFdD4Q9LiZj8mew== - dependencies: - postcss "^7.0.2" - postcss-selector-parser "^5.0.0-rc.3" - -postcss-reduce-initial@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz#7fd42ebea5e9c814609639e2c2e84ae270ba48df" - integrity sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA== - dependencies: - browserslist "^4.0.0" - caniuse-api "^3.0.0" - has "^1.0.0" - postcss "^7.0.0" - -postcss-reduce-transforms@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz#17efa405eacc6e07be3414a5ca2d1074681d4e29" - integrity sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg== - dependencies: - cssnano-util-get-match "^4.0.0" - has "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-replace-overflow-wrap@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-3.0.0.tgz#61b360ffdaedca84c7c918d2b0f0d0ea559ab01c" - integrity sha512-2T5hcEHArDT6X9+9dVSPQdo7QHzG4XKclFT8rU5TzJPDN7RIRTbO9c4drUISOVemLj03aezStHCR2AIcr8XLpw== - dependencies: - postcss "^7.0.2" - -postcss-safe-parser@4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-safe-parser/-/postcss-safe-parser-4.0.1.tgz#8756d9e4c36fdce2c72b091bbc8ca176ab1fcdea" - integrity sha512-xZsFA3uX8MO3yAda03QrG3/Eg1LN3EPfjjf07vke/46HERLZyHrTsQ9E1r1w1W//fWEhtYNndo2hQplN2cVpCQ== - dependencies: - postcss "^7.0.0" - -postcss-selector-matches@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-selector-matches/-/postcss-selector-matches-4.0.0.tgz#71c8248f917ba2cc93037c9637ee09c64436fcff" - integrity sha512-LgsHwQR/EsRYSqlwdGzeaPKVT0Ml7LAT6E75T8W8xLJY62CE4S/l03BWIt3jT8Taq22kXP08s2SfTSzaraoPww== - dependencies: - balanced-match "^1.0.0" - postcss "^7.0.2" - -postcss-selector-not@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-selector-not/-/postcss-selector-not-4.0.0.tgz#c68ff7ba96527499e832724a2674d65603b645c0" - integrity sha512-W+bkBZRhqJaYN8XAnbbZPLWMvZD1wKTu0UxtFKdhtGjWYmxhkUneoeOhRJKdAE5V7ZTlnbHfCR+6bNwK9e1dTQ== - dependencies: - balanced-match "^1.0.0" - postcss "^7.0.2" - -postcss-selector-parser@^3.0.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz#b310f5c4c0fdaf76f94902bbaa30db6aa84f5270" - integrity sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA== - dependencies: - dot-prop "^5.2.0" - indexes-of "^1.0.1" - uniq "^1.0.1" - -postcss-selector-parser@^5.0.0-rc.3, postcss-selector-parser@^5.0.0-rc.4: - version "5.0.0" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz#249044356697b33b64f1a8f7c80922dddee7195c" - integrity sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ== - dependencies: - cssesc "^2.0.0" - indexes-of "^1.0.1" - uniq "^1.0.1" - -postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz#934cf799d016c83411859e09dcecade01286ec5c" - integrity sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg== - dependencies: - cssesc "^3.0.0" - indexes-of "^1.0.1" - uniq "^1.0.1" - -postcss-svgo@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-4.0.2.tgz#17b997bc711b333bab143aaed3b8d3d6e3d38258" - integrity sha512-C6wyjo3VwFm0QgBy+Fu7gCYOkCmgmClghO+pjcxvrcBKtiKt0uCF+hvbMO1fyv5BMImRK90SMb+dwUnfbGd+jw== - dependencies: - is-svg "^3.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - svgo "^1.0.0" - -postcss-unique-selectors@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz#9446911f3289bfd64c6d680f073c03b1f9ee4bac" - integrity sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg== - dependencies: - alphanum-sort "^1.0.0" - postcss "^7.0.0" - uniqs "^2.0.0" - -postcss-value-parser@^3.0.0: +postcss-value-parser@^3.3.0: version "3.3.1" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== -postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" - integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== - -postcss-values-parser@^2.0.0, postcss-values-parser@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/postcss-values-parser/-/postcss-values-parser-2.0.1.tgz#da8b472d901da1e205b47bdc98637b9e9e550e5f" - integrity sha512-2tLuBsA6P4rYTNKCXYG/71C7j1pU6pK503suYOmn4xYrQIzW+opD+7FAFNuGSdZC/3Qfy334QbeMu7MEb8gOxg== +postcss@8.2.15: + version "8.2.15" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.15.tgz#9e66ccf07292817d226fc315cbbf9bc148fbca65" + integrity sha512-2zO3b26eJD/8rb106Qu2o7Qgg52ND5HPjcyQiK2B98O388h43A448LCslC0dI2P97wCAQRJsFvwTRcXxTKds+Q== dependencies: - flatten "^1.0.2" - indexes-of "^1.0.1" - uniq "^1.0.1" - -postcss@7.0.21: - version "7.0.21" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.21.tgz#06bb07824c19c2021c5d056d5b10c35b989f7e17" - integrity sha512-uIFtJElxJo29QC753JzhidoAhvp/e/Exezkdhfmt8AymWT6/5B7W1WmponYWkHk2eg6sONyTch0A3nkMPun3SQ== - dependencies: - chalk "^2.4.2" + colorette "^1.2.2" + nanoid "^3.1.23" source-map "^0.6.1" - supports-color "^6.1.0" - -postcss@^7, postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.23, postcss@^7.0.27, postcss@^7.0.32, postcss@^7.0.5, postcss@^7.0.6: - version "7.0.32" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.32.tgz#4310d6ee347053da3433db2be492883d62cec59d" - integrity sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw== - dependencies: - chalk "^2.4.2" - source-map "^0.6.1" - supports-color "^6.1.0" - -prelude-ls@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" - integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= - -prepend-http@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" - integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= - -prettier@~2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.5.tgz#d6d56282455243f2f92cc1716692c08aa31522d4" - integrity sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg== -pretty-bytes@^5.1.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.3.0.tgz#f2849e27db79fb4d6cfe24764fc4134f165989f2" - integrity sha512-hjGrh+P926p4R4WbaB6OckyRtO0F0/lQBiT+0gnxjV+5kjPBrfVBFCsCLbMqVQeydvIoouYTCmmEURiH3R1Bdg== - -pretty-error@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-2.1.1.tgz#5f4f87c8f91e5ae3f3ba87ab4cf5e03b1a17f1a3" - integrity sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM= - dependencies: - renderkid "^2.0.1" - utila "~0.4" - -pretty-format@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.9.0.tgz#12fac31b37019a4eea3c11aa9a959eb7628aa7c9" - integrity sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA== - dependencies: - "@jest/types" "^24.9.0" - ansi-regex "^4.0.0" - ansi-styles "^3.2.0" - react-is "^16.8.4" +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== -private@^0.1.8: - version "0.1.8" - resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" - integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg== +prettier@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.3.2.tgz#ef280a05ec253712e486233db5c6f23441e7342d" + integrity sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ== process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== -process@^0.11.10: +process@0.11.10, process@^0.11.10: version "0.11.10" resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= @@ -9020,27 +3334,20 @@ progress@^2.0.0: resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== -promise-inflight@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" - integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= - -promise@^8.0.3: - version "8.1.0" - resolved "https://registry.yarnpkg.com/promise/-/promise-8.1.0.tgz#697c25c3dfe7435dd79fcd58c38a135888eaf05e" - integrity sha512-W04AqnILOL/sPRXziNicCjSNRruLAuIHEOVBazepu0545DDNGYHz7ar9ZgZ1fMU8/MA4mVxp5rkBWRi6OXIy3Q== - dependencies: - asap "~2.0.6" +promise-polyfill@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.2.0.tgz#367394726da7561457aba2133c9ceefbd6267da0" + integrity sha512-k/TC0mIcPVF6yHhUvwAp7cvL6I2fFV7TzF1DuGPI8mBh4QQazf36xCKEHKTZKRysEoTQoQdKyP25J8MPJp7j5g== -prompts@^2.0.1: - version "2.3.2" - resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.3.2.tgz#480572d89ecf39566d2bd3fe2c9fccb7c4c0b068" - integrity sha512-Q06uKs2CkNYVID0VqwfAl9mipo99zkBv/n2JtWY89Yxa3ZabWSrs0e2KTudKVa3peLUvYXMefDqIleLPVUBZMA== +prop-types-extra@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/prop-types-extra/-/prop-types-extra-1.1.1.tgz#58c3b74cbfbb95d304625975aa2f0848329a010b" + integrity sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew== dependencies: - kleur "^3.0.3" - sisteransi "^1.0.4" + react-is "^16.3.2" + warning "^4.0.0" -prop-types@^15.5.6, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@~15.7.2: +prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -9049,24 +3356,6 @@ prop-types@^15.5.6, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, object-assign "^4.1.1" react-is "^16.8.1" -proxy-addr@~2.0.5: - version "2.0.6" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf" - integrity sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw== - dependencies: - forwarded "~0.1.2" - ipaddr.js "1.9.1" - -prr@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" - integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY= - -psl@^1.1.28: - version "1.8.0" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" - integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== - public-encrypt@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" @@ -9079,14 +3368,6 @@ public-encrypt@^4.0.0: randombytes "^2.0.1" safe-buffer "^5.1.2" -pump@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" - integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - pump@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" @@ -9095,15 +3376,6 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" -pumpify@^1.3.3: - version "1.5.1" - resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce" - integrity sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ== - dependencies: - duplexify "^3.6.0" - inherits "^2.0.3" - pump "^2.0.0" - punycode@1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" @@ -9114,35 +3386,12 @@ punycode@^1.2.4: resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= -punycode@^2.1.0, punycode@^2.1.1: +punycode@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -q@^1.1.2: - version "1.5.1" - resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" - integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= - -qs@6.7.0: - version "6.7.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" - integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== - -qs@~6.5.2: - version "6.5.2" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" - integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== - -query-string@^4.1.0: - version "4.3.4" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" - integrity sha1-u7aTucqRXCMlFbIosaArYJBD2+s= - dependencies: - object-assign "^4.1.0" - strict-uri-encode "^1.0.0" - -querystring-es3@^0.2.0: +querystring-es3@0.2.1, querystring-es3@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" integrity sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM= @@ -9152,19 +3401,31 @@ querystring@0.2.0: resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= -querystringify@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e" - integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA== +querystring@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.1.tgz#40d77615bb09d16902a85c3e38aa8b5ed761c2dd" + integrity sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +queue@6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/queue/-/queue-6.0.2.tgz#b91525283e2315c7553d2efa18d83e76432fed65" + integrity sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA== + dependencies: + inherits "~2.0.3" -raf@^3.4.0, raf@^3.4.1: +raf@^3.4.0: version "3.4.1" resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39" integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA== dependencies: performance-now "^2.1.0" -randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0: +randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== @@ -9179,282 +3440,125 @@ randomfill@^1.0.3: randombytes "^2.0.5" safe-buffer "^5.1.0" -range-parser@^1.2.1, range-parser@~1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" - integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== - -raw-body@2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" - integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q== +raw-body@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.1.tgz#30ac82f98bb5ae8c152e67149dac8d55153b168c" + integrity sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA== dependencies: bytes "3.1.0" - http-errors "1.7.2" + http-errors "1.7.3" iconv-lite "0.4.24" unpipe "1.0.0" -react-app-polyfill@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/react-app-polyfill/-/react-app-polyfill-1.0.6.tgz#890f8d7f2842ce6073f030b117de9130a5f385f0" - integrity sha512-OfBnObtnGgLGfweORmdZbyEz+3dgVePQBb3zipiaDsMHV1NpWm0rDFYIVXFV/AK+x4VIIfWHhrdMIeoTLyRr2g== +react-dom@^17.0.2: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" + integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA== dependencies: - core-js "^3.5.0" + loose-envify "^1.1.0" object-assign "^4.1.1" - promise "^8.0.3" - raf "^3.4.1" - regenerator-runtime "^0.13.3" - whatwg-fetch "^3.0.0" - -react-dev-utils@^10.2.1: - version "10.2.1" - resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-10.2.1.tgz#f6de325ae25fa4d546d09df4bb1befdc6dd19c19" - integrity sha512-XxTbgJnYZmxuPtY3y/UV0D8/65NKkmaia4rXzViknVnZeVlklSh8u6TnaEYPfAi/Gh1TP4mEOXHI6jQOPbeakQ== - dependencies: - "@babel/code-frame" "7.8.3" - address "1.1.2" - browserslist "4.10.0" - chalk "2.4.2" - cross-spawn "7.0.1" - detect-port-alt "1.1.6" - escape-string-regexp "2.0.0" - filesize "6.0.1" - find-up "4.1.0" - fork-ts-checker-webpack-plugin "3.1.1" - global-modules "2.0.0" - globby "8.0.2" - gzip-size "5.1.1" - immer "1.10.0" - inquirer "7.0.4" - is-root "2.1.0" - loader-utils "1.2.3" - open "^7.0.2" - pkg-up "3.1.0" - react-error-overlay "^6.0.7" - recursive-readdir "2.2.2" - shell-quote "1.7.2" - strip-ansi "6.0.0" - text-table "0.2.0" - -react-document-title@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/react-document-title/-/react-document-title-2.0.3.tgz#bbf922a0d71412fc948245e4283b2412df70f2b9" - integrity sha1-u/kioNcUEvyUgkXkKDskEt9w8rk= - dependencies: - prop-types "^15.5.6" - react-side-effect "^1.0.2" + scheduler "^0.20.2" -react-dom@~16.13.1: - version "16.13.1" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.13.1.tgz#c1bd37331a0486c078ee54c4740720993b2e0e7f" - integrity sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag== +react-dropzone@9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/react-dropzone/-/react-dropzone-9.0.0.tgz#4f5223cdcb4d3bd8a66e3298c4041eb0c75c4634" + integrity sha512-wZ2o9B2qkdE3RumWhfyZT9swgJYJPeU5qHEcMU8weYpmLex1eeWX0CC32/Y0VutB+BBi2D+iePV/YZIiB4kZGw== dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" + attr-accept "^1.1.3" + file-selector "^0.1.8" prop-types "^15.6.2" - scheduler "^0.19.1" - -react-error-overlay@^6.0.7: - version "6.0.7" - resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.7.tgz#1dcfb459ab671d53f660a991513cb2f0a0553108" - integrity sha512-TAv1KJFh3RhqxNvhzxj6LeT5NWklP6rDr2a0jaTfsZ5wSZWHOGeqQyejUp3xxLfPt2UpyJEcVQB/zyPcmonNFA== + prop-types-extra "^1.1.0" -react-fontawesome@~1.7.1: - version "1.7.1" - resolved "https://registry.yarnpkg.com/react-fontawesome/-/react-fontawesome-1.7.1.tgz#f74f5a338fef3ee3b379820109c1cba47290f035" - integrity sha512-kottReWW1I9Uupub6A5YX4VK7qfpFnEjAcm5zB4Aepst7iofONT27GJYdTcRsj7q5uQu9PXBL7GsxAFKANNUVg== +react-hotkeys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/react-hotkeys/-/react-hotkeys-2.0.0.tgz#a7719c7340cbba888b0e9184f806a9ec0ac2c53f" + integrity sha512-3n3OU8vLX/pfcJrR3xJ1zlww6KS1kEJt0Whxc4FiGV+MJrQ1mYSYI3qS/11d2MJDFm8IhOXMTFQirfu6AVOF6Q== dependencies: - prop-types "^15.5.6" + prop-types "^15.6.1" -react-google-login@~5.1.14: - version "5.1.20" - resolved "https://registry.yarnpkg.com/react-google-login/-/react-google-login-5.1.20.tgz#06afbf5fd9013455ae3bfba93054630df203542d" - integrity sha512-/5vDx8Hy7Wo1fO1VC/0e5D6/ZGWgIgvcscI8mYZUQ653QOFf0c4GhTnKkebX5uE7m5rAB/2bzzZIUlIesGqWig== - dependencies: - "@types/react" "*" - prop-types "^15.6.0" +react-is@16.10.2: + version "16.10.2" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.10.2.tgz#984120fd4d16800e9a738208ab1fba422d23b5ab" + integrity sha512-INBT1QEgtcCCgvccr5/86CfD71fw9EPmDxgiJX4I2Ddr6ZsV6iFXsuby+qWJPtmNuMY0zByTsG4468P7nHuNWA== + +react-is@17.0.2: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" + integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== -react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4: +react-is@^16.13.1, react-is@^16.3.2, react-is@^16.7.0, react-is@^16.8.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== -react-konva@~16.13.0-2: - version "16.13.0-3" - resolved "https://registry.yarnpkg.com/react-konva/-/react-konva-16.13.0-3.tgz#9ef1e813c8b2dd61b54b26151ccbdeed52b89a80" - integrity sha512-U9az1RidQD4c64oZoHiiv6GU6h2ggHO30nZDqfQWuBTH+Bl2wij6Z0NgbUyVyN1IpKIgXRiEKMS9idlxhAzTXQ== +react-konva@^17.0.2-5: + version "17.0.2-5" + resolved "https://registry.yarnpkg.com/react-konva/-/react-konva-17.0.2-5.tgz#e70b0acf323402de0a540f27b300fbe7ed151849" + integrity sha512-IyzdfqRDK8r1ulp/jbLPX18AuO+n5yNtL0+4T0QEUsgArRqIl/VRCG1imA5mYJBk0cBNC5+fWDHN+HWEW62ZEQ== dependencies: - react-reconciler "^0.25.1" - scheduler "^0.19.1" + react-reconciler "~0.26.2" + scheduler "^0.20.2" react-lifecycles-compat@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== -react-popper@^1.3.6: - version "1.3.7" - resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-1.3.7.tgz#f6a3471362ef1f0d10a4963673789de1baca2324" - integrity sha512-nmqYTx7QVjCm3WUZLeuOomna138R1luC4EqkW3hxJUrAe+3eNz3oFCLYdnPwILfn0mX1Ew2c3wctrjlUMYYUww== +react-query@^3.22.0: + version "3.22.0" + resolved "https://registry.yarnpkg.com/react-query/-/react-query-3.22.0.tgz#f59bbc48737c6f34070aee85037caf5a990de565" + integrity sha512-S1vv7N7np3N9MCCIE8vNGG7LpPhHDn4yQmY1sUZ+ABBuSU/h4Rtz+0qLpKh+Vs0/icvdsdrzI2LA2p0yqnLzRg== dependencies: - "@babel/runtime" "^7.1.2" - create-react-context "^0.3.0" - deep-equal "^1.1.1" - popper.js "^1.14.4" - prop-types "^15.6.1" - typed-styles "^0.0.7" - warning "^4.0.2" + "@babel/runtime" "^7.5.5" + broadcast-channel "^3.4.1" + match-sorter "^6.0.2" -react-reconciler@^0.25.1: - version "0.25.1" - resolved "https://registry.yarnpkg.com/react-reconciler/-/react-reconciler-0.25.1.tgz#f9814d59d115e1210762287ce987801529363aaa" - integrity sha512-R5UwsIvRcSs3w8n9k3tBoTtUHdVhu9u84EG7E5M0Jk9F5i6DA1pQzPfUZd6opYWGy56MJOtV3VADzy6DRwYDjw== +react-reconciler@~0.26.2: + version "0.26.2" + resolved "https://registry.yarnpkg.com/react-reconciler/-/react-reconciler-0.26.2.tgz#bbad0e2d1309423f76cf3c3309ac6c96e05e9d91" + integrity sha512-nK6kgY28HwrMNwDnMui3dvm3rCFjZrcGiuwLc5COUipBK5hWHLOxMJhSnSomirqWwjPBJKV1QcbkI0VJr7Gl1Q== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" - prop-types "^15.6.2" - scheduler "^0.19.1" + scheduler "^0.20.2" -react-redux@~7.2.0: - version "7.2.2" - resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.2.tgz#03862e803a30b6b9ef8582dadcc810947f74b736" - integrity sha512-8+CQ1EvIVFkYL/vu6Olo7JFLWop1qRUeb46sGtIMDCSpgwPQq8fPLpirIB0iTqFe9XYEFPHssdX8/UwN6pAkEA== +react-redux@^7.2.5: + version "7.2.5" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.5.tgz#213c1b05aa1187d9c940ddfc0b29450957f6a3b8" + integrity sha512-Dt29bNyBsbQaysp6s/dN0gUodcq+dVKKER8Qv82UrpeygwYeX1raTtil7O/fftw/rFqzaf6gJhDZRkkZnn6bjg== dependencies: "@babel/runtime" "^7.12.1" + "@types/react-redux" "^7.1.16" hoist-non-react-statics "^3.3.2" loose-envify "^1.4.0" prop-types "^15.7.2" react-is "^16.13.1" -react-resize-detector@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/react-resize-detector/-/react-resize-detector-2.3.0.tgz#57bad1ae26a28a62a2ddb678ba6ffdf8fa2b599c" - integrity sha512-oCAddEWWeFWYH5FAcHdBYcZjAw9fMzRUK9sWSx6WvSSOPVRxcHd5zTIGy/mOus+AhN/u6T4TMiWxvq79PywnJQ== +react-refresh@0.8.3: + version "0.8.3" + resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f" + integrity sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg== + +react-resize-detector@^6.6.3: + version "6.7.6" + resolved "https://registry.yarnpkg.com/react-resize-detector/-/react-resize-detector-6.7.6.tgz#4416994e5ead7eba76606e3a248a1dfca49b67a3" + integrity sha512-/6RZlul1yePSoYJxWxmmgjO320moeLC/khrwpEVIL+D2EjLKhqOwzFv+H8laMbImVj7Zu4FlMa0oA7au3/ChjQ== dependencies: + "@types/resize-observer-browser" "^0.1.6" lodash.debounce "^4.0.8" lodash.throttle "^4.1.1" - prop-types "^15.6.0" - resize-observer-polyfill "^1.5.0" - -react-router-dom@~5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.1.2.tgz#06701b834352f44d37fbb6311f870f84c76b9c18" - integrity sha512-7BPHAaIwWpZS074UKaw1FjVdZBSVWEk8IuDXdB+OkLb8vd/WRQIpA4ag9WQk61aEfQs47wHyjWUoUGGZxpQXew== - dependencies: - "@babel/runtime" "^7.1.2" - history "^4.9.0" - loose-envify "^1.3.1" - prop-types "^15.6.2" - react-router "5.1.2" - tiny-invariant "^1.0.2" - tiny-warning "^1.0.0" - -react-router@5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.1.2.tgz#6ea51d789cb36a6be1ba5f7c0d48dd9e817d3418" - integrity sha512-yjEuMFy1ONK246B+rsa0cUam5OeAQ8pyclRDgpxuSCrAlJ1qN9uZ5IgyKC7gQg0w8OM50NXHEegPh/ks9YuR2A== - dependencies: - "@babel/runtime" "^7.1.2" - history "^4.9.0" - hoist-non-react-statics "^3.1.0" - loose-envify "^1.3.1" - mini-create-react-context "^0.3.0" - path-to-regexp "^1.7.0" - prop-types "^15.6.2" - react-is "^16.6.0" - tiny-invariant "^1.0.2" - tiny-warning "^1.0.0" - -react-scripts@~3.4.1: - version "3.4.1" - resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-3.4.1.tgz#f551298b5c71985cc491b9acf3c8e8c0ae3ada0a" - integrity sha512-JpTdi/0Sfd31mZA6Ukx+lq5j1JoKItX7qqEK4OiACjVQletM1P38g49d9/D0yTxp9FrSF+xpJFStkGgKEIRjlQ== - dependencies: - "@babel/core" "7.9.0" - "@svgr/webpack" "4.3.3" - "@typescript-eslint/eslint-plugin" "^2.10.0" - "@typescript-eslint/parser" "^2.10.0" - babel-eslint "10.1.0" - babel-jest "^24.9.0" - babel-loader "8.1.0" - babel-plugin-named-asset-import "^0.3.6" - babel-preset-react-app "^9.1.2" - camelcase "^5.3.1" - case-sensitive-paths-webpack-plugin "2.3.0" - css-loader "3.4.2" - dotenv "8.2.0" - dotenv-expand "5.1.0" - eslint "^6.6.0" - eslint-config-react-app "^5.2.1" - eslint-loader "3.0.3" - eslint-plugin-flowtype "4.6.0" - eslint-plugin-import "2.20.1" - eslint-plugin-jsx-a11y "6.2.3" - eslint-plugin-react "7.19.0" - eslint-plugin-react-hooks "^1.6.1" - file-loader "4.3.0" - fs-extra "^8.1.0" - html-webpack-plugin "4.0.0-beta.11" - identity-obj-proxy "3.0.0" - jest "24.9.0" - jest-environment-jsdom-fourteen "1.0.1" - jest-resolve "24.9.0" - jest-watch-typeahead "0.4.2" - mini-css-extract-plugin "0.9.0" - optimize-css-assets-webpack-plugin "5.0.3" - pnp-webpack-plugin "1.6.4" - postcss-flexbugs-fixes "4.1.0" - postcss-loader "3.0.0" - postcss-normalize "8.0.1" - postcss-preset-env "6.7.0" - postcss-safe-parser "4.0.1" - react-app-polyfill "^1.0.6" - react-dev-utils "^10.2.1" - resolve "1.15.0" - resolve-url-loader "3.1.1" - sass-loader "8.0.2" - semver "6.3.0" - style-loader "0.23.1" - terser-webpack-plugin "2.3.5" - ts-pnp "1.1.6" - url-loader "2.3.0" - webpack "4.42.0" - webpack-dev-server "3.10.3" - webpack-manifest-plugin "2.2.0" - workbox-webpack-plugin "4.3.1" - optionalDependencies: - fsevents "2.1.2" - -react-shortcuts@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/react-shortcuts/-/react-shortcuts-2.1.0.tgz#e1ac50be4f847b96a473ce0ad877edadc5067ec6" - integrity sha512-yETQgoy/KRCOPjdlGSnfTjyVHwJYsFoHtVmuLzJABYBAnilpm1M14DhDavuAAMElbaQlXunOwKjh0Oq3I6Vt0A== - dependencies: - combokeys "^3.0.1" - events "^1.0.2" - invariant "^2.1.0" - just-reduce-object "^1.0.3" - platform "^1.3.0" - prop-types "^15.5.8" - -react-side-effect@^1.0.2: - version "1.2.0" - resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-1.2.0.tgz#0e940c78faba0c73b9b0eba9cd3dda8dfb7e7dae" - integrity sha512-v1ht1aHg5k/thv56DRcjw+WtojuuDHFUgGfc+bFHOWsF4ZK6C2V57DO0Or0GPsg6+LSTE0M6Ry/gfzhzSwbc5w== - dependencies: - shallowequal "^1.0.1" + resize-observer-polyfill "^1.5.1" -react-smooth@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/react-smooth/-/react-smooth-1.0.5.tgz#94ae161d7951cdd893ccb7099d031d342cb762ad" - integrity sha512-eW057HT0lFgCKh8ilr0y2JaH2YbNcuEdFpxyg7Gf/qDKk9hqGMyXryZJ8iMGJEuKH0+wxS0ccSsBBB3W8yCn8w== +react-smooth@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/react-smooth/-/react-smooth-2.0.0.tgz#561647b33e498b2e25f449b3c6689b2e9111bf91" + integrity sha512-wK4dBBR6P21otowgMT9toZk+GngMplGS1O5gk+2WSiHEXIrQgDvhR5IIlT74Vtu//qpTcipkgo21dD7a7AUNxw== dependencies: - lodash "~4.17.4" - prop-types "^15.6.0" + fast-equals "^2.0.0" raf "^3.4.0" - react-transition-group "^2.5.0" + react-transition-group "2.9.0" -react-transition-group@^2.3.1, react-transition-group@^2.5.0: +react-transition-group@2.9.0: version "2.9.0" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.9.0.tgz#df9cdb025796211151a436c69a8f3b97b5b07c8d" integrity sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg== @@ -9464,51 +3568,22 @@ react-transition-group@^2.3.1, react-transition-group@^2.5.0: prop-types "^15.6.2" react-lifecycles-compat "^3.0.4" -react@~16.13.1: - version "16.13.1" - resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e" - integrity sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w== +react@^17.0.2: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" + integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" - prop-types "^15.6.2" - -reactstrap@^8.6.0: - version "8.6.0" - resolved "https://registry.yarnpkg.com/reactstrap/-/reactstrap-8.6.0.tgz#baee0d12990c9fef3c82199fb05e84d9f0af1a26" - integrity sha512-03/UMbLPR6MhVStVUfCLuKh8xh4JOtNVkRxDB9/uHixN+cEQPOpSYa0K69YyK1/2YdZBs2qS6y0cQkK8NQKBHA== - dependencies: - "@babel/runtime" "^7.2.0" - classnames "^2.2.3" - prop-types "^15.5.8" - react-popper "^1.3.6" - react-transition-group "^2.3.1" -read-pkg-up@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" - integrity sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4= +read-pkg-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-3.0.0.tgz#3ed496685dba0f8fe118d0691dc51f4a1ff96f07" + integrity sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc= dependencies: find-up "^2.0.0" - read-pkg "^2.0.0" - -read-pkg-up@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-4.0.0.tgz#1b221c6088ba7799601c808f91161c66e58f8978" - integrity sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA== - dependencies: - find-up "^3.0.0" read-pkg "^3.0.0" -read-pkg@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" - integrity sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg= - dependencies: - load-json-file "^2.0.0" - normalize-package-data "^2.3.2" - path-type "^2.0.0" - read-pkg@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" @@ -9518,7 +3593,7 @@ read-pkg@^3.0.0: normalize-package-data "^2.3.2" path-type "^3.0.0" -"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6: +readable-stream@^2.0.2, readable-stream@^2.3.3, readable-stream@^2.3.6: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -9531,7 +3606,7 @@ read-pkg@^3.0.0: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.6.0: +readable-stream@^3.5.0, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -9540,22 +3615,6 @@ readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.6.0: string_decoder "^1.1.1" util-deprecate "^1.0.1" -readdirp@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" - integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ== - dependencies: - graceful-fs "^4.1.11" - micromatch "^3.1.10" - readable-stream "^2.0.2" - -readdirp@~3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.4.0.tgz#9fdccdf9e9155805449221ac645e8303ab5b9ada" - integrity sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ== - dependencies: - picomatch "^2.2.1" - readdirp@~3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e" @@ -9563,64 +3622,46 @@ readdirp@~3.5.0: dependencies: picomatch "^2.2.1" -realpath-native@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.1.0.tgz#2003294fea23fb0672f2476ebe22fcf498a2d65c" - integrity sha512-wlgPA6cCIIg9gKz0fgAPjnzh4yR/LnXovwuo9hvyGvx3h8nX4+/iLZplfUWasXpqD8BdnGnP5njOFjkUwPzvjA== +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== dependencies: - util.promisify "^1.0.0" + picomatch "^2.2.1" -recharts-scale@^0.4.2: - version "0.4.3" - resolved "https://registry.yarnpkg.com/recharts-scale/-/recharts-scale-0.4.3.tgz#040b4f638ed687a530357292ecac880578384b59" - integrity sha512-t8p5sccG9Blm7c1JQK/ak9O8o95WGhNXD7TXg/BW5bYbVlr6eCeRBNpgyigD4p6pSSMehC5nSvBUPj6F68rbFA== +recharts-scale@^0.4.4: + version "0.4.5" + resolved "https://registry.yarnpkg.com/recharts-scale/-/recharts-scale-0.4.5.tgz#0969271f14e732e642fcc5bd4ab270d6e87dd1d9" + integrity sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w== dependencies: decimal.js-light "^2.4.1" -recharts@~1.8.5: - version "1.8.5" - resolved "https://registry.yarnpkg.com/recharts/-/recharts-1.8.5.tgz#ca94a3395550946334a802e35004ceb2583fdb12" - integrity sha512-tM9mprJbXVEBxjM7zHsIy6Cc41oO/pVYqyAsOHLxlJrbNBuLs0PHB3iys2M+RqCF0//k8nJtZF6X6swSkWY3tg== +recharts@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/recharts/-/recharts-2.1.2.tgz#ceeb01e53fb46da0d946a1e0f82d783ddf5b9d06" + integrity sha512-rwFQT6T4imhLzD1kYtg9ql8YOesbFRdSwZi95KWgi5udbBdLGRCR4SgaPO8kf0URHcC23mdRbLLTMYCnXng7zQ== dependencies: + "@types/d3-scale" "^3.0.0" + "@types/d3-shape" "^2.0.0" classnames "^2.2.5" - core-js "^2.6.10" - d3-interpolate "^1.3.0" - d3-scale "^2.1.0" - d3-shape "^1.2.0" - lodash "^4.17.5" - prop-types "^15.6.0" - react-resize-detector "^2.3.0" - react-smooth "^1.0.5" - recharts-scale "^0.4.2" - reduce-css-calc "^1.3.0" - -recursive-readdir@2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/recursive-readdir/-/recursive-readdir-2.2.2.tgz#9946fb3274e1628de6e36b2f6714953b4845094f" - integrity sha512-nRCcW9Sj7NuZwa2XvH9co8NPeXUBhZP7CRKJtU+cS6PW9FpCIFoI5ib0NT1ZrbNuPoRy0ylyCaUL8Gih4LSyFg== - dependencies: - minimatch "3.0.4" - -reduce-css-calc@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz#747c914e049614a4c9cfbba629871ad1d2927716" - integrity sha1-dHyRTgSWFKTJz7umKYca0dKSdxY= - dependencies: - balanced-match "^0.4.2" - math-expression-evaluator "^1.2.14" - reduce-function-call "^1.0.1" - -reduce-function-call@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/reduce-function-call/-/reduce-function-call-1.0.3.tgz#60350f7fb252c0a67eb10fd4694d16909971300f" - integrity sha512-Hl/tuV2VDgWgCSEeWMLwxLZqX7OK59eU1guxXsRKTAyeYimivsKdtcV4fu3r710tpG5GmDKDhQ0HSZLExnNmyQ== + d3-interpolate "^2.0.1" + d3-scale "^3.2.3" + d3-shape "^2.0.0" + eventemitter3 "^4.0.1" + lodash "^4.17.19" + react-is "16.10.2" + react-resize-detector "^6.6.3" + react-smooth "^2.0.0" + recharts-scale "^0.4.4" + reduce-css-calc "^2.1.8" + +reduce-css-calc@^2.1.8: + version "2.1.8" + resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-2.1.8.tgz#7ef8761a28d614980dc0c982f772c93f7a99de03" + integrity sha512-8liAVezDmUcH+tdzoEGrhfbGcP7nOV4NkGE3a74+qqvE7nt9i4sKLGBuZNOnpI4WiGksiNPklZxva80061QiPg== dependencies: - balanced-match "^1.0.0" - -redux-localstorage@~0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/redux-localstorage/-/redux-localstorage-0.4.1.tgz#faf6d719c581397294d811473ffcedee065c933c" - integrity sha1-+vbXGcWBOXKU2BFHP/zt7gZckzw= + css-unit-converter "^1.1.1" + postcss-value-parser "^3.3.0" redux-logger@~3.0.6: version "3.0.6" @@ -9641,262 +3682,65 @@ redux-thunk@~2.3.0: resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622" integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw== -redux@^4.0.4, redux@~4.0.5: - version "4.0.5" - resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f" - integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w== - dependencies: - loose-envify "^1.4.0" - symbol-observable "^1.2.0" - -regenerate-unicode-properties@^8.2.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec" - integrity sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA== +redux@^4.0.0, redux@^4.0.4, redux@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.1.1.tgz#76f1c439bb42043f985fbd9bf21990e60bd67f47" + integrity sha512-hZQZdDEM25UY2P493kPYuKqviVwZ58lEmGQNeQ+gXa+U0gYPUBf7NKYazbe3m+bs/DzM/ahN12DbF+NG8i0CWw== dependencies: - regenerate "^1.4.0" - -regenerate@^1.4.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.1.tgz#cad92ad8e6b591773485fbe05a485caf4f457e6f" - integrity sha512-j2+C8+NtXQgEKWk49MMP5P/u2GhnahTtVkRIHr5R5lVRlbKvmQ+oS+A5aLKWp2ma5VkT8sh6v+v4hbH0YHR66A== - -regenerator-runtime@^0.11.0: - version "0.11.1" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" - integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== - -regenerator-runtime@^0.13.3: - version "0.13.5" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697" - integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA== + "@babel/runtime" "^7.9.2" regenerator-runtime@^0.13.4: - version "0.13.7" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" - integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew== - -regenerator-transform@^0.14.2: - version "0.14.4" - resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.4.tgz#5266857896518d1616a78a0479337a30ea974cc7" - integrity sha512-EaJaKPBI9GvKpvUz2mz4fhx7WPgvwRLY9v3hlNHWmAuJHI13T4nwKnNvm5RWJzEdnI5g5UwtOww+S8IdoUC2bw== - dependencies: - "@babel/runtime" "^7.8.4" - private "^0.1.8" - -regex-not@^1.0.0, regex-not@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" - integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== - dependencies: - extend-shallow "^3.0.2" - safe-regex "^1.1.0" - -regex-parser@2.2.10: - version "2.2.10" - resolved "https://registry.yarnpkg.com/regex-parser/-/regex-parser-2.2.10.tgz#9e66a8f73d89a107616e63b39d4deddfee912b37" - integrity sha512-8t6074A68gHfU8Neftl0Le6KTDwfGAj7IyjPIMSfikI2wJUTHDMaIq42bUsfVnj8mhx0R+45rdUXHGpN164avA== + version "0.13.9" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" + integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== -regexp.prototype.flags@^1.2.0, regexp.prototype.flags@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz#7aba89b3c13a64509dabcf3ca8d9fbb9bdf5cb75" - integrity sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ== +regexp.prototype.flags@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz#7ef352ae8d159e758c0eadca6f8fcb4eef07be26" + integrity sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" -regexpp@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" - integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw== - -regexpp@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2" - integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q== - -regexpu-core@^4.7.0: - version "4.7.0" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.0.tgz#fcbf458c50431b0bb7b45d6967b8192d91f3d938" - integrity sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ== - dependencies: - regenerate "^1.4.0" - regenerate-unicode-properties "^8.2.0" - regjsgen "^0.5.1" - regjsparser "^0.6.4" - unicode-match-property-ecmascript "^1.0.4" - unicode-match-property-value-ecmascript "^1.2.0" - -regjsgen@^0.5.1: - version "0.5.2" - resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.2.tgz#92ff295fb1deecbf6ecdab2543d207e91aa33733" - integrity sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A== - -regjsparser@^0.6.4: - version "0.6.4" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.4.tgz#a769f8684308401a66e9b529d2436ff4d0666272" - integrity sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw== - dependencies: - jsesc "~0.5.0" - -relateurl@^0.2.7: - version "0.2.7" - resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" - integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk= - -remove-trailing-separator@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" - integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= - -renderkid@^2.0.1: - version "2.0.3" - resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-2.0.3.tgz#380179c2ff5ae1365c522bf2fcfcff01c5b74149" - integrity sha512-z8CLQp7EZBPCwCnncgf9C4XAi3WR0dv+uWu/PjIyhhAb5d6IJ/QZqlHFprHeKT+59//V6BNUsLbvN8+2LarxGA== - dependencies: - css-select "^1.1.0" - dom-converter "^0.2" - htmlparser2 "^3.3.0" - strip-ansi "^3.0.0" - utila "^0.4.0" - -repeat-element@^1.1.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" - integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== - -repeat-string@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" - integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= - -request-promise-core@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.3.tgz#e9a3c081b51380dfea677336061fea879a829ee9" - integrity sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ== - dependencies: - lodash "^4.17.15" - -request-promise-native@^1.0.5: - version "1.0.8" - resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.8.tgz#a455b960b826e44e2bf8999af64dff2bfe58cb36" - integrity sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ== - dependencies: - request-promise-core "1.1.3" - stealthy-require "^1.1.1" - tough-cookie "^2.3.3" - -request@^2.87.0, request@^2.88.0: - version "2.88.2" - resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" - integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.8.0" - caseless "~0.12.0" - combined-stream "~1.0.6" - extend "~3.0.2" - forever-agent "~0.6.1" - form-data "~2.3.2" - har-validator "~5.1.3" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.19" - oauth-sign "~0.9.0" - performance-now "^2.1.0" - qs "~6.5.2" - safe-buffer "^5.1.2" - tough-cookie "~2.5.0" - tunnel-agent "^0.6.0" - uuid "^3.3.2" - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= - -require-main-filename@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" - integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE= +regexpp@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" + integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== -require-main-filename@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" - integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== +remove-accents@0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/remove-accents/-/remove-accents-0.4.2.tgz#0a43d3aaae1e80db919e07ae254b285d9e1c7bb5" + integrity sha1-CkPTqq4egNuRngeuJUsoXZ4ce7U= -requires-port@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" - integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== -resize-observer-polyfill@^1.5.0: +resize-observer-polyfill@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464" integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg== -resolve-cwd@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" - integrity sha1-AKn3OHVW4nA46uIyyqNypqWbZlo= - dependencies: - resolve-from "^3.0.0" - -resolve-from@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" - integrity sha1-six699nWiBvItuZTM17rywoYh0g= - resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== -resolve-pathname@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd" - integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng== - -resolve-url-loader@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-3.1.1.tgz#28931895fa1eab9be0647d3b2958c100ae3c0bf0" - integrity sha512-K1N5xUjj7v0l2j/3Sgs5b8CjrrgtC70SmdCuZiJ8tSyb5J+uk3FoeZ4b7yTnH6j7ngI+Bc5bldHJIa8hYdu2gQ== - dependencies: - adjust-sourcemap-loader "2.0.0" - camelcase "5.3.1" - compose-function "3.0.3" - convert-source-map "1.7.0" - es6-iterator "2.0.3" - loader-utils "1.2.3" - postcss "7.0.21" - rework "1.0.1" - rework-visit "1.0.0" - source-map "0.6.1" - -resolve-url@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" - integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= - -resolve@1.1.7: - version "1.1.7" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" - integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= - -resolve@1.15.0: - version "1.15.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.0.tgz#1b7ca96073ebb52e741ffd799f6b39ea462c67f5" - integrity sha512-+hTmAldEGE80U2wJJDC1lebb5jWqvTYAfm3YZ1ckk1gBr0MnCqUKlwK1e+anaFljIl+F5tR5IoZcm4ZDA1zMQw== +resolve@^1.10.0, resolve@^1.17.0, resolve@^1.20.0: + version "1.20.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" + integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== dependencies: + is-core-module "^2.2.0" path-parse "^1.0.6" -resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.15.1, resolve@^1.3.2, resolve@^1.8.1: - version "1.17.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" - integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== +resolve@^2.0.0-next.3: + version "2.0.0-next.3" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.3.tgz#d41016293d4a8586a39ca5d9b5f15cbea1f55e46" + integrity sha512-W8LucSynKUIDu9ylraa7ueVZ7hc0uAgJBxVsQSKOXOyle8a93qXhcz+XAXZ8bIq2d6i4Ehddn6Evt+0/UwKk6Q== dependencies: + is-core-module "^2.2.0" path-parse "^1.0.6" restore-cursor@^3.1.0: @@ -9907,50 +3751,15 @@ restore-cursor@^3.1.0: onetime "^5.1.0" signal-exit "^3.0.2" -ret@~0.1.10: - version "0.1.15" - resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" - integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== - -retry@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" - integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= - -rework-visit@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/rework-visit/-/rework-visit-1.0.0.tgz#9945b2803f219e2f7aca00adb8bc9f640f842c9a" - integrity sha1-mUWygD8hni96ygCtuLyfZA+ELJo= - -rework@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/rework/-/rework-1.0.1.tgz#30806a841342b54510aa4110850cd48534144aa7" - integrity sha1-MIBqhBNCtUUQqkEQhQzUhTQUSqc= - dependencies: - convert-source-map "^0.3.3" - css "^2.0.0" - -rgb-regex@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/rgb-regex/-/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1" - integrity sha1-wODWiC3w4jviVKR16O3UGRX+rrE= - -rgba-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3" - integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM= - -rimraf@2.6.3: - version "2.6.3" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" - integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== - dependencies: - glob "^7.1.3" +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== -rimraf@^2.5.4, rimraf@^2.6.3, rimraf@^2.7.1: - version "2.7.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" - integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== +rimraf@3.0.2, rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== dependencies: glob "^7.1.3" @@ -9962,254 +3771,87 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^3.0.0" inherits "^2.0.1" -rsvp@^4.8.4: - version "4.8.5" - resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" - integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== - -run-async@^2.2.0, run-async@^2.4.0: - version "2.4.1" - resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" - integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== - -run-queue@^1.0.0, run-queue@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47" - integrity sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec= +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== dependencies: - aproba "^1.1.1" + queue-microtask "^1.2.2" -rxjs@^6.5.3, rxjs@^6.5.5: - version "6.5.5" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.5.tgz#c5c884e3094c8cfee31bf27eb87e54ccfc87f9ec" - integrity sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ== +rxjs@^6.6.7: + version "6.6.7" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" + integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== dependencies: tslib "^1.9.0" -safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -safe-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" - integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= - dependencies: - ret "~0.1.10" +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -sane@^4.0.3: - version "4.1.0" - resolved "https://registry.yarnpkg.com/sane/-/sane-4.1.0.tgz#ed881fd922733a6c461bc189dc2b6c006f3ffded" - integrity sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA== - dependencies: - "@cnakazawa/watch" "^1.0.3" - anymatch "^2.0.0" - capture-exit "^2.0.0" - exec-sh "^0.3.2" - execa "^1.0.0" - fb-watchman "^2.0.0" - micromatch "^3.1.4" - minimist "^1.1.1" - walker "~1.0.5" - -sanitize.css@^10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/sanitize.css/-/sanitize.css-10.0.0.tgz#b5cb2547e96d8629a60947544665243b1dc3657a" - integrity sha512-vTxrZz4dX5W86M6oVWVdOVe72ZiPs41Oi7Z6Km4W5Turyz28mrXSJhhEBZoRtzJWIv3833WKVwLSDWWkEfupMg== - -sass-loader@8.0.2: - version "8.0.2" - resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-8.0.2.tgz#debecd8c3ce243c76454f2e8290482150380090d" - integrity sha512-7o4dbSK8/Ol2KflEmSco4jTjQoV988bM82P9CZdmo9hR3RLnvNc0ufMNdMrB0caq38JQ/FgF4/7RcbcfKzxoFQ== - dependencies: - clone-deep "^4.0.1" - loader-utils "^1.2.3" - neo-async "^2.6.1" - schema-utils "^2.6.1" - semver "^6.3.0" - -sass@^1.32.12: - version "1.32.12" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.32.12.tgz#a2a47ad0f1c168222db5206444a30c12457abb9f" - integrity sha512-zmXn03k3hN0KaiVTjohgkg98C3UowhL1/VSGdj4/VAAiMKGQOE80PFPxFP2Kyq0OUskPKcY5lImkhBKEHlypJA== +sass@^1.39.0: + version "1.39.0" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.39.0.tgz#6c64695d1c437767c8f1a4e471288e831f81d035" + integrity sha512-F4o+RhJkNOIG0b6QudYU8c78ZADKZjKDk5cyrf8XTKWfrgbtyVVXImFstJrc+1pkQDCggyidIOytq6gS4gCCZg== dependencies: chokidar ">=3.0.0 <4.0.0" -sax@^1.2.4, sax@~1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" - integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== - -saxes@^3.1.9: - version "3.1.11" - resolved "https://registry.yarnpkg.com/saxes/-/saxes-3.1.11.tgz#d59d1fd332ec92ad98a2e0b2ee644702384b1c5b" - integrity sha512-Ydydq3zC+WYDJK1+gRxRapLIED9PWeSuuS41wqyoRmzvhhh9nc+QQrVMKJYzJFULazeGhzSV0QleN2wD3boh2g== - dependencies: - xmlchars "^2.1.1" - -scheduler@^0.19.1: - version "0.19.1" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.19.1.tgz#4f3e2ed2c1a7d65681f4c854fa8c5a1ccb40f196" - integrity sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA== +scheduler@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91" + integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" -schema-utils@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770" - integrity sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g== - dependencies: - ajv "^6.1.0" - ajv-errors "^1.0.0" - ajv-keywords "^3.1.0" - -schema-utils@^2.5.0, schema-utils@^2.6.0, schema-utils@^2.6.1, schema-utils@^2.6.4, schema-utils@^2.6.5: - version "2.7.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.0.tgz#17151f76d8eae67fbbf77960c33c676ad9f4efc7" - integrity sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A== - dependencies: - "@types/json-schema" "^7.0.4" - ajv "^6.12.2" - ajv-keywords "^3.4.1" - seed-random@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/seed-random/-/seed-random-2.2.0.tgz#2a9b19e250a817099231a5b99a4daf80b7fbed54" integrity sha1-KpsZ4lCoFwmSMaW5mk2vgLf77VQ= -select-hose@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" - integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= - -selfsigned@^1.10.7: - version "1.10.7" - resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.7.tgz#da5819fd049d5574f28e88a9bcc6dbc6e6f3906b" - integrity sha512-8M3wBCzeWIJnQfl43IKwOmC4H/RAp50S8DF60znzjW5GVqTcSe2vWclt7hmYVPkKPlHWOu5EaWOMZ2Y6W8ZXTA== - dependencies: - node-forge "0.9.0" - semver-compare@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w= -semver-regex@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-2.0.0.tgz#a93c2c5844539a770233379107b38c7b4ac9d338" - integrity sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw== +semver-regex@^3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-3.1.3.tgz#b2bcc6f97f63269f286994e297e229b6245d0dc3" + integrity sha512-Aqi54Mk9uYTjVexLnR67rTyBusmwd04cLkHy9hNvk3+G3nT2Oyg7E0l4XVbOaNwIvQ3hHeYxGcyEy+mKreyBFQ== -"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: +"semver@2 || 3 || 4 || 5": version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@6.3.0, semver@^6.0.0, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: +semver@^6.0.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" - integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== - -semver@^7.3.2: - version "7.3.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" - integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== - -send@0.17.1: - version "0.17.1" - resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" - integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== +semver@^7.2.1, semver@^7.3.5: + version "7.3.5" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" + integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== dependencies: - debug "2.6.9" - depd "~1.1.2" - destroy "~1.0.4" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "~1.7.2" - mime "1.6.0" - ms "2.1.1" - on-finished "~2.3.0" - range-parser "~1.2.1" - statuses "~1.5.0" - -serialize-javascript@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61" - integrity sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ== - -serialize-javascript@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-3.1.0.tgz#8bf3a9170712664ef2561b44b691eafe399214ea" - integrity sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg== - dependencies: - randombytes "^2.1.0" - -serve-index@^1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" - integrity sha1-03aNabHn2C5c4FD/9bRTvqEqkjk= - dependencies: - accepts "~1.3.4" - batch "0.6.1" - debug "2.6.9" - escape-html "~1.0.3" - http-errors "~1.6.2" - mime-types "~2.1.17" - parseurl "~1.3.2" - -serve-static@1.14.1: - version "1.14.1" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" - integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== - dependencies: - encodeurl "~1.0.2" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.17.1" - -set-blocking@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= - -set-value@^2.0.0, set-value@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" - integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== - dependencies: - extend-shallow "^2.0.1" - is-extendable "^0.1.1" - is-plain-object "^2.0.3" - split-string "^3.0.1" + lru-cache "^6.0.0" setimmediate@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= -setprototypeof@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" - integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== - setprototypeof@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" @@ -10223,35 +3865,6 @@ sha.js@^2.4.0, sha.js@^2.4.8: inherits "^2.0.1" safe-buffer "^5.0.1" -shallow-clone@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-0.1.2.tgz#5909e874ba77106d73ac414cfec1ffca87d97060" - integrity sha1-WQnodLp3EG1zrEFM/sH/yofZcGA= - dependencies: - is-extendable "^0.1.1" - kind-of "^2.0.1" - lazy-cache "^0.2.3" - mixin-object "^2.0.1" - -shallow-clone@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" - integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== - dependencies: - kind-of "^6.0.2" - -shallowequal@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" - integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== - -shebang-command@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" - integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= - dependencies: - shebang-regex "^1.0.0" - shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -10259,11 +3872,6 @@ shebang-command@^2.0.0: dependencies: shebang-regex "^3.0.0" -shebang-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" - integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= - shebang-regex@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" @@ -10274,60 +3882,25 @@ shell-quote@1.7.2: resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2" integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg== -shellwords@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" - integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== - -side-channel@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.2.tgz#df5d1abadb4e4bf4af1cd8852bf132d2f7876947" - integrity sha512-7rL9YlPHg7Ancea1S96Pa8/QWb4BtXL/TZvS6B8XFetGBeuhAsfmUspK6DokBeZ64+Kj9TCNRD/30pVz1BvQNA== +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== dependencies: - es-abstract "^1.17.0-next.1" - object-inspect "^1.7.0" + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" -signal-exit@^3.0.0, signal-exit@^3.0.2: +signal-exit@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== -simple-swizzle@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" - integrity sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo= - dependencies: - is-arrayish "^0.3.1" - -sisteransi@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" - integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== - -slash@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" - integrity sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU= - -slash@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" - integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A== - slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== -slice-ansi@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" - integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== - dependencies: - ansi-styles "^3.2.0" - astral-regex "^1.0.0" - is-fullwidth-code-point "^2.0.0" - slice-ansi@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787" @@ -10346,131 +3919,23 @@ slice-ansi@^4.0.0: astral-regex "^2.0.0" is-fullwidth-code-point "^3.0.0" -snapdragon-node@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" - integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== - dependencies: - define-property "^1.0.0" - isobject "^3.0.0" - snapdragon-util "^3.0.1" - -snapdragon-util@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" - integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== - dependencies: - kind-of "^3.2.0" - -snapdragon@^0.8.1: - version "0.8.2" - resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" - integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== - dependencies: - base "^0.11.1" - debug "^2.2.0" - define-property "^0.2.5" - extend-shallow "^2.0.1" - map-cache "^0.2.2" - source-map "^0.5.6" - source-map-resolve "^0.5.0" - use "^3.1.0" - -socket.io-client@~2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-2.3.0.tgz#14d5ba2e00b9bcd145ae443ab96b3f86cbcc1bb4" - integrity sha512-cEQQf24gET3rfhxZ2jJ5xzAOo/xhZwK+mOqtGRg5IowZsMgwvHwnf/mCRapAAkadhM26y+iydgwsXGObBB5ZdA== - dependencies: - backo2 "1.0.2" - base64-arraybuffer "0.1.5" - component-bind "1.0.0" - component-emitter "1.2.1" - debug "~4.1.0" - engine.io-client "~3.4.0" - has-binary2 "~1.0.2" - has-cors "1.1.0" - indexof "0.0.1" - object-component "0.0.3" - parseqs "0.0.5" - parseuri "0.0.5" - socket.io-parser "~3.3.0" - to-array "0.1.4" - -socket.io-parser@~3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.3.0.tgz#2b52a96a509fdf31440ba40fed6094c7d4f1262f" - integrity sha512-hczmV6bDgdaEbVqhAeVMM/jfUfzuEZHsQg6eOmLgJht6G3mPKMxYm75w2+qhAQZ+4X+1+ATZ+QFKeOZD5riHng== - dependencies: - component-emitter "1.2.1" - debug "~3.1.0" - isarray "2.0.1" - -sockjs-client@1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.4.0.tgz#c9f2568e19c8fd8173b4997ea3420e0bb306c7d5" - integrity sha512-5zaLyO8/nri5cua0VtOrFXBPK1jbL4+1cebT/mmKA1E1ZXOvJrII75bPu0l0k843G/+iAbhEqzyKr0w/eCCj7g== - dependencies: - debug "^3.2.5" - eventsource "^1.0.7" - faye-websocket "~0.11.1" - inherits "^2.0.3" - json3 "^3.3.2" - url-parse "^1.4.3" - -sockjs@0.3.19: - version "0.3.19" - resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.19.tgz#d976bbe800af7bd20ae08598d582393508993c0d" - integrity sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw== - dependencies: - faye-websocket "^0.10.0" - uuid "^3.0.1" - -sort-keys@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" - integrity sha1-RBttTTRnmPG05J6JIK37oOVD+a0= - dependencies: - is-plain-obj "^1.0.0" - -source-list-map@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" - integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== - -source-map-resolve@^0.5.0, source-map-resolve@^0.5.2: - version "0.5.3" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" - integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== - dependencies: - atob "^2.1.2" - decode-uri-component "^0.2.0" - resolve-url "^0.2.1" - source-map-url "^0.4.0" - urix "^0.1.0" +source-map@0.7.3: + version "0.7.3" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" + integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== -source-map-support@^0.5.6, source-map-support@~0.5.12: - version "0.5.19" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" - integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== +source-map@0.8.0-beta.0: + version "0.8.0-beta.0" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.8.0-beta.0.tgz#d4c1bb42c3f7ee925f005927ba10709e0d1d1f11" + integrity sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA== dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map-url@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" - integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= + whatwg-url "^7.0.0" -source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: +source-map@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -source-map@^0.5.0, source-map@^0.5.6: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= - spdx-correct@^3.0.0: version "3.1.1" resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" @@ -10493,102 +3958,34 @@ spdx-expression-parse@^3.0.0: spdx-license-ids "^3.0.0" spdx-license-ids@^3.0.0: - version "3.0.5" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654" - integrity sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q== - -spdy-transport@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" - integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw== - dependencies: - debug "^4.1.0" - detect-node "^2.0.4" - hpack.js "^2.1.6" - obuf "^1.1.2" - readable-stream "^3.0.6" - wbuf "^1.7.3" - -spdy@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.2.tgz#b74f466203a3eda452c02492b91fb9e84a27677b" - integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA== - dependencies: - debug "^4.1.0" - handle-thing "^2.0.0" - http-deceiver "^1.2.7" - select-hose "^2.0.0" - spdy-transport "^3.0.0" - -split-string@^3.0.1, split-string@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" - integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== - dependencies: - extend-shallow "^3.0.0" + version "3.0.10" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.10.tgz#0d9becccde7003d6c658d487dd48a32f0bf3014b" + integrity sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA== sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= -sshpk@^1.7.0: - version "1.16.1" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" - integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== - dependencies: - asn1 "~0.2.3" - assert-plus "^1.0.0" - bcrypt-pbkdf "^1.0.0" - dashdash "^1.12.0" - ecc-jsbn "~0.1.1" - getpass "^0.1.1" - jsbn "~0.1.0" - safer-buffer "^2.0.2" - tweetnacl "~0.14.0" - -ssri@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8" - integrity sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA== +stacktrace-parser@0.1.10: + version "0.1.10" + resolved "https://registry.yarnpkg.com/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz#29fb0cae4e0d0b85155879402857a1639eb6051a" + integrity sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg== dependencies: - figgy-pudding "^3.5.1" + type-fest "^0.7.1" -ssri@^7.0.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-7.1.0.tgz#92c241bf6de82365b5c7fb4bd76e975522e1294d" - integrity sha512-77/WrDZUWocK0mvA5NTRQyveUf+wsrIc6vyrxpS8tVvYBcX215QbafrJR3KtkpskIzoFLqqNuuYQvxaMjXJ/0g== - dependencies: - figgy-pudding "^3.5.1" - minipass "^3.1.1" - -stable@^0.1.8: - version "0.1.8" - resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" - integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== - -stack-utils@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.2.tgz#33eba3897788558bebfc2db059dc158ec36cebb8" - integrity sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA== - -static-extend@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" - integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= - dependencies: - define-property "^0.2.5" - object-copy "^0.1.0" - -"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2", statuses@~1.5.0: +"statuses@>= 1.5.0 < 2": version "1.5.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= -stealthy-require@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" - integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks= +stream-browserify@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-3.0.0.tgz#22b0a2850cdf6503e73085da1fc7b7d0c2122f2f" + integrity sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA== + dependencies: + inherits "~2.0.4" + readable-stream "^3.5.0" stream-browserify@^2.0.1: version "2.0.2" @@ -10598,13 +3995,15 @@ stream-browserify@^2.0.1: inherits "~2.0.1" readable-stream "^2.0.2" -stream-each@^1.1.0: - version "1.2.3" - resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.3.tgz#ebe27a0c389b04fbcc233642952e10731afa9bae" - integrity sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw== +stream-http@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-3.1.1.tgz#0370a8017cf8d050b9a8554afe608f043eaff564" + integrity sha512-S7OqaYu0EkFpgeGFb/NPOoPLxFko7TPqtEeFg5DXPB4v/KETHG0Ln6fRFrNezoelpaDKmycEmmZ81cC9DAwgYg== dependencies: - end-of-stream "^1.1.0" - stream-shift "^1.0.0" + builtin-status-codes "^3.0.0" + inherits "^2.0.4" + readable-stream "^3.6.0" + xtend "^4.0.2" stream-http@^2.7.2: version "2.8.3" @@ -10617,101 +4016,63 @@ stream-http@^2.7.2: to-arraybuffer "^1.0.0" xtend "^4.0.0" -stream-shift@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" - integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== - -strict-uri-encode@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" - integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM= +stream-parser@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/stream-parser/-/stream-parser-0.3.1.tgz#1618548694420021a1182ff0af1911c129761773" + integrity sha1-FhhUhpRCACGhGC/wrxkRwSl2F3M= + dependencies: + debug "2" string-argv@0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da" integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg== -string-length@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed" - integrity sha1-1A27aGo6zpYMHP/KVivyxF+DY+0= - dependencies: - astral-regex "^1.0.0" - strip-ansi "^4.0.0" - -string-length@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/string-length/-/string-length-3.1.0.tgz#107ef8c23456e187a8abd4a61162ff4ac6e25837" - integrity sha512-Ttp5YvkGm5v9Ijagtaz1BnN+k9ObpvS0eIBblPMp2YWL8FBmi9qblQ9fexc2k/CXFgrTIteU3jAw3payCnwSTA== - dependencies: - astral-regex "^1.0.0" - strip-ansi "^5.2.0" - -string-width@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" - integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= - dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - strip-ansi "^3.0.0" - -string-width@^2.0.0, string-width@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" - integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== - dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^4.0.0" - -string-width@^3.0.0, string-width@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" - integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== - dependencies: - emoji-regex "^7.0.1" - is-fullwidth-code-point "^2.0.0" - strip-ansi "^5.1.0" +string-hash@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/string-hash/-/string-hash-1.1.3.tgz#e8aafc0ac1855b4666929ed7dd1275df5d6c811b" + integrity sha1-6Kr8CsGFW0Zmkp7X3RJ1311sgRs= string-width@^4.1.0, string-width@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" - integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg== + version "4.2.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.2.tgz#dafd4f9559a7585cfba529c6a0a4f73488ebd4c5" + integrity sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA== dependencies: emoji-regex "^8.0.0" is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.0" -string.prototype.matchall@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.2.tgz#48bb510326fb9fdeb6a33ceaa81a6ea04ef7648e" - integrity sha512-N/jp6O5fMf9os0JU3E72Qhf590RSRZU/ungsL/qJUYVTNv7hTG0P/dbPjxINVN9jpscu3nzYwKESU3P3RY5tOg== +string.prototype.matchall@^4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.5.tgz#59370644e1db7e4c0c045277690cf7b01203c4da" + integrity sha512-Z5ZaXO0svs0M2xd/6By3qpeKpLKd9mO4v4q3oMEQrk8Ck4xOD5d5XeBOOjGrmVZZ/AHB1S0CgG4N5r1G9N3E2Q== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.17.0" - has-symbols "^1.0.1" - internal-slot "^1.0.2" - regexp.prototype.flags "^1.3.0" - side-channel "^1.0.2" - -string.prototype.trimend@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913" - integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g== + es-abstract "^1.18.2" + get-intrinsic "^1.1.1" + has-symbols "^1.0.2" + internal-slot "^1.0.3" + regexp.prototype.flags "^1.3.1" + side-channel "^1.0.4" + +string.prototype.trimend@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" + integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.17.5" -string.prototype.trimstart@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54" - integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw== +string.prototype.trimstart@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed" + integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.17.5" -string_decoder@^1.0.0, string_decoder@^1.1.1: +string_decoder@1.3.0, string_decoder@^1.0.0, string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== @@ -10741,76 +4102,44 @@ strip-ansi@6.0.0, strip-ansi@^6.0.0: dependencies: ansi-regex "^5.0.0" -strip-ansi@^3.0.0, strip-ansi@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= - dependencies: - ansi-regex "^2.0.0" - -strip-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" - integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= - dependencies: - ansi-regex "^3.0.0" - -strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" - integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== - dependencies: - ansi-regex "^4.1.0" - strip-bom@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= -strip-comments@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/strip-comments/-/strip-comments-1.0.2.tgz#82b9c45e7f05873bee53f37168af930aa368679d" - integrity sha512-kL97alc47hoyIQSV165tTt9rG5dn4w1dNnBhOQ3bOU1Nc1hel09jnXANaHJ7vzHLd4Ju8kseDGzlev96pghLFw== - dependencies: - babel-extract-comments "^1.0.0" - babel-plugin-transform-object-rest-spread "^6.26.0" - -strip-eof@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" - integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= - strip-final-newline@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== -strip-json-comments@^3.0.1: - version "3.1.0" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.0.tgz#7638d31422129ecf4457440009fba03f9f9ac180" - integrity sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w== +strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -style-loader@0.23.1: - version "0.23.1" - resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.23.1.tgz#cb9154606f3e771ab6c4ab637026a1049174d925" - integrity sha512-XK+uv9kWwhZMZ1y7mysB+zoihsEj4wneFWAS5qoiLwzW0WzSqMrrsIy+a3zkQJq0ipFtBpX5W3MqyRIBF/WFGg== +styled-jsx@4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-4.0.1.tgz#ae3f716eacc0792f7050389de88add6d5245b9e9" + integrity sha512-Gcb49/dRB1k8B4hdK8vhW27Rlb2zujCk1fISrizCcToIs+55B4vmUM0N9Gi4nnVfFZWe55jRdWpAqH1ldAKWvQ== dependencies: - loader-utils "^1.1.0" - schema-utils "^1.0.0" + "@babel/plugin-syntax-jsx" "7.14.5" + "@babel/types" "7.15.0" + convert-source-map "1.7.0" + loader-utils "1.2.3" + source-map "0.7.3" + string-hash "1.1.3" + stylis "3.5.4" + stylis-rule-sheet "0.0.10" -stylehacks@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-4.0.3.tgz#6718fcaf4d1e07d8a1318690881e8d96726a71d5" - integrity sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g== - dependencies: - browserslist "^4.0.0" - postcss "^7.0.0" - postcss-selector-parser "^3.0.0" +stylis-rule-sheet@0.0.10: + version "0.0.10" + resolved "https://registry.yarnpkg.com/stylis-rule-sheet/-/stylis-rule-sheet-0.0.10.tgz#44e64a2b076643f4b52e5ff71efc04d8c3c4a430" + integrity sha512-nTbZoaqoBnmK+ptANthb10ZRZOGC+EmTLLUxeYIuHNkEKcmKgXX1XWKkUBT2Ac4es3NybooPe0SmvKdhKJZAuw== -supports-color@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" - integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= +stylis@3.5.4: + version "3.5.4" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.5.4.tgz#f665f25f5e299cf3d64654ab949a57c768b73fbe" + integrity sha512-8/3pSmthWM7lsPBKv7NXkzn2Uc9W7NotcwGNpJaa3k7WMM1XDCA4MgT5k/8BIexd5ydZdboXtU90XH9Ec4Bv/Q== supports-color@^5.3.0: version "5.5.0" @@ -10819,45 +4148,21 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" -supports-color@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" - integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== dependencies: - has-flag "^3.0.0" + has-flag "^4.0.0" -supports-color@^7.0.0, supports-color@^7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" - integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g== +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== dependencies: has-flag "^4.0.0" -svg-parser@^2.0.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5" - integrity sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ== - -svgo@^1.0.0, svgo@^1.2.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.3.2.tgz#b6dc511c063346c9e415b81e43401145b96d4167" - integrity sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw== - dependencies: - chalk "^2.4.1" - coa "^2.0.2" - css-select "^2.0.0" - css-select-base-adapter "^0.1.1" - css-tree "1.0.0-alpha.37" - csso "^4.0.2" - js-yaml "^3.13.1" - mkdirp "~0.5.1" - object.values "^1.1.0" - sax "~1.2.4" - stable "^0.1.8" - unquote "~1.1.1" - util.promisify "~1.0.0" - -svgsaver@~0.9.0: +svgsaver@^0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/svgsaver/-/svgsaver-0.9.0.tgz#93d5dbb3f840953b8df0a14a942f4cc8d552335e" integrity sha1-k9Xbs/hAlTuN8KFKlC9MyNVSM14= @@ -10865,151 +4170,56 @@ svgsaver@~0.9.0: computed-styles "^1.1.2" file-saver "^1.3.3" -symbol-observable@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" - integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== - -symbol-tree@^3.2.2: - version "3.2.4" - resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" - integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== - -table@^5.2.3: - version "5.4.6" - resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" - integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug== - dependencies: - ajv "^6.10.2" - lodash "^4.17.14" - slice-ansi "^2.1.0" - string-width "^3.0.0" - -tapable@^1.0.0, tapable@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" - integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== - -terser-webpack-plugin@2.3.5: - version "2.3.5" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-2.3.5.tgz#5ad971acce5c517440ba873ea4f09687de2f4a81" - integrity sha512-WlWksUoq+E4+JlJ+h+U+QUzXpcsMSSNXkDy9lBVkSqDn1w23Gg29L/ary9GeJVYCGiNJJX7LnVc4bwL1N3/g1w== - dependencies: - cacache "^13.0.1" - find-cache-dir "^3.2.0" - jest-worker "^25.1.0" - p-limit "^2.2.2" - schema-utils "^2.6.4" - serialize-javascript "^2.1.2" - source-map "^0.6.1" - terser "^4.4.3" - webpack-sources "^1.4.3" - -terser-webpack-plugin@^1.4.3: - version "1.4.4" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.4.tgz#2c63544347324baafa9a56baaddf1634c8abfc2f" - integrity sha512-U4mACBHIegmfoEe5fdongHESNJWqsGU+W0S/9+BmYGVQDw1+c2Ow05TpMhxjPK1sRb7cuYq1BPl1e5YHJMTCqA== - dependencies: - cacache "^12.0.2" - find-cache-dir "^2.1.0" - is-wsl "^1.1.0" - schema-utils "^1.0.0" - serialize-javascript "^3.1.0" - source-map "^0.6.1" - terser "^4.1.2" - webpack-sources "^1.4.0" - worker-farm "^1.7.0" +tabbable@^5.1.4: + version "5.2.1" + resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-5.2.1.tgz#e3fda7367ddbb172dcda9f871c0fdb36d1c4cd9c" + integrity sha512-40pEZ2mhjaZzK0BnI+QGNjJO8UYx9pP5v7BGe17SORTO0OEuuaAwQTkAp8whcZvqon44wKFOikD+Al11K3JICQ== -terser@^4.1.2, terser@^4.4.3, terser@^4.6.3: - version "4.8.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.0.tgz#63056343d7c70bb29f3af665865a46fe03a0df17" - integrity sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw== +table@^6.0.9: + version "6.7.1" + resolved "https://registry.yarnpkg.com/table/-/table-6.7.1.tgz#ee05592b7143831a8c94f3cee6aae4c1ccef33e2" + integrity sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg== dependencies: - commander "^2.20.0" - source-map "~0.6.1" - source-map-support "~0.5.12" + ajv "^8.0.1" + lodash.clonedeep "^4.5.0" + lodash.truncate "^4.4.2" + slice-ansi "^4.0.0" + string-width "^4.2.0" + strip-ansi "^6.0.0" -test-exclude@^5.2.3: - version "5.2.3" - resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-5.2.3.tgz#c3d3e1e311eb7ee405e092dac10aefd09091eac0" - integrity sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g== - dependencies: - glob "^7.1.3" - minimatch "^3.0.4" - read-pkg-up "^4.0.0" - require-main-filename "^2.0.0" +tapable@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.0.tgz#5c373d281d9c672848213d0e037d1c4165ab426b" + integrity sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw== -text-table@0.2.0, text-table@^0.2.0: +text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= -throat@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/throat/-/throat-4.1.0.tgz#89037cbc92c56ab18926e6ba4cbb200e15672a6a" - integrity sha1-iQN8vJLFarGJJua6TLsgDhVnKmo= - -through2@^2.0.0: - version "2.0.5" - resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" - integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== - dependencies: - readable-stream "~2.3.6" - xtend "~4.0.1" - -through@^2.3.6, through@^2.3.8: +through@^2.3.8: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= -thunky@^1.0.2: - version "1.1.0" - resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" - integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== - -timers-browserify@^2.0.4: - version "2.0.11" - resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.11.tgz#800b1f3eee272e5bc53ee465a04d0e804c31211f" - integrity sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ== +timers-browserify@2.0.12, timers-browserify@^2.0.4: + version "2.0.12" + resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.12.tgz#44a45c11fbf407f34f97bccd1577c652361b00ee" + integrity sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ== dependencies: setimmediate "^1.0.4" -timsort@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" - integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= - tiny-emitter@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423" integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q== -tiny-invariant@^1.0.2: - version "1.1.0" - resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875" - integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw== - -tiny-warning@^1.0.0, tiny-warning@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" - integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== - -tmp@^0.0.33: - version "0.0.33" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" - integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== +tippy.js@5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-5.1.2.tgz#5ac91233c59ab482ef5988cffe6e08bd26528e66" + integrity sha512-Qtrv2wqbRbaKMUb6bWWBQWPayvcDKNrGlvihxtsyowhT7RLGEh1STWuy6EMXC6QLkfKPB2MLnf8W2mzql9VDAw== dependencies: - os-tmpdir "~1.0.2" - -tmpl@1.0.x: - version "1.0.4" - resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1" - integrity sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE= - -to-array@0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890" - integrity sha1-F+bBH3PdTz10zaek/zI46a2b+JA= + popper.js "^1.16.0" to-arraybuffer@^1.0.0: version "1.0.1" @@ -11021,21 +4231,6 @@ to-fast-properties@^2.0.0: resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= -to-object-path@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" - integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= - dependencies: - kind-of "^3.0.2" - -to-regex-range@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" - integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= - dependencies: - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" @@ -11043,29 +4238,11 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -to-regex@^3.0.1, to-regex@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" - integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== - dependencies: - define-property "^2.0.2" - extend-shallow "^3.0.2" - regex-not "^1.0.2" - safe-regex "^1.1.0" - toidentifier@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== -tough-cookie@^2.3.3, tough-cookie@^2.3.4, tough-cookie@^2.5.0, tough-cookie@~2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" - integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== - dependencies: - psl "^1.1.28" - punycode "^2.1.1" - tr46@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" @@ -11073,30 +4250,35 @@ tr46@^1.0.1: dependencies: punycode "^2.1.0" -ts-pnp@1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.1.6.tgz#389a24396d425a0d3162e96d2b4638900fdc289a" - integrity sha512-CrG5GqAAzMT7144Cl+UIFP7mz/iIhiy+xQ6GGcnjTezhALT02uPMRw7tgDSESgB5MsfKt55+GPWw4ir1kVtMIQ== - ts-pnp@^1.1.6: version "1.2.0" resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92" integrity sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw== -tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0: - version "1.13.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" - integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q== +tsconfig-paths@^3.11.0, tsconfig-paths@^3.9.0: + version "3.11.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.11.0.tgz#954c1fe973da6339c78e06b03ce2e48810b65f36" + integrity sha512-7ecdYDnIdmv639mmDwslG6KQg1Z9STTz1j7Gcz0xa+nshh/gKDAHcPxRbWOsA3SPp0tXP2leTcY9Kw+NAkfZzA== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.1" + minimist "^1.2.0" + strip-bom "^3.0.0" -tslib@^1.9.3: +tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tsutils@^3.17.1: - version "3.17.1" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" - integrity sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g== +tslib@^2.0.0, tslib@^2.0.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" + integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== + +tsutils@^3.21.0: + version "3.21.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" + integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== dependencies: tslib "^1.8.1" @@ -11105,68 +4287,38 @@ tty-browserify@0.0.0: resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" integrity sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY= -tunnel-agent@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" - integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= - dependencies: - safe-buffer "^5.0.1" - -tweetnacl@^0.14.3, tweetnacl@~0.14.0: - version "0.14.5" - resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" - integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= +tty-browserify@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.1.tgz#3f05251ee17904dfd0677546670db9651682b811" + integrity sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw== -type-check@~0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" - integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== dependencies: - prelude-ls "~1.1.2" - -type-fest@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1" - integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ== + prelude-ls "^1.2.1" -type-fest@^0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" - integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== -type-is@~1.6.17, type-is@~1.6.18: - version "1.6.18" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" - integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== - dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" - -type@^1.0.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" - integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== -type@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/type/-/type-2.0.0.tgz#5f16ff6ef2eb44f260494dae271033b29c09a9c3" - integrity sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow== +type-fest@^0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.7.1.tgz#8dda65feaf03ed78f0a3f9678f1869147f7c5c48" + integrity sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg== typed-function@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/typed-function/-/typed-function-2.0.0.tgz#15ab3825845138a8b1113bd89e60cd6a435739e8" integrity sha512-Hhy1Iwo/e4AtLZNK10ewVVcP2UEs408DS35ubP825w/YgSBK1KVLwALvvIG4yX75QJrxjCpcWkzkVRB0BwwYlA== -typed-styles@^0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/typed-styles/-/typed-styles-0.0.7.tgz#93392a008794c4595119ff62dde6809dbc40a3d9" - integrity sha512-pzP0PWoZUhsECYjABgCGQlRGL1n7tOHsgwYv3oIiEpJwGhFTuty/YNeduxQYzXXa3Ge5BdT6sHYIQYpl4uJ+5Q== - -typedarray@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" - integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= - typescript-compare@^0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/typescript-compare/-/typescript-compare-0.0.2.tgz#7ee40a400a406c2ea0a7e551efd3309021d5f425" @@ -11186,120 +4338,41 @@ typescript-tuple@^2.2.1: dependencies: typescript-compare "^0.0.2" -unicode-canonical-property-names-ecmascript@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" - integrity sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ== - -unicode-match-property-ecmascript@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz#8ed2a32569961bce9227d09cd3ffbb8fed5f020c" - integrity sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg== - dependencies: - unicode-canonical-property-names-ecmascript "^1.0.4" - unicode-property-aliases-ecmascript "^1.0.4" - -unicode-match-property-value-ecmascript@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz#0d91f600eeeb3096aa962b1d6fc88876e64ea531" - integrity sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ== - -unicode-property-aliases-ecmascript@^1.0.4: - version "1.1.0" - resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz#dd57a99f6207bedff4628abefb94c50db941c8f4" - integrity sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg== - -union-value@^1.0.0: +unbox-primitive@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" - integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" + integrity sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw== dependencies: - arr-union "^3.1.0" - get-value "^2.0.6" - is-extendable "^0.1.1" - set-value "^2.0.1" - -uniq@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" - integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8= - -uniqs@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" - integrity sha1-/+3ks2slKQaW5uFl1KWe25mOawI= + function-bind "^1.1.1" + has-bigints "^1.0.1" + has-symbols "^1.0.2" + which-boxed-primitive "^1.0.2" -unique-filename@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" - integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ== - dependencies: - unique-slug "^2.0.0" +unfetch@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-4.2.0.tgz#7e21b0ef7d363d8d9af0fb929a5555f6ef97a3be" + integrity sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA== -unique-slug@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c" - integrity sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w== +unload@2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/unload/-/unload-2.2.0.tgz#ccc88fdcad345faa06a92039ec0f80b488880ef7" + integrity sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA== dependencies: - imurmurhash "^0.1.4" - -universalify@^0.1.0: - version "0.1.2" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" - integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + "@babel/runtime" "^7.6.2" + detect-node "^2.0.4" -unpipe@1.0.0, unpipe@~1.0.0: +unpipe@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= -unquote@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.1.tgz#8fded7324ec6e88a0ff8b905e7c098cdc086d544" - integrity sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ= - -unset-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" - integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= - dependencies: - has-value "^0.3.1" - isobject "^3.0.0" - -upath@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" - integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== - uri-js@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" - integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== dependencies: punycode "^2.1.0" -urix@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" - integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= - -url-loader@2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-2.3.0.tgz#e0e2ef658f003efb8ca41b0f3ffbf76bab88658b" - integrity sha512-goSdg8VY+7nPZKUEChZSEtW5gjbS66USIGCeSJ1OVOJ7Yfuh/36YxCwMi5HVEJh6mqUYOoy3NJ0vlOMrWsSHog== - dependencies: - loader-utils "^1.2.3" - mime "^2.4.4" - schema-utils "^2.5.0" - -url-parse@^1.4.3: - version "1.4.7" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278" - integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg== - dependencies: - querystringify "^2.1.1" - requires-port "^1.0.0" - url@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" @@ -11308,34 +4381,25 @@ url@^0.11.0: punycode "1.3.2" querystring "0.2.0" -use@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" - integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== +use-resize-observer@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/use-resize-observer/-/use-resize-observer-8.0.0.tgz#69bd80c1ddd94f3758563fe107efb25fed85067a" + integrity sha512-n0iKSeiQpJCyaFh5JA0qsVLBIovsF4EIIR1G6XiBwKJN66ZrD4Oj62bjcuTAATPKiSp6an/2UZZxCf/67fk3sQ== + dependencies: + "@juggle/resize-observer" "^3.3.1" + +use-subscription@1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/use-subscription/-/use-subscription-1.5.1.tgz#73501107f02fad84c6dd57965beb0b75c68c42d1" + integrity sha512-Xv2a1P/yReAjAbhylMfFplFKj9GssgTwN7RlcTxBujFQcloStWNDQdc4g4NRWH9xS4i/FDk04vQBptAXoF3VcA== + dependencies: + object-assign "^4.1.1" util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= -util.promisify@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.0.tgz#440f7165a459c9a16dc145eb8e72f35687097030" - integrity sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA== - dependencies: - define-properties "^1.1.2" - object.getownpropertydescriptors "^2.0.3" - -util.promisify@^1.0.0, util.promisify@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.1.tgz#6baf7774b80eeb0f7520d8b81d07982a59abbaee" - integrity sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.2" - has-symbols "^1.0.1" - object.getownpropertydescriptors "^2.1.0" - util@0.10.3: version "0.10.3" resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" @@ -11343,6 +4407,18 @@ util@0.10.3: dependencies: inherits "2.0.1" +util@0.12.4, util@^0.12.0: + version "0.12.4" + resolved "https://registry.yarnpkg.com/util/-/util-0.12.4.tgz#66121a31420df8f01ca0c464be15dfa1d1850253" + integrity sha512-bxZ9qtSlGUWSOy9Qa9Xgk11kSslpuZwaxCg4sNIDj6FLucDab2JxnHwyNTCpHMtK1MjoQiWQ6DiUMZYbSrO+Sw== + dependencies: + inherits "^2.0.3" + is-arguments "^1.0.4" + is-generator-function "^1.0.7" + is-typed-array "^1.1.3" + safe-buffer "^5.1.2" + which-typed-array "^1.1.2" + util@^0.11.0: version "0.11.1" resolved "https://registry.yarnpkg.com/util/-/util-0.11.1.tgz#3236733720ec64bb27f6e26f421aaa2e1b588d61" @@ -11350,38 +4426,23 @@ util@^0.11.0: dependencies: inherits "2.0.3" -utila@^0.4.0, utila@~0.4: - version "0.4.0" - resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" - integrity sha1-ihagXURWV6Oupe7MWxKk+lN5dyw= +uuid@8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== -utils-merge@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= - -uuid@8.2.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.2.0.tgz#cb10dd6b118e2dada7d0cd9730ba7417c93d920e" - integrity sha512-CYpGiFTUrmI6OBMkAdjSDM0k5h8SkkiTP4WAjQgDgNB1S3Ou9VBEvr6q0Kv2H1mMk7IWfxYGpMH5sd5AvcIV2Q== - -uuid@^3.0.1, uuid@^3.3.2: - version "3.4.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" - integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== - -uuidv4@~6.1.1: - version "6.1.1" - resolved "https://registry.yarnpkg.com/uuidv4/-/uuidv4-6.1.1.tgz#6565b4f2be7d6f841c14106f420fdb701eae5c81" - integrity sha512-ZplGb1SHFMVH3l7PUQl2Uwo+FpJQV6IPOoU+MjjbqrNYQolqbGwv+/sn9F+AGMsMOgGz3r9JN3ztGUi0VzMxmw== +uuidv4@^6.2.12: + version "6.2.12" + resolved "https://registry.yarnpkg.com/uuidv4/-/uuidv4-6.2.12.tgz#e8c1d1d733c3fa4963d4610b8a3a09b4ec58ca96" + integrity sha512-UnN4ThIYWhv3ZUE8UwDnnCvh4JafCNu+sQkxmLyjCVwK3rjLfkg3DYiEv6oCMDIAIVEDP4INg4kX/C5hKaRzZA== dependencies: - "@types/uuid" "8.0.0" - uuid "8.2.0" + "@types/uuid" "8.3.1" + uuid "8.3.2" v8-compile-cache@^2.0.3: - version "2.1.1" - resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745" - integrity sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ== + version "2.3.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" + integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== validate-npm-package-license@^3.0.1: version "3.0.4" @@ -11391,240 +4452,31 @@ validate-npm-package-license@^3.0.1: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" -value-equal@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c" - integrity sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw== - -vary@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= - -vendors@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.4.tgz#e2b800a53e7a29b93506c3cf41100d16c4c4ad8e" - integrity sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w== - -verror@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" - integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= - dependencies: - assert-plus "^1.0.0" - core-util-is "1.0.2" - extsprintf "^1.2.0" - -vm-browserify@^1.0.1: +vm-browserify@1.1.2, vm-browserify@^1.0.1: version "1.1.2" resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== -w3c-hr-time@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" - integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ== - dependencies: - browser-process-hrtime "^1.0.0" - -w3c-xmlserializer@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-1.1.2.tgz#30485ca7d70a6fd052420a3d12fd90e6339ce794" - integrity sha512-p10l/ayESzrBMYWRID6xbuCKh2Fp77+sA0doRuGn4tTIMrrZVeqfpKjXHY+oDh3K4nLdPgNwMTVP6Vp4pvqbNg== - dependencies: - domexception "^1.0.1" - webidl-conversions "^4.0.2" - xml-name-validator "^3.0.0" - -walker@^1.0.7, walker@~1.0.5: - version "1.0.7" - resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb" - integrity sha1-L3+bj9ENZ3JisYqITijRlhjgKPs= - dependencies: - makeerror "1.0.x" - -warning@^4.0.2, warning@^4.0.3: +warning@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w== dependencies: loose-envify "^1.0.0" -watchpack-chokidar2@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.0.tgz#9948a1866cbbd6cb824dea13a7ed691f6c8ddff0" - integrity sha512-9TyfOyN/zLUbA288wZ8IsMZ+6cbzvsNyEzSBp6e/zkifi6xxbl8SmQ/CxQq32k8NNqrdVEVUVSEf56L4rQ/ZxA== - dependencies: - chokidar "^2.1.8" - -watchpack@^1.6.0: - version "1.7.2" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.7.2.tgz#c02e4d4d49913c3e7e122c3325365af9d331e9aa" - integrity sha512-ymVbbQP40MFTp+cNMvpyBpBtygHnPzPkHqoIwRRj/0B8KhqQwV8LaKjtbaxF2lK4vl8zN9wCxS46IFCU5K4W0g== +watchpack@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.1.1.tgz#e99630550fca07df9f90a06056987baa40a689c7" + integrity sha512-Oo7LXCmc1eE1AjyuSBmtC3+Wy4HcV8PxWh2kP6fOl8yTlNS7r0K9l1ao2lrrUza7V39Y3D/BbJgY8VeSlc5JKw== dependencies: + glob-to-regexp "^0.4.1" graceful-fs "^4.1.2" - neo-async "^2.5.0" - optionalDependencies: - chokidar "^3.4.0" - watchpack-chokidar2 "^2.0.0" - -wbuf@^1.1.0, wbuf@^1.7.3: - version "1.7.3" - resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" - integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA== - dependencies: - minimalistic-assert "^1.0.0" webidl-conversions@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== -webpack-dev-middleware@^3.7.2: - version "3.7.2" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.2.tgz#0019c3db716e3fa5cecbf64f2ab88a74bab331f3" - integrity sha512-1xC42LxbYoqLNAhV6YzTYacicgMZQTqRd27Sim9wn5hJrX3I5nxYy1SxSd4+gjUFsz1dQFj+yEe6zEVmSkeJjw== - dependencies: - memory-fs "^0.4.1" - mime "^2.4.4" - mkdirp "^0.5.1" - range-parser "^1.2.1" - webpack-log "^2.0.0" - -webpack-dev-server@3.10.3: - version "3.10.3" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.10.3.tgz#f35945036813e57ef582c2420ef7b470e14d3af0" - integrity sha512-e4nWev8YzEVNdOMcNzNeCN947sWJNd43E5XvsJzbAL08kGc2frm1tQ32hTJslRS+H65LCb/AaUCYU7fjHCpDeQ== - dependencies: - ansi-html "0.0.7" - bonjour "^3.5.0" - chokidar "^2.1.8" - compression "^1.7.4" - connect-history-api-fallback "^1.6.0" - debug "^4.1.1" - del "^4.1.1" - express "^4.17.1" - html-entities "^1.2.1" - http-proxy-middleware "0.19.1" - import-local "^2.0.0" - internal-ip "^4.3.0" - ip "^1.1.5" - is-absolute-url "^3.0.3" - killable "^1.0.1" - loglevel "^1.6.6" - opn "^5.5.0" - p-retry "^3.0.1" - portfinder "^1.0.25" - schema-utils "^1.0.0" - selfsigned "^1.10.7" - semver "^6.3.0" - serve-index "^1.9.1" - sockjs "0.3.19" - sockjs-client "1.4.0" - spdy "^4.0.1" - strip-ansi "^3.0.1" - supports-color "^6.1.0" - url "^0.11.0" - webpack-dev-middleware "^3.7.2" - webpack-log "^2.0.0" - ws "^6.2.1" - yargs "12.0.5" - -webpack-log@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/webpack-log/-/webpack-log-2.0.0.tgz#5b7928e0637593f119d32f6227c1e0ac31e1b47f" - integrity sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg== - dependencies: - ansi-colors "^3.0.0" - uuid "^3.3.2" - -webpack-manifest-plugin@2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/webpack-manifest-plugin/-/webpack-manifest-plugin-2.2.0.tgz#19ca69b435b0baec7e29fbe90fb4015de2de4f16" - integrity sha512-9S6YyKKKh/Oz/eryM1RyLVDVmy3NSPV0JXMRhZ18fJsq+AwGxUY34X54VNwkzYcEmEkDwNxuEOboCZEebJXBAQ== - dependencies: - fs-extra "^7.0.0" - lodash ">=3.5 <5" - object.entries "^1.1.0" - tapable "^1.0.0" - -webpack-sources@^1.1.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" - integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ== - dependencies: - source-list-map "^2.0.0" - source-map "~0.6.1" - -webpack@4.42.0: - version "4.42.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.42.0.tgz#b901635dd6179391d90740a63c93f76f39883eb8" - integrity sha512-EzJRHvwQyBiYrYqhyjW9AqM90dE4+s1/XtCfn7uWg6cS72zH+2VPFAlsnW0+W0cDi0XRjNKUMoJtpSi50+Ph6w== - dependencies: - "@webassemblyjs/ast" "1.8.5" - "@webassemblyjs/helper-module-context" "1.8.5" - "@webassemblyjs/wasm-edit" "1.8.5" - "@webassemblyjs/wasm-parser" "1.8.5" - acorn "^6.2.1" - ajv "^6.10.2" - ajv-keywords "^3.4.1" - chrome-trace-event "^1.0.2" - enhanced-resolve "^4.1.0" - eslint-scope "^4.0.3" - json-parse-better-errors "^1.0.2" - loader-runner "^2.4.0" - loader-utils "^1.2.3" - memory-fs "^0.4.1" - micromatch "^3.1.10" - mkdirp "^0.5.1" - neo-async "^2.6.1" - node-libs-browser "^2.2.1" - schema-utils "^1.0.0" - tapable "^1.1.3" - terser-webpack-plugin "^1.4.3" - watchpack "^1.6.0" - webpack-sources "^1.4.1" - -websocket-driver@>=0.5.1: - version "0.7.4" - resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" - integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== - dependencies: - http-parser-js ">=0.5.1" - safe-buffer ">=5.1.0" - websocket-extensions ">=0.1.1" - -websocket-extensions@>=0.1.1: - version "0.1.4" - resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" - integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== - -whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3, whatwg-encoding@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" - integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== - dependencies: - iconv-lite "0.4.24" - -whatwg-fetch@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.1.0.tgz#49d630cdfa308dba7f2819d49d09364f540dbcc6" - integrity sha512-pgmbsVWKpH9GxLXZmtdowDIqtb/rvPyjjQv3z9wLcmgWKFHilKnZD3ldgrOlwJoPGOUluQsRPWd52yVkPfmI1A== - -whatwg-mimetype@^2.1.0, whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" - integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== - -whatwg-url@^6.4.1: - version "6.5.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.5.0.tgz#f2df02bff176fd65070df74ad5ccbb5a199965a8" - integrity sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ== - dependencies: - lodash.sortby "^4.7.0" - tr46 "^1.0.1" - webidl-conversions "^4.0.2" - whatwg-url@^7.0.0: version "7.1.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06" @@ -11634,22 +4486,33 @@ whatwg-url@^7.0.0: tr46 "^1.0.1" webidl-conversions "^4.0.2" -which-module@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" - integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= +which-boxed-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== + dependencies: + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" which-pm-runs@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb" integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs= -which@^1.2.9, which@^1.3.0, which@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" - integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== +which-typed-array@^1.1.2: + version "1.1.7" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.7.tgz#2761799b9a22d4b8660b3c1b40abaa7739691793" + integrity sha512-vjxaB4nfDqwKI0ws7wZpxIlde1XrLX5uB0ZjpfshgmapJMD7jJWhZI+yToJTqaFByF0eNBcYxbjmCzoRP7CfEw== dependencies: - isexe "^2.0.0" + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + es-abstract "^1.18.5" + foreach "^2.0.5" + has-tostringtag "^1.0.0" + is-typed-array "^1.1.7" which@^2.0.1: version "2.0.2" @@ -11658,177 +4521,11 @@ which@^2.0.1: dependencies: isexe "^2.0.0" -word-wrap@~1.2.3: +word-wrap@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== -workbox-background-sync@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/workbox-background-sync/-/workbox-background-sync-4.3.1.tgz#26821b9bf16e9e37fd1d640289edddc08afd1950" - integrity sha512-1uFkvU8JXi7L7fCHVBEEnc3asPpiAL33kO495UMcD5+arew9IbKW2rV5lpzhoWcm/qhGB89YfO4PmB/0hQwPRg== - dependencies: - workbox-core "^4.3.1" - -workbox-broadcast-update@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/workbox-broadcast-update/-/workbox-broadcast-update-4.3.1.tgz#e2c0280b149e3a504983b757606ad041f332c35b" - integrity sha512-MTSfgzIljpKLTBPROo4IpKjESD86pPFlZwlvVG32Kb70hW+aob4Jxpblud8EhNb1/L5m43DUM4q7C+W6eQMMbA== - dependencies: - workbox-core "^4.3.1" - -workbox-build@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/workbox-build/-/workbox-build-4.3.1.tgz#414f70fb4d6de47f6538608b80ec52412d233e64" - integrity sha512-UHdwrN3FrDvicM3AqJS/J07X0KXj67R8Cg0waq1MKEOqzo89ap6zh6LmaLnRAjpB+bDIz+7OlPye9iii9KBnxw== - dependencies: - "@babel/runtime" "^7.3.4" - "@hapi/joi" "^15.0.0" - common-tags "^1.8.0" - fs-extra "^4.0.2" - glob "^7.1.3" - lodash.template "^4.4.0" - pretty-bytes "^5.1.0" - stringify-object "^3.3.0" - strip-comments "^1.0.2" - workbox-background-sync "^4.3.1" - workbox-broadcast-update "^4.3.1" - workbox-cacheable-response "^4.3.1" - workbox-core "^4.3.1" - workbox-expiration "^4.3.1" - workbox-google-analytics "^4.3.1" - workbox-navigation-preload "^4.3.1" - workbox-precaching "^4.3.1" - workbox-range-requests "^4.3.1" - workbox-routing "^4.3.1" - workbox-strategies "^4.3.1" - workbox-streams "^4.3.1" - workbox-sw "^4.3.1" - workbox-window "^4.3.1" - -workbox-cacheable-response@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/workbox-cacheable-response/-/workbox-cacheable-response-4.3.1.tgz#f53e079179c095a3f19e5313b284975c91428c91" - integrity sha512-Rp5qlzm6z8IOvnQNkCdO9qrDgDpoPNguovs0H8C+wswLuPgSzSp9p2afb5maUt9R1uTIwOXrVQMmPfPypv+npw== - dependencies: - workbox-core "^4.3.1" - -workbox-core@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/workbox-core/-/workbox-core-4.3.1.tgz#005d2c6a06a171437afd6ca2904a5727ecd73be6" - integrity sha512-I3C9jlLmMKPxAC1t0ExCq+QoAMd0vAAHULEgRZ7kieCdUd919n53WC0AfvokHNwqRhGn+tIIj7vcb5duCjs2Kg== - -workbox-expiration@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/workbox-expiration/-/workbox-expiration-4.3.1.tgz#d790433562029e56837f341d7f553c4a78ebe921" - integrity sha512-vsJLhgQsQouv9m0rpbXubT5jw0jMQdjpkum0uT+d9tTwhXcEZks7qLfQ9dGSaufTD2eimxbUOJfWLbNQpIDMPw== - dependencies: - workbox-core "^4.3.1" - -workbox-google-analytics@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/workbox-google-analytics/-/workbox-google-analytics-4.3.1.tgz#9eda0183b103890b5c256e6f4ea15a1f1548519a" - integrity sha512-xzCjAoKuOb55CBSwQrbyWBKqp35yg1vw9ohIlU2wTy06ZrYfJ8rKochb1MSGlnoBfXGWss3UPzxR5QL5guIFdg== - dependencies: - workbox-background-sync "^4.3.1" - workbox-core "^4.3.1" - workbox-routing "^4.3.1" - workbox-strategies "^4.3.1" - -workbox-navigation-preload@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/workbox-navigation-preload/-/workbox-navigation-preload-4.3.1.tgz#29c8e4db5843803b34cd96dc155f9ebd9afa453d" - integrity sha512-K076n3oFHYp16/C+F8CwrRqD25GitA6Rkd6+qAmLmMv1QHPI2jfDwYqrytOfKfYq42bYtW8Pr21ejZX7GvALOw== - dependencies: - workbox-core "^4.3.1" - -workbox-precaching@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/workbox-precaching/-/workbox-precaching-4.3.1.tgz#9fc45ed122d94bbe1f0ea9584ff5940960771cba" - integrity sha512-piSg/2csPoIi/vPpp48t1q5JLYjMkmg5gsXBQkh/QYapCdVwwmKlU9mHdmy52KsDGIjVaqEUMFvEzn2LRaigqQ== - dependencies: - workbox-core "^4.3.1" - -workbox-range-requests@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/workbox-range-requests/-/workbox-range-requests-4.3.1.tgz#f8a470188922145cbf0c09a9a2d5e35645244e74" - integrity sha512-S+HhL9+iTFypJZ/yQSl/x2Bf5pWnbXdd3j57xnb0V60FW1LVn9LRZkPtneODklzYuFZv7qK6riZ5BNyc0R0jZA== - dependencies: - workbox-core "^4.3.1" - -workbox-routing@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/workbox-routing/-/workbox-routing-4.3.1.tgz#a675841af623e0bb0c67ce4ed8e724ac0bed0cda" - integrity sha512-FkbtrODA4Imsi0p7TW9u9MXuQ5P4pVs1sWHK4dJMMChVROsbEltuE79fBoIk/BCztvOJ7yUpErMKa4z3uQLX+g== - dependencies: - workbox-core "^4.3.1" - -workbox-strategies@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/workbox-strategies/-/workbox-strategies-4.3.1.tgz#d2be03c4ef214c115e1ab29c9c759c9fe3e9e646" - integrity sha512-F/+E57BmVG8dX6dCCopBlkDvvhg/zj6VDs0PigYwSN23L8hseSRwljrceU2WzTvk/+BSYICsWmRq5qHS2UYzhw== - dependencies: - workbox-core "^4.3.1" - -workbox-streams@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/workbox-streams/-/workbox-streams-4.3.1.tgz#0b57da70e982572de09c8742dd0cb40a6b7c2cc3" - integrity sha512-4Kisis1f/y0ihf4l3u/+ndMkJkIT4/6UOacU3A4BwZSAC9pQ9vSvJpIi/WFGQRH/uPXvuVjF5c2RfIPQFSS2uA== - dependencies: - workbox-core "^4.3.1" - -workbox-sw@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/workbox-sw/-/workbox-sw-4.3.1.tgz#df69e395c479ef4d14499372bcd84c0f5e246164" - integrity sha512-0jXdusCL2uC5gM3yYFT6QMBzKfBr2XTk0g5TPAV4y8IZDyVNDyj1a8uSXy3/XrvkVTmQvLN4O5k3JawGReXr9w== - -workbox-webpack-plugin@4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/workbox-webpack-plugin/-/workbox-webpack-plugin-4.3.1.tgz#47ff5ea1cc074b6c40fb5a86108863a24120d4bd" - integrity sha512-gJ9jd8Mb8wHLbRz9ZvGN57IAmknOipD3W4XNE/Lk/4lqs5Htw4WOQgakQy/o/4CoXQlMCYldaqUg+EJ35l9MEQ== - dependencies: - "@babel/runtime" "^7.0.0" - json-stable-stringify "^1.0.1" - workbox-build "^4.3.1" - -workbox-window@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/workbox-window/-/workbox-window-4.3.1.tgz#ee6051bf10f06afa5483c9b8dfa0531994ede0f3" - integrity sha512-C5gWKh6I58w3GeSc0wp2Ne+rqVw8qwcmZnQGpjiek8A2wpbxSJb1FdCoQVO+jDJs35bFgo/WETgl1fqgsxN0Hg== - dependencies: - workbox-core "^4.3.1" - -worker-farm@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8" - integrity sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw== - dependencies: - errno "~0.1.7" - -worker-rpc@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/worker-rpc/-/worker-rpc-0.1.1.tgz#cb565bd6d7071a8f16660686051e969ad32f54d5" - integrity sha512-P1WjMrUB3qgJNI9jfmpZ/htmBEjFh//6l/5y8SD9hg1Ef5zTTVVoRjTrTEzPrNBQvmhMxkoTsjOXN10GWU7aCg== - dependencies: - microevent.ts "~0.1.1" - -wrap-ansi@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" - integrity sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU= - dependencies: - string-width "^1.0.1" - strip-ansi "^3.0.1" - -wrap-ansi@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" - integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== - dependencies: - ansi-styles "^3.2.0" - string-width "^3.0.0" - strip-ansi "^5.0.0" - wrap-ansi@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" @@ -11838,146 +4535,36 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -write-file-atomic@2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.4.1.tgz#d0b05463c188ae804396fd5ab2a370062af87529" - integrity sha512-TGHFeZEZMnv+gBFRfjAcxL5bPHrsGKtnb4qsFAws7/vlh+QfwAaySIw4AXP9ZskTTh5GWu3FLuJhsWVdiJPGvg== - dependencies: - graceful-fs "^4.1.11" - imurmurhash "^0.1.4" - signal-exit "^3.0.2" - -write@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" - integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== - dependencies: - mkdirp "^0.5.1" - -ws@^5.2.0: - version "5.2.2" - resolved "https://registry.yarnpkg.com/ws/-/ws-5.2.2.tgz#dffef14866b8e8dc9133582514d1befaf96e980f" - integrity sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA== - dependencies: - async-limiter "~1.0.0" - -ws@^6.1.2, ws@^6.2.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" - integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== - dependencies: - async-limiter "~1.0.0" - -ws@~6.1.0: - version "6.1.4" - resolved "https://registry.yarnpkg.com/ws/-/ws-6.1.4.tgz#5b5c8800afab925e94ccb29d153c8d02c1776ef9" - integrity sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA== - dependencies: - async-limiter "~1.0.0" - -xml-name-validator@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" - integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== - -xmlchars@^2.1.1: - version "2.2.0" - resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" - integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== - -xmlhttprequest-ssl@~1.5.4: - version "1.5.5" - resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e" - integrity sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4= - -xregexp@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-4.3.0.tgz#7e92e73d9174a99a59743f67a4ce879a04b5ae50" - integrity sha512-7jXDIFXh5yJ/orPn4SXjuVrWWoi4Cr8jfV1eHv9CixKSbU+jY4mxfrBwAuDvupPNKpMUY+FeIqsVw/JLT9+B8g== - dependencies: - "@babel/runtime-corejs3" "^7.8.3" - -xtend@^4.0.0, xtend@~4.0.1: +xtend@^4.0.0, xtend@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== -"y18n@^3.2.1 || ^4.0.0", y18n@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" - integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== - -yallist@^3.0.2: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" - integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== - yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yaml@^1.7.2: - version "1.10.0" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e" - integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg== - -yargs-parser@^11.1.1: - version "11.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-11.1.1.tgz#879a0865973bca9f6bab5cbdf3b1c67ec7d3bcf4" - integrity sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" - -yargs-parser@^13.1.2: - version "13.1.2" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" - integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" - -yargs@12.0.5: - version "12.0.5" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13" - integrity sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw== - dependencies: - cliui "^4.0.0" - decamelize "^1.2.0" - find-up "^3.0.0" - get-caller-file "^1.0.1" - os-locale "^3.0.0" - require-directory "^2.1.1" - require-main-filename "^1.0.1" - set-blocking "^2.0.0" - string-width "^2.0.0" - which-module "^2.0.0" - y18n "^3.2.1 || ^4.0.0" - yargs-parser "^11.1.1" - -yargs@^13.3.0: - version "13.3.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" - integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== - dependencies: - cliui "^5.0.0" - find-up "^3.0.0" - get-caller-file "^2.0.1" - require-directory "^2.1.1" - require-main-filename "^2.0.0" - set-blocking "^2.0.0" - string-width "^3.0.0" - which-module "^2.0.0" - y18n "^4.0.0" - yargs-parser "^13.1.2" - -yeast@0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419" - integrity sha1-AI4G2AlDIMNy28L47XagymyKxBk= +yaml@^1.10.0: + version "1.10.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" + integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== diff --git a/opendc-workflow/opendc-workflow-service/build.gradle.kts b/opendc-workflow/opendc-workflow-service/build.gradle.kts index 5e73222c..43b64b15 100644 --- a/opendc-workflow/opendc-workflow-service/build.gradle.kts +++ b/opendc-workflow/opendc-workflow-service/build.gradle.kts @@ -39,8 +39,8 @@ dependencies { testImplementation(projects.opendcSimulator.opendcSimulatorCore) testImplementation(projects.opendcCompute.opendcComputeSimulator) - testImplementation(projects.opendcFormat) + testImplementation(projects.opendcTrace.opendcTraceApi) testImplementation(projects.opendcTelemetry.opendcTelemetrySdk) - testImplementation(libs.jackson.module.kotlin) + testRuntimeOnly(projects.opendcTrace.opendcTraceGwf) testRuntimeOnly(libs.log4j.slf4j) } diff --git a/opendc-workflow/opendc-workflow-service/src/main/kotlin/org/opendc/workflow/service/WorkflowService.kt b/opendc-workflow/opendc-workflow-service/src/main/kotlin/org/opendc/workflow/service/WorkflowService.kt index d3358ef1..a0248a93 100644 --- a/opendc-workflow/opendc-workflow-service/src/main/kotlin/org/opendc/workflow/service/WorkflowService.kt +++ b/opendc-workflow/opendc-workflow-service/src/main/kotlin/org/opendc/workflow/service/WorkflowService.kt @@ -22,7 +22,7 @@ package org.opendc.workflow.service -import io.opentelemetry.api.metrics.Meter +import io.opentelemetry.api.metrics.MeterProvider import org.opendc.compute.api.ComputeClient import org.opendc.workflow.api.Job import org.opendc.workflow.service.internal.WorkflowServiceImpl @@ -62,7 +62,7 @@ public interface WorkflowService : AutoCloseable { * @param context The [CoroutineContext] to use in the service. * @param clock The clock instance to use. * @param tracer The event tracer to use. - * @param meter The meter to use. + * @param meterProvider The meter provider to use. * @param compute The compute client to use. * @param mode The scheduling mode to use. * @param jobAdmissionPolicy The job admission policy to use. @@ -73,7 +73,7 @@ public interface WorkflowService : AutoCloseable { public operator fun invoke( context: CoroutineContext, clock: Clock, - meter: Meter, + meterProvider: MeterProvider, compute: ComputeClient, mode: WorkflowSchedulerMode, jobAdmissionPolicy: JobAdmissionPolicy, @@ -84,7 +84,7 @@ public interface WorkflowService : AutoCloseable { return WorkflowServiceImpl( context, clock, - meter, + meterProvider, compute, mode, jobAdmissionPolicy, diff --git a/opendc-workflow/opendc-workflow-service/src/main/kotlin/org/opendc/workflow/service/internal/WorkflowServiceImpl.kt b/opendc-workflow/opendc-workflow-service/src/main/kotlin/org/opendc/workflow/service/internal/WorkflowServiceImpl.kt index 32191b8f..a0fd3fad 100644 --- a/opendc-workflow/opendc-workflow-service/src/main/kotlin/org/opendc/workflow/service/internal/WorkflowServiceImpl.kt +++ b/opendc-workflow/opendc-workflow-service/src/main/kotlin/org/opendc/workflow/service/internal/WorkflowServiceImpl.kt @@ -23,6 +23,7 @@ package org.opendc.workflow.service.internal import io.opentelemetry.api.metrics.Meter +import io.opentelemetry.api.metrics.MeterProvider import kotlinx.coroutines.* import kotlinx.coroutines.flow.map import mu.KotlinLogging @@ -48,7 +49,7 @@ import kotlin.coroutines.resume public class WorkflowServiceImpl( context: CoroutineContext, internal val clock: Clock, - private val meter: Meter, + meterProvider: MeterProvider, private val computeClient: ComputeClient, mode: WorkflowSchedulerMode, jobAdmissionPolicy: JobAdmissionPolicy, @@ -67,6 +68,11 @@ public class WorkflowServiceImpl( private val logger = KotlinLogging.logger {} /** + * The [Meter] to collect metrics of this service. + */ + private val meter = meterProvider.get("org.opendc.workflow.service") + + /** * The incoming jobs ready to be processed by the scheduler. */ internal val incomingJobs: MutableSet<JobState> = linkedSetOf() @@ -155,7 +161,7 @@ public class WorkflowServiceImpl( /** * The number of jobs that have been submitted to the service. */ - private val submittedJobs = meter.longCounterBuilder("jobs.submitted") + private val submittedJobs = meter.counterBuilder("jobs.submitted") .setDescription("Number of submitted jobs") .setUnit("1") .build() @@ -163,7 +169,7 @@ public class WorkflowServiceImpl( /** * The number of jobs that are running. */ - private val runningJobs = meter.longUpDownCounterBuilder("jobs.active") + private val runningJobs = meter.upDownCounterBuilder("jobs.active") .setDescription("Number of jobs running") .setUnit("1") .build() @@ -171,7 +177,7 @@ public class WorkflowServiceImpl( /** * The number of jobs that have finished running. */ - private val finishedJobs = meter.longCounterBuilder("jobs.finished") + private val finishedJobs = meter.counterBuilder("jobs.finished") .setDescription("Number of jobs that finished running") .setUnit("1") .build() @@ -179,7 +185,7 @@ public class WorkflowServiceImpl( /** * The number of tasks that have been submitted to the service. */ - private val submittedTasks = meter.longCounterBuilder("tasks.submitted") + private val submittedTasks = meter.counterBuilder("tasks.submitted") .setDescription("Number of submitted tasks") .setUnit("1") .build() @@ -187,7 +193,7 @@ public class WorkflowServiceImpl( /** * The number of jobs that are running. */ - private val runningTasks = meter.longUpDownCounterBuilder("tasks.active") + private val runningTasks = meter.upDownCounterBuilder("tasks.active") .setDescription("Number of tasks running") .setUnit("1") .build() @@ -195,7 +201,7 @@ public class WorkflowServiceImpl( /** * The number of jobs that have finished running. */ - private val finishedTasks = meter.longCounterBuilder("tasks.finished") + private val finishedTasks = meter.counterBuilder("tasks.finished") .setDescription("Number of tasks that finished running") .setUnit("1") .build() diff --git a/opendc-workflow/opendc-workflow-service/src/test/kotlin/org/opendc/workflow/service/TraceReplayer.kt b/opendc-workflow/opendc-workflow-service/src/test/kotlin/org/opendc/workflow/service/TraceReplayer.kt new file mode 100644 index 00000000..9ee3736e --- /dev/null +++ b/opendc-workflow/opendc-workflow-service/src/test/kotlin/org/opendc/workflow/service/TraceReplayer.kt @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.workflow.service + +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import org.opendc.simulator.compute.workload.SimFlopsWorkload +import org.opendc.trace.* +import org.opendc.workflow.api.Job +import org.opendc.workflow.api.Task +import org.opendc.workflow.api.WORKFLOW_TASK_CORES +import org.opendc.workflow.api.WORKFLOW_TASK_DEADLINE +import java.time.Clock +import java.util.* +import kotlin.collections.HashMap +import kotlin.collections.HashSet +import kotlin.math.max +import kotlin.math.min + +/** + * Helper tool to replay workflow trace. + */ +internal class TraceReplayer(private val trace: Trace) { + /** + * Replay the workload. + */ + public suspend fun replay(clock: Clock, service: WorkflowService) { + val jobs = parseTrace(trace) + + // Sort jobs by their arrival time + (jobs as MutableList<Job>).sortBy { it.metadata["WORKFLOW_SUBMIT_TIME"] as Long } + + // Wait until the trace is started + val startTime = jobs[0].metadata["WORKFLOW_SUBMIT_TIME"] as Long + delay(min(0L, startTime - clock.millis())) + + val offset = startTime - clock.millis() + + coroutineScope { + for (job in jobs) { + val submitTime = job.metadata["WORKFLOW_SUBMIT_TIME"] as Long + delay(max(0, (submitTime - offset) - clock.millis())) + + launch { service.run(job) } + } + } + } + + /** + * Convert [trace] into a list of [Job]s that can be submitted to the workflow service. + */ + public fun parseTrace(trace: Trace): List<Job> { + val table = checkNotNull(trace.getTable(TABLE_TASKS)) + val reader = table.newReader() + + val jobs = mutableMapOf<Long, Job>() + val tasks = mutableMapOf<Long, Task>() + val taskDependencies = mutableMapOf<Task, Set<Long>>() + + try { + while (reader.nextRow()) { + // Bag of tasks without workflow ID all share the same workflow + val workflowId = if (reader.hasColumn(TASK_WORKFLOW_ID)) reader.get(TASK_WORKFLOW_ID).toLong() else 0L + val workflow = jobs.computeIfAbsent(workflowId) { id -> Job(UUID(0L, id), "<unnamed>", HashSet(), HashMap()) } + + val id = reader.get(TASK_ID).toLong() + val grantedCpus = if (reader.hasColumn(TASK_ALLOC_NCPUS)) + reader.getInt(TASK_ALLOC_NCPUS) + else + reader.getInt(TASK_REQ_NCPUS) + val submitTime = reader.get(TASK_SUBMIT_TIME) + val runtime = reader.get(TASK_RUNTIME) + val flops: Long = 4000 * runtime.seconds * grantedCpus + val workload = SimFlopsWorkload(flops) + val task = Task( + UUID(0L, id), + "<unnamed>", + HashSet(), + mapOf( + "workload" to workload, + WORKFLOW_TASK_CORES to grantedCpus, + WORKFLOW_TASK_DEADLINE to runtime.toMillis() + ), + ) + + tasks[id] = task + taskDependencies[task] = reader.get(TASK_PARENTS).map { it.toLong() }.toSet() + + (workflow.metadata as MutableMap<String, Any>).merge("WORKFLOW_SUBMIT_TIME", submitTime.toEpochMilli()) { a, b -> min(a as Long, b as Long) } + (workflow.tasks as MutableSet<Task>).add(task) + } + + // Resolve dependencies for all tasks + for ((task, deps) in taskDependencies) { + for (dep in deps) { + val parent = requireNotNull(tasks[dep]) { "Dependency task with id $dep not found" } + (task.dependencies as MutableSet<Task>).add(parent) + } + } + } finally { + reader.close() + } + + return jobs.values.toList() + } +} diff --git a/opendc-workflow/opendc-workflow-service/src/test/kotlin/org/opendc/workflow/service/WorkflowServiceIntegrationTest.kt b/opendc-workflow/opendc-workflow-service/src/test/kotlin/org/opendc/workflow/service/WorkflowServiceIntegrationTest.kt deleted file mode 100644 index a8d3a9e8..00000000 --- a/opendc-workflow/opendc-workflow-service/src/test/kotlin/org/opendc/workflow/service/WorkflowServiceIntegrationTest.kt +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.workflow.service - -import io.opentelemetry.api.metrics.MeterProvider -import io.opentelemetry.sdk.metrics.SdkMeterProvider -import io.opentelemetry.sdk.metrics.export.MetricProducer -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertAll -import org.opendc.compute.service.ComputeService -import org.opendc.compute.service.scheduler.FilterScheduler -import org.opendc.compute.service.scheduler.filters.ComputeCapabilitiesFilter -import org.opendc.compute.service.scheduler.filters.ComputeFilter -import org.opendc.compute.service.scheduler.weights.ProvisionedCoresWeigher -import org.opendc.compute.simulator.SimHost -import org.opendc.format.environment.sc18.Sc18EnvironmentReader -import org.opendc.format.trace.gwf.GwfTraceReader -import org.opendc.simulator.compute.SimSpaceSharedHypervisorProvider -import org.opendc.simulator.core.runBlockingSimulation -import org.opendc.telemetry.sdk.toOtelClock -import org.opendc.workflow.service.internal.WorkflowServiceImpl -import org.opendc.workflow.service.scheduler.WorkflowSchedulerMode -import org.opendc.workflow.service.scheduler.job.NullJobAdmissionPolicy -import org.opendc.workflow.service.scheduler.job.SubmissionTimeJobOrderPolicy -import org.opendc.workflow.service.scheduler.task.NullTaskEligibilityPolicy -import org.opendc.workflow.service.scheduler.task.SubmissionTimeTaskOrderPolicy -import kotlin.math.max - -/** - * Integration test suite for the [WorkflowServiceImpl]. - */ -@DisplayName("WorkflowServiceImpl") -@OptIn(ExperimentalCoroutinesApi::class) -internal class WorkflowServiceIntegrationTest { - /** - * A large integration test where we check whether all tasks in some trace are executed correctly. - */ - @Test - fun testTrace() = runBlockingSimulation { - val meterProvider: MeterProvider = SdkMeterProvider - .builder() - .setClock(clock.toOtelClock()) - .build() - - val hosts = Sc18EnvironmentReader(object {}.javaClass.getResourceAsStream("/environment.json")) - .use { it.read() } - .map { def -> - SimHost( - def.uid, - def.name, - def.model, - def.meta, - coroutineContext, - clock, - MeterProvider.noop().get("opendc-compute-simulator"), - SimSpaceSharedHypervisorProvider() - ) - } - - val meter = MeterProvider.noop().get("opendc-compute") - val computeScheduler = FilterScheduler( - filters = listOf(ComputeFilter(), ComputeCapabilitiesFilter()), - weighers = listOf(ProvisionedCoresWeigher() to -1.0) - ) - val compute = ComputeService(coroutineContext, clock, meter, computeScheduler, schedulingQuantum = 1000) - - hosts.forEach { compute.addHost(it) } - - val scheduler = WorkflowService( - coroutineContext, - clock, - meterProvider.get("opendc-workflow"), - compute.newClient(), - mode = WorkflowSchedulerMode.Batch(100), - jobAdmissionPolicy = NullJobAdmissionPolicy, - jobOrderPolicy = SubmissionTimeJobOrderPolicy(), - taskEligibilityPolicy = NullTaskEligibilityPolicy, - taskOrderPolicy = SubmissionTimeTaskOrderPolicy(), - ) - - val reader = GwfTraceReader(object {}.javaClass.getResourceAsStream("/trace.gwf")) - var offset = Long.MIN_VALUE - - coroutineScope { - while (reader.hasNext()) { - val entry = reader.next() - - if (offset < 0) { - offset = entry.start - clock.millis() - } - - delay(max(0, (entry.start - offset) - clock.millis())) - launch { - scheduler.run(entry.workload) - } - } - } - - hosts.forEach(SimHost::close) - scheduler.close() - compute.close() - - val metrics = collectMetrics(meterProvider as MetricProducer) - - assertAll( - { assertEquals(758, metrics.jobsSubmitted, "No jobs submitted") }, - { assertEquals(0, metrics.jobsActive, "Not all submitted jobs started") }, - { assertEquals(metrics.jobsSubmitted, metrics.jobsFinished, "Not all started jobs finished") }, - { assertEquals(0, metrics.tasksActive, "Not all started tasks finished") }, - { assertEquals(metrics.tasksSubmitted, metrics.tasksFinished, "Not all started tasks finished") } - ) - } - - class WorkflowMetrics { - var jobsSubmitted = 0L - var jobsActive = 0L - var jobsFinished = 0L - var tasksSubmitted = 0L - var tasksActive = 0L - var tasksFinished = 0L - } - - /** - * Collect the metrics of the workflow service. - */ - private fun collectMetrics(metricProducer: MetricProducer): WorkflowMetrics { - val metrics = metricProducer.collectAllMetrics().associateBy { it.name } - val res = WorkflowMetrics() - res.jobsSubmitted = metrics["jobs.submitted"]?.longSumData?.points?.last()?.value ?: 0 - res.jobsActive = metrics["jobs.active"]?.longSumData?.points?.last()?.value ?: 0 - res.jobsFinished = metrics["jobs.finished"]?.longSumData?.points?.last()?.value ?: 0 - res.tasksSubmitted = metrics["tasks.submitted"]?.longSumData?.points?.last()?.value ?: 0 - res.tasksActive = metrics["tasks.active"]?.longSumData?.points?.last()?.value ?: 0 - res.tasksFinished = metrics["tasks.finished"]?.longSumData?.points?.last()?.value ?: 0 - return res - } -} diff --git a/opendc-workflow/opendc-workflow-service/src/test/kotlin/org/opendc/workflow/service/WorkflowServiceTest.kt b/opendc-workflow/opendc-workflow-service/src/test/kotlin/org/opendc/workflow/service/WorkflowServiceTest.kt new file mode 100644 index 00000000..04f54e58 --- /dev/null +++ b/opendc-workflow/opendc-workflow-service/src/test/kotlin/org/opendc/workflow/service/WorkflowServiceTest.kt @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.workflow.service + +import io.opentelemetry.api.metrics.MeterProvider +import io.opentelemetry.sdk.metrics.SdkMeterProvider +import io.opentelemetry.sdk.metrics.export.MetricProducer +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertAll +import org.opendc.compute.service.ComputeService +import org.opendc.compute.service.scheduler.FilterScheduler +import org.opendc.compute.service.scheduler.filters.ComputeFilter +import org.opendc.compute.service.scheduler.filters.RamFilter +import org.opendc.compute.service.scheduler.filters.VCpuFilter +import org.opendc.compute.service.scheduler.weights.VCpuWeigher +import org.opendc.compute.simulator.SimHost +import org.opendc.simulator.compute.kernel.SimSpaceSharedHypervisorProvider +import org.opendc.simulator.compute.model.MachineModel +import org.opendc.simulator.compute.model.MemoryUnit +import org.opendc.simulator.compute.model.ProcessingNode +import org.opendc.simulator.compute.model.ProcessingUnit +import org.opendc.simulator.core.runBlockingSimulation +import org.opendc.simulator.flow.FlowEngine +import org.opendc.telemetry.sdk.toOtelClock +import org.opendc.trace.Trace +import org.opendc.workflow.service.internal.WorkflowServiceImpl +import org.opendc.workflow.service.scheduler.WorkflowSchedulerMode +import org.opendc.workflow.service.scheduler.job.NullJobAdmissionPolicy +import org.opendc.workflow.service.scheduler.job.SubmissionTimeJobOrderPolicy +import org.opendc.workflow.service.scheduler.task.NullTaskEligibilityPolicy +import org.opendc.workflow.service.scheduler.task.SubmissionTimeTaskOrderPolicy +import java.nio.file.Paths +import java.time.Duration +import java.util.* + +/** + * Integration test suite for the [WorkflowServiceImpl]. + */ +@DisplayName("WorkflowService") +internal class WorkflowServiceTest { + /** + * A large integration test where we check whether all tasks in some trace are executed correctly. + */ + @Test + fun testTrace() = runBlockingSimulation { + val meterProvider: MeterProvider = SdkMeterProvider + .builder() + .setClock(clock.toOtelClock()) + .build() + + val interpreter = FlowEngine(coroutineContext, clock) + val machineModel = createMachineModel() + val hvProvider = SimSpaceSharedHypervisorProvider() + val hosts = List(4) { id -> + SimHost( + UUID(0, id.toLong()), + "node-$id", + machineModel, + emptyMap(), + coroutineContext, + interpreter, + MeterProvider.noop(), + hvProvider, + ) + } + + val computeScheduler = FilterScheduler( + filters = listOf(ComputeFilter(), VCpuFilter(1.0), RamFilter(1.0)), + weighers = listOf(VCpuWeigher(1.0, multiplier = 1.0)) + ) + val compute = ComputeService(coroutineContext, clock, MeterProvider.noop(), computeScheduler, schedulingQuantum = Duration.ofSeconds(1)) + + hosts.forEach { compute.addHost(it) } + + val scheduler = WorkflowService( + coroutineContext, + clock, + meterProvider, + compute.newClient(), + mode = WorkflowSchedulerMode.Batch(100), + jobAdmissionPolicy = NullJobAdmissionPolicy, + jobOrderPolicy = SubmissionTimeJobOrderPolicy(), + taskEligibilityPolicy = NullTaskEligibilityPolicy, + taskOrderPolicy = SubmissionTimeTaskOrderPolicy(), + ) + + val trace = Trace.open( + Paths.get(checkNotNull(WorkflowServiceTest::class.java.getResource("/trace.gwf")).toURI()), + format = "gwf" + ) + val replayer = TraceReplayer(trace) + + replayer.replay(clock, scheduler) + + hosts.forEach(SimHost::close) + scheduler.close() + compute.close() + + val metrics = collectMetrics(meterProvider as MetricProducer) + + assertAll( + { assertEquals(758, metrics.jobsSubmitted, "No jobs submitted") }, + { assertEquals(0, metrics.jobsActive, "Not all submitted jobs started") }, + { assertEquals(metrics.jobsSubmitted, metrics.jobsFinished, "Not all started jobs finished") }, + { assertEquals(0, metrics.tasksActive, "Not all started tasks finished") }, + { assertEquals(metrics.tasksSubmitted, metrics.tasksFinished, "Not all started tasks finished") }, + { assertEquals(33213236L, clock.millis()) } + ) + } + + /** + * The machine model based on: https://www.spec.org/power_ssj2008/results/res2020q1/power_ssj2008-20191125-01012.html + */ + private fun createMachineModel(): MachineModel { + val node = ProcessingNode("AMD", "am64", "EPYC 7742", 32) + val cpus = List(node.coreCount) { id -> ProcessingUnit(node, id, 3400.0) } + val memory = List(8) { MemoryUnit("Samsung", "Unknown", 2933.0, 16_000) } + + return MachineModel(cpus, memory) + } + + class WorkflowMetrics { + var jobsSubmitted = 0L + var jobsActive = 0L + var jobsFinished = 0L + var tasksSubmitted = 0L + var tasksActive = 0L + var tasksFinished = 0L + } + + /** + * Collect the metrics of the workflow service. + */ + private fun collectMetrics(metricProducer: MetricProducer): WorkflowMetrics { + val metrics = metricProducer.collectAllMetrics().associateBy { it.name } + val res = WorkflowMetrics() + res.jobsSubmitted = metrics["jobs.submitted"]?.longSumData?.points?.last()?.value ?: 0 + res.jobsActive = metrics["jobs.active"]?.longSumData?.points?.last()?.value ?: 0 + res.jobsFinished = metrics["jobs.finished"]?.longSumData?.points?.last()?.value ?: 0 + res.tasksSubmitted = metrics["tasks.submitted"]?.longSumData?.points?.last()?.value ?: 0 + res.tasksActive = metrics["tasks.active"]?.longSumData?.points?.last()?.value ?: 0 + res.tasksFinished = metrics["tasks.finished"]?.longSumData?.points?.last()?.value ?: 0 + return res + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index d09e2912..d9b3d940 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -25,25 +25,36 @@ include(":opendc-platform") include(":opendc-compute:opendc-compute-api") include(":opendc-compute:opendc-compute-service") include(":opendc-compute:opendc-compute-simulator") +include(":opendc-compute:opendc-compute-workload") include(":opendc-workflow:opendc-workflow-api") include(":opendc-workflow:opendc-workflow-service") -include(":opendc-serverless:opendc-serverless-api") -include(":opendc-serverless:opendc-serverless-service") -include(":opendc-serverless:opendc-serverless-simulator") -include(":opendc-format") +include(":opendc-faas:opendc-faas-api") +include(":opendc-faas:opendc-faas-service") +include(":opendc-faas:opendc-faas-simulator") include(":opendc-experiments:opendc-experiments-capelin") -include(":opendc-experiments:opendc-experiments-energy21") include(":opendc-experiments:opendc-experiments-serverless20") include(":opendc-experiments:opendc-experiments-tf20") include(":opendc-web:opendc-web-api") include(":opendc-web:opendc-web-ui") include(":opendc-web:opendc-web-runner") include(":opendc-simulator:opendc-simulator-core") -include(":opendc-simulator:opendc-simulator-resources") +include(":opendc-simulator:opendc-simulator-flow") +include(":opendc-simulator:opendc-simulator-power") +include(":opendc-simulator:opendc-simulator-network") include(":opendc-simulator:opendc-simulator-compute") -include(":opendc-simulator:opendc-simulator-failures") include(":opendc-telemetry:opendc-telemetry-api") include(":opendc-telemetry:opendc-telemetry-sdk") +include(":opendc-telemetry:opendc-telemetry-compute") +include(":opendc-trace:opendc-trace-api") +include(":opendc-trace:opendc-trace-gwf") +include(":opendc-trace:opendc-trace-swf") +include(":opendc-trace:opendc-trace-wtf") +include(":opendc-trace:opendc-trace-wfformat") +include(":opendc-trace:opendc-trace-bitbrains") +include(":opendc-trace:opendc-trace-azure") +include(":opendc-trace:opendc-trace-opendc") +include(":opendc-trace:opendc-trace-parquet") +include(":opendc-trace:opendc-trace-tools") include(":opendc-harness:opendc-harness-api") include(":opendc-harness:opendc-harness-engine") include(":opendc-harness:opendc-harness-cli") diff --git a/traces/bitbrains-small/meta.parquet b/traces/bitbrains-small/meta.parquet Binary files differindex 43f51cb8..da6e5330 100644 --- a/traces/bitbrains-small/meta.parquet +++ b/traces/bitbrains-small/meta.parquet diff --git a/traces/bitbrains-small/trace.parquet b/traces/bitbrains-small/trace.parquet Binary files differindex f4dd5a50..fe0a254c 100644 --- a/traces/bitbrains-small/trace.parquet +++ b/traces/bitbrains-small/trace.parquet |
